Skip to content

Commit

Permalink
Some minor but helpful additions, like CookieOption. Relative: http…
Browse files Browse the repository at this point in the history
…s://github.com/kataras/iris/issues/1018. Simple cookies example added too. Cookie encoding (side by side with the already session's cookie id encoding) and version upgrade will come tomorrow with a new HISTORY.md entry as well, stay tuned!
  • Loading branch information
kataras committed Jun 2, 2018
1 parent a736487 commit 574414a
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 30 deletions.
4 changes: 4 additions & 0 deletions _examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ iris cache library lives on its own [package](https://github.com/kataras/iris/tr

> You're free to use your own favourite caching package if you'd like so.

### Cookies

- [Basic](cookies/basic/main.go)

### Sessions

iris session manager lives on its own [package](https://github.com/kataras/iris/tree/master/sessions).
Expand Down
4 changes: 4 additions & 0 deletions _examples/README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ Iris 独立缓存包 [package](https://github.com/kataras/iris/tree/master/cache

> 可以随意使用自定义的缓存包。

### Cookies

- [Basic](cookies/basic/main.go)

### Sessions

Iris session 管理独立包 [package](https://github.com/kataras/iris/tree/master/sessions).
Expand Down
64 changes: 64 additions & 0 deletions _examples/cookies/basic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import "github.com/kataras/iris"

func newApp() *iris.Application {
app := iris.New()

// Set A Cookie.
app.Get("/cookies/{name}/{value}", func(ctx iris.Context) {
name := ctx.Params().Get("name")
value := ctx.Params().Get("value")

ctx.SetCookieKV(name, value) // <--
// Alternatively: ctx.SetCookie(&http.Cookie{...})
//
// If you want to set custom the path:
// ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored"))
//
// If you want to be visible only to current request path:
// (note that client should be responsible for that if server sent an empty cookie's path, all browsers are compatible)
// ctx.SetCookieKV(name, value, iris.CookieCleanPath /* or iris.CookiePath("") */)
// More:
// iris.CookieExpires(time.Duration)
// iris.CookieHTTPOnly(false)

ctx.Writef("cookie added: %s = %s", name, value)
})

// Retrieve A Cookie.
app.Get("/cookies/{name}", func(ctx iris.Context) {
name := ctx.Params().Get("name")

value := ctx.GetCookie(name) // <--
// If you want more than the value then:
// cookie, err := ctx.Request().Cookie(name)
// if err != nil {
// handle error.
// }

ctx.WriteString(value)
})

// Delete A Cookie.
app.Delete("/cookies/{name}", func(ctx iris.Context) {
name := ctx.Params().Get("name")

ctx.RemoveCookie(name) // <--
// If you want to set custom the path:
// ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored"))

ctx.Writef("cookie %s removed", name)
})

return app
}

func main() {
app := newApp()

// GET: http://localhost:8080/cookies/my_name/my_value
// GET: http://localhost:8080/cookies/my_name
// DELETE: http://localhost:8080/cookies/my_name
app.Run(iris.Addr(":8080"))
}
32 changes: 32 additions & 0 deletions _examples/cookies/basic/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"fmt"
"testing"

"github.com/kataras/iris/httptest"
)

func TestCookiesBasic(t *testing.T) {
app := newApp()
e := httptest.New(t, app, httptest.URL("http://example.com"))

cookieName, cookieValue := "my_cookie_name", "my_cookie_value"

// Test Set A Cookie.
t1 := e.GET(fmt.Sprintf("/cookies/%s/%s", cookieName, cookieValue)).Expect().Status(httptest.StatusOK)
t1.Cookie(cookieName).Value().Equal(cookieValue) // validate cookie's existence, it should be there now.
t1.Body().Contains(cookieValue)

// Test Retrieve A Cookie.
t2 := e.GET(fmt.Sprintf("/cookies/%s", cookieName)).Expect().Status(httptest.StatusOK)
t2.Body().Equal(cookieValue)

// Test Remove A Cookie.
t3 := e.DELETE(fmt.Sprintf("/cookies/%s", cookieName)).Expect().Status(httptest.StatusOK)
t3.Body().Contains(cookieName)

t4 := e.GET(fmt.Sprintf("/cookies/%s", cookieName)).Expect().Status(httptest.StatusOK)
t4.Cookies().Empty()
t4.Body().Empty()
}
170 changes: 140 additions & 30 deletions context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,18 +854,41 @@ type Context interface {
// | Cookies |
// +------------------------------------------------------------+

// SetCookie adds a cookie
SetCookie(cookie *http.Cookie)
// SetCookieKV adds a cookie, receives just a name(string) and a value(string)
// SetCookie adds a cookie.
// Use of the "options" is not required, they can be used to amend the "cookie".
//
// If you use this method, it expires at 2 hours
// use ctx.SetCookie or http.SetCookie if you want to change more fields.
SetCookieKV(name, value string)
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
SetCookie(cookie *http.Cookie, options ...CookieOption)
// SetCookieKV adds a cookie, requires the name(string) and the value(string).
//
// By default it expires at 2 hours and it's added to the root path,
// use the `CookieExpires` and `CookiePath` to modify them.
// Alternatively: ctx.SetCookie(&http.Cookie{...})
//
// If you want to set custom the path:
// ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored"))
//
// If you want to be visible only to current request path:
// ctx.SetCookieKV(name, value, iris.CookieCleanPath/iris.CookiePath(""))
// More:
// iris.CookieExpires(time.Duration)
// iris.CookieHTTPOnly(false)
//
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
SetCookieKV(name, value string, options ...CookieOption)
// GetCookie returns cookie's value by it's name
// returns empty string if nothing was found.
GetCookie(name string) string
// RemoveCookie deletes a cookie by it's name.
RemoveCookie(name string)
//
// If you want more than the value then:
// cookie, err := ctx.Request().Cookie("name")
//
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
GetCookie(name string, options ...CookieOption) string
// RemoveCookie deletes a cookie by it's name and path = "/".
// Tip: change the cookie's path to the current one by: RemoveCookie("name", iris.CookieCleanPath)
//
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
RemoveCookie(name string, options ...CookieOption)
// VisitAllCookies takes a visitor which loops
// on each (request's) cookies' name and value.
VisitAllCookies(visitor func(name string, value string))
Expand Down Expand Up @@ -1233,7 +1256,7 @@ func (ctx *context) HandlerName() string {
// It can be changed to a customized one if needed (very advanced usage).
//
// See `DefaultNext` for more information about this and why it's exported like this.
var Next = DefaultNext ///TODO: add an example for this usecase, i.e describe handlers and skip only file handlers.
var Next = DefaultNext

// DefaultNext is the default function that executed on each middleware if `ctx.Next()`
// is called.
Expand Down Expand Up @@ -2992,57 +3015,144 @@ func (ctx *context) SendFile(filename string, destinationName string) error {
}

// +------------------------------------------------------------+
// | Cookies, Session and Flashes |
// | Cookies |
// +------------------------------------------------------------+

// SetCookie adds a cookie
func (ctx *context) SetCookie(cookie *http.Cookie) {
http.SetCookie(ctx.writer, cookie)
// CookieOption is the type of function that is accepted on
// context's methods like `SetCookieKV`, `RemoveCookie` and `SetCookie`
// as their (last) variadic input argument to amend the end cookie's form.
//
// Any custom or built'n `CookieOption` is valid,
// see `CookiePath`, `CookieCleanPath`, `CookieExpires` and `CookieHTTPOnly` for more.
type CookieOption func(*http.Cookie)

// CookiePath is a `CookieOption`.
// Use it to change the cookie's Path field.
func CookiePath(path string) CookieOption {
return func(c *http.Cookie) {
c.Path = path
}
}

var (
// SetCookieKVExpiration is 2 hours by-default
// you can change it or simple, use the SetCookie for more control.
SetCookieKVExpiration = time.Duration(120) * time.Minute
)
// CookieCleanPath is a `CookieOption`.
// Use it to clear the cookie's Path field, exactly the same as `CookiePath("")`.
func CookieCleanPath(c *http.Cookie) {
c.Path = ""
}

// CookieExpires is a `CookieOption`.
// Use it to change the cookie's Expires and MaxAge fields by passing the lifetime of the cookie.
func CookieExpires(durFromNow time.Duration) CookieOption {
return func(c *http.Cookie) {
c.Expires = time.Now().Add(durFromNow)
c.MaxAge = int(durFromNow.Seconds())
}
}

// SetCookieKV adds a cookie, receives just a name(string) and a value(string)
// CookieHTTPOnly is a `CookieOption`.
// Use it to set the cookie's HttpOnly field to false or true.
// HttpOnly field defaults to true for `RemoveCookie` and `SetCookieKV`.
func CookieHTTPOnly(httpOnly bool) CookieOption {
return func(c *http.Cookie) {
c.HttpOnly = httpOnly
}
}

// SetCookie adds a cookie.
// Use of the "options" is not required, they can be used to amend the "cookie".
//
// If you use this method, it expires at 2 hours
// use ctx.SetCookie or http.SetCookie if you want to change more fields.
func (ctx *context) SetCookieKV(name, value string) {
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
func (ctx *context) SetCookie(cookie *http.Cookie, options ...CookieOption) {
for _, opt := range options {
opt(cookie)
}

http.SetCookie(ctx.writer, cookie)
}

// SetCookieKV adds a cookie, requires the name(string) and the value(string).
//
// By default it expires at 2 hours and it's added to the root path,
// use the `CookieExpires` and `CookiePath` to modify them.
// Alternatively: ctx.SetCookie(&http.Cookie{...})
//
// If you want to set custom the path:
// ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored"))
//
// If you want to be visible only to current request path:
// (note that client should be responsible for that if server sent an empty cookie's path, all browsers are compatible)
// ctx.SetCookieKV(name, value, iris.CookieCleanPath/iris.CookiePath(""))
// More:
// iris.CookieExpires(time.Duration)
// iris.CookieHTTPOnly(false)
//
// Examples: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
func (ctx *context) SetCookieKV(name, value string, options ...CookieOption) {
c := &http.Cookie{}
c.Path = "/"
c.Name = name
c.Value = url.QueryEscape(value)
c.HttpOnly = true
c.Expires = time.Now().Add(SetCookieKVExpiration)
c.MaxAge = int(SetCookieKVExpiration.Seconds())
ctx.SetCookie(c)
ctx.SetCookie(c, options...)
}

// GetCookie returns cookie's value by it's name
// returns empty string if nothing was found.
func (ctx *context) GetCookie(name string) string {
//
// If you want more than the value then:
// cookie, err := ctx.Request().Cookie("name")
//
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
func (ctx *context) GetCookie(name string, options ...CookieOption) string {
cookie, err := ctx.request.Cookie(name)
if err != nil {
return ""
}

// TODO:
// Q: Why named as `CookieOption` and not like `CookieInterceptor`?
// A: Because an interceptor would be able to modify the cookie AND stop the 'x' operation, but we don't want to cancel anything.
//
// Q: Why "Cookie Options" here?
// A: Because of the future suport of cookie encoding like I did with sessions.
// Two impl ideas:
// - Do it so each caller of `GetCookie/SetCookieKV/SetCookie` can have each own encoding or share one, no limit.
// - Do it so every of the above three methods will use the same encoding, therefore to the Application's level, limit per Iris app.
// We'll see...
//
// Finally, I should not forget to add links for the new translated READMEs(2) and push a new version with the minor changes so far,
// API is stable, so relax and do it on the next commit tomorrow, need sleep.
for _, opt := range options {
opt(cookie)
}

value, _ := url.QueryUnescape(cookie.Value)
return value
}

// RemoveCookie deletes a cookie by it's name.
func (ctx *context) RemoveCookie(name string) {
// SetCookieKVExpiration is 2 hours by-default
// you can change it or simple, use the SetCookie for more control.
//
// See `SetCookieKVExpiration` and `CookieExpires` for more.
var SetCookieKVExpiration = time.Duration(120) * time.Minute

// RemoveCookie deletes a cookie by it's name and path = "/".
// Tip: change the cookie's path to the current one by: RemoveCookie("name", iris.CookieCleanPath)
//
// Example: https://github.com/kataras/iris/tree/master/_examples/cookies/basic
func (ctx *context) RemoveCookie(name string, options ...CookieOption) {
c := &http.Cookie{}
c.Name = name
c.Value = ""
c.Path = "/"
c.Path = "/" // if user wants to change it, use of the CookieOption `CookiePath` is required if not `ctx.SetCookie`.
c.HttpOnly = true
// RFC says 1 second, but let's do it 1 minute to make sure is working
// RFC says 1 second, but let's do it 1 to make sure is working
exp := time.Now().Add(-time.Duration(1) * time.Minute)
c.Expires = exp
c.MaxAge = -1
ctx.SetCookie(c)
ctx.SetCookie(c, options...)
// delete request's cookie also, which is temporary available.
ctx.request.Header.Set("Cookie", "")
}
Expand Down
10 changes: 10 additions & 0 deletions go19.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,14 @@ type (
//
// See `ExecutionRules` and `core/router/Party#SetExecutionRules` for more.
ExecutionOptions = router.ExecutionOptions

// CookieOption is the type of function that is accepted on
// context's methods like `SetCookieKV`, `RemoveCookie` and `SetCookie`
// as their (last) variadic input argument to amend the end cookie's form.
//
// Any custom or built'n `CookieOption` is valid,
// see `CookiePath`, `CookieCleanPath`, `CookieExpires` and `CookieHTTPOnly` for more.
//
// An alias for the `context/Context#CookieOption`.
CookieOption = context.CookieOption
)
1 change: 1 addition & 0 deletions httptest/httptest.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpe
// set the logger or disable it (default) and disable the updater (for any case).
app.Configure(iris.WithoutVersionChecker)
app.Logger().SetLevel(conf.LogLevel)

if err := app.Build(); err != nil {
if conf.Debug && (conf.LogLevel == "disable" || conf.LogLevel == "disabled") {
app.Logger().Println(err.Error())
Expand Down
22 changes: 22 additions & 0 deletions iris.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,28 @@ var (
//
// A shortcut of the `cache#Cache304`.
Cache304 = cache.Cache304

// CookiePath is a `CookieOption`.
// Use it to change the cookie's Path field.
//
// A shortcut for the `context#CookiePath`.
CookiePath = context.CookiePath
// CookieCleanPath is a `CookieOption`.
// Use it to clear the cookie's Path field, exactly the same as `CookiePath("")`.
//
// A shortcut for the `context#CookieCleanPath`.
CookieCleanPath = context.CookieCleanPath
// CookieExpires is a `CookieOption`.
// Use it to change the cookie's Expires and MaxAge fields by passing the lifetime of the cookie.
//
// A shortcut for the `context#CookieExpires`.
CookieExpires = context.CookieExpires
// CookieHTTPOnly is a `CookieOption`.
// Use it to set the cookie's HttpOnly field to false or true.
// HttpOnly field defaults to true for `RemoveCookie` and `SetCookieKV`.
//
// A shortcut for the `context#CookieHTTPOnly`.
CookieHTTPOnly = context.CookieHTTPOnly
)

// SPA accepts an "assetHandler" which can be the result of an
Expand Down

0 comments on commit 574414a

Please sign in to comment.