relax

package module
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2021 License: MIT Imports: 17 Imported by: 0

README

Go-Relax GoDoc Go Report Card

Build fast and complete RESTful APIs in Go

Go-Relax aims to provide the tools to help developers build RESTful web services, and information needed to abide by REST architectural constraints using correct HTTP semantics.

Quick Start

Install using "go get":

go get github.com/srfrog/go-relax

Then import from your source:

import "github.com/srfrog/go-relax"

View example_test.go for an extended example of basic usage and features.

Also, check the wiki for HowTo's and recipes.

Features

  • Helps build API's that follow the REST concept using ROA principles.
  • Built-in support of HATEOAS constraint with Web Linking header tags.
  • Follows REST "best practices", with inspiration from Heroku and GitHub.
  • Works fine along with http.ServeMux or independently as http.Handler
  • Supports different media types, and mixed for requests and responses.
  • It uses JSON media type by default, but also includes XML (needs import).
  • The default routing engine uses trie with regexp matching for speed and flexibility.
  • Comes with a complete set of filters to build a working API. "Batteries included"
  • Uses sync.pool to efficiently use resources when under heavy load.
Included filters
  • Content - handles mixed request/response encodings, language preference, and versioning.
  • Basic authentication - to protect any resource with passwords.
  • CORS - Cross-Origin Resource Sharing, for remote client-server setups.
  • ETag - entity tagging with conditional requests for efficient caching.
  • GZip - Dynamic gzip content data compression, with ETag support.
  • Logging - custom logging with pre- and post- request event support.
  • Method override - GET/POST method override via HTTP header and query string.
  • Security - Various security practices for request handling.
  • Limits - request throttler, token-based rate limiter, and memory limits.
Upcoming filters
  • JSON-API support.
  • JSON-Schema for validating requests and responses.
  • Collection-JSON support.

Documentation

The full code documentation is located at GoDoc:

https://pkg.go.dev/github.com/srfrog/go-relax

The source code is thoroughly commented, have a look.

Hello World

This minimal example creates a new Relax service that handles a Hello resource.

package main

import (
   "github.com/srfrog/go-relax"
)

type Hello string

func (h *Hello) Index(ctx *relax.Context) {
   ctx.Respond(h)
}

func main() {
   h := Hello("hello world!")
   svc := relax.NewService("http://api.company.com/")
   svc.Resource(&h)
   svc.Run()
}

$ curl -i -X GET http://api.company.com/hello

Response:

HTTP/1.1 200 OK
Content-Type: application/json;charset=utf-8
Link: </hello>; rel="self"
Link: </hello>; rel="index"
Request-Id: 61d430de-7bb6-4ff8-84da-aff6fe81c0d2
Server: Go-Relax/0.5.0
Date: Thu, 14 Aug 2014 06:20:48 GMT
Content-Length: 14

"hello world!"

Credits

Go-Relax is Copyright (c) Codehack. Published under an MIT License

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

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

View Source
const Version = "1.0.0"

Version is the version of this package.

Variables

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

View Source
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 "*&#5C;*", 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.

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

func GetRealIP(r *http.Request) string

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

func IsRequestSSL(r *http.Request) bool

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

func LinkHeader(uri string, param ...string) (string, string)

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

func NewRequestID(id string) string

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

func ParsePreferences(values string) (map[string]float32, error)

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.

func PathExt

func PathExt(path string) string

PathExt returns the media subtype extension in an URL path. The extension begins from the last dot:

/api/v1/tickets.xml => ".xml"

Returns the extension with dot, or empty string "" if not found.

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

func (ctx *Context) Bytes() int

Bytes returns the number of bytes written in the response.

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

func (ctx *Context) Error(code int, message string, details ...interface{})

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

func (ctx *Context) Format(f fmt.State, c rune)

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

func (ctx *Context) Get(key string) interface{}

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

func (ctx *Context) Header() http.Header

Header implements ResponseWriter.Header

func (*Context) Respond

func (ctx *Context) Respond(v interface{}, code ...int) error

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) Set added in v0.5.3

func (ctx *Context) Set(key string, value interface{})

Set stores the value of key in the Context k/v tree.

func (*Context) Status

func (ctx *Context) Status() int

Status returns the current known HTTP status code, or http.StatusOK if unknown.

func (*Context) Write

func (ctx *Context) Write(b []byte) (int, error)

Write implements ResponseWriter.Write

func (*Context) WriteHeader

func (ctx *Context) WriteHeader(code int)

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.

func (*EncoderJSON) Encode

func (e *EncoderJSON) Encode(writer io.Writer, v interface{}) error

Encode will try to encode the value of v into JSON. If EncoderJSON.Indented is true, then the JSON will be indented with tabs. Returns nil on success, 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 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",
}

func (*Link) String

func (l *Link) String() string

String returns a string representation of a Link object. Suitable for use in "Link" HTTP headers.

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

func (r *Resource) CRUD(pse string) *Resource

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

func (r *Resource) MethodNotAllowed(ctx *Context)

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 (r *Resource) NewLink(link *Link)

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

func (r *Resource) NotImplemented(ctx *Context)

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

func (r *Resource) OptionsHandler(ctx *Context)

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

func (r *Resource) Path(absolute bool) string

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

type ResponseBuffer struct {
	bytes.Buffer
	// contains filtered or unexported fields
}

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

func NewService(uri string, entities ...interface{}) *Service

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

func (svc *Service) Handler() (string, http.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

func (svc *Service) Index(ctx *Context)

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

func (svc *Service) Logf(format string, args ...interface{})

Logf prints an log entry to logger if set, or stdlog if nil. Based on the unexported function logf() in “net/http“.

func (*Service) Logger

func (svc *Service) Logger() Logger

Logger returns the service logging system.

func (*Service) Options

func (svc *Service) Options(ctx *Context)

Options implements the Optioner interface to handle OPTION requests for the root resource service.

func (*Service) Path added in v0.5.3

func (svc *Service) Path(absolute bool) string

Path returns the base path of this service. absolute whether or not it should return an absolute URL.

func (*Service) Resource

func (svc *Service) Resource(collection Resourcer, filters ...Filter) *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

func (svc *Service) Root() *Resource

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

func (svc *Service) Router() 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

func (svc *Service) Run(args ...string)

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

func (svc *Service) Uptime() int

Uptime returns the service uptime in seconds.

func (*Service) Use

func (svc *Service) Use(entities ...interface{}) *Service

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

Directories

Path Synopsis
encoder
xml
example
filter

Jump to

Keyboard shortcuts

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