Documentation ¶
Overview ¶
Package goserv provides a fast, easy and minimalistic framework for web applications in Go.
goserv requires at least Go v1.6
Getting Started ¶
The first thing to do is to import the goserv package:
import "github.com/gotschmarcel/goserv"
After that we need a goserv.Server as our entry point for incoming requests.
server := goserv.NewServer()
To start handling things we must register handlers to paths using one of the Server's embedded Router functions, like Get.
server.Get("/", func (w http.ResponseWriter, r *http.Request) { goserv.WriteString(w, "Welcome Home") })
The first argument in the Get() call is the path for which the handler gets registered and the second argument is the handler function itself. To learn more about the path syntax take a look at the "Path Syntax" section.
As the name of the function suggests the requests are only getting dispatched to the handler if the request method is "GET". There are a lot more methods like this one available, just take a look at the documentation of the Router or Route.
Sometimes it is useful to have handlers that are invoked all the time, also known as middleware. To register middleware use the Use() function.
server.Use(func(w http.ResponseWriter, r *http.Request) { log.Printf("Access %s %s", req.Method, req.URL.String()) })
After we've registered all handlers there is only one thing left to do, which is to start listening for incoming requests. The easiest way to do that, is to use the Listen method of goserv.Server which is a convenience wrapper around http.ListenAndServe.
err := server.Listen(":12345")
Now we have a running HTTP server which automatically dispatches incoming requests to the right handler functions. This was of course just a simple example of what can be achieved with goserv. To get deeper into it take a look at the examples or read the reference documentation below.
Path Syntax ¶
All Routes and Routers are registered under a path. This path is matched against the path of incoming requests and decides wether or not a handler will be processed. The following examples show the features of the path syntax supported by goserv.
NOTE: Paths must start with a "/". Also query strings are not part of a path.
This simple route will match request to "/mypath":
server.Get("/mypath", handler)
To match everything starting with "/mypath" use an asterisk as wildcard:
server.Get("/mypath*", handler)
The wildcard can be positioned anywhere. Multiple wildcards are also possible. The following route matches request to "/mypath" or anything starting with "/my" and ending with "path", e.g. "/myfunnypath":
server.Get("/my*path", handler)
The next route matches requests to "/abc" or "/ac" by using the "?" expression:
server.Get("/ab?c", handler)
To make multiple characters optional wrap them into parentheses:
server.Get("/a(bc)?d", handler)
Sometimes it is necessary to capture values from parts of the request path, so called parameters. To include parameters in a Route the path must contain a named parameter:
server.Get("/users/:user_id", handler)
Parameters always start with a ":". The name (without the leading ":") can contain alphanumeric symbols as well as "_" and "-". By default a parameter captures everything until the next slash. This behavior can be changed by providing a custom matching pattern:
server.Get("/users/:user_id(\\d+)", handler)
Remember to escape the backslash when using custom patterns.
Strict vs non-strict Slash ¶
A Route can have either strict slash or non-strict slash behavior. In non-strict mode paths with or without a trailing slash are considered to be the same, i.e. a Route registered with "/mypath" in non-strict mode matches both "/mypath" and "/mypath/". In strict mode both paths are considered to be different. The behavior can be modified by changing a Router's .StrictSlash property. Sub routers automatically inherit the strict slash behavior from their parent.
Order matters ¶
The order in which handlers are registered does matter, since incoming requests go through the exact same order. After each handler the Router checks wether an error was set on the ResponseWriter or if a response was written and ends the processing if necessary. In case of an error the Router forwards the request along with the error to its ErrorHandler, but only if one is available. All sub Routers have no ErrorHandler by default, so all errors are handled by the top level Server. It is possible though to handle errors in a sub Router by setting a custom ErrorHandler.
Index ¶
- Variables
- func AddHeaders(headers map[string]string) http.HandlerFunc
- func AllowedHosts(hosts []string, useXForwardedHost bool) http.HandlerFunc
- func ReadJSONBody(r *http.Request, result interface{}) error
- func SanitizePath(p string) string
- func WriteJSON(w http.ResponseWriter, v interface{}) error
- func WriteString(w http.ResponseWriter, s string) error
- func WriteStringf(w http.ResponseWriter, format string, v ...interface{})
- type ContextError
- type ErrorHandlerFunc
- type ParamHandlerFunc
- type RequestContext
- func (r *RequestContext) Delete(key string)
- func (r *RequestContext) Error(err error, code int)
- func (r *RequestContext) Exists(key string) bool
- func (r *RequestContext) Get(key string) interface{}
- func (r *RequestContext) Param(name string) string
- func (r *RequestContext) Set(key string, value interface{})
- func (r *RequestContext) SkipRouter()
- type Route
- func (r *Route) All(fn http.HandlerFunc) *Route
- func (r *Route) Delete(fn http.HandlerFunc) *Route
- func (r *Route) Get(fn http.HandlerFunc) *Route
- func (r *Route) Method(method string, fn http.HandlerFunc) *Route
- func (r *Route) Methods(methods []string, fn http.HandlerFunc) *Route
- func (r *Route) Patch(fn http.HandlerFunc) *Route
- func (r *Route) Post(fn http.HandlerFunc) *Route
- func (r *Route) Put(fn http.HandlerFunc) *Route
- func (r *Route) Rest(fn http.HandlerFunc) *Route
- type Router
- func (r *Router) All(path string, fn http.HandlerFunc) *Router
- func (r *Router) Delete(path string, fn http.HandlerFunc) *Router
- func (r *Router) Get(path string, fn http.HandlerFunc) *Router
- func (r *Router) Method(method, path string, fn http.HandlerFunc) *Router
- func (r *Router) Methods(methods []string, path string, fn http.HandlerFunc) *Router
- func (r *Router) Param(name string, fn ParamHandlerFunc) *Router
- func (r *Router) Patch(path string, fn http.HandlerFunc) *Router
- func (r *Router) Path() string
- func (r *Router) Post(path string, fn http.HandlerFunc) *Router
- func (r *Router) Put(path string, fn http.HandlerFunc) *Router
- func (r *Router) Route(path string) *Route
- func (r *Router) SubRouter(prefix string) *Router
- func (r *Router) Use(fn http.HandlerFunc) *Router
- func (r *Router) UseHandler(handler http.Handler) *Router
- type Server
- type TLS
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound is passed to the error handler if // no route matched the request path or none of the matching routes wrote // a response. ErrNotFound = errors.New(http.StatusText(http.StatusNotFound)) // ErrDisallowedHost is passed to the error handler if a handler // created with .AllowedHosts() found a disallowed host. ErrDisallowedHost = errors.New("disallowed host") )
var StdErrorHandler = func(w http.ResponseWriter, r *http.Request, err *ContextError) { w.WriteHeader(err.Code) fmt.Fprintf(w, err.Error()) }
StdErrorHandler is the default ErrorHandler added to all Server instances created with NewServer().
Functions ¶
func AddHeaders ¶
func AddHeaders(headers map[string]string) http.HandlerFunc
AddHeaders returns a new HandlerFunc which adds the specified response headers.
func AllowedHosts ¶
func AllowedHosts(hosts []string, useXForwardedHost bool) http.HandlerFunc
AllowedHosts returns a new HandlerFunc validating the HTTP Host header.
Values can be fully qualified (e.g. "www.example.com"), in which case they must match the Host header exactly. Values starting with a period (e.g. ".example.com") will match example.com and all subdomains (e.g. www.example.com)
If useXForwardedHost is true the X-Forwarded-Host header will be used in preference to the Host header. This is only useful if a proxy which sets the header is in use.
func ReadJSONBody ¶
ReadJSONBody decodes the request's body utilizing encoding/json. The body is closed after the decoding and any errors occured are returned.
func SanitizePath ¶
SanitizePath returns the clean version of the specified path.
It prepends a "/" to the path if none was found, uses path.Clean to resolve any "." and ".." and adds back any trailing slashes.
func WriteJSON ¶
func WriteJSON(w http.ResponseWriter, v interface{}) error
WriteJSON writes the passed value as JSON to the ResponseWriter utilizing the encoding/json package. It also sets the Content-Type header to "application/json". Any errors occured during encoding are returned.
func WriteString ¶
func WriteString(w http.ResponseWriter, s string) error
WriteString writes the s to the ResponseWriter utilizing io.WriteString. It also sets the Content-Type to "text/plain; charset=utf8". Any errors occured during Write are returned.
func WriteStringf ¶
func WriteStringf(w http.ResponseWriter, format string, v ...interface{})
WriteStringf writes a formatted string to the ResponseWriter utilizing fmt.Fprintf. It also sets the Content-Type to "text/plain; charset=utf8".
Types ¶
type ContextError ¶
A ContextError stores an error along with a response code usually in the range 4xx or 5xx. The ContextError is passed to the ErrorHandler.
func (*ContextError) Error ¶
func (c *ContextError) Error() string
Error returns the result of calling .Error() on the stored error.
func (*ContextError) String ¶
func (c *ContextError) String() string
String returns a formatted string with this format: (<code>) <error>.
type ErrorHandlerFunc ¶
type ErrorHandlerFunc func(http.ResponseWriter, *http.Request, *ContextError)
A ErrorHandlerFunc is the last handler in the request chain and is responsible for handling errors that occur during the request processing.
A ErrorHandlerFunc should always write a response!
type ParamHandlerFunc ¶
type ParamHandlerFunc func(http.ResponseWriter, *http.Request, string)
A ParamHandlerFunc can be registered to a Router using a parameter's name. It gets invoked with the corresponding value extracted from the request's path.
Parameters are part of a Route's path. To learn more about parameters take a look at the documentation of Route.
type RequestContext ¶
type RequestContext struct {
// contains filtered or unexported fields
}
RequestContext allows sharing of data between handlers by storing key-value pairs of arbitrary types. It also provides the captured URL parameter values depending on the current route.
Any occuring errors during the processing of handlers can be set on the RequestContext using .Error. By setting an error all Routers and Routes will stop processing immediately and the error is passed to the next error handler.
func Context ¶
func Context(r *http.Request) *RequestContext
Context returns the corresponding RequestContext for the given Request.
func (*RequestContext) Delete ¶
func (r *RequestContext) Delete(key string)
Delete deletes the value associated with key. If the key doesn't exist nothing happens.
func (*RequestContext) Error ¶
func (r *RequestContext) Error(err error, code int)
Error sets a ContextError which will be passed to the next error handler and forces all Routers and Routes to stop processing.
Note: Calling Error from different threads can cause race conditions. Also calling Error more than once causes a runtime panic!
func (*RequestContext) Exists ¶
func (r *RequestContext) Exists(key string) bool
Exists returns true if the specified key exists in the RequestContext, otherwise false is returned.
func (*RequestContext) Get ¶
func (r *RequestContext) Get(key string) interface{}
Get retrieves the value for key. If the key doesn't exist in the RequestContext, Get returns nil.
func (*RequestContext) Param ¶
func (r *RequestContext) Param(name string) string
Param returns the capture URL parameter value for the given parameter name. The name is the one specified in one of the routing functions without the leading ":".
func (*RequestContext) Set ¶
func (r *RequestContext) Set(key string, value interface{})
Set sets the value for the specified the key. It replaces any existing values.
func (*RequestContext) SkipRouter ¶
func (r *RequestContext) SkipRouter()
SkipRouter tells the current router to end processing, which means that the parent router will continue processing.
Calling SkipRouter in a top level route causes a "not found" error.
type Route ¶
type Route struct {
// contains filtered or unexported fields
}
A Route handles requests by processing method handlers.
Note that all handler functions return the Route itself to allow method chaining, e.g.
route.All(middleware).Get(getHandler).Put(putHandler)
func (*Route) All ¶
func (r *Route) All(fn http.HandlerFunc) *Route
All registers the specified functions for all methods in the order of appearance.
func (*Route) Delete ¶
func (r *Route) Delete(fn http.HandlerFunc) *Route
Delete is an adapter for .Method and registers the functions for the "DELETE" method.
func (*Route) Get ¶
func (r *Route) Get(fn http.HandlerFunc) *Route
Get is an adapter for .Method and registers the functions for the "GET" method.
func (*Route) Method ¶
func (r *Route) Method(method string, fn http.HandlerFunc) *Route
Method registers the functions for the specified HTTP method in the order of appearance.
func (*Route) Methods ¶
func (r *Route) Methods(methods []string, fn http.HandlerFunc) *Route
Methods is an adapter for .Method to register functions for multiple methods in one call.
func (*Route) Patch ¶
func (r *Route) Patch(fn http.HandlerFunc) *Route
Patch is an adapter for .Method and registers the functions for the "PATCH" method.
func (*Route) Post ¶
func (r *Route) Post(fn http.HandlerFunc) *Route
Post is an adapter for .Method and registers the functions for the "POST" method.
type Router ¶
type Router struct { // Handles errors set on the RequestContext with .Error, not found errors // and recovered panics. ErrorHandler ErrorHandlerFunc // Defines how Routes treat the trailing slash in a path. // // When enabled routes with a trailing slash are considered to be different routes // than routes without a trailing slash. StrictSlash bool // Enables/Disables panic recovery PanicRecovery bool // contains filtered or unexported fields }
A Router dispatches incoming requests to matching routes and routers.
Note that most methods return the Router itself to allow method chaining. Some methods like .Route or .SubRouter return the created instances instead.
func (*Router) All ¶
func (r *Router) All(path string, fn http.HandlerFunc) *Router
All registers the specified HandlerFunc for the given path for all http methods.
func (*Router) Delete ¶
func (r *Router) Delete(path string, fn http.HandlerFunc) *Router
Delete is an adapter for Method registering a HandlerFunc for the "DELETE" method on path.
func (*Router) Get ¶
func (r *Router) Get(path string, fn http.HandlerFunc) *Router
Get is an adapter for Method registering a HandlerFunc for the "GET" method on path.
func (*Router) Method ¶
func (r *Router) Method(method, path string, fn http.HandlerFunc) *Router
Method registers the specified HandlerFunc for the given path and method.
func (*Router) Methods ¶
Methods is an adapter for Method for registering a HandlerFunc on a path for multiple methods in a single call.
func (*Router) Param ¶
func (r *Router) Param(name string, fn ParamHandlerFunc) *Router
Param registers a handler for the specified parameter name (without the leading ":").
Parameter handlers are invoked with the extracted value before any route is processed. All handlers are only invoked once per request, even though the request may be dispatched to multiple routes.
func (*Router) Patch ¶
func (r *Router) Patch(path string, fn http.HandlerFunc) *Router
Patch is an adapter for Method registering a HandlerFunc for the "PATCH" method on path.
func (*Router) Post ¶
func (r *Router) Post(path string, fn http.HandlerFunc) *Router
Post is an adapter for Method registering a HandlerFunc for the "POST" method on path.
func (*Router) Put ¶
func (r *Router) Put(path string, fn http.HandlerFunc) *Router
Put is an adapter for Method registering a HandlerFunc for the "PUT" method on path.
func (*Router) SubRouter ¶
SubRouter returns a new sub router mounted on the specified prefix.
All sub routers automatically inherit their StrictSlash behaviour, have the full mount path and no error handler. It is possible though to set a custom error handler for a sub router.
Note that this function returns the new sub router instead of the parent router!
type Server ¶
type Server struct { // Embedded Router *Router // TCP address to listen on, set by .Listen or .ListenTLS Addr string // TLS information set by .ListenTLS or nil if .Listen was used TLS *TLS }
A Server is the main instance and entry point for all routing.
It is compatible with the http package an can be used as a http.Handler. A Server is also a Router and provides the same fields and methods as the goserv.Router.
Additionally to all routing methods a Server provides methods to register static file servers, short-hand methods for http.ListenAndServe as well as http.ListenAndServeTLS and the possibility to recover from panics.
Example (Context) ¶
package main import ( "github.com/gotschmarcel/goserv" "log" "net/http" ) func main() { // Share data between handlers: // // The middleware stores a shared value in the RequestContext under the name "shared". // The GET handler is the next handler in line and retrieves the value from the // context. Since a RequestContext can store arbitrary types a type assertion // is necessary to get the value in it's real type. server := goserv.NewServer() server.Use(func(w http.ResponseWriter, r *http.Request) { goserv.Context(r).Set("shared", "something to share") }) server.Get("/", func(w http.ResponseWriter, r *http.Request) { shared := goserv.Context(r).Get("shared").(string) goserv.WriteString(w, shared) }) log.Fatalln(server.Listen(":12345")) }
Output:
Example (ErrorHandling) ¶
package main import ( "fmt" "github.com/gotschmarcel/goserv" "log" "net/http" ) func main() { // Custom error handling: // // Every Router can have its own error handler. In this example // a custom error handler is set on the API sub router to handler // all errors occured on the /api route. // // Note: it is also possible to overwrite the default error handler of // the server. server := goserv.NewServer() server.Get("/error", func(w http.ResponseWriter, r *http.Request) { err := fmt.Errorf("a server error") goserv.Context(r).Error(err, http.StatusInternalServerError) }) api := server.SubRouter("/api") api.Get("/error", func(w http.ResponseWriter, r *http.Request) { err := fmt.Errorf("a API error") goserv.Context(r).Error(err, http.StatusInternalServerError) }) // Handle errors occured on the API router. api.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err *goserv.ContextError) { log.Printf("API Error: %s", err) w.WriteHeader(err.Code) goserv.WriteString(w, err.String()) } log.Fatalln(server.Listen(":12345")) }
Output:
Example (Json) ¶
package main import ( "github.com/gotschmarcel/goserv" "log" "net/http" ) func main() { // Example server showing how to read and write JSON body: // // Since WriteJSON and ReadJSONBody are based on the encoding/json // package of the standard library the usage is very similar. // One thing to notice is that occuring errors are passed to // the RequestContext which stops further processing and passes // the error to the server's error handler. server := goserv.NewServer() // Send a simple JSON response. server.Get("/", func(w http.ResponseWriter, r *http.Request) { // JSON data to send. data := &struct{ Title string }{"My First Todo"} // Try to write the data. // In case of an error pass it to the RequestContext // so it gets forwarded to the next error handler. if err := goserv.WriteJSON(w, data); err != nil { goserv.Context(r).Error(err, http.StatusInternalServerError) return } }) // Handle send JSON data. server.Post("/", func(w http.ResponseWriter, r *http.Request) { var data struct{ Title string } // Read and decode the request's body. // In case of an error pass it to the RequestContext // so it gets forwarded to the next error handler. if err := goserv.ReadJSONBody(r, &data); err != nil { goserv.Context(r).Error(err, http.StatusBadRequest) return } log.Println(data) }) log.Fatalln(server.Listen(":12345")) }
Output:
Example (Parameters) ¶
package main import ( "fmt" "github.com/gotschmarcel/goserv" "log" "net/http" ) func main() { // Use URL parameters: // // URL parameters can be specified by prefixing the name with a ":" in the handler path. // The captured value can be retrieved from the RequestContext using the .Param method and // the parameter's name. // // Servers and Routers both support parameter handlers which can be added using the // .Param method, i.e. server.Param(...). The first argument is the name of the parameter // (without the leading ":"). The parameter handlers are always invoked once before // the request handlers get invoked. // server := goserv.NewServer() // This route captures a single URL parameter named "resource_id". server.Get("/resource/:resource_id", func(w http.ResponseWriter, r *http.Request) { id := goserv.Context(r).Param("resource_id") goserv.WriteStringf(w, "Requested resource: %s", id) }) // Registers a parameter handler for the "resource_id" parameter. server.Param("resource_id", func(w http.ResponseWriter, r *http.Request, id string) { // Some sort of validation. if len(id) < 12 { goserv.Context(r).Error(fmt.Errorf("Invalid id"), http.StatusBadRequest) return } log.Printf("Requesting resource: %s", id) }) log.Fatalln(server.Listen(":12345")) }
Output:
Example (Simple) ¶
package main import ( "github.com/gotschmarcel/goserv" "log" "net/http" ) func main() { // A simple server example. // // First an access logging function is registered which gets invoked // before the request is forwarded to the home handler. After that // the home handler is registered which is the final handler writing // a simple message to the response body. // // As a last step server.Listen is called to start listening for incoming // requests. server := goserv.NewServer() server.Use(func(w http.ResponseWriter, r *http.Request) { log.Printf("Access %s %s", r.Method, r.URL.String()) }).Get("/", func(w http.ResponseWriter, r *http.Request) { goserv.WriteString(w, "Welcome Home") }) log.Fatalln(server.Listen(":12345")) }
Output:
Example (Static) ¶
package main import ( "github.com/gotschmarcel/goserv" "log" "net/http" ) func main() { // Example file server: server := goserv.NewServer() server.UseHandler(http.FileServer(http.Dir("/files"))) log.Fatalln(server.Listen(":12345")) }
Output:
func NewServer ¶
func NewServer() *Server
NewServer returns a newly allocated and initialized Server instance.
By default the Server has no template engine, the template root is "" and panic recovery is disabled. The Router's ErrorHandler is set to the StdErrorHandler.