I setup a middleware and router to handle an AJAX request on path /api/on like this
http.HandleFunc("/",route.IndexHandler)
http.HandleFunc("/login",route.GoogleLoginHandler)
http.HandleFunc("/auth/google",route.GoogleCallbackHandler)
http.Handle("/dashboard",negroni.New(
negroni.HandlerFunc(route.IsAuthenticated),
negroni.Wrap(http.HandlerFunc(route.DashboardHandler)),
))
http.Handle("/api/on",negroni.New(
negroni.HandlerFunc(route.IsAuthenticated),
negroni.Wrap(http.HandlerFunc(route.TurnOn)),
))
Middleware IsAuthenticated allows to jump to the next router if there is a user session in my web, otherwise it will redirect
func IsAuthenticated(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
session, _ := util.GlobalSessions.SessionStart(w, r)
defer session.SessionRelease(w)
if session.Get("profile") == nil {
http.Redirect(w, r, "/", http.StatusMovedPermanently)
} else {
next(w, r)
}
}
And this is TurnOn handler for later process
func TurnOn(w http.ResponseWriter, r *http.Request) {
session, _ := util.GlobalSessions.SessionStart(w, r)
defer session.SessionRelease(w)
w.Header().Set("Access-Control-Allow-Origin","*")
w.Header().Set("Access-Control-Allow-Methods","POST")
w.Header().Set("Access-Control-Allow-Headers","Content-Type")
fmt.Println(r.URL)
}
After I turned on server, I conducted a POST request with JavaScript's client-side code using AJAX, but the console logged nothing. It seems that the TurnOn function was not reached even when AJAX request was sent successfully. Did I miss something obviously?
Related
I'm trying to integrate saml using crewjam library with an open-source app in go.
After authentication test using samltest.id, I want to be redirected to the home page.
I have tried several ways, but nothing works well, i'm using gorilla/mux router:
func login(w http.ResponseWriter, r *http.Request) {
s := samlsp.SessionFromContext(r.Context())
if s == nil {
return
}
sa, ok := s.(samlsp.SessionWithAttributes)
if !ok {
return
}
fmt.Fprintf(w, "Token contents, %+v!", sa.GetAttributes())
w.Header().Add("Location", "http://localhost:8080/")
w.WriteHeader(http.StatusFound)
}
I have also tested :
http.Redirect(w, r, "http://localhost:8080/", http.StatusFound)
Can someone help me please?
Thanks :)
Calling w.Write or writing into it using Fmt.Fprintf requires HTTP status code to be set before, otherwise it sets default StatusOK
Server.go
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
Setting the status code multiple times throws superfluous log.
Therefore, Your code is setting the HTTP status code to 200 (http.StatusOk), so the redirect after that is simply impossible.
Solution:
func login(w http.ResponseWriter, r *http.Request) {
s := samlsp.SessionFromContext(r.Context())
if s == nil {
return
}
sa, ok := s.(samlsp.SessionWithAttributes)
if !ok {
return
}
// this line is removed
// fmt.Fprintf(w, "Token contents, %+v!", sa.GetAttributes())
w.Header().Add("Location", "http://localhost:8080/")
w.WriteHeader(http.StatusFound)
// Or Simply
// http.Redirect(w, r, "http://localhost:8080/", http.StatusFound)
}
Try to send your headers before writing content.
And optionally use a relative Location
w.Header().Add("Location", "/")
w.WriteHeader(http.StatusFound)
fmt.Fprintf(w, "Token contents, %+v!", sa.GetAttributes())
I have the following middleware, first sets currentUser in gorilla/context to current user acquired from database, and the second checks if currentUser exists and redirects otherwise:
package main
import (
"database/sql"
"github.com/gorilla/context"
"log"
"net/http"
"server/helpers"
)
func withCurrentUser(db *sql.DB, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userId := helpers.GetCurrentUserId(db, r)
if userId == nil {
next.ServeHTTP(w, r)
return
}
row := db.QueryRow("SELECT username FROM User WHERE id=?", userId)
var username string
switch err := row.Scan(&username); err {
case sql.ErrNoRows:
next.ServeHTTP(w, r)
return
case nil:
user := helpers.User{UserId: *userId, LoggedIn: true, Username: username}
context.Set(r, "currentUser", user)
default:
log.Fatal(err)
}
next.ServeHTTP(w, r)
})
}
func loginRequired(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := context.Get(r, "currentUser").(helpers.User)
log.Println(user, ok)
if !ok {
http.Redirect(w, r, "/login", 301)
return
}
next.ServeHTTP(w, r)
})
}
Then when I register a route that requires authenticated user, I do the following:
router.Handle("/create_post",
withCurrentUser(db, loginRequired(http.HandlerFunc(createPostGet))),
).Methods(http.MethodGet)
Where createPostGet:
func createPostGet(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/createPost.html"))
user := context.Get(r, "currentUser").(helpers.User)
_ = tmpl.Execute(w, helpers.FormStruct{CurrentUser: user})
}
My problem is: even if user is authenticated and context is populated correctly, this route always redirects to login. I tried setting breakpoints inside loginRequired and adding log.Println (as you can see in code), and that function doesn't seem to be even called (no breakpoints stop, no log output, too).
What's happening and how to make sure loginRequired is called every time and checks context properly?
UPD: It doesn't seem to persist, I recompiled the app a few times and now it's working. Anyway, what might be the reason for such behavior (I'm positive I saved everything the first time).
UPD 2: I found out that the problem is connected to browser caching, but I still have no idea why it happens. When I disable browser caching, everything works and function is called every time, but while browser cache is enabled, the function isn't called at all. Any ideas?
OMG, that was stupid. I was using 301 redirect code in loginRequired, so redirect was permanent and browser didn't even make requests.
While using Laravel, I really appreciated being able to use:
return Redirect::back();
to return to the previous URL after a POST request.
Is there a simple in-built way to do this in Go?
http.Redirect(w, r, backURL, http.StatusSeeOther)
where backURL is the URL that the POST request was made from.
I've looked through net/http and searched SE and google, but I haven't turned anything up. Failing an easy way, I'd appreciate any pointer toward the idiomatic way of doing this in Go.
The Redirect::back() function uses the URL from the Referer HTTP header specified by the client (browser).
You could access this header value like r.Header.Get("Referer"), but the http.Request type also provides a direct Request.Referer() method that returns the value of this Referer field, which also deals with its 2 possible forms ("Referer" and "Referrer").
This is how you can mimic the "go back" behavior:
func PostHandler(w http.ResponseWriter, r *http.Request) {
// Process form, then:
if rf := r.Referer(); rf != "" {
http.Redirect(w, r, rf, http.StatusSeeOther)
return
}
// No Referer specified, supply your own response
// or redirect to a default / home page
http.Redirect(w, r, "/", http.StatusSeeOther)
}
If you want to call this from many handlers, you may capture this functionality in a helper function:
func redirectBack(w http.ResponseWriter, r *http.Request) {
if rf := r.Referer(); rf != "" {
http.Redirect(w, r, rf, http.StatusSeeOther)
return
}
// No Referer specified, supply your own response
// or redirect to a default / home page
http.Redirect(w, r, "/", http.StatusSeeOther)
}
And then using it:
func PostHandler(w http.ResponseWriter, r *http.Request) {
// Process form, then:
redirectBack(w, r)
}
I have a very weird output ... let me post my code first then I will explain:
Under main function I declared
manageMux.HandleFunc("/info", info)
first I log in and redirect from "/login" to page "/":
func login(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
t, err := template.ParseFiles("manage/login.html")
checkError(err)
t.Execute(w, nil)
} else { //POST
r.ParseForm()
//do some authentications here
http.Redirect(w, r, "/", http.StatusFound)
}
}
Then I redirect to another page "/info" from current page "/" (which has only buttons):
func manage(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("manage/manage.html")
checkError(err)
t.Execute(w, nil)
r.ParseForm()
if r.Form["Button"] != nil { //to get only POST actions from buttons
if r.Form["Button"][0] == "Log" {
http.Redirect(w, r, "/info", http.StatusFound)
}
}
}
At last, I made a template and would like to show on client side:
const tpl=`stuff inside`
type InfoDefault struct {
//stuff inside
}
func info(w http.ResponseWriter, r *http.Request) {
info := InfoDefault{
//stuff inside
}
t, err := template.New("info").Parse(tpl)
checkError(err)
err = t.Execute(os.Stdout, info)
checkError(err)
}
Now, the weird thing is, when I click the button on page "/", I got the error "http: multiple response.WriteHeader calls". At the same time a link called "found" shows up on the bottom of my page (weird!), and when I click the link "found", I got all my parsed template printed on the server side instead of webpage.
Does anyone know why...? And how to fix the error and print stuff on client webpage? Thank you!!!
As JimB already pointed out: your server gets confused because there are different status codes associated with both writing to http.ResponseWriter and the redirect. You can't do both at the same time.
I would actually like to expand more on how you can carry data over to the next page (assuming you are redirecting).
Headers
You can write some information to the request object and receive it on the destination page. Example:
func myHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("My-Awesome-Header", "Rocks")
...
}
Session:
You are talking about access control as far as I can see, and I think persisting user data is better done through a session. Example: you can use a database or a session handler like https://github.com/gorilla/sessions. Check out this thread: Best practice with sessions (gorilla/sessions).
Cookies:
I'm not sure what kind of front-end you are using, but storing non-sensitive data on the cookie could be an option? Nothing beats this one (it has real choc-chip cookies in the examples ;-) ): https://astaxie.gitbooks.io/build-web-application-with-golang/content/en/06.1.html.
In your manage handler, you're executing the template which will write to the http.ResponseWriter and trigger an http.StatusOK (200) status code. You can't redirect after that, since that requires sending a different response code.
If you need to redirect, do it before executing the template.
I've built an application that uses the Go Gorilla sessions package. Everything seems fine, except when on logout I implement
func logout(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "authsesh")
session.Values["access"] = "denied"
session.Save(r, w)
http.Redirect(w, r, "/", 302)
return
}
Because the page requiring authentication is cached by the browser, it can still be accessed after logout. How can I get around that? Is there a way to prevent the browser from caching the page? There's nothing wrong with the cookie, if I clear the cache and keep the cookie I can see the logout has had the desired effect.
Set the correct cache headers in your handler(s):
w.Header().Set("Cache-Control", "no-cache, private, max-age=0")
w.Header().Set("Expires", time.Unix(0, 0).Format(http.TimeFormat))
w.Header().Set("Pragma", "no-cache")
w.Header().Set("X-Accel-Expires", "0")
Note that we set multiple headers to account for proxies and HTTP/1.0 clients.
You can wrap these into middleware you can apply as well:
func NoCache(h http.Handler) http.Handler) {
fn := func(w http.ResponseWriter, r *http.Request) {
// Set the headers
}
return http.HandlerFunc(fn)
}
// In your router
http.Handle("/user-dashboard", NoCache(http.HandlerFunc(YourDashboardHandler))