relax

package module
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Apr 21, 2015 License: MIT Imports: 23 Imported by: 0

README

Go-Relax GoDoc Project progress

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/codehack/go-relax

Then import from your source:

import "github.com/codehack/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 Link header (and soon JSON-LD).
  • 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.
  • RestCop (Constraints Output Profiler) - it warns you when your responses are not RESTful.
  • Status - system status.

Documentation

The full code documentation is located at GoDoc:

http://godoc.org/github.com/codehack/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/codehack/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) 2014 Codehack. Published under 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.

Example (Basic)

Example_basic creates a new service under path "/v1" and serves requests for the users resource.

package main

import (
	"github.com/codehack/go-relax"
	"log"
	"net/http"
	"strconv"
	"time"
)

// User could be a struct mapping a DB table.
type User struct {
	ID   int       `json:"id"`
	Name string    `json:"name"`
	DOB  time.Time `json:"dob"`
}

// Users will be our resource object.
type Users struct {
	Group  string  `json:"group"`
	People []*User `json:"people"`
}

// FindByID searches users.People for a user matching ID and returns it;
// or StatusError if not found. This could do a search in our DB and
// handle the error logic.
func (u *Users) FindByID(idstr string) (*User, error) {
	id, err := strconv.Atoi(idstr)
	if err != nil {
		return nil, &relax.StatusError{http.StatusInternalServerError, err.Error(), nil}
	}
	for _, user := range u.People {
		if id == user.ID {
			// user found, return it.
			return user, nil
		}
	}
	// user not found.
	return nil, &relax.StatusError{http.StatusNotFound, "That user was not found", nil}
}

// Index handles "GET /v1/users"
func (u *Users) Index(ctx *relax.Context) {
	ctx.Header().Set("X-Custom-Header", "important header info from my framework")
	// list all users in the resource.
	ctx.Respond(u)
}

// Create handles "POST /v1/users"
func (u *Users) Create(ctx *relax.Context) {
	user := &User{}
	// decode json payload from client
	if err := ctx.Decode(ctx.Request.Body, &user); err != nil {
		ctx.Error(http.StatusBadRequest, err.Error())
		return
	}
	// some validation
	if user.Name == "" {
		ctx.Error(http.StatusBadRequest, "must supply a name")
		return
	}
	if user.DOB.IsZero() {
		user.DOB = time.Now() // lies!
	}
	// create new user
	user.ID = len(u.People) + 1
	u.People = append(u.People, user)
	// send restful response
	ctx.Respond(user, http.StatusCreated)
}

// Read handles "GET /v1/users/ID"
func (u *Users) Read(ctx *relax.Context) {
	user, err := u.FindByID(ctx.PathValues.Get("id"))
	if err != nil {
		ctx.Error(err.(*relax.StatusError).Code, err.Error(), "more details here")
		return
	}
	ctx.Respond(user)
}

// Update handles "PUT /v1/users/ID" for changes to items.
func (u *Users) Update(ctx *relax.Context) {
	user, err := u.FindByID(ctx.PathValues.Get("id"))
	if err != nil {
		ctx.Error(err.(*relax.StatusError).Code, err.Error(), "more details here")
		return
	}
	// maybe some validation should go here...

	// decode json payload from client
	if err := ctx.Decode(ctx.Request.Body, &user); err != nil {
		ctx.Error(http.StatusBadRequest, err.Error())
		return
	}
	ctx.Respond(user)
}

// Delete handles "DELETE /v1/users/ID" to remove items.
// Note: this function wont be used because we override the route below.
func (u *Users) Delete(ctx *relax.Context) {
	ctx.Error(http.StatusInternalServerError, "not reached!")
}

// SampleHandler prints out all filter info, and responds with all path values.
func SampleHandler(ctx *relax.Context) {
	ctx.Respond(ctx.PathValues)
}

