Documentation
¶
Overview ¶
Package response describes a successful HTTP answer as a value, before anything is written.
A handler returns a Response: the status, the headers and a JSON body. The value knows nothing about the router, so the same answer can be written by net/http with Write or by a router with its own serializer.
Constructors ¶
JSON answers any value with a status. Data wraps the value in a data envelope; OK and Created are its 200 and 201 forms, and Created sets the Location header:
response.Created("/articles/42", article)
// 201, Location: /articles/42
// {"data":{"id":42,"title":"HTTP boundaries"}}
NoContent answers 204 without a body. Page answers one page of items with the total and the page position:
{"data":{"items":[...],"total":42,"page":2,"size":20}}
Nil items are written as an empty array, so a client never has to tell null from an empty list.
Headers ¶
Location and Content-Type have fields of their own; any other header, such as a custom X-User, goes to Header. Response.SetHeaders writes them all under canonical names, replacing values already set, so x-user and X-User are the same header whichever way a handler spells it.
Writing ¶
Response.Validate rejects a response that must not be written: a status outside 200-599, a body on 204 or 304, a body without a content type, or 201 without a Location.
Write is the net/http backend. It validates the response and encodes the body with encoding/json before it commits anything, so after a failure the writer is untouched and the handler can still answer with an error.
A router that has its own serializer writes a Response the same way: Validate, SetHeaders into the router's response headers, then Body with Status, or only Status when ContentType is empty.
Index ¶
Examples ¶
Constants ¶
const ContentTypeJSON = "application/json"
ContentTypeJSON is the media type of a JSON response body.
Variables ¶
This section is empty.
Functions ¶
func Write ¶
func Write(w http.ResponseWriter, resp Response) error
Write is the net/http backend. It encodes the body with encoding/json before committing anything, so an invalid response or an encoding failure leaves w untouched.
Example ¶
package main
import (
"fmt"
"log"
"net/http/httptest"
"github.com/uchaloop/httpx/response"
)
type article struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
func main() {
recorder := httptest.NewRecorder()
created := response.Created("/articles/42", article{ID: 42, Title: "HTTP boundaries"})
if err := response.Write(recorder, created); err != nil {
log.Fatal(err)
}
fmt.Println(recorder.Code)
fmt.Println(recorder.Header().Get("Location"))
fmt.Println(recorder.Header().Get("Content-Type"))
fmt.Println(recorder.Body.String())
}
Output: 201 /articles/42 application/json {"data":{"id":42,"title":"HTTP boundaries"}}
Types ¶
type Response ¶
type Response struct {
Status int
// Location is written as the Location header. Status 201 requires it.
Location string
// Header holds any other headers, such as a custom X-User; Content-Type and
// Location have fields of their own. Names are canonicalized when written.
Header http.Header
// ContentType is the JSON media type of Body, and is empty for a response
// without a body.
ContentType string
Body any
}
Response is what a handler answers, before anything is written. A framework adapter writes it with the framework's own serializer; Write is the net/http backend.
func Created ¶
Created answers a data envelope with status 201 and a Location header, which must not be empty.
func Data ¶
Data answers value inside a {"data": ...} envelope. A nil value is written explicitly as {"data":null}; it never changes the status.
func NoContent ¶
func NoContent() Response
NoContent answers status 204 without a body or content type.
Example ¶
package main
import (
"fmt"
"log"
"net/http/httptest"
"github.com/uchaloop/httpx/response"
)
func main() {
recorder := httptest.NewRecorder()
if err := response.Write(recorder, response.NoContent()); err != nil {
log.Fatal(err)
}
fmt.Println(recorder.Code, recorder.Body.Len())
}
Output: 204 0
func Page ¶
Page answers one page of items in a data envelope together with the total and the page position. Nil items are written as an empty array.
Example ¶
package main
import (
"fmt"
"log"
"net/http/httptest"
"github.com/uchaloop/httpx/page"
"github.com/uchaloop/httpx/response"
)
type article struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
func main() {
current, err := page.Make(page.Params{}, page.Config{DefaultSize: 2, MaxSize: 100})
if err != nil {
log.Fatal(err)
}
articles := []article{{ID: 1, Title: "API versions"}, {ID: 2, Title: "HTTP boundaries"}}
recorder := httptest.NewRecorder()
if err := response.Write(recorder, response.Page(articles, 5, current)); err != nil {
log.Fatal(err)
}
fmt.Println(recorder.Body.String())
// A page past the end has no items, written as an empty array.
recorder = httptest.NewRecorder()
if err := response.Write(recorder, response.Page[article](nil, 5, current)); err != nil {
log.Fatal(err)
}
fmt.Println(recorder.Body.String())
}
Output: {"data":{"items":[{"id":1,"title":"API versions"},{"id":2,"title":"HTTP boundaries"}],"total":5,"page":1,"size":2}} {"data":{"items":[],"total":5,"page":1,"size":2}}
func (Response) SetHeaders ¶
SetHeaders writes the response headers into h: Header under canonical names, replacing values already set, then Location and Content-Type. Every backend uses it, so all adapters send the same headers.
Example ¶
package main
import (
"fmt"
"net/http"
"github.com/uchaloop/httpx/response"
)
type article struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
func main() {
answer := response.OK(article{ID: 42, Title: "HTTP boundaries"})
answer.Header = http.Header{"x-user": {"7"}}
// A router writes the headers into its own response the same way.
header := make(http.Header)
answer.SetHeaders(header)
fmt.Println(header)
}
Output: map[Content-Type:[application/json] X-User:[7]]
func (Response) Validate ¶
Validate reports a response that must not be written: a status outside 200-599, a body with a status that forbids one, a body without a content type, or status 201 without a Location.
Example ¶
package main
import (
"fmt"
"net/http"
"github.com/uchaloop/httpx/response"
)
type article struct {
ID int64 `json:"id"`
Title string `json:"title"`
}
func main() {
answer := response.Response{
Status: http.StatusCreated,
ContentType: response.ContentTypeJSON,
Body: article{ID: 42, Title: "HTTP boundaries"},
}
fmt.Println(answer.Validate())
}
Output: status 201 requires a location