Skip to content

Commit

Permalink
Merge pull request #17 from dinever/golf_ctx
Browse files Browse the repository at this point in the history
Improved router to avoid allocation
  • Loading branch information
dinever committed Apr 22, 2016
2 parents b4e6385 + b4a20a3 commit 7a06be0
Show file tree
Hide file tree
Showing 20 changed files with 619 additions and 204 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ before_install:
- go get golang.org/x/tools/cmd/cover

script:
- go vet ./...
- go test -v -covermode=count -coverprofile=coverage.out

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ package main

import "github.com/dinever/golf"

func mainHandler(ctx *Golf.Context) {
func mainHandler(ctx *golf.Context) {
ctx.Write("Hello World!")
}

func pageHandler(ctx *Golf.Context) {
func pageHandler(ctx *golf.Context) {
page, err := ctx.Param("page")
if err != nil {
ctx.Abort(500)
Expand All @@ -65,7 +65,7 @@ func pageHandler(ctx *Golf.Context) {
}

func main() {
app := Golf.New()
app := golf.New()
app.Get("/", mainHandler)
app.Get("/p/:page/", pageHandler)
app.Run(":9000")
Expand Down
75 changes: 53 additions & 22 deletions app.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package Golf
package golf

import (
"net/http"
"os"
"path"
"strings"
"sync"
)

// Application is an abstraction of a Golf application, can be used for
Expand All @@ -25,16 +26,20 @@ type Application struct {
SessionManager SessionManager

// NotFoundHandler handles requests when no route is matched.
NotFoundHandler Handler
NotFoundHandler HandlerFunc

// MiddlewareChain is the default middlewares that Golf uses.
MiddlewareChain *Chain

errorHandler map[int]ErrorHandlerType
pool sync.Pool

errorHandler map[int]ErrorHandlerFunc

// The default error handler, if the corresponding error code is not specified
// in the `errorHandler` map, this handler will be called.
DefaultErrorHandler ErrorHandlerType
DefaultErrorHandler ErrorHandlerFunc

handlerChain HandlerFunc
}

// New is used for creating a new Golf Application instance.
Expand All @@ -43,11 +48,14 @@ func New() *Application {
app.router = newRouter()
app.staticRouter = make(map[string][]string)
app.View = NewView()
app.Config = NewConfig(app)
app.Config = NewConfig()
// debug, _ := app.Config.GetBool("debug", false)
app.errorHandler = make(map[int]ErrorHandlerType)
app.MiddlewareChain = NewChain(defaultMiddlewares...)
app.errorHandler = make(map[int]ErrorHandlerFunc)
app.MiddlewareChain = NewChain()
app.DefaultErrorHandler = defaultErrorHandler
app.pool.New = func() interface{} {
return new(Context)
}
return app
}

Expand All @@ -67,12 +75,12 @@ func (app *Application) handler(ctx *Context) {
}
}

params, handler := app.router.match(ctx.Request.URL.Path, ctx.Request.Method)
if handler != nil {
handler, params, err := app.router.FindRoute(ctx.Request.Method, ctx.Request.URL.Path)
if err != nil {
app.handleError(ctx, 404)
} else {
ctx.Params = params
handler(ctx)
} else {
app.handleError(ctx, 404)
}
ctx.Send()
}
Expand All @@ -84,8 +92,16 @@ func staticHandler(ctx *Context, filePath string) {

// Basic entrance of an `http.ResponseWriter` and an `http.Request`.
func (app *Application) ServeHTTP(res http.ResponseWriter, req *http.Request) {
ctx := NewContext(req, res, app)
app.MiddlewareChain.Final(app.handler)(ctx)
if app.handlerChain == nil {
app.handlerChain = app.MiddlewareChain.Final(app.handler)
}
ctx := app.pool.Get().(*Context)
ctx.reset()
ctx.Request = req
ctx.Response = res
ctx.App = app
app.handlerChain(ctx)
app.pool.Put(ctx)
}

// Run the Golf Application.
Expand All @@ -111,27 +127,42 @@ func (app *Application) Static(url string, path string) {
}

// Get method is used for registering a Get method route
func (app *Application) Get(pattern string, handler Handler) {
app.router.get(pattern, handler)
func (app *Application) Get(pattern string, handler HandlerFunc) {
app.router.AddRoute("GET", pattern, handler)
}

// Post method is used for registering a Post method route
func (app *Application) Post(pattern string, handler Handler) {
app.router.post(pattern, handler)
func (app *Application) Post(pattern string, handler HandlerFunc) {
app.router.AddRoute("POST", pattern, handler)
}

// Put method is used for registering a Put method route
func (app *Application) Put(pattern string, handler Handler) {
app.router.put(pattern, handler)
func (app *Application) Put(pattern string, handler HandlerFunc) {
app.router.AddRoute("PUT", pattern, handler)
}

// Delete method is used for registering a Delete method route
func (app *Application) Delete(pattern string, handler Handler) {
app.router.delete(pattern, handler)
func (app *Application) Delete(pattern string, handler HandlerFunc) {
app.router.AddRoute("DELETE", pattern, handler)
}

// Patch method is used for registering a Patch method route
func (app *Application) Patch(pattern string, handler HandlerFunc) {
app.router.AddRoute("PATCH", pattern, handler)
}

// Options method is used for registering a Options method route
func (app *Application) Options(pattern string, handler HandlerFunc) {
app.router.AddRoute("OPTIONS", pattern, handler)
}

// Head method is used for registering a Head method route
func (app *Application) Head(pattern string, handler HandlerFunc) {
app.router.AddRoute("HEAD", pattern, handler)
}

// Error method is used for registering an handler for a specified HTTP error code.
func (app *Application) Error(statusCode int, handler ErrorHandlerType) {
func (app *Application) Error(statusCode int, handler ErrorHandlerFunc) {
app.errorHandler[statusCode] = handler
}

Expand Down
30 changes: 15 additions & 15 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package Golf
package golf

import (
"encoding/json"
Expand Down Expand Up @@ -38,61 +38,61 @@ type Config struct {
}

// NewConfig creates a new configuration instance.
func NewConfig(app *Application) *Config {
func NewConfig() *Config {
mapping := make(map[string]interface{})
return &Config{mapping}
}

// GetString fetches the string value by indicating the key.
// It returns a ValueTypeError if the value is not a sring.
func (config *Config) GetString(key string, defaultValue interface{}) (string, error) {
func (config *Config) GetString(key string, defaultValue string) (string, error) {
value, err := config.Get(key, defaultValue)
if err != nil {
return "", err
return defaultValue, err
}
if result, ok := value.(string); ok {
return result, nil
}
return "", &ValueTypeError{key: key, value: value, message: "Value is not a string."}
return defaultValue, &ValueTypeError{key: key, value: value, message: "Value is not a string."}
}

// GetInt fetches the int value by indicating the key.
// It returns a ValueTypeError if the value is not a sring.
func (config *Config) GetInt(key string, defaultValue interface{}) (int, error) {
func (config *Config) GetInt(key string, defaultValue int) (int, error) {
value, err := config.Get(key, defaultValue)
if err != nil {
return 0, err
return defaultValue, err
}
if result, ok := value.(int); ok {
return result, nil
}
return 0, &ValueTypeError{key: key, value: value, message: "Value is not an integer."}
return defaultValue, &ValueTypeError{key: key, value: value, message: "Value is not an integer."}
}

// GetBool fetches the bool value by indicating the key.
// It returns a ValueTypeError if the value is not a sring.
func (config *Config) GetBool(key string, defaultValue interface{}) (bool, error) {
func (config *Config) GetBool(key string, defaultValue bool) (bool, error) {
value, err := config.Get(key, defaultValue)
if err != nil {
return false, err
return defaultValue, err
}
if result, ok := value.(bool); ok {
return result, nil
}
return false, &ValueTypeError{key: key, value: value, message: "Value is not an bool."}
return defaultValue, &ValueTypeError{key: key, value: value, message: "Value is not an bool."}
}

// GetFloat fetches the float value by indicating the key.
// It returns a ValueTypeError if the value is not a sring.
func (config *Config) GetFloat(key string, defaultValue interface{}) (float64, error) {
func (config *Config) GetFloat(key string, defaultValue float64) (float64, error) {
value, err := config.Get(key, defaultValue)
if err != nil {
return 0, err
return defaultValue, err
}
if result, ok := value.(float64); ok {
return result, nil
}
return 0, &ValueTypeError{key: key, value: value, message: "Value is not a float."}
return defaultValue, &ValueTypeError{key: key, value: value, message: "Value is not a float."}
}

// Set is used to set the value by indicating the key.
Expand Down Expand Up @@ -151,7 +151,7 @@ func (config *Config) Get(key string, defaultValue interface{}) (interface{}, er
if value, exists := mapping[item]; exists {
tmp = value
} else if defaultValue != nil {
return defaultValue, nil
return nil, &KeyError{key: path.Join(append(keys[:i], item)...)}
} else {
return nil, &KeyError{key: path.Join(append(keys[:i], item)...)}
}
Expand Down
96 changes: 91 additions & 5 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package Golf
package golf

import (
"bytes"
Expand All @@ -22,8 +22,7 @@ func TestConfig(t *testing.T) {
defaultValue := "None"

for _, c := range cases {
app := New()
config := NewConfig(app)
config := NewConfig()
config.Set(c.key, c.value)

value, err := config.Get(c.key, defaultValue)
Expand All @@ -46,8 +45,7 @@ func TestConfigWithMultipleEntires(t *testing.T) {
{"foo2", "bar4"},
}

app := New()
config := NewConfig(app)
config := NewConfig()

for _, c := range settings {
config.Set(c.key, c.value)
Expand Down Expand Up @@ -80,3 +78,91 @@ func TestFromJSON(t *testing.T) {
t.Errorf("expected value to be abc but it was %v", value)
}
}

func TestGetStringException(t *testing.T) {
defaultValue := "None"

config := NewConfig()
config.Set("foo", 123)
val, err := config.GetString("foo", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-string value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}

val, err = config.GetString("bar", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-existed value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}
}

func TestGetIntegerException(t *testing.T) {
defaultValue := 123

config := NewConfig()
config.Set("foo", "bar")
val, err := config.GetInt("foo", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-string value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}

val, err = config.GetInt("bar", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-existed value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}
}

func TestGetBoolException(t *testing.T) {
defaultValue := false

config := NewConfig()
config.Set("foo", "bar")
val, err := config.GetBool("foo", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-string value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}

val, err = config.GetBool("bar", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-existed value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}
}

func TestGetFloat64Exception(t *testing.T) {
defaultValue := 0.5

config := NewConfig()
config.Set("foo", "bar")
val, err := config.GetFloat("foo", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-string value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}

val, err = config.GetFloat("bar", defaultValue)
if err == nil {
t.Errorf("Should have raised an type error when getting a non-existed value by GetString.")
}
if val != defaultValue {
t.Errorf("Should have used the default value when raising an error.")
}
}
Loading

0 comments on commit 7a06be0

Please sign in to comment.