// Example_basic creates a new service under path "/v1" and serves requests
// for the users resource.
func main() {
	// Create our resource object.
	users := &Users{Group: "Influential Scientists"}

	// Fill-in the users.People list with some scientists (this could be from DB table).
	users.People = []*User{
		&User{1, "Issac Newton", time.Date(1643, 1, 4, 0, 0, 0, 0, time.UTC)},
		&User{2, "Albert Einstein", time.Date(1879, 3, 14, 0, 0, 0, 0, time.UTC)},
		&User{3, "Nikola Tesla", time.Date(1856, 7, 10, 0, 0, 0, 0, time.UTC)},
		&User{4, "Charles Darwin", time.Date(1809, 2, 12, 0, 0, 0, 0, time.UTC)},
		&User{5, "Neils Bohr", time.Date(1885, 10, 7, 0, 0, 0, 0, time.UTC)},
	}

	// Create a service under "/v1". If using absolute URI, it will limit requests
	// to a specific host. This service has FilterLog as service-level filter.
	svc := relax.NewService("/v1", &relax.FilterLog{})

	// More service-level filters (these could go inside NewService()).
	svc.Use(&relax.FilterETag{}) // ETag with cache conditionals
	svc.Use(&relax.FilterCORS{
		AllowAnyOrigin:   true,
		AllowCredentials: true,
	})
	svc.Use(&relax.FilterGzip{})     // on-the-fly gzip encoding
	svc.Use(&relax.FilterOverride{}) // method override support

	// I prefer pretty indentation.
	json := relax.NewEncoderJSON()
	json.Indented = true
	svc.Use(json)

	// Basic authentication, used as needed.
	needsAuth := &relax.FilterAuthBasic{
		Realm: "Masters of Science",
		Authenticate: func(user, pass string) bool {
			if user == "Pi" && pass == "3.14159" {
				return true
			}
			return false
		},
	}

	// Serve our resource with CRUD routes, using unsigned ints as ID's.
	// This resource has FilterSecurity as resource-level filter.
	res := svc.Resource(users, &relax.FilterSecurity{CacheDisable: true}).CRUD("{uint:id}")
	{
		// Although CRUD added a route for "DELETE /v1/users/{uint:id}",
		// we can change it here and respond with status 418.
		teapotted := func(ctx *relax.Context) {
			ctx.Error(418, "YOU are the teapot!", []string{"more details here...", "use your own struct"})
		}
		res.DELETE("{uint:id}", teapotted)

		// Some other misc. routes to test route expressions.
		// Shese routes will be added under "/v1/users/".
		res.GET("dob/{date:date}", SampleHandler)               // Get by ISO 8601 datetime string
		res.PUT("issues/{int:int}", SampleHandler)              // PUT by signed int
		res.GET("apikey/{hex:hex}", res.NotImplemented)         // Get by APIKey (hex value) - 501-"Not Implemented"
		res.GET("@{word:word}", SampleHandler)                  // Get by username (twitterish)
		res.GET("stuff/{whatever}/*", teapotted)                // sure, stuff whatever...
		res.POST("{uint:id}/checkin", SampleHandler, needsAuth) // POST with route-level filter
		res.GET("born/{date:d1}/to/{date:d2}", SampleHandler)   // Get by DOB in date range
		res.PATCH("", res.MethodNotAllowed)                     // PATCH method is not allowed for this resource.

		// Custom regexp PSE matching.
		// Example matching US phone numbers. Any of these values are ok:
		// +1-999-999-1234, +1 999-999-1234, +1 (999) 999-1234, 1-999-999-1234
		// 1 (999) 999-1234, 999-999-1234, (999) 999-1234
		res.GET(`phone/{re:(?:\+?(1)[\- ])?(\([0-9]{3}\)|[0-9]{3})[\- ]([0-9]{3})\-([0-9]{4})}`, SampleHandler)
		// Example matching month digits 01-12
		res.GET(`todos/month/{re:([0][1-9]|[1][0-2])}`, SampleHandler)

		// New internal method extension (notice the X).
		res.Route("XMODIFY", "properties", SampleHandler)
	}

	// Let http.ServeMux handle basic routing.
	http.Handle(svc.Handler())

	log.Fatal(http.ListenAndServe(":8000", nil))
}
Output:

Index

Examples

Constants

