1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-21 04:52:38 +00:00
This commit is contained in:
Fenny 2020-02-21 16:56:32 +01:00
parent bdaac2d116
commit 813850c83c
25 changed files with 2370 additions and 2405 deletions

4
.github/README.md vendored
View File

@ -208,11 +208,11 @@ func main() {
app := fiber.New()
app.Static("/public")
app.Get("/demo", func(c *fiber.Ctx) {
c.Send("This is a demo!")
})
app.Post("/register", func(c *fiber.Ctx) {
c.Send("Welcome!")
})

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>
@ -208,11 +208,11 @@ func main() {
app := fiber.New()
app.Static("/public")
app.Get("/demo", func(c *fiber.Ctx) {
c.Send("This is a demo!")
})
app.Post("/register", func(c *fiber.Ctx) {
c.Send("Welcome!")
})

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

View File

@ -1,8 +1,8 @@
<p align="center">
<a href="https://fiber.wiki">
<img alt="Fiber" height="100" src="https://github.com/gofiber/docs/blob/master/static/logo.svg">
<img alt="Fiber" height="125" src="https://github.com/gofiber/docs/blob/master/static/fiber_v2_logo.svg">
</a>
<br><br>
<br>
<a href="https://github.com/gofiber/fiber/blob/master/.github/README.md">
<img height="20px" src="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.4.6/flags/4x3/gb.svg">
</a>

18
.github/workflows/security.yml vendored Normal file
View File

@ -0,0 +1,18 @@
on: [push]
name: Security
jobs:
test:
strategy:
matrix:
go-version: [1.13.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Security
run: go get github.com/securego/gosec/cmd/gosec; `go env GOPATH`/bin/gosec ./...

367
app.go Normal file
View File

@ -0,0 +1,367 @@
package fiber
import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"time"
fasthttp "github.com/valyala/fasthttp"
)
// Version of Fiber
const Version = "2.0.0"
type (
// App denotes the Fiber application.
App struct {
server *fasthttp.Server
routes []*Route
child bool
recover func(*Ctx)
Settings *Settings
}
// Map defines a generic map of type `map[string]interface{}`.
Map map[string]interface{}
// Settings is a struct holding the server settings
Settings struct {
// fiber settings
Prefork bool `default:"false"`
// Enable strict routing. When enabled, the router treats "/foo" and "/foo/" as different. Otherwise, the router treats "/foo" and "/foo/" as the same.
StrictRouting bool `default:"false"`
// Enable case sensitivity. When enabled, "/Foo" and "/foo" are different routes. When disabled, "/Foo" and "/foo" are treated the same.
CaseSensitive bool `default:"false"`
// Enables the "Server: value" HTTP header.
ServerHeader string `default:""`
// fasthttp settings
GETOnly bool `default:"false"`
IdleTimeout time.Duration `default:"0"`
Concurrency int `default:"256 * 1024"`
ReadTimeout time.Duration `default:"0"`
WriteTimeout time.Duration `default:"0"`
TCPKeepalive bool `default:"false"`
MaxConnsPerIP int `default:"0"`
ReadBufferSize int `default:"4096"`
WriteBufferSize int `default:"4096"`
ConcurrencySleep time.Duration `default:"0"`
DisableKeepAlive bool `default:"false"`
ReduceMemoryUsage bool `default:"false"`
MaxRequestsPerConn int `default:"0"`
TCPKeepalivePeriod time.Duration `default:"0"`
MaxRequestBodySize int `default:"4 * 1024 * 1024"`
NoHeaderNormalizing bool `default:"false"`
NoDefaultContentType bool `default:"false"`
// template settings
ViewCache bool `default:"false"`
ViewFolder string `default:""`
ViewEngine string `default:""`
ViewExtension string `default:""`
}
)
var prefork, child bool
func regBoolVar(p *bool, name string, value bool, usage string) {
if flag.Lookup(name) == nil {
flag.BoolVar(p, name, value, usage)
}
}
func getBoolFlag(name string) bool {
return flag.Lookup(name).Value.(flag.Getter).Get().(bool)
}
func init() {
regBoolVar(&prefork, "prefork", false, "use prefork")
regBoolVar(&child, "child", false, "is child process")
}
// New ...
func New(settings ...*Settings) (app *App) {
flag.Parse()
prefork = getBoolFlag("prefork")
child = getBoolFlag("child")
app = &App{
child: child,
}
if len(settings) > 0 {
opt := settings[0]
if !opt.Prefork {
opt.Prefork = prefork
}
if opt.Concurrency == 0 {
opt.Concurrency = 256 * 1024
}
if opt.ReadBufferSize == 0 {
opt.ReadBufferSize = 4096
}
if opt.WriteBufferSize == 0 {
opt.WriteBufferSize = 4096
}
if opt.MaxRequestBodySize == 0 {
opt.MaxRequestBodySize = 4 * 1024 * 1024
}
app.Settings = opt
return
}
app.Settings = &Settings{
Prefork: prefork,
Concurrency: 256 * 1024,
ReadBufferSize: 4096,
WriteBufferSize: 4096,
MaxRequestBodySize: 4 * 1024 * 1024,
}
return
}
// Static ...
func (app *App) Static(args ...string) *App {
app.registerStatic("/", args...)
return app
}
// WebSocket ...
func (app *App) WebSocket(args ...interface{}) *App {
app.register("GET", "", args...)
return app
}
// Connect ...
func (app *App) Connect(args ...interface{}) *App {
app.register("CONNECT", "", args...)
return app
}
// Put ...
func (app *App) Put(args ...interface{}) *App {
app.register("PUT", "", args...)
return app
}
// Post ...
func (app *App) Post(args ...interface{}) *App {
app.register("POST", "", args...)
return app
}
// Delete ...
func (app *App) Delete(args ...interface{}) *App {
app.register("DELETE", "", args...)
return app
}
// Head ...
func (app *App) Head(args ...interface{}) *App {
app.register("HEAD", "", args...)
return app
}
// Patch ...
func (app *App) Patch(args ...interface{}) *App {
app.register("PATCH", "", args...)
return app
}
// Options ...
func (app *App) Options(args ...interface{}) *App {
app.register("OPTIONS", "", args...)
return app
}
// Trace ...
func (app *App) Trace(args ...interface{}) *App {
app.register("TRACE", "", args...)
return app
}
// Get ...
func (app *App) Get(args ...interface{}) *App {
app.register("GET", "", args...)
return app
}
// All ...
func (app *App) All(args ...interface{}) *App {
app.register("ALL", "", args...)
return app
}
// Use ...
func (app *App) Use(args ...interface{}) *App {
app.register("USE", "", args...)
return app
}
// Listen : https://fiber.wiki/application#listen
func (app *App) Listen(address interface{}, tls ...string) error {
addr, ok := address.(string)
if !ok {
port, ok := address.(int)
if !ok {
return fmt.Errorf("Listen: Host must be an INT port or STRING address")
}
addr = strconv.Itoa(port)
}
if !strings.Contains(addr, ":") {
addr = ":" + addr
}
// Create fasthttp server
app.server = app.newServer()
// Print banner
// if app.Settings.Banner && !app.child {
// fmt.Printf("Fiber-%s is listening on %s\n", Version, addr)
// }
var ln net.Listener
var err error
// Prefork enabled
if app.Settings.Prefork && runtime.NumCPU() > 1 {
if ln, err = app.prefork(addr); err != nil {
return err
}
} else {
if ln, err = net.Listen("tcp4", addr); err != nil {
return err
}
}
// enable TLS/HTTPS
if len(tls) > 1 {
return app.server.ServeTLS(ln, tls[0], tls[1])
}
return app.server.Serve(ln)
}
// Shutdown server gracefully
func (app *App) Shutdown() error {
if app.server == nil {
return fmt.Errorf("Server is not running")
}
return app.server.Shutdown()
}
// Test takes a http.Request and execute a fake connection to the application
// It returns a http.Response when the connection was successful
func (app *App) Test(req *http.Request) (*http.Response, error) {
// Get raw http request
reqRaw, err := httputil.DumpRequest(req, true)
if err != nil {
return nil, err
}
// Setup a fiber server struct
app.server = app.newServer()
// Create fake connection
conn := &testConn{}
// Pass HTTP request to conn
_, err = conn.r.Write(reqRaw)
if err != nil {
return nil, err
}
// Serve conn to server
channel := make(chan error)
go func() {
channel <- app.server.ServeConn(conn)
}()
// Wait for callback
select {
case err := <-channel:
if err != nil {
return nil, err
}
// Throw timeout error after 200ms
case <-time.After(1000 * time.Millisecond):
return nil, fmt.Errorf("timeout")
}
// Get raw HTTP response
respRaw, err := ioutil.ReadAll(&conn.w)
if err != nil {
return nil, err
}
// Create buffer
reader := strings.NewReader(getString(respRaw))
buffer := bufio.NewReader(reader)
// Convert raw HTTP response to http.Response
resp, err := http.ReadResponse(buffer, req)
if err != nil {
return nil, err
}
// Return *http.Response
return resp, nil
}
// https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
func (app *App) prefork(address string) (ln net.Listener, err error) {
// Master proc
if !app.child {
addr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return ln, err
}
tcplistener, err := net.ListenTCP("tcp", addr)
if err != nil {
return ln, err
}
fl, err := tcplistener.File()
if err != nil {
return ln, err
}
childs := make([]*exec.Cmd, runtime.NumCPU()/2)
// #nosec G204
for i := range childs {
childs[i] = exec.Command(os.Args[0], "-prefork", "-child")
childs[i].Stdout = os.Stdout
childs[i].Stderr = os.Stderr
childs[i].ExtraFiles = []*os.File{fl}
if err := childs[i].Start(); err != nil {
return ln, err
}
}
for _, child := range childs {
if err := child.Wait(); err != nil {
return ln, err
}
}
os.Exit(0)
} else {
ln, err = net.FileListener(os.NewFile(3, ""))
}
return ln, err
}
func (app *App) newServer() *fasthttp.Server {
return &fasthttp.Server{
Handler: app.handler,
ErrorHandler: func(ctx *fasthttp.RequestCtx, err error) {
ctx.Response.SetStatusCode(400)
ctx.Response.SetBodyString("Bad Request")
},
Name: app.Settings.ServerHeader,
Concurrency: app.Settings.Concurrency,
SleepWhenConcurrencyLimitsExceeded: app.Settings.ConcurrencySleep,
DisableKeepalive: app.Settings.DisableKeepAlive,
ReadBufferSize: app.Settings.ReadBufferSize,
WriteBufferSize: app.Settings.WriteBufferSize,
ReadTimeout: app.Settings.ReadTimeout,
WriteTimeout: app.Settings.WriteTimeout,
IdleTimeout: app.Settings.IdleTimeout,
MaxConnsPerIP: app.Settings.MaxConnsPerIP,
MaxRequestsPerConn: app.Settings.MaxRequestsPerConn,
TCPKeepalive: app.Settings.TCPKeepalive,
TCPKeepalivePeriod: app.Settings.TCPKeepalivePeriod,
MaxRequestBodySize: app.Settings.MaxRequestBodySize,
ReduceMemoryUsage: app.Settings.ReduceMemoryUsage,
GetOnly: app.Settings.GETOnly,
DisableHeaderNamesNormalizing: app.Settings.NoHeaderNormalizing,
NoDefaultServerHeader: app.Settings.ServerHeader == "",
NoDefaultContentType: app.Settings.NoDefaultContentType,
}
}

View File

@ -7,51 +7,67 @@ import (
var handler = func(c *Ctx) {}
func is200(t *testing.T, app *App, url string, m ...string) {
method := "GET"
if len(m) > 0 {
method = m[0]
}
req, _ := http.NewRequest(method, url, nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("%s - %s - %v", method, url, err)
}
if resp.StatusCode != 200 {
t.Fatalf("%s - %s - %v", method, url, resp.StatusCode)
}
}
func Test_Methods(t *testing.T) {
app := New()
methods := []string{"CONNECT", "PUT", "POST", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE", "GET", "ALL", "USE"}
app.Connect("", handler)
app.Connect("/CONNECT", handler)
app.Put("/PUT", handler)
app.Post("/POST", handler)
app.Delete("/DELETE", handler)
app.Head("/HEAD", handler)
app.Patch("/PATCH", handler)
app.Options("/OPTIONS", handler)
app.Trace("/TRACE", handler)
app.Get("/GET", handler)
app.All("/ALL", handler)
app.Use("/USE", handler)
app.Connect("/:john?/:doe?", handler)
is200(t, app, "/", "CONNECT")
app.Connect("/:john?/:doe?", handler)
is200(t, app, "/", "CONNECT")
app.Put("/:john?/:doe?", handler)
is200(t, app, "/", "CONNECT")
app.Post("/:john?/:doe?", handler)
is200(t, app, "/", "POST")
app.Delete("/:john?/:doe?", handler)
is200(t, app, "/", "DELETE")
app.Head("/:john?/:doe?", handler)
is200(t, app, "/", "HEAD")
app.Patch("/:john?/:doe?", handler)
is200(t, app, "/", "PATCH")
app.Options("/:john?/:doe?", handler)
is200(t, app, "/", "OPTIONS")
app.Trace("/:john?/:doe?", handler)
is200(t, app, "/", "TRACE")
app.Get("/:john?/:doe?", handler)
is200(t, app, "/", "GET")
app.All("/:john?/:doe?", handler)
is200(t, app, "/", "POST")
app.Use("/:john?/:doe?", handler)
is200(t, app, "/", "GET")
for _, method := range methods {
var req *http.Request
if method == "ALL" {
req, _ = http.NewRequest("CONNECT", "/"+method, nil)
} else if method == "USE" {
req, _ = http.NewRequest("OPTIONS", "/"+method+"/test", nil)
} else {
req, _ = http.NewRequest(method, "/"+method, nil)
}
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s %s`, t.Name(), method, err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: %s expecting 200 but received %v`, t.Name(), method, resp.StatusCode)
}
}
}
func Test_Static(t *testing.T) {
app := New()
grp := app.Group("/v1")
grp.Static("/v2", ".travis.yml")
grp.Static(".travis.yml")
app.Static("/yesyes*", ".github/FUNDING.yml")
app.Static("./.github")
app.Static("github", ".github/FUNDING.yml")
app.Static("/*", "./.github")
app.Static("/john", "./.github")
req, _ := http.NewRequest("GET", "/stale.yml", nil)
resp, err := app.Test(req)
@ -98,37 +114,53 @@ func Test_Static(t *testing.T) {
t.Fatalf(`%s: Missing Content-Length`, t.Name())
}
}
func Test_Group(t *testing.T) {
app := New()
grp := app.Group("/test")
grp.Get("/", handler)
is200(t, app, "/test", "GET")
grp.Get("/:demo?", handler)
is200(t, app, "/test/john", "GET")
grp.Connect("/CONNECT", handler)
is200(t, app, "/test/CONNECT", "CONNECT")
grp.Put("/PUT", handler)
is200(t, app, "/test/PUT", "PUT")
grp.Post("/POST", handler)
is200(t, app, "/test/POST", "POST")
grp.Delete("/DELETE", handler)
is200(t, app, "/test/DELETE", "DELETE")
grp.Head("/HEAD", handler)
is200(t, app, "/test/HEAD", "HEAD")
grp.Patch("/PATCH", handler)
is200(t, app, "/test/PATCH", "PATCH")
grp.Options("/OPTIONS", handler)
is200(t, app, "/test/OPTIONS", "OPTIONS")
grp.Trace("/TRACE", handler)
is200(t, app, "/test/TRACE", "TRACE")
grp.All("/ALL", handler)
is200(t, app, "/test/ALL", "POST")
grp.Use("/USE", handler)
req, _ := http.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
req, _ = http.NewRequest("GET", "/test/test", nil)
resp, err = app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
is200(t, app, "/test/USE/oke", "GET")
api := grp.Group("/v1")
api.Post("/", handler)
is200(t, app, "/test/v1/", "POST")
api.Get("/users", handler)
is200(t, app, "/test/v1/users", "GET")
}
// func Test_Listen(t *testing.T) {

View File

@ -1,549 +0,0 @@
// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
// 📌 Please open an issue if you got suggestions or found a bug!
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
// 🦸 Not all heroes wear capes, thank you to some amazing people
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
package fiber
import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
fasthttp "github.com/valyala/fasthttp"
reuseport "github.com/valyala/fasthttp/reuseport"
)
const (
// Version : Fiber release
Version = "1.7.0"
// Website : Fiber website
Website = "https://fiber.wiki"
banner = "\x1b[1;32m" + ` ______ __ ______ ______ ______
/\ ___\ /\ \ /\ == \ /\ ___\ /\ == \
\ \ __\ \ \ \ \ \ __< \ \ __\ \ \ __<
\ \_\ \ \_\ \ \_____\ \ \_____\ \ \_\ \_\
\/_/ \/_/ \/_____/ \/_____/ \/_/ /_/
` + "\x1b[0mFiber \x1b[1;32mv%s\x1b[0m %s on \x1b[1;32m%s\x1b[0m, visit \x1b[1;32m%s\x1b[0m\n\n"
)
var (
prefork = flag.Bool("prefork", false, "use prefork")
child = flag.Bool("child", false, "is child process")
)
// Application structure
type Application struct {
// Server name header
Server string
// HTTP server struct
httpServer *fasthttp.Server
// Show fiber banner
Banner bool
// https://github.com/valyala/fasthttp/blob/master/server.go#L150
Engine *engine
// https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
Prefork bool
// is child process
child bool
// Stores all routes
routes []*Route
// Recover holds a handler that is executed on a panic
recover func(*Ctx)
}
// Fasthttp settings
// https://github.com/valyala/fasthttp/blob/master/server.go#L150
type engine struct {
Concurrency int
DisableKeepAlive bool
ReadBufferSize int
WriteBufferSize int
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxConnsPerIP int
MaxRequestsPerConn int
TCPKeepalive bool
TCPKeepalivePeriod time.Duration
MaxRequestBodySize int
ReduceMemoryUsage bool
GetOnly bool
DisableHeaderNamesNormalizing bool
SleepWhenConcurrencyLimitsExceeded time.Duration
NoDefaultContentType bool
KeepHijackedConns bool
}
// New https://fiber.wiki/application#new
func New() *Application {
flag.Parse()
schemaDecoder.SetAliasTag("form")
return &Application{
Server: "",
httpServer: nil,
Banner: true,
Prefork: *prefork,
child: *child,
Engine: &engine{
Concurrency: 256 * 1024,
DisableKeepAlive: false,
ReadBufferSize: 4096,
WriteBufferSize: 4096,
WriteTimeout: 0,
ReadTimeout: 0,
IdleTimeout: 0,
MaxConnsPerIP: 0,
MaxRequestsPerConn: 0,
TCPKeepalive: false,
TCPKeepalivePeriod: 0,
MaxRequestBodySize: 4 * 1024 * 1024,
ReduceMemoryUsage: false,
GetOnly: false,
DisableHeaderNamesNormalizing: false,
SleepWhenConcurrencyLimitsExceeded: 0,
NoDefaultContentType: false,
KeepHijackedConns: false,
},
}
}
// Recover catches panics and avoids crashes
func (app *Application) Recover(ctx func(*Ctx)) {
app.recover = ctx
}
// Recover binding for groups
func (grp *Group) Recover(ctx func(*Ctx)) {
grp.app.recover = ctx
}
// Group :
type Group struct {
path string
app *Application
}
// Group :
func (app *Application) Group(path string) *Group {
return &Group{
path: path,
app: app,
}
}
// Connect establishes a tunnel to the server
// identified by the target resource.
func (app *Application) Connect(args ...interface{}) *Application {
app.register("CONNECT", args...)
return app
}
// Connect for group
func (grp *Group) Connect(args ...interface{}) *Group {
grp.register("CONNECT", args...)
return grp
}
// Put replaces all current representations
// of the target resource with the request payload.
func (app *Application) Put(args ...interface{}) *Application {
app.register("PUT", args...)
return app
}
// Put for group
func (grp *Group) Put(args ...interface{}) *Group {
grp.register("PUT", args...)
return grp
}
// Post is used to submit an entity to the specified resource,
// often causing a change in state or side effects on the server.
func (app *Application) Post(args ...interface{}) *Application {
app.register("POST", args...)
return app
}
// Post for group
func (grp *Group) Post(args ...interface{}) *Group {
grp.register("POST", args...)
return grp
}
// Delete deletes the specified resource.
func (app *Application) Delete(args ...interface{}) *Application {
app.register("DELETE", args...)
return app
}
// Delete for group
func (grp *Group) Delete(args ...interface{}) *Group {
grp.register("DELETE", args...)
return grp
}
// Head asks for a response identical to that of a GET request,
// but without the response body.
func (app *Application) Head(args ...interface{}) *Application {
app.register("HEAD", args...)
return app
}
// Head for group
func (grp *Group) Head(args ...interface{}) *Group {
grp.register("HEAD", args...)
return grp
}
// Patch is used to apply partial modifications to a resource.
func (app *Application) Patch(args ...interface{}) *Application {
app.register("PATCH", args...)
return app
}
// Patch for group
func (grp *Group) Patch(args ...interface{}) *Group {
grp.register("PATCH", args...)
return grp
}
// Options is used to describe the communication options
// for the target resource.
func (app *Application) Options(args ...interface{}) *Application {
app.register("OPTIONS", args...)
return app
}
// Options for group
func (grp *Group) Options(args ...interface{}) *Group {
grp.register("OPTIONS", args...)
return grp
}
// Trace performs a message loop-back test
// along the path to the target resource.
func (app *Application) Trace(args ...interface{}) *Application {
app.register("TRACE", args...)
return app
}
// Trace for group
func (grp *Group) Trace(args ...interface{}) *Group {
grp.register("TRACE", args...)
return grp
}
// Get requests a representation of the specified resource.
// Requests using GET should only retrieve data.
func (app *Application) Get(args ...interface{}) *Application {
app.register("GET", args...)
return app
}
// Get for group
func (grp *Group) Get(args ...interface{}) *Group {
grp.register("GET", args...)
return grp
}
// All matches any HTTP method
func (app *Application) All(args ...interface{}) *Application {
app.register("ALL", args...)
return app
}
// All for group
func (grp *Group) All(args ...interface{}) *Group {
grp.register("ALL", args...)
return grp
}
// Use only matches the starting path
func (app *Application) Use(args ...interface{}) *Application {
app.register("USE", args...)
return app
}
// Use for group
func (grp *Group) Use(args ...interface{}) *Group {
grp.register("USE", args...)
return grp
}
// Static for groups
func (grp *Group) Static(args ...string) {
prefix := grp.path
root := "./"
if len(args) == 1 {
root = args[0]
} else if len(args) == 2 {
root = args[1]
prefix = prefix + args[0]
prefix = strings.Replace(prefix, "//", "/", -1)
prefix = filepath.Clean(prefix)
prefix = filepath.ToSlash(prefix)
}
grp.app.Static(prefix, root)
}
// Static https://fiber.wiki/application#static
func (app *Application) Static(args ...string) {
prefix := "/"
root := "./"
wildcard := false
midware := false
// enable / disable gzipping somewhere?
// todo v2.0.0
gzip := true
if len(args) == 1 {
root = args[0]
} else if len(args) == 2 {
prefix = args[0]
root = args[1]
if prefix[0] != '/' {
prefix = "/" + prefix
}
}
// Check if wildcard for single files
// app.Static("*", "./public/index.html")
// app.Static("/*", "./public/index.html")
if prefix == "*" || prefix == "/*" {
wildcard = true
} else if strings.Contains(prefix, "*") {
prefix = strings.Replace(prefix, "*", "", -1)
midware = true
}
// Lets get all files from root
files, _, err := getFiles(root)
if err != nil {
log.Fatal("Static: ", err)
}
// ./static/compiled => static/compiled
mount := filepath.Clean(root)
// Loop over all files
for _, file := range files {
// Ignore the .gzipped files by fasthttp
if strings.Contains(file, ".fasthttp.gz") {
continue
}
// Time to create a fake path for the route match
// static/index.html => /index.html
path := filepath.Join(prefix, strings.Replace(file, mount, "", 1))
// for windows: static\index.html => /index.html
path = filepath.ToSlash(path)
// Store file path to use in ctx handler
filePath := file
// If the file is an index.html, bind the prefix to index.html directly
if filepath.Base(filePath) == "index.html" || filepath.Base(filePath) == "index.htm" {
app.routes = append(app.routes, &Route{"GET", prefix, midware, wildcard, nil, nil, func(c *Ctx) {
c.SendFile(filePath, gzip)
}})
}
// Add the route + SendFile(filepath) to routes
app.routes = append(app.routes, &Route{"GET", path, midware, wildcard, nil, nil, func(c *Ctx) {
c.SendFile(filePath, gzip)
}})
}
}
// Listen : https://fiber.wiki/application#listen
func (app *Application) Listen(address interface{}, tls ...string) {
host := ""
switch val := address.(type) {
case int:
host = ":" + strconv.Itoa(val) // 8080 => ":8080"
case string:
if !strings.Contains(val, ":") {
val = ":" + val // "8080" => ":8080"
}
host = val
default:
log.Fatal("Listen: Host must be an INT port or STRING address")
}
// Create fasthttp server
app.httpServer = app.setupServer()
// Prefork enabled
if app.Prefork && runtime.NumCPU() > 1 {
if app.Banner && !app.child {
fmt.Printf(banner, Version, "preforking", host, "fiber.wiki")
}
app.prefork(host, tls...)
}
// Prefork disabled
if app.Banner {
fmt.Printf(banner, Version, "listening", host, "fiber.wiki")
}
ln, err := net.Listen("tcp4", host)
if err != nil {
log.Fatal("Listen: ", err)
}
// enable TLS/HTTPS
if len(tls) > 1 {
if err := app.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil {
log.Fatal("Listen: ", err)
}
}
if err := app.httpServer.Serve(ln); err != nil {
log.Fatal("Listen: ", err)
}
}
// Shutdown server gracefully
func (app *Application) Shutdown() error {
if app.httpServer == nil {
return fmt.Errorf("server is not running")
}
return app.httpServer.Shutdown()
}
// Test takes a http.Request and execute a fake connection to the application
// It returns a http.Response when the connection was successful
func (app *Application) Test(req *http.Request) (*http.Response, error) {
// Get raw http request
reqRaw, err := httputil.DumpRequest(req, true)
if err != nil {
return nil, err
}
// Setup a fiber server struct
app.httpServer = app.setupServer()
// Create fake connection
conn := &conn{}
// Pass HTTP request to conn
_, err = conn.r.Write(reqRaw)
if err != nil {
return nil, err
}
// Serve conn to server
channel := make(chan error)
go func() {
channel <- app.httpServer.ServeConn(conn)
}()
// Wait for callback
select {
case err := <-channel:
if err != nil {
return nil, err
}
// Throw timeout error after 200ms
case <-time.After(1000 * time.Millisecond):
return nil, fmt.Errorf("timeout")
}
// Get raw HTTP response
respRaw, err := ioutil.ReadAll(&conn.w)
if err != nil {
return nil, err
}
// Create buffer
reader := strings.NewReader(getString(respRaw))
buffer := bufio.NewReader(reader)
// Convert raw HTTP response to http.Response
resp, err := http.ReadResponse(buffer, req)
if err != nil {
return nil, err
}
// Return *http.Response
return resp, nil
}
// https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
func (app *Application) prefork(host string, tls ...string) {
// Master proc
if !app.child {
// Create babies
childs := make([]*exec.Cmd, runtime.NumCPU())
// #nosec G204
for i := range childs {
childs[i] = exec.Command(os.Args[0], "-prefork", "-child")
childs[i].Stdout = os.Stdout
childs[i].Stderr = os.Stderr
if err := childs[i].Start(); err != nil {
log.Fatal("Listen-prefork: ", err)
}
}
for _, child := range childs {
if err := child.Wait(); err != nil {
log.Fatal("Listen-prefork: ", err)
}
}
os.Exit(0)
}
// Child proc
runtime.GOMAXPROCS(1)
ln, err := reuseport.Listen("tcp4", host)
if err != nil {
log.Fatal("Listen-prefork: ", err)
}
// enable TLS/HTTPS
if len(tls) > 1 {
if err := app.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil {
log.Fatal("Listen-prefork: ", err)
}
}
if err := app.httpServer.Serve(ln); err != nil {
log.Fatal("Listen-prefork: ", err)
}
}
func (app *Application) setupServer() *fasthttp.Server {
return &fasthttp.Server{
Handler: app.handler,
Name: app.Server,
Concurrency: app.Engine.Concurrency,
DisableKeepalive: app.Engine.DisableKeepAlive,
ReadBufferSize: app.Engine.ReadBufferSize,
WriteBufferSize: app.Engine.WriteBufferSize,
ReadTimeout: app.Engine.ReadTimeout,
WriteTimeout: app.Engine.WriteTimeout,
IdleTimeout: app.Engine.IdleTimeout,
MaxConnsPerIP: app.Engine.MaxConnsPerIP,
MaxRequestsPerConn: app.Engine.MaxRequestsPerConn,
TCPKeepalive: app.Engine.TCPKeepalive,
TCPKeepalivePeriod: app.Engine.TCPKeepalivePeriod,
MaxRequestBodySize: app.Engine.MaxRequestBodySize,
ReduceMemoryUsage: app.Engine.ReduceMemoryUsage,
GetOnly: app.Engine.GetOnly,
DisableHeaderNamesNormalizing: app.Engine.DisableHeaderNamesNormalizing,
SleepWhenConcurrencyLimitsExceeded: app.Engine.SleepWhenConcurrencyLimitsExceeded,
NoDefaultServerHeader: app.Server == "",
NoDefaultContentType: app.Engine.NoDefaultContentType,
KeepHijackedConns: app.Engine.KeepHijackedConns,
}
}

