github.com/goldeneggg/goa@v1.3.1/service.go (about)

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