View Source
const (
	// ContentMediaType is the vendor extended media type used by this framework.
	ContentMediaType = "application/vnd.relax"

	// ContentDefaultVersion is the default version value when no content version is requested.
	ContentDefaultVersion = "current"

	// ContentDefaultLanguage is the default langauge value when no content language is requested.
	ContentDefaultLanguage = "en-US"
)
View Source
const (
	StatusPreconditionRequired          = 428
	StatusTooManyRequests               = 429
	StatusRequestHeaderFieldsTooLarge   = 431
	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 also, https://tools.ietf.org/html/rfc6585

View Source
const (
	// LogFormatRelax is the default Relax post-event format
	LogFormatRelax = "%C [%-.8[1]L] \"%#[1]r\" => \"%#[1]s\" done in %.6[1]Ds"

	// LogFormatCommon is similar to Apache HTTP's Common Log Format (CLF)
	LogFormatCommon = "%h %[1]l %[1]u %[1]t \"%[1]r\" %#[1]s %[1]b"

	// LogFormatExtended is similar to NCSA extended/combined log format
	LogFormatExtended = LogFormatCommon + " \"%[1]R\" \"%[1]A\""

	// LogFormatReferer is similar to Apache HTTP's Referer log format
	LogFormatReferer = "%R -> %[1]U"
)

Pre-made log formats. Most are based on Apache HTTP's. Note: the [n] notation will index an specific argument from Sprintf list.

View Source
const Version = "0.5.2"

Version is the version of this package.

Variables

View Source
var (
	// ErrAuthInvalidRequest is returned when the auth request don't match the expected
	// challenge.
	ErrAuthInvalidRequest = errors.New("auth: Invalid authorization request")

	// ErrAuthInvalidSyntax is returned when the syntax of the credentials is not what is
	// expected.
	ErrAuthInvalidSyntax = errors.New("auth: Invalid credentials syntax")
)

Errors returned by FilterAuthBasic that are general and could be reused.

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

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 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 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 RFC 4287). Returns two strings: "Link","Link header spec"

func MustAuthenticate

func MustAuthenticate(w http.ResponseWriter, challenge string)

MustAuthenticate is a helper function used to send the WWW-Authenticate HTTP header. challenge is the auth scheme and the realm, as specified in section 2 of RFC 2617.

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

The CRUD interface is for Resourcer objects that provide create, read, update and delete operations, also known as CRUD.

type Context

type Context struct {
	// ResponseWriter is the response object passed from “net/http“.
	http.ResponseWriter

	// Buffer points to a buffered context, started with Context.Capture.
	// If not capturing, Buffer is nil.
	// See also: ResponseBuffer
	Buffer *ResponseBuffer

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

	// Info contains information passed down from processed filters.
	// To print all values to stdout use:
	//		ctx.Info.Print()
	//
	// For usage, see http://github.com/codehack/go-environ
	Info *environ.Env

	// Encode is the media encoding function requested by the client.
	// To see the media type use:
	//		ctx.Info.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.Info.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 NewContext

func NewContext(w http.ResponseWriter, r *http.Request) *Context

NewContext returns a new Context object. This function will alter Request.URL, adding scheme and host:port as provided by the client.

func (*Context) Bytes

func (ctx *Context) Bytes() int

Bytes returns the number of bytes written in the response.

func (*Context) Capture

func (ctx *Context) Capture() *Context

Capture starts a buffered context. All writes are diverted to a ResponseBuffer. Capture expects a call to Context.Release to end capturing. Returns a new buffered Context. See also: NewResponseBuffer, Context.Release

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 appropiate 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 reponse 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 reponse in bytes, excluding headers.
%C  	Colorized status code. For console, using ANSI escape codes.
%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) Free

func (ctx *Context) Free()

Free frees a Context object back to the usage pool for later, to conserve system resources.

func (*Context) Header

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

Header implements ResponseWriter.Header

func (*Context) IsSSL

func (ctx *Context) IsSSL() bool

IsSSL returns true if the context request 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.

func (*Context) ProxyClient

func (ctx *Context) ProxyClient() string

ProxyClient 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 (*Context) Release

func (ctx *Context) Release()

Release ends capturing within the context. Every Capture call needs a Release, otherwise the buffer will over-extend and the response will fail. You may or not defer this call after Capture, it depends on your state.

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

func NewEncoderJSON() *EncoderJSON

NewEncoderJSON 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 FilterAuthBasic

