Skip to content

Commit

Permalink
#37 error info based on content-type, code improvements, test update
Browse files Browse the repository at this point in the history
  • Loading branch information
jeevatkm committed Jul 22, 2017
1 parent 26b82ac commit 2ea70f0
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 91 deletions.
63 changes: 12 additions & 51 deletions engine.go
Expand Up @@ -37,7 +37,6 @@ var (
minifier MinifierFunc
noGzipStatusCodes = []int{http.StatusNotModified, http.StatusNoContent}
ctxPool *sync.Pool
reqPool *sync.Pool
)

type (
Expand Down Expand Up @@ -122,16 +121,7 @@ func (e *engine) handleRecovery(ctx *Context) {
st.Print(buf)
log.Error(buf.String())

ctx.Reply().InternalServerError()
e.negotiateContentType(ctx)
if ahttp.ContentTypeJSON.IsEqual(ctx.Reply().ContType) {
ctx.Reply().JSON(Data{"code": "500", "message": "Internal Server Error"})
} else if ahttp.ContentTypeXML.IsEqual(ctx.Reply().ContType) {
ctx.Reply().XML(Data{"code": "500", "message": "Internal Server Error"})
} else {
ctx.Reply().Text("500 Internal Server Error")
}

writeErrorInfo(ctx, http.StatusInternalServerError, "Internal Server Error")
e.writeReply(ctx)
}
}
Expand All @@ -149,10 +139,10 @@ func (e *engine) setRequestID(ctx *Context) {

// prepareContext method gets controller, request from pool, set the targeted
// controller, parses the request and returns the controller.
func (e *engine) prepareContext(w http.ResponseWriter, req *http.Request) *Context {
ctx, r := acquireContext(), acquireRequest()
ctx.Req = ahttp.ParseRequest(req, r)
ctx.Res = ahttp.GetResponseWriter(w)
func (e *engine) prepareContext(w http.ResponseWriter, r *http.Request) *Context {
ctx := acquireContext()
ctx.Req = ahttp.AcquireRequest(r)
ctx.Res = ahttp.AcquireResponseWriter(w)
ctx.reply = acquireReply()
ctx.subject = security.AcquireSubject()
return ctx
Expand All @@ -175,7 +165,7 @@ func (e *engine) prepareContext(w http.ResponseWriter, req *http.Request) *Conte
func (e *engine) handleRoute(ctx *Context) flowResult {
domain := AppRouter().FindDomain(ctx.Req)
if domain == nil {
ctx.Reply().NotFound().Text("404 Not Found")
writeErrorInfo(ctx, http.StatusNotFound, "Not Found")
e.writeReply(ctx)
return flowStop
}
Expand Down Expand Up @@ -271,7 +261,11 @@ func (e *engine) writeReply(ctx *Context) {
}

// ContentType
e.negotiateContentType(ctx)
if !ctx.Reply().IsContentTypeSet() {
if ct := identifyContentType(ctx); ct != nil {
ctx.Reply().ContentType(ct.String())
}
}

// resolving view template
e.resolveView(ctx)
Expand Down Expand Up @@ -314,19 +308,6 @@ func (e *engine) writeReply(ctx *Context) {
}
}

// negotiateContentType method tries to identify if reply.ContType is empty.
// Not necessarily it will set one.
func (e *engine) negotiateContentType(ctx *Context) {
if !ctx.Reply().IsContentTypeSet() {
if !ess.IsStrEmpty(ctx.Req.AcceptContentType.Mime) &&
ctx.Req.AcceptContentType.Mime != "*/*" { // based on 'Accept' Header
ctx.Reply().ContentType(ctx.Req.AcceptContentType.String())
} else if ct := defaultContentType(); ct != nil { // as per 'render.default' in aah.conf
ctx.Reply().ContentType(ct.String())
}
}
}

// wrapGzipWriter method writes respective header for gzip and wraps write into
// gzip writer.
func (e *engine) wrapGzipWriter(ctx *Context) {
Expand Down Expand Up @@ -391,26 +372,8 @@ func acquireContext() *Context {
return ctxPool.Get().(*Context)
}

func acquireRequest() *ahttp.Request {
return reqPool.Get().(*ahttp.Request)
}

func releaseContext(ctx *Context) {
// Close the writer and Put back to pool
if ctx.Res != nil {
if _, ok := ctx.Res.(*ahttp.GzipResponse); ok {
ahttp.PutGzipResponseWiriter(ctx.Res)
} else {
ahttp.PutResponseWriter(ctx.Res)
}
}

// clear and put `ahttp.Request` back to pool
if ctx.Req != nil {
ctx.Req.Reset()
reqPool.Put(ctx.Req)
}

ahttp.ReleaseResponseWriter(ctx.Res)
releaseReply(ctx.reply)
security.ReleaseSubject(ctx.subject)

Expand All @@ -425,6 +388,4 @@ func init() {
values: make(map[string]interface{}),
}
}}

reqPool = &sync.Pool{New: func() interface{} { return &ahttp.Request{} }}
}
4 changes: 1 addition & 3 deletions engine_test.go
Expand Up @@ -78,11 +78,9 @@ func TestEngineNew(t *testing.T) {
assert.True(t, e.isRequestIDEnabled)
assert.True(t, e.isGzipEnabled)

req := acquireRequest()
ctx := acquireContext()
ctx.Req = req
ctx.Req = &ahttp.Request{}
assert.NotNil(t, ctx)
assert.NotNil(t, req)
assert.NotNil(t, ctx.Req)
releaseContext(ctx)

Expand Down
40 changes: 40 additions & 0 deletions error.go
@@ -0,0 +1,40 @@
// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm)
// go-aah/aah source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package aah

import (
"strings"

"aahframework.org/ahttp.v0"
"aahframework.org/essentials.v0"
)

// writeError method writes the server error response based content type.
// It's handy internal method.
func writeErrorInfo(ctx *Context, code int, msg string) {
ct := ctx.Reply().ContType
if ess.IsStrEmpty(ct) {
if ict := identifyContentType(ctx); ict != nil {
ct = ict.Mime
}
} else if idx := strings.IndexByte(ct, ';'); idx > 0 {
ct = ct[:idx]
}

switch ct {
case ahttp.ContentTypeJSON.Mime, ahttp.ContentTypeJSONText.Mime:
ctx.Reply().Status(code).JSON(Data{
"code": code,
"message": msg,
})
case ahttp.ContentTypeXML.Mime, ahttp.ContentTypeXMLText.Mime:
ctx.Reply().Status(code).XML(Data{
"code": code,
"message": msg,
})
default:
ctx.Reply().Status(code).Text("%d %s", code, msg)
}
}
35 changes: 35 additions & 0 deletions error_test.go
@@ -0,0 +1,35 @@
// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm)
// go-aah/aah source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package aah

import (
"testing"

"aahframework.org/test.v0/assert"
)

func TestErrorWriteInfo(t *testing.T) {
ctx1 := &Context{reply: acquireReply()}
ctx1.Reply().ContentType("application/json")
writeErrorInfo(ctx1, 400, "Bad Request")

assert.NotNil(t, ctx1.Reply().Rdr)
jsonr := ctx1.Reply().Rdr.(*JSON)
assert.NotNil(t, jsonr)
assert.NotNil(t, jsonr.Data)
assert.Equal(t, 400, jsonr.Data.(Data)["code"])
assert.Equal(t, "Bad Request", jsonr.Data.(Data)["message"])

ctx2 := &Context{reply: acquireReply()}
ctx2.Reply().ContentType("application/xml")
writeErrorInfo(ctx2, 500, "Internal Server Error")

assert.NotNil(t, ctx2.Reply().Rdr)
xmlr := ctx2.Reply().Rdr.(*XML)
assert.NotNil(t, xmlr)
assert.NotNil(t, xmlr.Data)
assert.Equal(t, 500, xmlr.Data.(Data)["code"])
assert.Equal(t, "Internal Server Error", xmlr.Data.(Data)["message"])
}
36 changes: 33 additions & 3 deletions render.go
Expand Up @@ -13,16 +13,18 @@ import (
"io"
"os"
"path/filepath"
"strings"
"sync"

"aahframework.org/essentials.v0"
"aahframework.org/log.v0-unstable"
)

var (
rdrHTMLPool = &sync.Pool{New: func() interface{} { return &HTML{} }}
rdrJSONPool = &sync.Pool{New: func() interface{} { return &JSON{} }}
rdrXMLPool = &sync.Pool{New: func() interface{} { return &XML{} }}
xmlHeaderBytes = []byte(xml.Header)
rdrHTMLPool = &sync.Pool{New: func() interface{} { return &HTML{} }}
rdrJSONPool = &sync.Pool{New: func() interface{} { return &JSON{} }}
rdrXMLPool = &sync.Pool{New: func() interface{} { return &XML{} }}
)

type (
Expand Down Expand Up @@ -149,6 +151,10 @@ func (x *XML) Render(w io.Writer) error {
return err
}

if _, err = w.Write(xmlHeaderBytes); err != nil {
return err
}

if _, err = w.Write(bytes); err != nil {
return err
}
Expand All @@ -160,6 +166,29 @@ func (x *XML) reset() {
x.Data = nil
}

// MarshalXML method is to marshal `aah.Data` into XML.
func (d Data) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
tokens := []xml.Token{start}
for k, v := range d {
token := xml.StartElement{Name: xml.Name{Local: strings.Title(k)}}
tokens = append(tokens, token,
xml.CharData(fmt.Sprintf("%v", v)),
xml.EndElement{Name: token.Name})
}

tokens = append(tokens, xml.EndElement{Name: start.Name})

var err error
for _, t := range tokens {
if err = e.EncodeToken(t); err != nil {
return err
}
}

// flush to ensure tokens are written
return e.Flush()
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// File and Reader Render methods
//___________________________________
Expand Down Expand Up @@ -233,6 +262,7 @@ func (e *engine) doRender(ctx *Context) {

if err := reply.Rdr.Render(reply.body); err != nil {
log.Error("Render response body error: ", err)
// TODO handle response based on content type
reply.InternalServerError()
reply.body.Reset()
reply.body.WriteString("500 Internal Server Error\n")
Expand Down
6 changes: 4 additions & 2 deletions render_test.go
Expand Up @@ -119,7 +119,8 @@ func TestRenderXML(t *testing.T) {
xml1 := XML{Data: data}
err := xml1.Render(buf)
assert.FailOnError(t, err, "")
assert.Equal(t, `<Sample>
assert.Equal(t, `<?xml version="1.0" encoding="UTF-8"?>
<Sample>
<Name>John</Name>
<Age>28</Age>
<Address>this is my street</Address>
Expand All @@ -131,7 +132,8 @@ func TestRenderXML(t *testing.T) {

err = xml1.Render(buf)
assert.FailOnError(t, err, "")
assert.Equal(t, `<Sample><Name>John</Name><Age>28</Age><Address>this is my street</Address></Sample>`,
assert.Equal(t, `<?xml version="1.0" encoding="UTF-8"?>
<Sample><Name>John</Name><Age>28</Age><Address>this is my street</Address></Sample>`,
buf.String())
}

Expand Down
23 changes: 21 additions & 2 deletions reply_test.go
Expand Up @@ -191,7 +191,8 @@ func TestReplyXML(t *testing.T) {

err := re1.Rdr.Render(buf)
assert.FailOnError(t, err, "")
assert.Equal(t, `<Sample>
assert.Equal(t, `<?xml version="1.0" encoding="UTF-8"?>
<Sample>
<Name>John</Name>
<Age>28</Age>
<Address>this is my street</Address>
Expand All @@ -203,8 +204,26 @@ func TestReplyXML(t *testing.T) {

err = re1.Rdr.Render(buf)
assert.FailOnError(t, err, "")
assert.Equal(t, `<Sample><Name>John</Name><Age>28</Age><Address>this is my street</Address></Sample>`,
assert.Equal(t, `<?xml version="1.0" encoding="UTF-8"?>
<Sample><Name>John</Name><Age>28</Age><Address>this is my street</Address></Sample>`,
buf.String())

buf.Reset()

data2 := Data{
"Name": "John",
"Age": 28,
"Address": "this is my street",
}

re1.Rdr.(*XML).reset()
re1.XML(data2)
assert.True(t, re1.IsContentTypeSet())

err = re1.Rdr.Render(buf)
assert.FailOnError(t, err, "")
assert.True(t, strings.HasPrefix(buf.String(), `<?xml version="1.0" encoding="UTF-8"?>`))
re1.Rdr.(*XML).reset()
}

func TestReplyReadfrom(t *testing.T) {
Expand Down

0 comments on commit 2ea70f0

Please sign in to comment.