ucon

package module
v2.2.1+incompatible Latest Latest
Warning

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

Go to latest
Published: Dec 27, 2018 License: MIT Imports: 17 Imported by: 0

README

ucon

ucon is a web application framework, which is pluggable by Middleware and Plugin.

ucon is the name of turmeric in Japanese. ucon knocks down any alcohol. :)

Install

go get -u github.com/favclip/ucon

Get Start

Getting start using ucon, you should setup the http server. If you decided to using ucon, it is very simple. Let's take a look at the following code.

package main

import (
	"net/http"
	
	"github.com/favclip/ucon"
)

func main() {
	ucon.Orthodox()

	ucon.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello World!"))
	})

	ucon.ListenAndServe(":8080")
}

Next, execute go run to run the server.

go run main.go

Then, you can get Hello World! on localhost:8080.

You can find more examples in /sample directory if you want.

Features

  • Compatible interface with net/http
  • Flexible routing configuration
  • Middleware - Extendable request handler
    • Powerful DI (Dependency Injection) system
  • Plugin - Easy to customize the server
  • Orthodox() - Standard features provider
  • Helpful utilities for testing
  • Run on Google App Engine and more platform
  • Opt-in plugin for Swagger(Open API Initiative)
Run a server

ucon.ListenAndServe function starts new http server. This function is fully compatible with http.ListenAndServe except which doesn't have second argument.

If you want to use ucon on existing server, see "With existing server".

Routing

Routing of ucon is set by the Handle function or HandleFunc function. HandleFunc registers the function object as a request handler. If you want to make a complex request handler, you can use Handle function with a object which implements HandlerContainer interface.

The different from http package is that ucon requires HTTP request method to configure routing. (This is a necessary approach to improve friendliness for some platforms like Google Cloud Endpoints or Swagger.) Even if paths are same, different request handler is required for each request method. However, the request handler for the wildcard (*) is valid for all of the request method.

  • HandleFunc("GET", "/a/", ...) will match a GET request for /a/b or /a/b/c, but won't match a POST request for /a/b or a GET request for /a.
  • HandleFunc("*", "/", ...) can match any requests.
  • If there are two routing, (A)HandleFunc("GET", "/a", ...) and (B)HandleFunc("GET", "/a/", ...), a request for /a will match (A), but a request for /a/b will match (B).
  • HandleFunc("GET", "/users/{id}", ...) will match requests like /users/1 or /users/foo/bar but won't match /users.
Middleware

Middleware is a preprocessor which is executed in between server and request handler. Some of Middleware are provided as standard, and when you run the ucon.Orthodox(), the following Middleware will be loaded.

  • ResponseMapper - Converts the return value of the request handler to JSON.
  • HTTPRWDI - Injects dependencies of http.Request and http.ResponseWriter.
  • NetContextDI - Injects context.Context dependency.
  • RequestObjectMapper - Convert the parameters and data in the request to the argument of the request handler.

Of course, you can create your own Middleware. As an example, let's create a Middleware to write the log to stdout each time it receives a request.

Middleware is a function expressed as func(b *ucon.Bubble) error. Write Logger function into main.go.

func Logger(b *ucon.Bubble) error {
	fmt.Printf("Received: %s %s\n", b.R.Method, b.R.URL.String())
	return b.Next()
}

Next, Register Logger as Middleware. Call ucon.Middleware() to register a Middleware.

package main

import (
	"fmt"
	"net/http"
	
	"github.com/favclip/ucon"
)

func main() {
	ucon.Orthodox()

	ucon.Middleware(Logger)

	ucon.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello World!"))
	})

	ucon.ListenAndServe(":8080")
}

func Logger(b *ucon.Bubble) error {
	fmt.Printf("Received: %s %s\n", b.R.Method, b.R.URL.String())
	return b.Next()
}

OK! The server will output a log each time it receives the request.

Bubble given to Middleware will carry data until the request reaches the appropriate request handler. Bubble.Next() passes the processing to next Middleware. When All of Middleware has been called, the request handler will be executed.

DI (Dependency Injection)

DI system of ucon is solved by that the Middleware provides the data to Bubble.Arguments. The types of request handler's arguments are contained in Bubble.ArgumentTypes and so Middleware can give a value to each type.

For example, when you add the argument of time.Time type in the request handler, you can add the DI in the following Middleware.

package main

import (
	"fmt"
	"net/http"
	"reflect"
	"time"

	"github.com/favclip/ucon"
)