type FilterAuthBasic struct {
	// Realm is the authentication realm.
	// This defaults to "Authorization Required"
	Realm string

	// Authenticate is a function that will perform the actual authentication
	// check.
	// It should expect a username and password, then return true if those
	// credentials are accepted; false otherwise.
	// If no function is assigned, it defaults to a function that denies all
	// (false).
	Authenticate func(string, string) bool
}

FilterAuthBasic is a Filter that implements HTTP Basic Authentication as described in http://www.ietf.org/rfc/rfc2617.txt

func (*FilterAuthBasic) Run

Run runs the filter and passes down the following Info:

ctx.Info.Get("auth.user") // auth user
ctx.Info.Get("auth.type") // auth scheme type. e.g., "basic"

type FilterCORS

type FilterCORS struct {
	// AllowOrigin is the list of URI patterns that are allowed to use the resource.
	// The patterns consist of text with zero or more wildcards '*' '?' '+'.
	//
	// '*' matches zero or more characters.
	// '?' matches exactly one character.
	// '_' matches zero or one character.
	// '+' matches at least one character.
	//
	// Note that a single pattern of '*' will match all origins, if that's what you need
	// then use AllowAnyOrigin=true instead. If AllowOrigin is empty and AllowAnyOrigin=false,
	// then all CORS requests (simple and preflight) will fail with an HTTP error response.
	//
	// Examples:
	// 	http://*example.com - matches example.com and all its subdomains.
	// 	http_://+.example.com - matches SSL and non-SSL, and subdomains of example.com, but not example.com
	// 	http://foo??.example.com - matches subdomains fooXX.example.com where X can be any character.
	//		chrome-extension://* - good for testing from Chrome.
	//
	// Default: empty
	AllowOrigin []string

	// AllowAnyOrigin if set to true, it will allow all origin requests.
	// This is effectively "Access-Control-Allow-Origin: *" as in the CORS specification.
	//
	// Default: false
	AllowAnyOrigin bool

	// AllowMethods is the list of HTTP methods that can be used in a request. If AllowMethods
	// is empty, all permission requests (preflight) will fail with an HTTP error response.
	//
	// Default: "GET", "POST", "PATCH", "PUT", "DELETE"
	AllowMethods []string

	// AllowHeaders is the list of HTTP headers that can be used in a request. If AllowHeaders
	// is empty, then only simple common HTTP headers are allowed.
	//
	// Default: "Authorization", "Content-Type", "If-Match", "If-Modified-Since", "If-None-Match", "If-Unmodified-Since", "X-Requested-With"
	AllowHeaders []string

	// AllowCredentials whether or not to allow user credendials to propagate through a request.
	// If AllowCredentials is false, then all authentication and cookies are disabled.
	//
	// Default: false
	AllowCredentials bool

	// ExposeHeaders is a list of HTTP headers that can be exposed to the API. This list should
	// include any custom headers that are needed to complete the response.
	//
	// Default: "Etag", "Link", "RateLimit-Limit", "RateLimit-Remaining", "RateLimit-Reset", "X-Poll-Interval"
	ExposeHeaders []string

	// MaxAge is a number of seconds the permission request (preflight) results should be cached.
	// This number should be large enough to complete all request from a client, but short enough to
	// keep the API secure. Set to -1 to disable caching.
	//
	// Default: 86400
	MaxAge int

	// Strict specifies whether or not to adhere strictly to the W3C CORS recommendation. If
	// Strict=false then the focus is performance instead of correctness. Also, Strict=true
	// will add more security checks to permission requests (preflight) and other security decisions.
	//
	// Default: false
	Strict bool
}

FilterCORS implements the Cross-Origin Resource Sharing (CORS) recommendation, as described in http://www.w3.org/TR/cors/ (W3C).

func (*FilterCORS) Run

func (f *FilterCORS) Run(next HandlerFunc) HandlerFunc

Run runs the filter and passes down the following Info:

ctx.Info.Get("cors.request") // boolean, whether or not this was a CORS request.
ctx.Info.Get("cors.origin")  // Origin of the request, if it's a CORS request.

type FilterETag

type FilterETag struct {
	// DisableConditionals will make this filter ignore the values from the headers
	// If-None-Match and If-Match and not do conditional entity tests. An ETag will
	// still be generated, if possible.
	// Defaults to false
	DisableConditionals bool
}