869
context.go Normal file
View File

@ -0,0 +1,869 @@
package fiber
import (
"bytes"
"encoding/xml"
"fmt"
"html/template"
"io/ioutil"
"log"
"mime"
"mime/multipart"
"net/url"
"path/filepath"
"strings"
"sync"
"time"
// templates
pug "github.com/Joker/jade"
handlebars "github.com/aymerick/raymond"
mustache "github.com/cbroglie/mustache"
amber "github.com/eknkc/amber"
// core
websocket "github.com/fasthttp/websocket"
jsoniter "github.com/json-iterator/go"
fasthttp "github.com/valyala/fasthttp"
)
// Ctx represents the Context which hold the HTTP request and response.
// It has methods for the request query string, parameters, body, HTTP headers and so on.
// For more information please visit our documentation: https://fiber.wiki/context
type Ctx struct {
app *App
route *Route
next bool
error error
params *[]string
values []string
Fasthttp *fasthttp.RequestCtx
Socket *websocket.Conn
}
// Ctx pool
var poolCtx = sync.Pool{
New: func() interface{} {
return new(Ctx)
},
}
// Acquire Ctx from pool
func acquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
ctx := poolCtx.Get().(*Ctx)
ctx.Fasthttp = fctx
return ctx
}
// Return Ctx to pool
func releaseCtx(ctx *Ctx) {
ctx.route = nil
ctx.next = false
ctx.error = nil
ctx.params = nil
ctx.values = nil
ctx.Fasthttp = nil
ctx.Socket = nil
poolCtx.Put(ctx)
}
// Conn https://godoc.org/github.com/gorilla/websocket#pkg-index
type Conn struct {
params *[]string
values []string
*websocket.Conn
}
// Params : https://fiber.wiki/application#websocket
func (conn *Conn) Params(key string) string {
if conn.params == nil {
return ""
}
for i := 0; i < len(*conn.params); i++ {
if (*conn.params)[i] == key {
return conn.values[i]
}
}
return ""
}
// Conn pool
var poolConn = sync.Pool{
New: func() interface{} {
return new(Conn)
},
}
// Acquire Conn from pool
func acquireConn(fconn *websocket.Conn) *Conn {
conn := poolConn.Get().(*Conn)
conn.Conn = fconn
return conn
}
// Return Conn to pool
func releaseConn(conn *Conn) {
conn.Close()
conn.params = nil
conn.values = nil
conn.Conn = nil
poolConn.Put(conn)
}
// Cookie : struct
type Cookie struct {
Expire int // time.Unix(1578981376, 0)
MaxAge int
Domain string
Path string
HTTPOnly bool
Secure bool
SameSite string
}
// Accepts : https://fiber.wiki/context#accepts
func (ctx *Ctx) Accepts(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAccept)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
mimetype := getType(offer)
// if mimetype != "" {
// mimetype = strings.Split(mimetype, ";")[0]
// } else {
// mimetype = offer
// }
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*/*") {
return offer
}
if strings.HasPrefix(spec, mimetype) {
return offer
}
if strings.Contains(spec, "/*") {
if strings.HasPrefix(spec, strings.Split(mimetype, "/")[0]) {
return offer
}
}
}
}
return ""
}
// AcceptsCharsets : https://fiber.wiki/context#acceptscharsets
func (ctx *Ctx) AcceptsCharsets(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAcceptCharset)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*") {
return offer
}
if strings.HasPrefix(spec, offer) {
return offer
}
}
}
return ""
}
// AcceptsEncodings : https://fiber.wiki/context#acceptsencodings
func (ctx *Ctx) AcceptsEncodings(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAcceptEncoding)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*") {
return offer
}
if strings.HasPrefix(spec, offer) {
return offer
}
}
}
return ""
}
// AcceptsLanguages : https://fiber.wiki/context#acceptslanguages
func (ctx *Ctx) AcceptsLanguages(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAcceptLanguage)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*") {
return offer
}
if strings.HasPrefix(spec, offer) {
return offer
}
}
}
return ""
}
// Append : https://fiber.wiki/context#append
func (ctx *Ctx) Append(field string, values ...string) {
if len(values) == 0 {
return
}
h := getString(ctx.Fasthttp.Response.Header.Peek(field))
for i := range values {
if h == "" {
h += values[i]
} else {
h += ", " + values[i]
}
}
ctx.Set(field, h)
}
// Attachment : https://fiber.wiki/context#attachment
func (ctx *Ctx) Attachment(name ...string) {
if len(name) > 0 {
filename := filepath.Base(name[0])
ctx.Type(filepath.Ext(filename))
ctx.Set(fasthttp.HeaderContentDisposition, `attachment; filename="`+filename+`"`)
return
}
ctx.Set(fasthttp.HeaderContentDisposition, "attachment")
}
// BaseURL : https://fiber.wiki/context#baseurl
func (ctx *Ctx) BaseURL() string {
return ctx.Protocol() + "://" + ctx.Hostname()
}
// Body : https://fiber.wiki/context#body
func (ctx *Ctx) Body(args ...interface{}) string {
if len(args) == 0 {
return getString(ctx.Fasthttp.Request.Body())
}
if len(args) == 1 {
switch arg := args[0].(type) {
case string:
return getString(ctx.Fasthttp.Request.PostArgs().Peek(arg))
case []byte:
return getString(ctx.Fasthttp.Request.PostArgs().PeekBytes(arg))
case func(string, string):
ctx.Fasthttp.Request.PostArgs().VisitAll(func(k []byte, v []byte) {
arg(getString(k), getString(v))
})
default:
return getString(ctx.Fasthttp.Request.Body())
}
}
return ""
}
// BodyParser : https://fiber.wiki/context#bodyparser
func (ctx *Ctx) BodyParser(v interface{}) error {
ctype := getString(ctx.Fasthttp.Request.Header.ContentType())
// application/json
if strings.HasPrefix(ctype, MIMEApplicationJSON) {
return jsoniter.Unmarshal(ctx.Fasthttp.Request.Body(), v)
}
// application/xml text/xml
if strings.HasPrefix(ctype, MIMEApplicationXML) || strings.HasPrefix(ctype, MIMETextXML) {
return xml.Unmarshal(ctx.Fasthttp.Request.Body(), v)
}
// application/x-www-form-urlencoded
if strings.HasPrefix(ctype, MIMEApplicationForm) {
data, err := url.ParseQuery(getString(ctx.Fasthttp.PostBody()))
if err != nil {
return err
}
return schemaDecoder.Decode(v, data)
}
// multipart/form-data
if strings.HasPrefix(ctype, MIMEMultipartForm) {
data, err := ctx.Fasthttp.MultipartForm()
if err != nil {
return err
}
return schemaDecoder.Decode(v, data.Value)
}
return fmt.Errorf("cannot parse content-type: %v", ctype)
}
// ClearCookie : https://fiber.wiki/context#clearcookie
func (ctx *Ctx) ClearCookie(name ...string) {
if len(name) > 0 {
for i := range name {
//ctx.Fasthttp.Request.Header.DelAllCookies()
ctx.Fasthttp.Response.Header.DelClientCookie(name[i])
}
return
}
//ctx.Fasthttp.Response.Header.DelAllCookies()
ctx.Fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) {
ctx.Fasthttp.Response.Header.DelClientCookie(getString(k))
})
}
// Cookie : https://fiber.wiki/context#cookie
func (ctx *Ctx) Cookie(key, value string, options ...interface{}) {
cook := &fasthttp.Cookie{}
cook.SetKey(key)
cook.SetValue(value)
if len(options) > 0 {
switch opt := options[0].(type) {
case *Cookie:
if opt.Expire > 0 {
cook.SetExpire(time.Unix(int64(opt.Expire), 0))
}
if opt.MaxAge > 0 {
cook.SetMaxAge(opt.MaxAge)
}
if opt.Domain != "" {
cook.SetDomain(opt.Domain)
}
if opt.Path != "" {
cook.SetPath(opt.Path)
}
if opt.HTTPOnly {
cook.SetHTTPOnly(opt.HTTPOnly)
}
if opt.Secure {
cook.SetSecure(opt.Secure)
}
if opt.SameSite != "" {
sameSite := fasthttp.CookieSameSiteDefaultMode
if strings.EqualFold(opt.SameSite, "lax") {
sameSite = fasthttp.CookieSameSiteLaxMode
} else if strings.EqualFold(opt.SameSite, "strict") {
sameSite = fasthttp.CookieSameSiteStrictMode
} else if strings.EqualFold(opt.SameSite, "none") {
sameSite = fasthttp.CookieSameSiteNoneMode
}
// } else {
// sameSite = fasthttp.CookieSameSiteDisabled
// }
cook.SetSameSite(sameSite)
}
default:
log.Println("Cookie: Invalid &Cookie{} struct")
}
}
ctx.Fasthttp.Response.Header.SetCookie(cook)
}
// Cookies : https://fiber.wiki/context#cookies
func (ctx *Ctx) Cookies(args ...interface{}) string {
if len(args) == 0 {
return ctx.Get(fasthttp.HeaderCookie)
}
switch arg := args[0].(type) {
case string:
return getString(ctx.Fasthttp.Request.Header.Cookie(arg))
case []byte:
return getString(ctx.Fasthttp.Request.Header.CookieBytes(arg))
case func(string, string):
ctx.Fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) {
arg(getString(k), getString(v))
})
default:
return ctx.Get(fasthttp.HeaderCookie)
}
return ""
}
// Download : https://fiber.wiki/context#download
func (ctx *Ctx) Download(file string, name ...string) {
filename := filepath.Base(file)
if len(name) > 0 {
filename = name[0]
}
ctx.Set(fasthttp.HeaderContentDisposition, "attachment; filename="+filename)
ctx.SendFile(file)
}
// Error returns err that is passed via Next(err)
func (ctx *Ctx) Error() error {
return ctx.error
}
// Format : https://fiber.wiki/context#format
func (ctx *Ctx) Format(args ...interface{}) {
var body string
accept := ctx.Accepts("html", "json")
for i := range args {
switch arg := args[i].(type) {
case string:
body = arg
case []byte:
body = getString(arg)
default:
body = fmt.Sprintf("%v", arg)
}
switch accept {
case "html":
ctx.SendString("<p>" + body + "</p>")
case "json":
if err := ctx.JSON(body); err != nil {
log.Println("Format: error serializing json ", err)
}
default:
ctx.SendString(body)
}
}
}
// FormFile : https://fiber.wiki/context#formfile
func (ctx *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
return ctx.Fasthttp.FormFile(key)
}
// FormValue : https://fiber.wiki/context#formvalue
func (ctx *Ctx) FormValue(key string) string {
return getString(ctx.Fasthttp.FormValue(key))
}
// Fresh : https://fiber.wiki/context#fresh
func (ctx *Ctx) Fresh() bool {
return false
}
// Get : https://fiber.wiki/context#get
func (ctx *Ctx) Get(key string) string {
if key == "referrer" {
key = "referer"
}
return getString(ctx.Fasthttp.Request.Header.Peek(key))
}
// Hostname : https://fiber.wiki/context#hostname
func (ctx *Ctx) Hostname() string {
return getString(ctx.Fasthttp.URI().Host())
}
// IP : https://fiber.wiki/context#Ip
func (ctx *Ctx) IP() string {
return ctx.Fasthttp.RemoteIP().String()
}
// IPs : https://fiber.wiki/context#ips
func (ctx *Ctx) IPs() []string {
ips := strings.Split(ctx.Get(fasthttp.HeaderXForwardedFor), ",")
for i := range ips {
ips[i] = strings.TrimSpace(ips[i])
}
return ips
}
// Is : https://fiber.wiki/context#is
func (ctx *Ctx) Is(ext string) bool {
if ext[0] != '.' {
ext = "." + ext
}
exts, _ := mime.ExtensionsByType(ctx.Get(fasthttp.HeaderContentType))
if len(exts) > 0 {
for _, item := range exts {
if item == ext {
return true
}
}
}
return false
}
// JSON : https://fiber.wiki/context#json
func (ctx *Ctx) JSON(v interface{}) error {
ctx.Fasthttp.Response.Header.SetContentType(MIMEApplicationJSON)
raw, err := jsoniter.Marshal(&v)
if err != nil {
ctx.Fasthttp.Response.SetBodyString("")
return err
}
ctx.Fasthttp.Response.SetBodyString(getString(raw))
return nil
}
// JSONP : https://fiber.wiki/context#jsonp
func (ctx *Ctx) JSONP(v interface{}, cb ...string) error {
raw, err := jsoniter.Marshal(&v)
if err != nil {
return err
}
str := "callback("
if len(cb) > 0 {
str = cb[0] + "("
}
str += getString(raw) + ");"
ctx.Set(fasthttp.HeaderXContentTypeOptions, "nosniff")
ctx.Fasthttp.Response.Header.SetContentType(MIMEApplicationJavaScript)
ctx.Fasthttp.Response.SetBodyString(str)
return nil
}
// Links : https://fiber.wiki/context#links
func (ctx *Ctx) Links(link ...string) {
h := ""
for i, l := range link {
if i%2 == 0 {
h += "<" + l + ">"
} else {
h += `; rel="` + l + `",`
}
}
if len(link) > 0 {
h = strings.TrimSuffix(h, ",")
ctx.Set(fasthttp.HeaderLink, h)
}
}
// Locals : https://fiber.wiki/context#locals
func (ctx *Ctx) Locals(key string, val ...interface{}) interface{} {
if len(val) == 0 {
return ctx.Fasthttp.UserValue(key)
}
ctx.Fasthttp.SetUserValue(key, val[0])
return nil
}
// Location : https://fiber.wiki/context#location
func (ctx *Ctx) Location(path string) {
ctx.Set(fasthttp.HeaderLocation, path)
}
// Method : https://fiber.wiki/context#method
func (ctx *Ctx) Method() string {
return getString(ctx.Fasthttp.Request.Header.Method())
}
// MultipartForm : https://fiber.wiki/context#multipartform
func (ctx *Ctx) MultipartForm() (*multipart.Form, error) {
return ctx.Fasthttp.MultipartForm()
}
// Next : https://fiber.wiki/context#next
func (ctx *Ctx) Next(err ...error) {
ctx.route = nil
ctx.next = true
ctx.params = nil
ctx.values = nil
if len(err) > 0 {
ctx.error = err[0]
}
}
// OriginalURL : https://fiber.wiki/context#originalurl
func (ctx *Ctx) OriginalURL() string {
return getString(ctx.Fasthttp.Request.Header.RequestURI())
}
// Params : https://fiber.wiki/context#params
func (ctx *Ctx) Params(key string) string {
if ctx.params == nil {
return ""
}
for i := 0; i < len(*ctx.params); i++ {
if (*ctx.params)[i] == key {
return ctx.values[i]
}
}
return ""
}
// Path : https://fiber.wiki/context#path
func (ctx *Ctx) Path() string {
return getString(ctx.Fasthttp.URI().Path())
}
// Protocol : https://fiber.wiki/context#protocol
func (ctx *Ctx) Protocol() string {
if ctx.Fasthttp.IsTLS() {
return "https"
}
return "http"
}
// Query : https://fiber.wiki/context#query
func (ctx *Ctx) Query(key string) string {
return getString(ctx.Fasthttp.QueryArgs().Peek(key))
}
// Range : https://fiber.wiki/context#range
func (ctx *Ctx) Range() {
// https://expressjs.com/en/api.html#req.range
// https://github.com/jshttp/range-parser/blob/master/index.js
// r := ctx.Fasthttp.Request.Header.Peek(fasthttp.HeaderRange)
// *magic*
}
// Redirect : https://fiber.wiki/context#redirect
func (ctx *Ctx) Redirect(path string, status ...int) {
code := 302
if len(status) > 0 {
code = status[0]
}
ctx.Set(fasthttp.HeaderLocation, path)
ctx.Fasthttp.Response.SetStatusCode(code)
}
// Render : https://fiber.wiki/context#render
func (ctx *Ctx) Render(file string, data interface{}, e ...string) error {
var err error
var raw []byte
var html string
var engine string
if len(e) > 0 {
engine = e[0]
} else if ctx.app.Settings.ViewEngine != "" {
engine = ctx.app.Settings.ViewEngine
} else {
engine = filepath.Ext(file)[1:]
}
if ctx.app.Settings.ViewFolder != "" {
file = filepath.Join(ctx.app.Settings.ViewFolder, file)
}
if ctx.app.Settings.ViewExtension != "" {
file = file + ctx.app.Settings.ViewExtension
}
if raw, err = ioutil.ReadFile(filepath.Clean(file)); err != nil {
return err
}
switch engine {
case "amber": // https://github.com/eknkc/amber
var buf bytes.Buffer
var tmpl *template.Template
if tmpl, err = amber.Compile(getString(raw), amber.DefaultOptions); err != nil {
return err
}
if err = tmpl.Execute(&buf, data); err != nil {
return err
}
html = buf.String()
case "handlebars": // https://github.com/aymerick/raymond
if html, err = handlebars.Render(getString(raw), data); err != nil {
return err
}
case "mustache": // https://github.com/cbroglie/mustache
if html, err = mustache.Render(getString(raw), data); err != nil {
return err
}
case "pug": // https://github.com/Joker/jade
var parsed string
var buf bytes.Buffer
var tmpl *template.Template
if parsed, err = pug.Parse("", raw); err != nil {
return err
}
if tmpl, err = template.New("").Parse(parsed); err != nil {
return err
}
if err = tmpl.Execute(&buf, data); err != nil {
return err
}
html = buf.String()
default: // https://golang.org/pkg/text/template/
var buf bytes.Buffer
var tmpl *template.Template
if tmpl, err = template.New("").Parse(getString(raw)); err != nil {
return err
}
if err = tmpl.Execute(&buf, data); err != nil {
return err
}
html = buf.String()
}
ctx.Set("Content-Type", "text/html")
ctx.SendString(html)
return err
}
// Route : https://fiber.wiki/context#route
func (ctx *Ctx) Route() *Route {
return ctx.route
}
// SaveFile : https://fiber.wiki/context#secure
func (ctx *Ctx) SaveFile(fh *multipart.FileHeader, path string) error {
return fasthttp.SaveMultipartFile(fh, path)
}
// Secure : https://fiber.wiki/context#secure
func (ctx *Ctx) Secure() bool {
return ctx.Fasthttp.IsTLS()
}
// Send : https://fiber.wiki/context#send
func (ctx *Ctx) Send(args ...interface{}) {
if len(args) == 0 {
return
}
switch body := args[0].(type) {
case string:
ctx.Fasthttp.Response.SetBodyString(body)
case []byte:
ctx.Fasthttp.Response.SetBodyString(getString(body))
default:
ctx.Fasthttp.Response.SetBodyString(fmt.Sprintf("%v", body))
}
}
// SendBytes : https://fiber.wiki/context#sendbytes
func (ctx *Ctx) SendBytes(body []byte) {
ctx.Fasthttp.Response.SetBodyString(getString(body))
}
// SendFile : https://fiber.wiki/context#sendfile
func (ctx *Ctx) SendFile(file string, gzip ...bool) {
// Disable gzipping
if len(gzip) > 0 && !gzip[0] {
fasthttp.ServeFileUncompressed(ctx.Fasthttp, file)
return
}
fasthttp.ServeFile(ctx.Fasthttp, file)
// https://github.com/valyala/fasthttp/blob/master/fs.go#L81
//ctx.Type(filepath.Ext(path))
//ctx.Fasthttp.SendFile(path)
}
// SendStatus : https://fiber.wiki/context#sendstatus
func (ctx *Ctx) SendStatus(status int) {
ctx.Fasthttp.Response.SetStatusCode(status)
// Only set status body when there is no response body
if len(ctx.Fasthttp.Response.Body()) == 0 {
ctx.Fasthttp.Response.SetBodyString(getStatus(status))
}
}
// SendString : https://fiber.wiki/context#sendstring
func (ctx *Ctx) SendString(body string) {
ctx.Fasthttp.Response.SetBodyString(body)
}
// Set : https://fiber.wiki/context#set
func (ctx *Ctx) Set(key string, val string) {
ctx.Fasthttp.Response.Header.SetCanonical(getBytes(key), getBytes(val))
}
// Subdomains : https://fiber.wiki/context#subdomains
func (ctx *Ctx) Subdomains(offset ...int) (subs []string) {
o := 2
if len(offset) > 0 {
o = offset[0]
}
subs = strings.Split(ctx.Hostname(), ".")
subs = subs[:len(subs)-o]
return subs
}
// SignedCookies : https://fiber.wiki/context#signedcookies
func (ctx *Ctx) SignedCookies() {
}
// Stale : https://fiber.wiki/context#stale
func (ctx *Ctx) Stale() bool {
return !ctx.Fresh()
}
// Status : https://fiber.wiki/context#status
func (ctx *Ctx) Status(status int) *Ctx {
ctx.Fasthttp.Response.SetStatusCode(status)
return ctx
}
// Type : https://fiber.wiki/context#type
func (ctx *Ctx) Type(ext string) *Ctx {
ctx.Fasthttp.Response.Header.SetContentType(getType(ext))
return ctx
}
// Vary : https://fiber.wiki/context#vary
func (ctx *Ctx) Vary(fields ...string) {
if len(fields) == 0 {
return
}
h := getString(ctx.Fasthttp.Response.Header.Peek(fasthttp.HeaderVary))
for i := range fields {
if h == "" {
h += fields[i]
} else {
h += ", " + fields[i]
}
}
ctx.Set(fasthttp.HeaderVary, h)
}
// Write : https://fiber.wiki/context#write
func (ctx *Ctx) Write(args ...interface{}) {
for i := range args {
switch body := args[i].(type) {
case string:
ctx.Fasthttp.Response.AppendBodyString(body)
case []byte:
ctx.Fasthttp.Response.AppendBodyString(getString(body))
default:
ctx.Fasthttp.Response.AppendBodyString(fmt.Sprintf("%v", body))
}
}
}
// XHR : https://fiber.wiki/context#xhr
func (ctx *Ctx) XHR() bool {
return ctx.Get(fasthttp.HeaderXRequestedWith) == "XMLHttpRequest"
}

View File

@ -3,6 +3,7 @@ package fiber
import (
"bytes"
"fmt"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
@ -92,11 +93,6 @@ func Test_AcceptsLanguages(t *testing.T) {
if result != expect {
t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result)
}
expect = "*"
result = c.AcceptsLanguages(expect)
if result != expect {
t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result)
}
})
req, _ := http.NewRequest("GET", "/test", nil)
req.Header.Set("Accept-Language", "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5")
@ -111,7 +107,6 @@ func Test_AcceptsLanguages(t *testing.T) {
func Test_BaseURL(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.BaseUrl() // deprecated
expect := "http://google.com"
result := c.BaseURL()
if result != expect {
@ -127,29 +122,6 @@ func Test_BaseURL(t *testing.T) {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
}
func Test_BasicAuth(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
expect1 := "john"
expect2 := "doe"
result1, result2, _ := c.BasicAuth()
if result1 != expect1 {
t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect1, expect1)
}
if result2 != expect2 {
t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), result2, expect2)
}
})
req, _ := http.NewRequest("GET", "/test", nil)
req.SetBasicAuth("john", "doe")
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
}
func Test_Body(t *testing.T) {
app := New()
app.Post("/test", func(c *Ctx) {
@ -345,7 +317,6 @@ func Test_Hostname(t *testing.T) {
func Test_IP(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Ip() // deprecated
expect := "0.0.0.0"
result := c.IP()
if result != expect {
@ -364,7 +335,6 @@ func Test_IP(t *testing.T) {
func Test_IPs(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Ips() // deprecated
expect := []string{"0.0.0.0", "1.1.1.1"}
result := c.IPs()
if result[0] != expect[0] && result[1] != expect[1] {
@ -402,7 +372,6 @@ func Test_IPs(t *testing.T) {
// t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
// }
// }
func Test_Locals(t *testing.T) {
app := New()
app.Use(func(c *Ctx) {
@ -509,7 +478,6 @@ func Test_MultipartForm(t *testing.T) {
func Test_OriginalURL(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.OriginalUrl() // deprecated
expect := "/test?search=demo"
result := c.OriginalURL()
if result != expect {
@ -682,10 +650,6 @@ func Test_Subdomains(t *testing.T) {
if result[0] != expect[0] && result[1] != expect[1] {
t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result)
}
result = c.Subdomains(1)
if result[0] != expect[0] && result[1] != expect[1] {
t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result)
}
})
req, _ := http.NewRequest("GET", "http://john.doe.google.com/test", nil)
resp, err := app.Test(req)
@ -699,7 +663,6 @@ func Test_Subdomains(t *testing.T) {
func Test_XHR(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Xhr() // deprecated
expect := true
result := c.XHR()
if result != expect {
@ -716,3 +679,493 @@ func Test_XHR(t *testing.T) {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
}
func Test_Append(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Append("X-Test", "hel")
c.Append("X-Test", "lo", "world")
})
req, _ := http.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("X-Test") != "hel, lo, world" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "X-Test: hel, lo, world")
}
}
func Test_Attachment(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Attachment()
c.Attachment("./static/img/logo.png")
})
req, _ := http.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Disposition") != `attachment; filename="logo.png"` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `attachment; filename="logo.png"`)
}
if resp.Header.Get("Content-Type") != "image/png" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "image/png")
}
}
func Test_ClearCookie(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.ClearCookie()
})
app.Get("/test2", func(c *Ctx) {
c.ClearCookie("john")
})
req, _ := http.NewRequest("GET", "/test", nil)
req.AddCookie(&http.Cookie{Name: "john", Value: "doe"})
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if !strings.Contains(resp.Header.Get("Set-Cookie"), "expires=") {
t.Fatalf(`%s: Expecting %s`, t.Name(), "expires=")
}
req, _ = http.NewRequest("GET", "/test2", nil)
req.AddCookie(&http.Cookie{Name: "john", Value: "doe"})
resp, err = app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if !strings.Contains(resp.Header.Get("Set-Cookie"), "expires=") {
t.Fatalf(`%s: Expecting %s`, t.Name(), "expires=")
}
}
func Test_Cookie(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
options := &Cookie{
MaxAge: 60,
Domain: "example.com",
Path: "/",
HTTPOnly: true,
Secure: false,
SameSite: "lax",
}
c.Cookie("name", "john", options)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if !strings.Contains(resp.Header.Get("Set-Cookie"), "name=john; max-age=60; domain=example.com; path=/; HttpOnly; SameSite=Lax") {
t.Fatalf(`%s: Expecting %s`, t.Name(), "name=john; max-age=60; domain=example.com; path=/; HttpOnly; SameSite=Lax")
}
}
func Test_Download(t *testing.T) {
// TODO
}
func Test_Format(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Format("Hello, World!")
})
app.Get("/test2", func(c *Ctx) {
c.Format([]byte("Hello, World!"))
c.Format("Hello, World!")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
req.Header.Set("Accept", "text/html")
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != "<p>Hello, World!</p>" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "<p>Hello, World!</p>")
}
req, _ = http.NewRequest("GET", "http://example.com/test2", nil)
req.Header.Set("Accept", "application/json")
resp, err = app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `"Hello, World!"` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `"Hello, World!"`)
}
}
func Test_HeadersSent(t *testing.T) {
// TODO
}
func Test_JSON(t *testing.T) {
type SomeStruct struct {
Name string
Age uint8
}
app := New()
app.Get("/test", func(c *Ctx) {
if err := c.JSON(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
if err := c.JSON(data); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/json" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "application/json")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `{"Name":"Grame","Age":20}` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `{"Name":"Grame","Age":20}`)
}
}
func Test_JSONP(t *testing.T) {
type SomeStruct struct {
Name string
Age uint8
}
app := New()
app.Get("/test", func(c *Ctx) {
if err := c.JSONP(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
if err := c.JSONP(data, "alwaysjohn"); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/javascript" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "application/javascript")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `alwaysjohn({"Name":"Grame","Age":20});` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `alwaysjohn({"Name":"Grame","Age":20});`)
}
}
func Test_Links(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Links(
"http://api.example.com/users?page=2", "next",
"http://api.example.com/users?page=5", "last",
)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Link") != `<http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Link: <http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"`)
}
}
func Test_Location(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Location("http://example.com")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Location") != "http://example.com" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "http://example.com")
}
}
func Test_Next(t *testing.T) {
app := New()
app.Use("/", func(c *Ctx) {
c.Next()
})
app.Get("/test", func(c *Ctx) {
c.Set("X-Next-Result", "Works")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("X-Next-Result") != "Works" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "X-Next-Results: Works")
}
}
func Test_Redirect(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Redirect("http://example.com", 301)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 301 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Location") != "http://example.com" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "Location: http://example.com")
}
}
func Test_Render(t *testing.T) {
// TODO
}
func Test_Send(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Send([]byte("Hello, World"))
c.Send("Don't crash please")
c.Send(1337)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `1337` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `1337`)
}
}
func Test_SendBytes(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.SendBytes([]byte("Hello, World"))
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Hello, World` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Hello, World`)
}
}
func Test_SendStatus(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.SendStatus(415)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 415 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Unsupported Media Type` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Unsupported Media Type`)
}
}
func Test_SendString(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.SendString("Don't crash please")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Don't crash please` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Don't crash please`)
}
}
func Test_Set(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Set("X-1", "1")
c.Set("X-2", "2")
c.Set("X-3", "3")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("X-1") != "1" {
t.Fatalf(`%s: Expected %v`, t.Name(), "X-1: 1")
}
if resp.Header.Get("X-2") != "2" {
t.Fatalf(`%s: Expected %v`, t.Name(), "X-2: 2")
}
if resp.Header.Get("X-3") != "3" {
t.Fatalf(`%s: Expected %v`, t.Name(), "X-3: 3")
}
}
func Test_Status(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Status(400)
c.Status(415).Send("Hello, World")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 415 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Hello, World` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Hello, World`)
}
}
func Test_Type(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Type(".json")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/json" {
t.Fatalf(`%s: Expected %v`, t.Name(), `Content-Type: application/json`)
}
}
func Test_Vary(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Vary("Origin")
c.Vary("User-Agent")
c.Vary("Accept-Encoding", "Accept")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Vary") != "Origin, User-Agent, Accept-Encoding, Accept" {
t.Fatalf(`%s: Expected %v`, t.Name(), `Vary: Origin, User-Agent, Accept-Encoding, Accept`)
}
}
func Test_Write(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Write("Hello, ")
c.Write([]byte("World! "))
c.Write(123)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Hello, World! 123` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Hello, World! 123`)
}
}

8
go.mod
View File

@ -1,15 +1,15 @@
module github.com/gofiber/fiber
go 1.13
go 1.11
require (
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet v2.1.2+incompatible
github.com/Joker/jade v1.0.0
github.com/aymerick/raymond v2.0.2+incompatible
github.com/cbroglie/mustache v1.0.1
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385
github.com/fasthttp/websocket v1.4.1
github.com/gorilla/schema v1.1.0
github.com/json-iterator/go v1.1.9
github.com/valyala/fasthttp v1.9.0
github.com/yosssi/ace v0.0.5
gopkg.in/yaml.v2 v2.2.8 // indirect
)

21
go.sum
View File

@ -1,7 +1,6 @@
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet v2.1.2+incompatible h1:ybZoYzMBdoijK6I+Ke3vg9GZsmlKo/ZhKdNMWz0P26c=
github.com/CloudyKit/jet v2.1.2+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
github.com/Joker/hpp v0.0.0-20180418125244-6893e659854a/go.mod h1:MzD2WMdSxvbHw5fM/OXOFily/lipJWRc9C1px0Mt0ZE=
github.com/Joker/jade v1.0.0 h1:lOCEPvTAtWfLpSZYMOv/g44MGQFAolbKh2khHHGu0Kc=
github.com/Joker/jade v1.0.0/go.mod h1:efZIdO0py/LtcJRSa/j2WEklMSAw84WV0zZVMxNToB8=
github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/cbroglie/mustache v1.0.1 h1:ivMg8MguXq/rrz2eu3tw6g3b16+PQhoTn6EZAhst2mw=
@ -10,13 +9,17 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/fasthttp/websocket v1.4.1 h1:fisgNMCNCbIPM5GRRRTAckRrynbSzf76fevcJYJYnSM=
github.com/fasthttp/websocket v1.4.1/go.mod h1:toetUvZ3KISxtZERe0wzPPpnaN8GZCKHCowWctwA50o=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@ -24,17 +27,21 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/savsgio/gotils v0.0.0-20190714152828-365999d0a274 h1:F52t1X2ziOrMcQMVHo8ZxwOrDTMAq6MrlKtL1Atu2wU=
github.com/savsgio/gotils v0.0.0-20190714152828-365999d0a274/go.mod h1:w803/Fg1m0hrp1ZT9KNfQe4E4+WOMMFLcgzPvOcye10=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.4.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw=
github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

121
group.go Normal file
View File

@ -0,0 +1,121 @@
package fiber
import "strings"
// Group ...
type Group struct {
prefix string
app *App
}
// Group ...
func (app *App) Group(prefix string, args ...interface{}) *Group {
if len(args) > 0 {
app.register("USE", prefix, args...)
}
return &Group{
prefix: prefix,
app: app,
}
}
// Group ...
func (grp *Group) Group(newPrfx string, args ...interface{}) *Group {
var prefix = grp.prefix
if len(newPrfx) > 0 && newPrfx[0] != '/' && newPrfx[0] != '*' {
newPrfx = "/" + newPrfx
}
// When grouping, always remove single slash
if len(prefix) > 0 && newPrfx == "/" {
newPrfx = ""
}
// Prepent group prefix if exist
prefix = prefix + newPrfx
// Clean path by removing double "//" => "/"
prefix = strings.Replace(prefix, "//", "/", -1)
if len(args) > 0 {
grp.app.register("USE", prefix, args...)
}
return &Group{
prefix: prefix,
app: grp.app,
}
}
// Static ...
func (grp *Group) Static(args ...string) *Group {
grp.app.registerStatic(grp.prefix, args...)
return grp
}
// WebSocket ...
func (grp *Group) WebSocket(args ...interface{}) *Group {
grp.app.register("GET", grp.prefix, args...)
return grp
}
// Connect ...
func (grp *Group) Connect(args ...interface{}) *Group {
grp.app.register("CONNECT", grp.prefix, args...)
return grp
}
// Put ...
func (grp *Group) Put(args ...interface{}) *Group {
grp.app.register("PUT", grp.prefix, args...)
return grp
}
// Post ...
func (grp *Group) Post(args ...interface{}) *Group {
grp.app.register("POST", grp.prefix, args...)
return grp
}
// Delete ...
func (grp *Group) Delete(args ...interface{}) *Group {
grp.app.register("DELETE", grp.prefix, args...)
return grp
}
// Head ...
func (grp *Group) Head(args ...interface{}) *Group {
grp.app.register("HEAD", grp.prefix, args...)
return grp
}
// Patch ...
func (grp *Group) Patch(args ...interface{}) *Group {
grp.app.register("PATCH", grp.prefix, args...)
return grp
}
// Options ...
func (grp *Group) Options(args ...interface{}) *Group {
grp.app.register("OPTIONS", grp.prefix, args...)
return grp
}
// Trace ...
func (grp *Group) Trace(args ...interface{}) *Group {
grp.app.register("TRACE", grp.prefix, args...)
return grp
}
// Get ...
func (grp *Group) Get(args ...interface{}) *Group {
grp.app.register("GET", grp.prefix, args...)
return grp
}
// All ...
func (grp *Group) All(args ...interface{}) *Group {
grp.app.register("ALL", grp.prefix, args...)
return grp
}
// Use ...
func (grp *Group) Use(args ...interface{}) *Group {
grp.app.register("USE", grp.prefix, args...)
return grp
}

View File

@ -1,442 +0,0 @@
// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
// 📌 Please open an issue if you got suggestions or found a bug!
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
// 🦸 Not all heroes wear capes, thank you to some amazing people
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
package fiber
import (
"encoding/base64"
"encoding/xml"
"fmt"
"mime"
"mime/multipart"
"net/url"
"strings"
jsoniter "github.com/json-iterator/go"
fasthttp "github.com/valyala/fasthttp"
)
// Accepts : https://fiber.wiki/context#accepts
func (ctx *Ctx) Accepts(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAccept)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
mimetype := getType(offer)
// if mimetype != "" {
// mimetype = strings.Split(mimetype, ";")[0]
// } else {
// mimetype = offer
// }
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*/*") {
return offer
}
if strings.HasPrefix(spec, mimetype) {
return offer
}
if strings.Contains(spec, "/*") {
if strings.HasPrefix(spec, strings.Split(mimetype, "/")[0]) {
return offer
}
}
}
}
return ""
}
// AcceptsCharsets : https://fiber.wiki/context#acceptscharsets
func (ctx *Ctx) AcceptsCharsets(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAcceptCharset)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*") {
return offer
}
if strings.HasPrefix(spec, offer) {
return offer
}
}
}
return ""
}
// AcceptsEncodings : https://fiber.wiki/context#acceptsencodings
func (ctx *Ctx) AcceptsEncodings(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAcceptEncoding)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*") {
return offer
}
if strings.HasPrefix(spec, offer) {
return offer
}
}
}
return ""
}
// AcceptsLanguages : https://fiber.wiki/context#acceptslanguages
func (ctx *Ctx) AcceptsLanguages(offers ...string) string {
if len(offers) == 0 {
return ""
}
h := ctx.Get(fasthttp.HeaderAcceptLanguage)
if h == "" {
return offers[0]
}
specs := strings.Split(h, ",")
for _, offer := range offers {
for _, spec := range specs {
spec = strings.TrimSpace(spec)
if strings.HasPrefix(spec, "*") {
return offer
}
if strings.HasPrefix(spec, offer) {
return offer
}
}
}
return ""
}
// BaseUrl will be removed in v2
func (ctx *Ctx) BaseUrl() string {
fmt.Println("Fiber deprecated c.BaseUrl(), this will be removed in v2: Use c.BaseURL() instead")
return ctx.BaseURL()
}
// BaseURL : https://fiber.wiki/context#baseurl
func (ctx *Ctx) BaseURL() string {
return ctx.Protocol() + "://" + ctx.Hostname()
}
// BasicAuth : https://fiber.wiki/context#basicauth
func (ctx *Ctx) BasicAuth() (user, pass string, ok bool) {
fmt.Println("Fiber deprecated c.BasicAuth(), this will be removed in v2 and be available as a separate middleware")
auth := ctx.Get(fasthttp.HeaderAuthorization)
if auth == "" {
return
}
const prefix = "Basic "
// Case insensitive prefix match.
if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
return
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return
}
cs := getString(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
// Body : https://fiber.wiki/context#body
func (ctx *Ctx) Body(args ...interface{}) string {
if len(args) == 0 {
return getString(ctx.Fasthttp.Request.Body())
}
if len(args) == 1 {
switch arg := args[0].(type) {
case string:
return getString(ctx.Fasthttp.Request.PostArgs().Peek(arg))
case []byte:
return getString(ctx.Fasthttp.Request.PostArgs().PeekBytes(arg))
case func(string, string):
ctx.Fasthttp.Request.PostArgs().VisitAll(func(k []byte, v []byte) {
arg(getString(k), getString(v))
})
default:
return getString(ctx.Fasthttp.Request.Body())
}
}
return ""
}
// BodyParser : https://fiber.wiki/context#bodyparser
func (ctx *Ctx) BodyParser(v interface{}) error {
ctype := getString(ctx.Fasthttp.Request.Header.ContentType())
// application/json
if strings.HasPrefix(ctype, mimeApplicationJSON) {
return jsoniter.Unmarshal(ctx.Fasthttp.Request.Body(), v)
}
// application/xml text/xml
if strings.HasPrefix(ctype, mimeApplicationXML) || strings.HasPrefix(ctype, mimeTextXML) {
return xml.Unmarshal(ctx.Fasthttp.Request.Body(), v)
}
// application/x-www-form-urlencoded
if strings.HasPrefix(ctype, mimeApplicationForm) {
data, err := url.ParseQuery(getString(ctx.Fasthttp.PostBody()))
if err != nil {
return err
}
return schemaDecoder.Decode(v, data)
}
// multipart/form-data
if strings.HasPrefix(ctype, mimeMultipartForm) {
data, err := ctx.Fasthttp.MultipartForm()
if err != nil {
return err
}
return schemaDecoder.Decode(v, data.Value)
}
return fmt.Errorf("cannot parse content-type: %v", ctype)
}
// Cookies : https://fiber.wiki/context#cookies
func (ctx *Ctx) Cookies(args ...interface{}) string {
if len(args) == 0 {
return ctx.Get(fasthttp.HeaderCookie)
}
switch arg := args[0].(type) {
case string:
return getString(ctx.Fasthttp.Request.Header.Cookie(arg))
case []byte:
return getString(ctx.Fasthttp.Request.Header.CookieBytes(arg))
case func(string, string):
ctx.Fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) {
arg(getString(k), getString(v))
})
default:
return ctx.Get(fasthttp.HeaderCookie)
}
return ""
}
// Error returns err that is passed via Next(err)
func (ctx *Ctx) Error() error {
return ctx.error
}
// FormFile : https://fiber.wiki/context#formfile
func (ctx *Ctx) FormFile(key string) (*multipart.FileHeader, error) {
return ctx.Fasthttp.FormFile(key)
}
// FormValue : https://fiber.wiki/context#formvalue
func (ctx *Ctx) FormValue(key string) string {
return getString(ctx.Fasthttp.FormValue(key))
}
// Fresh : https://fiber.wiki/context#fresh
func (ctx *Ctx) Fresh() bool {
return false
}
// Get : https://fiber.wiki/context#get
func (ctx *Ctx) Get(key string) string {
if key == "referrer" {
key = "referer"
}
return getString(ctx.Fasthttp.Request.Header.Peek(key))
}
// Hostname : https://fiber.wiki/context#hostname
func (ctx *Ctx) Hostname() string {
return getString(ctx.Fasthttp.URI().Host())
}
// Ip will be removed in v2
func (ctx *Ctx) Ip() string {
fmt.Println("Fiber deprecated c.Ip(), this will be removed in v2: Use c.IP() instead")
return ctx.IP()
}
// IP : https://fiber.wiki/context#Ip
func (ctx *Ctx) IP() string {
return ctx.Fasthttp.RemoteIP().String()
}
// Ips will be removed in v2
func (ctx *Ctx) Ips() []string { // NOLINT
fmt.Println("Fiber deprecated c.Ips(), this will be removed in v2: Use c.IPs() instead")
return ctx.IPs()
}
// IPs : https://fiber.wiki/context#ips
func (ctx *Ctx) IPs() []string {
ips := strings.Split(ctx.Get(fasthttp.HeaderXForwardedFor), ",")
for i := range ips {
ips[i] = strings.TrimSpace(ips[i])
}
return ips
}
// Is : https://fiber.wiki/context#is
func (ctx *Ctx) Is(ext string) bool {
if ext[0] != '.' {
ext = "." + ext
}
exts, _ := mime.ExtensionsByType(ctx.Get(fasthttp.HeaderContentType))
if len(exts) > 0 {
for _, item := range exts {
if item == ext {
return true
}
}
}
return false
}
// Locals : https://fiber.wiki/context#locals
func (ctx *Ctx) Locals(key string, val ...interface{}) interface{} {
if len(val) == 0 {
return ctx.Fasthttp.UserValue(key)
}
ctx.Fasthttp.SetUserValue(key, val[0])
return nil
}
// Method : https://fiber.wiki/context#method
func (ctx *Ctx) Method() string {
return getString(ctx.Fasthttp.Request.Header.Method())
}
// MultipartForm : https://fiber.wiki/context#multipartform
func (ctx *Ctx) MultipartForm() (*multipart.Form, error) {
return ctx.Fasthttp.MultipartForm()
}
// OriginalUrl will be removed in v2
func (ctx *Ctx) OriginalUrl() string {
fmt.Println("Fiber deprecated c.OriginalUrl(), this will be removed in v2: Use c.OriginalURL() instead")
return ctx.OriginalURL()
}
// OriginalURL : https://fiber.wiki/context#originalurl
func (ctx *Ctx) OriginalURL() string {
return getString(ctx.Fasthttp.Request.Header.RequestURI())
}
// Params : https://fiber.wiki/context#params
func (ctx *Ctx) Params(key string) string {
for i := 0; i < len(*ctx.params); i++ {
if (*ctx.params)[i] == key {
return ctx.values[i]
}
}
return ""
}
// Path : https://fiber.wiki/context#path
func (ctx *Ctx) Path() string {
return getString(ctx.Fasthttp.URI().Path())
}
// Protocol : https://fiber.wiki/context#protocol
func (ctx *Ctx) Protocol() string {
if ctx.Fasthttp.IsTLS() {
return "https"
}
return "http"
}
// Query : https://fiber.wiki/context#query
func (ctx *Ctx) Query(key string) string {
return getString(ctx.Fasthttp.QueryArgs().Peek(key))
}
// Range : https://fiber.wiki/context#range
func (ctx *Ctx) Range() {
// https://expressjs.com/en/api.html#req.range
// https://github.com/jshttp/range-parser/blob/master/index.js
// r := ctx.Fasthttp.Request.Header.Peek(fasthttp.HeaderRange)
// *magic*
}
// Route : https://fiber.wiki/context#route
func (ctx *Ctx) Route() *Route {
return ctx.route
}
// SaveFile : https://fiber.wiki/context#secure
func (ctx *Ctx) SaveFile(fh *multipart.FileHeader, path string) error {
return fasthttp.SaveMultipartFile(fh, path)
}
// Secure : https://fiber.wiki/context#secure
func (ctx *Ctx) Secure() bool {
return ctx.Fasthttp.IsTLS()
}
// SignedCookies : https://fiber.wiki/context#signedcookies
func (ctx *Ctx) SignedCookies() {
}
// Stale : https://fiber.wiki/context#stale
func (ctx *Ctx) Stale() bool {
return !ctx.Fresh()
}
// Subdomains : https://fiber.wiki/context#subdomains
func (ctx *Ctx) Subdomains(offset ...int) (subs []string) {
o := 2
if len(offset) > 0 {
o = offset[0]
}
subs = strings.Split(ctx.Hostname(), ".")
subs = subs[:len(subs)-o]
return subs
}
// Xhr will be removed in v2
func (ctx *Ctx) Xhr() bool {
fmt.Println("Fiber deprecated c.Xhr(), this will be removed in v2: Use c.XHR() instead")
return ctx.XHR()
}
// XHR : https://fiber.wiki/context#xhr
func (ctx *Ctx) XHR() bool {
return ctx.Get(fasthttp.HeaderXRequestedWith) == "XMLHttpRequest"
}