func main() {
	ucon.Orthodox()

	ucon.Middleware(NowInJST)

	ucon.HandleFunc("GET", "/", func(w http.ResponseWriter, r *http.Request, now time.Time) {
		w.Write([]byte(
		    fmt.Sprintf("Hello World! : %s", now.Format("2006/01/02 15:04:05")))
		)
	})

	ucon.ListenAndServe(":8080")
}

func NowInJST(b *ucon.Bubble) error {
	for idx, argT := range b.ArgumentTypes {
		if argT == reflect.TypeOf(time.Time{}) {
			b.Arguments[idx] = reflect.ValueOf(time.Now())
			break
		}
	}
	return b.Next()
}
Plugin

Plugin is a preprocessor to customize the server. Plugin is not executed each time that comes request like Middleware. It will be executed only once when the server prepares.

sample of swagger will be a help to know how to use a Plugin.

To create a Plugin, you can register the object implements the interface of the Plugin in ucon.Plugin function. Currently, the following Plugin interfaces are provided.

Because the Plugin is given *ServeMux, it is also possible to add the request handler and Middleware by Plugin.

Testing Helper

ucon provides a useful utility to make the unit tests.

MakeMiddlewareTestBed

MakeMiddlewareTestBed provides a test bed for testing the Middleware. For example, test of NetContextDI Middleware is described as follows.

func TestNetContextDI(t *testing.T) {
	b, _ := MakeMiddlewareTestBed(t, NetContextDI, func(c context.Context) {
		if c == nil {
			t.Errorf("unexpected: %v", c)
		}
	}, nil)
	err := b.Next()
	if err != nil {
		t.Fatal(err)
	}
}
MakeHandlerTestBed

MakeHandlerTestBed provides a test bed for testing the request handler. Request handler must have been registered before calling this function.

In routing_test.go this function is used to describe a test such as the following.

func TestRouterServeHTTP1(t *testing.T) {
	DefaultMux = NewServeMux()
	Orthodox()

	HandleFunc("PUT", "/api/test/{id}", func(req *RequestOfRoutingInfoAddHandlers) (*ResponseOfRoutingInfoAddHandlers, error) {
		if v := req.ID; v != 1 {
			t.Errorf("unexpected: %v", v)
		}
		if v := req.Offset; v != 100 {
			t.Errorf("unexpected: %v", v)
		}
		if v := req.Text; v != "Hi!" {
			t.Errorf("unexpected: %v", v)
		}
		return &ResponseOfRoutingInfoAddHandlers{Text: req.Text + "!"}, nil
	})

	DefaultMux.Prepare()

	resp := MakeHandlerTestBed(t, "PUT", "/api/test/1?offset=100", strings.NewReader("{\"text\":\"Hi!\"}"))

	if v := resp.StatusCode; v != 200 {
		t.Errorf("unexpected: %v", v)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}
	if v := string(body); v != "{\"text\":\"Hi!!\"}" {
		t.Errorf("unexpected: %v", v)
	}
}
With existing server

Because ucon's ServeMux implements http.Handler interface, it can be easily integrated into existing Golang server by passing the http.Handle function. However before passing it to Handle function, you need to explicitly call ServeMux#Prepare function instead of ucon.ListenAndServe.

You can get default reference of ServeMux by ucon.DefaultMux.

func init() {
	ucon.Orthodox()

    ...
    
	ucon.DefaultMux.Prepare()
	http.Handle("/", ucon.DefaultMux)
}

LICENSE

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultMux = NewServeMux()

DefaultMux is the default ServeMux in ucon.

View Source
var ErrCSRFBadToken = newBadRequestf("invalid CSRF token")

ErrCSRFBadToken is the error returns CSRF token verify failure.

View Source
var ErrInvalidArgumentLength = errors.New("invalid arguments")

ErrInvalidArgumentLength is the error that length of Bubble.Arguments does not match to RequestHandler arguments.

View Source
var ErrInvalidArgumentValue = errors.New("invalid argument value")

ErrInvalidArgumentValue is the error that value in Bubble.Arguments is invalid.

View Source
var ErrInvalidPathParameterType = errors.New("path parameter type should be map[string]string")

ErrInvalidPathParameterType is the error that context with PathParameterKey key returns not map[string]string type.

View Source
var ErrInvalidRequestHandler = errors.New("invalid request handler. not function")

ErrInvalidRequestHandler is the error that Bubble.RequestHandler is not a function.

View Source
var ErrPathParameterFieldMissing = errors.New("can't find path parameter in struct")

ErrPathParameterFieldMissing is the path parameter mapping error.

View Source
var PathParameterKey = &struct{ temp string }{}

PathParameterKey is context key of path parameter. context returns map[string]string.

Functions

func CheckFunction

