diff --git a/Gopkg.lock b/Gopkg.lock index d65ab07..4c5440a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,65 +2,89 @@ [[projects]] + digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" name = "github.com/dgrijalva/jwt-go" packages = ["."] + pruneopts = "UT" revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" version = "v3.2.0" [[projects]] branch = "master" + digest = "1:977911e40ac577da6af9b6b8c301f17b2406293a5dabf7907cc8fcd3b2d755ce" name = "github.com/erikdubbelboer/fasthttp" packages = [ ".", "fasthttputil", "reuseport", - "stackless" + "stackless", ] - revision = "9d45869b466e1c7413609aa82b268fe84d17f3f5" + pruneopts = "UT" + revision = "16e0bf24d475f8dcd37b7241cf08b5a839d69c71" [[projects]] + branch = "master" + digest = "1:cdbed6a7b574b13bbabcd8c36c5be56d6444789bab21fcd37d39655eab0b2141" name = "github.com/klauspost/compress" packages = [ "flate", "gzip", - "zlib" + "zlib", ] - revision = "b939724e787a27c0005cabe3f78e7ed7987ac74f" - version = "v1.4.0" + pruneopts = "UT" + revision = "b50017755d442260d792c34c7c43216d9ba7ffc7" [[projects]] branch = "master" + digest = "1:2d643962fac133904694fffa959bc3c5dcfdcee38c6f5ffdd99a3c93eb9c835c" name = "github.com/klauspost/cpuid" packages = ["."] + pruneopts = "UT" revision = "e7e905edc00ea8827e58662220139109efea09db" [[projects]] + digest = "1:0dbfe9f0e50a4c923870a165a770a3bb6843c745d8e81ee702ef2a8c8ebc8282" name = "github.com/savsgio/go-logger" packages = ["."] + pruneopts = "UT" revision = "764afe23ced5bf5ef3f462417a939974b6facae3" version = "v2.0.1" [[projects]] + branch = "master" + digest = "1:1c7ddcb896dfc7b1124dc0c5a93135832f7991626c470a9d797fc3799422f40c" name = "github.com/thehowl/fasthttprouter" packages = ["."] + pruneopts = "UT" revision = "0e77ecfde28ad422d670671c17415e1d15f0f5be" - version = "v0.2.1" [[projects]] branch = "master" + digest = "1:c468422f334a6b46a19448ad59aaffdfc0a36b08fdcc1c749a0b29b6453d7e59" name = "github.com/valyala/bytebufferpool" packages = ["."] + pruneopts = "UT" revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7" [[projects]] branch = "master" + digest = "1:8507835fade46d48a94828b351e1f8fb83df1aa65ba1e67c9493b0a1b47abc73" name = "github.com/valyala/tcplisten" packages = ["."] + pruneopts = "UT" revision = "ceec8f93295a060cdb565ec25e4ccf17941dbd55" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "4d51514d15d4f8c4c732e381e449510eb46cb494b0b542e728288ed35d9208e3" + input-imports = [ + "github.com/dgrijalva/jwt-go", + "github.com/erikdubbelboer/fasthttp", + "github.com/erikdubbelboer/fasthttp/fasthttputil", + "github.com/erikdubbelboer/fasthttp/reuseport", + "github.com/savsgio/go-logger", + "github.com/thehowl/fasthttprouter", + "github.com/valyala/bytebufferpool", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index 3cddfc1..822ec3f 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,11 @@ Is based on [erikdubbelboer's fasthttp fork](https://github.com/erikdubbelboer/f - TLSEnable *(bool)*: Enable HTTPS - CertKey *(string)*: Path of cert.key file - CertFile *(string)*: Path of cert.pem file -- GracefulEnable *(bool)*: Start server with graceful shutdown +- GracefulShutdown *(bool)*: Start server with graceful shutdown +## Note: +`*atreugo.RequestCtx` is equal than `*fasthttp.RequestCtx`, but adding extra funtionality, so you can use +the same functions of `*fasthttp.RequestCtx`. Don't worry :smile: ## Example: @@ -45,38 +48,31 @@ import ( ) func main() { - // Configuration for Atreugo server config := &atreugo.Config{ Host: "0.0.0.0", Port: 8000, } - - // New instance of atreugo server with your config server := atreugo.New(config) - // Middlewares - fnMiddlewareOne := func(ctx *fasthttp.RequestCtx) (int, error) { + fnMiddlewareOne := func(ctx *atreugo.RequestCtx) (int, error) { return fasthttp.StatusOK, nil } - fnMiddlewareTwo := func(ctx *fasthttp.RequestCtx) (int, error) { - return fasthttp.StatusBadRequest, errors.New("Error message") + fnMiddlewareTwo := func(ctx *atreugo.RequestCtx) (int, error) { + // Disable this middleware if you don't want to see this error + return fasthttp.StatusBadRequest, errors.New("Error example") } - // Register middlewares server.UseMiddleware(fnMiddlewareOne, fnMiddlewareTwo) - - // Views - server.Path("GET", "/", func(ctx *fasthttp.RequestCtx) error { - return atreugo.HTTPResponse(ctx, []byte("

Atreugo Micro-Framework

")) + server.Path("GET", "/", func(ctx *atreugo.RequestCtx) error { + return ctx.HTTPResponse([]byte("

Atreugo Micro-Framework

")) }) - server.Path("GET", "/jsonPage", func(ctx *fasthttp.RequestCtx) error { - return atreugo.JSONResponse(ctx, atreugo.JSON{"Atreugo": true}) + server.Path("GET", "/jsonPage", func(ctx *atreugo.RequestCtx) error { + return ctx.JSONResponse(atreugo.JSON{"Atreugo": true}) }) - // Start server err := server.ListenAndServe() if err != nil { panic(err) diff --git a/atreugo.go b/atreugo.go index 12f5e80..bae54a5 100644 --- a/atreugo.go +++ b/atreugo.go @@ -2,6 +2,7 @@ package atreugo import ( "fmt" + "io" "net" "os" "os/signal" @@ -45,20 +46,25 @@ func New(cfg *Config) *Atreugo { func (s *Atreugo) handler(viewFn View) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { - s.log.Debugf("%s %s", ctx.Method(), ctx.URI()) + actx := acquireRequestCtx(ctx) + defer releaseRequestCtx(actx) + + if s.cfg.LogLevel == logger.DEBUG { + s.log.Debugf("%s %s", actx.Method(), actx.URI()) + } for _, middlewareFn := range s.middlewares { - if statusCode, err := middlewareFn(ctx); err != nil { - s.log.Errorf("Msg: %v | RequestUri: %s", err, ctx.URI().String()) + if statusCode, err := middlewareFn(actx); err != nil { + s.log.Errorf("Msg: %v | RequestUri: %s", err, actx.URI().String()) - ctx.Error(err.Error(), statusCode) + actx.Error(err.Error(), statusCode) return } } - if err := viewFn(ctx); err != nil { + if err := viewFn(actx); err != nil { s.log.Error(err) - ctx.Error(err.Error(), fasthttp.StatusInternalServerError) + actx.Error(err.Error(), fasthttp.StatusInternalServerError) } } } @@ -78,12 +84,12 @@ func (s *Atreugo) getListener(addr string) net.Listener { } func (s *Atreugo) serve(ln net.Listener) error { - protocol := "http" + schema := "http" if s.cfg.TLSEnable { - protocol = "https" + schema = "https" } - s.log.Infof("Listening on: %s://%s/", protocol, ln.Addr().String()) + s.log.Infof("Listening on: %s://%s/", schema, ln.Addr().String()) if s.cfg.TLSEnable { return s.server.ServeTLS(ln, s.cfg.CertFile, s.cfg.CertKey) } @@ -136,12 +142,17 @@ func (s *Atreugo) UseMiddleware(fns ...Middleware) { s.middlewares = append(s.middlewares, fns...) } +// SetLogOutput set log output of server +func (s *Atreugo) SetLogOutput(output io.Writer) { + s.log.SetOutput(output) +} + // ListenAndServe start Atreugo server according to the configuration func (s *Atreugo) ListenAndServe() error { addr := fmt.Sprintf("%s:%d", s.cfg.Host, s.cfg.Port) ln := s.getListener(addr) - if s.cfg.GracefulEnable { + if s.cfg.GracefulShutdown { return s.serveGracefully(ln) } diff --git a/atreugo_test.go b/atreugo_test.go index 96eccb4..084313b 100644 --- a/atreugo_test.go +++ b/atreugo_test.go @@ -2,6 +2,7 @@ package atreugo import ( "bufio" + "bytes" "errors" "testing" "time" @@ -78,12 +79,12 @@ func TestAtreugoServer(t *testing.T) { { name: "AllOk", args: args{ - viewFn: func(ctx *fasthttp.RequestCtx) error { + viewFn: func(ctx *RequestCtx) error { viewCalled = true return nil }, middlewareFns: []Middleware{ - func(ctx *fasthttp.RequestCtx) (int, error) { + func(ctx *RequestCtx) (int, error) { middleWareCounter++ return 0, nil }, @@ -98,15 +99,15 @@ func TestAtreugoServer(t *testing.T) { { name: "FirstMiddlewareError", args: args{ - viewFn: func(ctx *fasthttp.RequestCtx) error { + viewFn: func(ctx *RequestCtx) error { viewCalled = true return nil }, middlewareFns: []Middleware{ - func(ctx *fasthttp.RequestCtx) (int, error) { + func(ctx *RequestCtx) (int, error) { return 403, errors.New("Bad request") }, - func(ctx *fasthttp.RequestCtx) (int, error) { + func(ctx *RequestCtx) (int, error) { middleWareCounter++ return 0, nil }, @@ -121,16 +122,16 @@ func TestAtreugoServer(t *testing.T) { { name: "SecondMiddlewareError", args: args{ - viewFn: func(ctx *fasthttp.RequestCtx) error { + viewFn: func(ctx *RequestCtx) error { viewCalled = true return nil }, middlewareFns: []Middleware{ - func(ctx *fasthttp.RequestCtx) (int, error) { + func(ctx *RequestCtx) (int, error) { middleWareCounter++ return 0, nil }, - func(ctx *fasthttp.RequestCtx) (int, error) { + func(ctx *RequestCtx) (int, error) { return 403, errors.New("Bad request") }, }, @@ -144,12 +145,12 @@ func TestAtreugoServer(t *testing.T) { { name: "ViewError", args: args{ - viewFn: func(ctx *fasthttp.RequestCtx) error { + viewFn: func(ctx *RequestCtx) error { viewCalled = true return errors.New("Fake error") }, middlewareFns: []Middleware{ - func(ctx *fasthttp.RequestCtx) (int, error) { + func(ctx *RequestCtx) (int, error) { middleWareCounter++ return 0, nil }, @@ -297,6 +298,38 @@ func TestAtreugo_getListener(t *testing.T) { } } +func TestAtreugo_Static(t *testing.T) { + type args struct { + path string + } + type want struct { + getPanic bool + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "Ok", + args: args{ + path: "/tmp", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := New(testAtreugoConfig) + s.Static(tt.args.path) + + if s.router.NotFound == nil { + t.Error("Static files not configure") + } + }) + } +} + func TestAtreugo_Path(t *testing.T) { type args struct { method string @@ -306,7 +339,7 @@ func TestAtreugo_Path(t *testing.T) { type want struct { getPanic bool } - testViewFn := func(ctx *fasthttp.RequestCtx) error { + testViewFn := func(ctx *RequestCtx) error { return nil } tests := []struct { @@ -421,35 +454,34 @@ func TestAtreugo_Path(t *testing.T) { } } -func TestAtreugo_Static(t *testing.T) { - type args struct { - path string - } - type want struct { - getPanic bool +func TestAtreugo_UseMiddleware(t *testing.T) { + middlewareFns := []Middleware{ + func(ctx *RequestCtx) (int, error) { + return 403, errors.New("Bad request") + }, + func(ctx *RequestCtx) (int, error) { + return 0, nil + }, } - tests := []struct { - name string - args args - want want - }{ - { - name: "Ok", - args: args{ - path: "/tmp", - }, - }, + s := New(testAtreugoConfig) + s.UseMiddleware(middlewareFns...) + + if len(s.middlewares) != len(middlewareFns) { + t.Errorf("Middlewares are not registered") } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := New(testAtreugoConfig) - s.Static(tt.args.path) - if s.router.NotFound == nil { - t.Error("Static files not configure") - } - }) +} + +func TestAtreugo_SetLogOutput(t *testing.T) { + s := New(&Config{LogLevel: "info"}) + output := new(bytes.Buffer) + + s.SetLogOutput(output) + s.log.Info("Test") + + if len(output.Bytes()) <= 0 { + t.Error("SetLogOutput() log output was not changed") } } @@ -508,11 +540,11 @@ func TestAtreugo_ListenAndServe(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := New(&Config{ - Host: "localhost", - Port: 8000, - LogLevel: "error", - TLSEnable: tt.args.tlsEnable, - GracefulEnable: tt.args.graceful, + Host: "localhost", + Port: 8000, + LogLevel: "error", + TLSEnable: tt.args.tlsEnable, + GracefulShutdown: tt.args.graceful, }) serverCh := make(chan error, 1) @@ -538,13 +570,14 @@ func TestAtreugo_ListenAndServe(t *testing.T) { // Benchmarks func Benchmark_handler(b *testing.B) { s := New(testAtreugoConfig) - viewFn := func(ctx *fasthttp.RequestCtx) error { - return TextResponse(ctx, nil) + viewFn := func(ctx *RequestCtx) error { + return nil } ctx := new(fasthttp.RequestCtx) + h := s.handler(viewFn) b.ResetTimer() for i := 0; i <= b.N; i++ { - s.handler(viewFn)(ctx) + h(ctx) } } diff --git a/context.go b/context.go new file mode 100644 index 0000000..54e2e2f --- /dev/null +++ b/context.go @@ -0,0 +1,28 @@ +package atreugo + +import ( + "sync" + + "github.com/erikdubbelboer/fasthttp" +) + +var requestCtxPool = sync.Pool{ + New: func() interface{} { + return new(RequestCtx) + }, +} + +func (ctx *RequestCtx) reset() { + ctx.RequestCtx = nil +} + +func acquireRequestCtx(ctx *fasthttp.RequestCtx) *RequestCtx { + actx := requestCtxPool.Get().(*RequestCtx) + actx.RequestCtx = ctx + return actx +} + +func releaseRequestCtx(actx *RequestCtx) { + actx.reset() + requestCtxPool.Put(actx) +} diff --git a/context_test.go b/context_test.go new file mode 100644 index 0000000..149a7b9 --- /dev/null +++ b/context_test.go @@ -0,0 +1,38 @@ +package atreugo + +import ( + "testing" + + "github.com/erikdubbelboer/fasthttp" +) + +func TestRequestCtx_reset(t *testing.T) { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + actx.reset() + + if actx.RequestCtx != nil { + t.Errorf("reset() *fasthttp.RequestCtx = %p, want %v", actx.RequestCtx, nil) + } +} + +func Test_acquireRequestCtx(t *testing.T) { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + if actx.RequestCtx != ctx { + t.Errorf("acquireRequestCtx() = %p, want %p", actx.RequestCtx, ctx) + } +} + +func Test_releaseRequestCtx(t *testing.T) { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + releaseRequestCtx(actx) + + if actx.RequestCtx != nil { + t.Errorf("releaseRequestCtx() *fasthttp.RequestCtx = %p, want %v", actx.RequestCtx, nil) + } +} diff --git a/examples/jwt_auth_middleware_sever/main.go b/examples/jwt_auth_middleware_sever/main.go index 700b602..5a6bf6a 100644 --- a/examples/jwt_auth_middleware_sever/main.go +++ b/examples/jwt_auth_middleware_sever/main.go @@ -58,7 +58,7 @@ func validateToken(requestToken string) (*jwt.Token, *userCredential, error) { } // checkTokenMiddleware middleware to check jwt token authorization -func checkTokenMiddleware(ctx *fasthttp.RequestCtx) (int, error) { +func checkTokenMiddleware(ctx *atreugo.RequestCtx) (int, error) { // Avoid middleware when you are going to login view if string(ctx.Path()) == "/login" { return fasthttp.StatusOK, nil @@ -88,13 +88,12 @@ func main() { server.UseMiddleware(checkTokenMiddleware) - server.Path("GET", "/", func(ctx *fasthttp.RequestCtx) error { - return atreugo.HTTPResponse(ctx, - []byte(fmt.Sprintf(`

