From 28f19dec60a99531b24e3f509c56de5887ea2733 Mon Sep 17 00:00:00 2001 From: Yicheng Qin Date: Mon, 7 Apr 2014 11:10:08 -0700 Subject: [PATCH] feat(server): make header-only requests work --- server/server.go | 50 ++++++++++++++++++++--------- server/v1/tests/get_handler_test.go | 24 ++++++++++++++ server/v2/tests/get_handler_test.go | 24 ++++++++++++++ tests/http_utils.go | 4 +++ 4 files changed, 86 insertions(+), 16 deletions(-) diff --git a/server/server.go b/server/server.go index 29fcb21ef..09950b6d7 100644 --- a/server/server.go +++ b/server/server.go @@ -100,33 +100,33 @@ func (s *Server) Store() store.Store { } func (s *Server) installV1(r *mux.Router) { - s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.GetKeyHandler).Methods("GET") + s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.GetKeyHandler).Methods("GET", "HEAD") s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.SetKeyHandler).Methods("POST", "PUT") s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.DeleteKeyHandler).Methods("DELETE") s.handleFuncV1(r, "/v1/watch/{key:.*}", v1.WatchKeyHandler).Methods("GET", "POST") - s.handleFunc(r, "/v1/leader", s.GetLeaderHandler).Methods("GET") - s.handleFunc(r, "/v1/machines", s.GetPeersHandler).Methods("GET") - s.handleFunc(r, "/v1/peers", s.GetPeersHandler).Methods("GET") - s.handleFunc(r, "/v1/stats/self", s.GetStatsHandler).Methods("GET") - s.handleFunc(r, "/v1/stats/leader", s.GetLeaderStatsHandler).Methods("GET") - s.handleFunc(r, "/v1/stats/store", s.GetStoreStatsHandler).Methods("GET") + s.handleFunc(r, "/v1/leader", s.GetLeaderHandler).Methods("GET", "HEAD") + s.handleFunc(r, "/v1/machines", s.GetPeersHandler).Methods("GET", "HEAD") + s.handleFunc(r, "/v1/peers", s.GetPeersHandler).Methods("GET", "HEAD") + s.handleFunc(r, "/v1/stats/self", s.GetStatsHandler).Methods("GET", "HEAD") + s.handleFunc(r, "/v1/stats/leader", s.GetLeaderStatsHandler).Methods("GET", "HEAD") + s.handleFunc(r, "/v1/stats/store", s.GetStoreStatsHandler).Methods("GET", "HEAD") } func (s *Server) installV2(r *mux.Router) { r2 := mux.NewRouter() r.PathPrefix("/v2").Handler(ehttp.NewLowerQueryParamsHandler(r2)) - s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.GetHandler).Methods("GET") + s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.GetHandler).Methods("GET", "HEAD") s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.PostHandler).Methods("POST") s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.PutHandler).Methods("PUT") s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.DeleteHandler).Methods("DELETE") - s.handleFunc(r2, "/v2/leader", s.GetLeaderHandler).Methods("GET") - s.handleFunc(r2, "/v2/machines", s.GetPeersHandler).Methods("GET") - s.handleFunc(r2, "/v2/peers", s.GetPeersHandler).Methods("GET") - s.handleFunc(r2, "/v2/stats/self", s.GetStatsHandler).Methods("GET") - s.handleFunc(r2, "/v2/stats/leader", s.GetLeaderStatsHandler).Methods("GET") - s.handleFunc(r2, "/v2/stats/store", s.GetStoreStatsHandler).Methods("GET") - s.handleFunc(r2, "/v2/speedTest", s.SpeedTestHandler).Methods("GET") + s.handleFunc(r2, "/v2/leader", s.GetLeaderHandler).Methods("GET", "HEAD") + s.handleFunc(r2, "/v2/machines", s.GetPeersHandler).Methods("GET", "HEAD") + s.handleFunc(r2, "/v2/peers", s.GetPeersHandler).Methods("GET", "HEAD") + s.handleFunc(r2, "/v2/stats/self", s.GetStatsHandler).Methods("GET", "HEAD") + s.handleFunc(r2, "/v2/stats/leader", s.GetLeaderStatsHandler).Methods("GET", "HEAD") + s.handleFunc(r2, "/v2/stats/store", s.GetStoreStatsHandler).Methods("GET", "HEAD") + s.handleFunc(r2, "/v2/speedTest", s.SpeedTestHandler).Methods("GET", "HEAD") } func (s *Server) installMod(r *mux.Router) { @@ -134,7 +134,7 @@ func (s *Server) installMod(r *mux.Router) { } func (s *Server) installDebug(r *mux.Router) { - s.handleFunc(r, "/debug/metrics", s.GetMetricsHandler).Methods("GET") + s.handleFunc(r, "/debug/metrics", s.GetMetricsHandler).Methods("GET", "HEAD") r.HandleFunc("/debug/pprof", pprof.Index) r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) r.HandleFunc("/debug/pprof/profile", pprof.Profile) @@ -142,9 +142,20 @@ func (s *Server) installDebug(r *mux.Router) { r.HandleFunc("/debug/pprof/{name}", pprof.Index) } +type HEADResponseWriter struct { + http.ResponseWriter +} + +func (w *HEADResponseWriter) Write([]byte) (int, error) { + return 0, nil +} + // Adds a v1 server handler to the router. func (s *Server) handleFuncV1(r *mux.Router, path string, f func(http.ResponseWriter, *http.Request, v1.Server) error) *mux.Route { return s.handleFunc(r, path, func(w http.ResponseWriter, req *http.Request) error { + if req.Method == "HEAD" { + w = &HEADResponseWriter{w} + } return f(w, req, s) }) } @@ -152,6 +163,9 @@ func (s *Server) handleFuncV1(r *mux.Router, path string, f func(http.ResponseWr // Adds a v2 server handler to the router. func (s *Server) handleFuncV2(r *mux.Router, path string, f func(http.ResponseWriter, *http.Request, v2.Server) error) *mux.Route { return s.handleFunc(r, path, func(w http.ResponseWriter, req *http.Request) error { + if req.Method == "HEAD" { + w = &HEADResponseWriter{w} + } return f(w, req, s) }) } @@ -161,6 +175,10 @@ func (s *Server) handleFunc(r *mux.Router, path string, f func(http.ResponseWrit // Wrap the standard HandleFunc interface to pass in the server reference. return r.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { + if req.Method == "HEAD" { + w = &HEADResponseWriter{w} + } + // Log request. log.Debugf("[recv] %s %s %s [%s]", req.Method, s.URL(), req.URL.Path, req.RemoteAddr) diff --git a/server/v1/tests/get_handler_test.go b/server/v1/tests/get_handler_test.go index 7365519ff..8e9c52ee5 100644 --- a/server/v1/tests/get_handler_test.go +++ b/server/v1/tests/get_handler_test.go @@ -176,3 +176,27 @@ func TestV1WatchKeyWithIndex(t *testing.T) { assert.Equal(t, body["index"], 3, "") }) } + +// Ensures that HEAD works. +// +// $ curl -I localhost:4001/v1/keys/foo/bar -> fail +// $ curl -X PUT localhost:4001/v1/keys/foo/bar -d value=XXX +// $ curl -I localhost:4001/v1/keys/foo/bar +// +func TestV1HeadKey(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + fullURL := fmt.Sprintf("%s%s", s.URL(), "/v1/keys/foo/bar") + resp, _ := tests.Get(fullURL) + assert.Equal(t, resp.StatusCode, http.StatusNotFound) + assert.Equal(t, resp.ContentLength, -1) + + resp, _ = tests.PutForm(fullURL, v) + tests.ReadBody(resp) + + resp, _ = tests.Get(fullURL) + assert.Equal(t, resp.StatusCode, http.StatusOK) + assert.Equal(t, resp.ContentLength, -1) + }) +} diff --git a/server/v2/tests/get_handler_test.go b/server/v2/tests/get_handler_test.go index 55dc772c3..1c7825d07 100644 --- a/server/v2/tests/get_handler_test.go +++ b/server/v2/tests/get_handler_test.go @@ -229,3 +229,27 @@ func TestV2WatchKeyInDir(t *testing.T) { assert.Equal(t, node["key"], "/keyindir", "") }) } + +// Ensures that HEAD could work. +// +// $ curl -I localhost:4001/v2/keys/foo/bar -> fail +// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX +// $ curl -I localhost:4001/v2/keys/foo/bar +// +func TestV2HeadKey(t *testing.T) { + tests.RunServer(func(s *server.Server) { + v := url.Values{} + v.Set("value", "XXX") + fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar") + resp, _ := tests.Head(fullURL) + assert.Equal(t, resp.StatusCode, http.StatusNotFound) + assert.Equal(t, resp.ContentLength, -1) + + resp, _ = tests.PutForm(fullURL, v) + tests.ReadBody(resp) + + resp, _ = tests.Head(fullURL) + assert.Equal(t, resp.StatusCode, http.StatusOK) + assert.Equal(t, resp.ContentLength, -1) + }) +} diff --git a/tests/http_utils.go b/tests/http_utils.go index 575364ed3..408ded7bc 100644 --- a/tests/http_utils.go +++ b/tests/http_utils.go @@ -35,6 +35,10 @@ func ReadBodyJSON(resp *http.Response) map[string]interface{} { return m } +func Head(url string) (*http.Response, error) { + return send("HEAD", url, "application/json", nil) +} + func Get(url string) (*http.Response, error) { return send("GET", url, "application/json", nil) }