rst

package module
v0.0.0-...-7a74802 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2015 License: MIT Imports: 26 Imported by: 0

README

github.com/mohamedattahri/rst

GoDoc Build Status

rst implements tools and methods to expose resources in a RESTFul service.

Test Coverage

go test -cover reports 78.3%.

Getting started

The idea behind rst is to have endpoints and resources implement interfaces to add support for 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:

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. 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.

Media MIME type Encoder
application/json json
text/javascript json
application/xml xml
text/xml xml
text/plain text
*/* json

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 CompressionThreshold bytes are not compressed.

Both Gzip and Flate are supported.

Features

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-Ranges 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.

Interfaces

Endpoints
Getter

Getter allows GET and HEAD method requests.

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
}
Poster

Poster allows an endpoint to handle POST requests.

func (ep *endpoint) Post(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
}
Patcher

Patcher allows an endpoint to handle PATCH requests.

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 and apply changes to resource
    // then return it
    return resource, nil
}
Putter

Putter allows an endpoint to handle PUT requests.

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 and apply changes to resource
    // then return it
    return resource, nil
}
Deleter

Deleter allows an endpoint to handle DELETE requests.

func (ep *endpoint) Delete(vars rst.RouteVars, r *http.Request) error {
    resource := database.Find(vars.Get("id"))
    if resource == nil {
        return rst.NotFound()
    }
    return nil
}
Preflighter

Preflighter allows you to customize the CORS headers returned to an OPTIONS preflight request sent by user agents before the actual request.

For the endpoint in this example, different policies are implemented for different times of the day.

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"},
	}
}
Resources
Ranger

Resources that implement Ranger can handle requests with a Range header and return partial responses with status code 206 PARTIAL CONTENT. It's the HTTP solution to pagination.

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
}
Marshaler

Marshaler allows you to control the encoding of a resource and return the array of bytes that will form the payload of the response.

MarshalRST is to rst.Marshal what MarshalJSON is to json.Marshal.

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 := rst.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)
}
http.Handler

http.Handler is a low level solution for when you need complete control over the process by which a resource is written in the response's payload.

In the following example, http.Handler is implemented to return a chunked response.

type User struct{}
// assuming User implements rst.Resource

// ServeHTTP will send half the data now, and the
// rest 10 seconds later.
func (u *User) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    ct, b, err := rst.MarshalResource(u, r)
    if err != nil {
        rst.ErrorHandler(err).ServeHTTP(w, r)
        return
    }
    w.Header.Set("Content-Type", ct)

    half := len(b) / 2
    w.Write(b[:half])
    time.Sleep(10 *time.Second)
    w.Write(b[half:])
}

Debugging and Recovering from errors

Set mux.Debug to true and rst will recover from panics and errors with status code 500 to display a useful page with the full stack trace and info about the request.

alt tag

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

View Source
const (
	Options = "OPTIONS"
	Head    = "HEAD"
	Get     = "GET"
	Patch   = "PATCH"
	Put     = "PUT"
	Post    = "POST"
	Delete  = "DELETE"
)

Common HTTP methods.

Variables

View Source
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

)
View Source
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.

View Source
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

func AllowedMethods(endpoint Endpoint) (methods []string)

AllowedMethods returns the list of HTTP methods allowed by this endpoint.

func EndpointHandler

func EndpointHandler(endpoint Endpoint) http.Handler

EndpointHandler returns a handler that serves HTTP requests for the resource exposed by the given endpoint.

func ErrorHandler

func ErrorHandler(err error) http.Handler

ErrorHandler is a wrapper that allows any Go error to implement the http.Handler interface.

func Marshal

func Marshal(resource interface{}, r *http.Request) (contentType string, encoded []byte, err error)

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

func ValidateConditions(resource Resource, r *http.Request) bool

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

func ParseAccept(header string) Accept

ParseAccept parses the raw value of an accept Header, and returns a sorted list of clauses.

func (Accept) Len

func (accept Accept) Len() int

func (Accept) Less

func (accept Accept) Less(i, j int) bool

func (Accept) Negotiate

func (accept Accept) Negotiate(alternatives ...string) (contentType string)

Negotiate the most appropriate contentType given the accept header clauses and a list of alternatives.

func (Accept) Swap

func (accept Accept) Swap(i, j int)

type AcceptClause

type AcceptClause struct {
	Type, SubType string
	Q             float64
	Params        map[string]string
}

AcceptClause represents a clause in an HTTP Accept header.

type AccessControlRequest

type AccessControlRequest struct {
	Origin  string
	Method  string
	Headers []string
}

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

type ContentRange struct {
	*Range
	Total uint64
}

ContentRange is a structured representation of the Content-Range response header.

func (*ContentRange) String

func (cr *ContentRange) String() string

type DeleteFunc

type DeleteFunc func(RouteVars, *http.Request) error

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 Deleter

type Deleter interface {
	Delete(RouteVars, *http.Request) error
}

Deleter is implemented by endpoints allowing the DELETE method.

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) ETag

func (e *Envelope) ETag() string

ETag implements the rst.Resource interface.

func (*Envelope) Header

func (e *Envelope) Header() http.Header

Header returns the list of headers that will be added to the ResponseWriter.

func (*Envelope) LastModified

func (e *Envelope) LastModified() time.Time

LastModified implements the rst.Resource interface.

func (*Envelope) MarshalRST

func (e *Envelope) MarshalRST(r *http.Request) (string, []byte, error)

MarshalRST marshals projection.

func (*Envelope) Projection

func (e *Envelope) Projection() interface{}

Projection of the resource wrapped in this envelope.

func (*Envelope) ServeHTTP

func (e *Envelope) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler. e.MarshalRST will be called internally.

func (*Envelope) TTL

func (e *Envelope) TTL() time.Duration

TTL implements the rst.Resource interface.

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

func BadRequest(reason, description string) *Error

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

func InternalServerError(reason, description string, captureStack bool) *Error

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

func MethodNotAllowed(forbidden string, allowed []string) *Error

MethodNotAllowed is returned when the method specified in a request is not allowed by the resource identified by the request-URI.

func NewError

func NewError(code int, reason, description string) *Error

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

func UnsupportedMediaType(mimes ...string) *Error

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) Error

func (e *Error) Error() string

func (*Error) MarshalRST

func (e *Error) MarshalRST(r *http.Request) (string, []byte, error)

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

func (e *Error) StatusText() string

StatusText returns a text for the HTTP status code of this error. It returns the empty string if the code is unknown.

func (*Error) String

func (e *Error) String() string

type GetFunc

type GetFunc func(RouteVars, *http.Request) (Resource, error)

GetFunc allows a Getter.Get method to be used an http.Handler.

func (GetFunc) ServeHTTP

func (f GetFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface.

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 NewMux

func NewMux() *Mux

NewMux initializes a new REST multiplexer.

func (*Mux) Delete

func (s *Mux) Delete(pattern string, handler DeleteFunc)

Delete registers handler for DELETE requests on the given pattern.

func (*Mux) Get

func (s *Mux) Get(pattern string, handler GetFunc)

Get registers handler for GET requests on the given pattern.

func (*Mux) Handle

func (s *Mux) Handle(pattern string, handler http.Handler)

Handle registers the handler function for the given pattern.

func (*Mux) HandleEndpoint

func (s *Mux) HandleEndpoint(pattern string, endpoint Endpoint)

HandleEndpoint registers the endpoint for the given pattern. It's a shorthand for:

s.Handle(pattern, EndpointHandler(endpoint))

func (*Mux) Header

func (s *Mux) Header() http.Header

Header contains the headers that will automatically be set in all responses served from this mux.

func (*Mux) Patch

func (s *Mux) Patch(pattern string, handler PatchFunc)

Patch registers handler for PATCH requests on the given pattern.

func (*Mux) Post

func (s *Mux) Post(pattern string, handler PostFunc)

Post registers handler for POST requests on the given pattern.

func (*Mux) Put

func (s *Mux) Put(pattern string, handler PutFunc)

Put registers handler for PUT requests on the given pattern.

func (*Mux) ServeHTTP

func (s *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request)

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 PatchFunc

type PatchFunc func(RouteVars, *http.Request) (Resource, error)

PatchFunc allows a Patcher.Patch method to be used an http.Handler.

func (PatchFunc) ServeHTTP

func (f PatchFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface.

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 PostFunc

type PostFunc func(RouteVars, *http.Request) (Resource, string, error)

PostFunc allows a Poster.Post method to be used an http.Handler.

func (PostFunc) ServeHTTP

func (f PostFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface.

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 PutFunc

type PutFunc func(RouteVars, *http.Request) (Resource, error)

PutFunc allows a Putter.Put method to be used an http.Handler.

func (PutFunc) ServeHTTP

func (f PutFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface.

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

type Range struct {
	Unit string
	From uint64
	To   uint64
}

Range is a structured representation of the Range request header.

func ParseRange

func ParseRange(raw string) (*Range, error)

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)

func (*Range) Len

func (r *Range) Len() uint64

Len returns the number of units requested in this range.

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.

type RouteVars

type RouteVars map[string]string

RouteVars represents the variables extracted by the router from a URL.

func (RouteVars) Get

func (rv RouteVars) Get(key string) string

Get returns the value with key, or an empty string if not found.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL