From 96ed82702852775595aac09a680ecab6dc452f66 Mon Sep 17 00:00:00 2001 From: fredbi Date: Wed, 4 Jul 2018 17:04:00 +0200 Subject: [PATCH] regen examples Fix #1496 --- docs/tutorial/oauth2/README.md | 6 +- examples/2.0/petstore/server/api/petstore.go | 4 +- examples/2.0/petstore/server/petstore.go | 2 +- .../cmd/auth-sample-server/main.go | 3 +- examples/authentication/models/customer.go | 5 - examples/authentication/models/error.go | 1 - examples/authentication/models/social_id.go | 1 - .../restapi/configure_auth_sample.go | 4 +- .../operations/customers/create_parameters.go | 1 - .../operations/customers/get_id_parameters.go | 1 - examples/authentication/restapi/server.go | 106 ++++-- examples/composed-auth/auth/authorizers.go | 11 +- .../cmd/multi-auth-example-server/main.go | 3 +- examples/composed-auth/models/error.go | 1 - examples/composed-auth/models/order.go | 80 ++++- examples/composed-auth/models/order_line.go | 91 ------ examples/composed-auth/models/principal.go | 20 -- .../restapi/configure_multi_auth_example.go | 86 ++--- .../composed-auth/restapi/embedded_spec.go | 36 +- .../operations/add_order_parameters.go | 1 - .../restapi/operations/get_account.go | 4 - .../operations/get_account_responses.go | 6 +- .../operations/get_order_parameters.go | 1 + .../get_orders_for_item_parameters.go | 1 + .../operations/multi_auth_example_api.go | 32 +- examples/composed-auth/restapi/server.go | 106 ++++-- .../generated/cmd/petstore-server/main.go | 3 +- examples/generated/models/category.go | 6 - examples/generated/models/order.go | 2 +- examples/generated/models/pet.go | 9 - examples/generated/models/tag.go | 6 - examples/generated/models/user.go | 6 - .../generated/restapi/configure_petstore.go | 11 +- .../operations/pet/add_pet_parameters.go | 1 - .../operations/pet/delete_pet_parameters.go | 2 + .../pet/find_pets_by_status_parameters.go | 3 + .../pet/find_pets_by_tags_parameters.go | 3 + .../pet/get_pet_by_id_parameters.go | 1 + .../operations/pet/update_pet_parameters.go | 1 - .../pet/update_pet_with_form_parameters.go | 3 + .../restapi/operations/petstore_api.go | 31 +- .../store/delete_order_parameters.go | 1 + .../store/get_order_by_id_parameters.go | 1 + .../store/place_order_parameters.go | 1 - .../operations/user/create_user_parameters.go | 1 - ...reate_users_with_array_input_parameters.go | 9 +- ...create_users_with_list_input_parameters.go | 9 +- .../operations/user/delete_user_parameters.go | 1 + .../user/get_user_by_name_parameters.go | 1 + .../operations/user/login_user_parameters.go | 2 + .../operations/user/update_user_parameters.go | 2 +- examples/generated/restapi/server.go | 106 ++++-- examples/oauth2/README.md | 6 +- .../oauth2/cmd/oauth-sample-server/main.go | 3 +- examples/oauth2/models/customer.go | 5 - examples/oauth2/models/error.go | 1 - .../models/get_auth_callback_o_k_body.go | 49 --- examples/oauth2/models/get_login_o_k_body.go | 49 --- examples/oauth2/models/social_id.go | 1 - .../oauth2/restapi/configure_oauth_sample.go | 28 +- examples/oauth2/restapi/embedded_spec.go | 32 +- examples/oauth2/restapi/implementation.go | 6 +- .../operations/customers/create_parameters.go | 1 - .../operations/customers/get_id_parameters.go | 1 - .../restapi/operations/get_auth_callback.go | 33 ++ .../operations/get_auth_callback_responses.go | 6 +- .../oauth2/restapi/operations/get_login.go | 33 ++ .../restapi/operations/get_login_responses.go | 6 +- examples/oauth2/restapi/server.go | 106 ++++-- examples/stream-server/biz/count.go | 2 +- .../cmd/countdown-server/main.go | 3 +- examples/stream-server/models/mark.go | 1 - .../restapi/configure_countdown.go | 12 +- .../restapi/operations/elapse_parameters.go | 2 + examples/stream-server/restapi/server.go | 106 ++++-- .../tasks/add_comment_to_task_parameters.go | 14 +- .../tasks/add_comment_to_task_responses.go | 74 +++++ .../cmd/task-tracker-server/main.go | 3 +- .../models/add_comment_to_task_params_body.go | 86 ----- examples/task-tracker/models/comment.go | 6 +- examples/task-tracker/models/error.go | 4 +- examples/task-tracker/models/milestone.go | 46 ++- .../task-tracker/models/milestone_stats.go | 58 ---- examples/task-tracker/models/task.go | 308 +++++++++++++++++- examples/task-tracker/models/task_all_of1.go | 174 ---------- .../models/task_all_of1_attachments.go | 34 -- ...l_of1_attachments_additional_properties.go | 117 ------- examples/task-tracker/models/task_card.go | 15 +- examples/task-tracker/models/user_card.go | 3 - .../task-tracker/models/validation_error.go | 36 +- .../models/validation_error_all_of1.go | 49 --- .../restapi/configure_task_tracker.go | 13 +- .../task-tracker/restapi/embedded_spec.go | 246 +++++++------- .../restapi/operations/task_tracker_api.go | 32 +- .../operations/tasks/add_comment_to_task.go | 74 +++++ .../tasks/add_comment_to_task_parameters.go | 10 +- .../tasks/create_task_parameters.go | 1 - .../tasks/delete_task_parameters.go | 1 + .../tasks/get_task_comments_parameters.go | 5 +- .../tasks/get_task_details_parameters.go | 1 + .../operations/tasks/list_tasks_parameters.go | 10 + .../tasks/update_task_parameters.go | 2 +- .../tasks/upload_task_file_parameters.go | 6 +- examples/task-tracker/restapi/server.go | 106 ++++-- .../todo-list/cmd/todo-list-server/main.go | 3 +- examples/todo-list/models/error.go | 1 - examples/todo-list/models/item.go | 1 - .../todo-list/restapi/configure_todo_list.go | 4 +- .../operations/todos/add_one_parameters.go | 1 - .../todos/destroy_one_parameters.go | 1 + .../operations/todos/find_parameters.go | 5 + .../operations/todos/update_one_parameters.go | 2 +- examples/todo-list/restapi/server.go | 106 ++++-- .../custom-server/cmd/greeter/main.go | 4 +- .../gen/restapi/configure_greeter.go | 3 +- .../operations/get_greeting_parameters.go | 1 + .../custom-server/gen/restapi/server.go | 106 ++++-- .../server-1/cmd/todo-list-server/main.go | 3 +- .../todo-list/server-1/models/error.go | 1 - .../todo-list/server-1/models/item.go | 1 - .../server-1/restapi/configure_todo_list.go | 3 +- .../operations/todos/find_todos_parameters.go | 2 + .../todo-list/server-1/restapi/server.go | 106 ++++-- .../server-2/cmd/todo-list-server/main.go | 3 +- .../todo-list/server-2/models/error.go | 1 - .../todo-list/server-2/models/item.go | 1 - .../server-2/restapi/configure_todo_list.go | 3 +- .../operations/todos/add_one_parameters.go | 1 - .../todos/destroy_one_parameters.go | 1 + .../operations/todos/find_todos_parameters.go | 2 + .../operations/todos/update_one_parameters.go | 2 +- .../todo-list/server-2/restapi/server.go | 106 ++++-- .../cmd/todo-list-server/main.go | 3 +- .../todo-list/server-complete/models/error.go | 1 - .../todo-list/server-complete/models/item.go | 1 - .../restapi/configure_todo_list.go | 13 +- .../operations/todos/add_one_parameters.go | 1 - .../todos/destroy_one_parameters.go | 1 + .../operations/todos/find_todos_parameters.go | 2 + .../operations/todos/update_one_parameters.go | 2 +- .../server-complete/restapi/server.go | 106 ++++-- hack/regen-samples.sh | 9 + 142 files changed, 1927 insertions(+), 1547 deletions(-) delete mode 100644 examples/composed-auth/models/order_line.go delete mode 100644 examples/oauth2/models/get_auth_callback_o_k_body.go delete mode 100644 examples/oauth2/models/get_login_o_k_body.go delete mode 100644 examples/task-tracker/models/add_comment_to_task_params_body.go delete mode 100644 examples/task-tracker/models/milestone_stats.go delete mode 100644 examples/task-tracker/models/task_all_of1.go delete mode 100644 examples/task-tracker/models/task_all_of1_attachments.go delete mode 100644 examples/task-tracker/models/task_all_of1_attachments_additional_properties.go delete mode 100644 examples/task-tracker/models/validation_error_all_of1.go diff --git a/docs/tutorial/oauth2/README.md b/docs/tutorial/oauth2/README.md index 74698e68e1..2556d35b31 100644 --- a/docs/tutorial/oauth2/README.md +++ b/docs/tutorial/oauth2/README.md @@ -97,8 +97,8 @@ var ( state = "foobar" // Don't make this a global in production. // the credentials for this API (adapt values when registering API) - clientID = "" - clientSecret = "" + clientID = "" // <= enter registered API client ID here + clientSecret = "" // <= enter registered API client secret here // unused in this example: the signer of the delivered token issuer = "https://accounts.google.com" @@ -213,7 +213,7 @@ We set the following implementation for authentication in `restapi/implementatio func login(r *http.Request) string { // implements the login with a redirection and an access token var accessToken string - http.Redirect(wG, r, config.AuthCodeURL(state), http.StatusFound) + wG := r.Context().Value(ctxResponseWriter).(http.ResponseWriter) log.Println("Access token:", accessToken) return accessToken } diff --git a/examples/2.0/petstore/server/api/petstore.go b/examples/2.0/petstore/server/api/petstore.go index b2c0f348bf..5a8f25a31b 100644 --- a/examples/2.0/petstore/server/api/petstore.go +++ b/examples/2.0/petstore/server/api/petstore.go @@ -93,8 +93,8 @@ type Pet struct { } var pets = []Pet{ - {1, "Dog", []string{}, "available", nil}, - {2, "Cat", []string{}, "pending", nil}, + {ID: 1, Name: "Dog", PhotoURLs: []string{}, Status: "available", Tags: nil}, + {ID: 2, Name: "Cat", PhotoURLs: []string{}, Status: "pending", Tags: nil}, } var petsLock = &sync.Mutex{} diff --git a/examples/2.0/petstore/server/petstore.go b/examples/2.0/petstore/server/petstore.go index 87b2647aa7..206f31f69a 100644 --- a/examples/2.0/petstore/server/petstore.go +++ b/examples/2.0/petstore/server/petstore.go @@ -27,5 +27,5 @@ func main() { log.Fatalln(err) } log.Println("Serving petstore api on http://127.0.0.1:8344/swagger-ui/") - http.ListenAndServe(":8344", petstoreAPI) + _ = http.ListenAndServe(":8344", petstoreAPI) } diff --git a/examples/authentication/cmd/auth-sample-server/main.go b/examples/authentication/cmd/auth-sample-server/main.go index 7601c36ff9..7150b4137d 100644 --- a/examples/authentication/cmd/auth-sample-server/main.go +++ b/examples/authentication/cmd/auth-sample-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/authentication/restapi" "github.com/go-swagger/go-swagger/examples/authentication/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/authentication/models/customer.go b/examples/authentication/models/customer.go index 79486a5dbd..6c6117703f 100644 --- a/examples/authentication/models/customer.go +++ b/examples/authentication/models/customer.go @@ -51,27 +51,22 @@ func (m *Customer) Validate(formats strfmt.Registry) error { var res []error if err := m.validateCustomerID(formats); err != nil { - // prop res = append(res, err) } if err := m.validateFipsCode(formats); err != nil { - // prop res = append(res, err) } if err := m.validateName(formats); err != nil { - // prop res = append(res, err) } if err := m.validateSsn(formats); err != nil { - // prop res = append(res, err) } if err := m.validateSurname(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/authentication/models/error.go b/examples/authentication/models/error.go index e64b2d4b74..9a60acf008 100644 --- a/examples/authentication/models/error.go +++ b/examples/authentication/models/error.go @@ -33,7 +33,6 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/authentication/models/social_id.go b/examples/authentication/models/social_id.go index be83defa0a..32279cc9fb 100644 --- a/examples/authentication/models/social_id.go +++ b/examples/authentication/models/social_id.go @@ -28,7 +28,6 @@ func (m *SocialID) Validate(formats strfmt.Registry) error { var res []error if err := m.validateSsn(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/authentication/restapi/configure_auth_sample.go b/examples/authentication/restapi/configure_auth_sample.go index 3b614a6271..2e45f6f29e 100644 --- a/examples/authentication/restapi/configure_auth_sample.go +++ b/examples/authentication/restapi/configure_auth_sample.go @@ -9,7 +9,6 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/authentication/restapi/operations" "github.com/go-swagger/go-swagger/examples/authentication/restapi/operations/customers" @@ -47,7 +46,6 @@ func configureAPI(api *operations.AuthSampleAPI) http.Handler { // // Example: // api.APIAuthorizer = security.Authorized() - api.CustomersCreateHandler = customers.CreateHandlerFunc(func(params customers.CreateParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation customers.Create has not yet been implemented") }) @@ -69,7 +67,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/authentication/restapi/operations/customers/create_parameters.go b/examples/authentication/restapi/operations/customers/create_parameters.go index 086298d2a9..71dfd2dcc6 100644 --- a/examples/authentication/restapi/operations/customers/create_parameters.go +++ b/examples/authentication/restapi/operations/customers/create_parameters.go @@ -52,7 +52,6 @@ func (o *CreateParams) BindRequest(r *http.Request, route *middleware.MatchedRou if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("info", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/authentication/restapi/operations/customers/get_id_parameters.go b/examples/authentication/restapi/operations/customers/get_id_parameters.go index 0cebf7f429..2acdea2aa4 100644 --- a/examples/authentication/restapi/operations/customers/get_id_parameters.go +++ b/examples/authentication/restapi/operations/customers/get_id_parameters.go @@ -52,7 +52,6 @@ func (o *GetIDParams) BindRequest(r *http.Request, route *middleware.MatchedRout if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("info", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/authentication/restapi/server.go b/examples/authentication/restapi/server.go index a23e0f4f45..d56464b225 100644 --- a/examples/authentication/restapi/server.go +++ b/examples/authentication/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/authentication/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.AuthSampleAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/composed-auth/auth/authorizers.go b/examples/composed-auth/auth/authorizers.go index afdc259a56..7a57c69bcf 100644 --- a/examples/composed-auth/auth/authorizers.go +++ b/examples/composed-auth/auth/authorizers.go @@ -10,8 +10,9 @@ import ( ) const ( - privateKeyPath = "keys/apiKey.prv" - publicKeyPath = "keys/apiKey.pem" + // currently unused: privateKeyPath = "keys/apiKey.prv" + publicKeyPath = "keys/apiKey.pem" + issuerName = "example.com" ) var ( @@ -19,7 +20,7 @@ var ( // Keys used to sign and verify our tokens verifyKey *rsa.PublicKey - signKey *rsa.PrivateKey + // currently unused: signKey *rsa.PrivateKey ) // roleClaims describes the format of our JWT token's claims @@ -65,7 +66,7 @@ func IsRegistered(user, pass string) (*models.Principal, error) { func IsReseller(token string) (*models.Principal, error) { claims, err := parseAndCheckToken(token) if err == nil { - if claims.Issuer == "example.com" && claims.Id != "" { + if claims.Issuer == issuerName && claims.Id != "" { isReseller := false for _, role := range claims.Roles { if role == "reseller" { @@ -90,7 +91,7 @@ func IsReseller(token string) (*models.Principal, error) { func HasRole(token string, scopes []string) (*models.Principal, error) { claims, err := parseAndCheckToken(token) if err == nil { - if claims.Issuer == "example.com" { + if claims.Issuer == issuerName { isInScopes := false claimedRoles := []string{} for _, scope := range scopes { diff --git a/examples/composed-auth/cmd/multi-auth-example-server/main.go b/examples/composed-auth/cmd/multi-auth-example-server/main.go index d4b505f3aa..9c59a3ef8b 100644 --- a/examples/composed-auth/cmd/multi-auth-example-server/main.go +++ b/examples/composed-auth/cmd/multi-auth-example-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/composed-auth/restapi" "github.com/go-swagger/go-swagger/examples/composed-auth/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/composed-auth/models/error.go b/examples/composed-auth/models/error.go index 87f84c94ea..9c4a6ad019 100644 --- a/examples/composed-auth/models/error.go +++ b/examples/composed-auth/models/error.go @@ -30,7 +30,6 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/composed-auth/models/order.go b/examples/composed-auth/models/order.go index 3a269096fe..c6c65347fa 100644 --- a/examples/composed-auth/models/order.go +++ b/examples/composed-auth/models/order.go @@ -32,12 +32,10 @@ func (m *Order) Validate(formats strfmt.Registry) error { var res []error if err := m.validateOrderID(formats); err != nil { - // prop res = append(res, err) } if err := m.validateOrderLines(formats); err != nil { - // prop res = append(res, err) } @@ -63,20 +61,17 @@ func (m *Order) validateOrderLines(formats strfmt.Registry) error { } for i := 0; i < len(m.OrderLines); i++ { - if swag.IsZero(m.OrderLines[i]) { // not required continue } if m.OrderLines[i] != nil { - if err := m.OrderLines[i].Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("orderLines" + "." + strconv.Itoa(i)) } return err } - } } @@ -101,3 +96,78 @@ func (m *Order) UnmarshalBinary(b []byte) error { *m = res return nil } + +// OrderLine order line +// swagger:model orderLine +type OrderLine struct { + + // purchased item + // Required: true + PurchasedItem Item `json:"purchasedItem"` + + // quantity + // Required: true + // Minimum: 1 + Quantity *uint32 `json:"quantity"` +} + +// Validate validates this order line +func (m *OrderLine) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePurchasedItem(formats); err != nil { + res = append(res, err) + } + + if err := m.validateQuantity(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *OrderLine) validatePurchasedItem(formats strfmt.Registry) error { + + if err := m.PurchasedItem.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("purchasedItem") + } + return err + } + + return nil +} + +func (m *OrderLine) validateQuantity(formats strfmt.Registry) error { + + if err := validate.Required("quantity", "body", m.Quantity); err != nil { + return err + } + + if err := validate.Minimum("quantity", "body", float64(*m.Quantity), 1, false); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *OrderLine) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *OrderLine) UnmarshalBinary(b []byte) error { + var res OrderLine + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/examples/composed-auth/models/order_line.go b/examples/composed-auth/models/order_line.go deleted file mode 100644 index 8a4d8f3493..0000000000 --- a/examples/composed-auth/models/order_line.go +++ /dev/null @@ -1,91 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// OrderLine order line -// swagger:model orderLine -type OrderLine struct { - - // purchased item - // Required: true - PurchasedItem Item `json:"purchasedItem"` - - // quantity - // Required: true - // Minimum: 1 - Quantity *uint32 `json:"quantity"` -} - -// Validate validates this order line -func (m *OrderLine) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validatePurchasedItem(formats); err != nil { - // prop - res = append(res, err) - } - - if err := m.validateQuantity(formats); err != nil { - // prop - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *OrderLine) validatePurchasedItem(formats strfmt.Registry) error { - - if err := m.PurchasedItem.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("purchasedItem") - } - return err - } - - return nil -} - -func (m *OrderLine) validateQuantity(formats strfmt.Registry) error { - - if err := validate.Required("quantity", "body", m.Quantity); err != nil { - return err - } - - if err := validate.Minimum("quantity", "body", float64(*m.Quantity), 1, false); err != nil { - return err - } - - return nil -} - -// MarshalBinary interface implementation -func (m *OrderLine) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *OrderLine) UnmarshalBinary(b []byte) error { - var res OrderLine - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/composed-auth/models/principal.go b/examples/composed-auth/models/principal.go index dcfbf4e450..4aeeb998d0 100644 --- a/examples/composed-auth/models/principal.go +++ b/examples/composed-auth/models/principal.go @@ -8,7 +8,6 @@ package models import ( strfmt "github.com/go-openapi/strfmt" - "github.com/go-openapi/errors" "github.com/go-openapi/swag" ) @@ -25,25 +24,6 @@ type Principal struct { // Validate validates this principal func (m *Principal) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateRoles(formats); err != nil { - // prop - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *Principal) validateRoles(formats strfmt.Registry) error { - - if swag.IsZero(m.Roles) { // not required - return nil - } - return nil } diff --git a/examples/composed-auth/restapi/configure_multi_auth_example.go b/examples/composed-auth/restapi/configure_multi_auth_example.go index 5d6bc3d7f8..2efc3902e3 100644 --- a/examples/composed-auth/restapi/configure_multi_auth_example.go +++ b/examples/composed-auth/restapi/configure_multi_auth_example.go @@ -11,16 +11,14 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/composed-auth/restapi/operations" - models "github.com/go-swagger/go-swagger/examples/composed-auth/models" - auth "github.com/go-swagger/go-swagger/examples/composed-auth/auth" + models "github.com/go-swagger/go-swagger/examples/composed-auth/models" ) -//go:generate swagger generate server --target .. --name multiAuthExample --spec ../swagger.yml --principal models.Principal +//go:generate swagger generate server --target .. --name multi-auth-example --spec ../swagger.yml --principal models.Principal func configureFlags(api *operations.MultiAuthExampleAPI) { // api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... } @@ -32,66 +30,69 @@ func configureAPI(api *operations.MultiAuthExampleAPI) http.Handler { // Set your custom logger if needed. Default one is log.Printf // Expected interface func(string, ...interface{}) - api.Logger = log.Printf api.JSONConsumer = runtime.JSONConsumer() api.JSONProducer = runtime.JSONProducer() - // Applies when the "Authorization: Basic" header is set with the Basic scheme - api.IsRegisteredAuth = func(user string, pass string) (*models.Principal, error) { - // The header: Authorization: Basic {base64 string} has already been decoded by the runtime as a username:password pair - api.Logger("IsRegisteredAuth handler called") - return auth.IsRegistered(user, pass) - } - - // Applies when the "Authorization: Bearer" header or the "access_token" query is set api.HasRoleAuth = func(token string, scopes []string) (*models.Principal, error) { // The header: Authorization: Bearer {base64 string} (or ?access_token={base 64 string} param) has already // been decoded by the runtime as a token api.Logger("HasRoleAuth handler called") return auth.HasRole(token, scopes) } - - // Applies when the "CustomKeyAsQuery" query is set - api.IsResellerQueryAuth = func(token string) (*models.Principal, error) { - api.Logger("ResellerQueryAuth handler called") - return auth.IsReseller(token) + // Applies when the Authorization header is set with the Basic scheme + api.IsRegisteredAuth = func(user string, pass string) (*models.Principal, error) { + // The header: Authorization: Basic {base64 string} has already been decoded by the runtime as a + // username:password pair + api.Logger("IsRegisteredAuth handler called") + return auth.IsRegistered(user, pass) } - // Applies when the "X-Custom-Key" header is set api.IsResellerAuth = func(token string) (*models.Principal, error) { api.Logger("IsResellerAuth handler called") return auth.IsReseller(token) } + // Applies when the "CustomKeyAsQuery" query is set + api.IsResellerQueryAuth = func(token string) (*models.Principal, error) { + api.Logger("ResellerQueryAuth handler called") + return auth.IsReseller(token) + } // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer // // Example: // api.APIAuthorizer = security.Authorized() - - api.AddOrderHandler = operations.AddOrderHandlerFunc(func(params operations.AddOrderParams, principal *models.Principal) middleware.Responder { - log.Printf("AddOrder called with params: %s, and principal: %s", spew.Sdump(params.Order), spew.Sdump(principal)) - return middleware.NotImplemented("operation .AddOrder has not yet been implemented") - }) - api.GetItemsHandler = operations.GetItemsHandlerFunc(func(params operations.GetItemsParams) middleware.Responder { - log.Printf("GetItems called with NO params and NO principal") - return middleware.NotImplemented("operation .GetItems has not yet been implemented") - }) - api.GetOrderHandler = operations.GetOrderHandlerFunc(func(params operations.GetOrderParams, principal *models.Principal) middleware.Responder { - log.Printf("GetOrder called with params: %s, and principal: %s", spew.Sdump(params.OrderID), spew.Sdump(principal)) - return middleware.NotImplemented("operation .GetOrder has not yet been implemented") - }) - api.GetOrdersForItemHandler = operations.GetOrdersForItemHandlerFunc(func(params operations.GetOrdersForItemParams, principal *models.Principal) middleware.Responder { - log.Printf("GetOrdersForItem called with params: %v, and principal: %v", spew.Sdump(params.ItemID), spew.Sdump(principal)) - return middleware.NotImplemented("operation .GetOrdersForItem has not yet been implemented") - }) - api.GetAccountHandler = operations.GetAccountHandlerFunc(func(params operations.GetAccountParams, principal *models.Principal) middleware.Responder { - log.Printf("GetAccount called with NO params, and principal: %s", spew.Sdump(principal)) - return middleware.NotImplemented("operation .GetAccount has not yet been implemented") - }) + api.AddOrderHandler = operations.AddOrderHandlerFunc( + func(params operations.AddOrderParams, principal *models.Principal) middleware.Responder { + log.Printf("AddOrder called with params: %s, and principal: %s", + spew.Sdump(params.Order), spew.Sdump(principal)) + return middleware.NotImplemented("operation .AddOrder has not yet been implemented") + }) + api.GetAccountHandler = operations.GetAccountHandlerFunc( + func(params operations.GetAccountParams, principal *models.Principal) middleware.Responder { + log.Printf("GetAccount called with NO params, and principal: %s", spew.Sdump(principal)) + return middleware.NotImplemented("operation .GetAccount has not yet been implemented") + }) + api.GetItemsHandler = operations.GetItemsHandlerFunc( + func(params operations.GetItemsParams) middleware.Responder { + log.Printf("GetItems called with NO params and NO principal") + return middleware.NotImplemented("operation .GetItems has not yet been implemented") + }) + api.GetOrderHandler = operations.GetOrderHandlerFunc( + func(params operations.GetOrderParams, principal *models.Principal) middleware.Responder { + log.Printf("GetOrder called with params: %s, and principal: %s", + spew.Sdump(params.OrderID), spew.Sdump(principal)) + return middleware.NotImplemented("operation .GetOrder has not yet been implemented") + }) + api.GetOrdersForItemHandler = operations.GetOrdersForItemHandlerFunc( + func(params operations.GetOrdersForItemParams, principal *models.Principal) middleware.Responder { + log.Printf("GetOrdersForItem called with params: %v, and principal: %v", + spew.Sdump(params.ItemID), spew.Sdump(principal)) + return middleware.NotImplemented("operation .GetOrdersForItem has not yet been implemented") + }) api.ServerShutdown = func() {} @@ -107,7 +108,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. @@ -116,7 +117,8 @@ func setupMiddlewares(handler http.Handler) http.Handler { return handler } -// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document. +// The middleware configuration happens before anything, this middleware also applies to serving the +// swagger.json document. // So this is a good place to plug in a panic handling middleware, logging and metrics func setupGlobalMiddleware(handler http.Handler) http.Handler { return handler diff --git a/examples/composed-auth/restapi/embedded_spec.go b/examples/composed-auth/restapi/embedded_spec.go index 9a75de16e8..b3f2adf8c6 100644 --- a/examples/composed-auth/restapi/embedded_spec.go +++ b/examples/composed-auth/restapi/embedded_spec.go @@ -593,30 +593,26 @@ func init() { "orderLines": { "type": "array", "items": { - "$ref": "#/definitions/orderOrderLinesItems" + "type": "object", + "required": [ + "quantity", + "purchasedItem" + ], + "properties": { + "purchasedItem": { + "$ref": "#/definitions/Item" + }, + "quantity": { + "type": "string", + "format": "uint32", + "minimum": 1 + } + }, + "x-go-name": "orderLine" } } } }, - "orderOrderLinesItems": { - "type": "object", - "required": [ - "quantity", - "purchasedItem" - ], - "properties": { - "purchasedItem": { - "$ref": "#/definitions/Item" - }, - "quantity": { - "type": "string", - "format": "uint32", - "minimum": 1 - } - }, - "x-go-gen-location": "models", - "x-go-name": "orderLine" - }, "principal": { "type": "object", "properties": { diff --git a/examples/composed-auth/restapi/operations/add_order_parameters.go b/examples/composed-auth/restapi/operations/add_order_parameters.go index 4d9266dba2..b749cea455 100644 --- a/examples/composed-auth/restapi/operations/add_order_parameters.go +++ b/examples/composed-auth/restapi/operations/add_order_parameters.go @@ -58,7 +58,6 @@ func (o *AddOrderParams) BindRequest(r *http.Request, route *middleware.MatchedR res = append(res, errors.NewParseError("order", "body", "", err)) } } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/composed-auth/restapi/operations/get_account.go b/examples/composed-auth/restapi/operations/get_account.go index 55d2257d70..979105994a 100644 --- a/examples/composed-auth/restapi/operations/get_account.go +++ b/examples/composed-auth/restapi/operations/get_account.go @@ -74,7 +74,3 @@ func (o *GetAccount) ServeHTTP(rw http.ResponseWriter, r *http.Request) { o.Context.Respond(rw, r, route.Produces, route, res) } - -// GetAccountOKBody get account o k body -// swagger:model GetAccountOKBody -type GetAccountOKBody interface{} diff --git a/examples/composed-auth/restapi/operations/get_account_responses.go b/examples/composed-auth/restapi/operations/get_account_responses.go index 65ee8cc0c9..bcb4bc5816 100644 --- a/examples/composed-auth/restapi/operations/get_account_responses.go +++ b/examples/composed-auth/restapi/operations/get_account_responses.go @@ -25,7 +25,7 @@ type GetAccountOK struct { /* In: Body */ - Payload GetAccountOKBody `json:"body,omitempty"` + Payload interface{} `json:"body,omitempty"` } // NewGetAccountOK creates GetAccountOK with default headers values @@ -35,13 +35,13 @@ func NewGetAccountOK() *GetAccountOK { } // WithPayload adds the payload to the get account o k response -func (o *GetAccountOK) WithPayload(payload GetAccountOKBody) *GetAccountOK { +func (o *GetAccountOK) WithPayload(payload interface{}) *GetAccountOK { o.Payload = payload return o } // SetPayload sets the payload to the get account o k response -func (o *GetAccountOK) SetPayload(payload GetAccountOKBody) { +func (o *GetAccountOK) SetPayload(payload interface{}) { o.Payload = payload } diff --git a/examples/composed-auth/restapi/operations/get_order_parameters.go b/examples/composed-auth/restapi/operations/get_order_parameters.go index d6779984e6..cfc3dbed30 100644 --- a/examples/composed-auth/restapi/operations/get_order_parameters.go +++ b/examples/composed-auth/restapi/operations/get_order_parameters.go @@ -57,6 +57,7 @@ func (o *GetOrderParams) BindRequest(r *http.Request, route *middleware.MatchedR return nil } +// bindOrderID binds and validates parameter OrderID from path. func (o *GetOrderParams) bindOrderID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/composed-auth/restapi/operations/get_orders_for_item_parameters.go b/examples/composed-auth/restapi/operations/get_orders_for_item_parameters.go index 51c72a7a20..7fd215bba9 100644 --- a/examples/composed-auth/restapi/operations/get_orders_for_item_parameters.go +++ b/examples/composed-auth/restapi/operations/get_orders_for_item_parameters.go @@ -57,6 +57,7 @@ func (o *GetOrdersForItemParams) BindRequest(r *http.Request, route *middleware. return nil } +// bindItemID binds and validates parameter ItemID from path. func (o *GetOrdersForItemParams) bindItemID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/composed-auth/restapi/operations/multi_auth_example_api.go b/examples/composed-auth/restapi/operations/multi_auth_example_api.go index 0521c08017..9f779ce3d8 100644 --- a/examples/composed-auth/restapi/operations/multi_auth_example_api.go +++ b/examples/composed-auth/restapi/operations/multi_auth_example_api.go @@ -64,14 +64,14 @@ func NewMultiAuthExampleAPI(spec *loads.Document) *MultiAuthExampleAPI { return nil, errors.NotImplemented("basic auth (isRegistered) has not yet been implemented") }, - // Applies when the "CustomKeyAsQuery" query is set - IsResellerQueryAuth: func(token string) (*models.Principal, error) { - return nil, errors.NotImplemented("api key auth (isResellerQuery) CustomKeyAsQuery from query param [CustomKeyAsQuery] has not yet been implemented") - }, // Applies when the "X-Custom-Key" header is set IsResellerAuth: func(token string) (*models.Principal, error) { return nil, errors.NotImplemented("api key auth (isReseller) X-Custom-Key from header param [X-Custom-Key] has not yet been implemented") }, + // Applies when the "CustomKeyAsQuery" query is set + IsResellerQueryAuth: func(token string) (*models.Principal, error) { + return nil, errors.NotImplemented("api key auth (isResellerQuery) CustomKeyAsQuery from query param [CustomKeyAsQuery] has not yet been implemented") + }, // default authorizer is authorized meaning no requests are blocked APIAuthorizer: security.Authorized(), @@ -140,14 +140,14 @@ type MultiAuthExampleAPI struct { // it performs authentication with basic auth IsRegisteredAuth func(string, string) (*models.Principal, error) - // IsResellerQueryAuth registers a function that takes a token and returns a principal - // it performs authentication based on an api key CustomKeyAsQuery provided in the query - IsResellerQueryAuth func(string) (*models.Principal, error) - // IsResellerAuth registers a function that takes a token and returns a principal // it performs authentication based on an api key X-Custom-Key provided in the header IsResellerAuth func(string) (*models.Principal, error) + // IsResellerQueryAuth registers a function that takes a token and returns a principal + // it performs authentication based on an api key CustomKeyAsQuery provided in the query + IsResellerQueryAuth func(string) (*models.Principal, error) + // APIAuthorizer provides access control (ACL/RBAC/ABAC) by providing access to the request and authenticated principal APIAuthorizer runtime.Authorizer @@ -232,14 +232,14 @@ func (o *MultiAuthExampleAPI) Validate() error { unregistered = append(unregistered, "IsRegisteredAuth") } - if o.IsResellerQueryAuth == nil { - unregistered = append(unregistered, "CustomKeyAsQueryAuth") - } - if o.IsResellerAuth == nil { unregistered = append(unregistered, "XCustomKeyAuth") } + if o.IsResellerQueryAuth == nil { + unregistered = append(unregistered, "CustomKeyAsQueryAuth") + } + if o.AddOrderHandler == nil { unregistered = append(unregistered, "AddOrderHandler") } @@ -291,16 +291,16 @@ func (o *MultiAuthExampleAPI) AuthenticatorsFor(schemes map[string]spec.Security return o.IsRegisteredAuth(username, password) }) - case "isResellerQuery": + case "isReseller": result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, func(token string) (interface{}, error) { - return o.IsResellerQueryAuth(token) + return o.IsResellerAuth(token) }) - case "isReseller": + case "isResellerQuery": result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, func(token string) (interface{}, error) { - return o.IsResellerAuth(token) + return o.IsResellerQueryAuth(token) }) } diff --git a/examples/composed-auth/restapi/server.go b/examples/composed-auth/restapi/server.go index 0287c6e7ea..7be4243936 100644 --- a/examples/composed-auth/restapi/server.go +++ b/examples/composed-auth/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/composed-auth/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.MultiAuthExampleAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/generated/cmd/petstore-server/main.go b/examples/generated/cmd/petstore-server/main.go index 89520b5058..54dc4e5cbd 100644 --- a/examples/generated/cmd/petstore-server/main.go +++ b/examples/generated/cmd/petstore-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/generated/restapi" "github.com/go-swagger/go-swagger/examples/generated/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/generated/models/category.go b/examples/generated/models/category.go index ed20329d5a..243f40eb19 100644 --- a/examples/generated/models/category.go +++ b/examples/generated/models/category.go @@ -8,7 +8,6 @@ package models import ( strfmt "github.com/go-openapi/strfmt" - "github.com/go-openapi/errors" "github.com/go-openapi/swag" ) @@ -25,11 +24,6 @@ type Category struct { // Validate validates this category func (m *Category) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } return nil } diff --git a/examples/generated/models/order.go b/examples/generated/models/order.go index 89f906883b..a0843e743a 100644 --- a/examples/generated/models/order.go +++ b/examples/generated/models/order.go @@ -30,6 +30,7 @@ type Order struct { Quantity int32 `json:"quantity,omitempty"` // ship date + // Format: date-time ShipDate strfmt.DateTime `json:"shipDate,omitempty"` // Order Status @@ -41,7 +42,6 @@ func (m *Order) Validate(formats strfmt.Registry) error { var res []error if err := m.validateShipDate(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/generated/models/pet.go b/examples/generated/models/pet.go index 2762767e03..3c0cf65bd7 100644 --- a/examples/generated/models/pet.go +++ b/examples/generated/models/pet.go @@ -45,22 +45,18 @@ func (m *Pet) Validate(formats strfmt.Registry) error { var res []error if err := m.validateCategory(formats); err != nil { - // prop res = append(res, err) } if err := m.validateName(formats); err != nil { - // prop res = append(res, err) } if err := m.validatePhotoUrls(formats); err != nil { - // prop res = append(res, err) } if err := m.validateTags(formats); err != nil { - // prop res = append(res, err) } @@ -77,14 +73,12 @@ func (m *Pet) validateCategory(formats strfmt.Registry) error { } if m.Category != nil { - if err := m.Category.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("category") } return err } - } return nil @@ -115,20 +109,17 @@ func (m *Pet) validateTags(formats strfmt.Registry) error { } for i := 0; i < len(m.Tags); i++ { - if swag.IsZero(m.Tags[i]) { // not required continue } if m.Tags[i] != nil { - if err := m.Tags[i].Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("tags" + "." + strconv.Itoa(i)) } return err } - } } diff --git a/examples/generated/models/tag.go b/examples/generated/models/tag.go index 5a99f3b27e..90e79c62b0 100644 --- a/examples/generated/models/tag.go +++ b/examples/generated/models/tag.go @@ -8,7 +8,6 @@ package models import ( strfmt "github.com/go-openapi/strfmt" - "github.com/go-openapi/errors" "github.com/go-openapi/swag" ) @@ -25,11 +24,6 @@ type Tag struct { // Validate validates this tag func (m *Tag) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } return nil } diff --git a/examples/generated/models/user.go b/examples/generated/models/user.go index 3ab5920c4c..860e4ce279 100644 --- a/examples/generated/models/user.go +++ b/examples/generated/models/user.go @@ -8,7 +8,6 @@ package models import ( strfmt "github.com/go-openapi/strfmt" - "github.com/go-openapi/errors" "github.com/go-openapi/swag" ) @@ -43,11 +42,6 @@ type User struct { // Validate validates this user func (m *User) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } return nil } diff --git a/examples/generated/restapi/configure_petstore.go b/examples/generated/restapi/configure_petstore.go index 536e287b6e..fab04a4a04 100644 --- a/examples/generated/restapi/configure_petstore.go +++ b/examples/generated/restapi/configure_petstore.go @@ -9,7 +9,6 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/generated/restapi/operations" "github.com/go-swagger/go-swagger/examples/generated/restapi/operations/pet" @@ -43,21 +42,19 @@ func configureAPI(api *operations.PetstoreAPI) http.Handler { api.XMLProducer = runtime.XMLProducer() - api.PetstoreAuthAuth = func(token string, scopes []string) (interface{}, error) { - return nil, errors.NotImplemented("oauth2 bearer auth (petstore_auth) has not yet been implemented") - } - // Applies when the "api_key" header is set api.APIKeyAuth = func(token string) (interface{}, error) { return nil, errors.NotImplemented("api key auth (api_key) api_key from header param [api_key] has not yet been implemented") } + api.PetstoreAuthAuth = func(token string, scopes []string) (interface{}, error) { + return nil, errors.NotImplemented("oauth2 bearer auth (petstore_auth) has not yet been implemented") + } // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer // // Example: // api.APIAuthorizer = security.Authorized() - api.PetAddPetHandler = pet.AddPetHandlerFunc(func(params pet.AddPetParams, principal interface{}) middleware.Responder { return middleware.NotImplemented("operation pet.AddPet has not yet been implemented") }) @@ -127,7 +124,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/generated/restapi/operations/pet/add_pet_parameters.go b/examples/generated/restapi/operations/pet/add_pet_parameters.go index ab1430e6b0..c479446d46 100644 --- a/examples/generated/restapi/operations/pet/add_pet_parameters.go +++ b/examples/generated/restapi/operations/pet/add_pet_parameters.go @@ -52,7 +52,6 @@ func (o *AddPetParams) BindRequest(r *http.Request, route *middleware.MatchedRou if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/generated/restapi/operations/pet/delete_pet_parameters.go b/examples/generated/restapi/operations/pet/delete_pet_parameters.go index 216d56b7fe..5d1f18d129 100644 --- a/examples/generated/restapi/operations/pet/delete_pet_parameters.go +++ b/examples/generated/restapi/operations/pet/delete_pet_parameters.go @@ -68,6 +68,7 @@ func (o *DeletePetParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindAPIKey binds and validates parameter APIKey from header. func (o *DeletePetParams) bindAPIKey(rawData []string, hasKey bool, formats strfmt.Registry) error { if !hasKey { return errors.Required("api_key", "header") @@ -88,6 +89,7 @@ func (o *DeletePetParams) bindAPIKey(rawData []string, hasKey bool, formats strf return nil } +// bindPetID binds and validates parameter PetID from path. func (o *DeletePetParams) bindPetID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/operations/pet/find_pets_by_status_parameters.go b/examples/generated/restapi/operations/pet/find_pets_by_status_parameters.go index ea4d3b63fc..d0359e2872 100644 --- a/examples/generated/restapi/operations/pet/find_pets_by_status_parameters.go +++ b/examples/generated/restapi/operations/pet/find_pets_by_status_parameters.go @@ -60,6 +60,9 @@ func (o *FindPetsByStatusParams) BindRequest(r *http.Request, route *middleware. return nil } +// bindStatus binds and validates array parameter Status from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). func (o *FindPetsByStatusParams) bindStatus(rawData []string, hasKey bool, formats strfmt.Registry) error { // CollectionFormat: multi diff --git a/examples/generated/restapi/operations/pet/find_pets_by_tags_parameters.go b/examples/generated/restapi/operations/pet/find_pets_by_tags_parameters.go index 8cbb67a7b2..0ee5c9ca40 100644 --- a/examples/generated/restapi/operations/pet/find_pets_by_tags_parameters.go +++ b/examples/generated/restapi/operations/pet/find_pets_by_tags_parameters.go @@ -60,6 +60,9 @@ func (o *FindPetsByTagsParams) BindRequest(r *http.Request, route *middleware.Ma return nil } +// bindTags binds and validates array parameter Tags from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). func (o *FindPetsByTagsParams) bindTags(rawData []string, hasKey bool, formats strfmt.Registry) error { // CollectionFormat: multi diff --git a/examples/generated/restapi/operations/pet/get_pet_by_id_parameters.go b/examples/generated/restapi/operations/pet/get_pet_by_id_parameters.go index c9a9f66250..27f31c1bda 100644 --- a/examples/generated/restapi/operations/pet/get_pet_by_id_parameters.go +++ b/examples/generated/restapi/operations/pet/get_pet_by_id_parameters.go @@ -58,6 +58,7 @@ func (o *GetPetByIDParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindPetID binds and validates parameter PetID from path. func (o *GetPetByIDParams) bindPetID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/operations/pet/update_pet_parameters.go b/examples/generated/restapi/operations/pet/update_pet_parameters.go index 1f42788646..8df0e97693 100644 --- a/examples/generated/restapi/operations/pet/update_pet_parameters.go +++ b/examples/generated/restapi/operations/pet/update_pet_parameters.go @@ -52,7 +52,6 @@ func (o *UpdatePetParams) BindRequest(r *http.Request, route *middleware.Matched if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/generated/restapi/operations/pet/update_pet_with_form_parameters.go b/examples/generated/restapi/operations/pet/update_pet_with_form_parameters.go index 773ab1daa7..fd3b7908d5 100644 --- a/examples/generated/restapi/operations/pet/update_pet_with_form_parameters.go +++ b/examples/generated/restapi/operations/pet/update_pet_with_form_parameters.go @@ -88,6 +88,7 @@ func (o *UpdatePetWithFormParams) BindRequest(r *http.Request, route *middleware return nil } +// bindName binds and validates parameter Name from formData. func (o *UpdatePetWithFormParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { if !hasKey { return errors.Required("name", "formData") @@ -108,6 +109,7 @@ func (o *UpdatePetWithFormParams) bindName(rawData []string, hasKey bool, format return nil } +// bindPetID binds and validates parameter PetID from path. func (o *UpdatePetWithFormParams) bindPetID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -122,6 +124,7 @@ func (o *UpdatePetWithFormParams) bindPetID(rawData []string, hasKey bool, forma return nil } +// bindStatus binds and validates parameter Status from formData. func (o *UpdatePetWithFormParams) bindStatus(rawData []string, hasKey bool, formats strfmt.Registry) error { if !hasKey { return errors.Required("status", "formData") diff --git a/examples/generated/restapi/operations/petstore_api.go b/examples/generated/restapi/operations/petstore_api.go index b1da66f732..24c50fcfcd 100644 --- a/examples/generated/restapi/operations/petstore_api.go +++ b/examples/generated/restapi/operations/petstore_api.go @@ -99,14 +99,13 @@ func NewPetstoreAPI(spec *loads.Document) *PetstoreAPI { return middleware.NotImplemented("operation UserUpdateUser has not yet been implemented") }), - PetstoreAuthAuth: func(token string, scopes []string) (interface{}, error) { - return nil, errors.NotImplemented("oauth2 bearer auth (petstore_auth) has not yet been implemented") - }, - // Applies when the "api_key" header is set APIKeyAuth: func(token string) (interface{}, error) { return nil, errors.NotImplemented("api key auth (api_key) api_key from header param [api_key] has not yet been implemented") }, + PetstoreAuthAuth: func(token string, scopes []string) (interface{}, error) { + return nil, errors.NotImplemented("oauth2 bearer auth (petstore_auth) has not yet been implemented") + }, // default authorizer is authorized meaning no requests are blocked APIAuthorizer: security.Authorized(), @@ -152,14 +151,14 @@ type PetstoreAPI struct { // XMLProducer registers a producer for a "application/xml" mime type XMLProducer runtime.Producer - // PetstoreAuthAuth registers a function that takes an access token and a collection of required scopes and returns a principal - // it performs authentication based on an oauth2 bearer token provided in the request - PetstoreAuthAuth func(string, []string) (interface{}, error) - // APIKeyAuth registers a function that takes a token and returns a principal // it performs authentication based on an api key api_key provided in the header APIKeyAuth func(string) (interface{}, error) + // PetstoreAuthAuth registers a function that takes an access token and a collection of required scopes and returns a principal + // it performs authentication based on an oauth2 bearer token provided in the request + PetstoreAuthAuth func(string, []string) (interface{}, error) + // APIAuthorizer provides access control (ACL/RBAC/ABAC) by providing access to the request and authenticated principal APIAuthorizer runtime.Authorizer @@ -274,14 +273,14 @@ func (o *PetstoreAPI) Validate() error { unregistered = append(unregistered, "XMLProducer") } - if o.PetstoreAuthAuth == nil { - unregistered = append(unregistered, "PetstoreAuthAuth") - } - if o.APIKeyAuth == nil { unregistered = append(unregistered, "APIKeyAuth") } + if o.PetstoreAuthAuth == nil { + unregistered = append(unregistered, "PetstoreAuthAuth") + } + if o.PetAddPetHandler == nil { unregistered = append(unregistered, "pet.AddPetHandler") } @@ -373,14 +372,14 @@ func (o *PetstoreAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) for name, scheme := range schemes { switch name { - case "petstore_auth": - - result[name] = o.BearerAuthenticator(scheme.Name, o.PetstoreAuthAuth) - case "api_key": result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, o.APIKeyAuth) + case "petstore_auth": + + result[name] = o.BearerAuthenticator(scheme.Name, o.PetstoreAuthAuth) + } } return result diff --git a/examples/generated/restapi/operations/store/delete_order_parameters.go b/examples/generated/restapi/operations/store/delete_order_parameters.go index ea1e5272a4..9cd9f65725 100644 --- a/examples/generated/restapi/operations/store/delete_order_parameters.go +++ b/examples/generated/restapi/operations/store/delete_order_parameters.go @@ -57,6 +57,7 @@ func (o *DeleteOrderParams) BindRequest(r *http.Request, route *middleware.Match return nil } +// bindOrderID binds and validates parameter OrderID from path. func (o *DeleteOrderParams) bindOrderID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/operations/store/get_order_by_id_parameters.go b/examples/generated/restapi/operations/store/get_order_by_id_parameters.go index abded18167..145f606f7e 100644 --- a/examples/generated/restapi/operations/store/get_order_by_id_parameters.go +++ b/examples/generated/restapi/operations/store/get_order_by_id_parameters.go @@ -57,6 +57,7 @@ func (o *GetOrderByIDParams) BindRequest(r *http.Request, route *middleware.Matc return nil } +// bindOrderID binds and validates parameter OrderID from path. func (o *GetOrderByIDParams) bindOrderID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/operations/store/place_order_parameters.go b/examples/generated/restapi/operations/store/place_order_parameters.go index f48c63d533..ebb3f3558a 100644 --- a/examples/generated/restapi/operations/store/place_order_parameters.go +++ b/examples/generated/restapi/operations/store/place_order_parameters.go @@ -52,7 +52,6 @@ func (o *PlaceOrderParams) BindRequest(r *http.Request, route *middleware.Matche if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/generated/restapi/operations/user/create_user_parameters.go b/examples/generated/restapi/operations/user/create_user_parameters.go index c349150abf..20bd188804 100644 --- a/examples/generated/restapi/operations/user/create_user_parameters.go +++ b/examples/generated/restapi/operations/user/create_user_parameters.go @@ -52,7 +52,6 @@ func (o *CreateUserParams) BindRequest(r *http.Request, route *middleware.Matche if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/generated/restapi/operations/user/create_users_with_array_input_parameters.go b/examples/generated/restapi/operations/user/create_users_with_array_input_parameters.go index 58341fbeb7..180a92a7f5 100644 --- a/examples/generated/restapi/operations/user/create_users_with_array_input_parameters.go +++ b/examples/generated/restapi/operations/user/create_users_with_array_input_parameters.go @@ -52,11 +52,12 @@ func (o *CreateUsersWithArrayInputParams) BindRequest(r *http.Request, route *mi if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate array of body objects - o.Body = body - for _, io := range o.Body { - if err := io.Validate(route.Formats); err != nil { + for i := range body { + if body[i] == nil { + continue + } + if err := body[i].Validate(route.Formats); err != nil { res = append(res, err) break } diff --git a/examples/generated/restapi/operations/user/create_users_with_list_input_parameters.go b/examples/generated/restapi/operations/user/create_users_with_list_input_parameters.go index b5a4a519c0..273156fd65 100644 --- a/examples/generated/restapi/operations/user/create_users_with_list_input_parameters.go +++ b/examples/generated/restapi/operations/user/create_users_with_list_input_parameters.go @@ -52,11 +52,12 @@ func (o *CreateUsersWithListInputParams) BindRequest(r *http.Request, route *mid if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate array of body objects - o.Body = body - for _, io := range o.Body { - if err := io.Validate(route.Formats); err != nil { + for i := range body { + if body[i] == nil { + continue + } + if err := body[i].Validate(route.Formats); err != nil { res = append(res, err) break } diff --git a/examples/generated/restapi/operations/user/delete_user_parameters.go b/examples/generated/restapi/operations/user/delete_user_parameters.go index b6234a28ab..ff2c3dfd52 100644 --- a/examples/generated/restapi/operations/user/delete_user_parameters.go +++ b/examples/generated/restapi/operations/user/delete_user_parameters.go @@ -57,6 +57,7 @@ func (o *DeleteUserParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindUsername binds and validates parameter Username from path. func (o *DeleteUserParams) bindUsername(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/operations/user/get_user_by_name_parameters.go b/examples/generated/restapi/operations/user/get_user_by_name_parameters.go index 5691b2fc8e..c8f1ab8154 100644 --- a/examples/generated/restapi/operations/user/get_user_by_name_parameters.go +++ b/examples/generated/restapi/operations/user/get_user_by_name_parameters.go @@ -57,6 +57,7 @@ func (o *GetUserByNameParams) BindRequest(r *http.Request, route *middleware.Mat return nil } +// bindUsername binds and validates parameter Username from path. func (o *GetUserByNameParams) bindUsername(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/operations/user/login_user_parameters.go b/examples/generated/restapi/operations/user/login_user_parameters.go index b2db957619..f13770566c 100644 --- a/examples/generated/restapi/operations/user/login_user_parameters.go +++ b/examples/generated/restapi/operations/user/login_user_parameters.go @@ -68,6 +68,7 @@ func (o *LoginUserParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindPassword binds and validates parameter Password from query. func (o *LoginUserParams) bindPassword(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -85,6 +86,7 @@ func (o *LoginUserParams) bindPassword(rawData []string, hasKey bool, formats st return nil } +// bindUsername binds and validates parameter Username from query. func (o *LoginUserParams) bindUsername(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/operations/user/update_user_parameters.go b/examples/generated/restapi/operations/user/update_user_parameters.go index b54ce34eed..25ad01b4bd 100644 --- a/examples/generated/restapi/operations/user/update_user_parameters.go +++ b/examples/generated/restapi/operations/user/update_user_parameters.go @@ -59,7 +59,6 @@ func (o *UpdateUserParams) BindRequest(r *http.Request, route *middleware.Matche if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) @@ -81,6 +80,7 @@ func (o *UpdateUserParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindUsername binds and validates parameter Username from path. func (o *UpdateUserParams) bindUsername(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/generated/restapi/server.go b/examples/generated/restapi/server.go index a626fc25a2..d1093b2ebf 100644 --- a/examples/generated/restapi/server.go +++ b/examples/generated/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/generated/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.PetstoreAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/oauth2/README.md b/examples/oauth2/README.md index 74698e68e1..2556d35b31 100644 --- a/examples/oauth2/README.md +++ b/examples/oauth2/README.md @@ -97,8 +97,8 @@ var ( state = "foobar" // Don't make this a global in production. // the credentials for this API (adapt values when registering API) - clientID = "" - clientSecret = "" + clientID = "" // <= enter registered API client ID here + clientSecret = "" // <= enter registered API client secret here // unused in this example: the signer of the delivered token issuer = "https://accounts.google.com" @@ -213,7 +213,7 @@ We set the following implementation for authentication in `restapi/implementatio func login(r *http.Request) string { // implements the login with a redirection and an access token var accessToken string - http.Redirect(wG, r, config.AuthCodeURL(state), http.StatusFound) + wG := r.Context().Value(ctxResponseWriter).(http.ResponseWriter) log.Println("Access token:", accessToken) return accessToken } diff --git a/examples/oauth2/cmd/oauth-sample-server/main.go b/examples/oauth2/cmd/oauth-sample-server/main.go index 3a083cac43..fc0efeac12 100644 --- a/examples/oauth2/cmd/oauth-sample-server/main.go +++ b/examples/oauth2/cmd/oauth-sample-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/oauth2/restapi" "github.com/go-swagger/go-swagger/examples/oauth2/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/oauth2/models/customer.go b/examples/oauth2/models/customer.go index 79486a5dbd..6c6117703f 100644 --- a/examples/oauth2/models/customer.go +++ b/examples/oauth2/models/customer.go @@ -51,27 +51,22 @@ func (m *Customer) Validate(formats strfmt.Registry) error { var res []error if err := m.validateCustomerID(formats); err != nil { - // prop res = append(res, err) } if err := m.validateFipsCode(formats); err != nil { - // prop res = append(res, err) } if err := m.validateName(formats); err != nil { - // prop res = append(res, err) } if err := m.validateSsn(formats); err != nil { - // prop res = append(res, err) } if err := m.validateSurname(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/oauth2/models/error.go b/examples/oauth2/models/error.go index e64b2d4b74..9a60acf008 100644 --- a/examples/oauth2/models/error.go +++ b/examples/oauth2/models/error.go @@ -33,7 +33,6 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/oauth2/models/get_auth_callback_o_k_body.go b/examples/oauth2/models/get_auth_callback_o_k_body.go deleted file mode 100644 index 7623b3e2e2..0000000000 --- a/examples/oauth2/models/get_auth_callback_o_k_body.go +++ /dev/null @@ -1,49 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" -) - -// GetAuthCallbackOKBody get auth callback o k body -// swagger:model getAuthCallbackOKBody -type GetAuthCallbackOKBody struct { - - // access token - AccessToken string `json:"access_token,omitempty"` -} - -// Validate validates this get auth callback o k body -func (m *GetAuthCallbackOKBody) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -// MarshalBinary interface implementation -func (m *GetAuthCallbackOKBody) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *GetAuthCallbackOKBody) UnmarshalBinary(b []byte) error { - var res GetAuthCallbackOKBody - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/oauth2/models/get_login_o_k_body.go b/examples/oauth2/models/get_login_o_k_body.go deleted file mode 100644 index 05157eefcb..0000000000 --- a/examples/oauth2/models/get_login_o_k_body.go +++ /dev/null @@ -1,49 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" -) - -// GetLoginOKBody get login o k body -// swagger:model getLoginOKBody -type GetLoginOKBody struct { - - // access token - AccessToken string `json:"access_token,omitempty"` -} - -// Validate validates this get login o k body -func (m *GetLoginOKBody) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -// MarshalBinary interface implementation -func (m *GetLoginOKBody) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *GetLoginOKBody) UnmarshalBinary(b []byte) error { - var res GetLoginOKBody - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/oauth2/models/social_id.go b/examples/oauth2/models/social_id.go index be83defa0a..32279cc9fb 100644 --- a/examples/oauth2/models/social_id.go +++ b/examples/oauth2/models/social_id.go @@ -28,7 +28,6 @@ func (m *SocialID) Validate(formats strfmt.Registry) error { var res []error if err := m.validateSsn(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/oauth2/restapi/configure_oauth_sample.go b/examples/oauth2/restapi/configure_oauth_sample.go index f5e733c87f..9a55991368 100644 --- a/examples/oauth2/restapi/configure_oauth_sample.go +++ b/examples/oauth2/restapi/configure_oauth_sample.go @@ -1,8 +1,9 @@ -// This code has been generated by go-swagger then adapted for configuration. It is SAFE TO EDIT +// This file is safe to edit. Once it exists it will not be overwritten package restapi import ( + "context" "crypto/tls" "log" "net/http" @@ -11,14 +12,12 @@ import ( runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/swag" - graceful "github.com/tylerb/graceful" - "github.com/go-swagger/go-swagger/examples/oauth2/models" "github.com/go-swagger/go-swagger/examples/oauth2/restapi/operations" "github.com/go-swagger/go-swagger/examples/oauth2/restapi/operations/customers" -) -// This file is safe to edit. Once it exists it will not be overwritten + models "github.com/go-swagger/go-swagger/examples/oauth2/models" +) //go:generate swagger generate server --target .. --name oauthSample --spec ../swagger.yml --principal models.Principal @@ -50,6 +49,7 @@ func configureAPI(api *operations.OauthSampleAPI) http.Handler { } prin := models.Principal(token) return &prin, nil + } // Set your custom authorizer if needed. Default one is security.Authorized() @@ -57,7 +57,6 @@ func configureAPI(api *operations.OauthSampleAPI) http.Handler { // // Example: // api.APIAuthorizer = security.Authorized() - api.GetAuthCallbackHandler = operations.GetAuthCallbackHandlerFunc(func(params operations.GetAuthCallbackParams) middleware.Responder { token, err := callback(params.HTTPRequest) if err != nil { @@ -92,20 +91,27 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } -var wG http.ResponseWriter +// This demonstrates how to enrich and pass custom context keys. +// In this case, we cache the current responseWriter in context. +type customContextKey int8 + +const ( + _ customContextKey = iota + ctxResponseWriter +) // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. // The middleware executes after routing but before authentication, binding and validation func setupMiddlewares(handler http.Handler) http.Handler { ourFunc := func(w http.ResponseWriter, r *http.Request) { - wG = w - handler.ServeHTTP(w, r) + rctx := context.WithValue(r.Context(), ctxResponseWriter, w) + handler.ServeHTTP(w, r.WithContext(rctx)) } return http.HandlerFunc(ourFunc) - // return handler + } // The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document. diff --git a/examples/oauth2/restapi/embedded_spec.go b/examples/oauth2/restapi/embedded_spec.go index 12072eaeab..d35be4a8b0 100644 --- a/examples/oauth2/restapi/embedded_spec.go +++ b/examples/oauth2/restapi/embedded_spec.go @@ -278,7 +278,12 @@ func init() { "200": { "description": "login", "schema": { - "$ref": "#/definitions/getAuthCallbackOKBody" + "properties": { + "access_token": { + "type": "string", + "format": "string" + } + } } }, "default": { @@ -372,7 +377,12 @@ func init() { "200": { "description": "login", "schema": { - "$ref": "#/definitions/getLoginOKBody" + "properties": { + "access_token": { + "type": "string", + "format": "string" + } + } } }, "default": { @@ -448,24 +458,6 @@ func init() { } } }, - "getAuthCallbackOKBody": { - "properties": { - "access_token": { - "type": "string", - "format": "string" - } - }, - "x-go-gen-location": "operations" - }, - "getLoginOKBody": { - "properties": { - "access_token": { - "type": "string", - "format": "string" - } - }, - "x-go-gen-location": "operations" - }, "principal": { "type": "string" }, diff --git a/examples/oauth2/restapi/implementation.go b/examples/oauth2/restapi/implementation.go index 787ff27eee..c5416936b0 100644 --- a/examples/oauth2/restapi/implementation.go +++ b/examples/oauth2/restapi/implementation.go @@ -23,12 +23,13 @@ var ( clientSecret = "" // <= enter registered API client secret here // unused in this example: the signer of the delivered token - issuer = "https://accounts.google.com" + // issuer = "https://accounts.google.com" // the Google login URL authURL = "https://accounts.google.com/o/oauth2/v2/auth" // the Google OAuth2 resource provider which delivers access tokens + /* #nosec */ tokenURL = "https://www.googleapis.com/oauth2/v4/token" userInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo" @@ -51,8 +52,9 @@ var ( ) func login(r *http.Request) string { - // implements the login with a redirection and an access tokeb + // implements the login with a redirection and an access token var accessToken string + wG := r.Context().Value(ctxResponseWriter).(http.ResponseWriter) http.Redirect(wG, r, config.AuthCodeURL(state), http.StatusFound) return accessToken } diff --git a/examples/oauth2/restapi/operations/customers/create_parameters.go b/examples/oauth2/restapi/operations/customers/create_parameters.go index 0a4656675d..fffa7b2033 100644 --- a/examples/oauth2/restapi/operations/customers/create_parameters.go +++ b/examples/oauth2/restapi/operations/customers/create_parameters.go @@ -52,7 +52,6 @@ func (o *CreateParams) BindRequest(r *http.Request, route *middleware.MatchedRou if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("info", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/oauth2/restapi/operations/customers/get_id_parameters.go b/examples/oauth2/restapi/operations/customers/get_id_parameters.go index a39c8c74e7..fcd34469af 100644 --- a/examples/oauth2/restapi/operations/customers/get_id_parameters.go +++ b/examples/oauth2/restapi/operations/customers/get_id_parameters.go @@ -52,7 +52,6 @@ func (o *GetIDParams) BindRequest(r *http.Request, route *middleware.MatchedRout if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("info", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/oauth2/restapi/operations/get_auth_callback.go b/examples/oauth2/restapi/operations/get_auth_callback.go index 28a2af6126..a53f40e786 100644 --- a/examples/oauth2/restapi/operations/get_auth_callback.go +++ b/examples/oauth2/restapi/operations/get_auth_callback.go @@ -9,6 +9,8 @@ import ( "net/http" middleware "github.com/go-openapi/runtime/middleware" + strfmt "github.com/go-openapi/strfmt" + swag "github.com/go-openapi/swag" ) // GetAuthCallbackHandlerFunc turns a function with the right signature into a get auth callback handler @@ -56,3 +58,34 @@ func (o *GetAuthCallback) ServeHTTP(rw http.ResponseWriter, r *http.Request) { o.Context.Respond(rw, r, route.Produces, route, res) } + +// GetAuthCallbackOKBody get auth callback o k body +// swagger:model GetAuthCallbackOKBody +type GetAuthCallbackOKBody struct { + + // access token + AccessToken string `json:"access_token,omitempty"` +} + +// Validate validates this get auth callback o k body +func (o *GetAuthCallbackOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *GetAuthCallbackOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GetAuthCallbackOKBody) UnmarshalBinary(b []byte) error { + var res GetAuthCallbackOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/examples/oauth2/restapi/operations/get_auth_callback_responses.go b/examples/oauth2/restapi/operations/get_auth_callback_responses.go index 935d47d796..6e98db5517 100644 --- a/examples/oauth2/restapi/operations/get_auth_callback_responses.go +++ b/examples/oauth2/restapi/operations/get_auth_callback_responses.go @@ -25,7 +25,7 @@ type GetAuthCallbackOK struct { /* In: Body */ - Payload *models.GetAuthCallbackOKBody `json:"body,omitempty"` + Payload *GetAuthCallbackOKBody `json:"body,omitempty"` } // NewGetAuthCallbackOK creates GetAuthCallbackOK with default headers values @@ -35,13 +35,13 @@ func NewGetAuthCallbackOK() *GetAuthCallbackOK { } // WithPayload adds the payload to the get auth callback o k response -func (o *GetAuthCallbackOK) WithPayload(payload *models.GetAuthCallbackOKBody) *GetAuthCallbackOK { +func (o *GetAuthCallbackOK) WithPayload(payload *GetAuthCallbackOKBody) *GetAuthCallbackOK { o.Payload = payload return o } // SetPayload sets the payload to the get auth callback o k response -func (o *GetAuthCallbackOK) SetPayload(payload *models.GetAuthCallbackOKBody) { +func (o *GetAuthCallbackOK) SetPayload(payload *GetAuthCallbackOKBody) { o.Payload = payload } diff --git a/examples/oauth2/restapi/operations/get_login.go b/examples/oauth2/restapi/operations/get_login.go index 3cd6521630..0a50aceb34 100644 --- a/examples/oauth2/restapi/operations/get_login.go +++ b/examples/oauth2/restapi/operations/get_login.go @@ -9,6 +9,8 @@ import ( "net/http" middleware "github.com/go-openapi/runtime/middleware" + strfmt "github.com/go-openapi/strfmt" + swag "github.com/go-openapi/swag" ) // GetLoginHandlerFunc turns a function with the right signature into a get login handler @@ -56,3 +58,34 @@ func (o *GetLogin) ServeHTTP(rw http.ResponseWriter, r *http.Request) { o.Context.Respond(rw, r, route.Produces, route, res) } + +// GetLoginOKBody get login o k body +// swagger:model GetLoginOKBody +type GetLoginOKBody struct { + + // access token + AccessToken string `json:"access_token,omitempty"` +} + +// Validate validates this get login o k body +func (o *GetLoginOKBody) Validate(formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (o *GetLoginOKBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *GetLoginOKBody) UnmarshalBinary(b []byte) error { + var res GetLoginOKBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/examples/oauth2/restapi/operations/get_login_responses.go b/examples/oauth2/restapi/operations/get_login_responses.go index b6ee1f1a06..4916859d77 100644 --- a/examples/oauth2/restapi/operations/get_login_responses.go +++ b/examples/oauth2/restapi/operations/get_login_responses.go @@ -25,7 +25,7 @@ type GetLoginOK struct { /* In: Body */ - Payload *models.GetLoginOKBody `json:"body,omitempty"` + Payload *GetLoginOKBody `json:"body,omitempty"` } // NewGetLoginOK creates GetLoginOK with default headers values @@ -35,13 +35,13 @@ func NewGetLoginOK() *GetLoginOK { } // WithPayload adds the payload to the get login o k response -func (o *GetLoginOK) WithPayload(payload *models.GetLoginOKBody) *GetLoginOK { +func (o *GetLoginOK) WithPayload(payload *GetLoginOKBody) *GetLoginOK { o.Payload = payload return o } // SetPayload sets the payload to the get login o k response -func (o *GetLoginOK) SetPayload(payload *models.GetLoginOKBody) { +func (o *GetLoginOK) SetPayload(payload *GetLoginOKBody) { o.Payload = payload } diff --git a/examples/oauth2/restapi/server.go b/examples/oauth2/restapi/server.go index 78cd2eb36a..f632d0e0bc 100644 --- a/examples/oauth2/restapi/server.go +++ b/examples/oauth2/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/oauth2/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.OauthSampleAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/stream-server/biz/count.go b/examples/stream-server/biz/count.go index 394cf5ea89..c14d2ddae4 100644 --- a/examples/stream-server/biz/count.go +++ b/examples/stream-server/biz/count.go @@ -21,7 +21,7 @@ func (mc *MyCounter) Down(max int64, w io.Writer) error { for ix := int64(0); ix <= max; ix++ { r := max - ix fmt.Printf("Iteration %d\n", r) - e.Encode(models.Mark{Remains: &r}) + _ = e.Encode(models.Mark{Remains: &r}) if ix != max { time.Sleep(1 * time.Second) } diff --git a/examples/stream-server/cmd/countdown-server/main.go b/examples/stream-server/cmd/countdown-server/main.go index f3fecb6bc3..12123bbd4d 100644 --- a/examples/stream-server/cmd/countdown-server/main.go +++ b/examples/stream-server/cmd/countdown-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/stream-server/restapi" "github.com/go-swagger/go-swagger/examples/stream-server/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/stream-server/models/mark.go b/examples/stream-server/models/mark.go index 8e71c750ae..b6c55c4840 100644 --- a/examples/stream-server/models/mark.go +++ b/examples/stream-server/models/mark.go @@ -27,7 +27,6 @@ func (m *Mark) Validate(formats strfmt.Registry) error { var res []error if err := m.validateRemains(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/stream-server/restapi/configure_countdown.go b/examples/stream-server/restapi/configure_countdown.go index 8516dce27c..6275b864c6 100644 --- a/examples/stream-server/restapi/configure_countdown.go +++ b/examples/stream-server/restapi/configure_countdown.go @@ -1,4 +1,4 @@ -// Code generated by go-swagger; DO NOT EDIT. +// This file is safe to edit. Once it exists it will not be overwritten package restapi @@ -10,14 +10,11 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" - "github.com/go-swagger/go-swagger/examples/stream-server/biz" + "github.com/go-swagger/go-swagger/examples/stream-server/restapi/operations" ) -// This file is safe to edit. Once it exists it will not be overwritten - //go:generate swagger generate server --target .. --name Countdown --spec ../swagger.yml func configureFlags(api *operations.CountdownAPI) { @@ -46,8 +43,9 @@ func configureAPI(api *operations.CountdownAPI) http.Handler { return middleware.ResponderFunc(func(rw http.ResponseWriter, p runtime.Producer) { f, _ := rw.(http.Flusher) rw.WriteHeader(200) - myCounter.Down(params.Length, &flushWriter{f, rw}) + _ = myCounter.Down(params.Length, &flushWriter{f: f, w: rw}) }) + }) api.ServerShutdown = func() {} @@ -79,7 +77,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/stream-server/restapi/operations/elapse_parameters.go b/examples/stream-server/restapi/operations/elapse_parameters.go index 58a2b43cf1..bd59d2a1a0 100644 --- a/examples/stream-server/restapi/operations/elapse_parameters.go +++ b/examples/stream-server/restapi/operations/elapse_parameters.go @@ -61,6 +61,7 @@ func (o *ElapseParams) BindRequest(r *http.Request, route *middleware.MatchedRou return nil } +// bindLength binds and validates parameter Length from path. func (o *ElapseParams) bindLength(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -83,6 +84,7 @@ func (o *ElapseParams) bindLength(rawData []string, hasKey bool, formats strfmt. return nil } +// validateLength carries on validations for parameter Length func (o *ElapseParams) validateLength(formats strfmt.Registry) error { if err := validate.MinimumInt("length", "path", int64(o.Length), 2, false); err != nil { diff --git a/examples/stream-server/restapi/server.go b/examples/stream-server/restapi/server.go index 6f4844ee51..646718db95 100644 --- a/examples/stream-server/restapi/server.go +++ b/examples/stream-server/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/stream-server/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.CountdownAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/task-tracker/client/tasks/add_comment_to_task_parameters.go b/examples/task-tracker/client/tasks/add_comment_to_task_parameters.go index 3b1c9d4fa9..7fe67ff0cc 100644 --- a/examples/task-tracker/client/tasks/add_comment_to_task_parameters.go +++ b/examples/task-tracker/client/tasks/add_comment_to_task_parameters.go @@ -17,8 +17,6 @@ import ( "github.com/go-openapi/swag" strfmt "github.com/go-openapi/strfmt" - - models "github.com/go-swagger/go-swagger/examples/task-tracker/models" ) // NewAddCommentToTaskParams creates a new AddCommentToTaskParams object @@ -69,7 +67,7 @@ type AddCommentToTaskParams struct { The comment to add */ - Body *models.AddCommentToTaskParamsBody + Body AddCommentToTaskBody /*ID The id of the item @@ -115,13 +113,13 @@ func (o *AddCommentToTaskParams) SetHTTPClient(client *http.Client) { } // WithBody adds the body to the add comment to task params -func (o *AddCommentToTaskParams) WithBody(body *models.AddCommentToTaskParamsBody) *AddCommentToTaskParams { +func (o *AddCommentToTaskParams) WithBody(body AddCommentToTaskBody) *AddCommentToTaskParams { o.SetBody(body) return o } // SetBody adds the body to the add comment to task params -func (o *AddCommentToTaskParams) SetBody(body *models.AddCommentToTaskParamsBody) { +func (o *AddCommentToTaskParams) SetBody(body AddCommentToTaskBody) { o.Body = body } @@ -144,10 +142,8 @@ func (o *AddCommentToTaskParams) WriteToRequest(r runtime.ClientRequest, reg str } var res []error - if o.Body != nil { - if err := r.SetBodyParam(o.Body); err != nil { - return err - } + if err := r.SetBodyParam(o.Body); err != nil { + return err } // path param id diff --git a/examples/task-tracker/client/tasks/add_comment_to_task_responses.go b/examples/task-tracker/client/tasks/add_comment_to_task_responses.go index ba74b50670..2d3609f3b7 100644 --- a/examples/task-tracker/client/tasks/add_comment_to_task_responses.go +++ b/examples/task-tracker/client/tasks/add_comment_to_task_responses.go @@ -9,7 +9,10 @@ import ( "fmt" "io" + "github.com/go-openapi/errors" "github.com/go-openapi/runtime" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" strfmt "github.com/go-openapi/strfmt" @@ -107,3 +110,74 @@ func (o *AddCommentToTaskDefault) readResponse(response runtime.ClientResponse, return nil } + +/*AddCommentToTaskBody A comment to create +// +// These values can have github flavored markdown. +// +swagger:model AddCommentToTaskBody +*/ +type AddCommentToTaskBody struct { + + // content + // Required: true + Content *string `json:"content"` + + // user Id + // Required: true + UserID *int64 `json:"userId"` +} + +// Validate validates this add comment to task body +func (o *AddCommentToTaskBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateContent(formats); err != nil { + res = append(res, err) + } + + if err := o.validateUserID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *AddCommentToTaskBody) validateContent(formats strfmt.Registry) error { + + if err := validate.Required("body"+"."+"content", "body", o.Content); err != nil { + return err + } + + return nil +} + +func (o *AddCommentToTaskBody) validateUserID(formats strfmt.Registry) error { + + if err := validate.Required("body"+"."+"userId", "body", o.UserID); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (o *AddCommentToTaskBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *AddCommentToTaskBody) UnmarshalBinary(b []byte) error { + var res AddCommentToTaskBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/examples/task-tracker/cmd/task-tracker-server/main.go b/examples/task-tracker/cmd/task-tracker-server/main.go index 046aa674e5..722f3ae926 100644 --- a/examples/task-tracker/cmd/task-tracker-server/main.go +++ b/examples/task-tracker/cmd/task-tracker-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/task-tracker/restapi" "github.com/go-swagger/go-swagger/examples/task-tracker/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/task-tracker/models/add_comment_to_task_params_body.go b/examples/task-tracker/models/add_comment_to_task_params_body.go deleted file mode 100644 index e232afba5a..0000000000 --- a/examples/task-tracker/models/add_comment_to_task_params_body.go +++ /dev/null @@ -1,86 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// AddCommentToTaskParamsBody A comment to create -// -// These values can have github flavored markdown. -// -// swagger:model addCommentToTaskParamsBody -type AddCommentToTaskParamsBody struct { - - // content - // Required: true - Content *string `json:"content"` - - // user Id - // Required: true - UserID *int64 `json:"userId"` -} - -// Validate validates this add comment to task params body -func (m *AddCommentToTaskParamsBody) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateContent(formats); err != nil { - // prop - res = append(res, err) - } - - if err := m.validateUserID(formats); err != nil { - // prop - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *AddCommentToTaskParamsBody) validateContent(formats strfmt.Registry) error { - - if err := validate.Required("content", "body", m.Content); err != nil { - return err - } - - return nil -} - -func (m *AddCommentToTaskParamsBody) validateUserID(formats strfmt.Registry) error { - - if err := validate.Required("userId", "body", m.UserID); err != nil { - return err - } - - return nil -} - -// MarshalBinary interface implementation -func (m *AddCommentToTaskParamsBody) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *AddCommentToTaskParamsBody) UnmarshalBinary(b []byte) error { - var res AddCommentToTaskParamsBody - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/task-tracker/models/comment.go b/examples/task-tracker/models/comment.go index 4dde2227d2..711bc80e0a 100644 --- a/examples/task-tracker/models/comment.go +++ b/examples/task-tracker/models/comment.go @@ -31,6 +31,7 @@ type Comment struct { // // This field is autogenerated when the content is posted. // Read Only: true + // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` // user @@ -43,17 +44,14 @@ func (m *Comment) Validate(formats strfmt.Registry) error { var res []error if err := m.validateContent(formats); err != nil { - // prop res = append(res, err) } if err := m.validateCreatedAt(formats); err != nil { - // prop res = append(res, err) } if err := m.validateUser(formats); err != nil { - // prop res = append(res, err) } @@ -92,14 +90,12 @@ func (m *Comment) validateUser(formats strfmt.Registry) error { } if m.User != nil { - if err := m.User.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("user") } return err } - } return nil diff --git a/examples/task-tracker/models/error.go b/examples/task-tracker/models/error.go index 478cdf5596..1e8e8747b6 100644 --- a/examples/task-tracker/models/error.go +++ b/examples/task-tracker/models/error.go @@ -26,6 +26,7 @@ type Error struct { Code *int32 `json:"code"` // an optional url for getting more help about this error + // Format: uri HelpURL strfmt.URI `json:"helpUrl,omitempty"` // a human readable version of the error @@ -38,17 +39,14 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateCode(formats); err != nil { - // prop res = append(res, err) } if err := m.validateHelpURL(formats); err != nil { - // prop res = append(res, err) } if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/task-tracker/models/milestone.go b/examples/task-tracker/models/milestone.go index 41a2a86100..a803927c30 100644 --- a/examples/task-tracker/models/milestone.go +++ b/examples/task-tracker/models/milestone.go @@ -31,6 +31,7 @@ type Milestone struct { // // This property is optional, but when present it lets people know when they can expect this milestone to be completed. // + // Format: date DueDate strfmt.Date `json:"dueDate,omitempty"` // The name of the milestone. @@ -52,17 +53,14 @@ func (m *Milestone) Validate(formats strfmt.Registry) error { var res []error if err := m.validateDueDate(formats); err != nil { - // prop res = append(res, err) } if err := m.validateName(formats); err != nil { - // prop res = append(res, err) } if err := m.validateStats(formats); err != nil { - // prop res = append(res, err) } @@ -113,14 +111,12 @@ func (m *Milestone) validateStats(formats strfmt.Registry) error { } if m.Stats != nil { - if err := m.Stats.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("stats") } return err } - } return nil @@ -143,3 +139,43 @@ func (m *Milestone) UnmarshalBinary(b []byte) error { *m = res return nil } + +// MilestoneStats Some counters for this milestone. +// +// This object contains counts for the remaining open issues and the amount of issues that have been closed. +// +// swagger:model MilestoneStats +type MilestoneStats struct { + + // The closed issues. + Closed int32 `json:"closed,omitempty"` + + // The remaining open issues. + Open int32 `json:"open,omitempty"` + + // The total number of issues for this milestone. + Total int32 `json:"total,omitempty"` +} + +// Validate validates this milestone stats +func (m *MilestoneStats) Validate(formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *MilestoneStats) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MilestoneStats) UnmarshalBinary(b []byte) error { + var res MilestoneStats + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/examples/task-tracker/models/milestone_stats.go b/examples/task-tracker/models/milestone_stats.go deleted file mode 100644 index 0a04f2a860..0000000000 --- a/examples/task-tracker/models/milestone_stats.go +++ /dev/null @@ -1,58 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" -) - -// MilestoneStats Some counters for this milestone. -// -// This object contains counts for the remaining open issues and the amount of issues that have been closed. -// -// swagger:model milestoneStats -type MilestoneStats struct { - - // The closed issues. - Closed int32 `json:"closed,omitempty"` - - // The remaining open issues. - Open int32 `json:"open,omitempty"` - - // The total number of issues for this milestone. - Total int32 `json:"total,omitempty"` -} - -// Validate validates this milestone stats -func (m *MilestoneStats) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -// MarshalBinary interface implementation -func (m *MilestoneStats) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *MilestoneStats) UnmarshalBinary(b []byte) error { - var res MilestoneStats - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/task-tracker/models/task.go b/examples/task-tracker/models/task.go index 33ee8332c9..1db0f0c937 100644 --- a/examples/task-tracker/models/task.go +++ b/examples/task-tracker/models/task.go @@ -6,10 +6,13 @@ package models // Editing this file might prove futile when you re-run the swagger generate command import ( + "strconv" + strfmt "github.com/go-openapi/strfmt" "github.com/go-openapi/errors" "github.com/go-openapi/swag" + "github.com/go-openapi/validate" ) // Task a structure describing a complete task. @@ -20,30 +23,76 @@ import ( type Task struct { TaskCard - TaskAllOf1 + // The attached files. + // + // An issue can have at most 20 files attached to it. + // + Attachments map[string]TaskAttachmentsAnon `json:"attachments,omitempty"` + + // The 5 most recent items for this issue. + // + // The detail view of an issue includes the 5 most recent comments. + // This field is read only, comments are added through a separate process. + // + // Read Only: true + Comments []*Comment `json:"comments"` + + // The time at which this issue was last updated. + // + // This field is read only so it's only sent as part of the response. + // + // Read Only: true + // Format: date-time + LastUpdated strfmt.DateTime `json:"lastUpdated,omitempty"` + + // last updated by + LastUpdatedBy *UserCard `json:"lastUpdatedBy,omitempty"` + + // reported by + ReportedBy *UserCard `json:"reportedBy,omitempty"` } // UnmarshalJSON unmarshals this object from a JSON structure func (m *Task) UnmarshalJSON(raw []byte) error { - + // AO0 var aO0 TaskCard if err := swag.ReadJSON(raw, &aO0); err != nil { return err } m.TaskCard = aO0 - var aO1 TaskAllOf1 - if err := swag.ReadJSON(raw, &aO1); err != nil { + // AO1 + var dataAO1 struct { + Attachments map[string]TaskAttachmentsAnon `json:"attachments,omitempty"` + + Comments []*Comment `json:"comments,omitempty"` + + LastUpdated strfmt.DateTime `json:"lastUpdated,omitempty"` + + LastUpdatedBy *UserCard `json:"lastUpdatedBy,omitempty"` + + ReportedBy *UserCard `json:"reportedBy,omitempty"` + } + if err := swag.ReadJSON(raw, &dataAO1); err != nil { return err } - m.TaskAllOf1 = aO1 + + m.Attachments = dataAO1.Attachments + + m.Comments = dataAO1.Comments + + m.LastUpdated = dataAO1.LastUpdated + + m.LastUpdatedBy = dataAO1.LastUpdatedBy + + m.ReportedBy = dataAO1.ReportedBy return nil } // MarshalJSON marshals this object to a JSON structure func (m Task) MarshalJSON() ([]byte, error) { - var _parts [][]byte + _parts := make([][]byte, 0, 2) aO0, err := swag.WriteJSON(m.TaskCard) if err != nil { @@ -51,11 +100,33 @@ func (m Task) MarshalJSON() ([]byte, error) { } _parts = append(_parts, aO0) - aO1, err := swag.WriteJSON(m.TaskAllOf1) - if err != nil { - return nil, err + var dataAO1 struct { + Attachments map[string]TaskAttachmentsAnon `json:"attachments,omitempty"` + + Comments []*Comment `json:"comments,omitempty"` + + LastUpdated strfmt.DateTime `json:"lastUpdated,omitempty"` + + LastUpdatedBy *UserCard `json:"lastUpdatedBy,omitempty"` + + ReportedBy *UserCard `json:"reportedBy,omitempty"` } - _parts = append(_parts, aO1) + + dataAO1.Attachments = m.Attachments + + dataAO1.Comments = m.Comments + + dataAO1.LastUpdated = m.LastUpdated + + dataAO1.LastUpdatedBy = m.LastUpdatedBy + + dataAO1.ReportedBy = m.ReportedBy + + jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1) + if errAO1 != nil { + return nil, errAO1 + } + _parts = append(_parts, jsonDataAO1) return swag.ConcatJSON(_parts...), nil } @@ -64,11 +135,28 @@ func (m Task) MarshalJSON() ([]byte, error) { func (m *Task) Validate(formats strfmt.Registry) error { var res []error + // validation for a type composition with TaskCard if err := m.TaskCard.Validate(formats); err != nil { res = append(res, err) } - if err := m.TaskAllOf1.Validate(formats); err != nil { + if err := m.validateAttachments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateComments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateLastUpdated(formats); err != nil { + res = append(res, err) + } + + if err := m.validateLastUpdatedBy(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReportedBy(formats); err != nil { res = append(res, err) } @@ -78,6 +166,102 @@ func (m *Task) Validate(formats strfmt.Registry) error { return nil } +func (m *Task) validateAttachments(formats strfmt.Registry) error { + + if swag.IsZero(m.Attachments) { // not required + return nil + } + + for k := range m.Attachments { + + if swag.IsZero(m.Attachments[k]) { // not required + continue + } + if val, ok := m.Attachments[k]; ok { + if err := val.Validate(formats); err != nil { + return err + } + } + + } + + return nil +} + +func (m *Task) validateComments(formats strfmt.Registry) error { + + if swag.IsZero(m.Comments) { // not required + return nil + } + + for i := 0; i < len(m.Comments); i++ { + if swag.IsZero(m.Comments[i]) { // not required + continue + } + + if m.Comments[i] != nil { + if err := m.Comments[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("comments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +func (m *Task) validateLastUpdated(formats strfmt.Registry) error { + + if swag.IsZero(m.LastUpdated) { // not required + return nil + } + + if err := validate.FormatOf("lastUpdated", "body", "date-time", m.LastUpdated.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *Task) validateLastUpdatedBy(formats strfmt.Registry) error { + + if swag.IsZero(m.LastUpdatedBy) { // not required + return nil + } + + if m.LastUpdatedBy != nil { + if err := m.LastUpdatedBy.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("lastUpdatedBy") + } + return err + } + } + + return nil +} + +func (m *Task) validateReportedBy(formats strfmt.Registry) error { + + if swag.IsZero(m.ReportedBy) { // not required + return nil + } + + if m.ReportedBy != nil { + if err := m.ReportedBy.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("reportedBy") + } + return err + } + } + + return nil +} + // MarshalBinary interface implementation func (m *Task) MarshalBinary() ([]byte, error) { if m == nil { @@ -95,3 +279,105 @@ func (m *Task) UnmarshalBinary(b []byte) error { *m = res return nil } + +// TaskAttachmentsAnon task attachments anon +// swagger:model TaskAttachmentsAnon +type TaskAttachmentsAnon struct { + + // The content type of the file. + // + // The content type of the file is inferred from the upload request. + // + // Read Only: true + ContentType string `json:"contentType,omitempty"` + + // Extra information to attach to the file. + // + // This is a free form text field with support for github flavored markdown. + // + // Min Length: 3 + Description string `json:"description,omitempty"` + + // The name of the file. + // + // This name is inferred from the upload request. + // + // Read Only: true + Name string `json:"name,omitempty"` + + // The file size in bytes. + // + // This property was generated during the upload request of the file. + // Read Only: true + Size float64 `json:"size,omitempty"` + + // The url to download or view the file. + // + // This URL is generated on the server, based on where it was able to store the file when it was uploaded. + // + // Read Only: true + // Format: uri + URL strfmt.URI `json:"url,omitempty"` +} + +// Validate validates this task attachments anon +func (m *TaskAttachmentsAnon) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateDescription(formats); err != nil { + res = append(res, err) + } + + if err := m.validateURL(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TaskAttachmentsAnon) validateDescription(formats strfmt.Registry) error { + + if swag.IsZero(m.Description) { // not required + return nil + } + + if err := validate.MinLength("description", "body", string(m.Description), 3); err != nil { + return err + } + + return nil +} + +func (m *TaskAttachmentsAnon) validateURL(formats strfmt.Registry) error { + + if swag.IsZero(m.URL) { // not required + return nil + } + + if err := validate.FormatOf("url", "body", "uri", m.URL.String(), formats); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *TaskAttachmentsAnon) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *TaskAttachmentsAnon) UnmarshalBinary(b []byte) error { + var res TaskAttachmentsAnon + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/examples/task-tracker/models/task_all_of1.go b/examples/task-tracker/models/task_all_of1.go deleted file mode 100644 index 63d66a1093..0000000000 --- a/examples/task-tracker/models/task_all_of1.go +++ /dev/null @@ -1,174 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "strconv" - - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// TaskAllOf1 task all of1 -// swagger:model taskAllOf1 -type TaskAllOf1 struct { - - // attachments - Attachments TaskAllOf1Attachments `json:"attachments,omitempty"` - - // The 5 most recent items for this issue. - // - // The detail view of an issue includes the 5 most recent comments. - // This field is read only, comments are added through a separate process. - // - // Read Only: true - Comments []*Comment `json:"comments"` - - // The time at which this issue was last updated. - // - // This field is read only so it's only sent as part of the response. - // - // Read Only: true - LastUpdated strfmt.DateTime `json:"lastUpdated,omitempty"` - - // last updated by - LastUpdatedBy *UserCard `json:"lastUpdatedBy,omitempty"` - - // reported by - ReportedBy *UserCard `json:"reportedBy,omitempty"` -} - -// Validate validates this task all of1 -func (m *TaskAllOf1) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateComments(formats); err != nil { - // prop - res = append(res, err) - } - - if err := m.validateLastUpdated(formats); err != nil { - // prop - res = append(res, err) - } - - if err := m.validateLastUpdatedBy(formats); err != nil { - // prop - res = append(res, err) - } - - if err := m.validateReportedBy(formats); err != nil { - // prop - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *TaskAllOf1) validateComments(formats strfmt.Registry) error { - - if swag.IsZero(m.Comments) { // not required - return nil - } - - for i := 0; i < len(m.Comments); i++ { - - if swag.IsZero(m.Comments[i]) { // not required - continue - } - - if m.Comments[i] != nil { - - if err := m.Comments[i].Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("comments" + "." + strconv.Itoa(i)) - } - return err - } - - } - - } - - return nil -} - -func (m *TaskAllOf1) validateLastUpdated(formats strfmt.Registry) error { - - if swag.IsZero(m.LastUpdated) { // not required - return nil - } - - if err := validate.FormatOf("lastUpdated", "body", "date-time", m.LastUpdated.String(), formats); err != nil { - return err - } - - return nil -} - -func (m *TaskAllOf1) validateLastUpdatedBy(formats strfmt.Registry) error { - - if swag.IsZero(m.LastUpdatedBy) { // not required - return nil - } - - if m.LastUpdatedBy != nil { - - if err := m.LastUpdatedBy.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("lastUpdatedBy") - } - return err - } - - } - - return nil -} - -func (m *TaskAllOf1) validateReportedBy(formats strfmt.Registry) error { - - if swag.IsZero(m.ReportedBy) { // not required - return nil - } - - if m.ReportedBy != nil { - - if err := m.ReportedBy.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("reportedBy") - } - return err - } - - } - - return nil -} - -// MarshalBinary interface implementation -func (m *TaskAllOf1) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *TaskAllOf1) UnmarshalBinary(b []byte) error { - var res TaskAllOf1 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/task-tracker/models/task_all_of1_attachments.go b/examples/task-tracker/models/task_all_of1_attachments.go deleted file mode 100644 index fec543e90d..0000000000 --- a/examples/task-tracker/models/task_all_of1_attachments.go +++ /dev/null @@ -1,34 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/validate" -) - -// TaskAllOf1Attachments The attached files. -// -// An issue can have at most 20 files attached to it. -// -// swagger:model taskAllOf1Attachments -type TaskAllOf1Attachments map[string]TaskAllOf1AttachmentsAdditionalProperties - -// Validate validates this task all of1 attachments -func (m TaskAllOf1Attachments) Validate(formats strfmt.Registry) error { - var res []error - - if err := validate.Required("", "body", TaskAllOf1Attachments(m)); err != nil { - return err - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/examples/task-tracker/models/task_all_of1_attachments_additional_properties.go b/examples/task-tracker/models/task_all_of1_attachments_additional_properties.go deleted file mode 100644 index dd6eed6570..0000000000 --- a/examples/task-tracker/models/task_all_of1_attachments_additional_properties.go +++ /dev/null @@ -1,117 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" - "github.com/go-openapi/validate" -) - -// TaskAllOf1AttachmentsAdditionalProperties task all of1 attachments additional properties -// swagger:model taskAllOf1AttachmentsAdditionalProperties -type TaskAllOf1AttachmentsAdditionalProperties struct { - - // The content type of the file. - // - // The content type of the file is inferred from the upload request. - // - // Read Only: true - ContentType string `json:"contentType,omitempty"` - - // Extra information to attach to the file. - // - // This is a free form text field with support for github flavored markdown. - // - // Min Length: 3 - Description string `json:"description,omitempty"` - - // The name of the file. - // - // This name is inferred from the upload request. - // - // Read Only: true - Name string `json:"name,omitempty"` - - // The file size in bytes. - // - // This property was generated during the upload request of the file. - // Read Only: true - Size float64 `json:"size,omitempty"` - - // The url to download or view the file. - // - // This URL is generated on the server, based on where it was able to store the file when it was uploaded. - // - // Read Only: true - URL strfmt.URI `json:"url,omitempty"` -} - -// Validate validates this task all of1 attachments additional properties -func (m *TaskAllOf1AttachmentsAdditionalProperties) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateDescription(formats); err != nil { - // prop - res = append(res, err) - } - - if err := m.validateURL(formats); err != nil { - // prop - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *TaskAllOf1AttachmentsAdditionalProperties) validateDescription(formats strfmt.Registry) error { - - if swag.IsZero(m.Description) { // not required - return nil - } - - if err := validate.MinLength("description", "body", string(m.Description), 3); err != nil { - return err - } - - return nil -} - -func (m *TaskAllOf1AttachmentsAdditionalProperties) validateURL(formats strfmt.Registry) error { - - if swag.IsZero(m.URL) { // not required - return nil - } - - if err := validate.FormatOf("url", "body", "uri", m.URL.String(), formats); err != nil { - return err - } - - return nil -} - -// MarshalBinary interface implementation -func (m *TaskAllOf1AttachmentsAdditionalProperties) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *TaskAllOf1AttachmentsAdditionalProperties) UnmarshalBinary(b []byte) error { - var res TaskAllOf1AttachmentsAdditionalProperties - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/task-tracker/models/task_card.go b/examples/task-tracker/models/task_card.go index 14f65d9055..d786c34967 100644 --- a/examples/task-tracker/models/task_card.go +++ b/examples/task-tracker/models/task_card.go @@ -61,6 +61,7 @@ type TaskCard struct { // This field is read-only, so it's only sent as part of the response. // // Read Only: true + // Format: date-time ReportedAt strfmt.DateTime `json:"reportedAt,omitempty"` // severity @@ -74,6 +75,7 @@ type TaskCard struct { // Ignored means as much as accepted but not now, perhaps later. // // Required: true + // Enum: [open closed ignored rejected] Status *string `json:"status"` // task tags. @@ -99,47 +101,38 @@ func (m *TaskCard) Validate(formats strfmt.Registry) error { var res []error if err := m.validateAssignedTo(formats); err != nil { - // prop res = append(res, err) } if err := m.validateEffort(formats); err != nil { - // prop res = append(res, err) } if err := m.validateKarma(formats); err != nil { - // prop res = append(res, err) } if err := m.validateMilestone(formats); err != nil { - // prop res = append(res, err) } if err := m.validateReportedAt(formats); err != nil { - // prop res = append(res, err) } if err := m.validateSeverity(formats); err != nil { - // prop res = append(res, err) } if err := m.validateStatus(formats); err != nil { - // prop res = append(res, err) } if err := m.validateTags(formats); err != nil { - // prop res = append(res, err) } if err := m.validateTitle(formats); err != nil { - // prop res = append(res, err) } @@ -156,14 +149,12 @@ func (m *TaskCard) validateAssignedTo(formats strfmt.Registry) error { } if m.AssignedTo != nil { - if err := m.AssignedTo.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("assignedTo") } return err } - } return nil @@ -210,14 +201,12 @@ func (m *TaskCard) validateMilestone(formats strfmt.Registry) error { } if m.Milestone != nil { - if err := m.Milestone.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("milestone") } return err } - } return nil diff --git a/examples/task-tracker/models/user_card.go b/examples/task-tracker/models/user_card.go index 40e3a09e62..12d2728fc7 100644 --- a/examples/task-tracker/models/user_card.go +++ b/examples/task-tracker/models/user_card.go @@ -64,17 +64,14 @@ func (m *UserCard) Validate(formats strfmt.Registry) error { var res []error if err := m.validateAvailableKarma(formats); err != nil { - // prop res = append(res, err) } if err := m.validateID(formats); err != nil { - // prop res = append(res, err) } if err := m.validateScreenName(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/task-tracker/models/validation_error.go b/examples/task-tracker/models/validation_error.go index 2de630fc9c..b8cd272925 100644 --- a/examples/task-tracker/models/validation_error.go +++ b/examples/task-tracker/models/validation_error.go @@ -17,30 +17,35 @@ import ( type ValidationError struct { Error - ValidationErrorAllOf1 + // an optional field name to which this validation error applies + Field string `json:"field,omitempty"` } // UnmarshalJSON unmarshals this object from a JSON structure func (m *ValidationError) UnmarshalJSON(raw []byte) error { - + // AO0 var aO0 Error if err := swag.ReadJSON(raw, &aO0); err != nil { return err } m.Error = aO0 - var aO1 ValidationErrorAllOf1 - if err := swag.ReadJSON(raw, &aO1); err != nil { + // AO1 + var dataAO1 struct { + Field string `json:"field,omitempty"` + } + if err := swag.ReadJSON(raw, &dataAO1); err != nil { return err } - m.ValidationErrorAllOf1 = aO1 + + m.Field = dataAO1.Field return nil } // MarshalJSON marshals this object to a JSON structure func (m ValidationError) MarshalJSON() ([]byte, error) { - var _parts [][]byte + _parts := make([][]byte, 0, 2) aO0, err := swag.WriteJSON(m.Error) if err != nil { @@ -48,11 +53,17 @@ func (m ValidationError) MarshalJSON() ([]byte, error) { } _parts = append(_parts, aO0) - aO1, err := swag.WriteJSON(m.ValidationErrorAllOf1) - if err != nil { - return nil, err + var dataAO1 struct { + Field string `json:"field,omitempty"` } - _parts = append(_parts, aO1) + + dataAO1.Field = m.Field + + jsonDataAO1, errAO1 := swag.WriteJSON(dataAO1) + if errAO1 != nil { + return nil, errAO1 + } + _parts = append(_parts, jsonDataAO1) return swag.ConcatJSON(_parts...), nil } @@ -61,14 +72,11 @@ func (m ValidationError) MarshalJSON() ([]byte, error) { func (m *ValidationError) Validate(formats strfmt.Registry) error { var res []error + // validation for a type composition with Error if err := m.Error.Validate(formats); err != nil { res = append(res, err) } - if err := m.ValidationErrorAllOf1.Validate(formats); err != nil { - res = append(res, err) - } - if len(res) > 0 { return errors.CompositeValidationError(res...) } diff --git a/examples/task-tracker/models/validation_error_all_of1.go b/examples/task-tracker/models/validation_error_all_of1.go deleted file mode 100644 index 713d244d60..0000000000 --- a/examples/task-tracker/models/validation_error_all_of1.go +++ /dev/null @@ -1,49 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - strfmt "github.com/go-openapi/strfmt" - - "github.com/go-openapi/errors" - "github.com/go-openapi/swag" -) - -// ValidationErrorAllOf1 validation error all of1 -// swagger:model validationErrorAllOf1 -type ValidationErrorAllOf1 struct { - - // an optional field name to which this validation error applies - Field string `json:"field,omitempty"` -} - -// Validate validates this validation error all of1 -func (m *ValidationErrorAllOf1) Validate(formats strfmt.Registry) error { - var res []error - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -// MarshalBinary interface implementation -func (m *ValidationErrorAllOf1) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *ValidationErrorAllOf1) UnmarshalBinary(b []byte) error { - var res ValidationErrorAllOf1 - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/examples/task-tracker/restapi/configure_task_tracker.go b/examples/task-tracker/restapi/configure_task_tracker.go index 37f894baeb..c3d4d9bc3b 100644 --- a/examples/task-tracker/restapi/configure_task_tracker.go +++ b/examples/task-tracker/restapi/configure_task_tracker.go @@ -9,7 +9,6 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/task-tracker/restapi/operations" "github.com/go-swagger/go-swagger/examples/task-tracker/restapi/operations/tasks" @@ -37,22 +36,20 @@ func configureAPI(api *operations.TaskTrackerAPI) http.Handler { api.JSONProducer = runtime.JSONProducer() - // Applies when the "X-Token" header is set - api.TokenHeaderAuth = func(token string) (interface{}, error) { - return nil, errors.NotImplemented("api key auth (token_header) X-Token from header param [X-Token] has not yet been implemented") - } - // Applies when the "token" query is set api.APIKeyAuth = func(token string) (interface{}, error) { return nil, errors.NotImplemented("api key auth (api_key) token from query param [token] has not yet been implemented") } + // Applies when the "X-Token" header is set + api.TokenHeaderAuth = func(token string) (interface{}, error) { + return nil, errors.NotImplemented("api key auth (token_header) X-Token from header param [X-Token] has not yet been implemented") + } // Set your custom authorizer if needed. Default one is security.Authorized() // Expected interface runtime.Authorizer // // Example: // api.APIAuthorizer = security.Authorized() - api.TasksAddCommentToTaskHandler = tasks.AddCommentToTaskHandlerFunc(func(params tasks.AddCommentToTaskParams, principal interface{}) middleware.Responder { return middleware.NotImplemented("operation tasks.AddCommentToTask has not yet been implemented") }) @@ -92,7 +89,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/task-tracker/restapi/embedded_spec.go b/examples/task-tracker/restapi/embedded_spec.go index af5e748211..67797341b5 100644 --- a/examples/task-tracker/restapi/embedded_spec.go +++ b/examples/task-tracker/restapi/embedded_spec.go @@ -1165,7 +1165,22 @@ func init() { "name": "body", "in": "body", "schema": { - "$ref": "#/definitions/addCommentToTaskParamsBody" + "description": "These values can have github flavored markdown.\n", + "type": "object", + "title": "A comment to create", + "required": [ + "content", + "userId" + ], + "properties": { + "content": { + "type": "string" + }, + "userId": { + "type": "integer", + "format": "int64" + } + } } } ], @@ -1339,7 +1354,26 @@ func init() { "pattern": "[A-Za-z][\\w- ]+" }, "stats": { - "$ref": "#/definitions/milestoneStats" + "description": "This object contains counts for the remaining open issues and the amount of issues that have been closed.\n", + "type": "object", + "title": "Some counters for this milestone.", + "properties": { + "closed": { + "type": "integer", + "format": "int32", + "title": "The closed issues." + }, + "open": { + "type": "integer", + "format": "int32", + "title": "The remaining open issues." + }, + "total": { + "type": "integer", + "format": "int32", + "title": "The total number of issues for this milestone." + } + } } } }, @@ -1352,7 +1386,74 @@ func init() { "$ref": "#/definitions/TaskCard" }, { - "$ref": "#/definitions/taskAllOf1" + "type": "object", + "properties": { + "attachments": { + "description": "An issue can have at most 20 files attached to it.\n", + "type": "object", + "title": "The attached files.", + "additionalProperties": { + "type": "object", + "maxProperties": 20, + "properties": { + "contentType": { + "description": "The content type of the file is inferred from the upload request.\n", + "type": "string", + "title": "The content type of the file.", + "readOnly": true + }, + "description": { + "description": "This is a free form text field with support for github flavored markdown.\n", + "type": "string", + "title": "Extra information to attach to the file.", + "minLength": 3 + }, + "name": { + "description": "This name is inferred from the upload request.\n", + "type": "string", + "title": "The name of the file.", + "readOnly": true + }, + "size": { + "description": "This property was generated during the upload request of the file.", + "type": "number", + "format": "float64", + "title": "The file size in bytes.", + "readOnly": true + }, + "url": { + "description": "This URL is generated on the server, based on where it was able to store the file when it was uploaded.\n", + "type": "string", + "format": "uri", + "title": "The url to download or view the file.", + "readOnly": true + } + } + } + }, + "comments": { + "description": "The detail view of an issue includes the 5 most recent comments.\nThis field is read only, comments are added through a separate process.\n", + "type": "array", + "title": "The 5 most recent items for this issue.", + "items": { + "$ref": "#/definitions/Comment" + }, + "readOnly": true + }, + "lastUpdated": { + "description": "This field is read only so it's only sent as part of the response.\n", + "type": "string", + "format": "date-time", + "title": "The time at which this issue was last updated.", + "readOnly": true + }, + "lastUpdatedBy": { + "$ref": "#/definitions/UserCard" + }, + "reportedBy": { + "$ref": "#/definitions/UserCard" + } + } } ] }, @@ -1491,140 +1592,15 @@ func init() { "$ref": "#/definitions/Error" }, { - "$ref": "#/definitions/validationErrorAllOf1" + "type": "object", + "properties": { + "field": { + "description": "an optional field name to which this validation error applies", + "type": "string" + } + } } ] - }, - "addCommentToTaskParamsBody": { - "description": "These values can have github flavored markdown.\n", - "type": "object", - "title": "A comment to create", - "required": [ - "content", - "userId" - ], - "properties": { - "content": { - "type": "string" - }, - "userId": { - "type": "integer", - "format": "int64" - } - }, - "x-go-gen-location": "operations" - }, - "milestoneStats": { - "description": "This object contains counts for the remaining open issues and the amount of issues that have been closed.\n", - "type": "object", - "title": "Some counters for this milestone.", - "properties": { - "closed": { - "type": "integer", - "format": "int32", - "title": "The closed issues." - }, - "open": { - "type": "integer", - "format": "int32", - "title": "The remaining open issues." - }, - "total": { - "type": "integer", - "format": "int32", - "title": "The total number of issues for this milestone." - } - }, - "x-go-gen-location": "models" - }, - "taskAllOf1": { - "type": "object", - "properties": { - "attachments": { - "$ref": "#/definitions/taskAllOf1Attachments" - }, - "comments": { - "description": "The detail view of an issue includes the 5 most recent comments.\nThis field is read only, comments are added through a separate process.\n", - "type": "array", - "title": "The 5 most recent items for this issue.", - "items": { - "$ref": "#/definitions/Comment" - }, - "readOnly": true - }, - "lastUpdated": { - "description": "This field is read only so it's only sent as part of the response.\n", - "type": "string", - "format": "date-time", - "title": "The time at which this issue was last updated.", - "readOnly": true - }, - "lastUpdatedBy": { - "$ref": "#/definitions/UserCard" - }, - "reportedBy": { - "$ref": "#/definitions/UserCard" - } - }, - "x-go-gen-location": "models" - }, - "taskAllOf1Attachments": { - "description": "An issue can have at most 20 files attached to it.\n", - "type": "object", - "title": "The attached files.", - "additionalProperties": { - "$ref": "#/definitions/taskAllOf1AttachmentsAdditionalProperties" - }, - "x-go-gen-location": "models" - }, - "taskAllOf1AttachmentsAdditionalProperties": { - "type": "object", - "maxProperties": 20, - "properties": { - "contentType": { - "description": "The content type of the file is inferred from the upload request.\n", - "type": "string", - "title": "The content type of the file.", - "readOnly": true - }, - "description": { - "description": "This is a free form text field with support for github flavored markdown.\n", - "type": "string", - "title": "Extra information to attach to the file.", - "minLength": 3 - }, - "name": { - "description": "This name is inferred from the upload request.\n", - "type": "string", - "title": "The name of the file.", - "readOnly": true - }, - "size": { - "description": "This property was generated during the upload request of the file.", - "type": "number", - "format": "float64", - "title": "The file size in bytes.", - "readOnly": true - }, - "url": { - "description": "This URL is generated on the server, based on where it was able to store the file when it was uploaded.\n", - "type": "string", - "format": "uri", - "title": "The url to download or view the file.", - "readOnly": true - } - }, - "x-go-gen-location": "models" - }, - "validationErrorAllOf1": { - "type": "object", - "properties": { - "field": { - "description": "an optional field name to which this validation error applies", - "type": "string" - } - }, - "x-go-gen-location": "models" } }, "parameters": { diff --git a/examples/task-tracker/restapi/operations/task_tracker_api.go b/examples/task-tracker/restapi/operations/task_tracker_api.go index a9dd8f7d4d..83f40b4c1e 100644 --- a/examples/task-tracker/restapi/operations/task_tracker_api.go +++ b/examples/task-tracker/restapi/operations/task_tracker_api.go @@ -65,14 +65,14 @@ func NewTaskTrackerAPI(spec *loads.Document) *TaskTrackerAPI { return middleware.NotImplemented("operation TasksUploadTaskFile has not yet been implemented") }), - // Applies when the "X-Token" header is set - TokenHeaderAuth: func(token string) (interface{}, error) { - return nil, errors.NotImplemented("api key auth (token_header) X-Token from header param [X-Token] has not yet been implemented") - }, // Applies when the "token" query is set APIKeyAuth: func(token string) (interface{}, error) { return nil, errors.NotImplemented("api key auth (api_key) token from query param [token] has not yet been implemented") }, + // Applies when the "X-Token" header is set + TokenHeaderAuth: func(token string) (interface{}, error) { + return nil, errors.NotImplemented("api key auth (token_header) X-Token from header param [X-Token] has not yet been implemented") + }, // default authorizer is authorized meaning no requests are blocked APIAuthorizer: security.Authorized(), @@ -115,14 +115,14 @@ type TaskTrackerAPI struct { // JSONProducer registers a producer for a "application/vnd.goswagger.examples.task-tracker.v1+json" mime type JSONProducer runtime.Producer - // TokenHeaderAuth registers a function that takes a token and returns a principal - // it performs authentication based on an api key X-Token provided in the header - TokenHeaderAuth func(string) (interface{}, error) - // APIKeyAuth registers a function that takes a token and returns a principal // it performs authentication based on an api key token provided in the query APIKeyAuth func(string) (interface{}, error) + // TokenHeaderAuth registers a function that takes a token and returns a principal + // it performs authentication based on an api key X-Token provided in the header + TokenHeaderAuth func(string) (interface{}, error) + // APIAuthorizer provides access control (ACL/RBAC/ABAC) by providing access to the request and authenticated principal APIAuthorizer runtime.Authorizer @@ -209,14 +209,14 @@ func (o *TaskTrackerAPI) Validate() error { unregistered = append(unregistered, "JSONProducer") } - if o.TokenHeaderAuth == nil { - unregistered = append(unregistered, "XTokenAuth") - } - if o.APIKeyAuth == nil { unregistered = append(unregistered, "TokenAuth") } + if o.TokenHeaderAuth == nil { + unregistered = append(unregistered, "XTokenAuth") + } + if o.TasksAddCommentToTaskHandler == nil { unregistered = append(unregistered, "tasks.AddCommentToTaskHandler") } @@ -268,14 +268,14 @@ func (o *TaskTrackerAPI) AuthenticatorsFor(schemes map[string]spec.SecuritySchem for name, scheme := range schemes { switch name { - case "token_header": - - result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, o.TokenHeaderAuth) - case "api_key": result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, o.APIKeyAuth) + case "token_header": + + result[name] = o.APIKeyAuthenticator(scheme.Name, scheme.In, o.TokenHeaderAuth) + } } return result diff --git a/examples/task-tracker/restapi/operations/tasks/add_comment_to_task.go b/examples/task-tracker/restapi/operations/tasks/add_comment_to_task.go index 8d67a0edc8..f4d4856945 100644 --- a/examples/task-tracker/restapi/operations/tasks/add_comment_to_task.go +++ b/examples/task-tracker/restapi/operations/tasks/add_comment_to_task.go @@ -8,7 +8,11 @@ package tasks import ( "net/http" + errors "github.com/go-openapi/errors" middleware "github.com/go-openapi/runtime/middleware" + strfmt "github.com/go-openapi/strfmt" + swag "github.com/go-openapi/swag" + validate "github.com/go-openapi/validate" ) // AddCommentToTaskHandlerFunc turns a function with the right signature into a add comment to task handler @@ -73,3 +77,73 @@ func (o *AddCommentToTask) ServeHTTP(rw http.ResponseWriter, r *http.Request) { o.Context.Respond(rw, r, route.Produces, route, res) } + +// AddCommentToTaskBody A comment to create +// +// These values can have github flavored markdown. +// +// swagger:model AddCommentToTaskBody +type AddCommentToTaskBody struct { + + // content + // Required: true + Content *string `json:"content"` + + // user Id + // Required: true + UserID *int64 `json:"userId"` +} + +// Validate validates this add comment to task body +func (o *AddCommentToTaskBody) Validate(formats strfmt.Registry) error { + var res []error + + if err := o.validateContent(formats); err != nil { + res = append(res, err) + } + + if err := o.validateUserID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (o *AddCommentToTaskBody) validateContent(formats strfmt.Registry) error { + + if err := validate.Required("body"+"."+"content", "body", o.Content); err != nil { + return err + } + + return nil +} + +func (o *AddCommentToTaskBody) validateUserID(formats strfmt.Registry) error { + + if err := validate.Required("body"+"."+"userId", "body", o.UserID); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (o *AddCommentToTaskBody) MarshalBinary() ([]byte, error) { + if o == nil { + return nil, nil + } + return swag.WriteJSON(o) +} + +// UnmarshalBinary interface implementation +func (o *AddCommentToTaskBody) UnmarshalBinary(b []byte) error { + var res AddCommentToTaskBody + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *o = res + return nil +} diff --git a/examples/task-tracker/restapi/operations/tasks/add_comment_to_task_parameters.go b/examples/task-tracker/restapi/operations/tasks/add_comment_to_task_parameters.go index fb9988492c..a3dbfb1ede 100644 --- a/examples/task-tracker/restapi/operations/tasks/add_comment_to_task_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/add_comment_to_task_parameters.go @@ -14,8 +14,6 @@ import ( "github.com/go-openapi/swag" strfmt "github.com/go-openapi/strfmt" - - models "github.com/go-swagger/go-swagger/examples/task-tracker/models" ) // NewAddCommentToTaskParams creates a new AddCommentToTaskParams object @@ -37,7 +35,7 @@ type AddCommentToTaskParams struct { /*The comment to add In: body */ - Body *models.AddCommentToTaskParamsBody + Body AddCommentToTaskBody /*The id of the item Required: true In: path @@ -56,18 +54,17 @@ func (o *AddCommentToTaskParams) BindRequest(r *http.Request, route *middleware. if runtime.HasBody(r) { defer r.Body.Close() - var body models.AddCommentToTaskParamsBody + var body AddCommentToTaskBody if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) } if len(res) == 0 { - o.Body = &body + o.Body = body } } } @@ -82,6 +79,7 @@ func (o *AddCommentToTaskParams) BindRequest(r *http.Request, route *middleware. return nil } +// bindID binds and validates parameter ID from path. func (o *AddCommentToTaskParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/task-tracker/restapi/operations/tasks/create_task_parameters.go b/examples/task-tracker/restapi/operations/tasks/create_task_parameters.go index b80c566ec9..45d41c388c 100644 --- a/examples/task-tracker/restapi/operations/tasks/create_task_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/create_task_parameters.go @@ -58,7 +58,6 @@ func (o *CreateTaskParams) BindRequest(r *http.Request, route *middleware.Matche res = append(res, errors.NewParseError("body", "body", "", err)) } } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/task-tracker/restapi/operations/tasks/delete_task_parameters.go b/examples/task-tracker/restapi/operations/tasks/delete_task_parameters.go index c507113146..c541a9d088 100644 --- a/examples/task-tracker/restapi/operations/tasks/delete_task_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/delete_task_parameters.go @@ -58,6 +58,7 @@ func (o *DeleteTaskParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindID binds and validates parameter ID from path. func (o *DeleteTaskParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/task-tracker/restapi/operations/tasks/get_task_comments_parameters.go b/examples/task-tracker/restapi/operations/tasks/get_task_comments_parameters.go index fcede02059..d7f0863e6b 100644 --- a/examples/task-tracker/restapi/operations/tasks/get_task_comments_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/get_task_comments_parameters.go @@ -89,6 +89,7 @@ func (o *GetTaskCommentsParams) BindRequest(r *http.Request, route *middleware.M return nil } +// bindID binds and validates parameter ID from path. func (o *GetTaskCommentsParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -107,6 +108,7 @@ func (o *GetTaskCommentsParams) bindID(rawData []string, hasKey bool, formats st return nil } +// bindPageSize binds and validates parameter PageSize from query. func (o *GetTaskCommentsParams) bindPageSize(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -129,6 +131,7 @@ func (o *GetTaskCommentsParams) bindPageSize(rawData []string, hasKey bool, form return nil } +// bindSince binds and validates parameter Since from query. func (o *GetTaskCommentsParams) bindSince(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -155,11 +158,11 @@ func (o *GetTaskCommentsParams) bindSince(rawData []string, hasKey bool, formats return nil } +// validateSince carries on validations for parameter Since func (o *GetTaskCommentsParams) validateSince(formats strfmt.Registry) error { if err := validate.FormatOf("since", "query", "date-time", o.Since.String(), formats); err != nil { return err } - return nil } diff --git a/examples/task-tracker/restapi/operations/tasks/get_task_details_parameters.go b/examples/task-tracker/restapi/operations/tasks/get_task_details_parameters.go index 4a3ce9d1e1..3019d54d39 100644 --- a/examples/task-tracker/restapi/operations/tasks/get_task_details_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/get_task_details_parameters.go @@ -58,6 +58,7 @@ func (o *GetTaskDetailsParams) BindRequest(r *http.Request, route *middleware.Ma return nil } +// bindID binds and validates parameter ID from path. func (o *GetTaskDetailsParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/task-tracker/restapi/operations/tasks/list_tasks_parameters.go b/examples/task-tracker/restapi/operations/tasks/list_tasks_parameters.go index 845b600090..2e2c8c65cd 100644 --- a/examples/task-tracker/restapi/operations/tasks/list_tasks_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/list_tasks_parameters.go @@ -101,6 +101,7 @@ func (o *ListTasksParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindPageSize binds and validates parameter PageSize from query. func (o *ListTasksParams) bindPageSize(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -123,6 +124,7 @@ func (o *ListTasksParams) bindPageSize(rawData []string, hasKey bool, formats st return nil } +// bindSinceID binds and validates parameter SinceID from query. func (o *ListTasksParams) bindSinceID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -144,6 +146,9 @@ func (o *ListTasksParams) bindSinceID(rawData []string, hasKey bool, formats str return nil } +// bindStatus binds and validates array parameter Status from query. +// +// Arrays are parsed according to CollectionFormat: "pipes" (defaults to "csv" when empty). func (o *ListTasksParams) bindStatus(rawData []string, hasKey bool, formats strfmt.Registry) error { var qvStatus string @@ -176,6 +181,7 @@ func (o *ListTasksParams) bindStatus(rawData []string, hasKey bool, formats strf return nil } +// validateStatus carries on validations for parameter Status func (o *ListTasksParams) validateStatus(formats strfmt.Registry) error { // uniqueItems: true @@ -186,6 +192,9 @@ func (o *ListTasksParams) validateStatus(formats strfmt.Registry) error { return nil } +// bindTags binds and validates array parameter Tags from query. +// +// Arrays are parsed according to CollectionFormat: "" (defaults to "csv" when empty). func (o *ListTasksParams) bindTags(rawData []string, hasKey bool, formats strfmt.Registry) error { var qvTags string @@ -214,6 +223,7 @@ func (o *ListTasksParams) bindTags(rawData []string, hasKey bool, formats strfmt return nil } +// validateTags carries on validations for parameter Tags func (o *ListTasksParams) validateTags(formats strfmt.Registry) error { // uniqueItems: true diff --git a/examples/task-tracker/restapi/operations/tasks/update_task_parameters.go b/examples/task-tracker/restapi/operations/tasks/update_task_parameters.go index 8c1d0ddf81..5e6bfd3877 100644 --- a/examples/task-tracker/restapi/operations/tasks/update_task_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/update_task_parameters.go @@ -66,7 +66,6 @@ func (o *UpdateTaskParams) BindRequest(r *http.Request, route *middleware.Matche res = append(res, errors.NewParseError("body", "body", "", err)) } } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) @@ -90,6 +89,7 @@ func (o *UpdateTaskParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindID binds and validates parameter ID from path. func (o *UpdateTaskParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/task-tracker/restapi/operations/tasks/upload_task_file_parameters.go b/examples/task-tracker/restapi/operations/tasks/upload_task_file_parameters.go index 6c23b6175f..28b7299bd0 100644 --- a/examples/task-tracker/restapi/operations/tasks/upload_task_file_parameters.go +++ b/examples/task-tracker/restapi/operations/tasks/upload_task_file_parameters.go @@ -94,6 +94,7 @@ func (o *UploadTaskFileParams) BindRequest(r *http.Request, route *middleware.Ma return nil } +// bindDescription binds and validates parameter Description from formData. func (o *UploadTaskFileParams) bindDescription(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -111,11 +112,14 @@ func (o *UploadTaskFileParams) bindDescription(rawData []string, hasKey bool, fo return nil } +// bindFile binds file parameter File. +// +// The only supported validations on files are MinLength and MaxLength func (o *UploadTaskFileParams) bindFile(file multipart.File, header *multipart.FileHeader) error { - return nil } +// bindID binds and validates parameter ID from path. func (o *UploadTaskFileParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/task-tracker/restapi/server.go b/examples/task-tracker/restapi/server.go index 6c6fdb13ac..fd2a79f7cb 100644 --- a/examples/task-tracker/restapi/server.go +++ b/examples/task-tracker/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/task-tracker/restapi/operations" ) @@ -45,6 +48,7 @@ func NewServer(api *operations.TaskTrackerAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -95,6 +99,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -162,14 +169,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -187,22 +197,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -219,20 +227,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -385,26 +391,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -446,3 +470,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/todo-list/cmd/todo-list-server/main.go b/examples/todo-list/cmd/todo-list-server/main.go index d77e1db035..4adabcb012 100644 --- a/examples/todo-list/cmd/todo-list-server/main.go +++ b/examples/todo-list/cmd/todo-list-server/main.go @@ -8,10 +8,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flag "github.com/spf13/pflag" - "github.com/go-swagger/go-swagger/examples/todo-list/restapi" "github.com/go-swagger/go-swagger/examples/todo-list/restapi/operations" + flag "github.com/spf13/pflag" ) // This file was generated by the swagger tool. diff --git a/examples/todo-list/models/error.go b/examples/todo-list/models/error.go index bb98b47187..03c64f4a1f 100644 --- a/examples/todo-list/models/error.go +++ b/examples/todo-list/models/error.go @@ -30,7 +30,6 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/todo-list/models/item.go b/examples/todo-list/models/item.go index d6af55024b..92b8ec91ec 100644 --- a/examples/todo-list/models/item.go +++ b/examples/todo-list/models/item.go @@ -35,7 +35,6 @@ func (m *Item) Validate(formats strfmt.Registry) error { var res []error if err := m.validateDescription(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/todo-list/restapi/configure_todo_list.go b/examples/todo-list/restapi/configure_todo_list.go index 42042ae975..4aa5cbdedb 100644 --- a/examples/todo-list/restapi/configure_todo_list.go +++ b/examples/todo-list/restapi/configure_todo_list.go @@ -9,7 +9,6 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/todo-list/restapi/operations" "github.com/go-swagger/go-swagger/examples/todo-list/restapi/operations/todos" @@ -45,7 +44,6 @@ func configureAPI(api *operations.TodoListAPI) http.Handler { // // Example: // api.APIAuthorizer = security.Authorized() - api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams, principal interface{}) middleware.Responder { return middleware.NotImplemented("operation todos.AddOne has not yet been implemented") }) @@ -73,7 +71,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/todo-list/restapi/operations/todos/add_one_parameters.go b/examples/todo-list/restapi/operations/todos/add_one_parameters.go index 895ea3bce4..8f2cc6da49 100644 --- a/examples/todo-list/restapi/operations/todos/add_one_parameters.go +++ b/examples/todo-list/restapi/operations/todos/add_one_parameters.go @@ -52,7 +52,6 @@ func (o *AddOneParams) BindRequest(r *http.Request, route *middleware.MatchedRou if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/todo-list/restapi/operations/todos/destroy_one_parameters.go b/examples/todo-list/restapi/operations/todos/destroy_one_parameters.go index db8f622edd..a8ad8e85ca 100644 --- a/examples/todo-list/restapi/operations/todos/destroy_one_parameters.go +++ b/examples/todo-list/restapi/operations/todos/destroy_one_parameters.go @@ -57,6 +57,7 @@ func (o *DestroyOneParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindID binds and validates parameter ID from path. func (o *DestroyOneParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/todo-list/restapi/operations/todos/find_parameters.go b/examples/todo-list/restapi/operations/todos/find_parameters.go index a9a8ab7358..f2cecb9d8d 100644 --- a/examples/todo-list/restapi/operations/todos/find_parameters.go +++ b/examples/todo-list/restapi/operations/todos/find_parameters.go @@ -90,6 +90,7 @@ func (o *FindParams) BindRequest(r *http.Request, route *middleware.MatchedRoute return nil } +// bindXRateLimit binds and validates parameter XRateLimit from header. func (o *FindParams) bindXRateLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { if !hasKey { return errors.Required("X-Rate-Limit", "header") @@ -114,6 +115,7 @@ func (o *FindParams) bindXRateLimit(rawData []string, hasKey bool, formats strfm return nil } +// bindLimit binds and validates parameter Limit from formData. func (o *FindParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { if !hasKey { return errors.Required("limit", "formData") @@ -138,6 +140,9 @@ func (o *FindParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Reg return nil } +// bindTags binds and validates array parameter Tags from formData. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). func (o *FindParams) bindTags(rawData []string, hasKey bool, formats strfmt.Registry) error { if !hasKey { return errors.Required("tags", "formData") diff --git a/examples/todo-list/restapi/operations/todos/update_one_parameters.go b/examples/todo-list/restapi/operations/todos/update_one_parameters.go index 541d153a6c..4b62b9e013 100644 --- a/examples/todo-list/restapi/operations/todos/update_one_parameters.go +++ b/examples/todo-list/restapi/operations/todos/update_one_parameters.go @@ -59,7 +59,6 @@ func (o *UpdateOneParams) BindRequest(r *http.Request, route *middleware.Matched if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) @@ -81,6 +80,7 @@ func (o *UpdateOneParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindID binds and validates parameter ID from path. func (o *UpdateOneParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/todo-list/restapi/server.go b/examples/todo-list/restapi/server.go index b117515bba..a7f14f8bc7 100644 --- a/examples/todo-list/restapi/server.go +++ b/examples/todo-list/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -12,15 +13,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flag "github.com/spf13/pflag" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/todo-list/restapi/operations" ) @@ -147,6 +150,7 @@ func NewServer(api *operations.TodoListAPI) *Server { s.TLSWriteTimeout = tlsWriteTimeout s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -197,6 +201,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -264,14 +271,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -289,22 +299,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -321,20 +329,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -487,26 +493,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -548,3 +572,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/tutorials/custom-server/cmd/greeter/main.go b/examples/tutorials/custom-server/cmd/greeter/main.go index e217dbb128..5689f92d92 100644 --- a/examples/tutorials/custom-server/cmd/greeter/main.go +++ b/examples/tutorials/custom-server/cmd/greeter/main.go @@ -25,7 +25,9 @@ func main() { // create new service API api := operations.NewGreeterAPI(swaggerSpec) server := restapi.NewServer(api) - defer server.Shutdown() + defer func() { + _ = server.Shutdown() + }() // parse flags flag.Parse() diff --git a/examples/tutorials/custom-server/gen/restapi/configure_greeter.go b/examples/tutorials/custom-server/gen/restapi/configure_greeter.go index 24382ec6f6..6a27220b53 100644 --- a/examples/tutorials/custom-server/gen/restapi/configure_greeter.go +++ b/examples/tutorials/custom-server/gen/restapi/configure_greeter.go @@ -9,7 +9,6 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/tutorials/custom-server/gen/restapi/operations" ) @@ -52,7 +51,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/tutorials/custom-server/gen/restapi/operations/get_greeting_parameters.go b/examples/tutorials/custom-server/gen/restapi/operations/get_greeting_parameters.go index 0f37a57531..c7677ae400 100644 --- a/examples/tutorials/custom-server/gen/restapi/operations/get_greeting_parameters.go +++ b/examples/tutorials/custom-server/gen/restapi/operations/get_greeting_parameters.go @@ -59,6 +59,7 @@ func (o *GetGreetingParams) BindRequest(r *http.Request, route *middleware.Match return nil } +// bindName binds and validates parameter Name from query. func (o *GetGreetingParams) bindName(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/custom-server/gen/restapi/server.go b/examples/tutorials/custom-server/gen/restapi/server.go index 1f6c5e699d..c50f119eb8 100644 --- a/examples/tutorials/custom-server/gen/restapi/server.go +++ b/examples/tutorials/custom-server/gen/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/tutorials/custom-server/gen/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.GreeterAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/tutorials/todo-list/server-1/cmd/todo-list-server/main.go b/examples/tutorials/todo-list/server-1/cmd/todo-list-server/main.go index 6d21e7c287..907c1c29e9 100644 --- a/examples/tutorials/todo-list/server-1/cmd/todo-list-server/main.go +++ b/examples/tutorials/todo-list/server-1/cmd/todo-list-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-1/restapi" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-1/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/tutorials/todo-list/server-1/models/error.go b/examples/tutorials/todo-list/server-1/models/error.go index bb98b47187..03c64f4a1f 100644 --- a/examples/tutorials/todo-list/server-1/models/error.go +++ b/examples/tutorials/todo-list/server-1/models/error.go @@ -30,7 +30,6 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/tutorials/todo-list/server-1/models/item.go b/examples/tutorials/todo-list/server-1/models/item.go index d6af55024b..92b8ec91ec 100644 --- a/examples/tutorials/todo-list/server-1/models/item.go +++ b/examples/tutorials/todo-list/server-1/models/item.go @@ -35,7 +35,6 @@ func (m *Item) Validate(formats strfmt.Registry) error { var res []error if err := m.validateDescription(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/tutorials/todo-list/server-1/restapi/configure_todo_list.go b/examples/tutorials/todo-list/server-1/restapi/configure_todo_list.go index 72952f1e88..5d6d7bd243 100644 --- a/examples/tutorials/todo-list/server-1/restapi/configure_todo_list.go +++ b/examples/tutorials/todo-list/server-1/restapi/configure_todo_list.go @@ -9,7 +9,6 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-1/restapi/operations" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-1/restapi/operations/todos" @@ -53,7 +52,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/tutorials/todo-list/server-1/restapi/operations/todos/find_todos_parameters.go b/examples/tutorials/todo-list/server-1/restapi/operations/todos/find_todos_parameters.go index 54736d57f6..b882d43d0f 100644 --- a/examples/tutorials/todo-list/server-1/restapi/operations/todos/find_todos_parameters.go +++ b/examples/tutorials/todo-list/server-1/restapi/operations/todos/find_todos_parameters.go @@ -78,6 +78,7 @@ func (o *FindTodosParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindLimit binds and validates parameter Limit from query. func (o *FindTodosParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -100,6 +101,7 @@ func (o *FindTodosParams) bindLimit(rawData []string, hasKey bool, formats strfm return nil } +// bindSince binds and validates parameter Since from query. func (o *FindTodosParams) bindSince(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/todo-list/server-1/restapi/server.go b/examples/tutorials/todo-list/server-1/restapi/server.go index f4f9529a6d..e5d22db141 100644 --- a/examples/tutorials/todo-list/server-1/restapi/server.go +++ b/examples/tutorials/todo-list/server-1/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-1/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.TodoListAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/tutorials/todo-list/server-2/cmd/todo-list-server/main.go b/examples/tutorials/todo-list/server-2/cmd/todo-list-server/main.go index 6173a52793..92d62f6b67 100644 --- a/examples/tutorials/todo-list/server-2/cmd/todo-list-server/main.go +++ b/examples/tutorials/todo-list/server-2/cmd/todo-list-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-2/restapi" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-2/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/tutorials/todo-list/server-2/models/error.go b/examples/tutorials/todo-list/server-2/models/error.go index bb98b47187..03c64f4a1f 100644 --- a/examples/tutorials/todo-list/server-2/models/error.go +++ b/examples/tutorials/todo-list/server-2/models/error.go @@ -30,7 +30,6 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/tutorials/todo-list/server-2/models/item.go b/examples/tutorials/todo-list/server-2/models/item.go index d6af55024b..92b8ec91ec 100644 --- a/examples/tutorials/todo-list/server-2/models/item.go +++ b/examples/tutorials/todo-list/server-2/models/item.go @@ -35,7 +35,6 @@ func (m *Item) Validate(formats strfmt.Registry) error { var res []error if err := m.validateDescription(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/tutorials/todo-list/server-2/restapi/configure_todo_list.go b/examples/tutorials/todo-list/server-2/restapi/configure_todo_list.go index a1291697e4..2376a4e387 100644 --- a/examples/tutorials/todo-list/server-2/restapi/configure_todo_list.go +++ b/examples/tutorials/todo-list/server-2/restapi/configure_todo_list.go @@ -9,7 +9,6 @@ import ( errors "github.com/go-openapi/errors" runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" - graceful "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-2/restapi/operations" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-2/restapi/operations/todos" @@ -62,7 +61,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/tutorials/todo-list/server-2/restapi/operations/todos/add_one_parameters.go b/examples/tutorials/todo-list/server-2/restapi/operations/todos/add_one_parameters.go index 347bbb2c7f..749fa8645d 100644 --- a/examples/tutorials/todo-list/server-2/restapi/operations/todos/add_one_parameters.go +++ b/examples/tutorials/todo-list/server-2/restapi/operations/todos/add_one_parameters.go @@ -52,7 +52,6 @@ func (o *AddOneParams) BindRequest(r *http.Request, route *middleware.MatchedRou if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/tutorials/todo-list/server-2/restapi/operations/todos/destroy_one_parameters.go b/examples/tutorials/todo-list/server-2/restapi/operations/todos/destroy_one_parameters.go index 5a11240a72..83014dfef2 100644 --- a/examples/tutorials/todo-list/server-2/restapi/operations/todos/destroy_one_parameters.go +++ b/examples/tutorials/todo-list/server-2/restapi/operations/todos/destroy_one_parameters.go @@ -58,6 +58,7 @@ func (o *DestroyOneParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindID binds and validates parameter ID from path. func (o *DestroyOneParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/todo-list/server-2/restapi/operations/todos/find_todos_parameters.go b/examples/tutorials/todo-list/server-2/restapi/operations/todos/find_todos_parameters.go index 54736d57f6..b882d43d0f 100644 --- a/examples/tutorials/todo-list/server-2/restapi/operations/todos/find_todos_parameters.go +++ b/examples/tutorials/todo-list/server-2/restapi/operations/todos/find_todos_parameters.go @@ -78,6 +78,7 @@ func (o *FindTodosParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindLimit binds and validates parameter Limit from query. func (o *FindTodosParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -100,6 +101,7 @@ func (o *FindTodosParams) bindLimit(rawData []string, hasKey bool, formats strfm return nil } +// bindSince binds and validates parameter Since from query. func (o *FindTodosParams) bindSince(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/todo-list/server-2/restapi/operations/todos/update_one_parameters.go b/examples/tutorials/todo-list/server-2/restapi/operations/todos/update_one_parameters.go index 5ada386da7..01d3724b92 100644 --- a/examples/tutorials/todo-list/server-2/restapi/operations/todos/update_one_parameters.go +++ b/examples/tutorials/todo-list/server-2/restapi/operations/todos/update_one_parameters.go @@ -60,7 +60,6 @@ func (o *UpdateOneParams) BindRequest(r *http.Request, route *middleware.Matched if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) @@ -82,6 +81,7 @@ func (o *UpdateOneParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindID binds and validates parameter ID from path. func (o *UpdateOneParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/todo-list/server-2/restapi/server.go b/examples/tutorials/todo-list/server-2/restapi/server.go index cfdccdde7b..0692905755 100644 --- a/examples/tutorials/todo-list/server-2/restapi/server.go +++ b/examples/tutorials/todo-list/server-2/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-2/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.TodoListAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/examples/tutorials/todo-list/server-complete/cmd/todo-list-server/main.go b/examples/tutorials/todo-list/server-complete/cmd/todo-list-server/main.go index b6638e74dd..31fd2675ed 100644 --- a/examples/tutorials/todo-list/server-complete/cmd/todo-list-server/main.go +++ b/examples/tutorials/todo-list/server-complete/cmd/todo-list-server/main.go @@ -7,10 +7,9 @@ import ( "os" loads "github.com/go-openapi/loads" - flags "github.com/jessevdk/go-flags" - "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete/restapi" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete/restapi/operations" + flags "github.com/jessevdk/go-flags" ) // This file was generated by the swagger tool. diff --git a/examples/tutorials/todo-list/server-complete/models/error.go b/examples/tutorials/todo-list/server-complete/models/error.go index bb98b47187..03c64f4a1f 100644 --- a/examples/tutorials/todo-list/server-complete/models/error.go +++ b/examples/tutorials/todo-list/server-complete/models/error.go @@ -30,7 +30,6 @@ func (m *Error) Validate(formats strfmt.Registry) error { var res []error if err := m.validateMessage(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/tutorials/todo-list/server-complete/models/item.go b/examples/tutorials/todo-list/server-complete/models/item.go index d6af55024b..92b8ec91ec 100644 --- a/examples/tutorials/todo-list/server-complete/models/item.go +++ b/examples/tutorials/todo-list/server-complete/models/item.go @@ -35,7 +35,6 @@ func (m *Item) Validate(formats strfmt.Registry) error { var res []error if err := m.validateDescription(formats); err != nil { - // prop res = append(res, err) } diff --git a/examples/tutorials/todo-list/server-complete/restapi/configure_todo_list.go b/examples/tutorials/todo-list/server-complete/restapi/configure_todo_list.go index d1fb467c74..8ea6843a0f 100644 --- a/examples/tutorials/todo-list/server-complete/restapi/configure_todo_list.go +++ b/examples/tutorials/todo-list/server-complete/restapi/configure_todo_list.go @@ -1,3 +1,5 @@ +// This file is safe to edit. Once it exists it will not be overwritten + package restapi import ( @@ -10,15 +12,12 @@ import ( runtime "github.com/go-openapi/runtime" middleware "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/swag" - "github.com/tylerb/graceful" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete/models" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete/restapi/operations" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete/restapi/operations/todos" ) -// This file is safe to edit. Once it exists it will not be overwritten - //go:generate swagger generate server --target .. --name TodoList --spec ../swagger.yml var items = make(map[int64]*models.Item) @@ -101,9 +100,10 @@ func configureAPI(api *operations.TodoListAPI) http.Handler { // Expected interface func(string, ...interface{}) // // Example: - // s.api.Logger = log.Printf + // api.Logger = log.Printf api.JSONConsumer = runtime.JSONConsumer() + api.JSONProducer = runtime.JSONProducer() api.TodosAddOneHandler = todos.AddOneHandlerFunc(func(params todos.AddOneParams) middleware.Responder { @@ -112,14 +112,12 @@ func configureAPI(api *operations.TodoListAPI) http.Handler { } return todos.NewAddOneCreated().WithPayload(params.Body) }) - api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams) middleware.Responder { if err := deleteItem(params.ID); err != nil { return todos.NewDestroyOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())}) } return todos.NewDestroyOneNoContent() }) - api.TodosFindTodosHandler = todos.FindTodosHandlerFunc(func(params todos.FindTodosParams) middleware.Responder { mergedParams := todos.NewFindTodosParams() mergedParams.Since = swag.Int64(0) @@ -131,7 +129,6 @@ func configureAPI(api *operations.TodoListAPI) http.Handler { } return todos.NewFindTodosOK().WithPayload(allItems(*mergedParams.Since, *mergedParams.Limit)) }) - api.TodosUpdateOneHandler = todos.UpdateOneHandlerFunc(func(params todos.UpdateOneParams) middleware.Responder { if err := updateItem(params.ID, params.Body); err != nil { return todos.NewUpdateOneDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())}) @@ -153,7 +150,7 @@ func configureTLS(tlsConfig *tls.Config) { // If you need to modify a config, store server instance to stop it individually later, this is the place. // This function can be called multiple times, depending on the number of serving schemes. // scheme value will be set accordingly: "http", "https" or "unix" -func configureServer(s *graceful.Server, scheme, addr string) { +func configureServer(s *http.Server, scheme, addr string) { } // The middleware configuration is for the handler executors. These do not apply to the swagger.json document. diff --git a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/add_one_parameters.go b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/add_one_parameters.go index 01d7a673fb..0bcdaa632a 100644 --- a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/add_one_parameters.go +++ b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/add_one_parameters.go @@ -52,7 +52,6 @@ func (o *AddOneParams) BindRequest(r *http.Request, route *middleware.MatchedRou if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) diff --git a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/destroy_one_parameters.go b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/destroy_one_parameters.go index 5a11240a72..83014dfef2 100644 --- a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/destroy_one_parameters.go +++ b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/destroy_one_parameters.go @@ -58,6 +58,7 @@ func (o *DestroyOneParams) BindRequest(r *http.Request, route *middleware.Matche return nil } +// bindID binds and validates parameter ID from path. func (o *DestroyOneParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/find_todos_parameters.go b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/find_todos_parameters.go index 54736d57f6..b882d43d0f 100644 --- a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/find_todos_parameters.go +++ b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/find_todos_parameters.go @@ -78,6 +78,7 @@ func (o *FindTodosParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindLimit binds and validates parameter Limit from query. func (o *FindTodosParams) bindLimit(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { @@ -100,6 +101,7 @@ func (o *FindTodosParams) bindLimit(rawData []string, hasKey bool, formats strfm return nil } +// bindSince binds and validates parameter Since from query. func (o *FindTodosParams) bindSince(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/update_one_parameters.go b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/update_one_parameters.go index bc54e7d9e2..bef8c18692 100644 --- a/examples/tutorials/todo-list/server-complete/restapi/operations/todos/update_one_parameters.go +++ b/examples/tutorials/todo-list/server-complete/restapi/operations/todos/update_one_parameters.go @@ -60,7 +60,6 @@ func (o *UpdateOneParams) BindRequest(r *http.Request, route *middleware.Matched if err := route.Consumer.Consume(r.Body, &body); err != nil { res = append(res, errors.NewParseError("body", "body", "", err)) } else { - // validate body object if err := body.Validate(route.Formats); err != nil { res = append(res, err) @@ -82,6 +81,7 @@ func (o *UpdateOneParams) BindRequest(r *http.Request, route *middleware.Matched return nil } +// bindID binds and validates parameter ID from path. func (o *UpdateOneParams) bindID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string if len(rawData) > 0 { diff --git a/examples/tutorials/todo-list/server-complete/restapi/server.go b/examples/tutorials/todo-list/server-complete/restapi/server.go index dc70c3b744..359f0e41dc 100644 --- a/examples/tutorials/todo-list/server-complete/restapi/server.go +++ b/examples/tutorials/todo-list/server-complete/restapi/server.go @@ -3,6 +3,7 @@ package restapi import ( + "context" "crypto/tls" "crypto/x509" "errors" @@ -11,15 +12,17 @@ import ( "net" "net/http" "os" + "os/signal" "strconv" "sync" "sync/atomic" + "syscall" "time" "github.com/go-openapi/runtime/flagext" "github.com/go-openapi/swag" flags "github.com/jessevdk/go-flags" - graceful "github.com/tylerb/graceful" + "golang.org/x/net/netutil" "github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete/restapi/operations" ) @@ -44,6 +47,7 @@ func NewServer(api *operations.TodoListAPI) *Server { s.shutdown = make(chan struct{}) s.api = api + s.interrupt = make(chan os.Signal, 1) return s } @@ -94,6 +98,9 @@ type Server struct { hasListeners bool shutdown chan struct{} shuttingDown int32 + interrupted bool + interrupt chan os.Signal + chanLock sync.RWMutex } // Logf logs message either via defined user logger or via system one if no user logger is defined. @@ -161,14 +168,17 @@ func (s *Server) Serve() (err error) { } var wg sync.WaitGroup + quitting := make(chan struct{}) + once := new(sync.Once) + signalNotify(s.interrupt) + go handleInterrupt(once, s, quitting) if s.hasScheme(schemeUnix) { - domainSocket := &graceful.Server{Server: new(http.Server)} + domainSocket := new(http.Server) domainSocket.MaxHeaderBytes = int(s.MaxHeaderSize) domainSocket.Handler = s.handler - domainSocket.LogFunc = s.Logf if int64(s.CleanupTimeout) > 0 { - domainSocket.Timeout = s.CleanupTimeout + domainSocket.IdleTimeout = s.CleanupTimeout } configureServer(domainSocket, "unix", string(s.SocketPath)) @@ -186,22 +196,20 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTP) { - httpServer := &graceful.Server{Server: new(http.Server)} + httpServer := new(http.Server) httpServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpServer.ReadTimeout = s.ReadTimeout httpServer.WriteTimeout = s.WriteTimeout httpServer.SetKeepAlivesEnabled(int64(s.KeepAlive) > 0) - httpServer.TCPKeepAlive = s.KeepAlive if s.ListenLimit > 0 { - httpServer.ListenLimit = s.ListenLimit + s.httpServerL = netutil.LimitListener(s.httpServerL, s.ListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpServer.Timeout = s.CleanupTimeout + httpServer.IdleTimeout = s.CleanupTimeout } httpServer.Handler = s.handler - httpServer.LogFunc = s.Logf configureServer(httpServer, "http", s.httpServerL.Addr().String()) @@ -218,20 +226,18 @@ func (s *Server) Serve() (err error) { } if s.hasScheme(schemeHTTPS) { - httpsServer := &graceful.Server{Server: new(http.Server)} + httpsServer := new(http.Server) httpsServer.MaxHeaderBytes = int(s.MaxHeaderSize) httpsServer.ReadTimeout = s.TLSReadTimeout httpsServer.WriteTimeout = s.TLSWriteTimeout httpsServer.SetKeepAlivesEnabled(int64(s.TLSKeepAlive) > 0) - httpsServer.TCPKeepAlive = s.TLSKeepAlive if s.TLSListenLimit > 0 { - httpsServer.ListenLimit = s.TLSListenLimit + s.httpsServerL = netutil.LimitListener(s.httpsServerL, s.TLSListenLimit) } if int64(s.CleanupTimeout) > 0 { - httpsServer.Timeout = s.CleanupTimeout + httpsServer.IdleTimeout = s.CleanupTimeout } httpsServer.Handler = s.handler - httpsServer.LogFunc = s.Logf // Inspired by https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go httpsServer.TLSConfig = &tls.Config{ @@ -384,26 +390,44 @@ func (s *Server) Shutdown() error { s.Logf("already shutting down") return nil } - s.shutdown <- struct{}{} + close(s.shutdown) return nil } -func (s *Server) handleShutdown(wg *sync.WaitGroup, server *graceful.Server) { +func (s *Server) handleShutdown(wg *sync.WaitGroup, server *http.Server) { defer wg.Done() - for { + ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Second) + defer cancel() + + <-s.shutdown + if err := server.Shutdown(ctx); err != nil { + // Error from closing listeners, or context timeout: + s.Logf("HTTP server Shutdown: %v", err) + } else { + atomic.AddInt32(&s.shuttingDown, 1) select { - case <-s.shutdown: - atomic.AddInt32(&s.shuttingDown, 1) - server.Stop(s.CleanupTimeout) - <-server.StopChan() - s.api.ServerShutdown() - return - case <-server.StopChan(): - atomic.AddInt32(&s.shuttingDown, 1) - s.api.ServerShutdown() - return + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + s.Logf("Error %s", err) + } + default: + done := make(chan error) + defer close(done) + go func() { + <-ctx.Done() + done <- ctx.Err() + }() + go func() { + //done <- s.api.Shutdown(ctx) + s.api.ServerShutdown() + done <- errors.New("API shut down") + }() + if err := <-done; err != nil { + s.Logf("Error %s", err) + } } } + return } // GetHandler returns a handler useful for testing @@ -445,3 +469,31 @@ func (s *Server) TLSListener() (net.Listener, error) { } return s.httpsServerL, nil } + +func handleInterrupt(once *sync.Once, s *Server, quitting chan struct{}) { + once.Do(func() { + for _ = range s.interrupt { + if s.interrupted { + s.Logf("Server already shutting down") + continue + } + s.interrupted = true + s.Logf("Shutting down... ") + close(quitting) + + if err := s.httpServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.httpsServerL.Close(); err != nil { + s.Logf("Error: %s", err) + } + if err := s.domainSocketL.Close(); err != nil { + s.Logf("Error: %s", err) + } + } + }) +} + +func signalNotify(interrupt chan<- os.Signal) { + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) +} diff --git a/hack/regen-samples.sh b/hack/regen-samples.sh index b6ed9315d6..201337e5b9 100755 --- a/hack/regen-samples.sh +++ b/hack/regen-samples.sh @@ -49,3 +49,12 @@ cd "${examples}/tutorials/custom-server" rm -rf gen mkdir gen swagger generate server --exclude-main -A greeter -t gen -f ./swagger/swagger.yml + +cd "${examples}/composed-auth" +cp restapi/configure_multi_auth_example.go . +rm -rf cmd models restapi +swagger generate server -A multi-auth-example -P models.Principal -f ./swagger.yml +mv configure_multi_auth_example.go restapi/ + +cd ${examples} +go test -v ./... \ No newline at end of file