Go multiple response.WriteHeader calls - http

So I'm writing a basic webapp and I'm having trouble redirecting the user after a sucessfull login. The code is:
t, err := template.ParseFiles("home.html")
if err != nil {
log.Fatal("template.ParseFiles: ", err)
}
err = t.Execute(w, nil)
if err != nil {
log.Fatal("t.Execute: ", err)
}
if r.Method == "POST" {
r.ParseForm()
user := r.FormValue("username")
pass := r.FormValue("password")
if checkLogin(user, pass) {
loggedIn = true
http.Redirect(w, r, "/home", 302)
}
}
The error message is: "http: multiple response.WriteHeader calls".
My problem is that I don't see a way to serve the html file containing the login-form without calling t.Execute which sets the header.
How can I display the login page and still be able to redirect to a different page?

You are writing (using w) and then later trying to redirect (also using w) using 302 header redirection.
You can only send headers once, and if you start writing to w it assumes a 200 header (OK)
Also, Its best if you check the http.Method before writing to the ResponseWriter (w)
And, Remember to return after a redirection or handing over the ResponseWriter and Request pair to another function!
Hope this helps.

How can I display the login page and still be able to redirect to a different page?
Have a different route for authentication. Make the login form submit to the authentication route. Have a separate handler for authentication as well.
For example, your login form:
<form method="post" action="/auth">
your Go main:
http.HandleFunc("/", homeHandler)
http.HandleFunc("/auth", authHandler)
When authentication processing is complete you can redirect the user to the appropriate page. You could pass a parameter in the query string that contains the destination path for the redirect.

Related

Golang Gin: Middleware with CORS not working