func CheckFunction(target interface{})

CheckFunction checks whether the target is a function.

func Handle

func Handle(method string, path string, hc HandlerContainer)

Handle register the HandlerContainer for the given method & path to the ServeMux.

func HandleFunc

func HandleFunc(method string, path string, h interface{})

HandleFunc register the handler function for the given method & path to the ServeMux.

func IsEmpty

func IsEmpty(fV reflect.Value) bool

IsEmpty returns whether the value is empty.

func ListenAndServe

func ListenAndServe(addr string)

ListenAndServe start accepts the client request.

func MakeHandlerTestBed

func MakeHandlerTestBed(t *testing.T, method string, path string, body io.Reader) *http.Response

MakeHandlerTestBed returns a response by the request made from arguments. To test some handlers, those must be registered by Handle or HandleFunc before calling this.

func MakeMiddlewareTestBed

func MakeMiddlewareTestBed(t *testing.T, middleware MiddlewareFunc, handler interface{}, opts *BubbleTestOption) (*Bubble, *ServeMux)

MakeMiddlewareTestBed returns a Bubble and ServeMux for handling the request made from the option.

func Middleware

func Middleware(f MiddlewareFunc)

Middleware can append Middleware to ServeMux.

func Orthodox

func Orthodox()

Orthodox middlewares enable to DefaultServeMux.

func Plugin

func Plugin(plugin interface{})

Plugin can append Plugin to ServeMux.

func SetValueFromString

func SetValueFromString(f reflect.Value, value string) error

SetValueFromString parses string and sets value.

func SetValueFromStrings

func SetValueFromStrings(f reflect.Value, values []string) error

SetValueFromStrings parses strings and sets value.

Types

type Bubble

type Bubble struct {
	R              *http.Request
	W              http.ResponseWriter
	Context        context.Context
	RequestHandler HandlerContainer

	Debug bool

	Handled       bool
	ArgumentTypes []reflect.Type
	Arguments     []reflect.Value
	Returns       []reflect.Value
	// contains filtered or unexported fields
}

Bubble is a context of data processing that will be passed to a request handler at last. The name `Bubble` means that the processing flow is a event-bubbling. Processors, called `middleware`, are executed in order with same context, and at last the RequestHandler will be called.

func (*Bubble) Next

func (b *Bubble) Next() error

Next passes the bubble to next middleware. If the bubble reaches at last, RequestHandler will be called.

type BubbleTestOption

type BubbleTestOption struct {
	Method            string
	URL               string
	Body              io.Reader
	MiddlewareContext Context
}

BubbleTestOption is an option for setting a mock request.

type CSRFOption

type CSRFOption struct {
	Salt              []byte
	SafeMethods       []string
	CookieName        string
	RequestHeaderName string
	GenerateCookie    func(r *http.Request) (*http.Cookie, error)
}

CSRFOption is options for CSRFProtect.

type Context

type Context interface {
	Value(key interface{}) interface{}
}

Context is a key-value store.

func WithValue

func WithValue(parent Context, key interface{}, val interface{}) Context

WithValue returns a new context containing the value. Values contained by parent context are inherited.

type HTTPErrorResponse

type HTTPErrorResponse interface {
	// StatusCode returns http response status code.
	StatusCode() int
	// ErrorMessage returns an error object.
	// Returned object will be converted by json.Marshal and written as http response body.
	ErrorMessage() interface{}
}

HTTPErrorResponse is a response to represent http errors.

type HTTPResponseModifier

type HTTPResponseModifier interface {
	Handle(b *Bubble) error
}

HTTPResponseModifier is an interface to hook on each responses and modify those. The hook will hijack ResponseMapper, so it makes possible to do something in place of ResponseMapper. e.g. You can convert a response object to xml and write it as response body.

type HandlerContainer

type HandlerContainer interface {
	Handler() interface{}
	Context
}

HandlerContainer is handler function container. and It has a ucon Context that make it possible communicate to Plugins.

type HandlersScannerPlugin

type HandlersScannerPlugin interface {
	HandlersScannerProcess(m *ServeMux, rds []*RouteDefinition) error
}

HandlersScannerPlugin is an interface to make a plugin for scanning request handlers.

type MiddlewareFunc

type MiddlewareFunc func(b *Bubble) error

MiddlewareFunc is an adapter to hook middleware processing. Middleware works with 1 request.

func CSRFProtect

func CSRFProtect(opts *CSRFOption) (MiddlewareFunc, error)

CSRFProtect is a CSRF (Cross Site Request Forgery) prevention middleware.

func ContextDI

func ContextDI() MiddlewareFunc

