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 ```