Skip to content

Commit

Permalink
Merge pull request #31 from tarent/bugfix/1463-enabled-head-requests
Browse files Browse the repository at this point in the history
Enabled HEAD requests + refactoring
  • Loading branch information
Dennis Berthold authored Nov 22, 2016
2 parents d4e78f0 + 13f46bb commit 475d8ec
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 27 deletions.
119 changes: 93 additions & 26 deletions composition/composition_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,32 +44,25 @@ func (agg *CompositionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

fetcher := agg.contentFetcherFactory(r)

if fetcher.Empty() {
w.WriteHeader(500)
w.Write([]byte("Internal server error"))
logging.Application(r.Header).Error("No fetchers available for composition, throwing error 500")
if agg.handleEmptyFetcher(fetcher, w, r) {
return
}

// fetch all contents
results := fetcher.WaitForResults()

// Allow HEAD requests and disable composition of body fragments
if agg.handleHeadRequests(results, w, r) {
return
}

mergeContext := agg.contentMergerFactory(fetcher.MetaJSON())

for _, res := range results {
if res.Err == nil && res.Content != nil {

if res.Content.HttpStatusCode() >= 300 && res.Content.HttpStatusCode() <= 308 {
copyHeaders(res.Content.HttpHeader(), w.Header(), ForwardResponseHeaders)
w.WriteHeader(res.Content.HttpStatusCode())
return
}

if res.Content.Reader() != nil {
copyHeaders(res.Content.HttpHeader(), w.Header(), ForwardResponseHeaders)
w.WriteHeader(res.Content.HttpStatusCode())
io.Copy(w, res.Content.Reader())
res.Content.Reader().Close()
// Handle responses with 30x status code or with response bodies
if agg.handleNonMergeableResponses(res, w, r) {
return
}

Expand All @@ -83,31 +76,105 @@ func (agg *CompositionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
}
}

status := 200
// Take status code and headers from first fetch definition
status := agg.extractStatusCode(results, w, r)

agg.copyHeadersIfNeeded(results, w, r)

// Overwrite Content-Type to ensure, that the encoding is correct
w.Header().Set("Content-Type", "text/html; charset=utf-8")

html, err := agg.processHtml(mergeContext, w, r)
// Return if an error occured within the html aggregation
if err {
return
}

w.Header().Set("Content-Length", strconv.Itoa(len(html)))
w.WriteHeader(status)
w.Write(html)
}

func (agg *CompositionHandler) handleNonMergeableResponses(result *FetchResult, w http.ResponseWriter, r *http.Request) bool {

if agg.handle30xResponses(result, w, r) {
// Return if it's a forwarded status code
return true
}

if agg.handleStreamResponses(result, w, r) {
// Return if it's a response with body
return true
}

return false
}

func (agg *CompositionHandler) extractStatusCode(results []*FetchResult, w http.ResponseWriter, r *http.Request) (statusCode int) {
if len(results) > 0 {
copyHeaders(results[0].Content.HttpHeader(), w.Header(), ForwardResponseHeaders)
if results[0].Content.HttpStatusCode() != 0 {
status = results[0].Content.HttpStatusCode()
return results[0].Content.HttpStatusCode()
}
}
return 200
}

// Overwrite Content-Type to ensure, that the encoding is correct
w.Header().Set("Content-Type", "text/html; charset=utf-8")
func (agg *CompositionHandler) copyHeadersIfNeeded(results []*FetchResult, w http.ResponseWriter, r *http.Request) {
// Take status code and headers from first fetch definition
if len(results) > 0 {
copyHeaders(results[0].Content.HttpHeader(), w.Header(), ForwardResponseHeaders)
}
}

func (agg *CompositionHandler) processHtml(mergeContext ContentMerger, w http.ResponseWriter, r *http.Request) ([]byte, bool) {
html, err := mergeContext.GetHtml()
if err != nil {
if agg.cache != nil {
agg.cache.PurgeEntries(mergeContext.GetHashes())
}
logging.Application(r.Header).Error(err.Error())
http.Error(w, "Internal Server Error: "+err.Error(), 500)
return
return nil, true
}
return html, false
}

w.Header().Set("Content-Length", strconv.Itoa(len(html)))
w.WriteHeader(status)
w.Write(html)
func (agg *CompositionHandler) handleHeadRequests(results []*FetchResult, w http.ResponseWriter, r *http.Request) bool {
if r.Method == "HEAD" && len(results) > 0 {
copyHeaders(results[0].Content.HttpHeader(), w.Header(), ForwardResponseHeaders)
w.WriteHeader(results[0].Content.HttpStatusCode())
return true
}
return false
}

func (agg *CompositionHandler) handleEmptyFetcher(fetcher FetchResultSupplier, w http.ResponseWriter, r *http.Request) bool {
if fetcher.Empty() {
w.WriteHeader(500)
w.Write([]byte("Internal server error"))
logging.Application(r.Header).Error("No fetchers available for composition, throwing error 500")
return true
}
return false
}

func (agg *CompositionHandler) handle30xResponses(result *FetchResult, w http.ResponseWriter, r *http.Request) bool {
if result.Content.HttpStatusCode() >= 300 && result.Content.HttpStatusCode() <= 308 {
copyHeaders(result.Content.HttpHeader(), w.Header(), ForwardResponseHeaders)
w.WriteHeader(result.Content.HttpStatusCode())
return true
}
return false
}

func (agg *CompositionHandler) handleStreamResponses(result *FetchResult, w http.ResponseWriter, r *http.Request) bool {
if result.Content.Reader() != nil {
copyHeaders(result.Content.HttpHeader(), w.Header(), ForwardResponseHeaders)
w.WriteHeader(result.Content.HttpStatusCode())
io.Copy(w, result.Content.Reader())
result.Content.Reader().Close()
return true
}
return false
}

func LogFetchResultLoadingError(res *FetchResult, w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -150,7 +217,7 @@ func getHostFromRequest(r *http.Request) string {

func hasPrioritySetting(results []*FetchResult) bool {
for _, res := range results {
if(res.Def.Priority > 0){
if res.Def.Priority > 0 {
return true
}
}
Expand Down
87 changes: 86 additions & 1 deletion composition/composition_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func Test_CompositionHandler_PositiveCaseWithCache(t *testing.T) {
"": StringFragment("Hello World\n"),
},
},
Hash: "hashString",
Hash: "hashString",
},
}
}
Expand Down Expand Up @@ -127,6 +127,91 @@ func Test_CompositionHandler_CorrectHeaderAndStatusCodeReturned(t *testing.T) {
a.Contains(resp.Header()["Set-Cookie"], "cookie-content 2")
}

func Test_CompositionHandler_HeadRequest_CorrectHeaderAndStatusCodeReturned(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
a := assert.New(t)

contentFetcherFactory := func(r *http.Request) FetchResultSupplier {
return MockFetchResultSupplier{
&FetchResult{
Def: NewFetchDefinition("/foo"),
Content: &MemoryContent{
httpHeader: http.Header{
"Transfer-Encoding": {"gzip"}, // removed
"Location": {"/look/somewhere"},
"Set-Cookie": {
"cookie-content 1",
"cookie-content 2",
},
},
httpStatusCode: 200,
},
},
&FetchResult{
Def: NewFetchDefinition("..."),
Content: &MemoryContent{},
},
}
}
ch := NewCompositionHandler(ContentFetcherFactory(contentFetcherFactory))

resp := httptest.NewRecorder()
r, _ := http.NewRequest("HEAD", "http://example.com", nil)
ch.ServeHTTP(resp, r)

a.Equal(200, resp.Code)
a.Equal(2, len(resp.Header()))
a.Equal("/look/somewhere", resp.Header().Get("Location"))
a.Equal("", resp.Header().Get("Transfer-Encoding"))
a.Contains(resp.Header()["Set-Cookie"], "cookie-content 1")
a.Contains(resp.Header()["Set-Cookie"], "cookie-content 2")
}

func Test_CompositionHandler_CorrectStatusCodeReturned(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
a := assert.New(t)

contentFetcherFactory := func(r *http.Request) FetchResultSupplier {
return MockFetchResultSupplier{
&FetchResult{
Def: NewFetchDefinition("/foo"),
Content: &MemoryContent{
body: map[string]Fragment{
"": StringFragment(""),
},
httpHeader: http.Header{
"Transfer-Encoding": {"gzip"}, // removed
"Location": {"/look/somewhere"},
"Set-Cookie": {
"cookie-content 1",
"cookie-content 2",
},
},
httpStatusCode: 200,
},
},
&FetchResult{
Def: NewFetchDefinition("..."),
Content: &MemoryContent{},
},
}
}
ch := NewCompositionHandler(ContentFetcherFactory(contentFetcherFactory))

resp := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "http://example.com", nil)
ch.ServeHTTP(resp, r)

a.Equal(200, resp.Code)
a.Equal(4, len(resp.Header()))
a.Equal("/look/somewhere", resp.Header().Get("Location"))
a.Equal("", resp.Header().Get("Transfer-Encoding"))
a.Contains(resp.Header()["Set-Cookie"], "cookie-content 1")
a.Contains(resp.Header()["Set-Cookie"], "cookie-content 2")
}

func Test_CompositionHandler_ReturnStream(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down

0 comments on commit 475d8ec

Please sign in to comment.