You are login with JWT

+ server.Path("GET", "/", func(ctx *atreugo.RequestCtx) error { + return ctx.HTTPResponse([]byte(fmt.Sprintf(`

You are login with JWT

JWT cookie value: %s`, ctx.Request.Header.Cookie("atreugo_jwt")))) }) - server.Path("GET", "/login", func(ctx *fasthttp.RequestCtx) error { + server.Path("GET", "/login", func(ctx *atreugo.RequestCtx) error { qUser := []byte("savsgio") qPasswd := []byte("mypasswd") @@ -105,13 +104,15 @@ func main() { // Set cookie for domain cookie := fasthttp.AcquireCookie() + defer fasthttp.ReleaseCookie(cookie) + cookie.SetKey("atreugo_jwt") cookie.SetValue(tokenString) cookie.SetExpire(expireAt) ctx.Response.Header.SetCookie(cookie) } - return atreugo.RedirectResponse(ctx, "/", ctx.Response.StatusCode()) + return ctx.RedirectResponse("/", ctx.Response.StatusCode()) }) err := server.ListenAndServe() diff --git a/examples/simple_server/main.go b/examples/simple_server/main.go index 30f02a5..c548f37 100644 --- a/examples/simple_server/main.go +++ b/examples/simple_server/main.go @@ -14,22 +14,23 @@ func main() { } server := atreugo.New(config) - fnMiddlewareOne := func(ctx *fasthttp.RequestCtx) (int, error) { + fnMiddlewareOne := func(ctx *atreugo.RequestCtx) (int, error) { return fasthttp.StatusOK, nil } - fnMiddlewareTwo := func(ctx *fasthttp.RequestCtx) (int, error) { - return fasthttp.StatusBadRequest, errors.New("Error message") + fnMiddlewareTwo := func(ctx *atreugo.RequestCtx) (int, error) { + // Disable this middleware if you don't want to see this error + return fasthttp.StatusBadRequest, errors.New("Error example") } server.UseMiddleware(fnMiddlewareOne, fnMiddlewareTwo) - server.Path("GET", "/", func(ctx *fasthttp.RequestCtx) error { - return atreugo.HTTPResponse(ctx, []byte("

Atreugo Micro-Framework

")) + server.Path("GET", "/", func(ctx *atreugo.RequestCtx) error { + return ctx.HTTPResponse([]byte("

Atreugo Micro-Framework

")) }) - server.Path("GET", "/jsonPage", func(ctx *fasthttp.RequestCtx) error { - return atreugo.JSONResponse(ctx, atreugo.JSON{"Atreugo": true}) + server.Path("GET", "/jsonPage", func(ctx *atreugo.RequestCtx) error { + return ctx.JSONResponse(atreugo.JSON{"Atreugo": true}) }) err := server.ListenAndServe() diff --git a/response.go b/response.go index 6ddd0dc..a802a61 100644 --- a/response.go +++ b/response.go @@ -7,7 +7,7 @@ import ( "github.com/valyala/bytebufferpool" ) -func newResponse(ctx *fasthttp.RequestCtx, contentType string, statusCode ...int) { +func (ctx *RequestCtx) newResponse(contentType string, statusCode ...int) { ctx.SetContentType(contentType) if len(statusCode) > 0 { @@ -20,55 +20,54 @@ func newResponse(ctx *fasthttp.RequestCtx, contentType string, statusCode ...int } // JSONResponse return response with body in json format -func JSONResponse(ctx *fasthttp.RequestCtx, body interface{}, statusCode ...int) error { - newResponse(ctx, "application/json", statusCode...) +func (ctx *RequestCtx) JSONResponse(body interface{}, statusCode ...int) error { + ctx.newResponse("application/json", statusCode...) return json.NewEncoder(ctx).Encode(body) } // HTTPResponse return response with body in html format -func HTTPResponse(ctx *fasthttp.RequestCtx, body []byte, statusCode ...int) error { - newResponse(ctx, "text/html; charset=utf-8", statusCode...) +func (ctx *RequestCtx) HTTPResponse(body []byte, statusCode ...int) error { + ctx.newResponse("text/html; charset=utf-8", statusCode...) _, err := ctx.Write(body) return err } // TextResponse return response with body in text format -func TextResponse(ctx *fasthttp.RequestCtx, body []byte, statusCode ...int) error { - newResponse(ctx, "text/plain; charset=utf-8", statusCode...) +func (ctx *RequestCtx) TextResponse(body []byte, statusCode ...int) error { + ctx.newResponse("text/plain; charset=utf-8", statusCode...) _, err := ctx.Write(body) return err } // RawResponse returns response without encoding the body. -func RawResponse(ctx *fasthttp.RequestCtx, body []byte, statusCode ...int) error { - newResponse(ctx, "application/octet-stream", statusCode...) +func (ctx *RequestCtx) RawResponse(body []byte, statusCode ...int) error { + ctx.newResponse("application/octet-stream", statusCode...) _, err := ctx.Write(body) return err } // FileResponse return a streaming response with file data. -func FileResponse(ctx *fasthttp.RequestCtx, fileName, filePath, mimeType string) error { +func (ctx *RequestCtx) FileResponse(fileName, filePath, mimeType string) error { buff := bytebufferpool.Get() defer bytebufferpool.Put(buff) - fasthttp.ServeFile(ctx, filePath) + fasthttp.ServeFile(ctx.RequestCtx, filePath) buff.SetString("attachment; filename=") buff.WriteString(fileName) ctx.Response.Header.Set("Content-Disposition", buff.String()) - ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetContentType(mimeType) return nil } // RedirectResponse redirect request to an especific url -func RedirectResponse(ctx *fasthttp.RequestCtx, url string, statusCode int) error { +func (ctx *RequestCtx) RedirectResponse(url string, statusCode int) error { ctx.ResetBody() ctx.Redirect(url, statusCode) diff --git a/response_test.go b/response_test.go index da97d43..ce389fb 100644 --- a/response_test.go +++ b/response_test.go @@ -11,7 +11,6 @@ import ( func Test_newResponse(t *testing.T) { type args struct { - ctx *fasthttp.RequestCtx contentType string statusCode []int } @@ -27,7 +26,6 @@ func Test_newResponse(t *testing.T) { { name: "WithStatusCode", args: args{ - ctx: new(fasthttp.RequestCtx), contentType: "text/plain", statusCode: []int{301}, }, @@ -39,7 +37,6 @@ func Test_newResponse(t *testing.T) { { name: "WithOutStatusCode", args: args{ - ctx: new(fasthttp.RequestCtx), contentType: "text/plain", statusCode: make([]int, 0), }, @@ -52,14 +49,17 @@ func Test_newResponse(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - newResponse(tt.args.ctx, tt.args.contentType, tt.args.statusCode...) + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + actx.newResponse(tt.args.contentType, tt.args.statusCode...) - responseStatusCode := tt.args.ctx.Response.StatusCode() + responseStatusCode := actx.Response.StatusCode() if responseStatusCode != tt.want.statusCode { t.Errorf("status_code: '%v', want: '%v'", responseStatusCode, tt.want.statusCode) } - responseContentType := string(tt.args.ctx.Response.Header.ContentType()) + responseContentType := string(actx.Response.Header.ContentType()) if responseContentType != tt.want.contentType { t.Errorf("content-type: '%v', want: '%v'", responseContentType, tt.want.contentType) } @@ -69,7 +69,6 @@ func Test_newResponse(t *testing.T) { func TestJSONResponse(t *testing.T) { type args struct { - ctx *fasthttp.RequestCtx body interface{} statusCode int contentType string @@ -87,7 +86,6 @@ func TestJSONResponse(t *testing.T) { { name: "Test", args: args{ - ctx: new(fasthttp.RequestCtx), body: JSON{"test": true}, statusCode: 200, }, @@ -101,21 +99,24 @@ func TestJSONResponse(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := JSONResponse(tt.args.ctx, tt.args.body, tt.args.statusCode); err != nil { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + if err := actx.JSONResponse(tt.args.body, tt.args.statusCode); err != nil { t.Errorf("JSONResponse() error: %v", err) } - responseBody := string(bytes.TrimSpace(tt.args.ctx.Response.Body())) + responseBody := string(bytes.TrimSpace(actx.Response.Body())) if responseBody != tt.want.body { t.Errorf("body: '%v', want: '%v'", responseBody, tt.want.body) } - responseStatusCode := tt.args.ctx.Response.StatusCode() + responseStatusCode := actx.Response.StatusCode() if responseStatusCode != tt.want.statusCode { t.Errorf("status_code: '%v', want: '%v'", responseStatusCode, tt.want.statusCode) } - responseContentType := string(tt.args.ctx.Response.Header.ContentType()) + responseContentType := string(actx.Response.Header.ContentType()) if responseContentType != tt.want.contentType { t.Errorf("content-type: '%v', want: '%v'", responseContentType, tt.want.contentType) } @@ -125,7 +126,6 @@ func TestJSONResponse(t *testing.T) { func TestHTTPResponse(t *testing.T) { type args struct { - ctx *fasthttp.RequestCtx body []byte statusCode int contentType string @@ -143,7 +143,6 @@ func TestHTTPResponse(t *testing.T) { { name: "Test", args: args{ - ctx: new(fasthttp.RequestCtx), body: []byte("

Test

"), statusCode: 200, }, @@ -157,21 +156,24 @@ func TestHTTPResponse(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := HTTPResponse(tt.args.ctx, tt.args.body, tt.args.statusCode); err != nil { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + if err := actx.HTTPResponse(tt.args.body, tt.args.statusCode); err != nil { t.Errorf("HTTPResponse() error: %v", err) } - responseBody := string(bytes.TrimSpace(tt.args.ctx.Response.Body())) + responseBody := string(bytes.TrimSpace(actx.Response.Body())) if responseBody != tt.want.body { t.Errorf("body: '%v', want: '%v'", responseBody, tt.want.body) } - responseStatusCode := tt.args.ctx.Response.StatusCode() + responseStatusCode := actx.Response.StatusCode() if responseStatusCode != tt.want.statusCode { t.Errorf("status_code: '%v', want: '%v'", responseStatusCode, tt.want.statusCode) } - responseContentType := string(tt.args.ctx.Response.Header.ContentType()) + responseContentType := string(actx.Response.Header.ContentType()) if responseContentType != tt.want.contentType { t.Errorf("content-type: '%v', want: '%v'", responseContentType, tt.want.contentType) } @@ -181,7 +183,6 @@ func TestHTTPResponse(t *testing.T) { func TestTextResponse(t *testing.T) { type args struct { - ctx *fasthttp.RequestCtx body []byte statusCode int contentType string @@ -199,7 +200,6 @@ func TestTextResponse(t *testing.T) { { name: "Test", args: args{ - ctx: new(fasthttp.RequestCtx), body: []byte("

Test

"), statusCode: 200, }, @@ -213,21 +213,24 @@ func TestTextResponse(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := TextResponse(tt.args.ctx, tt.args.body, tt.args.statusCode); err != nil { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + if err := actx.TextResponse(tt.args.body, tt.args.statusCode); err != nil { t.Errorf("TextResponse() error: %v", err) } - responseBody := string(bytes.TrimSpace(tt.args.ctx.Response.Body())) + responseBody := string(bytes.TrimSpace(actx.Response.Body())) if responseBody != tt.want.body { t.Errorf("body: '%v', want: '%v'", responseBody, tt.want.body) } - responseStatusCode := tt.args.ctx.Response.StatusCode() + responseStatusCode := actx.Response.StatusCode() if responseStatusCode != tt.want.statusCode { t.Errorf("status_code: '%v', want: '%v'", responseStatusCode, tt.want.statusCode) } - responseContentType := string(tt.args.ctx.Response.Header.ContentType()) + responseContentType := string(actx.Response.Header.ContentType()) if responseContentType != tt.want.contentType { t.Errorf("content-type: '%v', want: '%v'", responseContentType, tt.want.contentType) } @@ -237,7 +240,6 @@ func TestTextResponse(t *testing.T) { func TestRawResponse(t *testing.T) { type args struct { - ctx *fasthttp.RequestCtx body []byte statusCode int contentType string @@ -255,7 +257,6 @@ func TestRawResponse(t *testing.T) { { name: "Test", args: args{ - ctx: new(fasthttp.RequestCtx), body: []byte("

Test

"), statusCode: 200, }, @@ -269,21 +270,24 @@ func TestRawResponse(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := RawResponse(tt.args.ctx, tt.args.body, tt.args.statusCode); err != nil { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + if err := actx.RawResponse(tt.args.body, tt.args.statusCode); err != nil { t.Errorf("RawResponse() error: %v", err) } - responseBody := string(bytes.TrimSpace(tt.args.ctx.Response.Body())) + responseBody := string(bytes.TrimSpace(actx.Response.Body())) if responseBody != tt.want.body { t.Errorf("body: '%v', want: '%v'", responseBody, tt.want.body) } - responseStatusCode := tt.args.ctx.Response.StatusCode() + responseStatusCode := actx.Response.StatusCode() if responseStatusCode != tt.want.statusCode { t.Errorf("status_code: '%v', want: '%v'", responseStatusCode, tt.want.statusCode) } - responseContentType := string(tt.args.ctx.Response.Header.ContentType()) + responseContentType := string(actx.Response.Header.ContentType()) if responseContentType != tt.want.contentType { t.Errorf("content-type: '%v', want: '%v'", responseContentType, tt.want.contentType) } @@ -332,25 +336,26 @@ func TestFileResponse(t *testing.T) { defer os.Remove(tt.args.filePath) ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) - FileResponse(ctx, tt.args.fileName, tt.args.filePath, tt.args.mimeType) + actx.FileResponse(tt.args.fileName, tt.args.filePath, tt.args.mimeType) - responseBody := string(bytes.TrimSpace(ctx.Response.Body())) + responseBody := string(bytes.TrimSpace(actx.Response.Body())) if responseBody != tt.want.body { t.Errorf("body: '%v', want: '%v'", responseBody, tt.want.body) } - responseStatusCode := ctx.Response.StatusCode() + responseStatusCode := actx.Response.StatusCode() if responseStatusCode != tt.want.statusCode { t.Errorf("status_code: '%v', want: '%v'", responseStatusCode, tt.want.statusCode) } - responseContentType := string(ctx.Response.Header.ContentType()) + responseContentType := string(actx.Response.Header.ContentType()) if responseContentType != tt.want.contentType { t.Errorf("Header content-type: '%v', want: '%v'", responseContentType, tt.want.contentType) } - responseContentDisposition := string(ctx.Response.Header.Peek("Content-Disposition")) + responseContentDisposition := string(actx.Response.Header.Peek("Content-Disposition")) if responseContentDisposition != tt.want.contentDisposition { t.Errorf("Header content-disposition: '%v', want: '%v'", responseContentDisposition, tt.want.contentDisposition) } @@ -360,7 +365,6 @@ func TestFileResponse(t *testing.T) { func TestRedirectResponse(t *testing.T) { type args struct { - ctx *fasthttp.RequestCtx url string statusCode int } @@ -376,7 +380,6 @@ func TestRedirectResponse(t *testing.T) { { name: "Test", args: args{ - ctx: new(fasthttp.RequestCtx), url: "http://urltoredirect.es", statusCode: 301, }, @@ -388,16 +391,19 @@ func TestRedirectResponse(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := RedirectResponse(tt.args.ctx, tt.args.url, tt.args.statusCode); err != nil { + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + + if err := actx.RedirectResponse(tt.args.url, tt.args.statusCode); err != nil { t.Errorf("RedirectResponse() error: %v", err) } - responseLocation := string(tt.args.ctx.Response.Header.Peek("Location")) + responseLocation := string(actx.Response.Header.Peek("Location")) if responseLocation != tt.want.locationURL { t.Errorf("Header content-disposition: '%v', want: '%v'", responseLocation, tt.want.locationURL) } - responseStatusCode := tt.args.ctx.Response.StatusCode() + responseStatusCode := actx.Response.StatusCode() if responseStatusCode != tt.want.statusCode { t.Errorf("status_code: '%v', want: '%v'", responseStatusCode, tt.want.statusCode) } @@ -409,9 +415,12 @@ func TestRedirectResponse(t *testing.T) { func Benchmark_FileResponse(b *testing.B) { cwd, _ := os.Getwd() path := cwd + "/LICENSE" + ctx := new(fasthttp.RequestCtx) + actx := acquireRequestCtx(ctx) + b.ResetTimer() for i := 0; i <= b.N; i++ { - FileResponse(ctx, "hola", path, "text/plain") + actx.FileResponse("hola", path, "text/plain") } } diff --git a/types.go b/types.go index 6694e39..3d23eb7 100644 --- a/types.go +++ b/types.go @@ -12,14 +12,14 @@ import ( // Config config for Atreugo type Config struct { - Host string - Port int - LogLevel string - Compress bool - TLSEnable bool - CertKey string - CertFile string - GracefulEnable bool + Host string + Port int + LogLevel string + Compress bool + TLSEnable bool + CertKey string + CertFile string + GracefulShutdown bool } // Atreugo struct for make up a server @@ -31,11 +31,16 @@ type Atreugo struct { cfg *Config } +// RequestCtx context wrapper for fasthttp.RequestCtx to adds extra funtionality +type RequestCtx struct { + *fasthttp.RequestCtx +} + // View must process incoming requests. -type View func(ctx *fasthttp.RequestCtx) error +type View func(ctx *RequestCtx) error // Middleware must process all incoming requests before defined views. -type Middleware func(ctx *fasthttp.RequestCtx) (int, error) +type Middleware func(ctx *RequestCtx) (int, error) // JSON is a map whose key is a string and whose value an interface type JSON map[string]interface{} diff --git a/utils.go b/utils.go index da781e3..cb3442d 100644 --- a/utils.go +++ b/utils.go @@ -1,25 +1,11 @@ package atreugo -import ( - "unsafe" -) - func panicOnError(err error) { if err != nil { panic(err) } } -// b2s convert bytes array to string without memory allocation (non safe) -func b2s(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} - -// int64ToInt convert int64 to int without memory allocation (non safe) -func int64ToInt(i int64) int { - return *(*int)(unsafe.Pointer(&i)) -} - // index returns the first index of the target string `t`, or // -1 if no match is found. func indexOf(vs []string, t string) int { diff --git a/utils_test.go b/utils_test.go index 64ac010..e6f0df8 100644 --- a/utils_test.go +++ b/utils_test.go @@ -46,44 +46,6 @@ func Test_panicOnError(t *testing.T) { } } -func Test_b2s(t *testing.T) { - type args struct { - b []byte - } - tests := struct { - args args - want string - }{ - args: args{ - b: []byte("Test"), - }, - want: "Test", - } - - if got := b2s(tests.args.b); got != tests.want { - t.Errorf("b2s(): '%v', want: '%v'", got, tests.want) - } -} - -func Test_int64ToInt(t *testing.T) { - type args struct { - i int64 - } - tests := struct { - args args - want int - }{ - args: args{ - i: int64(3), - }, - want: 3, - } - - if got := int64ToInt(tests.args.i); got != tests.want { - t.Errorf("int64ToInt: '%v', want: '%v'", got, tests.want) - } -} - func Test_indexOf(t *testing.T) { type args struct { vs []string