假设一个带有几个处理函数的Go程序,如下所示:
type FooRequest struct {
FooField string `json:"foofield"`
// ...
}
type FooResponse struct {
BarField string `json:"barfield"`
// ...
}
func handleFoo(w http.ResponseWriter, r *http.Request) {
var req FooRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// do what actually matters:
foo := DoStuff(req)
baz, err := DoSomething(foo)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp := DoEvenMoreStuff(baz)
// back to boiler plate:
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
如何重构此代码以避免JSON解码/编码样板?
我可能希望看到一个通用的"处理JSON" func和另一个处理实际foo的函数:
func handleJson(w http.ResponseWriter, r *http.Request) {
var req FooRequest // what about this line?
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp, err := handleFooElegantly(req)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(resp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func handleFoo(req FooRequest) (FooResponse, error) {
var resp FooResponse
foo := DoStuff(req)
baz, err := DoSomething(foo)
if err != nil {
return resp, err
}
resp = DoEvenMoreStuff(baz)
return resp, nil
}
这给我们留下了告诉JSON解码器它应该尝试解码的类型的问题。
实现这个的惯用Go方式是什么?
答案 0 :(得分:0)
您可以使用反射来消除样板。这是一个将func(指针)(指针,错误)调整为处理JSON编码和解码的http.Handler的示例。
type jsonHandler struct {
fn interface{}
}
func (jh jsonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// allocate the input argument
v := reflect.ValueOf(jh.fn)
arg := reflect.New(v.Type().In(0).Elem())
// decode to the argument
if err := json.NewDecoder(r.Body).Decode(arg.Interface()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// invoke the function
out := v.Call([]reflect.Value{arg})
// check error
if err, ok := out[1].Interface().(error); ok && err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// decode result
if err := json.NewEncoder(w).Encode(out[0].Interface()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
以下是如何使用此适配器的示例:
type Input struct {
A, B int
}
type Output struct {
Result int
}
func add(in *Input) (*Output, error) {
return &Output{Result: in.A + in.B}, nil
}
handler := jsonHandler{add} // handler satisfies http.Handler
如果函数没有单个指针参数并返回指针和错误,代码将会发生混乱。更强大和完整的实现应该在返回处理程序之前检查函数是否满足这些约束。
func newHandler(fn interface{)) (http.Handler, error) {
// use reflect to check fn, return error
...
return jsonHandler{fn}, nil
}
在这个答案中使用反射有点类似to an approach used in the standard library。