FilterETag generates an entity-tag header "ETag" for body content of a response. It will use pre-generated etags from the underlying filters or handlers, if availble. Optionally, it will also handle the conditional response based on If-Match and If-None-Match checks on specific entity-tag values. This implementation follows the recommendation in http://tools.ietf.org/html/rfc7232

func (*FilterETag) Run

func (f *FilterETag) Run(next HandlerFunc) HandlerFunc

Run runs the filter and passes down the following Info:

ctx.Info.Get("etag.enabled") // boolean; true if etag is enabled (always)

type FilterGzip

type FilterGzip struct {
	// CompressionLevel specifies the level of compression used for gzip.
	// Value must be between -1 (gzip.DefaultCompression) to 9 (gzip.BestCompression)
	// A value of 0 (gzip.DisableCompression) will disable compression.
	// Defaults to “gzip.BestSpeed“
	CompressionLevel int

	// MinLength is the minimum content length, in bytes, required to do compression.
	// Defaults to 100
	MinLength int
}

FilterGzip compresses the response with gzip encoding, if the client indicates support for it.

func (*FilterGzip) Run

func (f *FilterGzip) Run(next HandlerFunc) HandlerFunc

Run runs the filter and passes down the following Info:

ctx.Info.Get("content.gzip") // boolean; whether gzip actually happened.

The info passed is used by ETag to generate distinct entity-tags for gzip'ed content.

type FilterLog

type FilterLog struct {
	// Logger is an interface that is based on Go's log package. Any logging
	// system that implements Logger can be used.
	// Defaults to the stdlog in 'log' package.
	Logger

	// PreLogFormat is the format for the pre-request log entry.
	// Leave empty if no log even is needed.
	// Default to empty (no pre-log)
	PreLogFormat string

	// PostLogFormat is the format for the post-request log entry.
	// Defaults to the value of LogFormatRelax
	PostLogFormat string
}

FilterLog provides pre- and post-request event logs. It uses a custom log format similar to the one used for Apache HTTP CustomLog directive.

myservice.Use(logrus.New())
log := &FilterLog{Logger: myservice.Logger(), PreLogFormat: LogFormatReferer}
log.Println("FilterLog implements Logger.")

// Context-specific format verbs (see Context.Format)
log.Panicf("%C bad status", ctx)

func (*FilterLog) Run

func (f *FilterLog) Run(next HandlerFunc) HandlerFunc

Run processes the filter. No info is passed.

type FilterOverride

type FilterOverride struct {
	// Header expected for HTTP Method override
	// Default: "X-HTTP-Method-Override"
	Header string

	// QueryVar is used if header can't be set
	// Default: "_method"
	QueryVar string

	// Methods specifies the methods can be overriden.
	// Format is Methods["method"] = "override".
	// Default methods:
	//		f.Methods = map[string]string{
	//			"DELETE":  "POST",
	//			"OPTIONS": "GET",
	//			"PATCH":   "POST",
	//			"PUT":     "POST",
	//		}
	Methods map[string]string
}

FilterOverride changes the Request.Method if the client specifies override via HTTP header or query. This allows clients with limited HTTP verbs to send REST requests through GET/POST.

func (*FilterOverride) Run

func (f *FilterOverride) Run(next HandlerFunc) HandlerFunc

Run runs the filter and passes down the following Info:

ctx.Info.Get("override.method") // method replaced. e.g., "DELETE"

type FilterSecurity

