HTTP context
This guide covers the HTTP context in Tinka. You will learn how to:
- Use the
*tinka.Ctxa handler receives - Pass it wherever a
context.Contextis expected - Reach the request and the response writer when you need them
- Resolve request-scoped dependencies from the container
Overview
Every request gets one HTTP context, a *tinka.Ctx, which the App passes to each middleware and to the handler. It holds everything about the request in one place: the incoming *http.Request, the response being built, the request's ID, and the request's dependencies. Handlers take it as their only argument, so their signatures stay the same however much of the request they use:
package main
import (
"github.com/mucolabs/tinka"
)
type Post struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
func showPost(c *tinka.Ctx) (Post, error) {
id, err := c.ParamInt("id")
if err != nil {
return Post{}, err
}
return Post{ID: id, Title: "Hello, Tinka"}, nil
}A Ctx has methods for the request, such as Param, Query, IP and Accepts, and for the response, such as Status and Header. Those guides cover them.
Pass it as a context
A Ctx is a context.Context: its deadline, cancellation and values are the request's. Pass it to the database, to HTTP clients and to your own code, and their work stops when the client disconnects or the request times out:
func (s *PostService) Find(ctx context.Context, id int64) (Post, error) {
var p Post
err := s.db.QueryRowContext(ctx, "SELECT id, title FROM posts WHERE id = $1", id).Scan(&p.ID, &p.Title)
return p, err
}
func (h Posts) Show(c *tinka.Ctx) (Post, error) {
id, err := c.ParamInt("id")
if err != nil {
return Post{}, err
}
return h.Service.Find(c, id)
}Records you log with it carry the request's ID as request_id, as the Logger guide shows. c.ID() returns the same ID, which the response also carries in its X-Request-Id header.
Reach the request and the response writer
c.Request() returns the *http.Request, for anything the Ctx has no method for, such as request headers:
agent := c.Request().Header.Get("User-Agent")c.ResponseWriter() returns the http.ResponseWriter, for a handler that writes the response itself, such as one that streams Server-Sent Events. Once anything is written, the handler's returned value is ignored. When a middleware replaces the request or the writer, as tinka.Timeout and compression middleware do, both methods return the replacement.
Resolve request-scoped dependencies
Give the App your dependency injection container, and c.Make resolves from it through a resolver made for this request. A Scoped binding is built once per request, and can ask for the *tinka.Ctx itself:
container := di.New()
container.Scoped(func(r *di.Resolver) (*Cart, error) {
c := r.MustMake[*tinka.Ctx]()
return carts.ForSession(c, c.Query("session"))
})
app := tinka.New(tinka.HTTPConfig{Container: container})
app.Get("/cart", func(c *tinka.Ctx) (*Cart, error) {
return c.Make[*Cart]()
})Resolve singletons at boot instead, and capture them in your handlers: a request that never calls Make pays nothing for the container.
See also: Request, Response, Middleware, Dependency injection