View File

@ -1,483 +0,0 @@
// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
// 📌 Please open an issue if you got suggestions or found a bug!
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
// 🦸 Not all heroes wear capes, thank you to some amazing people
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
package fiber
import (
"bytes"
"encoding/xml"
"fmt"
"html/template"
"io/ioutil"
"log"
"path/filepath"
"strings"
"time"
"github.com/CloudyKit/jet"
"github.com/aymerick/raymond"
"github.com/cbroglie/mustache"
"github.com/eknkc/amber"
jsoniter "github.com/json-iterator/go"
fasthttp "github.com/valyala/fasthttp"
"github.com/yosssi/ace"
)
// Cookie : struct
type Cookie struct {
Expire int // time.Unix(1578981376, 0)
MaxAge int
Domain string
Path string
HTTPOnly bool
Secure bool
SameSite string
}
// Append : https://fiber.wiki/context#append
func (ctx *Ctx) Append(field string, values ...string) {
if len(values) == 0 {
return
}
h := getString(ctx.Fasthttp.Response.Header.Peek(field))
for i := range values {
if h == "" {
h += values[i]
} else {
h += ", " + values[i]
}
}
ctx.Set(field, h)
}
// Attachment : https://fiber.wiki/context#attachment
func (ctx *Ctx) Attachment(name ...string) {
if len(name) > 0 {
filename := filepath.Base(name[0])
ctx.Type(filepath.Ext(filename))
ctx.Set(fasthttp.HeaderContentDisposition, `attachment; filename="`+filename+`"`)
return
}
ctx.Set(fasthttp.HeaderContentDisposition, "attachment")
}
// ClearCookie : https://fiber.wiki/context#clearcookie
func (ctx *Ctx) ClearCookie(name ...string) {
if len(name) > 0 {
for i := range name {
//ctx.Fasthttp.Request.Header.DelAllCookies()
ctx.Fasthttp.Response.Header.DelClientCookie(name[i])
}
return
}
//ctx.Fasthttp.Response.Header.DelAllCookies()
ctx.Fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) {
ctx.Fasthttp.Response.Header.DelClientCookie(getString(k))
})
}
// Cookie : https://fiber.wiki/context#cookie
func (ctx *Ctx) Cookie(key, value string, options ...interface{}) {
cook := &fasthttp.Cookie{}
cook.SetKey(key)
cook.SetValue(value)
if len(options) > 0 {
switch opt := options[0].(type) {
case *Cookie:
if opt.Expire > 0 {
cook.SetExpire(time.Unix(int64(opt.Expire), 0))
}
if opt.MaxAge > 0 {
cook.SetMaxAge(opt.MaxAge)
}
if opt.Domain != "" {
cook.SetDomain(opt.Domain)
}
if opt.Path != "" {
cook.SetPath(opt.Path)
}
if opt.HTTPOnly {
cook.SetHTTPOnly(opt.HTTPOnly)
}
if opt.Secure {
cook.SetSecure(opt.Secure)
}
if opt.SameSite != "" {
sameSite := fasthttp.CookieSameSiteDefaultMode
if strings.EqualFold(opt.SameSite, "lax") {
sameSite = fasthttp.CookieSameSiteLaxMode
} else if strings.EqualFold(opt.SameSite, "strict") {
sameSite = fasthttp.CookieSameSiteStrictMode
} else if strings.EqualFold(opt.SameSite, "none") {
sameSite = fasthttp.CookieSameSiteNoneMode
}
// } else {
// sameSite = fasthttp.CookieSameSiteDisabled
// }
cook.SetSameSite(sameSite)
}
default:
log.Println("Cookie: Invalid &Cookie{} struct")
}
}
ctx.Fasthttp.Response.Header.SetCookie(cook)
}
// Download : https://fiber.wiki/context#download
func (ctx *Ctx) Download(file string, name ...string) {
filename := filepath.Base(file)
if len(name) > 0 {
filename = name[0]
}
ctx.Set(fasthttp.HeaderContentDisposition, "attachment; filename="+filename)
ctx.SendFile(file)
}
// End : https://fiber.wiki/context#end
func (ctx *Ctx) End() {
}
// Format : https://fiber.wiki/context#format
func (ctx *Ctx) Format(args ...interface{}) {
var body string
accept := ctx.Accepts("html", "json")
for i := range args {
switch arg := args[i].(type) {
case string:
body = arg
case []byte:
body = getString(arg)
default:
body = fmt.Sprintf("%v", arg)
}
switch accept {
case "html":
ctx.SendString("<p>" + body + "</p>")
case "json":
if err := ctx.JSON(body); err != nil {
log.Println("Format: error serializing json ", err)
}
default:
ctx.SendString(body)
}
}
}
// HeadersSent indicates if the app sent HTTP headers for the response.
// func (ctx *Ctx) HeadersSent() {}
// Json will be removed in v2
func (ctx *Ctx) Json(v interface{}) error {
fmt.Println("Fiber deprecated c.Json(), this will be removed in v2: Use c.JSON() instead")
return ctx.JSON(v)
}
// JSON : https://fiber.wiki/context#json
func (ctx *Ctx) JSON(v interface{}) error {
ctx.Fasthttp.Response.Header.SetContentType(mimeApplicationJSON)
raw, err := jsoniter.Marshal(&v)
if err != nil {
ctx.Fasthttp.Response.SetBodyString("")
return err
}
ctx.Fasthttp.Response.SetBodyString(getString(raw))
return nil
}
// JsonBytes ...
func (ctx *Ctx) JsonBytes(raw []byte) {
ctx.JSONBytes(raw)
}
// JSONBytes will be removed in v2
func (ctx *Ctx) JSONBytes(raw []byte) {
fmt.Println("Fiber deprecated c.JSONBytes(), this will function be removed in v2")
ctx.Fasthttp.Response.Header.SetContentType(mimeApplicationJSON)
ctx.Fasthttp.Response.SetBodyString(getString(raw))
}
// Jsonp will be removed in v2
func (ctx *Ctx) Jsonp(v interface{}, cb ...string) error {
fmt.Println("Fiber deprecated c.Jsonp(), this will be removed in v2: Use c.JSONP() instead")
return ctx.JSONP(v, cb...)
}
// JSONP : https://fiber.wiki/context#jsonp
func (ctx *Ctx) JSONP(v interface{}, cb ...string) error {
raw, err := jsoniter.Marshal(&v)
if err != nil {
return err
}
str := "callback("
if len(cb) > 0 {
str = cb[0] + "("
}
str += getString(raw) + ");"
ctx.Set(fasthttp.HeaderXContentTypeOptions, "nosniff")
ctx.Fasthttp.Response.Header.SetContentType(mimeApplicationJavascript)
ctx.Fasthttp.Response.SetBodyString(str)
return nil
}
// JsonString ...
func (ctx *Ctx) JsonString(raw string) {
ctx.JSONString(raw)
}
// JSONString will be removed in v2
func (ctx *Ctx) JSONString(raw string) {
fmt.Println("Fiber deprecated c.JSONString(), this function will be removed in v2")
ctx.Fasthttp.Response.Header.SetContentType(mimeApplicationJSON)
ctx.Fasthttp.Response.SetBodyString(raw)
}
// Links : https://fiber.wiki/context#links
func (ctx *Ctx) Links(link ...string) {
h := ""
for i, l := range link {
if i%2 == 0 {
h += "<" + l + ">"
} else {
h += `; rel="` + l + `",`
}
}
if len(link) > 0 {
h = strings.TrimSuffix(h, ",")
ctx.Set(fasthttp.HeaderLink, h)
}
}
// Location : https://fiber.wiki/context#location
func (ctx *Ctx) Location(path string) {
ctx.Set(fasthttp.HeaderLocation, path)
}
// Next : https://fiber.wiki/context#next
func (ctx *Ctx) Next(err ...error) {
ctx.route = nil
ctx.next = true
ctx.params = nil
ctx.values = nil
if len(err) > 0 {
ctx.error = err[0]
}
}
// Redirect : https://fiber.wiki/context#redirect
func (ctx *Ctx) Redirect(path string, status ...int) {
code := 302
if len(status) > 0 {
code = status[0]
}
ctx.Set(fasthttp.HeaderLocation, path)
ctx.Fasthttp.Response.SetStatusCode(code)
}
// Render : https://fiber.wiki/context#render
func (ctx *Ctx) Render(file string, v ...interface{}) error {
var err error
var raw []byte
var html string
var data interface{}
var tmpl *template.Template
if len(v) > 0 {
data = v[0]
}
if raw, err = ioutil.ReadFile(file); err != nil {
return err
}
engine := filepath.Ext(file)
switch engine {
case ".template": // https://golang.org/pkg/text/template/
if tmpl, err = template.New("test").Parse(getString(raw)); err != nil {
return err
}
var buf bytes.Buffer
if err = tmpl.Execute(&buf, data); err != nil {
return err
}
html = buf.String()
case ".ace": // https://github.com/yosssi/ace
if tmpl, err = ace.Load(strings.TrimSuffix(file, filepath.Ext(file)), "", nil); err != nil {
return err
}
var buf bytes.Buffer
if err = tmpl.Execute(&buf, data); err != nil {
return err
}
html = buf.String()
case ".amber": // https://github.com/eknkc/amber
if tmpl, err = amber.Compile(getString(raw), amber.DefaultOptions); err != nil {
return err
}
var buf bytes.Buffer
if err = tmpl.Execute(&buf, data); err != nil {
return err
}
html = buf.String()
case ".jet": // https://github.com/CloudyKit/jet
d, f := filepath.Split(file)
var jetview = jet.NewHTMLSet(d)
var t *jet.Template
if t, err = jetview.GetTemplate(f); err != nil {
return err
}
var buf bytes.Buffer
if err = t.Execute(&buf, make(jet.VarMap), data); err != nil {
return err
}
html = buf.String()
case ".mustache": // https://github.com/hoisie/mustache
if html, err = mustache.Render(getString(raw), data); err != nil {
return err
}
case ".raymond": // https://github.com/aymerick/raymond
if html, err = raymond.Render(getString(raw), data); err != nil {
return err
}
default:
err = fmt.Errorf("render: does not support the %s extension", engine)
}
ctx.Set("Content-Type", "text/html")
ctx.SendString(html)
return err
}
// Send : https://fiber.wiki/context#send
func (ctx *Ctx) Send(args ...interface{}) {
if len(args) == 0 {
return
}
switch body := args[0].(type) {
case string:
ctx.Fasthttp.Response.SetBodyString(body)
case []byte:
ctx.Fasthttp.Response.SetBodyString(getString(body))
default:
ctx.Fasthttp.Response.SetBodyString(fmt.Sprintf("%v", body))
}
}
// SendBytes : https://fiber.wiki/context#sendbytes
func (ctx *Ctx) SendBytes(body []byte) {
ctx.Fasthttp.Response.SetBodyString(getString(body))
}
// SendFile : https://fiber.wiki/context#sendfile
func (ctx *Ctx) SendFile(file string, gzip ...bool) {
// Disable gzipping
if len(gzip) > 0 && !gzip[0] {
fasthttp.ServeFileUncompressed(ctx.Fasthttp, file)
return
}
fasthttp.ServeFile(ctx.Fasthttp, file)
// https://github.com/valyala/fasthttp/blob/master/fs.go#L81
//ctx.Type(filepath.Ext(path))
//ctx.Fasthttp.SendFile(path)
}
// SendStatus : https://fiber.wiki/context#sendstatus
func (ctx *Ctx) SendStatus(status int) {
ctx.Fasthttp.Response.SetStatusCode(status)
// Only set status body when there is no response body
if len(ctx.Fasthttp.Response.Body()) == 0 {
msg := getStatus(status)
if msg != "" {
ctx.Fasthttp.Response.SetBodyString(msg)
}
}
}
// SendString : https://fiber.wiki/context#sendstring
func (ctx *Ctx) SendString(body string) {
ctx.Fasthttp.Response.SetBodyString(body)
}
// Set : https://fiber.wiki/context#set
func (ctx *Ctx) Set(key string, val string) {
ctx.Fasthttp.Response.Header.SetCanonical(getBytes(key), getBytes(val))
}
// Status : https://fiber.wiki/context#status
func (ctx *Ctx) Status(status int) *Ctx {
ctx.Fasthttp.Response.SetStatusCode(status)
return ctx
}
// Type : https://fiber.wiki/context#type
func (ctx *Ctx) Type(ext string) *Ctx {
ctx.Fasthttp.Response.Header.SetContentType(getType(ext))
return ctx
}
// Vary : https://fiber.wiki/context#vary
func (ctx *Ctx) Vary(fields ...string) {
if len(fields) == 0 {
return
}
h := getString(ctx.Fasthttp.Response.Header.Peek(fasthttp.HeaderVary))
for i := range fields {
if h == "" {
h += fields[i]
} else {
h += ", " + fields[i]
}
}
ctx.Set(fasthttp.HeaderVary, h)
}
// Write : https://fiber.wiki/context#write
func (ctx *Ctx) Write(args ...interface{}) {
for i := range args {
switch body := args[i].(type) {
case string:
ctx.Fasthttp.Response.AppendBodyString(body)
case []byte:
ctx.Fasthttp.Response.AppendBodyString(getString(body))
default:
ctx.Fasthttp.Response.AppendBodyString(fmt.Sprintf("%v", body))
}
}
}
// Xml ...
func (ctx *Ctx) Xml(v interface{}) error {
return ctx.XML(v)
}
// XML will be removed in v2
func (ctx *Ctx) XML(v interface{}) error {
fmt.Println("Fiber deprecated c.XML(), this function will be removed in v2")
raw, err := xml.Marshal(v)
if err != nil {
return err
}
ctx.Fasthttp.Response.Header.SetContentType(mimeApplicationXML)
ctx.Fasthttp.Response.SetBody(raw)
return nil
}

