IO Pipeline 不算什么新鲜事儿,通过 io.Reader
io.Writer
等接口,把多个流处理连接一起,只需返回 Reader
, 直到调用 Read
函数时才读数据,高效节约内存。类比 Spark 流处理,transformation 时只是传递 RDD, 只有 Action 时才会触发数据计算
JSON Decoder 例子
举一个从 http 读取 json 数据的例子:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { request := new(Person) decoder := json.NewDecoder(r.Body) err := decoder.Decode(&request) if err != nil { http.Error(w, err) } ...... })
我们不需要 ioutil.ReadAll
全部 body 再调用 Unmarshal
, decoder
内置 buffer 流式解析即可。但是这个例子不完美,有很多问题
-
如果 client 传入的 json 有未识别的字段,服务端如何处理?
-
json.NewDecoder 会一直读 r.Body, 未做长度限制
-
没有检查
Content-Type
header, 只有 json 才允许 Decode -
错误处理不够好,error 需要转换,不能直接返回 client
func decodeJSONBody(w http.ResponseWriter, r *http.Request, dst interface{}) error { if r.Header.Get("Content-Type") != "" { value, _ := header.ParseValueAndParams(r.Header, "Content-Type") if value != "application/json" { msg := "Content-Type header is not application/json" return &malformedRequest{status: http.StatusUnsupportedMediaType, msg: msg} } } r.Body = http.MaxBytesReader(w, r.Body, 1048576) dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() err := dec.Decode(&dst) if err != nil { var syntaxError *json.SyntaxError var unmarshalTypeError *json.UnmarshalTypeError switch { case errors.As(err, &syntaxError): msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset) return &malformedRequest{status: http.StatusBadRequest, msg: msg} ...... } } err = dec.Decode(&struct{}{}) if err != io.EOF { msg := "Request body must only contain a single JSON object" return &malformedRequest{status: http.StatusBadRequest, msg: msg} } }
上面是改进后的版本,看着舒服多了,这还只是一个 reader 的实现。在 minio
中,经常有 N 多个 io.Reader
或者 io.Writer
组合在一起,实现 io pipeline, 稍复杂一些
Minio 下载数据
略去错误处理,只看 ge