id | title | sidebar_label |
---|---|---|
headers |
Using custom HTTP Headers |
Custom HTTP Headers |
Sometimes, you need to send custom HTTP headers.
For Twirp, HTTP headers are a transport implementation detail. You should not have to worry about them, but maybe your HTTP middleware requires them.
If so, there's nothing the Twirp spec that forbids extra headers, so go ahead. The rest of this doc is a guide on how to do this.
Use twirp.WithHTTPRequestHeaders
to attach the http.Header
to a particular
context.Context
, then use that context in the client request:
// Given a client ...
client := haberdasher.NewHaberdasherProtobufClient(addr, &http.Client{})
// Given some headers ...
header := make(http.Header)
header.Set("Twitch-Authorization", "uDRlDxQYbFVXarBvmTncBoWKcZKqrZTY")
header.Set("Twitch-Client-ID", "FrankerZ")
// Attach the headers to a context
ctx := context.Background()
ctx, err := twirp.WithHTTPRequestHeaders(ctx, header)
if err != nil {
log.Printf("twirp error setting headers: %s", err)
return
}
// And use the context in the request. Headers will be included in the request!
resp, err := client.MakeHat(ctx, &haberdasher.Size{Inches: 7})
Twirp client responses are structs that depend only on the Protobuf response. HTTP headers can not be used by the Twirp client in any way.
However, remember that the Twirp client is instantiated with an http.Client
,
which can be configured with any http.RoundTripper
transport. You could make a
RoundTripper that reads some response headers and does something with them.
In your server implementation code, set response headers one by one with the
helper twirp.SetHTTPResponseHeader
, using the same context provided by the
handler. For example:
func (h *myServer) MyRPC(ctx context.Context, req *pb.Req) (*pb.Resp, error) {
// Add Cache-Control custom header to HTTP response
err := twirp.SetHTTPResponseHeader(ctx, "Cache-Control", "public, max-age=60")
if err != nil {
return nil, twirp.InternalErrorWith(err)
}
return &pb.Resp{}, nil
}
Twirp server methods are abstracted away from HTTP, therefore they don't have direct access to HTTP Headers.
However, they receive the http.Request
's context.Context
as parameter that
can be modified by HTTP middleware before being used by the Twirp method.
In more detail, you could do the following:
- Write some middleware (a
func(http.Handler) http.Handler)
that reads the header's value and stores it in the request context. - Wrap your Twirp server with the middleware you wrote.
- Inside your service, pull the header value out through the context.
For example, lets say you want to read the 'User-Agent' HTTP header inside a twirp server method. You might write this middleware:
func WithUserAgent(base http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ua := r.Header.Get("User-Agent")
ctx = context.WithValue(ctx, "user-agent", ua)
r = r.WithContext(ctx)
base.ServeHTTP(w, r)
})
}
Then, you could wrap your generated Twirp server with this middleware:
h := haberdasher.NewHaberdasherServer(...)
wrapped := WithUserAgent(h)
http.ListenAndServe(":8080", wrapped)
Now, in your application code, you would have access to the header through the context, so you can do whatever you like with it:
func (h *haberdasherImpl) MakeHat(ctx context.Context, req *pb.MakeHatRequest) (*pb.Hat, error) {
ua := ctx.Value("user-agent").(string)
log.Printf("user agent: %v", ua)
}