skip to content

Request Inputs

What a request actually carries and how Go hands it over: a URL whose Path is decoded while RawPath keeps escapes, a body you read once, multipart that spills to disk, and cookies as a struct.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

20

In Go's net/http, how do you set a cookie on a response and read it back on the next request?

level: juniorimportance: must knowfreq 78%

answer

  1. one call writes it, one call reads it
  2. the write side takes a struct pointer
  3. the read side returns a sentinel error
  4. attributes travel outbound only
  5. must run before the first response write

basics

~20 s

Build an http.Cookie value and pass it to http.SetCookie(w, c) before writing anything to the response; it adds a Set-Cookie header. On a later request, call r.Cookie("name"), which returns the cookie or the error http.ErrNoCookie.

solid answer

~40 s

On the way out you fill an `http.Cookie` struct — `Name`, `Value`, plus the attribute fields `Path`, `Domain`, `MaxAge`, `Secure`, `HttpOnly`, `SameSite` — and call `http.SetCookie(w, c)`. That function returns nothing; it just formats the struct and adds a `Set-Cookie` header, so it has to run before the first `w.Write` or `w.WriteHeader`, after which the header map is already flushed. On the way in, `r.Cookie("session")` returns `(*http.Cookie, error)` and the error is `http.ErrNoCookie` when the request carries no cookie by that name; `r.Cookies()` returns every cookie on the request. The cookie you read back has only `Name` and `Value` filled in, because a browser sends `Cookie: session=abc` with no attributes — `Path`, `MaxAge` and the flags are directives you send to the browser, not data it sends back.

code

go · 22 lines
go
func login(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:     "session",
		Value:    newSessionID(),
		Path:     "/",
		MaxAge:   3600,
		HttpOnly: true,
		Secure:   true,
		SameSite: http.SameSiteLaxMode,
	})
	w.WriteHeader(http.StatusNoContent) // SetCookie had to come first
}

func whoami(w http.ResponseWriter, r *http.Request) {
	c, err := r.Cookie("session")
	if err != nil { // http.ErrNoCookie when the header is absent
		http.Error(w, "no session", http.StatusUnauthorized)
		return
	}
	// c.Name and c.Value are set; c.MaxAge and c.Secure are zero values
	fmt.Fprintln(w, c.Value)
}

go deeper

for a junior

Be ready to write the two calls from memory: http.SetCookie(w, &http.Cookie{...}) on the response, and r.Cookie("name") on the request with http.ErrNoCookie as the miss case.

for a middle

Explain the ordering constraint — the header map is committed on the first write — and why an inbound cookie carries only Name and Value while attributes are outbound directives.

for a senior

Show the judgment of setting Path and the flag fields explicitly on every cookie a service issues, rather than relying on zero values, so the emitted header is the same on every code path.

for a principal

Be able to argue for one small helper that issues the service's cookies with agreed defaults, so scope and flags are set in one reviewed place instead of being retyped in each handler.