I've got a POST request from my frontend app to my backend app written in Go using Gin. I was getting an error saying:
"No 'Access-Control-Allow-Origin' header is present on the requested resource"
so that pointed me to implement CORS in my backend app. So I did by using "github.com/gin-contrib/cors":
web.go:
func NewApp() *App {
db := SetUpDB()
router := gin.Default()
router.Use(cors.New(cors.Config{
//AllowOrigins: []string{"http://localhost:3000", "http://127.0.0.1:3000"},
AllowMethods: []string{"PUT", "POST", "GET", "OPTIONS","DELETE"},
AllowHeaders: []string{"Origin"},
AllowAllOrigins: true,
//ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
return &App{
Db: db,
Router: router,
}
}
and in main.go I've got:
app := core.NewApp()
//app.Router.Use(CORS())
defer func() {
app.Db.Close()
log.Printf("core: database stopping")
}()
app.Router.Use(func(c *gin.Context) {
c.Set("db", app.Db)
})
app.Router.GET("/", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{"data": "welcome TEST"})
})
// Initialize all api routes
routes.InitializeRoutes(app.Router.Group("/api/v1"))
as you can see I only set PUT in AllowMethods with the intention of testing CORS was actually working. By allowing only PUT I was expecting no methods other than PUT were allowed but I was wrong. I've performed a GET request from my frontend app and it goes through (it returns data), this leads me to think than the CORS implementation is not being picked up.
While browsing around, I've found people not using the package "github.com/gin-contrib/cors" but creating their own middleware:
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println(c.Request.Header)
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, Origin, Cache-Control, X-Requested-With")
//c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Methods", "PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
and then:
func NewApp() *App {
db := SetUpDB()
router := gin.Default()
router.Use(CORS())
return &App{
Db: db,
Router: router,
}
}
I tried this as well with no luck. Same results are coming back.
Furthermore, when I perform the GET and print the method in my backend (c.Request.Method) the result is GET. But when I perform a POST and print the method I'm getting OPTIONS
What am I missing? Why router is not using the provided middleware?
There are two pieces to this question:
The first one was indicated above by Heiko: Get is a simple request so the result is always gonna be returned for these kind of requests.
Now, after testing back my POST, I was still getting errors. I had checked over and over the CORS config, changing things here and there just to find out that the routes for Category were define such as:
categoryRouter.POST("/", controllers.CreateNewCategory)
categoryRouter.GET("/", controllers.ListAllCategories)
as you can see there is a trailing / which was causing my request to be redirected and an error to be returned since the url used for the request was http://127.0.0.1:8080/api/v1/categories. I updated the routes to be:
categoryRouter.POST("", controllers.CreateNewCategory)
categoryRouter.GET("", controllers.ListAllCategories)
and now it is working as expected.
The Access-Control-Allow-Methods header is only checked for CORS requests that cannot result from a Javascript-less HTML page (so-called non-simple requests). For simple requests such as GET with only standard headers, only the Access-Control-Allow-Origin header is checked and the Access-Control-Allow-Methods header plays no role.

Send few response through one http.ResponseWriter

after I click on post button on frontend I receive data to my handler, create email and send it, but I want to inform user about status of email(sended or not) and redirect it back to main page. Problem is that I dont know how to pass alert and redirecting to main page at once.
Here is a handler code:
func contactHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
r.ParseForm()
/* creating email */
err := smtp.SendMail("smtp.gmail.com:587", smtp.PlainAuth("", *emailFromLogin, *emailFromPassword, "smtp.gmail.com"), *emailFrom, []string{*emailTo}, []byte(msg))
http.Redirect(w, r, "/", http.StatusMovedPermanently)
}
}
That what I want send to user before redirecting.
if err != nil {
fmt.Fprint(w, "<script>Email didn't send<script>")
} else {
fmt.Fprint(w, "<script>Email sent successfully<script>")
}
You can't send data back with a redirect request, so here are 2 proposed alternatives.
Client side navigation
One option would be to send back a normal response with the message you want to show to the user, optionally with the URL to move to. After this, the client may do the navigation.
This may be an AJAX request, and client side JavaScript can process the result, and act upon it: display the message, and navigate. Here are some ways for client side JavaScript navigation:
window.location = "http://new-website.com";
window.location.href = "http://new-website.com";
window.location.assign("http://new-website.com");
window.location.replace("http://new-website.com");
Encode message in URL as parameter
Another common way is to send a redirect, and encode the message in the new URL as a query parameter, such as:
newPath := "/?msg=Hello"
This is a simple example, but if the message is more complex, you have to escape it to get a valid path. For that, you may use url.QueryEscape() like this:
msg := "Email didn't send"
path := "/?msg=" + url.QueryEscape(msg)
This would result in a path /?msg=Email+didn%27t+send (try it on the Go Playground). Another option would be to use url.URL and url.Values to assemble the URL, for example:
values := url.Values{}
values.Add("msg", "Email didn't send")
url := &url.URL{
Path: "/",
RawQuery: values.Encode(),
}
fmt.Println(url)
The above prints (try it on the Go Playground):
/?msg=Email+didn%27t+send
Of course, the new page must handle that query parameter, e.g. use JavaScript to detect if in this case the msg query param exists, and if so, display it somehow to the user.
Also note that you shouldn't use the StatusMovedPermanently code for this redirect, as that may be cached by browsers and proxies. Instead use StatusFound. This status code indicates to clients that the redirection is temporary and may change in the future, so if the client needs the original URL again, it will query the original again (and not the new one automatically).

301 status code after PostForm

I am trying to write a program which will login to ahrefs.com and parse some data.
At first I am sending GET request to ahrefs.com to get cookies and html to parse needed token:
client := &http.Client{}
jar := &myjar{}
jar.jar = make(map[string] []*http.Cookie)
client.Jar = jar
resp, _ := client.Get("https://ahrefs.com")
root, _ := html.Parse(resp.Body)
element, _ := getElementByName("_token", root)
token := ""
for _, a := range element.Attr {
if a.Key == "value" {
token = a.Val
}
}
Then I am sending POST request using PostForm to ahrefs.com/user/login/. I fill the fields with correct data (tested it via browser). When I submit form in browser it has field return_to with value of main page of the site, which should redirect to ahrefs.com/dashboard/metrics/ (the page from I want to parse data). But my program's behavior is different. After PostForm I got 301 status code:
resp, _ = client.PostForm(
"https://ahrefs.com/user/login/",
url.Values{
"email": {"djviman#gmail.com"},
"password": {"Aau4bqRxfc4ZEvu"},
"_token": {token},
"return_to": {"https://ahrefs.com/"},
})
log.Println(resp.Status)
resp.Body.Close()
Then I am sending GET request to ahrefs.com/dashboard/metrics/ but it redirects me to the home page, like I'm not logged in:
resp, _ = client.Get("https://ahrefs.com/")
log.Println(resp.Status)
resp.Body.Close()
Questions are: what I am doing wrong? And hot to successfully log in to this site?

