Documentation
¶
Overview ¶
Package httputil provides HTTP utility functions, focused around decoding and responding with JSON.
Decoding requests ¶
The httputil package defines the Decoder interface to serve as the central building block:
type Decoder interface {
// Decode decodes the HTTP request into the given value.
Decode(r *http.Request, v interface{}) error
}
JSONDecoder implements this interface and can be used to parse the request body if the content type is JSON.
var (
jsonDec httputil.JSONDecoder
responder httputil.JSONResponder
)
// ...
var u myapp.User
if err := jsonDec.Decode(r, &u); err != nil {
responder.Error(r, w, err)
return
}
Validation of input can be plugged into the decoding step using ValidatingDecoderMiddleware with an implementation of xgo.Validator:
var vd xgo.Validator = MyValidator{}
var dec httputil.Decoder
{
dec = httputil.JSONDecoder{}
dec = httputil.ValidatingDecoderMiddleware(vd)(dec)
}
Encoding responses ¶
JSONResponder is a simple helper for responding to requests with JSON either using a value or an error:
var responder httputil.JSONResponder
// ...
responder.Respond(r, w, myapp.Response{
Success: true,
Data: result,
})
By default, when responding with a value, the status is set to '200: OK' but this can be overridden using JSONResponder.RespondWithStatus:
responder.RespondWithStatus(r, w, http.StatusCreated, myapp.Response{
Success: true,
Data: id,
})
JSONResponder builds upon the interfaces declared in the github.com/sudo-suhas/xgo/errors package to translate the error value into the status and response body suitable to be sent to the caller.
JSONResponder.Error leverages the errors.StatusCoder interface to infer the status code to be set for sending the response.
type StatusCoder interface {
StatusCode() int
}
The status code for the error response can be overridden using JSONResponder.ErrorWithStatus:
responder.ErrorWithStatus(r, w, http.StatusServiceUnavailable, err)
For transforming the error into the response body, a default implementation is provided but it can also be overridden by specifying ErrToRespBody on the JSONResponder instance:
var genericErrMsg = "We are not able to process your request. Please try again."
func newJSONResponder() httputil.JSONResponder {
return httputil.JSONResponder{ErrToRespBody: errToRespBody}
}
func errToRespBody(err error) interface{} {
// A contrived implementation of the transform func.
return myapp.GenericResponse{
Errors: []myapp.ErrorResponse{{Message: genericErrMsg}},
}
}
Observing errors ¶
Tracking errors, be it logging or instrumentation, is an important aspect and it can be done easily by specifying ErrObservers on the JSONResponder instance:
func newJSONResponder() httputil.JSONResponder {
return httputil.JSONResponder{
// Called for each error and can 'track' the error.
ErrObservers: []httputil.ErrorObserverFunc{errLogger},
}
}
func errLogger(r *http.Request, err error) {
var e *errors.Error
if !errors.As(err, &e) {
httplog.LogEntrySetField(r, "error", err.Error())
return
}
httplog.LogEntrySetField(r, "error_details", e.Details())
}
Building URLs ¶
URLBuilder makes building URLs convenient and prevents common mistakes. Specifically, it handles escaping the path parameters, encoding the query parameters and building the complete URL with an easy to use API.
b, err := httputil.NewURLBuilderSource("https://api.example.com/")
if err != nil {
// ...
}
var u *url.URL
u = b.NewURLBuilder().
Path("/users/{id}/posts").
PathParamInt("id", 123).
QueryParamInt("limit", 10).
QueryParamInt("offset", 120).
URL()
fmt.Println(u) // https://api.example.com/users/123/posts?limit=10&offset=120
u = b.NewURLBuilder().
Path("/posts/{title}").
PathParam("title", `Letters / "Special" Characters`).
URL()
fmt.Println(u) // https://api.example.com/posts/Letters%2520%252F%2520%2522Special%2522%2520Characters
u = b.NewURLBuilder().
Path("/users/{userID}/posts/{postID}/comments").
PathParam("userID", "foo").
PathParam("postID", "bar").
QueryParams(url.Values{
"search": {"some text"},
"limit": {"10"},
}).
URL()
fmt.Println(u) // https://api.example.com/users/foo/posts/bar/comments?limit=10&search=some+text
Index ¶
- Variables
- type DecodeFunc
- type Decoder
- type DecoderMiddleware
- type ErrorObserverFunc
- type JSONDecoder
- type JSONResponder
- func (jr *JSONResponder) Error(r *http.Request, w http.ResponseWriter, err error)
- func (jr *JSONResponder) ErrorWithStatus(r *http.Request, w http.ResponseWriter, status int, err error)
- func (jr *JSONResponder) Respond(r *http.Request, w http.ResponseWriter, v interface{})
- func (jr *JSONResponder) RespondWithStatus(r *http.Request, w http.ResponseWriter, status int, v interface{})
- type URLBuilder
- func (u *URLBuilder) Path(p string) *URLBuilder
- func (u *URLBuilder) PathParam(name, value string) *URLBuilder
- func (u *URLBuilder) PathParamInt(name string, value int64) *URLBuilder
- func (u *URLBuilder) PathParams(params map[string]string) *URLBuilder
- func (u *URLBuilder) QueryParam(key string, values ...string) *URLBuilder
- func (u *URLBuilder) QueryParamBool(key string, value bool) *URLBuilder
- func (u *URLBuilder) QueryParamFloat(key string, values ...float64) *URLBuilder
- func (u *URLBuilder) QueryParamInt(key string, values ...int64) *URLBuilder
- func (u *URLBuilder) QueryParams(params url.Values) *URLBuilder
- func (u *URLBuilder) URL() *url.URL
- type URLBuilderSource
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrKindUnsupportedMediaType = errors.Kind{ Code: "UNSUPPORTED_MEDIA_TYPE", Status: http.StatusUnsupportedMediaType, } ErrKindRequestEntityTooLarge = errors.Kind{ Code: "REQUEST_ENTITY_TOO_LARGE", Status: http.StatusRequestEntityTooLarge, } )
Error kinds.
Functions ¶
This section is empty.
Types ¶
type DecodeFunc ¶
DecodeFunc type is an adapter to allow the use of ordinary functions as a Decoder. If f is a function with the appropriate signature, DecodeFunc(f) is a Decoder that calls f.
type Decoder ¶
type Decoder interface {
// Decode decodes the HTTP request into the given value.
Decode(r *http.Request, v interface{}) error
}
Decoder is implemented by any value which has a Decode method.
type DecoderMiddleware ¶
DecoderMiddleware describes a middleware function for Decoder.
func ValidatingDecoderMiddleware ¶
func ValidatingDecoderMiddleware(vd xgo.Validator) DecoderMiddleware
ValidatingDecoderMiddleware returns a DecoderMiddleware which validates the decoded value.
var vd xgo.Validator = MyValidator{}
var dec httputil.Decoder
{
dec = httputil.JSONDecoder{}
dec = httputil.ValidatingDecoderMiddleware(vd)(dec)
}
type ErrorObserverFunc ¶
ErrorObserverFunc takes some action when an error occurs during request processing.
func errLogger(r *http.Request, err error) {
var e *errors.Error
if !errors.As(err, &e) {
httplog.LogEntrySetField(r, "error", err.Error())
return
}
httplog.LogEntrySetField(r, "error_details", e.Details())
}
type JSONDecoder ¶
type JSONDecoder struct {
// SkipCheckContentType, if set to true, skips the check on
// value of Content-Type header being "application/json".
SkipCheckContentType bool
// UseNumber causes the Decoder to unmarshal a number into an
// interface{} as a Number instead of as a float64.
UseNumber bool
// DisallowUnknownFields causes the Decoder to return an error when
// the destination is a struct and the input contains object keys
// which do not match any non-ignored, exported fields in the
// destination.
DisallowUnknownFields bool
}
JSONDecoder decodes the request body into the given value. It expects the request body to be JSON.
type JSONResponder ¶
type JSONResponder struct {
// ErrToRespBody converts the error to the response body. Optional.
ErrToRespBody func(error) interface{}
// ErrObservers are notified of errors for responses sent via
// JSONResponder.Error and JSONResponder.ErrorWithStatus.
ErrObservers []ErrorObserverFunc
}
JSONResponder responds with the value or error encoded as JSON.
func (*JSONResponder) Error ¶
func (jr *JSONResponder) Error(r *http.Request, w http.ResponseWriter, err error)
Error writes the error response. The status code and response body are constructed from the error. ErrToResponseBody can be used to define/override the response body structure.
func (*JSONResponder) ErrorWithStatus ¶
func (jr *JSONResponder) ErrorWithStatus(r *http.Request, w http.ResponseWriter, status int, err error)
ErrorWithStatus writes the error response. The response body is constructed from the error. ErrToResponseBody can be used to define/override the response body structure.
func (*JSONResponder) Respond ¶
func (jr *JSONResponder) Respond(r *http.Request, w http.ResponseWriter, v interface{})
Respond encodes v as JSON and writes the response with status '200: OK'. Only the HTTP status is written as response if v is nil. Furthermore, interface upgrade to xgo.JSON is supported for v.
func (*JSONResponder) RespondWithStatus ¶
func (jr *JSONResponder) RespondWithStatus(r *http.Request, w http.ResponseWriter, status int, v interface{})
RespondWithStatus encodes the value as JSON and writes the response with the specified status code. Only HTTP status is written as the response if v is nil. Furthermore, interface upgrade to xgo.JSON is supported for v.
type URLBuilder ¶
type URLBuilder struct {
// contains filtered or unexported fields
}
URLBuilder is used to build a URL.
URLBuilderSource should be used to create an instance of URLBuilder.
b, err := httputil.NewURLBuilderSource("https://api.example.com/")
if err != nil {
// handle error
}
u := b.NewURLBuilder().
Path("/users/{id}/posts").
PathParam("id", id).
QueryParam("limit", limit).
QueryParam("offset", offset).
URL()
// { id: 123, limit: 10, offset: 120 }
// https://api.example.com/users/123/posts?limit=10&offset=120
r, err := http.NewRequestWithContext(context.Background(), http.MethodGet, u.String(), nil)
if err != nil {
// handle error
}
// send HTTP request.
Example ¶
package main
import (
"fmt"
"github.com/sudo-suhas/xgo/httputil"
)
func main() {
b, err := httputil.NewURLBuilderSource("https://api.example.com/v1/")
check(err)
u := b.NewURLBuilder().
Path("/users/{userID}/posts/{postID}/comments").
PathParam("userID", "foo").
PathParamInt("postID", 42).
QueryParam("author_id", "foo", "bar").
QueryParamInt("limit", 20).
QueryParamBool("recent", true).
URL()
fmt.Println("URL:", u.String())
u = b.NewURLBuilder().
Path("posts/{title}").
PathParam("title", `Letters & "Special" Characters`).
URL()
fmt.Println("URL (encoded path param):", u.String())
}
func check(err error) {
if err != nil {
panic(err)
}
}
Output: URL: https://api.example.com/v1/users/foo/posts/42/comments?author_id=foo&author_id=bar&limit=20&recent=true URL (encoded path param): https://api.example.com/v1/posts/Letters%2520&%2520%2522Special%2522%2520Characters
func (*URLBuilder) Path ¶
func (u *URLBuilder) Path(p string) *URLBuilder
Path sets the path template for the URL.
Path parameters of the format "{paramName}" are supported and can be substituted using PathParam*.
func (*URLBuilder) PathParam ¶
func (u *URLBuilder) PathParam(name, value string) *URLBuilder
PathParam sets the path parameter name and value which needs to be substituted in the path template. Substitution happens when the URL is built using URLBuilder.URL()
func (*URLBuilder) PathParamInt ¶
func (u *URLBuilder) PathParamInt(name string, value int64) *URLBuilder
PathParamInt sets the path parameter name and value which needs to be substituted in the path template. Substitution happens when the URL is built using URLBuilder.URL()
func (*URLBuilder) PathParams ¶
func (u *URLBuilder) PathParams(params map[string]string) *URLBuilder
PathParams sets the path parameter names and values which need to be substituted in the path template. Substitution happens when the URL is built using URLBuilder.URL()
func (*URLBuilder) QueryParam ¶
func (u *URLBuilder) QueryParam(key string, values ...string) *URLBuilder
QueryParam sets the query parameter with the given values. If a value was previously set, it is replaced.
func (*URLBuilder) QueryParamBool ¶
func (u *URLBuilder) QueryParamBool(key string, value bool) *URLBuilder
QueryParamBool sets the query parameter with the given value. If a value was previously set, it is replaced.
func (*URLBuilder) QueryParamFloat ¶
func (u *URLBuilder) QueryParamFloat(key string, values ...float64) *URLBuilder
QueryParamFloat sets the query parameter with the given values. If a value was previously set, it is replaced.
func (*URLBuilder) QueryParamInt ¶
func (u *URLBuilder) QueryParamInt(key string, values ...int64) *URLBuilder
QueryParamInt sets the query parameter with the given values. If a value was previously set, it is replaced.
func (*URLBuilder) QueryParams ¶
func (u *URLBuilder) QueryParams(params url.Values) *URLBuilder
QueryParams sets the query parameters. If a value was previously set for any of the given parameters, it is replaced.
func (*URLBuilder) URL ¶
func (u *URLBuilder) URL() *url.URL
URL constructs and returns an instance of URL.
The constructed URL has the complete path and query parameters setup. The path parameters are substituted before being joined with the base URL.
type URLBuilderSource ¶
type URLBuilderSource struct {
// contains filtered or unexported fields
}
URLBuilderSource is used to create URLBuilder instances.
The URLBuilder created using URLBuilderSource will include the base URL and query parameters present on the URLBuilderSource instance.
Ideally, URLBuilderSource instance should be created only once for a given base URL.
Example ¶
package main
import (
"fmt"
"github.com/sudo-suhas/xgo/httputil"
)
func main() {
b, err := httputil.NewURLBuilderSource("https://api.example.com/v1")
check(err)
u := b.NewURLBuilder().
Path("/users").
URL()
fmt.Println("URL:", u.String())
}
func check(err error) {
if err != nil {
panic(err)
}
}
Output: URL: https://api.example.com/v1/users
func NewURLBuilderSource ¶
func NewURLBuilderSource(baseURL string) (URLBuilderSource, error)
NewURLBuilderSource builds a URLBuilderSource instance by parsing the baseURL.
The baseURL is expected to specify the host. If no scheme is specified, it defaults to http scheme.
func (URLBuilderSource) NewURLBuilder ¶
func (b URLBuilderSource) NewURLBuilder() *URLBuilder
NewURLBuilder creates a new instance of URLBuilder with the base URL and query parameters carried over from URLBuilderSource.