tinka alpha

Request

This guide covers reading the request in Tinka. You will learn how to:

Overview

The HTTP context has methods for the parts of a request handlers read most. Each returns a plain Go value, and anything they don't cover is on the *http.Request that c.Request() returns. The body has its own guide, Body parser, because it needs decoding and validation.

Path parameters

c.Param returns a value the route pattern matched, and c.ParamInt returns it as a whole number, answering 422 when it isn't one:

comments.go
package main

import (
	"github.com/mucolabs/tinka"
)

type Comment struct {
	PostID int64  `json:"post_id"`
	Slug   string `json:"slug"`
}

// Registered as app.Get("/posts/{id}/comments/{slug}", showComment).
func showComment(c *tinka.Ctx) (Comment, error) {
	postID, err := c.ParamInt("id")
	if err != nil {
		return Comment{}, err
	}
	return Comment{PostID: postID, Slug: c.Param("slug")}, nil
}

Query string

c.Query returns the first value of a query string parameter, or "" when the request has none. c.QueryValues returns them all as url.Values, for parameters that repeat. The query string is parsed once per request, however often you read it.

posts.go
// GET /posts?page=2&tag=go&tag=web
func listPosts(c *tinka.Ctx) ([]Post, error) {
	page := c.Query("page")            // "2"
	tags := c.QueryValues()["tag"]     // ["go", "web"]
	return posts.List(c, page, tags)
}

Query values are text the client chose. Parse and check them before use, as you would a form field: strconv.Atoi(c.Query("page")) returns an error for ?page=two.

Headers

Read request headers from the request. Header.Get is case-insensitive and returns "" for a missing header:

go
	agent := c.Request().Header.Get("User-Agent")
	languages := c.Request().Header.Values("Accept-Language")

c.Header() returns the response's headers instead, which the Response guide covers.

Client IP address

c.IP returns the client's IP address. Without a proxy, it's the address of the connection, and a client can't change it with a header.

Behind a load balancer or reverse proxy, every connection comes from the proxy, which adds the client's address to the X-Forwarded-For header. List your proxies in TrustedProxies, and c.IP reads the header, from the nearest hop back, until it reaches an address no trusted proxy added:

main.go
	app := tinka.New(tinka.HTTPConfig{
		TrustedProxies: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")},
	})

Without TrustedProxies, only a proxy on the same machine (a loopback address) is trusted. An empty list trusts none.

Content negotiation

c.Accepts returns the format the client prefers among the ones you offer, by the request's Accept header and its quality values. Offer media types, such as application/json, or file extensions, such as json and html. With no Accept header the first offer wins, and so does the earlier offer when the client likes two equally. It returns "" when the client accepts none of them.

posts.go
func showPost(c *tinka.Ctx) (tinka.Response, error) {
	post, err := posts.Find(c, c.Param("id"))
	if err != nil {
		return nil, err
	}
	if c.Accepts("json", "html") == "html" {
		return tinka.HTML(renderPost(post)), nil
	}
	return tinka.JSON(post), nil
}

The App negotiates the same way for errors: a browser that asks for HTML gets an error page, and every other client gets problem details.

See also: HTTP context, Body parser, Response