goserv

package module
v1.0.3 Latest Latest
Warning

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

Go to latest
Published: May 21, 2016 License: BSD-3-Clause Imports: 11 Imported by: 1

README

goserv

GoServ

A fast, easy and minimalistic framework for web applications in Go.

goserv requires at least Go v1.6.0

GoDoc Build Status

Read all about it at goserv.it

package main

import (
	"github.com/gotschmarcel/goserv"
	"net/http"
	"log"
)

func main() {
	server := goserv.NewServer()
	server.Get("/", func (w http.ResponseWriter, r *http.Request) {
		goserv.WriteString(w, "Welcome Home")
	}
	log.Fatalln(server.Listen(":12345"))
}

Installation

$ go get github.com/gotschmarcel/goserv

Features

  • Fully compatible with net/http
  • Robust and fast routing
  • Middleware handlers
  • Nested routers
  • Request context
  • URL parameters
  • Response and request helpers
  • Centralized error handling

Examples

Examples can be found in example_test.go

License

BSD licensed. See the LICENSE file for more information.

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

Examples

Constants

This section is empty.

Variables

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

func ReadJSONBody(r *http.Request, result interface{}) error

ReadJSONBody decodes the request's body utilizing encoding/json. The body is closed after the decoding and any errors occured are returned.

func SanitizePath

func SanitizePath(p string) string

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

type ContextError struct {
	Err  error
	Code int
}

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.

func (*Route) Put

func (r *Route) Put(fn http.HandlerFunc) *Route

Put is an adapter for .Method and registers the functions for the "PUT" method.

func (*Route) Rest

func (r *Route) Rest(fn http.HandlerFunc) *Route

Rest registers the given handler on all methods without a handler.

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

func (r *Router) Methods(methods []string, path string, fn http.HandlerFunc) *Router

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

func (r *Router) Path() string

Path returns the routers full mount 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) Route

func (r *Router) Route(path string) *Route

Route returns a new Route for the given path.

func (*Router) SubRouter

func (r *Router) SubRouter(prefix string) *Router

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!

func (*Router) Use

func (r *Router) Use(fn http.HandlerFunc) *Router

Use registers the specified function as middleware. Middleware is always processed before any dispatching happens.

func (*Router) UseHandler

func (r *Router) UseHandler(handler http.Handler) *Router

UseHandler is an adapter for Use to register a Handler as middleware.

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.

func (*Server) Listen

func (s *Server) Listen(addr string) error

Listen is a convenience method that uses http.ListenAndServe.

func (*Server) ListenTLS

func (s *Server) ListenTLS(addr, certFile, keyFile string) error

ListenTLS is a convenience method that uses http.ListenAndServeTLS. The TLS informations used are stored in .TLS after calling this method.

func (*Server) ServeHTTP

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

ServeHTTP dispatches the request to the internal Router.

type TLS

type TLS struct {
	CertFile, KeyFile string
}

A TLS contains both the certificate and key file paths.

Jump to

Keyboard shortcuts

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