## The two halves of the API Go's `net/http` models a cookie as a plain struct, `http.Cookie`, and gives you one helper for each direction. **Writing.** `http.SetCookie(w http.ResponseWriter, cookie *http.Cookie)` formats the struct into a `Set-Cookie` header line and adds it to the response header. Note what it does *not* do: it returns no error, and it does not encode your value for you. Calling it twice adds two `Set-Cookie` lines, which is how you set several cookies in one response. Because it works by mutating the response header map, it must be called **before** the response header is written. The header is committed the first time you call `w.WriteHeader(...)` or the first time you call `w.Write(...)` (which implicitly writes a 200). A `http.SetCookie` after either of those is silently ineffective — the bytes are already on the wire. This is the single most common junior mistake with the API. **Reading.** `(*http.Request).Cookie(name string) (*http.Cookie, error)` looks through the request's `Cookie` header for a cookie with that exact name (names are case-sensitive) and returns it. When there is no such cookie the error is the sentinel `http.ErrNoCookie`, so the idiomatic handler is: ```go c, err := r.Cookie("session") if err != nil { // only possible error is http.ErrNoCookie } ``` `(*http.Request).Cookies()` returns `[]*http.Cookie` for everything the request carried. ## Why the cookie you read back looks empty A browser does not echo attributes. The response says: ``` Set-Cookie: session=abc123; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Lax ``` but the next request says only: ``` Cookie: session=abc123 ``` So the `*http.Cookie` that `r.Cookie` hands you has `Name` and `Value` populated and everything else at its zero value. `Path`, `Domain`, `MaxAge`, `Expires`, `Secure`, `HttpOnly` and `SameSite` are *instructions to the browser* about when to send the cookie and who may read it; they are one-way. A handler that tries to read `c.Secure` or `c.MaxAge` off an inbound cookie to make a decision is reading a zero value, not a fact. The same struct type is used for both directions, which is what makes this confusing the first time. Think of `http.Cookie` as "the union of what you can send and what you can receive", with the receive side being a strict subset. ## The struct in practice ```go http.SetCookie(w, &http.Cookie{ Name: "session", Value: sid, Path: "/", MaxAge: 3600, HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode, }) ``` - `Path` scopes which URLs get the cookie back. Leave it empty and Go emits no `Path` attribute at all, which makes the browser scope the cookie to the directory of the URL that set it — rarely what you want. `Path: "/"` is the usual choice. - `Domain` left empty produces a host-only cookie for the exact host that set it. That is usually the right default; you only set it to share a cookie across subdomains. - `MaxAge` is a lifetime in seconds; leaving it at zero produces a cookie with no lifetime attribute, which lives until the browser session ends. - `Secure`, `HttpOnly` and `SameSite` are the flag fields. `SameSite` takes one of `http.SameSiteDefaultMode` (the zero value, which emits no attribute), `http.SameSiteLaxMode`, `http.SameSiteStrictMode` or `http.SameSiteNoneMode`. ## On the client side of the same struct The struct is symmetric for outbound *requests* too: `(*http.Request).AddCookie(c *http.Cookie)` appends a name/value pair to the request's `Cookie` header, and it deliberately ignores the attribute fields, since a client never sends them. That is the method to reach for when you are writing the caller rather than the server. ## What to remember `http.SetCookie` on the way out, before the first write, with no error to check. `r.Cookie` on the way in, with `http.ErrNoCookie` as the only error. Attributes travel outbound only.

  • What exactly does r.Cookie return when the request carries no cookie of that name?
    A nil `*http.Cookie` and the sentinel error `http.ErrNoCookie`. That is the only error the method produces, so `if err != nil` is enough, though comparing with `errors.Is(err, http.ErrNoCookie)` documents the intent. Do not dereference the returned pointer before checking the error.
  • Why are Path, MaxAge and Secure empty on the cookie you get from r.Cookie?
    Because the browser never sends them. A request's `Cookie` header is just `name=value` pairs; every attribute lives only in the outbound `Set-Cookie` header and is a directive to the browser about scope and lifetime. The inbound struct therefore has those fields at their zero values, and reading them tells you nothing.
  • What happens if you call http.SetCookie after writing the response body?
    Nothing useful. The first `w.Write` or `w.WriteHeader` commits the status line and headers, so a later addition to the header map is never sent. The cookie silently fails to appear, with no error anywhere — set cookies at the top of the handler, before any output.

saying these in an interview costs you the question

  • Calls http.SetCookie after writing the response body
  • Reads c.Secure or c.MaxAge off an inbound request cookie
  • Expects http.SetCookie to return an error to check
  • Sets the Cookie header by hand instead of using http.SetCookie
  • Thinks the browser echoes Path and SameSite back
open as a page

In a Go HTTP handler, what does r.URL.Query().Get("page") return when the URL carries no page parameter?

level: juniorimportance: must knowfreq 78%

basics

~10 s

It returns the empty string and never an error, so a missing parameter and a present-but-empty one look identical. Use url.Values.Has, or index the url.Values map directly, when that difference matters.

open as a page

Why does r.Header.Get("x-request-id") find a header that r.Header["x-request-id"] misses?

level: juniorimportance: must knowfreq 68%

basics

~20 s

Go's http.Header is a plain map whose keys are stored in canonical form, such as X-Request-Id. Header.Get canonicalizes the name you pass before the lookup; indexing the map does not, so a lowercase key finds nothing.

open as a page

In Go, why build a query string with url.Values.Encode instead of concatenating the parameters yourself?

level: juniorimportance: must knowfreq 60%

basics

~10 s

url.Values.Encode escapes every key and value with url.QueryEscape, so an ampersand or equals sign inside a value cannot invent a new parameter. It also carries repeated keys and emits the pairs sorted by key.

open as a page

In a Go net/http handler, what does r.FormFile("avatar") return and what must the handler do with it?

level: juniorimportance: should knowfreq 55%

basics

~10 s

r.FormFile returns a multipart.File to read the upload from, a *multipart.FileHeader carrying the client-supplied filename, size and part headers, and an error. The handler must close the file, normally with defer file.Close().

open as a page

What do the three ranges of http.Cookie.MaxAge mean in Go, and how do you delete a cookie?

level: middleimportance: should knowfreq 60%

basics

~10 s

In http.Cookie, a positive MaxAge writes Max-Age in seconds, zero writes no Max-Age attribute at all (a session cookie), and a negative MaxAge writes Max-Age=0, which tells the browser to delete the cookie immediately.

open as a page

After r.ParseForm in a Go handler, what is the difference between r.Form and r.PostForm?

level: middleimportance: should knowfreq 60%

basics

~10 s

