Browse Source

feat(server): make header-only requests work

Yicheng Qin 11 years ago
parent
commit
28f19dec60
4 changed files with 86 additions and 16 deletions
  1. 34 16
      server/server.go
  2. 24 0
      server/v1/tests/get_handler_test.go
  3. 24 0
      server/v2/tests/get_handler_test.go
  4. 4 0
      tests/http_utils.go

+ 34 - 16
server/server.go

@@ -100,33 +100,33 @@ func (s *Server) Store() store.Store {
 }
 }
 
 
 func (s *Server) installV1(r *mux.Router) {
 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.SetKeyHandler).Methods("POST", "PUT")
 	s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.DeleteKeyHandler).Methods("DELETE")
 	s.handleFuncV1(r, "/v1/keys/{key:.*}", v1.DeleteKeyHandler).Methods("DELETE")
 	s.handleFuncV1(r, "/v1/watch/{key:.*}", v1.WatchKeyHandler).Methods("GET", "POST")
 	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) {
 func (s *Server) installV2(r *mux.Router) {
 	r2 := mux.NewRouter()
 	r2 := mux.NewRouter()
 	r.PathPrefix("/v2").Handler(ehttp.NewLowerQueryParamsHandler(r2))
 	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.PostHandler).Methods("POST")
 	s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.PutHandler).Methods("PUT")
 	s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.PutHandler).Methods("PUT")
 	s.handleFuncV2(r2, "/v2/keys/{key:.*}", v2.DeleteHandler).Methods("DELETE")
 	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) {
 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) {
 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", pprof.Index)
 	r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
 	r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
 	r.HandleFunc("/debug/pprof/profile", pprof.Profile)
 	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)
 	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.
 // 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 {
 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 {
 	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)
 		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.
 // 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 {
 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 {
 	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)
 		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.
 	// Wrap the standard HandleFunc interface to pass in the server reference.
 	return r.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
 	return r.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
+		if req.Method == "HEAD" {
+			w = &HEADResponseWriter{w}
+		}
+
 		// Log request.
 		// Log request.
 		log.Debugf("[recv] %s %s %s [%s]", req.Method, s.URL(), req.URL.Path, req.RemoteAddr)
 		log.Debugf("[recv] %s %s %s [%s]", req.Method, s.URL(), req.URL.Path, req.RemoteAddr)
 
 

+ 24 - 0
server/v1/tests/get_handler_test.go

@@ -176,3 +176,27 @@ func TestV1WatchKeyWithIndex(t *testing.T) {
 		assert.Equal(t, body["index"], 3, "")
 		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)
+	})
+}

+ 24 - 0
server/v2/tests/get_handler_test.go

@@ -229,3 +229,27 @@ func TestV2WatchKeyInDir(t *testing.T) {
 		assert.Equal(t, node["key"], "/keyindir", "")
 		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)
+	})
+}

+ 4 - 0
tests/http_utils.go

@@ -35,6 +35,10 @@ func ReadBodyJSON(resp *http.Response) map[string]interface{} {
 	return m
 	return m
 }
 }
 
 
+func Head(url string) (*http.Response, error) {
+	return send("HEAD", url, "application/json", nil)
+}
+
 func Get(url string) (*http.Response, error) {
 func Get(url string) (*http.Response, error) {
 	return send("GET", url, "application/json", nil)
 	return send("GET", url, "application/json", nil)
 }
 }