Golang - http.Redirect fails to redirect in a handler than handles post data

I was thinking of doing the following for the authentication.
(1) Client visits home,say http://localhost:3000
The client is shown a login form with username, password, gamecode.
(2) When the client submits this form, I use javascript to post the data to
http://localhost:3000/login that accepts POST requests. In my golang file the relevant route is defined as:
rtr := mux.NewRouter()
rtr.HandleFunc("/login", loginHandler).Methods("POST")
(3) Now I want to check if the three, username, password and the gamecode are valid. I start with the gamecode, and I have no difficulty reading the value submitted and checking in against the list of valid gamecodes in the database. Now, if the gamecode is invalid, I want to redirect the user to another route. I am failing to do so. These are the relevant lines:
// I check if gamecode is valid. If it is invalid, gameCodeExists = 0
if gameCodeExists == 0 {
// this message prints on the server as expected for an invalid code
fmt.Println("gamecode invalid...")
// this redirect never occurs
http.Redirect(w, req, "/loginfail", 302)
return
} else {
fmt.Println("Valid code")
}
Can someone please help me with what I am doing wrong, and how I can fix it?
Thank you.

Golang Modify HTTP Request Parameters Such As URL Path Before Routing

It's common practice in some cases to pass plain URIs as suffix of the path instead of a query parameter. Here is an example from Internet Archive's Wayback Machine.
https://web.archive.org/web/20150825082012/http://example.com/
In this example, user is requesting a copy of http://example.com/ as captured at 2015-08-25 08:20:12. If we were to implement similar service in Go, we probably would have a router as follows:
http.HandleFunc("/web/", returnArchivedCopy)
Then in the returnArchivedCopy handler function, we will split r.URL.Path (where r is the Request object) to extract the date-time and the target URL. However there is a problem in this style of URL scheme; Go's net/http package calls cleanPath function on the path portion to sanitize it. This sanitization process does various cleanup tasks such as eeliminating . and .. from the path and replace multiple slashes with a single one. This later operation makes sense when because in Unix systems // in the file path are same as /. However this causes an issue in the above described use case as http://example becomes http:/example and the server internally returns a redirect response to the client with the sanitized path.
I am wondering, what are my options in this case? Is there a way to ask HTTP not to sanitize the request path while still utilizing all the default behavior that is shipped with the default (or slightly modified) server, multiplexer, and handler? Or is there a way to modify the request parameters (path in this case) before it hits the multiplexer's routing patterns. If the later is possible, we might try to perform something like URL encoding to avoid the redirect and later decode the URL back in the handler function before extracting desired bits.
I have experimented with some custom handlers and multiplexers, but I am new to Go, hence I was not quite sure how to delegate the routing back to the default handlers after making changes in the request.
You can implement a wrapper mux, that falls back to the default one, here's a very simple example:
func main() {
http.HandleFunc("/blah", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("w00t"))
})
http.ListenAndServe(":9090", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
p := strings.SplitN(req.URL.RequestURI()[1:] /*trim the first slash*/, "/", 3)
if len(p) != 3 || p[0] != "web" {
http.DefaultServeMux.ServeHTTP(w, req)
return
}
t, err := time.Parse("20060102150405", p[1])
if err != nil {
http.Error(w, "invalid time", 400)
return
}
url := p[2]
fmt.Fprintf(w, "requested url %v # %v", url, t)
}))
}

Resources