github.com/jamescostian/go-swagger@v0.30.4-0.20221130163922-68364d6b567b/docs/generate/server.md (about)

     1  # Generate a server from a swagger spec
     2  
     3  The toolkit has a command that will let you generate a docker friendly server with support for TLS.
     4  You can configure it through environment variables that are commonly used on PaaS services.
     5  
     6  <!--more-->
     7  
     8  A generated server uses _no reflection_ except for an enum validation and the required validation. The server builds all the necessary plans and execution paths at startup time so that at runtime there is only the absolute minimum processing required to respond to requests.
     9  
    10  The default router for go-swagger is [naoina's denco](https://github.com/naoina/denco) which is a [**very** fast](https://github.com/julienschmidt/go-http-routing-benchmark#github) ternary search tree based router that allows for much greater flexibility than the trie based router implementation of julienschmidt at almost the same and sometimes lower cost.
    11  
    12  You can provide your own router implementation should you so desire it's abstracted through an interface with this use case in mind.
    13  
    14  ### Server usage
    15  
    16  ```
    17  Usage:
    18    swagger [OPTIONS] generate server [server-OPTIONS]
    19  
    20  generate all the files for a server application
    21  
    22  Application Options:
    23    -q, --quiet                                                                     silence logs
    24        --log-output=LOG-FILE                                                       redirect logs to file
    25  
    26  Help Options:
    27    -h, --help                                                                      Show this help message
    28  
    29  [server command options]
    30        -s, --server-package=                                                       the package to save the server specific code (default: restapi)
    31            --main-package=                                                         the location of the generated main. Defaults to cmd/{name}-server
    32        -P, --principal=                                                            the model to use for the security principal
    33            --default-scheme=                                                       the default scheme for this API (default: http)
    34            --principal-is-interface                                                the security principal provided is an interface, not a struct
    35            --default-produces=                                                     the default mime type that API operations produce (default: application/json)
    36            --default-consumes=                                                     the default mime type that API operations consume (default: application/json)
    37            --skip-models                                                           no models will be generated when this flag is specified
    38            --skip-operations                                                       no operations will be generated when this flag is specified
    39            --skip-support                                                          no supporting files will be generated when this flag is specified
    40            --exclude-main                                                          exclude main function, so just generate the library
    41            --exclude-spec                                                          don't embed the swagger specification
    42            --flag-strategy=[go-flags|pflag|flag]                                   the strategy to provide flags for the server (default: go-flags)
    43            --compatibility-mode=[modern|intermediate]                              the compatibility mode for the tls server (default: modern)
    44            --regenerate-configureapi                                               Force regeneration of configureapi.go
    45        -A, --name=                                                                 the name of the application, defaults to a mangled value of info.title
    46            --with-context                                                          handlers get a context as first arg (deprecated)
    47  
    48      Options common to all code generation commands:
    49        -f, --spec=                                                                 the spec file to use (default swagger.{json,yml,yaml})
    50        -t, --target=                                                               the base directory for generating the files (default: ./)
    51            --template=[stratoscale]                                                load contributed templates
    52        -T, --template-dir=                                                         alternative template override directory
    53        -C, --config-file=                                                          configuration file to use for overriding template options
    54        -r, --copyright-file=                                                       copyright file used to add copyright header
    55            --additional-initialism=                                                consecutive capitals that should be considered intialisms
    56            --allow-template-override                                               allows overriding protected templates
    57            --skip-validation                                                       skips validation of spec prior to generation
    58            --dump-data                                                             when present dumps the json for the template generator instead of generating files
    59            --with-expand                                                           expands all $ref's in spec prior to generation (shorthand to --with-flatten=expand)
    60            --with-flatten=[minimal|full|expand|verbose|noverbose|remove-unused]    flattens all $ref's in spec prior to generation (default: minimal, verbose)
    61  
    62      Options for model generation:
    63        -m, --model-package=                                                        the package to save the models (default: models)
    64        -M, --model=                                                                specify a model to include in generation, repeat for multiple (defaults to all)
    65            --existing-models=                                                      use pre-generated models e.g. github.com/foobar/model
    66            --strict-additional-properties                                          disallow extra properties when additionalProperties is set to false
    67            --keep-spec-order                                                       keep schema properties order identical to spec file
    68            --struct-tags                                                           specify custom struct tags for third-party libraries, repeat for multiple (defaults to json)
    69  
    70      Options for operation generation:
    71        -O, --operation=                                                            specify an operation to include, repeat for multiple (defaults to all)
    72            --tags=                                                                 the tags to include, if not specified defaults to all
    73        -a, --api-package=                                                          the package to save the operations (default: operations)
    74            --with-enum-ci                                                          set all enumerations case-insensitive by default
    75            --skip-tag-packages                                                     skips the generation of tag-based operation packages, resulting in a flat generation
    76  ```
    77  
    78  ### Build a server
    79  
    80  The server application gets generated with all the handlers stubbed out with a not implemented handler. That means that you can start the API server immediately after generating it. It will respond to all valid requests with 501 Not Implemented. When a request is invalid it will most likely respond with an appropriate 4xx response.
    81  
    82  The generated server allows for a number of command line parameters to customize it.
    83  
    84  ```
    85        --cleanup-timeout duration     grace period for which to wait before killing idle connections (default 10s)
    86        --graceful-timeout duration    grace period for which to wait before shutting down the server (default 15s)
    87        --host string                  the IP to listen on (default "localhost")
    88        --keep-alive duration          sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download) (default 3m0s)
    89        --listen-limit int             limit the number of outstanding requests
    90        --max-header-size byte-size    controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body (default 1MB)
    91        --port int                     the port to listen on for insecure connections, defaults to a random value
    92        --read-timeout duration        maximum duration before timing out read of the request (default 30s)
    93        --scheme strings               the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec (default [http,https,unix])
    94        --socket-path string           the unix socket to listen on (default "/var/run/todo-list.sock")
    95        --tls-ca string                the certificate authority certificate file to be used with mutual tls auth
    96        --tls-certificate string       the certificate file to use for secure connections
    97        --tls-host string              the IP to listen on (default "localhost")
    98        --tls-keep-alive duration      sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download) (default 3m0s)
    99        --tls-key string               the private key file to use for secure connections (without passphrase)
   100        --tls-listen-limit int         limit the number of outstanding requests
   101        --tls-port int                 the port to listen on for secure connections, defaults to a random value
   102        --tls-read-timeout duration    maximum duration before timing out read of the request (default 30s)
   103        --tls-write-timeout duration   maximum duration before timing out write of the response (default 30s)
   104        --write-timeout duration       maximum duration before timing out write of the response (default 30s)
   105  ```
   106  
   107  The server takes care of a number of things when a request arrives:
   108  
   109  * routing
   110  * authentication
   111  * input validation
   112  * content negotiation
   113  * parameter and body binding
   114  
   115  To illustrate this with a pseudo handler, this is what happens in a request.
   116  
   117  ```go
   118  import (
   119    "net/http"
   120  
   121    "github.com/go-openapi/errors"
   122    "github.com/go-openapi/runtime/middleware"
   123    "github.com/gorilla/context"
   124  )
   125  
   126  func newCompleteMiddleware(ctx *middleware.Context) http.Handler {
   127    return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
   128      defer context.Clear(r)
   129  
   130      // use context to lookup routes
   131      if matched, ok := ctx.RouteInfo(r); ok {
   132  
   133        if len(matched.Authenticators) > 0 {
   134          if _, err := ctx.Authorize(r, matched); err != nil {
   135            ctx.Respond(rw, r, matched.Produces, matched, err)
   136            return
   137          }
   138        }
   139  
   140        bound, validation := ctx.BindAndValidate(r, matched)
   141        if validation != nil {
   142          ctx.Respond(rw, r, matched.Produces, matched, validation)
   143          return
   144        }
   145  
   146        result, err := matched.Handler.Handle(bound)
   147        if err != nil {
   148          ctx.Respond(rw, r, matched.Produces, matched, err)
   149          return
   150        }
   151  
   152        ctx.Respond(rw, r, matched.Produces, matched, result)
   153        return
   154      }
   155  
   156      // Not found, check if it exists in the other methods first
   157      if others := ctx.AllowedMethods(r); len(others) > 0 {
   158        ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.MethodNotAllowed(r.Method, others))
   159        return
   160      }
   161      ctx.Respond(rw, r, ctx.spec.RequiredProduces(), nil, errors.NotFound("path %s was not found", r.URL.Path))
   162    })
   163  }
   164  ```
   165  
   166  Prior to handling requests however you probably want to configure the API with some actual implementations.  To do that you have to edit the configure_xxx.go file.  That file will only be generated the first time you generate a server application from a swagger spec. So the generated server uses this file to let you fill in the blanks.
   167  
   168  For the todolist application that file looks like:
   169  
   170  ```go
   171  package main
   172  
   173  import (
   174  	"github.com/go-openapi/errors"
   175  	"github.com/go-openapi/runtime"
   176  	"github.com/go-openapi/runtime/middleware"
   177  
   178  	"github.com/go-openapi/examples/todo-list/restapi/operations"
   179  	"github.com/go-openapi/examples/todo-list/restapi/operations/todos"
   180  )
   181  
   182  // This file is safe to edit. Once it exists it will not be overwritten
   183  
   184  func configureAPI(api *operations.ToDoListAPI) http.Handler {
   185  	// configure the api here
   186  	api.JSONConsumer = runtime.JSONConsumer()
   187  
   188  	api.JSONProducer = runtime.JSONProducer()
   189  
   190  	api.KeyAuth = func(token string) (interface{}, error) {
   191  		return nil, errors.NotImplemented("api key auth (key) x-petstore-token from header has not yet been implemented")
   192  	}
   193  
   194  	api.AddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams, principal interface{}) middleware.Responder {
   195  		return middleware.NotImplemented("operation addOne has not yet been implemented")
   196  	})
   197  	api.DestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams, principal interface{}) middleware.Responder {
   198  		return middleware.NotImplemented("operation destroyOne has not yet been implemented")
   199  	})
   200  	api.FindHandler = todos.FindHandlerFunc(func(params todos.FindParams, principal interface{}) middleware.Responder {
   201  		return middleware.NotImplemented("operation find has not yet been implemented")
   202  	})
   203  	api.UpdateOneHandler = todos.UpdateOneHandlerFunc(func(params todos.UpdateOneParams, principal interface{}) middleware.Responder {
   204  		return middleware.NotImplemented("operation updateOne has not yet been implemented")
   205  	})
   206  
   207  	return setupGlobalMiddleware(api.Serve(setupMiddlewares))
   208  }
   209  ```
   210  
   211  When you look at the code for the configureAPI method then you'll notice that the api object has properties for consumers.
   212  A consumer is an object that can marshal things from a wireformat to an object.  Consumers and their counterpart producers who write objects get their names generated from the consumes and produces properties on a swagger specification.
   213  
   214  Often, this will be JSON. If you want to use XML, additionally you have to enable XML compatible models when generating the server. For that, you have to set the command options `--default-consumes` or `--default-produces` to an XML mime type like `application/xml`. For more details on using XML, also see the [client generation](client.md).
   215  
   216  The interface definitions for consumers and producers look like this:
   217  
   218  ```go
   219  // ConsumerFunc represents a function that can be used as a consumer
   220  type ConsumerFunc func(io.Reader, interface{}) error
   221  
   222  // Consume consumes the reader into the data parameter
   223  func (fn ConsumerFunc) Consume(reader io.Reader, data interface{}) error {
   224  	return fn(reader, data)
   225  }
   226  
   227  // Consumer implementations know how to bind the values on the provided interface to
   228  // data provided by the request body
   229  type Consumer interface {
   230  	// Consume performs the binding of request values
   231  	Consume(io.Reader, interface{}) error
   232  }
   233  
   234  // ProducerFunc represents a function that can be used as a producer
   235  type ProducerFunc func(io.Writer, interface{}) error
   236  
   237  // Produce produces the response for the provided data
   238  func (f ProducerFunc) Produce(writer io.Writer, data interface{}) error {
   239  	return f(writer, data)
   240  }
   241  
   242  // Producer implementations know how to turn the provided interface into a valid
   243  // HTTP response
   244  type Producer interface {
   245  	// Produce writes to the http response
   246  	Produce(io.Writer, interface{}) error
   247  }
   248  ```
   249  
   250  So it's something that can turn a reader into a hydrated interface. A producer is the counterpart of a consumer and writes objects to an io.Writer.  When you configure an api with those you make sure it can marshal the types for the supported content types.
   251  
   252  Go swagger automatically provides consumers and producers for known media types. To register a new mapping for a media
   253  type or to override an existing mapping, call the corresponding API functions in your configure_xxx.go file:
   254  
   255  ```go
   256  func configureAPI(api *operations.ToDoListAPI) http.Handler {
   257  	// other setup code here...
   258  
   259  	api.RegisterConsumer("application/pkcs10", myCustomConsumer)
   260  	api.RegisterProducer("application/pkcs10", myCustomProducer)
   261  }
   262  
   263  ```
   264  
   265  The next thing that happens in the configureAPI method is setting up the authentication with a stub handler in this case. This particular swagger specification supports token based authentication and as such it wants you to configure a token auth handler.  Any error for an authentication handler is assumed to be an invalid authentication and will return the 401 status code.
   266  
   267  ```go
   268  // UserPassAuthentication authentication function
   269  type UserPassAuthentication func(string, string) (interface{}, error)
   270  
   271  // TokenAuthentication authentication function
   272  type TokenAuthentication func(string) (interface{}, error)
   273  
   274  // AuthenticatorFunc turns a function into an authenticator
   275  type AuthenticatorFunc func(interface{}) (bool, interface{}, error)
   276  
   277  // Authenticate authenticates the request with the provided data
   278  func (f AuthenticatorFunc) Authenticate(params interface{}) (bool, interface{}, error) {
   279  	return f(params)
   280  }
   281  
   282  // Authenticator represents an authentication strategy
   283  // implementations of Authenticator know how to authenticate the
   284  // request data and translate that into a valid principal object or an error
   285  type Authenticator interface {
   286  	Authenticate(interface{}) (bool, interface{}, error)
   287  }
   288  ```
   289  
   290  So we finally get to configuring our route handlers. For each operation there exists an interface so that implementations have some freedom to provide alternative implementations. For example mocks in certain tests, automatic stubbing handlers, not implemented handlers. Let's look at the addOne handler in a bit more detail.
   291  
   292  ```go
   293  // AddOneHandlerFunc turns a function with the right signature into a add one handler
   294  type AddOneHandlerFunc func(AddOneParams, interface{}) middleware.Responder
   295  
   296  // Handle executing the request and returning a response
   297  func (fn AddOneHandlerFunc) Handle(params AddOneParams, principal interface{}) middleware.Responder {
   298  	return fn(params, principal)
   299  }
   300  
   301  // AddOneHandler interface for that can handle valid add one params
   302  type AddOneHandler interface {
   303  	Handle(AddOneParams, interface{}) middleware.Responder
   304  }
   305  ```
   306  
   307  Because the `addOne` operation requires authentication, this interface definition requires 2 arguments. The first argument is about the request parameters and the second parameter is the security principal for the request.  In this case it is of type `interface{}`, typically that is a type like Account, User, Session, ...
   308  
   309  It is your job to provide such a handler. Go swagger guarantees that by the time the request processing ends up at the handler, the parameters and security principal have been bound and validated.  So you can safely proceed with saving the request body to some persistence medium perhaps.
   310  
   311  There is a context that gets created where the handlers get wired up into a `http.Handler`. For the add one this looks like this:
   312  
   313  ```go
   314  // NewAddOne creates a new http.Handler for the add one operation
   315  func NewAddOne(ctx *middleware.Context, handler AddOneHandler) *AddOne {
   316  	return &AddOne{Context: ctx, Handler: handler}
   317  }
   318  
   319  /*AddOne swagger:route POST / todos addOne
   320  
   321  AddOne add one API
   322  
   323  */
   324  type AddOne struct {
   325  	Context *middleware.Context
   326  	Params  AddOneParams
   327  	Handler AddOneHandler
   328  }
   329  
   330  func (o *AddOne) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
   331  	route, _ := o.Context.RouteInfo(r)
   332  
   333  	uprinc, err := o.Context.Authorize(r, route)
   334  	if err != nil {
   335  		o.Context.Respond(rw, r, route.Produces, route, err)
   336  		return
   337  	}
   338  	var principal interface{}
   339  	if uprinc != nil {
   340  		principal = uprinc
   341  	}
   342  
   343  	if err := o.Context.BindValidRequest(r, route, &o.Params); err != nil { // bind params
   344  		o.Context.Respond(rw, r, route.Produces, route, err)
   345  		return
   346  	}
   347  
   348  	res := o.Handler.Handle(o.Params, principal) // actually handle the request
   349  
   350  	o.Context.Respond(rw, r, route.Produces, route, res)
   351  
   352  }
   353  ```
   354  
   355  The `http.Handler` implementation takes care of authentication, binding, user code execution and generating a response. For authentication this request would end up in the `TokenAuthentication` handler that was put on the api context object earlier.  When a request is authenticated it gets bound. This operation eventually requires an object that is an implementation of `RequestBinder`.  The `AddOneParams` are such an implementation:
   356  
   357  ```go
   358  // RequestBinder is an interface for types to implement
   359  // when they want to be able to bind from a request
   360  type RequestBinder interface {
   361  	BindRequest(*http.Request, *MatchedRoute) error
   362  }
   363  
   364  // AddOneParams contains all the bound params for the add one operation
   365  // typically these are obtained from a http.Request
   366  //
   367  // swagger:parameters addOne
   368  type AddOneParams struct {
   369  	/*
   370  	  In: body
   371  	*/
   372  	Body *models.Item
   373  }
   374  
   375  // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
   376  // for simple values it will use straight method calls
   377  func (o *AddOneParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
   378  	var res []error
   379  
   380  	var body models.Item
   381  	if err := route.Consumer.Consume(r.Body, &body); err != nil {
   382  		res = append(res, errors.NewParseError("body", "body", "", err))
   383  	} else {
   384  		if err := body.Validate(route.Formats); err != nil {
   385  			res = append(res, err)
   386  		}
   387  
   388  		if len(res) == 0 {
   389  			o.Body = &body
   390  		}
   391  	}
   392  
   393  	if len(res) > 0 {
   394  		return errors.CompositeValidationError(res...)
   395  	}
   396  	return nil
   397  }
   398  ```
   399  
   400  In this example there is only a body parameter, so we make use of the selected consumer to read the request body and turn it into an instance of models.Item. When the body parameter is bound, it gets validated and when validation passes no error is returned and the body property is set.  After a request is bound and validated the parameters and security principal are passed to the request handler. For this configuration that would return a 501 responder.
   401  
   402  Go swagger uses responders which are an interface implementation for things that can write to a response. For the generated server there are status code response and a default response object generated for every entry in the spec. For the `addOne` operation that are 2 objects one for the success case (201) and one for an error (default).
   403  
   404  ```go
   405  // Responder is an interface for types to implement
   406  // when they want to be considered for writing HTTP responses
   407  type Responder interface {
   408  	WriteResponse(http.ResponseWriter, runtime.Producer)
   409  }
   410  
   411  /*AddOneCreated Created
   412  
   413  swagger:response addOneCreated
   414  */
   415  type AddOneCreated struct {
   416  
   417  	// In: body
   418  	Payload *models.Item `json:"body,omitempty"`
   419  }
   420  
   421  // WriteResponse to the client
   422  func (o *AddOneCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
   423  
   424  	rw.WriteHeader(201)
   425  	if o.Payload != nil {
   426  		if err := producer.Produce(rw, o.Payload); err != nil {
   427  			panic(err) // let the recovery middleware deal with this
   428  		}
   429  	}
   430  }
   431  
   432  /*AddOneDefault error
   433  
   434  swagger:response addOneDefault
   435  */
   436  type AddOneDefault struct {
   437  
   438  	// In: body
   439  	Payload *models.Error `json:"body,omitempty"`
   440  }
   441  
   442  // WriteResponse to the client
   443  func (o *AddOneDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
   444  
   445  	rw.WriteHeader(500)
   446  	if o.Payload != nil {
   447  		if err := producer.Produce(rw, o.Payload); err != nil {
   448  			panic(err) // let the recovery middleware deal with this
   449  		}
   450  	}
   451  }
   452  ```
   453  
   454  So an implementer of the `AddOneHandler` could return one of these 2 objects and go-swagger is able to respect the contract set forward by the spec document.
   455  
   456  So to implement the AddOneHandler you could do something like this.
   457  
   458  ```go
   459  todos.AddOneHandlerFunc(func(params todos.AddOneParams, principal interface{}) middleware.Responder {
   460    created, err := database.Save(params.Body)
   461    if err != nil {
   462      return AddOneDefault{models.Error{500, err.Error()}}
   463    }
   464    return AddOneCreated{created}
   465  })
   466  ```