Skip to content

Commit

Permalink
Merge pull request #123 from ant0ine/v3-alpha
Browse files Browse the repository at this point in the history
Implementation branch for the v3
  • Loading branch information
ant0ine committed Feb 3, 2015
2 parents 8eb955c + 8e716f2 commit 7d2bcc8
Show file tree
Hide file tree
Showing 31 changed files with 977 additions and 963 deletions.
840 changes: 302 additions & 538 deletions README.md

Large diffs are not rendered by default.

27 changes: 18 additions & 9 deletions rest/access_log_apache.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,30 @@ const (
CommonLogFormat = "%h %l %u %t \"%r\" %s %b"

// NCSA extended/combined log format.
CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\""
CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""

// Default format, colored output and response time, convenient for development.
DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
)

// accessLogApacheMiddleware produces the access log following a format inpired
// by Apache mod_log_config. It depends on the timer, recorder and auth middlewares.
type accessLogApacheMiddleware struct {
Logger *log.Logger
Format AccessLogFormat
// AccessLogApacheMiddleware produces the access log following a format inspired by Apache
// mod_log_config. It depends on TimerMiddleware and RecorderMiddleware that must be in the wrapped
// middlewares. It also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
type AccessLogApacheMiddleware struct {

// Logger points to the logger object used by this middleware, it defaults to
// log.New(os.Stderr, "", 0).
Logger *log.Logger

// Format defines the format of the access log record. See AccessLogFormat for the details.
// It defaults to DefaultLogFormat.
Format AccessLogFormat

textTemplate *template.Template
}

func (mw *accessLogApacheMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
// MiddlewareFunc makes AccessLogApacheMiddleware implement the Middleware interface.
func (mw *AccessLogApacheMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {

// set the default Logger
if mw.Logger == nil {
Expand Down Expand Up @@ -104,7 +113,7 @@ var apacheAdapter = strings.NewReplacer(
)

// Convert the Apache access log format into a text/template
func (mw *accessLogApacheMiddleware) convertFormat() {
func (mw *AccessLogApacheMiddleware) convertFormat() {

tmplText := apacheAdapter.Replace(string(mw.Format))

Expand Down Expand Up @@ -142,7 +151,7 @@ func (mw *accessLogApacheMiddleware) convertFormat() {
}

// Execute the text template with the data derived from the request, and return a string.
func (mw *accessLogApacheMiddleware) executeTextTemplate(util *accessLogUtil) string {
func (mw *AccessLogApacheMiddleware) executeTextTemplate(util *accessLogUtil) string {
buf := bytes.NewBufferString("")
err := mw.textTemplate.Execute(buf, util)
if err != nil {
Expand Down
37 changes: 17 additions & 20 deletions rest/access_log_apache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,41 @@ package rest

import (
"bytes"
"github.com/ant0ine/go-json-rest/rest/test"
"log"
"net/http"
"net/http/httptest"
"regexp"
"testing"
)

func TestAccessLogApacheMiddleware(t *testing.T) {

// the middlewares
recorder := &recorderMiddleware{}
timer := &timerMiddleware{}
api := NewApi()

// the middlewares stack
buffer := bytes.NewBufferString("")
logger := &accessLogApacheMiddleware{
api.Use(&AccessLogApacheMiddleware{
Logger: log.New(buffer, "", 0),
Format: CommonLogFormat,
textTemplate: nil,
}
})
api.Use(&TimerMiddleware{})
api.Use(&RecorderMiddleware{})

// the app
app := func(w ResponseWriter, r *Request) {
// a simple app
api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
w.WriteJson(map[string]string{"Id": "123"})
}
}))

// wrap all
handlerFunc := adapterFunc(WrapMiddlewares([]Middleware{logger, timer, recorder}, app))

// fake request
r, _ := http.NewRequest("GET", "http://localhost/", nil)
r.RemoteAddr = "127.0.0.1:1234"

// fake writer
w := httptest.NewRecorder()
handler := api.MakeHandler()

handlerFunc(w, r)
req := test.MakeSimpleRequest("GET", "http://localhost/", nil)
req.RemoteAddr = "127.0.0.1:1234"
recorded := test.RunRequest(t, handler, req)
recorded.CodeIs(200)
recorded.ContentTypeIsJson()

// eg: '127.0.0.1 - - 29/Nov/2014:22:28:34 +0000 "GET / HTTP/1.1" 200 12'
// log tests, eg: '127.0.0.1 - - 29/Nov/2014:22:28:34 +0000 "GET / HTTP/1.1" 200 12'
apacheCommon := regexp.MustCompile(`127.0.0.1 - - \d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2} [+\-]\d{4}\ "GET / HTTP/1.1" 200 12`)

if !apacheCommon.Match(buffer.Bytes()) {
Expand Down
17 changes: 11 additions & 6 deletions rest/access_log_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import (
"time"
)

// accessLogJsonMiddleware produces the access log with records written as JSON.
// It depends on the timer, recorder and auth middlewares.
type accessLogJsonMiddleware struct {
// AccessLogJsonMiddleware produces the access log with records written as JSON. This middleware
// depends on TimerMiddleware and RecorderMiddleware that must be in the wrapped middlewares. It
// also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
type AccessLogJsonMiddleware struct {

// Logger points to the logger object used by this middleware, it defaults to
// log.New(os.Stderr, "", 0).
Logger *log.Logger
}

func (mw *accessLogJsonMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
// MiddlewareFunc makes AccessLogJsonMiddleware implement the Middleware interface.
func (mw *AccessLogJsonMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {

// set the default Logger
if mw.Logger == nil {
Expand All @@ -29,8 +34,8 @@ func (mw *accessLogJsonMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {
}
}

// When EnableLogAsJson is true, this object is dumped as JSON in the Logger.
// (Public for documentation only, no public method uses it).
// AccessLogJsonRecord is the data structure used by AccessLogJsonMiddleware to create the JSON
// records. (Public for documentation only, no public method uses it)
type AccessLogJsonRecord struct {
Timestamp *time.Time
StatusCode int
Expand Down
36 changes: 17 additions & 19 deletions rest/access_log_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,38 @@ package rest
import (
"bytes"
"encoding/json"
"github.com/ant0ine/go-json-rest/rest/test"
"log"
"net/http"
"net/http/httptest"
"testing"
)

func TestAccessLogJsonMiddleware(t *testing.T) {

// the middlewares
recorder := &recorderMiddleware{}
timer := &timerMiddleware{}
api := NewApi()

// the middlewares stack
buffer := bytes.NewBufferString("")
logger := &accessLogJsonMiddleware{
api.Use(&AccessLogJsonMiddleware{
Logger: log.New(buffer, "", 0),
}
})
api.Use(&TimerMiddleware{})
api.Use(&RecorderMiddleware{})

// the app
app := func(w ResponseWriter, r *Request) {
// a simple app
api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
w.WriteJson(map[string]string{"Id": "123"})
}
}))

// wrap all
handlerFunc := adapterFunc(WrapMiddlewares([]Middleware{logger, timer, recorder}, app))

// fake request
r, _ := http.NewRequest("GET", "http://localhost/", nil)
r.RemoteAddr = "127.0.0.1:1234"

// fake writer
w := httptest.NewRecorder()
handler := api.MakeHandler()

handlerFunc(w, r)
req := test.MakeSimpleRequest("GET", "http://localhost/", nil)
req.RemoteAddr = "127.0.0.1:1234"
recorded := test.RunRequest(t, handler, req)
recorded.CodeIs(200)
recorded.ContentTypeIsJson()

// log tests
decoded := &AccessLogJsonRecord{}
err := json.Unmarshal(buffer.Bytes(), decoded)
if err != nil {
Expand Down
83 changes: 83 additions & 0 deletions rest/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package rest

import (
"net/http"
)

// Api defines a stack of Middlewares and an App.
type Api struct {
stack []Middleware
app App
}

// NewApi makes a new Api object. The Middleware stack is empty, and the App is nil.
func NewApi() *Api {
return &Api{
stack: []Middleware{},
app: nil,
}
}

// Use pushes one or multiple middlewares to the stack for middlewares
// maintained in the Api object.
func (api *Api) Use(middlewares ...Middleware) {
api.stack = append(api.stack, middlewares...)
}

// SetApp sets the App in the Api object.
func (api *Api) SetApp(app App) {
api.app = app
}

// MakeHandler wraps all the Middlewares of the stack and the App together, and returns an
// http.Handler ready to be used. If the Middleware stack is empty the App is used directly. If the
// App is nil, a HandlerFunc that does nothing is used instead.
func (api *Api) MakeHandler() http.Handler {
var appFunc HandlerFunc
if api.app != nil {
appFunc = api.app.AppFunc()
} else {
appFunc = func(w ResponseWriter, r *Request) {}
}
return http.HandlerFunc(
adapterFunc(
WrapMiddlewares(api.stack, appFunc),
),
)
}

// Defines a stack of middlewares convenient for development. Among other things:
// console friendly logging, JSON indentation, error stack strace in the response.
var DefaultDevStack = []Middleware{
&AccessLogApacheMiddleware{},
&TimerMiddleware{},
&RecorderMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{
EnableResponseStackTrace: true,
},
&JsonIndentMiddleware{},
&ContentTypeCheckerMiddleware{},
}

// Defines a stack of middlewares convenient for production. Among other things:
// Apache CombinedLogFormat logging, gzip compression.
var DefaultProdStack = []Middleware{
&AccessLogApacheMiddleware{
Format: CombinedLogFormat,
},
&TimerMiddleware{},
&RecorderMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{},
&GzipMiddleware{},
&ContentTypeCheckerMiddleware{},
}

// Defines a stack of middlewares that should be common to most of the middleware stacks.
var DefaultCommonStack = []Middleware{
&TimerMiddleware{},
&RecorderMiddleware{},
&PoweredByMiddleware{},
&RecoverMiddleware{},
}
97 changes: 97 additions & 0 deletions rest/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package rest

import (
"github.com/ant0ine/go-json-rest/rest/test"
"testing"
)

func TestApiNoAppNoMiddleware(t *testing.T) {

api := NewApi()
if api == nil {
t.Fatal("Api object must be instantiated")
}

handler := api.MakeHandler()
if handler == nil {
t.Fatal("the http.Handler must be have been create")
}

recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil))
recorded.CodeIs(200)
}

func TestApiSimpleAppNoMiddleware(t *testing.T) {

api := NewApi()
api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
w.WriteJson(map[string]string{"Id": "123"})
}))

handler := api.MakeHandler()
if handler == nil {
t.Fatal("the http.Handler must be have been create")
}

recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil))
recorded.CodeIs(200)
recorded.ContentTypeIsJson()
recorded.BodyIs(`{"Id":"123"}`)
}

func TestDevStack(t *testing.T) {

api := NewApi()
api.Use(DefaultDevStack...)
api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
w.WriteJson(map[string]string{"Id": "123"})
}))

handler := api.MakeHandler()
if handler == nil {
t.Fatal("the http.Handler must be have been create")
}

recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil))
recorded.CodeIs(200)
recorded.ContentTypeIsJson()
recorded.BodyIs("{\n \"Id\": \"123\"\n}")
}

func TestProdStack(t *testing.T) {

api := NewApi()
api.Use(DefaultProdStack...)
api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
w.WriteJson(map[string]string{"Id": "123"})
}))

handler := api.MakeHandler()
if handler == nil {
t.Fatal("the http.Handler must be have been create")
}

recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil))
recorded.CodeIs(200)
recorded.ContentTypeIsJson()
recorded.ContentEncodingIsGzip()
}

func TestCommonStack(t *testing.T) {

api := NewApi()
api.Use(DefaultCommonStack...)
api.SetApp(AppSimple(func(w ResponseWriter, r *Request) {
w.WriteJson(map[string]string{"Id": "123"})
}))

handler := api.MakeHandler()
if handler == nil {
t.Fatal("the http.Handler must be have been create")
}

recorded := test.RunRequest(t, handler, test.MakeSimpleRequest("GET", "http://localhost/", nil))
recorded.CodeIs(200)
recorded.ContentTypeIsJson()
recorded.BodyIs(`{"Id":"123"}`)
}
Loading

0 comments on commit 7d2bcc8

Please sign in to comment.