// handleConsume is an HTTP request handler for `GET /topic/{topic}/messages` func (as *T) handleConsume(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() topic := mux.Vars(r)[paramTopic] group, err := getGroupParam(r) if err != nil { respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{err.Error()}) return } consMsg, err := as.consumer.Consume(group, topic) if err != nil { var status int switch err.(type) { case consumer.ErrRequestTimeout: status = http.StatusRequestTimeout case consumer.ErrBufferOverflow: status = 429 // StatusTooManyRequests default: status = http.StatusInternalServerError } respondWithJSON(w, status, errorHTTPResponse{err.Error()}) return } respondWithJSON(w, http.StatusOK, consumeHTTPResponse{ Key: consMsg.Key, Value: consMsg.Value, Partition: consMsg.Partition, Offset: consMsg.Offset, }) }
// handleProduce is an HTTP request handler for `POST /topic/{topic}/messages` func (as *T) handleProduce(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() topic := mux.Vars(r)[paramTopic] key := getParamBytes(r, paramKey) _, isSync := r.Form[paramSync] // Get the message body from the HTTP request. if _, ok := r.Header[headerContentLength]; !ok { errorText := fmt.Sprintf("Missing %s header", headerContentLength) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{errorText}) return } messageSizeStr := r.Header.Get(headerContentLength) messageSize, err := strconv.Atoi(messageSizeStr) if err != nil { errorText := fmt.Sprintf("Invalid %s header: %s", headerContentLength, messageSizeStr) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{errorText}) return } message, err := ioutil.ReadAll(r.Body) if err != nil { errorText := fmt.Sprintf("Failed to read a message: err=(%s)", err) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{errorText}) return } if len(message) != messageSize { errorText := fmt.Sprintf("Message size does not match %s: expected=%v, actual=%v", headerContentLength, messageSize, len(message)) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{errorText}) return } // Asynchronously submit the message to the Kafka cluster. if !isSync { as.producer.AsyncProduce(topic, toEncoderPreservingNil(key), sarama.StringEncoder(message)) respondWithJSON(w, http.StatusOK, EmptyResponse) return } prodMsg, err := as.producer.Produce(topic, toEncoderPreservingNil(key), sarama.StringEncoder(message)) if err != nil { var status int switch err { case sarama.ErrUnknownTopicOrPartition: status = http.StatusNotFound default: status = http.StatusInternalServerError } respondWithJSON(w, status, errorHTTPResponse{err.Error()}) return } respondWithJSON(w, http.StatusOK, produceHTTPResponse{ Partition: prodMsg.Partition, Offset: prodMsg.Offset, }) }
// handleGetTopicConsumers is an HTTP request handler for `GET /topic/{topic}/consumers` func (as *T) handleGetTopicConsumers(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() var err error topic := mux.Vars(r)[paramTopic] group := "" r.ParseForm() groups := r.Form[paramGroup] if len(groups) > 1 { err = fmt.Errorf("One consumer group is expected, but %d provided", len(groups)) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{err.Error()}) return } if len(groups) == 1 { group = groups[0] } var consumers map[string]map[string][]int32 if group == "" { consumers, err = as.admin.GetAllTopicConsumers(topic) if err != nil { respondWithJSON(w, http.StatusInternalServerError, errorHTTPResponse{err.Error()}) return } } else { groupConsumers, err := as.admin.GetTopicConsumers(group, topic) if err != nil { if _, ok := err.(admin.ErrInvalidParam); ok { respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{err.Error()}) return } respondWithJSON(w, http.StatusInternalServerError, errorHTTPResponse{err.Error()}) return } consumers = make(map[string]map[string][]int32) if len(groupConsumers) != 0 { consumers[group] = groupConsumers } } encodedRes, err := json.MarshalIndent(consumers, "", " ") if err != nil { log.Errorf("Failed to send HTTP reponse: status=%d, body=%v, reason=%v", http.StatusOK, encodedRes, err) http.Error(w, err.Error(), http.StatusInternalServerError) return } encodedRes = prettyfmt.CollapseJSON(encodedRes) w.Header().Add(headerContentType, "application/json") w.WriteHeader(http.StatusOK) if _, err := w.Write(encodedRes); err != nil { log.Errorf("Failed to send HTTP reponse: status=%d, body=%v, reason=%v", http.StatusOK, encodedRes, err) } }
// handleGetOffsets is an HTTP request handler for `POST /topic/{topic}/offsets` func (as *T) handleSetOffsets(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() topic := mux.Vars(r)[paramTopic] group, err := getGroupParam(r) if err != nil { respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{err.Error()}) return } body, err := ioutil.ReadAll(r.Body) if err != nil { errorText := fmt.Sprintf("Failed to read the request: err=(%s)", err) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{errorText}) return } var partitionOffsetViews []partitionOffsetView if err := json.Unmarshal(body, &partitionOffsetViews); err != nil { errorText := fmt.Sprintf("Failed to parse the request: err=(%s)", err) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{errorText}) return } partitionOffsets := make([]admin.PartitionOffset, len(partitionOffsetViews)) for i, pov := range partitionOffsetViews { partitionOffsets[i].Partition = pov.Partition partitionOffsets[i].Offset = pov.Offset partitionOffsets[i].Metadata = pov.Metadata } err = as.admin.SetGroupOffsets(group, topic, partitionOffsets) if err != nil { if err, ok := err.(admin.ErrQuery); ok && err.Cause() == sarama.ErrUnknownTopicOrPartition { respondWithJSON(w, http.StatusNotFound, errorHTTPResponse{"Unknown topic"}) return } respondWithJSON(w, http.StatusInternalServerError, errorHTTPResponse{err.Error()}) return } respondWithJSON(w, http.StatusOK, EmptyResponse) }
// handleGetTopicConsumers is an HTTP request handler for `GET /topic/{topic}/consumers` func (as *HTTPAPIServer) handleGetTopicConsumers(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() var err error topic := mux.Vars(r)[ParamTopic] group := "" r.ParseForm() groups := r.Form[ParamGroup] if len(groups) > 1 { err = fmt.Errorf("One consumer group is expected, but %d provided", len(groups)) respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{err.Error()}) return } if len(groups) == 1 { group = groups[0] } var consumers map[string]map[string][]int32 if group == "" { consumers, err = as.admin.GetAllTopicConsumers(topic) if err != nil { respondWithJSON(w, http.StatusInternalServerError, errorHTTPResponse{err.Error()}) return } } else { groupConsumers, err := as.admin.GetTopicConsumers(group, topic) if err != nil { if _, ok := err.(ErrAdminInvalidParam); ok { respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{err.Error()}) return } respondWithJSON(w, http.StatusInternalServerError, errorHTTPResponse{err.Error()}) return } consumers = make(map[string]map[string][]int32) if len(groupConsumers) != 0 { consumers[group] = groupConsumers } } respondWithJSON(w, http.StatusOK, consumers) }
// handleGetOffsets is an HTTP request handler for `GET /topic/{topic}/offsets` func (as *T) handleGetOffsets(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() topic := mux.Vars(r)[paramTopic] group, err := getGroupParam(r) if err != nil { respondWithJSON(w, http.StatusBadRequest, errorHTTPResponse{err.Error()}) return } partitionOffsets, err := as.admin.GetGroupOffsets(group, topic) if err != nil { if err, ok := err.(admin.ErrQuery); ok && err.Cause() == sarama.ErrUnknownTopicOrPartition { respondWithJSON(w, http.StatusNotFound, errorHTTPResponse{"Unknown topic"}) return } respondWithJSON(w, http.StatusInternalServerError, errorHTTPResponse{err.Error()}) return } partitionOffsetView := make([]partitionOffsetView, len(partitionOffsets)) for i, po := range partitionOffsets { partitionOffsetView[i].Partition = po.Partition partitionOffsetView[i].Begin = po.Begin partitionOffsetView[i].End = po.End partitionOffsetView[i].Count = po.End - po.Begin partitionOffsetView[i].Offset = po.Offset if po.Offset == sarama.OffsetNewest { partitionOffsetView[i].Lag = 0 } else if po.Offset == sarama.OffsetOldest { partitionOffsetView[i].Lag = po.End - po.Begin } else { partitionOffsetView[i].Lag = po.End - po.Offset } partitionOffsetView[i].Metadata = po.Metadata } respondWithJSON(w, http.StatusOK, partitionOffsetView) }