diff --git a/context.go b/context.go index f0cafceb..04d80fc9 100644 --- a/context.go +++ b/context.go @@ -130,32 +130,31 @@ func (ctx *Ctx) Body(args ...interface{}) string { return "" } +// Cookie : +func (ctx *Ctx) Cookie(name, value string, options ...interface{}) { + cook := &fasthttp.Cookie{} + if len(options) > 0 { + // options + } + cook.SetKey(name) + cook.SetValue(value) + ctx.Fasthttp.Response.Header.SetCookie(cook) +} + // Cookies : func (ctx *Ctx) Cookies(args ...interface{}) string { if len(args) == 0 { return ctx.Get("Cookie") } - if len(args) == 1 { - switch arg := args[0].(type) { - case string: - return ctx.Get(arg) - case func(string, string): - ctx.Fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) { - arg(b2s(k), b2s(v)) - }) - default: - panic("Argument must be a string or func(string, string)") - } - } - if len(args) > 1 { - cook := &fasthttp.Cookie{} - cook.SetKey(args[0].(string)) - cook.SetValue(args[1].(string)) - if len(args) > 2 { - // Do something with cookie options (args[2]) - // Dont forget to finish this - } - ctx.Fasthttp.Response.Header.SetCookie(cook) + switch arg := args[0].(type) { + case string: + return b2s(ctx.Fasthttp.Request.Header.Cookie(arg)) + case func(string, string): + ctx.Fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) { + arg(b2s(k), b2s(v)) + }) + default: + panic("Argument must be a string or func(string, string)") } return "" } @@ -174,6 +173,7 @@ func (ctx *Ctx) ClearCookies(args ...string) { // Send : func (ctx *Ctx) Send(args ...interface{}) { + // https://github.com/valyala/fasthttp/blob/master/http.go#L490 if len(args) != 1 { panic("To many arguments!") @@ -190,6 +190,16 @@ func (ctx *Ctx) Send(args ...interface{}) { } } +// SendString internal use +func (ctx *Ctx) SendString(body string) { + ctx.Fasthttp.Response.SetBodyString(body) +} + +// SendByte internal use +func (ctx *Ctx) SendByte(body []byte) { + ctx.Fasthttp.Response.SetBodyString(b2s(body)) +} + // Write : func (ctx *Ctx) Write(args ...interface{}) { if len(args) != 1 { @@ -226,8 +236,8 @@ func (ctx *Ctx) Json(v interface{}) error { return err } ctx.Set("Content-Type", "application/json") - ctx.Send(b2s(raw)) - return err + ctx.SendByte(raw) + return nil } // Redirect : @@ -263,8 +273,8 @@ func (ctx *Ctx) Hostname() string { return b2s(ctx.Fasthttp.URI().Host()) } -// OriginalURL : -func (ctx *Ctx) OriginalURL() string { +// OriginalUrl : +func (ctx *Ctx) OriginalUrl() string { return b2s(ctx.Fasthttp.Request.Header.RequestURI()) } @@ -281,8 +291,8 @@ func (ctx *Ctx) Secure() bool { return ctx.Fasthttp.IsTLS() } -// IP : -func (ctx *Ctx) IP() string { +// Ip : +func (ctx *Ctx) Ip() string { return ctx.Fasthttp.RemoteIP().String() } @@ -344,3 +354,81 @@ func (ctx *Ctx) SendFile(file string) { func (ctx *Ctx) Location(path string) { ctx.Set("Location", path) } + +// Subdomains : +func (ctx *Ctx) Subdomains() (subs []string) { + subs = strings.Split(ctx.Hostname(), ".") + subs = subs[:len(subs)-2] + return subs +} + +// Ips https://expressjs.com/en/4x/api.html#req.ips +func (ctx *Ctx) Ips() []string { + ips := strings.Split(ctx.Get("X-Forwarded-For"), " ") + return ips +} + +// Jsonp TODO https://expressjs.com/en/4x/api.html#res.jsonp +func (ctx *Ctx) Jsonp(args ...interface{}) error { + jsonp := "callback(" + if len(args) == 1 { + raw, err := json.Marshal(&args[0]) + if err != nil { + return err + } + jsonp += b2s(raw) + ");" + } else if len(args) == 2 { + jsonp = args[0].(string) + "(" + raw, err := json.Marshal(&args[0]) + if err != nil { + return err + } + jsonp += b2s(raw) + ");" + } else { + panic("Missing interface{}") + } + ctx.Set("X-Content-Type-Options", "nosniff") + ctx.Set("Content-Type", "text/javascript") + ctx.SendString(jsonp) + return nil +} + +// Vary TODO https://expressjs.com/en/4x/api.html#res.vary +func (ctx *Ctx) Vary() { + +} + +// Links TODO https://expressjs.com/en/4x/api.html#res.links +func (ctx *Ctx) Links() { + +} + +// Append TODO https://expressjs.com/en/4x/api.html#res.append +func (ctx *Ctx) Append(field, val string) { + prev := ctx.Get(field) + value := val + if prev != "" { + value = prev + "; " + val + } + ctx.Set(field, value) +} + +// Accepts TODO https://expressjs.com/en/4x/api.html#req.accepts +func (ctx *Ctx) Accepts() bool { + return true +} + +// Range TODO https://expressjs.com/en/4x/api.html#req.range +func (ctx *Ctx) Range() bool { + return true +} + +// Fresh TODO https://expressjs.com/en/4x/api.html#req.fresh +func (ctx *Ctx) Fresh() bool { + return true +} + +// Stale TODO https://expressjs.com/en/4x/api.html#req.fresh +func (ctx *Ctx) Stale() bool { + return true +} diff --git a/docs/context.md b/docs/context.md index 5f0910c1..50344216 100644 --- a/docs/context.md +++ b/docs/context.md @@ -4,6 +4,28 @@ The ctx object represents the HTTP request and response and has methods for the ## Accepts !> Planned for V2 +## Append +Appends the specified value to the HTTP response header field. If the header is not already set, it creates the header with the specified value. The value parameter must be a string. +```go +// Function signature +c.Append(field, value string) + +// Example +app.Get("/", func(c *fiber.Ctx) { + // Let's see if the Link header exist + c.Get("Link") + // => "" + + // Lets append some values to the link header + c.Append("Link", "http://localhost") + // => Link: http://localhost + + c.Append("Link", "http://google.com") + // => Link: http://localhost; http://google.com +}) +``` + + ## Attachment Sets the HTTP response [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header field to “attachment”. If a filename is given, then it sets the Content-Type based on the extension name via (Type)[#type], and sets the Content-Disposition “filename=” parameter. ```go @@ -53,7 +75,7 @@ The following example shows how to use the body function. // Function signature c.Body() string c.Body(key string) string -c.Body(func(key string, value string)) func(string, string) +c.Body(func(key value string)) func(string, string) // Example // curl -X POST http://localhost:8080 -d user=john @@ -91,31 +113,41 @@ app.Get("/", func(c *fiber.Ctx) { }) ``` +## Cookie +Sets cookie name to value, the third options parameter is not implemented yet. +```go +c.Cookie(name, value string) +//c.Cookie(name, value string, options...) + +// Example +app.Get("/", func(c *fiber.Ctx) { + // Set cookie + c.Cookie("name", "john") + // => Cookie: name=john; +}) + +``` + ## Cookies Get and set cookies ```go // Function signature c.Cookies() string c.Cookies(key string) string -c.Cookies(key string, value string) string -c.Cookies(func(key string, value string)) +c.Cookies(func(key, value string)) string // Example app.Get("/", func(c *fiber.Ctx) { - // Create cookie with key, value - c.Cookies("name", "john") - // => Cookie: name=john + // Get raw cookie header + c.Cookies() + // => name=john; // Get cookie by key c.Cookies("name") // => "john" - // Get raw cookie header - c.Cookies() - // => name=john; - // Show all cookies - c.Cookies(func(key string, val string) { + c.Cookies(func(key, val string) { fmt.Println(key, val) // => "name", "john" }) @@ -127,7 +159,7 @@ Transfers the file at path as an “attachment”. Typically, browsers will prom ```go // Function signature c.Download(path string) -c.Download(path string, filename string) +c.Download(path, filename string) // Example app.Get("/", func(c *fiber.Ctx) { @@ -223,15 +255,15 @@ app.Get("/", func(c *fiber.Ctx) { }) ``` -## IP +## Ip Contains the remote IP address of the request. ```go // Function signature -c.IP() string +c.Ip() string // Example app.Get("/", func(c *fiber.Ctx) { - c.IP() + c.Ip() // => "127.0.0.1" }) ``` @@ -288,7 +320,33 @@ app.Listen(8080) ``` ## Jsonp -!> Planned for V2 +Sends a JSON response with JSONP support. This method is identical to [Json()](#json), except that it opts-in to JSONP callback support. + +By default, the JSONP callback name is simply callback. Override this by passing a named string in the function. +```go +// Function signature +c.Jsonp(json interface{}) error +c.Jsonp(callback string, v interface{}) error +// Example +type JsonStruct struct { + name string + age uint8 +} + +app := fiber.New() +app.Get("/", func(c *fiber.Ctx) { + data := JsonStruct{ + name: "Grame", + age: 20, + } + c.Jsonp(data) + // => callback({"name": "Grame", "age": 20}) + + c.Jsonp("customFunc", data) + // => customFunc({"name": "Grame", "age": 20}) +}) +app.Listen(8080) +``` ## Method Contains a string corresponding to the HTTP method of the request: GET, POST, PUT, and so on. @@ -359,16 +417,16 @@ app.Get("/", func(c *fiber.Ctx) { }) ``` -## OriginalURL +## OriginalUrl Contains the original request URL. ```go // Function signature -c.OriginalURL() string +c.OriginalUrl() string // Example app.Get("/", func(c *fiber.Ctx) { // GET /search?q=something - c.OriginalURL() + c.OriginalUrl() // => '/search?q=something' }) ``` @@ -488,7 +546,7 @@ app.Get("/not-found", func(c *fiber.Ctx) { Sets the response’s HTTP header field to value. To set multiple fields at once, pass an object as the parameter. ```go // Function signature -c.Set(key string, value string) +c.Set(key, value string) // Example app.Get("/", func(c *fiber.Ctx) { diff --git a/router.go b/router.go index 9c624682..86d8c28f 100644 --- a/router.go +++ b/router.go @@ -245,6 +245,7 @@ func (r *Fiber) registerHandler(method, path string, handler func(*Ctx)) { // handler : func (r *Fiber) handler(fctx *fasthttp.RequestCtx) { + found := false // get custom context from sync pool ctx := acquireCtx(fctx) // get path and method from main context @@ -263,6 +264,7 @@ func (r *Fiber) handler(fctx *fasthttp.RequestCtx) { ctx.params = &[]string{"*"} ctx.values = []string{path} } + found = true // Execute handler with context route.handler(ctx) // if next is not set, leave loop and release ctx @@ -287,6 +289,7 @@ func (r *Fiber) handler(fctx *fasthttp.RequestCtx) { ctx.values = matches[0][1:len(matches[0])] } } + found = true // Execute handler with context route.handler(ctx) // if next is not set, leave loop and release ctx @@ -296,6 +299,10 @@ func (r *Fiber) handler(fctx *fasthttp.RequestCtx) { // set next to false for next iteration ctx.next = false } + if !found { + // No routes found + ctx.Status(404).Send("Not Found") + } // release context back into sync pool releaseCtx(ctx) }