diff --git a/agent/http.go b/agent/http.go index ed6f350922..930f003472 100644 --- a/agent/http.go +++ b/agent/http.go @@ -39,6 +39,29 @@ type HTTPServer struct { proto string } +// endpoint is a Consul-specific HTTP handler that takes the usual arguments in +// but returns a response object and error, both of which are handled in a +// common manner by Consul's HTTP server. +type endpoint func(resp http.ResponseWriter, req *http.Request) (interface{}, error) + +// unboundEndpoint is an endpoint method on a server. +type unboundEndpoint func(s *HTTPServer, resp http.ResponseWriter, req *http.Request) (interface{}, error) + +// endpoints is a map from URL pattern to unbound endpoint. +var endpoints map[string]unboundEndpoint + +// registerEndpoint registers a new endpoint, which should be done at package +// init() time. +func registerEndpoint(pattern string, fn unboundEndpoint) { + if endpoints == nil { + endpoints = make(map[string]unboundEndpoint) + } + if endpoints[pattern] != nil { + panic(fmt.Errorf("Pattern %q is already registered", pattern)) + } + endpoints[pattern] = fn +} + // handler is used to attach our handlers to the mux func (s *HTTPServer) handler(enableDebug bool) http.Handler { mux := http.NewServeMux() @@ -75,77 +98,13 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { } mux.HandleFunc("/", s.Index) - - // API V1. - handleFuncMetrics("/v1/acl/bootstrap", s.wrap(s.ACLBootstrap)) - handleFuncMetrics("/v1/acl/create", s.wrap(s.ACLCreate)) - handleFuncMetrics("/v1/acl/update", s.wrap(s.ACLUpdate)) - handleFuncMetrics("/v1/acl/destroy/", s.wrap(s.ACLDestroy)) - handleFuncMetrics("/v1/acl/info/", s.wrap(s.ACLGet)) - handleFuncMetrics("/v1/acl/clone/", s.wrap(s.ACLClone)) - handleFuncMetrics("/v1/acl/list", s.wrap(s.ACLList)) - handleFuncMetrics("/v1/acl/replication", s.wrap(s.ACLReplicationStatus)) - handleFuncMetrics("/v1/agent/token/", s.wrap(s.AgentToken)) - handleFuncMetrics("/v1/agent/self", s.wrap(s.AgentSelf)) - handleFuncMetrics("/v1/agent/maintenance", s.wrap(s.AgentNodeMaintenance)) - handleFuncMetrics("/v1/agent/reload", s.wrap(s.AgentReload)) - handleFuncMetrics("/v1/agent/monitor", s.wrap(s.AgentMonitor)) - handleFuncMetrics("/v1/agent/metrics", s.wrap(s.AgentMetrics)) - handleFuncMetrics("/v1/agent/services", s.wrap(s.AgentServices)) - handleFuncMetrics("/v1/agent/checks", s.wrap(s.AgentChecks)) - handleFuncMetrics("/v1/agent/members", s.wrap(s.AgentMembers)) - handleFuncMetrics("/v1/agent/join/", s.wrap(s.AgentJoin)) - handleFuncMetrics("/v1/agent/leave", s.wrap(s.AgentLeave)) - handleFuncMetrics("/v1/agent/force-leave/", s.wrap(s.AgentForceLeave)) - handleFuncMetrics("/v1/agent/check/register", s.wrap(s.AgentRegisterCheck)) - handleFuncMetrics("/v1/agent/check/deregister/", s.wrap(s.AgentDeregisterCheck)) - handleFuncMetrics("/v1/agent/check/pass/", s.wrap(s.AgentCheckPass)) - handleFuncMetrics("/v1/agent/check/warn/", s.wrap(s.AgentCheckWarn)) - handleFuncMetrics("/v1/agent/check/fail/", s.wrap(s.AgentCheckFail)) - handleFuncMetrics("/v1/agent/check/update/", s.wrap(s.AgentCheckUpdate)) - handleFuncMetrics("/v1/agent/service/register", s.wrap(s.AgentRegisterService)) - handleFuncMetrics("/v1/agent/service/deregister/", s.wrap(s.AgentDeregisterService)) - handleFuncMetrics("/v1/agent/service/maintenance/", s.wrap(s.AgentServiceMaintenance)) - handleFuncMetrics("/v1/catalog/register", s.wrap(s.CatalogRegister)) - handleFuncMetrics("/v1/catalog/deregister", s.wrap(s.CatalogDeregister)) - handleFuncMetrics("/v1/catalog/datacenters", s.wrap(s.CatalogDatacenters)) - handleFuncMetrics("/v1/catalog/nodes", s.wrap(s.CatalogNodes)) - handleFuncMetrics("/v1/catalog/services", s.wrap(s.CatalogServices)) - handleFuncMetrics("/v1/catalog/service/", s.wrap(s.CatalogServiceNodes)) - handleFuncMetrics("/v1/catalog/node/", s.wrap(s.CatalogNodeServices)) - handleFuncMetrics("/v1/coordinate/datacenters", s.wrap(s.CoordinateDatacenters)) - handleFuncMetrics("/v1/coordinate/nodes", s.wrap(s.CoordinateNodes)) - handleFuncMetrics("/v1/coordinate/node/", s.wrap(s.CoordinateNode)) - handleFuncMetrics("/v1/coordinate/update", s.wrap(s.CoordinateUpdate)) - handleFuncMetrics("/v1/event/fire/", s.wrap(s.EventFire)) - handleFuncMetrics("/v1/event/list", s.wrap(s.EventList)) - handleFuncMetrics("/v1/health/node/", s.wrap(s.HealthNodeChecks)) - handleFuncMetrics("/v1/health/checks/", s.wrap(s.HealthServiceChecks)) - handleFuncMetrics("/v1/health/state/", s.wrap(s.HealthChecksInState)) - handleFuncMetrics("/v1/health/service/", s.wrap(s.HealthServiceNodes)) - handleFuncMetrics("/v1/internal/ui/nodes", s.wrap(s.UINodes)) - handleFuncMetrics("/v1/internal/ui/node/", s.wrap(s.UINodeInfo)) - handleFuncMetrics("/v1/internal/ui/services", s.wrap(s.UIServices)) - handleFuncMetrics("/v1/kv/", s.wrap(s.KVSEndpoint)) - handleFuncMetrics("/v1/operator/raft/configuration", s.wrap(s.OperatorRaftConfiguration)) - handleFuncMetrics("/v1/operator/raft/peer", s.wrap(s.OperatorRaftPeer)) - handleFuncMetrics("/v1/operator/keyring", s.wrap(s.OperatorKeyringEndpoint)) - handleFuncMetrics("/v1/operator/autopilot/configuration", s.wrap(s.OperatorAutopilotConfiguration)) - handleFuncMetrics("/v1/operator/autopilot/health", s.wrap(s.OperatorServerHealth)) - handleFuncMetrics("/v1/query", s.wrap(s.PreparedQueryGeneral)) - handleFuncMetrics("/v1/query/", s.wrap(s.PreparedQuerySpecific)) - handleFuncMetrics("/v1/session/create", s.wrap(s.SessionCreate)) - handleFuncMetrics("/v1/session/destroy/", s.wrap(s.SessionDestroy)) - handleFuncMetrics("/v1/session/renew/", s.wrap(s.SessionRenew)) - handleFuncMetrics("/v1/session/info/", s.wrap(s.SessionGet)) - handleFuncMetrics("/v1/session/node/", s.wrap(s.SessionsForNode)) - handleFuncMetrics("/v1/session/list", s.wrap(s.SessionList)) - handleFuncMetrics("/v1/status/leader", s.wrap(s.StatusLeader)) - handleFuncMetrics("/v1/status/peers", s.wrap(s.StatusPeers)) - handleFuncMetrics("/v1/snapshot", s.wrap(s.Snapshot)) - handleFuncMetrics("/v1/txn", s.wrap(s.Txn)) - - // Debug endpoints. + for pattern, fn := range endpoints { + thisFn := fn + bound := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + return thisFn(s, resp, req) + } + handleFuncMetrics(pattern, s.wrap(bound)) + } if enableDebug { handleFuncMetrics("/debug/pprof/", pprof.Index) handleFuncMetrics("/debug/pprof/cmdline", pprof.Cmdline) @@ -186,7 +145,7 @@ var ( ) // wrap is used to wrap functions to make them more convenient -func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) http.HandlerFunc { +func (s *HTTPServer) wrap(handler endpoint) http.HandlerFunc { return func(resp http.ResponseWriter, req *http.Request) { setHeaders(resp, s.agent.config.HTTPResponseHeaders) setTranslateAddr(resp, s.agent.config.TranslateWANAddrs) diff --git a/agent/http_oss.go b/agent/http_oss.go new file mode 100644 index 0000000000..10e9abda04 --- /dev/null +++ b/agent/http_oss.go @@ -0,0 +1,71 @@ +package agent + +func init() { + registerEndpoint("/v1/acl/bootstrap", (*HTTPServer).ACLBootstrap) + registerEndpoint("/v1/acl/create", (*HTTPServer).ACLCreate) + registerEndpoint("/v1/acl/update", (*HTTPServer).ACLUpdate) + registerEndpoint("/v1/acl/destroy/", (*HTTPServer).ACLDestroy) + registerEndpoint("/v1/acl/info/", (*HTTPServer).ACLGet) + registerEndpoint("/v1/acl/clone/", (*HTTPServer).ACLClone) + registerEndpoint("/v1/acl/list", (*HTTPServer).ACLList) + registerEndpoint("/v1/acl/replication", (*HTTPServer).ACLReplicationStatus) + registerEndpoint("/v1/agent/token/", (*HTTPServer).AgentToken) + registerEndpoint("/v1/agent/self", (*HTTPServer).AgentSelf) + registerEndpoint("/v1/agent/maintenance", (*HTTPServer).AgentNodeMaintenance) + registerEndpoint("/v1/agent/reload", (*HTTPServer).AgentReload) + registerEndpoint("/v1/agent/monitor", (*HTTPServer).AgentMonitor) + registerEndpoint("/v1/agent/metrics", (*HTTPServer).AgentMetrics) + registerEndpoint("/v1/agent/services", (*HTTPServer).AgentServices) + registerEndpoint("/v1/agent/checks", (*HTTPServer).AgentChecks) + registerEndpoint("/v1/agent/members", (*HTTPServer).AgentMembers) + registerEndpoint("/v1/agent/join/", (*HTTPServer).AgentJoin) + registerEndpoint("/v1/agent/leave", (*HTTPServer).AgentLeave) + registerEndpoint("/v1/agent/force-leave/", (*HTTPServer).AgentForceLeave) + registerEndpoint("/v1/agent/check/register", (*HTTPServer).AgentRegisterCheck) + registerEndpoint("/v1/agent/check/deregister/", (*HTTPServer).AgentDeregisterCheck) + registerEndpoint("/v1/agent/check/pass/", (*HTTPServer).AgentCheckPass) + registerEndpoint("/v1/agent/check/warn/", (*HTTPServer).AgentCheckWarn) + registerEndpoint("/v1/agent/check/fail/", (*HTTPServer).AgentCheckFail) + registerEndpoint("/v1/agent/check/update/", (*HTTPServer).AgentCheckUpdate) + registerEndpoint("/v1/agent/service/register", (*HTTPServer).AgentRegisterService) + registerEndpoint("/v1/agent/service/deregister/", (*HTTPServer).AgentDeregisterService) + registerEndpoint("/v1/agent/service/maintenance/", (*HTTPServer).AgentServiceMaintenance) + registerEndpoint("/v1/catalog/register", (*HTTPServer).CatalogRegister) + registerEndpoint("/v1/catalog/deregister", (*HTTPServer).CatalogDeregister) + registerEndpoint("/v1/catalog/datacenters", (*HTTPServer).CatalogDatacenters) + registerEndpoint("/v1/catalog/nodes", (*HTTPServer).CatalogNodes) + registerEndpoint("/v1/catalog/services", (*HTTPServer).CatalogServices) + registerEndpoint("/v1/catalog/service/", (*HTTPServer).CatalogServiceNodes) + registerEndpoint("/v1/catalog/node/", (*HTTPServer).CatalogNodeServices) + registerEndpoint("/v1/coordinate/datacenters", (*HTTPServer).CoordinateDatacenters) + registerEndpoint("/v1/coordinate/nodes", (*HTTPServer).CoordinateNodes) + registerEndpoint("/v1/coordinate/node/", (*HTTPServer).CoordinateNode) + registerEndpoint("/v1/coordinate/update", (*HTTPServer).CoordinateUpdate) + registerEndpoint("/v1/event/fire/", (*HTTPServer).EventFire) + registerEndpoint("/v1/event/list", (*HTTPServer).EventList) + registerEndpoint("/v1/health/node/", (*HTTPServer).HealthNodeChecks) + registerEndpoint("/v1/health/checks/", (*HTTPServer).HealthServiceChecks) + registerEndpoint("/v1/health/state/", (*HTTPServer).HealthChecksInState) + registerEndpoint("/v1/health/service/", (*HTTPServer).HealthServiceNodes) + registerEndpoint("/v1/internal/ui/nodes", (*HTTPServer).UINodes) + registerEndpoint("/v1/internal/ui/node/", (*HTTPServer).UINodeInfo) + registerEndpoint("/v1/internal/ui/services", (*HTTPServer).UIServices) + registerEndpoint("/v1/kv/", (*HTTPServer).KVSEndpoint) + registerEndpoint("/v1/operator/raft/configuration", (*HTTPServer).OperatorRaftConfiguration) + registerEndpoint("/v1/operator/raft/peer", (*HTTPServer).OperatorRaftPeer) + registerEndpoint("/v1/operator/keyring", (*HTTPServer).OperatorKeyringEndpoint) + registerEndpoint("/v1/operator/autopilot/configuration", (*HTTPServer).OperatorAutopilotConfiguration) + registerEndpoint("/v1/operator/autopilot/health", (*HTTPServer).OperatorServerHealth) + registerEndpoint("/v1/query", (*HTTPServer).PreparedQueryGeneral) + registerEndpoint("/v1/query/", (*HTTPServer).PreparedQuerySpecific) + registerEndpoint("/v1/session/create", (*HTTPServer).SessionCreate) + registerEndpoint("/v1/session/destroy/", (*HTTPServer).SessionDestroy) + registerEndpoint("/v1/session/renew/", (*HTTPServer).SessionRenew) + registerEndpoint("/v1/session/info/", (*HTTPServer).SessionGet) + registerEndpoint("/v1/session/node/", (*HTTPServer).SessionsForNode) + registerEndpoint("/v1/session/list", (*HTTPServer).SessionList) + registerEndpoint("/v1/status/leader", (*HTTPServer).StatusLeader) + registerEndpoint("/v1/status/peers", (*HTTPServer).StatusPeers) + registerEndpoint("/v1/snapshot", (*HTTPServer).Snapshot) + registerEndpoint("/v1/txn", (*HTTPServer).Txn) +} diff --git a/agent/http_oss_test.go b/agent/http_oss_test.go new file mode 100644 index 0000000000..d2e04419b7 --- /dev/null +++ b/agent/http_oss_test.go @@ -0,0 +1,113 @@ +package agent + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/hashicorp/consul/logger" +) + +func TestHTTPAPI_MethodNotAllowed_OSS(t *testing.T) { + tests := []struct { + methods, uri string + }{ + {"PUT", "/v1/acl/bootstrap"}, + {"PUT", "/v1/acl/create"}, + {"PUT", "/v1/acl/update"}, + {"PUT", "/v1/acl/destroy/"}, + {"GET", "/v1/acl/info/"}, + {"PUT", "/v1/acl/clone/"}, + {"GET", "/v1/acl/list"}, + {"GET", "/v1/acl/replication"}, + {"PUT", "/v1/agent/token/"}, + {"GET", "/v1/agent/self"}, + {"GET", "/v1/agent/members"}, + {"PUT", "/v1/agent/check/deregister/"}, + {"PUT", "/v1/agent/check/fail/"}, + {"PUT", "/v1/agent/check/pass/"}, + {"PUT", "/v1/agent/check/register"}, + {"PUT", "/v1/agent/check/update/"}, + {"PUT", "/v1/agent/check/warn/"}, + {"GET", "/v1/agent/checks"}, + {"PUT", "/v1/agent/force-leave/"}, + {"PUT", "/v1/agent/join/"}, + {"PUT", "/v1/agent/leave"}, + {"PUT", "/v1/agent/maintenance"}, + {"GET", "/v1/agent/metrics"}, + // {"GET", "/v1/agent/monitor"}, // requires LogWriter. Hangs if LogWriter is provided + {"PUT", "/v1/agent/reload"}, + {"PUT", "/v1/agent/service/deregister/"}, + {"PUT", "/v1/agent/service/maintenance/"}, + {"PUT", "/v1/agent/service/register"}, + {"GET", "/v1/agent/services"}, + {"GET", "/v1/catalog/datacenters"}, + {"PUT", "/v1/catalog/deregister"}, + {"GET", "/v1/catalog/node/"}, + {"GET", "/v1/catalog/nodes"}, + {"PUT", "/v1/catalog/register"}, + {"GET", "/v1/catalog/service/"}, + {"GET", "/v1/catalog/services"}, + {"GET", "/v1/coordinate/datacenters"}, + {"GET", "/v1/coordinate/nodes"}, + {"GET", "/v1/coordinate/node/"}, + {"PUT", "/v1/event/fire/"}, + {"GET", "/v1/event/list"}, + {"GET", "/v1/health/checks/"}, + {"GET", "/v1/health/node/"}, + {"GET", "/v1/health/service/"}, + {"GET", "/v1/health/state/"}, + {"GET", "/v1/internal/ui/node/"}, + {"GET", "/v1/internal/ui/nodes"}, + {"GET", "/v1/internal/ui/services"}, + {"GET PUT DELETE", "/v1/kv/"}, + {"GET PUT", "/v1/operator/autopilot/configuration"}, + {"GET", "/v1/operator/autopilot/health"}, + {"GET POST PUT DELETE", "/v1/operator/keyring"}, + {"GET", "/v1/operator/raft/configuration"}, + {"DELETE", "/v1/operator/raft/peer"}, + {"GET POST", "/v1/query"}, + {"GET PUT DELETE", "/v1/query/"}, + {"GET", "/v1/query/xxx/execute"}, + {"GET", "/v1/query/xxx/explain"}, + {"PUT", "/v1/session/create"}, + {"PUT", "/v1/session/destroy/"}, + {"GET", "/v1/session/info/"}, + {"GET", "/v1/session/list"}, + {"GET", "/v1/session/node/"}, + {"PUT", "/v1/session/renew/"}, + {"GET PUT", "/v1/snapshot"}, + {"GET", "/v1/status/leader"}, + // {"GET", "/v1/status/peers"},// hangs + {"PUT", "/v1/txn"}, + } + + a := NewTestAgent(t.Name(), `acl_datacenter = "dc1"`) + a.Agent.LogWriter = logger.NewLogWriter(512) + defer a.Shutdown() + + all := []string{"GET", "PUT", "POST", "DELETE", "HEAD"} + client := http.Client{} + + for _, tt := range tests { + for _, m := range all { + t.Run(m+" "+tt.uri, func(t *testing.T) { + uri := fmt.Sprintf("http://%s%s", a.HTTPAddr(), tt.uri) + req, _ := http.NewRequest(m, uri, nil) + resp, err := client.Do(req) + if err != nil { + t.Fatal("client.Do failed: ", err) + } + + allowed := strings.Contains(tt.methods, m) + if allowed && resp.StatusCode == http.StatusMethodNotAllowed { + t.Fatalf("method allowed: got status code %d want any other code", resp.StatusCode) + } + if !allowed && resp.StatusCode != http.StatusMethodNotAllowed { + t.Fatalf("method not allowed: got status code %d want %d", resp.StatusCode, http.StatusMethodNotAllowed) + } + }) + } + } +} diff --git a/agent/http_test.go b/agent/http_test.go index a80c6df12a..8a347d046e 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -20,7 +20,6 @@ import ( "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/logger" "github.com/hashicorp/consul/testutil" "github.com/hashicorp/go-cleanhttp" "golang.org/x/net/http2" @@ -390,115 +389,6 @@ func TestHTTPAPIResponseHeaders(t *testing.T) { } } -func TestHTTPAPI_MethodNotAllowed(t *testing.T) { - tests := []struct { - methods, uri string - }{ - {"PUT", "/v1/acl/bootstrap"}, - {"PUT", "/v1/acl/create"}, - {"PUT", "/v1/acl/update"}, - {"PUT", "/v1/acl/destroy/"}, - {"GET", "/v1/acl/info/"}, - {"PUT", "/v1/acl/clone/"}, - {"GET", "/v1/acl/list"}, - {"GET", "/v1/acl/replication"}, - {"PUT", "/v1/agent/token/"}, - {"GET", "/v1/agent/self"}, - {"GET", "/v1/agent/members"}, - {"PUT", "/v1/agent/check/deregister/"}, - {"PUT", "/v1/agent/check/fail/"}, - {"PUT", "/v1/agent/check/pass/"}, - {"PUT", "/v1/agent/check/register"}, - {"PUT", "/v1/agent/check/update/"}, - {"PUT", "/v1/agent/check/warn/"}, - {"GET", "/v1/agent/checks"}, - {"PUT", "/v1/agent/force-leave/"}, - {"PUT", "/v1/agent/join/"}, - {"PUT", "/v1/agent/leave"}, - {"PUT", "/v1/agent/maintenance"}, - {"GET", "/v1/agent/metrics"}, - // {"GET", "/v1/agent/monitor"}, // requires LogWriter. Hangs if LogWriter is provided - {"PUT", "/v1/agent/reload"}, - {"PUT", "/v1/agent/service/deregister/"}, - {"PUT", "/v1/agent/service/maintenance/"}, - {"PUT", "/v1/agent/service/register"}, - {"GET", "/v1/agent/services"}, - {"GET", "/v1/catalog/datacenters"}, - {"PUT", "/v1/catalog/deregister"}, - {"GET", "/v1/catalog/node/"}, - {"GET", "/v1/catalog/nodes"}, - {"PUT", "/v1/catalog/register"}, - {"GET", "/v1/catalog/service/"}, - {"GET", "/v1/catalog/services"}, - {"GET", "/v1/coordinate/datacenters"}, - {"GET", "/v1/coordinate/nodes"}, - {"GET", "/v1/coordinate/node/"}, - {"PUT", "/v1/event/fire/"}, - {"GET", "/v1/event/list"}, - {"GET", "/v1/health/checks/"}, - {"GET", "/v1/health/node/"}, - {"GET", "/v1/health/service/"}, - {"GET", "/v1/health/state/"}, - {"GET", "/v1/internal/ui/node/"}, - {"GET", "/v1/internal/ui/nodes"}, - {"GET", "/v1/internal/ui/services"}, - {"GET PUT DELETE", "/v1/kv/"}, - {"GET PUT", "/v1/operator/autopilot/configuration"}, - {"GET", "/v1/operator/autopilot/health"}, - {"GET POST PUT DELETE", "/v1/operator/keyring"}, - {"GET", "/v1/operator/raft/configuration"}, - {"DELETE", "/v1/operator/raft/peer"}, - {"GET POST", "/v1/query"}, - {"GET PUT DELETE", "/v1/query/"}, - {"GET", "/v1/query/xxx/execute"}, - {"GET", "/v1/query/xxx/explain"}, - {"PUT", "/v1/session/create"}, - {"PUT", "/v1/session/destroy/"}, - {"GET", "/v1/session/info/"}, - {"GET", "/v1/session/list"}, - {"GET", "/v1/session/node/"}, - {"PUT", "/v1/session/renew/"}, - {"GET PUT", "/v1/snapshot"}, - {"GET", "/v1/status/leader"}, - // {"GET", "/v1/status/peers"},// hangs - {"PUT", "/v1/txn"}, - - // enterprise only - // {"GET POST", "/v1/operator/area"}, - // {"GET PUT DELETE", "/v1/operator/area/"}, - // {"GET", "/v1/operator/area/xxx/members"}, - } - - a := NewTestAgent(t.Name(), `acl_datacenter = "dc1"`) - a.Agent.LogWriter = logger.NewLogWriter(512) - defer a.Shutdown() - - all := []string{"GET", "PUT", "POST", "DELETE", "HEAD"} - client := http.Client{} - - for _, tt := range tests { - for _, m := range all { - - t.Run(m+" "+tt.uri, func(t *testing.T) { - uri := fmt.Sprintf("http://%s%s", a.HTTPAddr(), tt.uri) - req, _ := http.NewRequest(m, uri, nil) - resp, err := client.Do(req) - if err != nil { - t.Fatal("client.Do failed: ", err) - } - - allowed := strings.Contains(tt.methods, m) - if allowed && resp.StatusCode == http.StatusMethodNotAllowed { - t.Fatalf("method allowed: got status code %d want any other code", resp.StatusCode) - } - if !allowed && resp.StatusCode != http.StatusMethodNotAllowed { - t.Fatalf("method not allowed: got status code %d want %d", resp.StatusCode, http.StatusMethodNotAllowed) - } - }) - } - } -} - func TestContentTypeIsJSON(t *testing.T) { t.Parallel() a := NewTestAgent(t.Name(), "")