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