// JSONMiddleware provides a JSONContextEndpoint hook wrapped around all requests. // In this implementation, we're using it to provide application logging and to check errors // and provide generic responses. func (s *RPCService) JSONMiddleware(j server.JSONContextEndpoint) server.JSONContextEndpoint { return func(ctx context.Context, r *http.Request) (int, interface{}, error) { status, res, err := j(ctx, r) if err != nil { server.LogWithFields(r).WithFields(logrus.Fields{ "error": err, }).Error("problems with serving request") return http.StatusServiceUnavailable, nil, &jsonErr{"sorry, this service is unavailable"} } server.LogWithFields(r).Info("success!") return status, res, nil } }
func (s *SimpleService) GetCats(w http.ResponseWriter, r *http.Request) { res, err := s.client.SemanticConceptSearch("des", "cats") if err != nil { server.LogWithFields(r).WithFields(logrus.Fields{ "error": err, }).Error("unable to perform semantic search") http.Error(w, "unable to perform cat search", http.StatusServiceUnavailable) return } w.Header().Add("Content-Type", "text/html; charset=utf-8") err = catsTemplate.Execute(w, &catList{res}) if err != nil { server.LogWithFields(r).WithFields(logrus.Fields{ "error": err, }).Error("unable to execute cats template") http.Error(w, "unable to perform cat search", http.StatusServiceUnavailable) } }
// Put is a JSONEndpoint for adding a new saved item to a user's list. func (s *SavedItemsService) Put(r *http.Request) (int, interface{}, error) { // gather the inputs from the request id := context.Get(r, userIDKey).(uint64) url := r.URL.Query().Get("url") // do work and respond err := s.repo.Put(id, url) if err != nil { return http.StatusInternalServerError, nil, err } server.LogWithFields(r).Info("successfully saved item") return http.StatusCreated, jsonResponse{"successfully saved item"}, nil }
// CreateStream is a JSON endpoint for creating a new topic in Kafka. func (s *StreamService) CreateStream(r *http.Request) (int, interface{}, error) { id := time.Now().Unix() topic := topicName(id) err := createTopic(topic) if err != nil { return http.StatusInternalServerError, nil, jsonErr{err} } server.LogWithFields(r).WithField("topic", topic).Info("created new topic") return http.StatusOK, struct { Status string `json:"status"` StreamID int64 `json:"stream_id"` }{"success!", id}, nil }
// JSONMiddleware provides a hook to add service-wide middleware for how JSONEndpoints // should behave. In this example, we’re using the hook to check for a header to // identify and authorize the user. This method helps satisfy the server.JSONService interface. func (s *SavedItemsService) JSONMiddleware(j server.JSONEndpoint) server.JSONEndpoint { return func(r *http.Request) (code int, res interface{}, err error) { // wrap our endpoint with an auth check j = authCheck(j) // call the endpoint code, res, err = j(r) // if the endpoint returns an unexpected error, return a generic message // and log it. if err != nil && code != http.StatusUnauthorized { // LogWithFields will add all the request context values // to the structured log entry along some other request info server.LogWithFields(r).WithField("error", err).Error("unexpected service error") return http.StatusServiceUnavailable, nil, ServiceUnavailableErr } return code, res, err } }
// Stream will init a new pubsub.KafkaPublisher and pubsub.KafkaSubscriber // then upgrade the current request to a websocket connection. Any messages // consumed from Kafka will be published to the web socket and vice versa. func (s *StreamService) Stream(w http.ResponseWriter, r *http.Request) { cfg := *s.cfg cfg.Topic = topicName(web.GetInt64Var(r, "stream_id")) server.LogWithFields(r).WithField("topic", cfg.Topic).Info("new stream req") sub, err := pubsub.NewKafkaSubscriber(&cfg, zeroOffset, discardOffset) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to create sub") http.Error(w, "unable to create subscriber: "+err.Error(), http.StatusBadRequest) return } defer func() { if err := sub.Stop(); err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to stop sub") } }() var pub *pubsub.KafkaPublisher pub, err = pubsub.NewKafkaPublisher(&cfg) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to create pub") http.Error(w, "unable to create publisher: "+err.Error(), http.StatusBadRequest) return } defer func() { if err := pub.Stop(); err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to stop pub") } }() var ws *websocket.Conn ws, err = upgrader.Upgrade(w, r, nil) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to create websocket") http.Error(w, "unable to create websocket: "+err.Error(), http.StatusBadRequest) return } defer func() { if err := ws.Close(); err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to close ws") } }() // start consumer, emit to ws noopTicker := time.NewTicker(time.Second * 5) subscriberDone := make(chan bool, 1) stopSubscriber := make(chan bool, 1) go func() { defer func() { subscriberDone <- true }() var ( payload []byte msgs = sub.Start() ) for { select { case msg := <-msgs: payload = msg.Message() err = ws.SetWriteDeadline(time.Now().Add(time.Second * 30)) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to write deadline") } err = ws.WriteMessage(websocket.TextMessage, payload) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to write ws message") } err = msg.Done() if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to mark gizmo message as done") } case <-noopTicker.C: err = ws.SetWriteDeadline(time.Now().Add(time.Second * 30)) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to write deadline") } if err := ws.WriteMessage(websocket.PingMessage, []byte("ping")); err != nil { server.LogWithFields(r).WithField("error", err).Error("error writing ws message") return } case <-stopSubscriber: return } } }() // start producer, emit to kafka producerDone := make(chan bool, 1) go func() { defer func() { producerDone <- true stopSubscriber <- true server.LogWithFields(r).WithField("topic", cfg.Topic).Info("closing stream req") }() var ( messageType int payload []byte err error read io.Reader ) for { messageType, read, err = ws.NextReader() if err != nil { if err != io.EOF { server.LogWithFields(r).WithField("error", err).Error("error reading message") } return } switch messageType { case websocket.TextMessage: payload, err = ioutil.ReadAll(read) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to read payload") return } err = pub.PublishRaw(cfg.Topic, payload) if err != nil { server.LogWithFields(r).WithField("error", err).Error("unable to publish payload") return } case websocket.PingMessage, websocket.PongMessage, websocket.BinaryMessage: server.LogWithFields(r).Info("discarding message type: ", messageType) case websocket.CloseMessage: server.LogWithFields(r).Info("closing websocket") return } } }() <-subscriberDone <-producerDone noopTicker.Stop() server.LogWithFields(r).WithField("topic", cfg.Topic).Info("leaving stream req") }