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