github.com/nsqio/nsq@v1.3.0/internal/http_api/api_response.go (about) 1 package http_api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 "time" 9 10 "github.com/julienschmidt/httprouter" 11 "github.com/nsqio/nsq/internal/lg" 12 ) 13 14 type Decorator func(APIHandler) APIHandler 15 16 type APIHandler func(http.ResponseWriter, *http.Request, httprouter.Params) (interface{}, error) 17 18 type Err struct { 19 Code int 20 Text string 21 } 22 23 func (e Err) Error() string { 24 return e.Text 25 } 26 27 func PlainText(f APIHandler) APIHandler { 28 return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 29 code := 200 30 data, err := f(w, req, ps) 31 if err != nil { 32 code = err.(Err).Code 33 data = err.Error() 34 } 35 switch d := data.(type) { 36 case string: 37 w.WriteHeader(code) 38 io.WriteString(w, d) 39 case []byte: 40 w.WriteHeader(code) 41 w.Write(d) 42 default: 43 panic(fmt.Sprintf("unknown response type %T", data)) 44 } 45 return nil, nil 46 } 47 } 48 49 func V1(f APIHandler) APIHandler { 50 return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 51 data, err := f(w, req, ps) 52 if err != nil { 53 RespondV1(w, err.(Err).Code, err) 54 return nil, nil 55 } 56 RespondV1(w, 200, data) 57 return nil, nil 58 } 59 } 60 61 func RespondV1(w http.ResponseWriter, code int, data interface{}) { 62 var response []byte 63 var err error 64 var isJSON bool 65 66 if code == 200 { 67 switch data := data.(type) { 68 case string: 69 response = []byte(data) 70 case []byte: 71 response = data 72 case nil: 73 response = []byte{} 74 default: 75 isJSON = true 76 response, err = json.Marshal(data) 77 if err != nil { 78 code = 500 79 data = err 80 } 81 } 82 } 83 84 if code != 200 { 85 isJSON = true 86 response, _ = json.Marshal(struct { 87 Message string `json:"message"` 88 }{fmt.Sprintf("%s", data)}) 89 } 90 91 if isJSON { 92 w.Header().Set("Content-Type", "application/json; charset=utf-8") 93 } 94 w.Header().Set("X-NSQ-Content-Type", "nsq; version=1.0") 95 w.WriteHeader(code) 96 w.Write(response) 97 } 98 99 func Decorate(f APIHandler, ds ...Decorator) httprouter.Handle { 100 decorated := f 101 for _, decorate := range ds { 102 decorated = decorate(decorated) 103 } 104 return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 105 decorated(w, req, ps) 106 } 107 } 108 109 func Log(logf lg.AppLogFunc) Decorator { 110 return func(f APIHandler) APIHandler { 111 return func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 112 start := time.Now() 113 response, err := f(w, req, ps) 114 elapsed := time.Since(start) 115 status := 200 116 if e, ok := err.(Err); ok { 117 status = e.Code 118 } 119 logf(lg.INFO, "%d %s %s (%s) %s", 120 status, req.Method, req.URL.RequestURI(), req.RemoteAddr, elapsed) 121 return response, err 122 } 123 } 124 } 125 126 func LogPanicHandler(logf lg.AppLogFunc) func(w http.ResponseWriter, req *http.Request, p interface{}) { 127 return func(w http.ResponseWriter, req *http.Request, p interface{}) { 128 logf(lg.ERROR, "panic in HTTP handler - %s", p) 129 Decorate(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 130 return nil, Err{500, "INTERNAL_ERROR"} 131 }, Log(logf), V1)(w, req, nil) 132 } 133 } 134 135 func LogNotFoundHandler(logf lg.AppLogFunc) http.Handler { 136 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 137 Decorate(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 138 return nil, Err{404, "NOT_FOUND"} 139 }, Log(logf), V1)(w, req, nil) 140 }) 141 } 142 143 func LogMethodNotAllowedHandler(logf lg.AppLogFunc) http.Handler { 144 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 145 Decorate(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (interface{}, error) { 146 return nil, Err{405, "METHOD_NOT_ALLOWED"} 147 }, Log(logf), V1)(w, req, nil) 148 }) 149 }