View File

@ -1,590 +0,0 @@
package fiber
import (
"io/ioutil"
"net/http"
"strings"
"testing"
)
func Test_Append(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Append("X-Test", "hel")
c.Append("X-Test", "lo", "world")
})
req, _ := http.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("X-Test") != "hel, lo, world" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "X-Test: hel, lo, world")
}
}
func Test_Attachment(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Attachment()
c.Attachment("./static/img/logo.png")
})
req, _ := http.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Disposition") != `attachment; filename="logo.png"` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `attachment; filename="logo.png"`)
}
if resp.Header.Get("Content-Type") != "image/png" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "image/png")
}
}
func Test_ClearCookie(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.ClearCookie()
})
app.Get("/test2", func(c *Ctx) {
c.ClearCookie("john")
})
req, _ := http.NewRequest("GET", "/test", nil)
req.AddCookie(&http.Cookie{Name: "john", Value: "doe"})
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if !strings.Contains(resp.Header.Get("Set-Cookie"), "expires=") {
t.Fatalf(`%s: Expecting %s`, t.Name(), "expires=")
}
req, _ = http.NewRequest("GET", "/test2", nil)
req.AddCookie(&http.Cookie{Name: "john", Value: "doe"})
resp, err = app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if !strings.Contains(resp.Header.Get("Set-Cookie"), "expires=") {
t.Fatalf(`%s: Expecting %s`, t.Name(), "expires=")
}
}
func Test_Cookie(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
options := &Cookie{
MaxAge: 60,
Domain: "example.com",
Path: "/",
HTTPOnly: true,
Secure: false,
SameSite: "lax",
}
c.Cookie("name", "john", options)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if !strings.Contains(resp.Header.Get("Set-Cookie"), "name=john; max-age=60; domain=example.com; path=/; HttpOnly; SameSite=Lax") {
t.Fatalf(`%s: Expecting %s`, t.Name(), "name=john; max-age=60; domain=example.com; path=/; HttpOnly; SameSite=Lax")
}
}
func Test_Download(t *testing.T) {
// TODO
}
func Test_Format(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Format("Hello, World!")
})
app.Get("/test2", func(c *Ctx) {
c.Format([]byte("Hello, World!"))
c.Format("Hello, World!")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
req.Header.Set("Accept", "text/html")
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != "<p>Hello, World!</p>" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "<p>Hello, World!</p>")
}
req, _ = http.NewRequest("GET", "http://example.com/test2", nil)
req.Header.Set("Accept", "application/json")
resp, err = app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `"Hello, World!"` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `"Hello, World!"`)
}
}
func Test_HeadersSent(t *testing.T) {
// TODO
}
func Test_JSON(t *testing.T) {
type SomeStruct struct {
Name string
Age uint8
}
app := New()
app.Get("/test", func(c *Ctx) {
if err := c.Json(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if err := c.JSON(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
if err := c.JSON(data); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/json" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "application/json")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `{"Name":"Grame","Age":20}` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `{"Name":"Grame","Age":20}`)
}
}
func Test_JSONBytes(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.JsonBytes([]byte(""))
c.JSONBytes([]byte(`{"Name":"Grame","Age":20}`))
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/json" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "application/json")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `{"Name":"Grame","Age":20}` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `{"Name":"Grame","Age":20}`)
}
}
func Test_JSONP(t *testing.T) {
type SomeStruct struct {
Name string
Age uint8
}
app := New()
app.Get("/test", func(c *Ctx) {
if err := c.Jsonp(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if err := c.JSONP(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
data := SomeStruct{
Name: "Grame",
Age: 20,
}
if err := c.JSONP(data, "alwaysjohn"); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/javascript" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "application/javascript")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `alwaysjohn({"Name":"Grame","Age":20});` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `alwaysjohn({"Name":"Grame","Age":20});`)
}
}
func Test_JSONString(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.JsonString("")
c.JSONString(`{"Name":"Grame","Age":20}`)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/json" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "application/json")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `{"Name":"Grame","Age":20}` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `{"Name":"Grame","Age":20}`)
}
}
func Test_Links(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Links(
"http://api.example.com/users?page=2", "next",
"http://api.example.com/users?page=5", "last",
)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Link") != `<http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Link: <http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"`)
}
}
func Test_Location(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Location("http://example.com")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Location") != "http://example.com" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "http://example.com")
}
}
func Test_Next(t *testing.T) {
app := New()
app.Use("/", func(c *Ctx) {
c.Next()
})
app.Get("/test", func(c *Ctx) {
c.Set("X-Next-Result", "Works")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("X-Next-Result") != "Works" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "X-Next-Results: Works")
}
}
func Test_Redirect(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Redirect("http://example.com", 301)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 301 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Location") != "http://example.com" {
t.Fatalf(`%s: Expecting %s`, t.Name(), "Location: http://example.com")
}
}
func Test_Render(t *testing.T) {
// TODO
}
func Test_Send(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Send([]byte("Hello, World"))
c.Send("Don't crash please")
c.Send(1337)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `1337` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `1337`)
}
}
func Test_SendBytes(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.SendBytes([]byte("Hello, World"))
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Hello, World` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Hello, World`)
}
}
func Test_SendStatus(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.SendStatus(415)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 415 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Unsupported Media Type` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Unsupported Media Type`)
}
}
func Test_SendString(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.SendString("Don't crash please")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Don't crash please` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Don't crash please`)
}
}
func Test_Set(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Set("X-1", "1")
c.Set("X-2", "2")
c.Set("X-3", "3")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("X-1") != "1" {
t.Fatalf(`%s: Expected %v`, t.Name(), "X-1: 1")
}
if resp.Header.Get("X-2") != "2" {
t.Fatalf(`%s: Expected %v`, t.Name(), "X-2: 2")
}
if resp.Header.Get("X-3") != "3" {
t.Fatalf(`%s: Expected %v`, t.Name(), "X-3: 3")
}
}
func Test_Status(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Status(400)
c.Status(415).Send("Hello, World")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 415 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Hello, World` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Hello, World`)
}
}
func Test_Type(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Type(".json")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/json" {
t.Fatalf(`%s: Expected %v`, t.Name(), `Content-Type: application/json`)
}
}
func Test_Vary(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Vary("Origin")
c.Vary("User-Agent")
c.Vary("Accept-Encoding", "Accept")
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Vary") != "Origin, User-Agent, Accept-Encoding, Accept" {
t.Fatalf(`%s: Expected %v`, t.Name(), `Vary: Origin, User-Agent, Accept-Encoding, Accept`)
}
}
func Test_Write(t *testing.T) {
app := New()
app.Get("/test", func(c *Ctx) {
c.Write("Hello, ")
c.Write([]byte("World! "))
c.Write(123)
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `Hello, World! 123` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `Hello, World! 123`)
}
}
func Test_XML(t *testing.T) {
type person struct {
Name string `xml:"name"`
Stars int `xml:"stars"`
}
app := New()
app.Get("/test", func(c *Ctx) {
if err := c.Xml(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if err := c.XML(""); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if err := c.XML(person{"John", 50}); err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
})
req, _ := http.NewRequest("GET", "http://example.com/test", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf(`%s: %s`, t.Name(), err)
}
if resp.StatusCode != 200 {
t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/xml" {
t.Fatalf(`%s: Expected %v`, t.Name(), "application/xml")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf(`%s: Error %s`, t.Name(), err)
}
if string(body) != `<person><name>John</name><stars>50</stars></person>` {
t.Fatalf(`%s: Expecting %s`, t.Name(), `<person><name>John</name><stars>50</stars></person>`)
}
}

481
router.go
View File

@ -1,164 +1,276 @@
// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
// 📌 Please open an issue if you got suggestions or found a bug!
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
// 🦸 Not all heroes wear capes, thank you to some amazing people
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
package fiber
import (
"fmt"
"log"
"path/filepath"
"reflect"
"regexp"
"strings"
"sync"
websocket "github.com/fasthttp/websocket"
fasthttp "github.com/valyala/fasthttp"
)
// Ctx is the context that contains everything
type Ctx struct {
route *Route
next bool
error error
params *[]string
values []string
Fasthttp *fasthttp.RequestCtx
}
// Route struct
type Route struct {
// HTTP method in uppercase, can be a * for Use() & All()
// HTTP method in uppercase, can be a * for "Use" & "All" routes
Method string
// Stores the original path
Path string
// Bool that defines if the route is a Use() middleware
Midware bool
// wildcard bool is for routes without a path, * and /*
Wildcard bool
// Stores compiled regex special routes :params, *wildcards, optionals?
// Prefix is for ending wildcards or middlewares
Prefix string
// Stores regex for :params & :optionals?
Regex *regexp.Regexp
// Store params if special routes :params, *wildcards, optionals?
// Stores params keys for :params & :optionals?
Params []string
// Callback function for specific route
Handler func(*Ctx)
// Callback function for context
HandlerCtx func(*Ctx)
// Callback function for websockets
HandlerConn func(*Conn)
}
// Ctx pool
var poolCtx = sync.Pool{
New: func() interface{} {
return new(Ctx)
},
}
func (app *App) registerStatic(grpPrefix string, args ...string) {
var prefix = "/"
var root = "./"
// enable / disable gzipping somewhere?
// todo v2.0.0
gzip := true
// Get new Ctx from pool
func acquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
ctx := poolCtx.Get().(*Ctx)
ctx.Fasthttp = fctx
return ctx
}
// Return Context to pool
func releaseCtx(ctx *Ctx) {
ctx.route = nil
ctx.next = false
ctx.error = nil
ctx.params = nil
ctx.values = nil
ctx.Fasthttp = nil
poolCtx.Put(ctx)
}
func (grp *Group) register(method string, args ...interface{}) {
path := grp.path
var handler func(*Ctx)
if len(args) == 1 {
handler = args[0].(func(*Ctx))
} else if len(args) > 1 {
path = path + args[0].(string)
handler = args[1].(func(*Ctx))
if path[0] != '/' && path[0] != '*' {
path = "/" + path
root = args[0]
}
if len(args) == 2 {
prefix = args[0]
root = args[1]
}
// A non wildcard path must start with a '/'
if prefix != "*" && len(prefix) > 0 && prefix[0] != '/' {
prefix = "/" + prefix
}
// Prepend group prefix
if len(grpPrefix) > 0 {
// `/v1`+`/` => `/v1`+``
if prefix == "/" {
prefix = grpPrefix
} else {
prefix = grpPrefix + prefix
}
path = strings.Replace(path, "//", "/", -1)
path = filepath.Clean(path)
// Remove duplicate slashes `//`
prefix = strings.Replace(prefix, "//", "/", -1)
}
// Empty or '/*' path equals "match anything"
// TODO fix * for paths with grpprefix
if prefix == "/*" {
prefix = "*"
}
// Lets get all files from root
files, _, err := getFiles(root)
if err != nil {
log.Fatal("Static: ", err)
}
// ./static/compiled => static/compiled
mount := filepath.Clean(root)
if !app.Settings.CaseSensitive {
prefix = strings.ToLower(prefix)
}
if !app.Settings.StrictRouting && len(prefix) > 1 {
prefix = strings.TrimRight(prefix, "/")
}
// Loop over all files
for _, file := range files {
// Ignore the .gzipped files by fasthttp
if strings.Contains(file, ".fasthttp.gz") {
continue
}
// Time to create a fake path for the route match
// static/index.html => /index.html
path := filepath.Join(prefix, strings.Replace(file, mount, "", 1))
// for windows: static\index.html => /index.html
path = filepath.ToSlash(path)
// Store file path to use in ctx handler
filePath := file
if len(prefix) > 1 && strings.Contains(prefix, "*") {
app.routes = append(app.routes, &Route{
Method: "GET",
Path: path,
Prefix: strings.Split(prefix, "*")[0],
HandlerCtx: func(c *Ctx) {
c.SendFile(filePath, gzip)
},
})
return
}
// If the file is an index.html, bind the prefix to index.html directly
if filepath.Base(filePath) == "index.html" || filepath.Base(filePath) == "index.htm" {
app.routes = append(app.routes, &Route{
Method: "GET",
Path: prefix,
HandlerCtx: func(c *Ctx) {
c.SendFile(filePath, gzip)
},
})
}
if !app.Settings.CaseSensitive {
path = strings.ToLower(path)
}
if !app.Settings.StrictRouting && len(prefix) > 1 {
path = strings.TrimRight(path, "/")
}
// Add the route + SendFile(filepath) to routes
app.routes = append(app.routes, &Route{
Method: "GET",
Path: path,
HandlerCtx: func(c *Ctx) {
c.SendFile(filePath, gzip)
},
})
}
grp.app.register(method, path, handler)
}
// Function to add a route correctly
func (app *Application) register(method string, args ...interface{}) {
// Set if method is Use() midware
var midware = method == "USE"
// Match any method
if method == "ALL" || midware {
method = "*"
}
// Prepare possible variables
var path string // We could have a path/prefix
var handler func(*Ctx) // We could have a ctx handler
// Only 1 argument, so no path/prefix
if len(args) == 1 {
handler = args[0].(func(*Ctx))
} else if len(args) > 1 {
path = args[0].(string)
handler = args[1].(func(*Ctx))
if path == "" || path[0] != '/' && path[0] != '*' {
path = "/" + path
func (app *App) register(method, grpPrefix string, args ...interface{}) {
// Set variables
var path = "*"
var prefix string
var middleware = method == "USE"
var handlersCtx []func(*Ctx)
var handlersConn []func(*Conn)
for i := 0; i < len(args); i++ {
switch arg := args[i].(type) {
case string:
path = arg
case func(*Ctx):
handlersCtx = append(handlersCtx, arg)
case func(*Conn):
handlersConn = append(handlersConn, arg)
default:
log.Fatalf("Invalid argument type: %v", reflect.TypeOf(arg))
}
}
if midware && strings.Contains(path, "/:") {
log.Fatal("Router: You cannot use :params in Use()")
// A non wildcard path must start with a '/'
if path != "*" && len(path) > 0 && path[0] != '/' {
path = "/" + path
}
// If Use() path == "/", match anything aka *
if midware && path == "/" {
// Prepend group prefix
if len(grpPrefix) > 0 {
// `/v1`+`/` => `/v1`+``
if path == "/" {
path = grpPrefix
} else {
path = grpPrefix + path
}
// Remove duplicate slashes `//`
path = strings.Replace(path, "//", "/", -1)
}
// Empty or '/*' path equals "match anything"
// TODO fix * for paths with grpprefix
if path == "" || path == "/*" {
path = "*"
}
// If the route needs to match any path
if path == "" || path == "*" || path == "/*" {
app.routes = append(app.routes, &Route{method, path, midware, true, nil, nil, handler})
if method == "ALL" || middleware {
method = "*"
}
// Routes are case insensitive by default
if !app.Settings.CaseSensitive {
path = strings.ToLower(path)
}
if !app.Settings.StrictRouting && len(path) > 1 {
path = strings.TrimRight(path, "/")
}
// If the route can match anything
if path == "*" {
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, HandlerConn: handlersConn[i],
})
}
return
}
// Get params from path
// Get ':param' & ':optional?' & '*' from path
params := getParams(path)
// Enable prefix for midware
if len(params) == 0 && middleware {
prefix = path
}
// If path has no params (simple path), we don't need regex (also for use())
if midware || len(params) == 0 {
app.routes = append(app.routes, &Route{method, path, midware, false, nil, nil, handler})
// If path has no params (simple path)
if len(params) == 0 {
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix, HandlerConn: handlersConn[i],
})
}
return
}
// We have parametes, so we need to compile regex from the path
// If path only contains 1 wildcard, we can create a prefix
// If its a middleware, we also create a prefix
if len(params) == 1 && params[0] == "*" {
prefix = strings.Split(path, "*")[0]
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix,
Params: params, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix,
Params: params, HandlerConn: handlersConn[i],
})
}
return
}
// We have an :param or :optional? and need to compile a regex struct
regex, err := getRegex(path)
if err != nil {
log.Fatal("Router: Invalid url pattern: " + path)
log.Fatal("Router: Invalid path pattern: " + path)
}
// Add route with regex
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Regex: regex,
Params: params, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Regex: regex,
Params: params, HandlerConn: handlersConn[i],
})
}
// Add regex + params to route
app.routes = append(app.routes, &Route{method, path, midware, false, regex, params, handler})
}
// then try to match a route as efficient as possible.
func (app *Application) handler(fctx *fasthttp.RequestCtx) {
found := false
func (app *App) handler(fctx *fasthttp.RequestCtx) {
// Use this boolean to perform 404 not found at the end
var match = false
// get custom context from sync pool
ctx := acquireCtx(fctx)
// get path and method from main context
if ctx.app == nil {
ctx.app = app
}
// get path and method
path := ctx.Path()
if !app.Settings.CaseSensitive {
path = strings.ToLower(path)
}
if !app.Settings.StrictRouting && len(path) > 1 {
path = strings.TrimRight(path, "/")
}
method := ctx.Method()
// enable recovery
if app.recover != nil {
defer func() {
if r := recover(); r != nil {
@ -167,48 +279,101 @@ func (app *Application) handler(fctx *fasthttp.RequestCtx) {
}
}()
}
// loop trough routes
for _, route := range app.routes {
// Skip route if method is not allowed
// Skip route if method does not match
if route.Method != "*" && route.Method != method {
continue
}
// First check if we match a wildcard or static path
if route.Wildcard || route.Path == path {
// if route.wildcard || (route.path == path && route.params == nil) {
// If * always set the path to the wildcard parameter
if route.Wildcard {
// Set route pointer if user wants to call .Route()
ctx.route = route
// wilcard or exact same path
// TODO v2: enable or disable case insensitive match
if route.Path == "*" || route.Path == path {
// if * always set the path to the wildcard parameter
if route.Path == "*" {
ctx.params = &[]string{"*"}
ctx.values = make([]string, 1)
ctx.values[0] = path
ctx.values = []string{path}
}
found = true
// Set route pointer if user wants to call .Route()
ctx.route = route
// Execute handler with context
route.Handler(ctx)
// ctx.Fasthttp.Request.Header.ConnectionUpgrade()
// Websocket request
if route.HandlerConn != nil && websocket.FastHTTPIsWebSocketUpgrade(fctx) {
// Try to upgrade
err := socketUpgrade.Upgrade(ctx.Fasthttp, func(fconn *websocket.Conn) {
conn := acquireConn(fconn)
defer releaseConn(conn)
conn.params = ctx.params
conn.values = ctx.values
releaseCtx(ctx)
route.HandlerConn(conn)
})
// Upgrading failed
if err != nil {
panic(err)
}
return
}
// No handler for HTTP nor websocket
if route.HandlerCtx == nil {
continue
}
// Match found, 404 not needed
match = true
route.HandlerCtx(ctx)
// if next is not set, leave loop and release ctx
if !ctx.next {
break
} else {
// reset match to false
match = false
}
// set next to false for next iteration
ctx.next = false
// continue to go to the next route
continue
}
// If route is Use() and path starts with route.path
// aka strings.HasPrefix(route.path, path)
if route.Midware && strings.HasPrefix(path, route.Path) {
found = true
if route.Prefix != "" && strings.HasPrefix(path, route.Prefix) {
ctx.route = route
route.Handler(ctx)
if strings.Contains(route.Path, "*") {
ctx.params = &[]string{"*"}
// ctx.values = matches[0][1:len(matches[0])]
// parse query source
ctx.values = []string{strings.Replace(path, route.Prefix, "", 1)}
}
// Websocket request
if route.HandlerConn != nil {
// Try to upgrade
err := socketUpgrade.Upgrade(ctx.Fasthttp, func(fconn *websocket.Conn) {
conn := acquireConn(fconn)
defer releaseConn(conn)
conn.params = ctx.params
conn.values = ctx.values
releaseCtx(ctx)
route.HandlerConn(conn)
})
// Upgrading failed
if err != nil {
panic(err)
}
return
}
// No handler for HTTP nor websocket
if route.HandlerCtx == nil {
continue
}
// Match found, 404 not needed
match = true
route.HandlerCtx(ctx)
// if next is not set, leave loop and release ctx
if !ctx.next {
break
} else {
// reset match to false
match = false
}
// set next to false for next iteration
ctx.next = false
// continue to go to the next route
continue
}
@ -228,34 +393,46 @@ func (app *Application) handler(fctx *fasthttp.RequestCtx) {
// If we have matches, add params and values to context
if len(matches) > 0 && len(matches[0]) > 1 {
ctx.params = &route.Params
// ctx.values = make([]string, len(*ctx.params))
ctx.values = matches[0][1:len(matches[0])]
}
}
found = true
// Set route pointer if user wants to call .Route()
ctx.route = route
// Execute handler with context
route.Handler(ctx)
// Websocket route
if route.HandlerConn != nil {
// Try to upgrade
err := socketUpgrade.Upgrade(ctx.Fasthttp, func(fconn *websocket.Conn) {
conn := acquireConn(fconn)
conn.params = ctx.params
conn.values = ctx.values
releaseCtx(ctx)
defer releaseConn(conn)
route.HandlerConn(conn)
})
// Upgrading failed
if err != nil {
panic(err)
}
return
}
// No handler for HTTP nor websocket
if route.HandlerCtx == nil {
continue
}
// Match found, 404 not needed
match = true
route.HandlerCtx(ctx)
// if next is not set, leave loop and release ctx
if !ctx.next {
break
} else {
// reset match to false
match = false
}
// set next to false for next iteration
ctx.next = false
}
// No routes found
if !found {
// Custom 404 handler?
// No match, send default 404
if !match {
ctx.SendStatus(404)
}
// release context back into sync pool
releaseCtx(ctx)
}

View File

@ -1,10 +0,0 @@
package fiber
import "testing"
func BenchmarkFib10(b *testing.B) {
// run the Fib function b.N times
// for n := 0; n < b.N; n++ {
// Fib(10)
// }
}

113
utils.go
View File

@ -1,10 +1,3 @@
// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
// 📌 Please open an issue if you got suggestions or found a bug!
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
// 🦸 Not all heroes wear capes, thank you to some amazing people
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
package fiber
import (
@ -13,18 +6,44 @@ import (
"net"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"time"
"unsafe"
websocket "github.com/fasthttp/websocket"
schema "github.com/gorilla/schema"
fasthttp "github.com/valyala/fasthttp"
)
var schemaDecoder = schema.NewDecoder()
var socketUpgrade = websocket.FastHTTPUpgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(fctx *fasthttp.RequestCtx) bool {
return true
},
}
// MIME types
const (
MIMEApplicationJSON = "application/json"
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationXML = "application/xml"
MIMETextXML = "text/xml"
MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEApplicationProtobuf = "application/protobuf"
MIMEApplicationMsgpack = "application/msgpack"
MIMETextHTML = "text/html"
MIMETextPlain = "text/plain"
MIMEMultipartForm = "multipart/form-data"
MIMEOctetStream = "application/octet-stream"
)
func getParams(path string) (params []string) {
if len(path) < 1 {
return
}
segments := strings.Split(path, "/")
replacer := strings.NewReplacer(":", "", "?", "")
for _, s := range segments {
@ -32,11 +51,12 @@ func getParams(path string) (params []string) {
continue
} else if s[0] == ':' {
params = append(params, replacer.Replace(s))
} else if s[0] == '*' {
}
if strings.Contains(s, "*") {
params = append(params, "*")
}
}
return params
return
}
func getRegex(path string) (*regexp.Regexp, error) {
@ -63,21 +83,20 @@ func getRegex(path string) (*regexp.Regexp, error) {
return regex, err
}
func getFiles(root string) (files []string, isDir bool, err error) {
func getFiles(root string) (files []string, dir bool, err error) {
root = filepath.Clean(root)
// Check if dir/file exists
if _, err := os.Lstat(root); err != nil {
return files, isDir, fmt.Errorf("%s", err)
return files, dir, fmt.Errorf("%s", err)
}
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
files = append(files, path)
} else {
isDir = true
dir = true
}
return err
})
return files, isDir, err
return
}
func getType(ext string) (mime string) {
@ -85,17 +104,18 @@ func getType(ext string) (mime string) {
return mime
}
if ext[0] == '.' {
ext = ext[1:]
mime = extensionMIME[ext[1:]]
} else {
mime = extensionMIME[ext]
}
mime = mimeTypes[ext]
if mime == "" {
return mimeApplicationOctetStream
return MIMEOctetStream
}
return mime
}
func getStatus(status int) (msg string) {
return statusMessages[status]
return statusMessage[status]
}
// #nosec G103
@ -109,47 +129,32 @@ func getString(b []byte) string {
// getBytes converts string to a byte slice without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
func getBytes(s string) (b []byte) {
// return *(*[]byte)(unsafe.Pointer(&s))
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
return b
return *(*[]byte)(unsafe.Pointer(&s))
}
// Check for error and format
// func checkErr(err error, title ...string) {
// if err != nil {
// t := "Error"
// if len(title) > 0 {
// t = title[0]
// }
// fmt.Printf("\n%s%s: %v%s\n\n", "\x1b[1;30m", t, err, "\x1b[0m")
// os.Exit(1)
// }
// }
// https://golang.org/src/net/net.go#L113
// Helper methods for Testing
type conn struct {
// Helper methods for application#test
type testConn struct {
net.Conn
r bytes.Buffer
w bytes.Buffer
}
func (c *conn) RemoteAddr() net.Addr {
func (c *testConn) RemoteAddr() net.Addr {
return &net.TCPAddr{
IP: net.IPv4(0, 0, 0, 0),
}
}
func (c *conn) LocalAddr() net.Addr { return c.LocalAddr() }
func (c *conn) Read(b []byte) (int, error) { return c.r.Read(b) }
func (c *conn) Write(b []byte) (int, error) { return c.w.Write(b) }
func (c *conn) Close() error { return nil }
func (c *conn) SetDeadline(t time.Time) error { return nil }
func (c *conn) SetReadDeadline(t time.Time) error { return nil }
func (c *conn) SetWriteDeadline(t time.Time) error { return nil }
func (c *testConn) LocalAddr() net.Addr { return c.RemoteAddr() }
func (c *testConn) Read(b []byte) (int, error) { return c.r.Read(b) }
func (c *testConn) Write(b []byte) (int, error) { return c.w.Write(b) }
func (c *testConn) Close() error { return nil }
func (c *testConn) SetDeadline(t time.Time) error { return nil }
func (c *testConn) SetReadDeadline(t time.Time) error { return nil }
func (c *testConn) SetWriteDeadline(t time.Time) error { return nil }
var statusMessages = map[int]string{
// HTTP status codes
var statusMessage = map[int]string{
100: "Continue",
101: "Switching Protocols",
102: "Processing",
@ -212,18 +217,8 @@ var statusMessages = map[int]string{
511: "Network Authentication Required",
}
const (
mimeApplicationJSON = "application/json"
mimeApplicationJavascript = "application/javascript"
mimeApplicationXML = "application/xml"
mimeTextXML = "text/xml"
mimeApplicationOctetStream = "application/octet-stream"
mimeApplicationForm = "application/x-www-form-urlencoded"
mimeMultipartForm = "multipart/form-data"
)
// https://github.com/nginx/nginx/blob/master/conf/mime.types
var mimeTypes = map[string]string{
// MIME types for file extensions
var extensionMIME = map[string]string{
"html": "text/html",
"htm": "text/html",
"shtml": "text/html",