Я разрабатываю промежуточное программное обеспечение на Go, которое перехватывает HTTP-запросы, считывает данные JSON из тела запроса и индексирует их как документ в Elasticsearch на основе этой документации.
Однако, хотя документ кажется проиндексированным в Elasticsearch, процесс возвращает ошибку Invalid JSON format: EOF
. Эта ошибка не позволяет промежуточному программному обеспечению перейти к основному обработчику, который выполняет дополнительные операции с базой данных.
Вот соответствующая часть моего кода промежуточного программного обеспечения:
package middlewares
import (
"encoding/json"
"net/http"
"github.com/elastic/go-elasticsearch/v8"
"github.com/elastic/go-elasticsearch/v8/typedapi/types"
"github.com/google/uuid"
)
// IndexDocumentMiddleware creates a middleware to index documents into Elasticsearch
func IndexDocumentMiddleware(es *elasticsearch.TypedClient) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Read and decode the request body into a generic map to determine the type of document
var doc map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&doc); err != nil {
http.Error(w, "Error parsing request body", http.StatusBadRequest)
return
}
var indexName string
if typeName, ok := doc["type"].(string); ok {
indexName = typeName
} else {
http.Error(w, "Error: 'type' is not a string or is missing", http.StatusBadRequest)
return
}
existsRes, err := es.Indices.Exists(indexName).Do(ctx)
if err != nil {
http.Error(w, "Error existsRes: "+err.Error(), http.StatusInternalServerError)
return
}
if !existsRes {
_, err := es.Indices.Create(indexName).Mappings(types.NewTypeMapping()).Do(ctx)
if err != nil {
http.Error(w, "Error creating index: "+err.Error(), http.StatusInternalServerError)
return
}
}
docID := uuid.New().String()
_, err = es.Index(indexName).
Id(docID).
Document(doc).Do(ctx)
if err != nil {
http.Error(w, "Error indexing document: "+err.Error(), http.StatusInternalServerError)
return
}
next.ServeHTTP(w, r)
})
}
}
Будем очень признательны за любые идеи или предложения по решению этой проблемы!
Большое спасибо, @Peter! Очень полезно! Я не знал, что чтение тела HTTP-запроса поглотит его, не оставив ничего для следующих обработчиков.
.Decode(&doc)
→ Декодирование делает тело пустымЭта проблема возникает потому, что когда вы читаете из http.Request.Body
, который является io.ReadCloser
, данные потребляются и недоступны для последующего чтения. По-видимому, это распространенная ошибка, особенно когда нескольким обработчикам или промежуточному программному обеспечению требуется доступ к телу запроса.
Чтобы исправить это, вам нужно сначала прочитать все тело, а затем восстановить его, чтобы его могли перечитать последующие обработчики или промежуточное программное обеспечение. Вот основной фрагмент кода, который выполняет это:
// Read the entire body
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
// Restore the io.ReadCloser to its original state
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
//code continues...
// Read and decode the request body into a generic map to determine the type of document
var doc map[string]interface{} //...
Объяснение:
Потребление тела: операция json.NewDecoder(r.Body).Decode(&doc)
считывает всё содержимое потока io.ReadCloser
. После прочтения поток становится пустым, поскольку io.ReadCloser
не поддерживает перемотку. Это эффективно «съедает» тело, делая его пустым для любых последующих попыток его чтения.
Восстановление тела: После прочтения r.Body
переназначается с помощью io.NopCloser(bytes.NewBuffer(bodyBytes))
. Эта строка создает новый io.ReadCloser
из bodyBytes
, который мы прочитали ранее, эффективно дублируя исходное содержимое. io.NopCloser
используется для преобразования io.Reader
(возвращенного bytes.NewBuffer
) в io.ReadCloser
без добавления каких-либо функций для закрытия, поскольку буфер не нужно закрывать.
Такой подход гарантирует, что любое промежуточное ПО или обработчик, который запускается после этого кода, по-прежнему будет иметь доступ ко всему телу запроса, как если бы оно было нетронутым.
Почему это важно:
Неправильная обработка тела запроса может привести к тонким ошибкам, особенно в более крупных приложениях, где нескольким частям цепочки промежуточного программного обеспечения может потребоваться проверить или изменить запрос. Этот метод гарантирует, что все части вашего HTTP-сервера смогут работать правильно, не мешая друг другу.
Вы читаете тело запроса в промежуточном программном обеспечении, не заменяя его впоследствии. Я предполагаю, что другой обработчик пытается прочитать его еще раз, но вы не можете прочитать тело более одного раза. Чтобы исправить это, замените тело запроса в промежуточном программном обеспечении свежей копией после его прочтения: используйте io.ReadAll и json.Unmarshal или io.TeeReader с bytes.Buffer в промежуточном программном обеспечении, затем используйте
io.NopCloser(bytes.NewReader(b))
илиio.NopCloser(buf)
для создания нового ReadCloser).