Search code examples
gohttpcookies

Delete cookie on page refresh


I'm learning how to handle cookies in Go with the std library so i made a basic server with a couple handlers, one of which checks for the existence of the cookie, if it exists, displays the value and sets cookie to be deleted, if it doesn't just prints to the page. however when i refresh the page the cookie stays until it naturally expires and only then will it print to the page.

Here's the handler that does this:

func finishHandler(w http.ResponseWriter, r *http.Request) {
    cookieVal, err := r.Cookie("message")
    if err != nil || cookieVal.Value == "" {
        fmt.Fprintln(w, "Cookie is gone")
    } else {
        fmt.Fprintln(w, "Cookie with value: "+cookieVal.Value+", refresh should delete it.")
        http.SetCookie(w, &http.Cookie{
            Name:    "message",
            Value:   "",
            MaxAge:  -1,
            Path:    "/",
            Expires: time.Now().Add(-100 * time.Second),
        })
    }
}

So obviously http.SetCookie() isn't setting anything.

I went back and forth changing the values of http.Cookie{} according to https://go.dev/src/net/http/cookie.go and other posts here on SO (set same name, specify path, MaxAge<0, etc) but it just won't go through.

When using gorrilla/sessions this doesn't happen but i wanted to understand how to do it with the std library and why the cookie isn't being deleted even though i'm doing it the correct way as per the docs.

Is it because i'm refreshing the page and the cookie stays cached? If so, how can i work around this in order for the cookie to be deleted as instructed?

EDIT: same thing happens with gorilla/sessions i assumed it didn't because i did something similar with sessions and it worked but it's a button, not a refresh. However upon reproducing this same example, the session still remains. In that manner and since it can be something with the workflow itself i'll share it here for context:

var (
    templates = template.Must(template.ParseGlob("templates/*"))
    port      = ":8080"
)

func startHandler(w http.ResponseWriter, r *http.Request) {
    err := templates.ExecuteTemplate(w, "ch6-flash.html", nil)
    if err != nil {
        log.Fatal("Template ch6-flash missing")
    }
}

func middleHandler(w http.ResponseWriter, r *http.Request) {
    cookieValue := r.PostFormValue("message")
    cookie := http.Cookie{Name: "message",
        Value:    "message:" + cookieValue,
        Expires:  time.Now().Add(60 * time.Second),
        HttpOnly: true,
    }
    http.SetCookie(w, &cookie)
    http.Redirect(w, r, "/finish", http.StatusMovedPermanently)
}

func finishHandler(w http.ResponseWriter, r *http.Request) {
    cookieVal, err := r.Cookie("message")
    if err != nil || cookieVal.Value == "" {
        fmt.Fprintln(w, "Cookie is gone")
    } else {
        fmt.Fprintln(w, "Cookie with value: "+cookieVal.Value+", refresh should delete it.")
        http.SetCookie(w, &http.Cookie{
            Name:     "message",
            Value:    "",
            MaxAge:   -1,
            Path:     "/",
            Expires:  time.Now().Add(-100 * time.Second),
            HttpOnly: true,
        })
    }
}


func main() {
    http.HandleFunc("/start", startHandler)
    http.HandleFunc("/middle", middleHandler)
    http.HandleFunc("/finish", finishHandler)
    log.Fatal(http.ListenAndServe(port, nil))
}

and for completeness sake here's the HTML referenced:

<html>
<head>
    <title>Flash Message</title>
</head>
<body>
    <form action="/middle" method="POST">
        <input type="text" name="message" />
        <input type="submit" value="Send Message" />
    </form>
</body>
</html>

Maybe, hopefully, it's something stupid, maybe it is the cookie getting cached by the browser and not being updated. Idk, i'm stumped.


Solution

  • The docs for http.ResponseWriter state:

    Write writes the data to the connection as part of an HTTP reply. If ResponseWriter.WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) before writing the data.

    So the first call to Write sends the headers (including any cookies). This means that setting a cookie after calling Write has no effect. This is what you are doing in your example (fmt.Fprintln will call Write):

    fmt.Fprintln(w, "Cookie with value: "+cookieVal.Value+", refresh should delete it.")
    http.SetCookie(w, &http.Cookie{...
    

    Move the fmt.Fprintln under the http.SetCookie so the cookie is set before the first write (which will mean it's included in the headers transmitted).

    Note: Using the developer tools in your browser you can check what is actually received (in this case you would see that the cookie header is not received). This is often a good first step when debugging.