From df91661ec6f72a3725fd836f4f18f8eb0382b1b4 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Tue, 2 Feb 2016 17:38:02 -0800 Subject: [PATCH] flags: Remove anonymous, ratelimit, json and web-address flags. - Web address now uses the port + 1 from the API address port directly. - Remove ratelimiting, ratelimiting will be achieved if necessary through iptables. - Remove json flag, not needed anymore. - Remove anonymous flag, server will be no more anonymous for play.minio.io we will use demo credentials. --- bucket-handlers.go | 104 +++++------------------- flags.go | 25 ------ main.go | 4 - object-handlers.go | 186 +++++++++++++------------------------------ routers.go | 7 +- server-main.go | 46 ++++++----- server_fs_test.go | 1 - signature-handler.go | 7 -- 8 files changed, 98 insertions(+), 282 deletions(-) diff --git a/bucket-handlers.go b/bucket-handlers.go index 0956b44f1..c87ab5321 100644 --- a/bucket-handlers.go +++ b/bucket-handlers.go @@ -34,13 +34,6 @@ func (api CloudStorageAPI) GetBucketLocationHandler(w http.ResponseWriter, req * vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - _, err := api.Filesystem.GetBucketMetadata(bucket) if err != nil { errorIf(err.Trace(), "GetBucketMetadata failed.", nil) @@ -75,13 +68,6 @@ func (api CloudStorageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, re vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - resources := getBucketMultipartResources(req.URL.Query()) if resources.MaxUploads < 0 { writeErrorResponse(w, req, InvalidMaxUploads, req.URL.Path) @@ -121,15 +107,6 @@ func (api CloudStorageAPI) ListObjectsHandler(w http.ResponseWriter, req *http.R vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - } - // TODO handle encoding type. prefix, marker, delimiter, maxkeys, _ := getBucketResources(req.URL.Query()) if maxkeys < 0 { @@ -171,12 +148,6 @@ func (api CloudStorageAPI) ListObjectsHandler(w http.ResponseWriter, req *http.R // This implementation of the GET operation returns a list of all buckets // owned by the authenticated sender of the request. func (api CloudStorageAPI) ListBucketsHandler(w http.ResponseWriter, req *http.Request) { - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } buckets, err := api.Filesystem.ListBuckets() if err == nil { // generate response @@ -199,13 +170,6 @@ func (api CloudStorageAPI) PutBucketHandler(w http.ResponseWriter, req *http.Req vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - // read from 'x-amz-acl' aclType := getACLType(req) if aclType == unsupportedACLType { @@ -214,26 +178,24 @@ func (api CloudStorageAPI) PutBucketHandler(w http.ResponseWriter, req *http.Req } var signature *fs.Signature - if !api.Anonymous { - // Init signature V4 verification - if isRequestSignatureV4(req) { - var err *probe.Error - signature, err = initSignatureV4(req) - if err != nil { - switch err.ToGoError() { - case errInvalidRegion: - errorIf(err.Trace(), "Unknown region in authorization header.", nil) - writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) - return - case errAccessKeyIDInvalid: - errorIf(err.Trace(), "Invalid access key id.", nil) - writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) - return - default: - errorIf(err.Trace(), "Initializing signature v4 failed.", nil) - writeErrorResponse(w, req, InternalError, req.URL.Path) - return - } + // Init signature V4 verification + if isRequestSignatureV4(req) { + var err *probe.Error + signature, err = initSignatureV4(req) + if err != nil { + switch err.ToGoError() { + case errInvalidRegion: + errorIf(err.Trace(), "Unknown region in authorization header.", nil) + writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) + return + case errAccessKeyIDInvalid: + errorIf(err.Trace(), "Invalid access key id.", nil) + writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) + return + default: + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) + writeErrorResponse(w, req, InternalError, req.URL.Path) + return } } } @@ -374,13 +336,6 @@ func (api CloudStorageAPI) PutBucketACLHandler(w http.ResponseWriter, req *http. vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - // read from 'x-amz-acl' aclType := getACLType(req) if aclType == unsupportedACLType { @@ -413,13 +368,6 @@ func (api CloudStorageAPI) GetBucketACLHandler(w http.ResponseWriter, req *http. vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - bucketMetadata, err := api.Filesystem.GetBucketMetadata(bucket) if err != nil { errorIf(err.Trace(), "GetBucketMetadata failed.", nil) @@ -452,15 +400,6 @@ func (api CloudStorageAPI) HeadBucketHandler(w http.ResponseWriter, req *http.Re vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - } - _, err := api.Filesystem.GetBucketMetadata(bucket) if err != nil { errorIf(err.Trace(), "GetBucketMetadata failed.", nil) @@ -482,13 +421,6 @@ func (api CloudStorageAPI) DeleteBucketHandler(w http.ResponseWriter, req *http. vars := mux.Vars(req) bucket := vars["bucket"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - err := api.Filesystem.DeleteBucket(bucket) if err != nil { errorIf(err.Trace(), "DeleteBucket failed.", nil) diff --git a/flags.go b/flags.go index cfc58b6e8..3274cca58 100644 --- a/flags.go +++ b/flags.go @@ -34,32 +34,12 @@ var ( Usage: "ADDRESS:PORT for cloud storage access.", } - webAddressFlag = cli.StringFlag{ - Name: "web-address", - Value: ":9001", - Hide: true, - Usage: "WEBADDRESS:PORT for cloud storage access.", - } - accessLogFlag = cli.BoolFlag{ Name: "enable-accesslog", Hide: true, Usage: "Enable access logs for all incoming HTTP request.", } - rateLimitFlag = cli.IntFlag{ - Name: "ratelimit", - Hide: true, - Value: 0, - Usage: "Limit for total concurrent requests: [DEFAULT: 0].", - } - - anonymousFlag = cli.BoolFlag{ - Name: "anonymous", - Hide: true, - Usage: "Make server run in anonymous mode where all client connections are accepted.", - } - certFlag = cli.StringFlag{ Name: "cert", Usage: "Provide your domain certificate.", @@ -69,11 +49,6 @@ var ( Name: "key", Usage: "Provide your domain private key.", } - - jsonFlag = cli.BoolFlag{ - Name: "json", - Usage: "Enable json formatted output.", - } ) // registerFlag registers a cli flag diff --git a/main.go b/main.go index f5911e406..30c2ab1c7 100644 --- a/main.go +++ b/main.go @@ -121,13 +121,9 @@ func registerApp() *cli.App { // register all flags registerFlag(configFolderFlag) registerFlag(addressFlag) - registerFlag(webAddressFlag) registerFlag(accessLogFlag) - registerFlag(rateLimitFlag) - registerFlag(anonymousFlag) registerFlag(certFlag) registerFlag(keyFlag) - registerFlag(jsonFlag) // set up app app := cli.NewApp() diff --git a/object-handlers.go b/object-handlers.go index 686029d18..311e78e8b 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -39,15 +39,6 @@ func (api CloudStorageAPI) GetObjectHandler(w http.ResponseWriter, req *http.Req bucket = vars["bucket"] object = vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - } - metadata, err := api.Filesystem.GetObjectMetadata(bucket, object) if err != nil { errorIf(err.Trace(), "GetObject failed.", nil) @@ -87,15 +78,6 @@ func (api CloudStorageAPI) HeadObjectHandler(w http.ResponseWriter, req *http.Re bucket = vars["bucket"] object = vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - if api.Filesystem.IsPrivateBucket(bucket) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - } - metadata, err := api.Filesystem.GetObjectMetadata(bucket, object) if err != nil { switch err.ToGoError().(type) { @@ -125,15 +107,6 @@ func (api CloudStorageAPI) PutObjectHandler(w http.ResponseWriter, req *http.Req bucket = vars["bucket"] object = vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - } - // get Content-MD5 sent by client and verify if valid md5 := req.Header.Get("Content-MD5") if !isValidMD5(md5) { @@ -153,26 +126,24 @@ func (api CloudStorageAPI) PutObjectHandler(w http.ResponseWriter, req *http.Req } var signature *fs.Signature - if !api.Anonymous { - if isRequestSignatureV4(req) { - // Init signature V4 verification - var err *probe.Error - signature, err = initSignatureV4(req) - if err != nil { - switch err.ToGoError() { - case errInvalidRegion: - errorIf(err.Trace(), "Unknown region in authorization header.", nil) - writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) - return - case errAccessKeyIDInvalid: - errorIf(err.Trace(), "Invalid access key id.", nil) - writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) - return - default: - errorIf(err.Trace(), "Initializing signature v4 failed.", nil) - writeErrorResponse(w, req, InternalError, req.URL.Path) - return - } + if isRequestSignatureV4(req) { + // Init signature V4 verification + var err *probe.Error + signature, err = initSignatureV4(req) + if err != nil { + switch err.ToGoError() { + case errInvalidRegion: + errorIf(err.Trace(), "Unknown region in authorization header.", nil) + writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) + return + case errAccessKeyIDInvalid: + errorIf(err.Trace(), "Invalid access key id.", nil) + writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) + return + default: + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) + writeErrorResponse(w, req, InternalError, req.URL.Path) + return } } } @@ -219,14 +190,6 @@ func (api CloudStorageAPI) NewMultipartUploadHandler(w http.ResponseWriter, req bucket = vars["bucket"] object = vars["object"] - if !api.Anonymous { - // Unauthorized multipart uploads are not supported - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - uploadID, err := api.Filesystem.NewMultipartUpload(bucket, object) if err != nil { errorIf(err.Trace(), "NewMultipartUpload failed.", nil) @@ -261,13 +224,6 @@ func (api CloudStorageAPI) PutObjectPartHandler(w http.ResponseWriter, req *http bucket := vars["bucket"] object := vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - // get Content-MD5 sent by client and verify if valid md5 := req.Header.Get("Content-MD5") if !isValidMD5(md5) { @@ -302,26 +258,24 @@ func (api CloudStorageAPI) PutObjectPartHandler(w http.ResponseWriter, req *http } var signature *fs.Signature - if !api.Anonymous { - if isRequestSignatureV4(req) { - // Init signature V4 verification - var err *probe.Error - signature, err = initSignatureV4(req) - if err != nil { - switch err.ToGoError() { - case errInvalidRegion: - errorIf(err.Trace(), "Unknown region in authorization header.", nil) - writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) - return - case errAccessKeyIDInvalid: - errorIf(err.Trace(), "Invalid access key id.", nil) - writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) - return - default: - errorIf(err.Trace(), "Initializing signature v4 failed.", nil) - writeErrorResponse(w, req, InternalError, req.URL.Path) - return - } + if isRequestSignatureV4(req) { + // Init signature V4 verification + var err *probe.Error + signature, err = initSignatureV4(req) + if err != nil { + switch err.ToGoError() { + case errInvalidRegion: + errorIf(err.Trace(), "Unknown region in authorization header.", nil) + writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) + return + case errAccessKeyIDInvalid: + errorIf(err.Trace(), "Invalid access key id.", nil) + writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) + return + default: + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) + writeErrorResponse(w, req, InternalError, req.URL.Path) + return } } } @@ -361,13 +315,6 @@ func (api CloudStorageAPI) AbortMultipartUploadHandler(w http.ResponseWriter, re bucket := vars["bucket"] object := vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - objectResourcesMetadata := getObjectResources(req.URL.Query()) err := api.Filesystem.AbortMultipartUpload(bucket, object, objectResourcesMetadata.UploadID) if err != nil { @@ -397,13 +344,6 @@ func (api CloudStorageAPI) ListObjectPartsHandler(w http.ResponseWriter, req *ht bucket := vars["bucket"] object := vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - objectResourcesMetadata := getObjectResources(req.URL.Query()) if objectResourcesMetadata.PartNumberMarker < 0 { writeErrorResponse(w, req, InvalidPartNumberMarker, req.URL.Path) @@ -450,35 +390,26 @@ func (api CloudStorageAPI) CompleteMultipartUploadHandler(w http.ResponseWriter, bucket := vars["bucket"] object := vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - objectResourcesMetadata := getObjectResources(req.URL.Query()) var signature *fs.Signature - if !api.Anonymous { - if isRequestSignatureV4(req) { - // Init signature V4 verification - var err *probe.Error - signature, err = initSignatureV4(req) - if err != nil { - switch err.ToGoError() { - case errInvalidRegion: - errorIf(err.Trace(), "Unknown region in authorization header.", nil) - writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) - return - case errAccessKeyIDInvalid: - errorIf(err.Trace(), "Invalid access key id.", nil) - writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) - return - default: - errorIf(err.Trace(), "Initializing signature v4 failed.", nil) - writeErrorResponse(w, req, InternalError, req.URL.Path) - return - } + if isRequestSignatureV4(req) { + // Init signature V4 verification + var err *probe.Error + signature, err = initSignatureV4(req) + if err != nil { + switch err.ToGoError() { + case errInvalidRegion: + errorIf(err.Trace(), "Unknown region in authorization header.", nil) + writeErrorResponse(w, req, AuthorizationHeaderMalformed, req.URL.Path) + return + case errAccessKeyIDInvalid: + errorIf(err.Trace(), "Invalid access key id.", nil) + writeErrorResponse(w, req, InvalidAccessKeyID, req.URL.Path) + return + default: + errorIf(err.Trace(), "Initializing signature v4 failed.", nil) + writeErrorResponse(w, req, InternalError, req.URL.Path) + return } } } @@ -528,15 +459,6 @@ func (api CloudStorageAPI) DeleteObjectHandler(w http.ResponseWriter, req *http. bucket := vars["bucket"] object := vars["object"] - if !api.Anonymous { - if isRequestRequiresACLCheck(req) { - if api.Filesystem.IsPrivateBucket(bucket) || api.Filesystem.IsReadOnlyBucket(bucket) { - writeErrorResponse(w, req, AccessDenied, req.URL.Path) - return - } - } - } - err := api.Filesystem.DeleteObject(bucket, object) if err != nil { errorIf(err.Trace(), "DeleteObject failed.", nil) diff --git a/routers.go b/routers.go index 8154ae9f6..0d7fe5387 100644 --- a/routers.go +++ b/routers.go @@ -30,8 +30,6 @@ import ( // CloudStorageAPI container for S3 compatible API. type CloudStorageAPI struct { - // Do not check for incoming signatures, allow all requests. - Anonymous bool // Once true log all incoming requests. AccessLog bool // Filesystem instance. @@ -153,7 +151,6 @@ func getNewCloudStorageAPI(conf cloudServerConfig) CloudStorageAPI { } return CloudStorageAPI{ Filesystem: fs, - Anonymous: conf.Anonymous, AccessLog: conf.AccessLog, } } @@ -163,9 +160,7 @@ func getCloudStorageAPIHandler(api CloudStorageAPI) http.Handler { TimeValidityHandler, IgnoreResourcesHandler, IgnoreSignatureV2RequestHandler, - } - if !api.Anonymous { - mwHandlers = append(mwHandlers, SignatureHandler) + SignatureHandler, } if api.AccessLog { mwHandlers = append(mwHandlers, AccessLogHandler) diff --git a/server-main.go b/server-main.go index da39ab047..628df3e27 100644 --- a/server-main.go +++ b/server-main.go @@ -69,10 +69,8 @@ EXAMPLES: // cloudServerConfig - http server config type cloudServerConfig struct { /// HTTP server options - Address string // Address:Port listening - WebAddress string // WebAddress:Port listening - AccessLog bool // Enable access log handler - Anonymous bool // No signature turn off + Address string // Address:Port listening + AccessLog bool // Enable access log handler // Credentials. AccessKeyID string // Access key id. @@ -87,15 +85,25 @@ type cloudServerConfig struct { TLS bool // TLS on when certs are specified CertFile string // Domain certificate KeyFile string // Domain key - - /// Advanced HTTP server options - RateLimit int // Ratelimited server of incoming connections } func configureWebServer(conf cloudServerConfig) (*http.Server, *probe.Error) { + // Split the api address into host and port. + host, port, e := net.SplitHostPort(conf.Address) + if e != nil { + return nil, probe.NewError(e) + } + webPort, e := strconv.Atoi(port) + if e != nil { + return nil, probe.NewError(e) + } + // Always choose the next port, based on the API address port. + webPort = webPort + 1 + webAddress := net.JoinHostPort(host, strconv.Itoa(webPort)) + // Minio server config webServer := &http.Server{ - Addr: conf.WebAddress, + Addr: webAddress, Handler: getWebAPIHandler(getNewWebAPI(conf)), MaxHeaderBytes: 1 << 20, } @@ -133,8 +141,8 @@ func configureAPIServer(conf cloudServerConfig) (*http.Server, *probe.Error) { return apiServer, nil } -func printServerMsg(conf cloudServerConfig) { - host, port, e := net.SplitHostPort(conf.Address) +func printServerMsg(serverConf *http.Server) { + host, port, e := net.SplitHostPort(serverConf.Addr) fatalIf(probe.NewError(e), "Unable to split host port.", nil) var hosts []string @@ -155,7 +163,7 @@ func printServerMsg(conf cloudServerConfig) { } } for _, host := range hosts { - if conf.TLS { + if serverConf.TLSConfig != nil { Printf(" https://%s:%s\n", host, port) } else { Printf(" http://%s:%s\n", host, port) @@ -328,9 +336,7 @@ func serverMain(c *cli.Context) { tls := (certFile != "" && keyFile != "") serverConfig := cloudServerConfig{ Address: c.GlobalString("address"), - WebAddress: c.GlobalString("web-address"), AccessLog: c.GlobalBool("enable-accesslog"), - Anonymous: c.GlobalBool("anonymous"), AccessKeyID: conf.Credentials.AccessKeyID, SecretAccessKey: conf.Credentials.SecretAccessKey, Path: path, @@ -339,23 +345,22 @@ func serverMain(c *cli.Context) { TLS: tls, CertFile: certFile, KeyFile: keyFile, - RateLimit: c.GlobalInt("ratelimit"), } - Println("\nMinio Object Storage:") - printServerMsg(serverConfig) - // configure API server. apiServer, err := configureAPIServer(serverConfig) errorIf(err.Trace(), "Failed to configure API server.", nil) - Println("\nMinio Browser:") - printServerMsg(serverConfig) + Println("\nMinio Object Storage:") + printServerMsg(apiServer) // configure Web server. webServer, err := configureWebServer(serverConfig) errorIf(err.Trace(), "Failed to configure Web server.", nil) + Println("\nMinio Browser:") + printServerMsg(webServer) + Println("\nTo configure Minio Client:") if runtime.GOOS == "windows" { Println(" Download \"mc\" from https://dl.minio.io/client/mc/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/mc.exe") @@ -367,7 +372,6 @@ func serverMain(c *cli.Context) { } // Start server. - rateLimit := serverConfig.RateLimit - err = minhttp.ListenAndServeLimited(rateLimit, apiServer, webServer) + err = minhttp.ListenAndServe(apiServer, webServer) errorIf(err.Trace(), "Failed to start the minio server.", nil) } diff --git a/server_fs_test.go b/server_fs_test.go index 3c87b18fc..d13412aa5 100644 --- a/server_fs_test.go +++ b/server_fs_test.go @@ -78,7 +78,6 @@ func (s *MyAPIFSCacheSuite) SetUpSuite(c *C) { cloudServer := cloudServerConfig{ Path: fsroot, MinFreeDisk: 0, - Anonymous: false, } cloudStorageAPI := getNewCloudStorageAPI(cloudServer) httpHandler := getCloudStorageAPIHandler(cloudStorageAPI) diff --git a/signature-handler.go b/signature-handler.go index 93457de65..19bae67ab 100644 --- a/signature-handler.go +++ b/signature-handler.go @@ -60,13 +60,6 @@ func isRequestPostPolicySignatureV4(req *http.Request) bool { return false } -func isRequestRequiresACLCheck(req *http.Request) bool { - if isRequestSignatureV4(req) || isRequestPresignedSignatureV4(req) || isRequestPostPolicySignatureV4(req) { - return false - } - return true -} - func (s signatureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if isRequestPostPolicySignatureV4(r) && r.Method == "POST" { s.handler.ServeHTTP(w, r)