type FilterSecurity struct {
	// UACheckDisable if false, a check is done to see if the client sent a valid non-emtpy
	// User-Agent header with the request.
	// Defaults to false.
	UACheckDisable bool

	// UACheckErrMsg is the response body sent when a client fails User-Agent check.
	// Defaults to (taken from Heroku's UA check message):
	// 	"Request forbidden by security rules.\n" +
	// 	"Please make sure your request has an User-Agent header."
	UACheckErrMsg string

	// XFrameDisable if false, will send a X-Frame-Options header with the response,
	// using the value in XFrameOptions. X-Frame-Options provides clickjacking protection.
	// For details see https://www.owasp.org/index.php/Clickjacking
	// https://www.rfc-editor.org/rfc/rfc7034.txt
	// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-12
	// Defaults to false.
	XFrameDisable bool

	// XFrameOptions expected values are:
	//		"DENY"                // no rendering within a frame
	//		"SAMEORIGIN"          // no rendering if origin mismatch
	//		"ALLOW-FROM {origin}" // allow rendering if framed by frame loaded from {origin};
	//			              // where {origin} is a top-level URL. ie., http//codehack.com
	// Only one value can be used at a time.
	// Defaults to "SAMEORIGIN"
	XFrameOptions string

	// XCTODisable if false, will send a X-Content-Type-Options header with the response
	// using the value "nosniff". This prevents Internet Explorer and Google Chrome from
	// MIME-sniffing and ignoring the value set in Content-Type.
	// Defaults to false.
	XCTODisable bool

	// HSTSDisable if false, will send a Strict-Transport-Security (HSTS) header
	// with the respose, using the value in HSTSOptions. HSTS enforces secure
	// connections to the server. http://tools.ietf.org/html/rfc6797
	// If the server is not on a secure HTTPS/TLS connection, it will temporarily
	// change to true.
	// Defaults to false.
	HSTSDisable bool

	// HSTSOptions are the values sent in an HSTS header.
	// Expected values are one or both of:
	//		"max-age=delta"     // delta in seconds, the time this host is a known HSTS host
	//		"includeSubDomains" // HSTS policy applies to this domain and all subdomains.
	// Defaults to "max-age=31536000; includeSubDomains"
	HSTSOptions string

	// CacheDisable if false, will send a Cache-Control header with the response,
	// using the value in CacheOptions. If this value is true, it will also
	// disable Pragma header (see below).
	// Defaults to false.
	CacheDisable bool

	// CacheOptions are the value sent in an Cache-Control header.
	// For details, see http://tools.ietf.org/html/rfc7234#section-5.2
	// Defaults to "no-store, must-revalidate"
	CacheOptions string

	// PragmaDisable if false and CacheDisable is false, will send a Pragma header
	// with the response, using the value "no-cache".
	// For details see http://tools.ietf.org/html/rfc7234#section-5.4
	// Defaults to false.
	PragmaDisable bool
}

FilterSecurity is a Filter that provides some security options and checks. Most of the options are HTTP headers sent back so that web clients can adjust their configuration. See https://www.owasp.org/index.php/List_of_useful_HTTP_headers

func (*FilterSecurity) Run

func (f *FilterSecurity) Run(next HandlerFunc) HandlerFunc

Run runs the filter and passes down the following Info:

ctx.Info.Get("security.sts") // boolean; whether STS was enabled.

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 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"`
	Type     string `json:"type,omitempty"`
}

Link 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 URI Template: http://tools.ietf.org/html/rfc6570

func (*Link) String

func (l *Link) String() string

String returns a string representation of a Link object. Suitable for use in Link: 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 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.
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 (*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) 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) 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(*Context) (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.

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

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", &FilterETag{})

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.Info.Get("context.start_time")  // Time when request started, as string time.Time.
ctx.Info.Get("context.request_id")  // Unique or user-supplied request ID.

Returns an http.HandlerFunc function that can be used with http.Handle.

func (*Service) Content

func (svc *Service) Content(next HandlerFunc) HandlerFunc

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=XX; lang=YY

The values for {subtype}, {version} and {lang} 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"

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.Info.Get("content.encoding") // media type used for encoding
ctx.Info.Get("content.decoding") // Type used in payload requests POST/PUT/PATCH
ctx.Info.Get("content.version")  // requested version, or "current"
ctx.Info.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.

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) 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() *ServiceOptions

Options returns the options available from this service. This information is useful when creating OPTIONS routes.

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) 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(&FilterCORS{})
myservice.Use(&FilterSecurity{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 ServiceOptions

type ServiceOptions struct {
	BaseURI string `json:"href"`
	Media   struct {
		Type     string   `json:"type"`
		Version  string   `json:"version"`
		Language string   `json:"language"`
		Encoders []string `json:"encoders"`
	} `json:"media"`
}

ServiceOptions has a description of the options available for using this service. This is used by the OPTIONS handler.

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

Jump to

Keyboard shortcuts

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