我正在用 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)
})
}
}
任何解决此问题的见解或建议将不胜感激!
出现此问题的原因是,当您从
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))
说明:
消耗主体:
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 服务器的所有部分都能正确运行而不会互相干扰。