github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/service.go (about) 1 package goa 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "net" 8 "net/http" 9 "net/url" 10 "os" 11 "path/filepath" 12 "sort" 13 "strings" 14 15 "golang.org/x/net/context" 16 ) 17 18 type ( 19 // Service is the data structure supporting goa services. 20 // It provides methods for configuring a service and running it. 21 // At the basic level a service consists of a set of controllers, each implementing a given 22 // resource actions. goagen generates global functions - one per resource - that make it 23 // possible to mount the corresponding controller onto a service. A service contains the 24 // middleware, not found handler, encoders and muxes shared by all its controllers. 25 Service struct { 26 // Name of service used for logging, tracing etc. 27 Name string 28 // Mux is the service request mux 29 Mux ServeMux 30 // Context is the root context from which all request contexts are derived. 31 // Set values in the root context prior to starting the server to make these values 32 // available to all request handlers. 33 Context context.Context 34 // Request body decoder 35 Decoder *HTTPDecoder 36 // Response body encoder 37 Encoder *HTTPEncoder 38 39 middleware []Middleware // Middleware chain 40 cancel context.CancelFunc // Service context cancel signal trigger 41 } 42 43 // Controller defines the common fields and behavior of generated controllers. 44 Controller struct { 45 // Controller resource name 46 Name string 47 // Service that exposes the controller 48 Service *Service 49 // Controller root context 50 Context context.Context 51 // MaxRequestBodyLength is the maximum length read from request bodies. 52 // Set to 0 to remove the limit altogether. Defaults to 1GB. 53 MaxRequestBodyLength int64 54 55 middleware []Middleware // Controller specific middleware if any 56 } 57 58 // FileServer is the interface implemented by controllers that can serve static files. 59 FileServer interface { 60 // FileHandler returns a handler that serves files under the given request path. 61 FileHandler(path, filename string) Handler 62 } 63 64 // Handler defines the request handler signatures. 65 Handler func(context.Context, http.ResponseWriter, *http.Request) error 66 67 // Unmarshaler defines the request payload unmarshaler signatures. 68 Unmarshaler func(context.Context, *Service, *http.Request) error 69 70 // DecodeFunc is the function that initialize the unmarshaled payload from the request body. 71 DecodeFunc func(context.Context, io.ReadCloser, interface{}) error 72 ) 73 74 // New instantiates a service with the given name. 75 func New(name string) *Service { 76 var ( 77 stdlog = log.New(os.Stderr, "", log.LstdFlags) 78 ctx = WithLogger(context.Background(), NewLogger(stdlog)) 79 cctx, cancel = context.WithCancel(ctx) 80 mux = NewMux() 81 service = &Service{ 82 Name: name, 83 Context: cctx, 84 Mux: mux, 85 Decoder: NewHTTPDecoder(), 86 Encoder: NewHTTPEncoder(), 87 88 cancel: cancel, 89 } 90 notFoundHandler Handler 91 ) 92 93 // Setup default NotFound handler 94 mux.HandleNotFound(func(rw http.ResponseWriter, req *http.Request, params url.Values) { 95 if resp := ContextResponse(ctx); resp != nil && resp.Written() { 96 return 97 } 98 // Use closure to do lazy computation of middleware chain so all middlewares are 99 // registered. 100 if notFoundHandler == nil { 101 notFoundHandler = func(_ context.Context, _ http.ResponseWriter, req *http.Request) error { 102 return ErrNotFound(req.URL.Path) 103 } 104 chain := service.middleware 105 ml := len(chain) 106 for i := range chain { 107 notFoundHandler = chain[ml-i-1](notFoundHandler) 108 } 109 } 110 ctx := NewContext(service.Context, rw, req, params) 111 err := notFoundHandler(ctx, ContextResponse(ctx), req) 112 if !ContextResponse(ctx).Written() { 113 service.Send(ctx, 404, err) 114 } 115 }) 116 117 return service 118 } 119 120 // CancelAll sends a cancel signals to all request handlers via the context. 121 // See https://godoc.org/golang.org/x/net/context for details on how to handle the signal. 122 func (service *Service) CancelAll() { 123 service.cancel() 124 } 125 126 // Use adds a middleware to the service wide middleware chain. 127 // goa comes with a set of commonly used middleware, see the middleware package. 128 // Controller specific middleware should be mounted using the Controller struct Use method instead. 129 func (service *Service) Use(m Middleware) { 130 service.middleware = append(service.middleware, m) 131 } 132 133 // WithLogger sets the logger used internally by the service and by Log. 134 func (service *Service) WithLogger(logger LogAdapter) { 135 service.Context = WithLogger(service.Context, logger) 136 } 137 138 // LogInfo logs the message and values at odd indeces using the keys at even indeces of the keyvals slice. 139 func (service *Service) LogInfo(msg string, keyvals ...interface{}) { 140 LogInfo(service.Context, msg, keyvals...) 141 } 142 143 // LogError logs the error and values at odd indeces using the keys at even indeces of the keyvals slice. 144 func (service *Service) LogError(msg string, keyvals ...interface{}) { 145 LogError(service.Context, msg, keyvals...) 146 } 147 148 // ListenAndServe starts a HTTP server and sets up a listener on the given host/port. 149 func (service *Service) ListenAndServe(addr string) error { 150 service.LogInfo("listen", "transport", "http", "addr", addr) 151 return http.ListenAndServe(addr, service.Mux) 152 } 153 154 // ListenAndServeTLS starts a HTTPS server and sets up a listener on the given host/port. 155 func (service *Service) ListenAndServeTLS(addr, certFile, keyFile string) error { 156 service.LogInfo("listen", "transport", "https", "addr", addr) 157 return http.ListenAndServeTLS(addr, certFile, keyFile, service.Mux) 158 } 159 160 // Serve accepts incoming HTTP connections on the listener l, invoking the service mux handler for each. 161 func (service *Service) Serve(l net.Listener) error { 162 if err := http.Serve(l, service.Mux); err != nil { 163 return err 164 } 165 return nil 166 } 167 168 // NewController returns a controller for the given resource. This method is mainly intended for 169 // use by the generated code. User code shouldn't have to call it directly. 170 func (service *Service) NewController(name string) *Controller { 171 return &Controller{ 172 Name: name, 173 Service: service, 174 Context: context.WithValue(service.Context, ctrlKey, name), 175 MaxRequestBodyLength: 1073741824, // 1 GB 176 } 177 } 178 179 // Send serializes the given body matching the request Accept header against the service 180 // encoders. It uses the default service encoder if no match is found. 181 func (service *Service) Send(ctx context.Context, code int, body interface{}) error { 182 r := ContextResponse(ctx) 183 if r == nil { 184 return fmt.Errorf("no response data in context") 185 } 186 r.WriteHeader(code) 187 return service.EncodeResponse(ctx, body) 188 } 189 190 // ServeFiles create a "FileServer" controller and calls ServerFiles on it. 191 func (service *Service) ServeFiles(path, filename string) error { 192 ctrl := service.NewController("FileServer") 193 return ctrl.ServeFiles(path, filename) 194 } 195 196 // DecodeRequest uses the HTTP decoder to unmarshal the request body into the provided value based 197 // on the request Content-Type header. 198 func (service *Service) DecodeRequest(req *http.Request, v interface{}) error { 199 body, contentType := req.Body, req.Header.Get("Content-Type") 200 defer body.Close() 201 202 if err := service.Decoder.Decode(v, body, contentType); err != nil { 203 return fmt.Errorf("failed to decode request body with content type %#v: %s", contentType, err) 204 } 205 206 return nil 207 } 208 209 // EncodeResponse uses the HTTP encoder to marshal and write the response body based on the request 210 // Accept header. 211 func (service *Service) EncodeResponse(ctx context.Context, v interface{}) error { 212 accept := ContextRequest(ctx).Header.Get("Accept") 213 return service.Encoder.Encode(v, ContextResponse(ctx), accept) 214 } 215 216 // ServeFiles replies to the request with the contents of the named file or directory. See 217 // FileHandler for details. 218 func (ctrl *Controller) ServeFiles(path, filename string) error { 219 if strings.Contains(path, ":") { 220 return fmt.Errorf("path may only include wildcards that match the entire end of the URL (e.g. *filepath)") 221 } 222 LogInfo(ctrl.Context, "mount file", "name", filename, "route", fmt.Sprintf("GET %s", path)) 223 handler := func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 224 if !ContextResponse(ctx).Written() { 225 return ctrl.FileHandler(path, filename)(ctx, rw, req) 226 } 227 return nil 228 } 229 ctrl.Service.Mux.Handle("GET", path, ctrl.MuxHandler("serve", handler, nil)) 230 return nil 231 } 232 233 // Use adds a middleware to the controller. 234 // Service-wide middleware should be added via the Service Use method instead. 235 func (ctrl *Controller) Use(m Middleware) { 236 ctrl.middleware = append(ctrl.middleware, m) 237 } 238 239 // MuxHandler wraps a request handler into a MuxHandler. The MuxHandler initializes the request 240 // context by loading the request state, invokes the handler and in case of error invokes the 241 // controller (if there is one) or Service error handler. 242 // This function is intended for the controller generated code. User code should not need to call 243 // it directly. 244 func (ctrl *Controller) MuxHandler(name string, hdlr Handler, unm Unmarshaler) MuxHandler { 245 // Use closure to enable late computation of handlers to ensure all middleware has been 246 // registered. 247 var handler Handler 248 249 return func(rw http.ResponseWriter, req *http.Request, params url.Values) { 250 // Build handler middleware chains on first invocation 251 if handler == nil { 252 handler = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 253 if !ContextResponse(ctx).Written() { 254 return hdlr(ctx, rw, req) 255 } 256 return nil 257 } 258 chain := append(ctrl.Service.middleware, ctrl.middleware...) 259 ml := len(chain) 260 for i := range chain { 261 handler = chain[ml-i-1](handler) 262 } 263 } 264 265 // Build context 266 ctx := NewContext(WithAction(ctrl.Context, name), rw, req, params) 267 268 // Protect against request bodies with unreasonable length 269 if ctrl.MaxRequestBodyLength > 0 { 270 req.Body = http.MaxBytesReader(rw, req.Body, ctrl.MaxRequestBodyLength) 271 } 272 273 // Load body if any 274 if req.ContentLength > 0 && unm != nil { 275 if err := unm(ctx, ctrl.Service, req); err != nil { 276 if err.Error() == "http: request body too large" { 277 msg := fmt.Sprintf("request body length exceeds %d bytes", ctrl.MaxRequestBodyLength) 278 err = ErrRequestBodyTooLarge(msg) 279 } else { 280 err = ErrBadRequest(err) 281 } 282 ctx = WithError(ctx, err) 283 } 284 } 285 286 // Invoke handler 287 if err := handler(ctx, ContextResponse(ctx), req); err != nil { 288 LogError(ctx, "uncaught error", "err", err) 289 respBody := fmt.Sprintf("Internal error: %s", err) // Sprintf catches panics 290 ctrl.Service.Send(ctx, 500, respBody) 291 } 292 } 293 } 294 295 // FileHandler returns a handler that serves files under the given filename for the given route path. 296 // The logic for what to do when the filename points to a file vs. a directory is the same as the 297 // standard http package ServeFile function. The path may end with a wildcard that matches the rest 298 // of the URL (e.g. *filepath). If it does the matching path is appended to filename to form the 299 // full file path, so: 300 // 301 // c.FileHandler("/index.html", "/www/data/index.html") 302 // 303 // Returns the content of the file "/www/data/index.html" when requests are sent to "/index.html" 304 // and: 305 // 306 // c.FileHandler("/assets/*filepath", "/www/data/assets") 307 // 308 // returns the content of the file "/www/data/assets/x/y/z" when requests are sent to 309 // "/assets/x/y/z". 310 func (ctrl *Controller) FileHandler(path, filename string) Handler { 311 var wc string 312 if idx := strings.LastIndex(path, "/*"); idx > -1 && idx < len(path)-1 { 313 wc = path[idx+2:] 314 if strings.Contains(wc, "/") { 315 wc = "" 316 } 317 } 318 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 319 fname := filename 320 if len(wc) > 0 { 321 if m, ok := ContextRequest(ctx).Params[wc]; ok { 322 fname = filepath.Join(filename, m[0]) 323 } 324 } 325 LogInfo(ctx, "serve file", "name", fname, "route", req.URL.Path) 326 dir, name := filepath.Split(fname) 327 fs := http.Dir(dir) 328 f, err := fs.Open(name) 329 if err != nil { 330 return ErrInvalidFile(err) 331 } 332 defer f.Close() 333 d, err := f.Stat() 334 if err != nil { 335 return ErrInvalidFile(err) 336 } 337 // use contents of index.html for directory, if present 338 if d.IsDir() { 339 index := strings.TrimSuffix(name, "/") + "/index.html" 340 ff, err := fs.Open(index) 341 if err == nil { 342 defer ff.Close() 343 dd, err := ff.Stat() 344 if err == nil { 345 name = index 346 d = dd 347 f = ff 348 } 349 } 350 } 351 352 // serveContent will check modification time 353 // Still a directory? (we didn't find an index.html file) 354 if d.IsDir() { 355 return dirList(rw, f) 356 } 357 http.ServeContent(rw, req, d.Name(), d.ModTime(), f) 358 return nil 359 } 360 } 361 362 var replacer = strings.NewReplacer( 363 "&", "&", 364 "<", "<", 365 ">", ">", 366 // """ is shorter than """. 367 `"`, """, 368 // "'" is shorter than "'" and apos was not in HTML until HTML5. 369 "'", "'", 370 ) 371 372 func dirList(w http.ResponseWriter, f http.File) error { 373 dirs, err := f.Readdir(-1) 374 if err != nil { 375 return err 376 } 377 sort.Sort(byName(dirs)) 378 379 w.Header().Set("Content-Type", "text/html; charset=utf-8") 380 fmt.Fprintf(w, "<pre>\n") 381 for _, d := range dirs { 382 name := d.Name() 383 if d.IsDir() { 384 name += "/" 385 } 386 // name may contain '?' or '#', which must be escaped to remain 387 // part of the URL path, and not indicate the start of a query 388 // string or fragment. 389 url := url.URL{Path: name} 390 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), replacer.Replace(name)) 391 } 392 fmt.Fprintf(w, "</pre>\n") 393 return nil 394 } 395 396 type byName []os.FileInfo 397 398 func (s byName) Len() int { return len(s) } 399 func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() } 400 func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }