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  	"&", "&amp;",
   364  	"<", "&lt;",
   365  	">", "&gt;",
   366  	// "&#34;" is shorter than "&quot;".
   367  	`"`, "&#34;",
   368  	// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
   369  	"'", "&#39;",
   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] }