ContextDI injects Bubble.Context into the bubble.Arguments.

func HTTPRWDI

func HTTPRWDI() MiddlewareFunc

HTTPRWDI injects Bubble.R and Bubble.W into the bubble.Arguments.

func NetContextDI

func NetContextDI() MiddlewareFunc

NetContextDI injects Bubble.Context into the bubble.Arguments. deprecated. use ContextDI instead of NetContextDI.

func RequestObjectMapper

func RequestObjectMapper() MiddlewareFunc

RequestObjectMapper converts a request to object and injects it into the bubble.Arguments.

func RequestValidator

func RequestValidator(validator Validator) MiddlewareFunc

RequestValidator checks request object validity.

func ResponseMapper

func ResponseMapper() MiddlewareFunc

ResponseMapper converts a response object to JSON and writes it as response body.

type PathTemplate

type PathTemplate struct {
	PathTemplate string

	PathParameters []string
	// contains filtered or unexported fields
}

PathTemplate is a path with parameters template.

func ParsePathTemplate

func ParsePathTemplate(pathTmpl string) *PathTemplate

ParsePathTemplate parses path string to PathTemplate.

func (*PathTemplate) Match

func (pt *PathTemplate) Match(requestPath string) (bool, map[string]string)

Match checks whether PathTemplate matches the request path. If the path contains parameter templates, those key-value map is also returned.

type RouteDefinition

type RouteDefinition struct {
	Method           string
	PathTemplate     *PathTemplate
	HandlerContainer HandlerContainer
}

RouteDefinition is a definition of route handling. If a request matches on both the method and the path, the handler runs.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router is a handler to pass requests to the best-matched route definition. For the router decides the best route definition, there are 3 rules.

  1. Methods must match. a. If the method of request is `HEAD`, exceptionally `GET` definition is also allowed. b. If the method of definition is `*`, the definition matches on all method.
  2. Paths must match as longer as possible. a. The path of definition must match to the request path completely. b. Select the longest match. * Against Request[/api/foo/hi/comments/1], Definition[/api/foo/{bar}/] is stronger than Definition[/api/foo/].
  3. If there are multiple options after 1 and 2 rules, select the earliest one which have been added to router.

func (*Router) ServeHTTP

func (ro *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP routes a request to the handler and creates new bubble.

type ServeMux

type ServeMux struct {
	Debug bool
	// contains filtered or unexported fields
}

ServeMux is an HTTP request multiplexer.

func NewServeMux

func NewServeMux() *ServeMux

NewServeMux allocates and returns a new ServeMux.

func (*ServeMux) Handle

func (m *ServeMux) Handle(method string, path string, hc HandlerContainer)

Handle register the HandlerContainer for the given method & path to the ServeMux.

func (*ServeMux) HandleFunc

func (m *ServeMux) HandleFunc(method string, path string, h interface{})

HandleFunc register the handler function for the given method & path to the ServeMux.

func (*ServeMux) ListenAndServe

func (m *ServeMux) ListenAndServe(addr string) error

ListenAndServe start accepts the client request.

func (*ServeMux) Middleware

func (m *ServeMux) Middleware(f MiddlewareFunc)

Middleware can append Middleware to ServeMux.

func (*ServeMux) Plugin

func (m *ServeMux) Plugin(plugin interface{})

Plugin can append Plugin to ServeMux.

func (*ServeMux) Prepare

func (m *ServeMux) Prepare()

Prepare the ServeMux. Plugin is not show affect to anything. This method is enabled plugins.

func (*ServeMux) ServeHTTP

func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP dispatches request to the handler.

type StringParser

type StringParser interface {
	ParseString(value string) (interface{}, error)
}

StringParser is a parser for string-to-object custom conversion.

type TagJSON

type TagJSON string

TagJSON provides methods to operate "json" tag.

func NewTagJSON

func NewTagJSON(tag reflect.StructTag) TagJSON

NewTagJSON returns new TagJSON.

func (TagJSON) HasString

func (jsonTag TagJSON) HasString() bool

HasString returns whether a field is emitted as string.

func (TagJSON) Ignored

func (jsonTag TagJSON) Ignored() bool

Ignored returns whether json tag is ignored.

func (TagJSON) Name

func (jsonTag TagJSON) Name() string

Name returns json tag name.

func (TagJSON) OmitEmpty

func (jsonTag TagJSON) OmitEmpty() bool

OmitEmpty returns whether json tag is set as omitempty.

type Validator

type Validator interface {
	Validate(v interface{}) error
}

Validator is an interface of request object validation.

Directories

Path Synopsis
sample

Jump to

Keyboard shortcuts

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