Documentation ¶
Overview ¶
Package rst implements tools and methods to expose resources in a RESTFul web service.
The idea behind rst is to have endpoints and resources implement interfaces to support HTTP features.
Endpoints can implement Getter, Poster, Patcher, Putter or Deleter to respectively allow the HEAD/GET, POST, PATCH, PUT, and DELETE HTTP methods.
Resources can implement Ranger to support partial GET requests, Marshaler to customize the process with which they are encoded, or http.Handler to have a complete control over the ResponseWriter.
With these interfaces, the complexity behind dealing with all the headers and status codes of the HTTP protocol is abstracted to let you focus on returning a resource or an error.
Resources ¶
A resource must implement the rst.Resource interface.
For that, you can either wrap an rst.Envelope around an existing type, or define a new type and implement the methods of the interface yourself.
Using a rst.Envelope:
projection := map[string]string{ "ID" : "a1-b2-c3-d4-e5-f6", "Name" : "Francis Underwood", } lastModified := time.Now() etag := fmt.Sprintf("%d-%s", lastModified.Unix(), projection["ID"]) ttl = 10 * time.Minute resource := rst.NewEnvelope( projection, lastModified, etag, ttl, )
Using a struct:
type Person struct { ID string Name string modifiedDate time.Time } // This will be helpful for conditional GETs // and to detect conflicts before PATCHs for example. func (p *Person) LastModified() time.Time { return p.modifiedDate } // An ETag inspired by Facebook. func (p *Person) ETag() string { return fmt.Sprintf("%d-%s", p.LastModified().Unix(), p.ID) } // This value will help set the Expires header and // improve the cacheability of this resource. func (p *Person) TTL() time.Duration { return 10 * time.Second } resource := &Person{ ID: "a1-b2-c3-d4-e5-f6", Name: "Francis Underwood", modifiedDate: time.Now(), }
Endpoints ¶
An endpoint is an access point to a resource in your service.
You can either define an endpoint by defining handlers for different methods sharing the same pattern, or by submitting a type that implements Getter, Poster, Patcher, Putter, Deleter and/or Prefligher.
Using rst.Mux:
mux := rst.NewMux() mux.Get("/people/{id:\\d+}", func(vars RouteVars, r *http.Request) (rst.Resource, error) { resource := database.Find(vars.Get("id")) if resource == nil { return nul, rst.NotFound() } return resource, nil }) mux.Delete("/people/{id:\\d+}", func(vars RouteVars, r *http.Request) error { resource := database.Find(vars.Get("id")) if resource == nil { return nul, rst.NotFound() } return resource.Delete() })
Using a struct:
In the following example, PersonEP implements Getter and is therefore able to handle GET requests.
type PersonEP struct {} func (ep *PersonEP) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, error) { resource := database.Find(vars.Get("id")) if resource == nil { return nil, rst.NotFound() } return resource, nil } func (ep *PersonEP) Delete(vars rst.RouteVars, r *http.Request) error { resource := database.Find(vars.Get("id")) if resource == nil { return nil, rst.NotFound() } return resource.Delete() }
Routing ¶
Routing of requests in rst is powered by Gorilla mux (https://github.com/gorilla/mux). Only URL patterns are available for now. Optional regular expressions are supported.
mux := rst.NewMux() mux.Debug = true // make sure this is switched back to false before production // Headers set in mux are added to all responses mux.Header().Set("Server", "Awesome Service Software 1.0") mux.Header().Set("X-Powered-By", "rst") mux.Handle("/people/{id:\\d+}", rst.EndpointHandler(&PersonEP{})) http.ListenAndServe(":8080", mux)
Encoding ¶
rst supports JSON, XML and text encoding of resources using the encoders in Go's standard library.
It negotiates the right encoding format based on the content of the Accept header in the request, calls the appropriate marshaler, and inserts the result in a response with the right status code and headers.
You can implement the Marshaler interface if you want to add support for another format, or for more control over the encoding process of a specific resource.
Compression ¶
rst compresses the payload of responses using the supported algorithm detected in the request's Accept-Encoding header.
Payloads under the size defined in the CompressionThreshold const are not compressed.
Both Gzip and Flate are supported.
Options ¶
OPTIONS requests are implicitly supported by all endpoints.
Cache ¶
The ETag, Last-Modified and Vary headers are automatically set.
rst responds with 304 NOT MODIFIED when an appropriate If-Modified-Since or If-None-Match header is found in the request.
The Expires header is also automatically inserted with the duration returned by Resource.TTL().
Partial Gets ¶
A resource can implement the Ranger interface to gain the ability to return partial responses with status code 206 PARTIAL CONTENT and Content-Range header automatically inserted.
Ranger.Range method will be called when a valid Range header is found in an incoming GET request.
The Accept-Range header will be inserted automatically.
The supported range units and the range extent will be validated for you.
Note that the If-Range conditional header is supported as well.
CORS ¶
rst can add the headers required to serve cross-origin (CORS) requests for you.
You can choose between two provided policies (DefaultAccessControl and PermissiveAccessControl), or define your own.
mux.SetCORSPolicy(rst.PermissiveAccessControl)
Support can be disabled by passing nil.
Preflighted requests are also supported. However, you can customize the responses returned by preflight OPTIONS requests if you implement the Preflighter interface in your endpoint.
Index ¶
- Constants
- Variables
- func AllowedMethods(endpoint Endpoint) (methods []string)
- func EndpointHandler(endpoint Endpoint) http.Handler
- func ErrorHandler(err error) http.Handler
- func Marshal(resource interface{}, r *http.Request) (contentType string, encoded []byte, err error)
- func MarshalResource(resource interface{}, r *http.Request) (contentType string, encoded []byte, err error)
- func ValidateConditions(resource Resource, r *http.Request) bool
- type Accept
- type AcceptClause
- type AccessControlRequest
- type AccessControlResponse
- type ContentRange
- type DeleteFunc
- type Deleter
- type Endpoint
- type Envelope
- func (e *Envelope) ETag() string
- func (e *Envelope) Header() http.Header
- func (e *Envelope) LastModified() time.Time
- func (e *Envelope) MarshalRST(r *http.Request) (string, []byte, error)
- func (e *Envelope) Projection() interface{}
- func (e *Envelope) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (e *Envelope) TTL() time.Duration
- type Error
- func BadRequest(reason, description string) *Error
- func Conflict() *Error
- func Forbidden() *Error
- func InternalServerError(reason, description string, captureStack bool) *Error
- func MethodNotAllowed(forbidden string, allowed []string) *Error
- func NewError(code int, reason, description string) *Error
- func NotAcceptable() *Error
- func NotFound() *Error
- func PreconditionFailed() *Error
- func RequestedRangeNotSatisfiable(cr *ContentRange) *Error
- func Unauthorized() *Error
- func UnsupportedMediaType(mimes ...string) *Error
- type GetFunc
- type Getter
- type Marshaler
- type Mux
- func (s *Mux) Delete(pattern string, handler DeleteFunc)
- func (s *Mux) Get(pattern string, handler GetFunc)
- func (s *Mux) Handle(pattern string, handler http.Handler)
- func (s *Mux) HandleEndpoint(pattern string, endpoint Endpoint)
- func (s *Mux) Header() http.Header
- func (s *Mux) Patch(pattern string, handler PatchFunc)
- func (s *Mux) Post(pattern string, handler PostFunc)
- func (s *Mux) Put(pattern string, handler PutFunc)
- func (s *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Mux) SetCORSPolicy(ac *AccessControlResponse)
- type PatchFunc
- type Patcher
- type PostFunc
- type Poster
- type Preflighter
- type PutFunc
- type Putter
- type Range
- type Ranger
- type Resource
- type RouteVars
Constants ¶
const ( Options = "OPTIONS" Head = "HEAD" Get = "GET" Patch = "PATCH" Put = "PUT" Post = "POST" Delete = "DELETE" )
Common HTTP methods.
Variables ¶
var ( // CompressionThreshold is the minimal length of the data to send in the // response ResponseWriter must reach before compression is enabled. // The current default value is the one used by Akamai, and falls within the // range recommended by Google. CompressionThreshold = 860 // bytes )
var DefaultAccessControl = &AccessControlResponse{ Origin: "*", Credentials: true, AllowedHeaders: nil, ExposedHeaders: []string{"Etag"}, Methods: nil, MaxAge: 24 * time.Hour, }
DefaultAccessControl defines a limited CORS policy that only allows simple cross-origin requests.
var PermissiveAccessControl = &AccessControlResponse{ Origin: "*", Credentials: true, AllowedHeaders: []string{}, ExposedHeaders: []string{"Etag"}, Methods: []string{}, MaxAge: 24 * time.Hour, }
PermissiveAccessControl defines a permissive CORS policy in which all methods and all headers are allowed for all origins.
Functions ¶
func AllowedMethods ¶
AllowedMethods returns the list of HTTP methods allowed by this endpoint.
func EndpointHandler ¶
EndpointHandler returns a handler that serves HTTP requests for the resource exposed by the given endpoint.
func ErrorHandler ¶
ErrorHandler is a wrapper that allows any Go error to implement the http.Handler interface.
func Marshal ¶
Marshal negotiates contentType based on the Accept header in r, and returns the encoded version of resource as an array of bytes.
Marshal uses resource.MarshalRST if resource implements the Marshaler interface, or MarshalResource method if it doesn't.
func MarshalResource ¶
func MarshalResource(resource interface{}, r *http.Request) (contentType string, encoded []byte, err error)
MarshalResource negotiates contentType based on the Accept header in r, and returns the encoded version of resource as an array of bytes.
MarshalResource can encode a resource in JSON and XML, as well as text using either encoding.TextMarshaler or fmt.Stringer.
MarshalResource's XML marshaling will always return a valid XML document with a header and a root object, which is not the case for the encoding/xml package.
MarshalResource can be called from Marshaler.MarshalRST on the same resource safely.
func ValidateConditions ¶
ValidateConditions returns true if the If-Unmodified-Since or the If-Match headers of r are not matching with the current version of resource.
func (ep *endpoint) Patch(vars RouteVars, r *http.Request) (Resource, error) { resource := db.Lookup(vars.Get("id")) if ValidateConditions(resource, r) { return nil, Conflict() } // apply the patch safely from here }
Types ¶
type Accept ¶
type Accept []AcceptClause
Accept represents a set of clauses in an HTTP Accept header.
func ParseAccept ¶
ParseAccept parses the raw value of an accept Header, and returns a sorted list of clauses.
type AcceptClause ¶
AcceptClause represents a clause in an HTTP Accept header.
type AccessControlRequest ¶
AccessControlRequest represents the headers of a CORS access control request.
func ParseAccessControlRequest ¶
func ParseAccessControlRequest(r *http.Request) *AccessControlRequest
ParseAccessControlRequest returns a new instance of AccessControlRequest filled with CORS headers found in r.
type AccessControlResponse ¶
type AccessControlResponse struct { Origin string ExposedHeaders []string Methods []string // Empty array means any, nil means none. AllowedHeaders []string // Empty array means any, nil means none. Credentials bool MaxAge time.Duration }
AccessControlResponse defines the response headers to a CORS access control request.
type ContentRange ¶
ContentRange is a structured representation of the Content-Range response header.
func (*ContentRange) String ¶
func (cr *ContentRange) String() string
type DeleteFunc ¶
DeleteFunc allows a Deleter.Deleter method to be used an http.Handler.
func (DeleteFunc) ServeHTTP ¶
func (f DeleteFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements the http.Handler interface.
type Endpoint ¶
type Endpoint interface{}
Endpoint represents an access point exposing a resource in the REST service.
type Envelope ¶
type Envelope struct {
// contains filtered or unexported fields
}
Envelope is a wrapper to allow any interface{} to be used as an rst.Resource interface.
func NewEnvelope ¶
func NewEnvelope(projection interface{}, lastModified time.Time, etag string, ttl time.Duration) *Envelope
NewEnvelope returns a struct that marshals projection when used as an rst.Resource interface.
func (*Envelope) Header ¶
Header returns the list of headers that will be added to the ResponseWriter.
func (*Envelope) LastModified ¶
LastModified implements the rst.Resource interface.
func (*Envelope) MarshalRST ¶
MarshalRST marshals projection.
func (*Envelope) Projection ¶
func (e *Envelope) Projection() interface{}
Projection of the resource wrapped in this envelope.
type Error ¶
type Error struct { Code int `json:"-" xml:"-"` Header http.Header `json:"-" xml:"-"` Reason string `json:"message" xml:"Message"` Description string `json:"description,omitempty" xml:"Description,omitempty"` Stack []*stackRecord `json:"stack,omitempty" xml:"Stack,omitempty"` }
Error represents an HTTP error, with a status code, a reason and a description. Error implements both the error and http.Handler interfaces.
Header can be used to specify headers that will be written in the HTTP response generated from this error.
func BadRequest ¶
BadRequest is returned when the request could not be understood by the server due to malformed syntax.
func Conflict ¶
func Conflict() *Error
Conflict is returned when a request can't be processed due to a conflict with the current state of the resource.
func Forbidden ¶
func Forbidden() *Error
Forbidden is returned when a resource is protected and inaccessible.
func InternalServerError ¶
InternalServerError represents an error with status code 500.
When captureStack is true, the stack trace will be captured and displayed in the HTML projection of the returned error if mux.Debug is true.
func MethodNotAllowed ¶
MethodNotAllowed is returned when the method specified in a request is not allowed by the resource identified by the request-URI.
func NewError ¶
NewError returns a new error with the given code, reason and description. It will panic if code < 400.
func NotAcceptable ¶
func NotAcceptable() *Error
NotAcceptable is returned when the resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.
func NotFound ¶
func NotFound() *Error
NotFound is returned when the server has not found a resource matching the Request-URI.
func PreconditionFailed ¶
func PreconditionFailed() *Error
PreconditionFailed is returned when one of the conditions the request was made under has failed.
func RequestedRangeNotSatisfiable ¶
func RequestedRangeNotSatisfiable(cr *ContentRange) *Error
RequestedRangeNotSatisfiable is returned when the range in the Range header does not overlap the current extent of the requested resource.
func Unauthorized ¶
func Unauthorized() *Error
Unauthorized is returned when authentication is required for the server to process the request.
func UnsupportedMediaType ¶
UnsupportedMediaType is returned when the entity in the request is in a format not support by the server. The supported media MIME type strings can be passed to improve the description of the error description.
func (*Error) MarshalRST ¶
MarshalRST is implemented to generate an HTML rendering of the error.
func (*Error) ServeHTTP ¶
func (e *Error) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements the http.Handler interface.
func (*Error) StatusText ¶
StatusText returns a text for the HTTP status code of this error. It returns the empty string if the code is unknown.
type Getter ¶
type Getter interface { // Returns the resource or an error. A nil resource pointer will generate // a response with status code 204 No Content. Get(RouteVars, *http.Request) (Resource, error) }
Getter is implemented by endpoints allowing the GET and HEAD method.
func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, error) { resource := database.Find(vars.Get("id")) if resource == nil { return nil, rst.NotFound() } return resource, nil }
type Marshaler ¶
type Marshaler interface { // MarshalRST must return the chosen encoding media MIME type and the // encoded resource as an array of bytes, or an error. // // MarshalRST is to rst.Marshal what MarshalJSON is to json.Marshal. MarshalRST(*http.Request) (contentType string, data []byte, err error) }
Marshaler is implemented by resources wishing to handle their encoding on their own.
Example:
const png = "image/png" type User struct{} // assuming User implements rst.Resource // MarshalRST returns the profile picture of the user if the Accept header // of the request indicates "image/png", and relies on rst.MarshalResource // to handle the other cases. func (u *User) MarshalRST(r *http.Request) (string, []byte, error) { accept := ParseAccept(r.Header.Get("Accept")) if accept.Negotiate(png) == png { b, err := ioutil.ReadFile("path/of/user/profile/picture.png") return png, b, err } return rst.MarshalResource(u, r) }
type Mux ¶
type Mux struct { Debug bool // Set to true to display stack traces and debug info in errors. Logger *log.Logger // contains filtered or unexported fields }
Mux is an HTTP request multiplexer. It matches the URL of each incoming requests against a list of registered REST endpoints.
func (*Mux) Delete ¶
func (s *Mux) Delete(pattern string, handler DeleteFunc)
Delete registers handler for DELETE requests on the given pattern.
func (*Mux) HandleEndpoint ¶
HandleEndpoint registers the endpoint for the given pattern. It's a shorthand for:
s.Handle(pattern, EndpointHandler(endpoint))
func (*Mux) Header ¶
Header contains the headers that will automatically be set in all responses served from this mux.
func (*Mux) SetCORSPolicy ¶
func (s *Mux) SetCORSPolicy(ac *AccessControlResponse)
SetCORSPolicy sets the access control parameters that will be used to write CORS related headers. By default, CORS support is disabled.
Endpoints that implement Preflighter can customize the CORS headers returned with the response to an HTTP OPTIONS preflight request.
The ac parameter can be DefaultAccessControl, PermissiveAccessControl, or a custom defined AccessControlResponse struct. A nil value will disable support.
type Patcher ¶
type Patcher interface { // Returns the patched resource or an error. Patch(RouteVars, *http.Request) (Resource, error) }
Patcher is implemented by endpoints allowing the PATCH method.
func (ep *endpoint) Patch(vars rst.RouteVars, r *http.Request) (rst.Resource, error) { resource := database.Find(vars.Get("id")) if resource == nil { return nil, rst.NotFound() } if r.Header.Get("Content-Type") != "application/www-form-urlencoded" { return nil, rst.UnsupportedMediaType("application/www-form-urlencoded") } // Detect any writing conflicts if rst.ValidateConditions(resource, r) { return nil, rst.PreconditionFailed() } // Read r.Body, apply changes to resource, then return it return resource, nil }
type Poster ¶
type Poster interface { // Returns the resource newly created and the URI where it can be located, or // an error. Post(RouteVars, *http.Request) (resource Resource, location string, err error) }
Poster is implemented by endpoints allowing the POST method.
func (ep *endpoint) Get(vars rst.RouteVars, r *http.Request) (rst.Resource, string, error) { resource, err := NewResourceFromRequest(r) if err != nil { return nil, "", err } uri := "https://example.com/resource/" + resource.ID return resource, uri, nil }
type Preflighter ¶
type Preflighter interface {
Preflight(*AccessControlRequest, RouteVars, *http.Request) *AccessControlResponse
}
Preflighter is implemented by endpoints wishing to customize the response to a CORS preflighted request.
func (e *endpoint) Preflight(req *rst.AccessControlRequest, vars rst.RouteVars, r *http.Request) *rst.AccessControlResponse { if time.Now().Hour() < 12 { return &rst.AccessControlResponse{ Origin: "morning.example.com", Methods: []string{"GET"}, } } return &rst.AccessControlResponse{ Origin: "afternoon.example.com", Methods: []string{"POST"}, } }
type Putter ¶
type Putter interface { // Returns the modified resource or an error. Put(RouteVars, *http.Request) (Resource, error) }
Putter is implemented by endpoints allowing the PUT method.
func (ep *endpoint) Put(vars rst.RouteVars, r *http.Request) (rst.Resource, error) { resource := database.Find(vars.Get("id")) if resource == nil { return nil, rst.NotFound() } // Detect any writing conflicts if rst.ValidateConditions(resource, r) { return nil, rst.PreconditionFailed() } // Read r.Body, apply changes to resource, then return it return resource, nil }
type Range ¶
Range is a structured representation of the Range request header.
func ParseRange ¶
ParseRange parses raw into a new Range instance.
ParseRange("bytes=0-1024") // (OK) ParseRange("resources=239-392") // (OK) ParseRange("items=39-") // (OK) ParseRange("bytes 50-100") // (ERROR: syntax) ParseRange("bytes=100-50") // (ERROR: logic)
type Ranger ¶
type Ranger interface { // Supported range units Units() []string // Total number of units available Count() uint64 // Range is used to return the part of the resource that is indicated by the // passed range. Range(*Range) (*ContentRange, Resource, error) }
Ranger is implemented by resources that support partial responses.
Range will only be called if the request contains a valid Range header. Otherwise, it will be processed as a normal Get request.
type Doc []byte // assuming Doc implements rst.Resource interface // Supported units will be displayed in the Accept-Range header func (d *Doc) Units() []string { return []string{"bytes"} } // Count returns the total number of range units available func (d *Doc) Count() uint64 { return uint64(len(d)) } func (d *Doc) Range(rg *rst.Range) (*rst.ContentRange, rst.Resource, error) { cr := &ContentRange{rg, c.Count()} part := d[rg.From : rg.To+1] return cr, part, nil }
type Resource ¶
type Resource interface { ETag() string // ETag identifying the current version of the resource. LastModified() time.Time // Date and time of the last modification of the resource. TTL() time.Duration // Time to live, or caching duration of the resource. }
Resource represents a resource exposed on a REST service using an Endpoint.
There are other interfaces that can be implemented by a resource to either control its projection in a response payload, or add support for advanced HTTP features:
- The Ranger interface adds support for range requests and allows the resource to return partial responses.
- The Marshaler interface allows you to customize the encoding process of the resource and control the bytes returned in the payload of the response.
- The http.Handler interface can be used to gain direct access to the ResponseWriter and Request. This is a low level method that should only be used when you need to write chunked responses, or if you wish to add specific headers such a Content-Disposition, etc.