r.PostForm holds only values parsed from a urlencoded request body. r.Form holds those plus the URL query string merged in, with body values taking precedence. r.FormValue reads r.Form; r.PostFormValue reads r.PostForm.

open as a page

What does json.NewDecoder(r.Body).Decode(&v) accept in a Go handler that a strict API should reject?

level: middleimportance: should knowfreq 48%

basics

~20 s

By default it ignores JSON fields your struct does not declare, stops after the first JSON value so trailing data slips through, and reads without any size limit. Add DisallowUnknownFields, a second Decode expecting io.EOF, and http.MaxBytesReader.

open as a page

Why does r.Header.Get("Host") return an empty string inside a Go HTTP handler?

level: middleimportance: should knowfreq 45%

basics

~10 s

Go's HTTP server promotes the request's Host field into the Request.Host struct field and deletes it from the header map, so read r.Host. An HTTP/2 request's :authority pseudo-header lands in the same place.

open as a page

In Go's net/http, what does Header.Get return when a request carries the same field twice?

level: middleimportance: should knowfreq 55%

basics

~20 s

Header.Get returns only the first value. Go's http.Header maps each canonical field name to a slice with one entry per header line, so Header.Values gives every value in arrival order, Add appends a line and Set replaces them all.

open as a page

In Go's r.ParseMultipartForm(maxMemory), what does the maxMemory argument actually bound?

level: middleimportance: should knowfreq 44%

basics

~20 s

maxMemory caps the bytes of file parts kept in RAM; the rest spills to temporary files on disk. It is not an upload size limit — a huge body is still received in full, just written to disk.

open as a page

When should a Go handler use r.MultipartReader() instead of r.ParseMultipartForm?

level: middleimportance: should knowfreq 36%

basics

~20 s

Use r.MultipartReader when the upload should be processed as a stream: it hands back one part at a time, so nothing is buffered whole or spilled to a temp file. ParseMultipartForm instead reads the entire body first.

open as a page

What do url.URL.Path, url.URL.RawPath and the EscapedPath method each hold after url.Parse?

level: middleimportance: should knowfreq 42%

basics

~20 s

Path holds the decoded path. RawPath holds the original escaped path, but only when it differs from the default encoding of Path. EscapedPath returns RawPath when it is a valid encoding of Path, otherwise it re-encodes Path itself.

open as a page

Your Go login handler sends Set-Cookie but the next request has no session cookie — how do you diagnose it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

First decide whether the browser rejected the cookie or stored it and declined to send it, by comparing the raw Set-Cookie line with the browser's cookie store. Then check the Go fields that produce each case: Domain, Path, Secure and SameSite.

open as a page

Behind a CDN, r.RemoteAddr is the edge's IP — how do you recover the client address in Go?

level: seniorimportance: should knowfreq 58%

basics

~20 s

r.RemoteAddr is the connection's peer, which behind an edge is the proxy, in host:port form, so split it with net.SplitHostPort. Go never reads X-Forwarded-For for you; parse that list and count in from the right.

open as a page

Your validator calls url.Parse on a callback URL and compares u.Host to an allowlist — what slips through, and how does url.ParseRequestURI differ?

level: seniorimportance: should knowfreq 45%

basics

~20 s

url.Parse accepts relative references, so a string with no scheme and no host parses without error and leaves u.Host empty — the allowlist comparison then never runs against anything. url.ParseRequestURI accepts only an absolute URI or an absolute path.

open as a page

What does Go's http.SetCookie do to a cookie value containing a space, a semicolon or a quote?

level: middleimportance: nice to knowfreq 38%

basics

~20 s

Go sanitises rather than escapes: a value containing a space or a comma is wrapped in double quotes, and never-legal bytes such as semicolons, backslashes and quotes are dropped with a log line. Nothing is percent-encoded.

open as a page

Why does r.ParseForm leave r.PostForm empty, with no error, after middleware has read r.Body?

level: seniorimportance: nice to knowfreq 45%

basics

~20 s

r.Body is a one-shot stream over the connection. Once middleware drains it with io.ReadAll, ParseForm reads zero bytes, parses an empty form and returns nil. Buffer the bytes and reassign r.Body with io.NopCloser before calling the next handler.

open as a page

Why do multipart temp files pile up on disk in a Go upload service after every request?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

net/http removes the temp files behind r.MultipartForm when the handler returns. They pile up when your own code called mime/multipart's ReadForm, because the server never sees that form — defer its RemoveAll, or stream the parts so nothing spills.

open as a page

A round trip through url.Parse and url.Values.Encode broke our signed callback URLs — what does it not preserve?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Parsing a query into url.Values and re-encoding it canonicalises the bytes: parameter order becomes sorted by key, a space sent as %20 comes back as +, unnecessary escapes are dropped, and a valueless key gains a trailing equals sign. A signature over the original bytes then fails.

open as a page