Documentation ¶
Overview ¶
Package relax is a framework of pluggable components to build RESTful API's. It provides a thin layer over “net/http“ to serve resources, without imposing a rigid structure. It is meant to be used along “http.ServeMux“, but will work as a replacement as it implements “http.Handler“.
The framework is divided into components: Encoding, Filters, Routing, Hypermedia and, Resources. These are the parts of a complete REST Service. All the components are designed to be pluggable (replaced) through interfaces by external packages. Relax provides enough built-in functionality to assemble a complete REST API.
The system is based on Resource Oriented Architecture (ROA), and had some inspiration from Heroku's REST API.
Index ¶
- Constants
- Variables
- func GetRealIP(r *http.Request) string
- func InternalServerError(w http.ResponseWriter, r *http.Request)
- func IsRequestSSL(r *http.Request) bool
- func LinkHeader(uri string, param ...string) (string, string)
- func NewRequestID(id string) string
- func ParsePreferences(values string) (map[string]float32, error)
- func PathExt(path string) string
- type CRUD
- type Context
- func (ctx *Context) Bytes() int
- func (ctx *Context) Clone(w http.ResponseWriter) *Context
- func (ctx *Context) Error(code int, message string, details ...interface{})
- func (ctx *Context) Format(f fmt.State, c rune)
- func (ctx *Context) Get(key string) interface{}
- func (ctx *Context) Header() http.Header
- func (ctx *Context) Respond(v interface{}, code ...int) error
- func (ctx *Context) Set(key string, value interface{})
- func (ctx *Context) Status() int
- func (ctx *Context) Write(b []byte) (int, error)
- func (ctx *Context) WriteHeader(code int)
- type Encoder
- type EncoderJSON
- type Filter
- type HandlerFunc
- type LimitedFilter
- type Link
- type Logger
- type Optioner
- type Resource
- func (r *Resource) CRUD(pse string) *Resource
- func (r *Resource) DELETE(path string, h HandlerFunc, filters ...Filter) *Resource
- func (r *Resource) GET(path string, h HandlerFunc, filters ...Filter) *Resource
- func (r *Resource) MethodNotAllowed(ctx *Context)
- func (r *Resource) NewLink(link *Link)
- func (r *Resource) NotImplemented(ctx *Context)
- func (r *Resource) OPTIONS(path string, h HandlerFunc, filters ...Filter) *Resource
- func (r *Resource) OptionsHandler(ctx *Context)
- func (r *Resource) PATCH(path string, h HandlerFunc, filters ...Filter) *Resource
- func (r *Resource) POST(path string, h HandlerFunc, filters ...Filter) *Resource
- func (r *Resource) PUT(path string, h HandlerFunc, filters ...Filter) *Resource
- func (r *Resource) Path(absolute bool) string
- func (r *Resource) Route(method, path string, h HandlerFunc, filters ...Filter) *Resource
- type Resourcer
- type ResponseBuffer
- func (rb *ResponseBuffer) Flush(w http.ResponseWriter) (int64, error)
- func (rb *ResponseBuffer) FlushHeader(w http.ResponseWriter)
- func (rb *ResponseBuffer) Free()
- func (rb *ResponseBuffer) Header() http.Header
- func (rb *ResponseBuffer) Status() int
- func (rb *ResponseBuffer) Write(b []byte) (int, error)
- func (rb *ResponseBuffer) WriteHeader(code int)
- func (rb *ResponseBuffer) WriteTo(w io.Writer) (int64, error)
- type Router
- type Service
- func (svc *Service) Adapter() http.HandlerFunc
- func (svc *Service) Handler() (string, http.Handler)
- func (svc *Service) Index(ctx *Context)
- func (svc *Service) Logf(format string, args ...interface{})
- func (svc *Service) Logger() Logger
- func (svc *Service) Options(ctx *Context)
- func (svc *Service) Path(absolute bool) string
- func (svc *Service) Resource(collection Resourcer, filters ...Filter) *Resource
- func (svc *Service) Root() *Resource
- func (svc *Service) Router() Router
- func (svc *Service) Run(args ...string)
- func (svc *Service) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (svc *Service) Uptime() int
- func (svc *Service) Use(entities ...interface{}) *Service
- type StatusError
- Bugs
Constants ¶
const ( // StatusUnprocessableEntity indicates the user sent content that while it is // syntactically correct, it might be erroneous. // See: http://tools.ietf.org/html/rfc4918#section-11.2 StatusUnprocessableEntity = 422 // StatusPreconditionRequired indicates that the origin server requires the // request to be conditional. StatusPreconditionRequired = 428 // StatusTooManyRequests indicates that the user has sent too many requests // in a given amount of time ("rate limiting"). StatusTooManyRequests = 429 // StatusRequestHeaderFieldsTooLarge indicates that the server is unwilling to // process the request because its header fields are too large. StatusRequestHeaderFieldsTooLarge = 431 // StatusNetworkAuthenticationRequired indicates that the client needs to // authenticate to gain network access. StatusNetworkAuthenticationRequired = 511 )
These status codes are inaccessible in net/http but they work with http.StatusText(). They are included here as they might be useful. See: https://tools.ietf.org/html/rfc6585
const Version = "1.0.0"
Version is the version of this package.
Variables ¶
var ( // ErrRouteNotFound is returned when the path searched didn't reach a resource handler. ErrRouteNotFound = &StatusError{http.StatusNotFound, "That route was not found.", nil} // ErrRouteBadMethod is returned when the path did not match a given HTTP method. ErrRouteBadMethod = &StatusError{http.StatusMethodNotAllowed, "That method is not supported", nil} )
These are errors returned by the default routing engine. You are encouraged to reuse them with your own Router.
var Content struct { // MediaType is the vendor extended media type used by this framework. // Default: application/vnd.codehack.relax Mediatype string // Version is the version used when no content version is requested. // Default: current Version string // Language is the language used when no content language is requested. // Default: en-US Language string }
Content does content negotiation to select the supported representations for the request and response. The default representation uses media type "application/json". If new media types are available to the service, a client can request it via the Accept header. The format of the Accept header uses the following vendor extension:
Accept: application/vnd.relax+{subtype}; version={version}; lang={language}
The values for {subtype}, {version} and {language} are optional. They correspond in order; to media subtype, content version and, language. If any value is missing or unsupported the default values are used. If a request Accept header is not using the vendor extension, the default values are used:
Accept: application/vnd.relax+json; version="current"; lang="en"
By decoupling version and lang from the media type, it allows us to have separate versions for the same resource and with individual language coverage.
When Accept indicates all media types "*C;*", the media subtype can be requested through the URL path's extension. If the service doesn't support the media encoding, then it will respond with an HTTP error code.
GET /api/v1/tickets.xml GET /company/users/123.json
Note that the extension should be appended to a collection or a resource item. The extension is removed before the request is dispatched to the routing engine.
If the request header Accept-Language is found, the value for content language is automatically set to that. The underlying application should use this to construct a proper respresentation in that language.
Content passes down the following info to filters:
ctx.Get("content.encoding") // media type used for encoding ctx.Get("content.decoding") // Type used in payload requests POST/PUT/PATCH ctx.Get("content.version") // requested version, or "current" ctx.Get("content.language") // requested language, or "en-US"
Requests and responses can use mixed representations if the service supports the media types.
See also, http://tools.ietf.org/html/rfc5646; tags to identify languages.
var ErrBodyTooLarge = errors.New("encoder: Body too large")
ErrBodyTooLarge is returned by Encoder.Decode when the read length exceeds the maximum size set for payload.
Functions ¶
func GetRealIP ¶ added in v0.5.3
GetRealIP returns the client address if the request is proxied. This is a best-guess based on the headers sent. The function will check the following headers, in order, to find a proxied client: Forwarded, X-Forwarded-For and X-Real-IP. Returns the client address or "unknown".
func InternalServerError ¶
func InternalServerError(w http.ResponseWriter, r *http.Request)
InternalServerError responds with HTTP status code 500-"Internal Server Error". This function is the default service recovery handler.
func IsRequestSSL ¶ added in v0.5.3
IsRequestSSL returns true if the request 'r' is done via SSL/TLS. SSL status is guessed from value of Request.TLS. It also checks the value of the X-Forwarded-Proto header, in case the request is proxied. Returns true if the request is via SSL, false otherwise.
func LinkHeader ¶
LinkHeader returns a complete Link header value that can be plugged into http.Header().Add(). Use this when you don't need a Link object for your relation, just a header. uri is the URI of target. param is one or more name=value pairs for link values. if nil, will default to rel="alternate" (as per https://tools.ietf.org/html/rfc4287#section-4.2.7). Returns two strings: "Link","Link header spec"
func NewRequestID ¶
NewRequestID returns a new request ID value based on UUID; or checks an id specified if it's valid for use as a request ID. If the id is not valid then it returns a new ID.
A valid ID must be between 20 and 200 chars in length, and URL-encoded.
func ParsePreferences ¶
ParsePreferences is a very naive and simple parser for header value preferences. Returns a map of preference=quality values for each preference with a quality value. If a preference doesn't specify quality, then a value of 1.0 is assumed (bad!). If the quality float value can't be parsed from string, an error is returned.
Types ¶
type CRUD ¶
type CRUD interface { // Create may allow the creation of new resource items via methods POST/PUT. Create(*Context) // Read may display a specific resource item given an ID or name via method GET. Read(*Context) // Update may allow updating resource items via methods PATCH/PUT. Update(*Context) // Delete may allow removing items from a resource via method DELETE. Delete(*Context) }
CRUD is an interface for Resourcer objects that provide create, read, update, and delete operations; also known as CRUD.
type Context ¶
type Context struct { context.Context // ResponseWriter is the response object passed from “net/http“. http.ResponseWriter // Request points to the http.Request information for this request. Request *http.Request // PathValues contains the values matched in PSEs by the router. It is a // name=values map (map[string][]string). // Examples: // // ctx.PathValues.Get("username") // returns the first value for "username" // ctx.PathValues.Get("_2") // values are also accessible by index // ctx.PathValues["colors"] // if more than one color value. // // See also: Router, url.Values PathValues url.Values // Encode is the media encoding function requested by the client. // To see the media type use: // // ctx.Get("content.encoding") // // See also: Encoder.Encode Encode func(io.Writer, interface{}) error // Decode is the decoding function when this request was made. It expects an // object that implements io.Reader, usually Request.Body. Then it will decode // the data and try to save it into a variable interface. // To see the media type use: // // ctx.Get("content.decoding") // // See also: Encoder.Decode Decode func(io.Reader, interface{}) error // contains filtered or unexported fields }
Context has information about the request and filters. It implements http.ResponseWriter.
func (*Context) Clone ¶
func (ctx *Context) Clone(w http.ResponseWriter) *Context
Clone returns a shallow cloned context using 'w', an http.ResponseWriter object. If 'w' is nil, the ResponseWriter value can be assigned after cloning.
func (*Context) Error ¶
Error sends an error response, with appropriate encoding. It basically calls Respond using a status code and wrapping the message in a StatusError object.
'code' is the HTTP status code of the error. 'message' is the actual error message or reason. 'details' are additional details about this error (optional).
type RouteDetails struct { Method string `json:"method"` Path string `json:"path"` } ctx.Error(http.StatusNotImplemented, "That route is not implemented", &RouteDetails{"PATCH", "/v1/tickets/{id}"})
See also: Respond, StatusError
func (*Context) Format ¶
Format implements the fmt.Formatter interface, based on Apache HTTP's CustomLog directive. This allows a Context object to have Sprintf verbs for its values. See: https://httpd.apache.org/docs/2.4/mod/mod_log_config.html#formats
Verb Description ---- --------------------------------------------------- %% Percent sign %a Client remote address %b Size of response in bytes, excluding headers. Or '-' if zero. %#a Proxy client address, or unknown. %h Remote hostname. Will perform lookup. %l Remote ident, will write '-' (only for Apache log support). %m Request method %q Request query string. %r Request line. %#r Request line without protocol. %s Response status code. %#s Response status code and text. %t Request time, as string. %u Remote user, if any. %v Request host name. %A User agent. %B Size of response in bytes, excluding headers. %D Time lapsed to serve request, in seconds. %H Request protocol. %I Bytes received. %L Request ID. %P Server port used. %R Referer. %U Request path.
Example:
// Print request line and remote address. // Index [1] needed to reuse ctx argument. fmt.Printf("\"%r\" %[1]a", ctx) // Output: // "GET /v1/" 192.168.1.10
func (*Context) Get ¶ added in v0.5.3
Get retrieves the value of key from Context storage. The value is returned as an interface so it must be converted to an actual type. If the type implements fmt.Stringer then it may be used by functions that expect a string.
func (*Context) Respond ¶
Respond writes a response back to the client. A complete RESTful response should be contained within a structure.
'v' is the object value to be encoded. 'code' is an optional HTTP status code.
If at any point the response fails (due to encoding or system issues), an error is returned but not written back to the client.
type Message struct { Status int `json:"status"` Text string `json:"text"` } ctx.Respond(&Message{Status: 201, Text: "Ticket created"}, http.StatusCreated)
See also: Context.Encode, WriteHeader
func (*Context) Status ¶
Status returns the current known HTTP status code, or http.StatusOK if unknown.
func (*Context) WriteHeader ¶
WriteHeader will force a status code header, if one hasn't been set. If no call to WriteHeader is done within this context, it defaults to http.StatusOK (200), which is sent by net/http.
type Encoder ¶
type Encoder interface { // Accept returns the media type used in HTTP Accept header. Accept() string // ContentType returns the media type, and optionally character set, // for decoding used in Content-Type header. ContentType() string // Encode function encodes the value of an interface and writes it to an // io.Writer stream (usually an http.ResponseWriter object). Encode(io.Writer, interface{}) error // Decode function decodes input from an io.Reader (usually Request.Body) and // tries to save it to an interface variable. Decode(io.Reader, interface{}) error }
Encoder objects provide new data encoding formats.
Once a request enters service context, all responses are encoded according to the assigned encoder. Relax includes support for JSON encoding. Other types of encoding can be added by implementing the Encoder interface.
type EncoderJSON ¶
type EncoderJSON struct { // MaxBodySize is the maximum size (in bytes) of JSON payload to read. // Defaults to 2097152 (2MB) MaxBodySize int64 // Indented indicates whether or not to output indented JSON. // Note: indented JSON is slower to encode. // Defaults to false Indented bool // AcceptHeader is the media type used in Accept HTTP header. // Defaults to "application/json" AcceptHeader string // ContentTypeHeader is the media type used in Content-Type HTTP header // Defaults to "application/json;charset=utf-8" ContentTypeHeader string }
EncoderJSON implements the Encoder interface. It encode/decodes JSON data.
func NewEncoder ¶ added in v0.5.3
func NewEncoder() *EncoderJSON
NewEncoder returns an EncoderJSON object. This function will initiallize the object with sane defaults, for use with Service.encoders. Returns the new EncoderJSON object.
func (*EncoderJSON) Accept ¶
func (e *EncoderJSON) Accept() string
Accept returns the media type for JSON content, used in Accept header.
func (*EncoderJSON) ContentType ¶
func (e *EncoderJSON) ContentType() string
ContentType returns the media type for JSON content, used in the Content-Type header.
func (*EncoderJSON) Decode ¶
func (e *EncoderJSON) Decode(reader io.Reader, v interface{}) error
Decode reads a JSON payload (usually from Request.Body) and tries to save it to a variable v. If the payload is too large, with maximum EncoderJSON.MaxBodySize, it will fail with error ErrBodyTooLarge Returns nil on success and error on failure.
type Filter ¶
type Filter interface { // Run executes the current filter in a chain. // It takes a HandlerFunc function argument, which is executed within the // closure returned. Run(HandlerFunc) HandlerFunc }
Filter is a function closure that is chained in FILO (First-In Last-Out) order. Filters pre and post process all requests. At any time, a filter can stop a request by returning before the next chained filter is called. The final link points to the resource handler.
Filters are run at different times during a request, and in order: Service, Resource and, Route. Service filters are run before resource filters, and resource filters before route filters. This allows some granularity to filters.
Relax comes with filters that provide basic functionality needed by most REST API's. Some included filters: CORS, method override, security, basic auth and content negotiation. Adding filters is a matter of creating new objects that implement the Filter interface. The position of the “next()“ handler function is important to the effect of the particular filter execution.
type HandlerFunc ¶
type HandlerFunc func(*Context)
HandlerFunc is simply a version of http.HandlerFunc that uses Context. All filters must return and accept this type.
type LimitedFilter ¶ added in v0.5.3
type LimitedFilter interface {
RunIn(interface{}) bool
}
LimitedFilter are filters that only can be used with a set of resources. Where resource is one of: “Router“ (interface), “*Resource“ and “*Service“ The “RunIn()“ func should return true for the type(s) allowed, false otherwise.
func (f *MyFilter) RunIn(r interface{}) bool { switch r.(type) { case relax.Router: return true case *relax.Resource: return true case *relax.Service: return false } return false }
type Link ¶
type Link struct { URI string `json:"href"` Rel string `json:"rel"` Anchor string `json:"anchor,omitempty"` Rev string `json:"rev,omitempty"` HrefLang string `json:"hreflang,omitempty"` Media string `json:"media,omitempty"` Title string `json:"title,omitempty"` Titlex string `json:"title*,omitempty"` Type string `json:"type,omitempty"` Ext string }
Link an HTTP header tag that represents a hypertext relation link. It implements HTTP web links between resources that are not format specific.
For details see also, Web Linking: :https://tools.ietf.org/html/rfc5988 Relations: http://www.iana.org/assignments/link-relations/link-relations.xhtml Item and Collection Link Relations: http://tools.ietf.org/html/rfc6573 Versioning: https://tools.ietf.org/html/rfc5829 URI Template: http://tools.ietf.org/html/rfc6570 Media: http://www.w3.org/TR/css3-mediaqueries/
The field title* “Titlex“ must be encoded as per RFC5987. See: http://greenbytes.de/tech/webdav/rfc5988.html#RFC5987
Extension field “Ext“ must be name lowercase and quoted-string value, as needed.
Example:
link := Link{ URI: "/v1/schemas", Rel: "index", Ext: "priority=\"important\"", Title: "Definition of schemas", Titlex: "utf-8'es'\"Definición de esquemas\"", HrefLang: "en-US", Media: "screen, print", Type: "text/html;charset=utf-8", }
type Logger ¶
type Logger interface { Print(...interface{}) Printf(string, ...interface{}) Println(...interface{}) }
Logger interface is based on Go's “log“ package. Objects that implement this interface can provide logging to Relax resources.
type Optioner ¶ added in v0.5.3
type Optioner interface { // Options may display details about the resource or how to access it. Options(*Context) }
Optioner is implemented by Resourcer objects that want to provide their own response to OPTIONS requests.
type Resource ¶
type Resource struct {
// contains filtered or unexported fields
}
Resource is an object that implements Resourcer; serves requests for a resource.
func (*Resource) CRUD ¶
CRUD adds Create/Read/Update/Delete routes using the handlers in CRUD interface, if the object implements it. A typical resource will implement one or all of the handlers, but those that aren't implemented should respond with "Method Not Allowed" or "Not Implemented".
pse is a route path segment expression (PSE) - see Router for details. If pse is empty string "", then CRUD() will guess a value or use "{item}".
type Jobs struct{} // functions needed for Jobs to implement CRUD. func (l *Jobs) Create (ctx *Context) {} func (l *Jobs) Read (ctx *Context) {} func (l *Jobs) Update (ctx *Context) {} func (l *Jobs) Delete (ctx *Context) {} // CRUD() will add routes handled using "{uint:ticketid}" as PSE. jobs := &Jobs{} myservice.Resource(jobs).CRUD("{uint:ticketid}")
The following routes are added:
GET /api/jobs/{uint:ticketid} => use handler jobs.Read() POST /api/jobs => use handler jobs.Create() PUT /api/jobs => Status: 405 Method not allowed PUT /api/jobs/{uint:ticketid} => use handler jobs.Update() DELETE /api/jobs => Status: 405 Method not allowed DELETE /api/jobs/{uint:ticketid} => use handler jobs.Delete()
Specific uses of PUT/PATCH/DELETE are dependent on the application, so CRUD() won't make any assumptions for those.
func (*Resource) DELETE ¶
func (r *Resource) DELETE(path string, h HandlerFunc, filters ...Filter) *Resource
DELETE is a convenient alias to Route using DELETE as method
func (*Resource) GET ¶
func (r *Resource) GET(path string, h HandlerFunc, filters ...Filter) *Resource
GET is a convenient alias to Route using GET as method
func (*Resource) MethodNotAllowed ¶
MethodNotAllowed is a handler used to send a response when a method is not allowed.
// Route "PATCH /users/profile" => 405 Method Not Allowed users.PATCH("profile", users.MethodNotAllowed)
func (*Resource) NewLink ¶ added in v0.5.3
NewLink inserts new link relation for a resource. If the relation already exists, determined by comparing URI and relation type, then it is replaced with the new one.
func (*Resource) NotImplemented ¶
NotImplemented is a handler used to send a response when a resource route is not yet implemented.
// Route "GET /myresource/apikey" => 501 Not Implemented myresource.GET("apikey", myresource.NotImplemented)
func (*Resource) OPTIONS ¶
func (r *Resource) OPTIONS(path string, h HandlerFunc, filters ...Filter) *Resource
OPTIONS is a convenient alias to Route using OPTIONS as method
func (*Resource) OptionsHandler ¶ added in v0.5.3
OptionsHandler responds to OPTION requests. It returns an Allow header listing the methods allowed for an URI. If the URI is the Service's path then it returns information about the service.
func (*Resource) PATCH ¶
func (r *Resource) PATCH(path string, h HandlerFunc, filters ...Filter) *Resource
PATCH is a convenient alias to Route using PATCH as method
func (*Resource) POST ¶
func (r *Resource) POST(path string, h HandlerFunc, filters ...Filter) *Resource
POST is a convenient alias to Route using POST as method
func (*Resource) PUT ¶
func (r *Resource) PUT(path string, h HandlerFunc, filters ...Filter) *Resource
PUT is a convenient alias to Route using PUT as method
func (*Resource) Path ¶ added in v0.5.3
Path similar to Service.Path but returns the path to this resource. absolute whether or not it should return an absolute URL.
func (*Resource) Route ¶
func (r *Resource) Route(method, path string, h HandlerFunc, filters ...Filter) *Resource
Route adds a resource route (method + path) and its handler to the router.
'method' is the HTTP method verb (GET, POST, ...). 'path' is the URI path and optional path matching expressions (PSE). 'h' is the handler function with signature HandlerFunc. 'filters' are route-level filters run before the handler. If the resource has its own filters, those are prepended to the filters list; resource-level filters will run before route-level filters.
Returns the resource itself for chaining.
type Resourcer ¶
type Resourcer interface { // Index may serve the entry GET request to a resource. Such as the listing // of a collection. Index(*Context) }
Resourcer is any object that implements the this interface. A resource is a namespace where all operations for that resource happen.
type Locations struct{ City string Country string } // This function is needed for Locations to implement Resourcer func (l *Locations) Index (ctx *Context) { ctx.Respond(l) } loc := &Locations{City: "Scottsdale", Country: "US"} myresource := service.Resource(loc)
type ResponseBuffer ¶
ResponseBuffer implements http.ResponseWriter, but redirects all writes and headers to a buffer. This allows to inspect the response before sending it. When a response is buffered, it needs an explicit call to Flush or WriteTo to send it.
ResponseBuffer also implements io.WriteTo to write data to any object that implements io.Writer.
func NewResponseBuffer ¶
func NewResponseBuffer(w http.ResponseWriter) *ResponseBuffer
NewResponseBuffer returns a ResponseBuffer object initialized with the headers of 'w', an object that implements “http.ResponseWriter“. Objects returned using this function are pooled to save resources. See also: ResponseBuffer.Free
func (*ResponseBuffer) Flush ¶
func (rb *ResponseBuffer) Flush(w http.ResponseWriter) (int64, error)
Flush sends the headers, status and buffered content to 'w', an http.ResponseWriter object. The ResponseBuffer object is freed after this call. Returns the number of bytes written to 'w' or error on failure. See also: ResponseBuffer.Free, ResponseBuffer.FlushHeader, ResponseBuffer.WriteTo
func (*ResponseBuffer) FlushHeader ¶
func (rb *ResponseBuffer) FlushHeader(w http.ResponseWriter)
FlushHeader sends the buffered headers and status, but not the content, to 'w' an object that implements http.ResponseWriter. This function won't free the buffer or reset the headers but it will send the status using ResponseWriter.WriterHeader, if status was saved before. See also: ResponseBuffer.Flush, ResponseBuffer.WriteHeader
func (*ResponseBuffer) Free ¶
func (rb *ResponseBuffer) Free()
Free frees a ResponseBuffer object returning it back to the usage pool. Use with “defer“ after calling NewResponseBuffer if WriteTo or Flush arent used. The values of the ResponseBuffer are reset and must be re-initialized.
func (*ResponseBuffer) Header ¶
func (rb *ResponseBuffer) Header() http.Header
Header returns the buffered header map.
func (*ResponseBuffer) Status ¶
func (rb *ResponseBuffer) Status() int
Status returns the last known status code saved. If no status has been set, it returns http.StatusOK which is the default in “net/http“.
func (*ResponseBuffer) Write ¶
func (rb *ResponseBuffer) Write(b []byte) (int, error)
Write writes the data to the buffer. Returns the number of bytes written or error on failure.
func (*ResponseBuffer) WriteHeader ¶
func (rb *ResponseBuffer) WriteHeader(code int)
WriteHeader stores the value of status code.
func (*ResponseBuffer) WriteTo ¶
func (rb *ResponseBuffer) WriteTo(w io.Writer) (int64, error)
WriteTo implements io.WriterTo. It sends the buffer, except headers, to any object that implements io.Writer. The buffer will be empty after this call. Returns the number of bytes written or error on failure.
type Router ¶
type Router interface { // FindHandler should match request parameters to an existing resource handler and // return it. If no match is found, it should return an StatusError error which will // be sent to the requester. The default errors ErrRouteNotFound and // ErrRouteBadMethod cover the default cases. FindHandler(string, string, *url.Values) (HandlerFunc, error) // AddRoute is used to create new routes to resources. It expects the HTTP method // (GET, POST, ...) followed by the resource path and the handler function. AddRoute(string, string, HandlerFunc) // PathMethods returns a comma-separated list of HTTP methods that are matched // to a path. It will do PSE expansion. PathMethods(string) string }
Router defines the routing system. Objects that implement it have functions that add routes, find a handle to resources and provide information about routes.
Relax's default router is trieRegexpRouter. It takes full routes, with HTTP method and path, and inserts them in a trie that can use regular expressions to match individual path segments.
PSE: trieRegexpRouter's path segment expressions (PSE) are match strings that are pre-compiled as regular expressions. PSE's provide a simple layer of security when accepting values from the path. Each PSE is made out of a {type:varname} format, where type is the expected type for a value and varname is the name to give the variable that matches the value.
"{word:varname}" // matches any word; alphanumeric and underscore. "{uint:varname}" // matches an unsigned integer. "{int:varname}" // matches a signed integer. "{float:varname}" // matches a floating-point number in decimal notation. "{date:varname}" // matches a date in ISO 8601 format. "{geo:varname}" // matches a geo location as described in RFC 5870 "{hex:varname}" // matches a hex number, with optional "0x" prefix. "{uuid:varname}" // matches an UUID. "{varname}" // catch-all; matches anything. it may overlap other matches. "*" // translated into "{wild}" "{re:pattern}" // custom regexp pattern.
Some sample routes supported by trieRegexpRouter:
GET /api/users/@{word:name} GET /api/users/{uint:id}/* POST /api/users/{uint:id}/profile DELETE /api/users/{date:from}/to/{date:to} GET /api/cities/{geo:location} PUT /api/investments/\${float:dollars}/fund GET /api/todos/month/{re:([0][1-9]|[1][0-2])}
Since PSE's are compiled to regexp, care must be taken to escape characters that might break the compilation.
type Service ¶
type Service struct { // URI is the full reference URI to the service. URI *url.URL // Recovery is a handler function used to intervene after panic occur. Recovery http.HandlerFunc // contains filtered or unexported fields }
Service contains all the information about the service and resources handled. Specifically, the routing, encoding and service filters. Additionally, a Service is a collection of resources making it a resource by itself. Therefore, it implements the Resourcer interface. See: “Service.Root“
func NewService ¶
NewService returns a new Service that can serve resources.
'uri' is the URI to this service, it should be an absolute URI but not required. If an existing path is specified, the last path is used. 'entities' is an optional value that contains a list of Filter, Encoder, Router objects that are assigned at the service-level; the same as Service.Use().
myservice := NewService("https://api.codehack.com/v1", &eTag.Filter{})
This function will panic if it can't parse 'uri'.
func (*Service) Adapter ¶
func (svc *Service) Adapter() http.HandlerFunc
Adapter creates a new request context, sets default HTTP headers, creates the link-chain of service filters, then passes the request to content negotiation. Also, it uses a recovery function for panics, that responds with HTTP status 500-"Internal Server Error" and logs the event.
Info passed down by the adapter:
ctx.Get("request.start_time").(time.Time) // Time when request started, as string time.Time. ctx.Get("request.id").(string) // Unique or user-supplied request ID.
Returns an http.HandlerFunc function that can be used with http.Handle.
func (*Service) Handler ¶
Handler is a function that returns the values needed by http.Handle to handle a path. This allows Relax services to work along http.ServeMux. It returns the path of the service and the Service.Adapter handler.
// restrict requests to host "api.codehack.com" myAPI := relax.NewService("http://api.codehack.com/v1") // ... your resources might go here ... // maps "api.codehack.com/v1" in http.ServeMux http.Handle(myAPI.Handler()) // map other resources independently http.Handle("/docs", DocsHandler) http.Handle("/help", HelpHandler) http.Handle("/blog", BlogHandler) log.Fatal(http.ListenAndServe(":8000", nil))
Using this function with http.Handle is _recommended_ over using Service.Adapter directly. You benefit from the security options built-in to http.ServeMux; like restricting to specific hosts, clean paths, and separate path matching.
func (*Service) Index ¶ added in v0.5.3
Index is a handler that responds with a list of all resources managed by the service. This is the default route to the base URI. With this function Service implements the Resourcer interface which is a resource of itself (the "root" resource). FIXME: this pukes under XML (maps of course).
func (*Service) Logf ¶
Logf prints an log entry to logger if set, or stdlog if nil. Based on the unexported function logf() in “net/http“.
func (*Service) Options ¶
Options implements the Optioner interface to handle OPTION requests for the root resource service.
func (*Service) Path ¶ added in v0.5.3
Path returns the base path of this service. absolute whether or not it should return an absolute URL.
func (*Service) Resource ¶
Resource creates a new Resource object within a Service, and returns it. It will add an OPTIONS route that replies with an Allow header listing the methods available. Also, it will create a GET route to the handler in Resourcer.Index.
collection is an object that implements the Resourcer interface.
filters are resource-level filters that are ran before a resource handler, but after service-level filters.
This function will panic if it can't determine the name of a collection through reflection.
func (*Service) Root ¶ added in v0.5.3
Root points to the root resource, the service itself -- a collection of resources. This allows us to manipulate the service as a resource.
Example:
// Create a new service mapped to "/v2" svc := relax.NewService("/v2") // Route /v2/status/{level} to SystemStatus() via root svc.Root().GET("status/{word:level}", SystemStatus, &etag.Filter{})
This is similar to:
svc.AddRoute("GET", "/v2/status/{level}", SystemStatus)
Except that route-level filters can be used, without needing to meddle with service filters (which are global).
func (*Service) Router ¶
Router returns the service routing engine.
The routing engine is responsible for creating routes (method + path) to service resources, and accessing them for each request. To add new routes you can use this interface directly:
myservice.Router().AddRoute(method, path, handler)
Any route added directly with AddRoute() must reside under the service URI base path, otherwise it won't work. No checks are made. To find a handler to a request:
h := myservice.Router().FindHandler(ctx)
This will return the handler for the route in request context 'ctx'.
func (*Service) Run ¶
Run will start the service using basic defaults or using arguments supplied. If 'args' is nil, it will start the service on port 8000. If 'args' is not nil, it expects in order: address (host:port), certificate file and key file for TLS.
Run() is equivalent to:
http.Handle(svc.Handler()) http.ListenAndServe(":8000", nil)
Run(":3000") is equivalent to:
... http.ListenAndServe(":3000", nil)
Run("10.1.1.100:10443", "tls/cert.pem", "tls/key.pem") is eq. to:
... http.ListenAndServeTLS("10.1.1.100:10443", "tls/cert.pem", "tls/key.pem", nil)
If the key file is missing, TLS is not used.
func (*Service) ServeHTTP ¶
func (svc *Service) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.HandlerFunc. It lets the Service route all requests directly, bypassing http.ServeMux.
myService := relax.NewService("/") // ... your resources might go here ... // your service has complete handling of all the routes. log.Fatal(http.ListenAndServe(":8000", myService))
Using Service.Handler has more benefits than this method.
func (*Service) Use ¶
Use adds one or more encoders, filters and/or router to the service. Returns the service itself, for chaining.
To add new filters, assign an object that implements the Filter interface. Filters are not replaced or updated, only appended to the service list. Examples:
myservice.Use(&cors.Filter{}) myservice.Use(&security.Filter{CacheDisable: true})
To add encoders, assign an object that implements the Encoder interface. Encoders will replace any matching existing encoder(s), and they will be discoverable on the service encoders map.
newenc := NewEncoderXML() // encoder with default settings newenc.Indented = true // change a setting myservice.Use(newenc) // assign it to service
To change the routing engine, assign an object that implements the Router interface:
myservice.Use(MyFastRouter())
To change the logging system, assign an object that implements the Logger interface:
// Use the excellent logrus package. myservice.Use(logrus.New()) // With advanced usage log := &logrus.Logger{ Out: os.Stderr, Formatter: new(JSONFormatter), Level: logrus.Debug, } myservice.Use(log)
Any entities that don't implement the required interfaces, will be ignored.
type StatusError ¶
type StatusError struct { // Code is meant for a HTTP status code or any other numeric ID. Code int `json:"code"` // Message is the default error message used in logs. Message string `json:"message"` // Details can be any data structure that gives more information about the // error. Details interface{} `json:"details,omitempty"` }
StatusError is an error with a HTTP Status code. It allows errors to be complete and uniform.
func (*StatusError) Error ¶
func (e *StatusError) Error() string
StatusError implements the error interface.
Notes ¶
Bugs ¶
StatusError is too shallow, need to implement better error system with locale support.
Complete PATCH support - http://tools.ietf.org/html/rfc5789, http://tools.ietf.org/html/rfc6902