How is gorilla/context different from gorilla/sessions? - go

I get sessions, coming from PHP I used to
<?php
session_start();
$_SESSION["key"] = "val";
echo $_SESSION["key"];
?>
Set one or more keys and their values serverside and be able to retrieve or overwrite it until the session expires.
The same with gorilla/sessions
var(
sessionStore *sessions.CookieStore
sessionSecret []byte = make([]byte, 64)
session *sessions.Session
)
func init(){
sessionSecret = []byte("12345678901234567890123456789012")
sessionStore = sessions.NewCookieStore(sessionSecret)
session = sessions.NewSession(sessionStore, "session_name")
}
func SetSessionHandler(w http.ResponseWriter, r *http.Request) {
session, _ = sessionStore.Get(r, "session_name")
session.Values["key"] = "val"
session.Save(r, w)
}
func GetSessionHandler(w http.ResponseWriter, r *http.Request) {
session, _ = sessionStore.Get(r, "session_name")
fmt.FPrintln(session.Values["key"])
}
Now I don't get what the point of gorilla/context is.
I know what a context is but... I don't know how it fits in the big picture.
It says that it's bound to the current request. Another question here on stackoverflow said that "simply using gorilla/context should suffice" in the context of Writing Per-Handler Middleware.
But if it's request bound... err.. syntax error, does not compute. If a duck floats on water then witches are made from wood. And because ducks also float on water if her weight is the same as that of a duck she must be a witch. Or something like that ;)
And how could this be useful as a middleware "manager" when it's request-bound, I can't set it globally. Could you perhaps show an example of how a gorilla/sessions could be used with gorilla/context?

As the person who asked that other question:
gorilla/context allows you to store data in the request. If you have some middleware that does some pre-processing on a request before deciding to continue (i.e. anti-CSRF), you might want to store a token in the request so your handler can pass it to the template. The gorilla/context documentation explains it well:
... a router can set variables extracted from the URL and later application handlers can access those values, or it can be used to store sessions values to be saved at the end of a request. There are several others common uses.
You may also want to store data in the session: error messages from form submissions, a user ID, or the 'canonical' version of the CSRF token for that visitor will likely be stored here. If you try to store an error message in the request context, and then re-direct the user, you'll lose it (that's a new request).
So why would you use context over sessions? It's lighter, and allows you to de-couple parts of your application (often, HTTP middleware!) from each other.
Example:
Request comes in
CSRF middleware checks the session for an existing CSRF token. Does not exist, so it sets one.
It also passes this new token (via the request context!) to the handler that renders your form, so it can render it in the template (otherwise you would have to pull the token from the session again, which is wasted effort)
Request is done.
New request on form submission
The token still persists in the session, so we can compare it to the submitted token from the form.
If it checks out, we proceed to process the form
If not, we can save an error in the session (a flash message; i.e. one that is erased after reading) and re-direct.
This re-direction is a new request, and therefore we can't pass the error message via the request context here.

An example.
I'm writing this multi-community-forum software.
Now I have a gorilla/mux router that serves different content for the main domain and another router that serves different content filtered by subdomain.domain.tld.
Context is very useful here, else you would repeat yourself over and over again. So if I know that for the subdomain router every request would do string operations to find out the subdomain name and check if it exists in the database I could just do this here (in context) for every request and then store the subdomain name in a context variable.
And likewise if a forum's category slug or forum slug or a thread slug is set pass it to the handler, keep the processing that needs to be done in "context" and have less code in your handlers.
So the advantage of context is essentially to keep code DRY.
Like elihrar wrote, his example of a CSRF token. If you know that you need to check for the CSRF token on each request - don't duplicate this check in every handler that needs to do this, instead write a context wrapper ( / http.Handler) and do this for every request.

Related

Why should I make a copy of a context for goroutines inside handlers?

I recently started rewriting some of my Python services in Go to speed them up and came across this section of the gin documentation:
https://github.com/gin-gonic/gin#goroutines-inside-a-middleware
So I understand the instructions, but I'm trying to understand why? What is the significance of making a copy, and what problem is introduced if I do not make a copy of context for goroutines within handlers?
Gin-gonic is async itself which makes it great.
If you are using concurrency within your handlers it's very likely you will face situation when Context, which is struct goes
outdated since it's holding the data from each request (parameters, keys protected by mutex)
empty, which will cause a fallback to default Context
here's how it looks like:
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine *Engine
params *Params
skippedNodes *[]skippedNode
// This mutex protects Keys map.
mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]any
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
// queryCache caches the query result from c.Request.URL.Query().
queryCache url.Values
// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests.
sameSite http.SameSite
}
In order to foresee such issues and race conditions - you must use Copy whenever your handlers are using go concurrency
here's a quote from gin-gonic repository:
// Copy returns a copy of the current context that can be safely used outside the request's scope.
// This has to be used when the context has to be passed to a goroutine.

What might cause a session ID to sometimes change after redirect (Java/Kotlin Spring web app)?

I have a web application built with Kotlin+Spring which identifies users by session ID. On a static front page I have a form which, on submit, sends a POST request to "/start". This is handled by Spring by executing some code and redirecting user to another page "/page" – here's the minimal code:
#Controller
class SomeController() {
// No GetMapping("/"), because '/' is 100% static
#PostMapping("/start")
fun start(#ModelAttribute someModelAttr: SomeModelAttr, model: Model,
response: HttpServletResponse,
session: HttpSession) {
log.info { "POST /start visited by ${session.id}" }
val id = getSomeStuffSynchronously(someModelAttr, session.id);
response.sendRedirect("page/${id}")
}
#GetMapping("/page/{id}")
fun page(#PathVariable id: String,
model: Model,
session: HttpSession) {
log.info { "GET /page/${id} visited by ${session.id}" }
doOtherStuff(id, session.id);
return "page" // i.e. render a Thymeleaf template
}
The code above assumes that the session ID in start and page is the same. However, sometimes (but not always) this is false which breaks stuff for the users. In this situation, the log lines are basically:
POST /start visited by abcd
Log from getSomeStuffSynchronously(someModelAttr, "abcd")
GET /page/123 visited by vxyz
Unfortunately I could not capture headers sent and received by the browser when this happens, because when I tried, I could not reproduce this issue.
I've checked:
This issue might happen regardless of whether the user uses incognito mode in their browser, or not
I have not yet observed this happening on other requests than front-page->/start->/page/id
I do not have caching enabled neither at my hosting provider nor in Spring
I do not have a huge load on my website
I do not use spring-security
Sessions are Cookie sessions and are managed by spring-session-redis, with timeout in Redis set to 15 minutes. However I did not see any obvious correlation between the time between visits to the site and this issue happening.
My question is: what might cause the session ID to change during the redirect?
The reason for getting a different sessions, might be because the cookie/url param containing a reference to the session might not have been set/is used (so a new session is generated every time).
It might be an obvious question but are you sure the filter responsible for handling the session is registered? E.g. did you add the #EnableRedisHttpSession annotation to a configuration class (see redig http session docs)?
Relying on session.id is unreliable between redirects, but as a work-around one can use RedirectAttributes and store relevant data that will be needed in the second request handler as flash attributes – see e.g. How to pass the model as a redirect attribute in spring

Redirect to another endpoint after calling a function

I have a subrouter with prefix /api/a and I want to redirect, all requests to endopoint with prefix api/b/{aid}, to api/a after calling a function (which sets context.) How to achieve this?
When there is a call to /api/a/<whatever>, I take aid from cookies and add to request context but there are other endpoints where cookies doesn't contain aid parameter so I take it as a route param. What I want is any call to /api/b/{aid}/<whatever> is redirected to /api/a/<whatever> after I set aid in its request context (from route param).
I absolutely don't want to use client side redirects, he doesn't need to know.
Also I know that I can pass directly to any function with http.Handler signature. Its exactly what I want to avoid.
There are a fixed set of endpoints in which cookie won't be set (and thats intended, so I don't want to set one) and this set would always be prefixed by say /api/b/{aid}.
Since there are 150+ endpoints and handlers already configured to the subrouter prefixed /api/a (lets call it A). There is another subrouter B with prefix /api/b/{aid}. I want to redirect all calls to subrouter B to subrouter A after calling a function. It won't be logical to copy the entire list of handlers for separate subrouter if I want the exact same thing.
You cannot set a value in the request context, then redirect to some other URL and expect to get that value. You have to pass that value as part of the redirect using a query param, or a cookie, etc. Or you can do this without a redirect, and call the handler for the redirected URL directly.
If you want to do this with a cookie, you can get the aid in a handler, set a cookie containing aid using http.SetCookie, and redirect using http.Redirect. The other handler should receive the cookie.
If you want to do this using a query param, you can write the redirect URL with the added query parameter, redirect using http.Redirect, and parse it in the other handler.
If you want to do this without a redirect, you can get aid in one handler, put it into the request context, and call the other handler directly:
request.WithContext(context.WithValue(request.Context(),key,aid))
otherHandler(writer,request)
You can automate forwarding to the other caller using Router.Match with a single handler:
Register a handler for pathprefix /api/b/{aid}/
Handler parses aid
Handler rewrites a url /api/a/<remaining path>, sets request.URL to it, and uses router.Match to find the corresponding path under /api/a subtree
Handler calls the handler for /api/a/<remaining path>

Redirect with data

After successful create user Model (for example) I need to redirect request to... for example root page. But I wanna send message for ex. "User has been created!".
i can redirect with:
c.Redirect(http.StatusCreated, "/")
but how I can add message?
I tried (guess it was bad idea)
c.Set("message": "Message")
and in root page
s.MustGet("message")
but if root page loads without payload message it complain with panic.
Pls suggest best way for redirection with data.
EDIT
Unfortunately c.Set() doesn't work, guess it's because of redirect.
Maybe some one suggest any tip to send data to redirect?
You can always call c.GetString("message") instead of c.MustGet("message")
MustGet panics if the key does not exist as opposed to Get which lets you handle existence of key and Get sounds more appropriate for your use-case
For case with Redirect it is impossible send data in requests. So in this case uses Session.
According to DOCUMENTATION
r := gin.Default()
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("mysession", store))
session.Set("message", "Oh-ho!")
session.Save()

golang martini session.Set not setting any value

There isn't much context with this because it really is a situation where something should work, but it just doesn't.
I am using the martini framework. In one handler I'm using this:
session.Set("deployed", "true")
r.Redirect("/myOtherURL", http.StatusSeeOther)
Whereby 'session' is the sessions.Session object passed through to the handler.
In the handler that loads myOtherURL, I'm using session.Get but nothing is being returned. I printed out all of the session content and the 'deployed' is not present.
What could be causing this problem? What could I possibly be missing out? I would give more context if I could but it really is as simple as this.
Just to extend on my comment/help others in the future:
When you set a cookie without an explicit Path value, the cookie takes on the current path.
Cookies are only sent for that path and paths below - not above - e.g.
Cookie set for /modules when you implicitly set it for the first time via session.Set(val, key)
Cookie is sent for /modules, /modules/all and /modules/detail/12
Cookie is NOT sent for /about or /
This can be fixed by explicitly setting the path:
var store = sessions.NewCookieStore([]byte("secret123"))
func main() {
store.Options.Path = "/"
...
}
Be aware that you may not want to send a cookie for all routes (which is what / will do) - so use judgement.

Resources