From dc48259a6805c274af66cf1295ef9e87131002bd Mon Sep 17 00:00:00 2001 From: Jan Berktold Date: Mon, 2 Mar 2015 21:28:57 +0100 Subject: [PATCH 1/2] Implement part-string checking in ServeFormatted to confrom to RFC2616 --- routes.go | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/routes.go b/routes.go index 6870373..3c0133c 100644 --- a/routes.go +++ b/routes.go @@ -99,7 +99,7 @@ func (m *RouteMux) AddRoute(method string, pattern string, handler http.HandlerF if strings.HasPrefix(part, ":") { expr := "([^/]+)" //a user may choose to override the defult expression - // similar to expressjs: ‘/user/:id([0-9]+)’ + // similar to expressjs: ‘/user/:id([0-9]+)’ if index := strings.Index(part, "("); index != -1 { expr = part[index:] part = part[:index] @@ -138,13 +138,15 @@ func (m *RouteMux) Filter(filter http.HandlerFunc) { // FilterParam adds the middleware filter iff the REST URL parameter exists. func (m *RouteMux) FilterParam(param string, filter http.HandlerFunc) { - if !strings.HasPrefix(param,":") { - param = ":"+param + if !strings.HasPrefix(param, ":") { + param = ":" + param } m.Filter(func(w http.ResponseWriter, r *http.Request) { p := r.URL.Query().Get(param) - if len(p) > 0 { filter(w, r) } + if len(p) > 0 { + filter(w, r) + } }) } @@ -248,6 +250,14 @@ func (w *responseWriter) WriteHeader(code int) { // code that serializes resources and writes to the // http response. +type contentSeralizer func(w http.ResponseWriter, v interface{}) + +var contentHandler = map[string]contentSeralizer{ + applicationJson: ServeJson, + applicationXml: ServeXml, + textXml: ServeXml, +} + // ServeJson replies to the request with a JSON // representation of resource v. func ServeJson(w http.ResponseWriter, v interface{}) { @@ -304,14 +314,10 @@ func ReadXml(r *http.Request, v interface{}) error { // Accept header. func ServeFormatted(w http.ResponseWriter, r *http.Request, v interface{}) { accept := r.Header.Get("Accept") - switch accept { - case applicationJson: - ServeJson(w, v) - case applicationXml, textXml: - ServeXml(w, v) - default: - ServeJson(w, v) + for header, handler := range contentHandler { + if strings.Contains(accept, header) { + handler(w, v) + return + } } - - return } From 1640a5d380b1611e5b7598742fb96549b28c73f3 Mon Sep 17 00:00:00 2001 From: Jan Berktold Date: Mon, 2 Mar 2015 21:54:06 +0100 Subject: [PATCH 2/2] Write tests for ServeFormatted --- routes_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/routes_test.go b/routes_test.go index 5af10c1..ab572ae 100644 --- a/routes_test.go +++ b/routes_test.go @@ -32,6 +32,51 @@ var FilterId = func(w http.ResponseWriter, r *http.Request) { } } +// Test data used for ServeFormatted +type testData struct { + SomeField string + Number int + Test float64 +} + +// contentTest describes a test for ServerFormatted +type contentTest struct { + Header string + Expected string +} + +// The correct formatted data for use by TestContentFormatted +var dataJSON = "{\n \"SomeField\": \"Hi\",\n \"Number\": 20,\n \"Test\": 2.5\n}" +var dataXML = "Hi202.5" + +// Various test cases for TestContentFormatted +var contentTester = []contentTest{ + contentTest{ + "", + dataJSON, + }, + contentTest{ + "application/json", + dataJSON, + }, + contentTest{ + "application/xml", + dataXML, + }, + contentTest{ + "text/xml", + dataXML, + }, + contentTest{ + "text/plain; q=0.5, text/html,\ntext/xml; q=0.8, text/x-c", + dataXML, + }, + contentTest{ + "text/html;\ntext/json;", + dataJSON, + }, +} + // TestAuthOk tests that an Auth handler will append the // username and password to to the request URL, and will // continue processing the request by invoking the handler. @@ -148,6 +193,28 @@ func TestFilterParam(t *testing.T) { } +// TestContentFormatted tests the basic Accept-Header content matching. +func TestContentFormatted(t *testing.T) { + recorder := httptest.NewRecorder() + response, _ := http.NewRequest("GET", "localhost/does/not/matter", nil) + + data := testData{ + SomeField: "Hi", + Number: 20, + Test: 2.5, + } + + for _, test := range contentTester { + response.Header.Set("Accept", test.Header) + ServeFormatted(recorder, response, data) + if recorder.Body.String() != test.Expected { + t.Fatalf("Sent %q header, expected %q. Got %q", test.Header, test.Expected, recorder.Body.String()) + } + recorder.Body.Reset() + } + +} + // Benchmark_RoutedHandler runs a benchmark against // the RouteMux using the default settings. func Benchmark_RoutedHandler(b *testing.B) {