Documentation ¶
Overview ¶
Package http implement custom HTTP server with memory file system and simplified routing handler.
Problems ¶
There are two problems that this library try to handle. First, optimizing serving local file system; second, complexity of routing regarding to their method, request type, and response type.
Assuming that we want to serve file system and API using ServeMux, the simplest registered handler are,
mux.HandleFunc("/", handleFileSystem) mux.HandleFunc("/api", handleAPI)
The first problem is regarding to "http.ServeFile". Everytime the request hit "handleFileSystem" the "http.ServeFile" try to locate the file regarding to request path in system, read the content of file, parse its content type, and finally write the content-type, content-length, and body as response. This is time consuming. Of course, on modern OS, they may caching readed file descriptor in memory to minimize disk lookup, so the next call to the same file path may not touch the hard storage back again.
The second problem is regarding to handling API. We must check the request method, checking content-type, parsing query parameter or POST form in every sub-handler of API. Assume that we have an API with method POST and query parameter, the method to handle it would be like these,
handleAPILogin(res, req) { // (1) Check if method is POST // (2) Parse query parameter // (3) Process request // (4) Write response }
The step number 1, 2, 4 needs to be written for every handler of our API.
Solutions ¶
The solution to the first problem is by mapping all content of files to be served into memory. This cause more memory to be consumed on server side but we minimize path lookup, and cache-miss on OS level.
Serving file system is handled by memory file system using map of path to file node.
map[/index.html] = Node{Type: ..., Size: ..., ContentType: ...}
There is a limit on size of file to be mapped on memory. See the package "lib/memfs" for more information.
The solution to the second problem is by mapping the registered request per method and by path. User just need to focus on step 3, handling on how to process request, all of process on step 1, 2, and 4 will be handled by our library.
import ( libhttp "github.com/shuLhan/share/lib/http" ) ... epAPILogin := &libhttp.Endpoint{ Path: "/api/login", RequestType: libhttp.RequestTypeQuery, ResponseType: libhttp.ResponseTypeJSON, Call: handleLogin, } server.RegisterPost(epAPILogin) ...
Upon receiving request to "/api/login", the library will call "req.ParseForm()", read the content of body and pass them to "handleLogin",
func handleLogin(res http.ResponseWriter, req *http.Request, reqBody []byte) ( resBody []byte, err error, ) { // Process login input from req.Form, req.PostForm, and/or // reqBody. // Return response body and error. }
Routing ¶
The Endpoint allow binding the unique key into path using colon ":" as the first character.
For example, after registering the following Endpoint,
epBinding := &libhttp.Endpoint{ Path: "/category/:name", RequestType: libhttp.RequestTypeQuery, ResponseType: libhttp.ResponseTypeJSON, Call: handleCategory, } server.RegisterGet(epBinding)
when the server receiving GET request using path "/category/book?limit=10", it will put the "book" and "10" into http.Request's Form with key is "name" and "limit"
fmt.Printf("request.Form:", req.Form) // request.Form: map[name:[book] limit:[10]]
The key binding must be unique between path and query. If query has the same key then it will be overridden by value in path. For example, using the above endpoint, request with "/category/book?name=Hitchiker" will result in Request.Form:
map[name:[book]]
not
map[name:[book Hitchiker]]
Known Bugs and Limitations ¶
* The server does not handle CONNECT method
* Missing test for request with content-type multipart-form
* We can not register path with ambigous route. For example, "/:x" and "/y" are ambiguous because one is dynamic path using key binding "x" and the last one is static path to "y".
Index ¶
- Constants
- Variables
- func ParseResponseHeader(raw []byte) (resp *http.Response, rest []byte, err error)
- type Callback
- type Client
- func (client *Client) Delete(headers http.Header, path string, params url.Values) (httpRes *http.Response, resBody []byte, err error)
- func (client *Client) Get(headers http.Header, path string, params url.Values) (httpRes *http.Response, resBody []byte, err error)
- func (client *Client) PostForm(headers http.Header, path string, params url.Values) (httpRes *http.Response, resBody []byte, err error)
- func (client *Client) PostFormData(headers http.Header, path string, params map[string][]byte) (httpRes *http.Response, resBody []byte, err error)
- func (client *Client) PostJSON(headers http.Header, path string, params interface{}) (httpRes *http.Response, resBody []byte, err error)
- func (client *Client) Put(headers http.Header, path, contentType string, body []byte) (*http.Response, []byte, error)
- func (client *Client) PutJSON(headers http.Header, path string, params interface{}) (httpRes *http.Response, resBody []byte, err error)
- type Endpoint
- type Evaluator
- type RequestMethod
- type RequestType
- type ResponseType
- type Server
- func (srv *Server) RedirectTemp(res http.ResponseWriter, redirectURL string)
- func (srv *Server) RegisterEndpoint(ep *Endpoint) (err error)
- func (srv *Server) RegisterEvaluator(eval Evaluator)
- func (srv *Server) ServeHTTP(res http.ResponseWriter, req *http.Request)
- func (srv *Server) Start() (err error)
- func (srv *Server) Stop(wait time.Duration) (err error)
- type ServerOptions
Constants ¶
const ( AcceptEncoding = "Accept-Encoding" ContentEncoding = "Content-Encoding" ContentEncodingBzip2 = "bzip2" ContentEncodingCompress = "compress" // Using LZW. ContentEncodingGzip = "gzip" ContentEncodingDeflate = "deflate" // Using zlib. ContentTypeBinary = "application/octet-stream" ContentTypeForm = "application/x-www-form-urlencoded" ContentTypeHTML = "text/html; charset=utf-8" ContentTypeJSON = "application/json" ContentTypePlain = "text/plain; charset=utf-8" ContentTypeXML = "text/xml; charset=utf-8" HeaderACAllowOrigin = "Access-Control-Allow-Origin" HeaderACAllowMethod = "Access-Control-Allow-Method" HeaderACAllowCredentials = "Access-Control-Allow-Credentials" HeaderACAllowHeaders = "Access-Control-Allow-Headers" HeaderACExposeHeaders = "Access-Control-Expose-Headers" HeaderACMaxAge = "Access-Control-Max-Age" HeaderACRequestMethod = "Access-Control-Request-Method" HeaderACRequestHeaders = "Access-Control-Request-Headers" HeaderAllow = "Allow" HeaderAuthorization = "Authorization" HeaderContentLength = "Content-Length" HeaderContentType = "Content-Type" HeaderLocation = "Location" HeaderOrigin = "Origin" HeaderUserAgent = "User-Agent" HeaderAuthKeyBearer = "Bearer" )
List of known HTTP header keys and values.
Variables ¶
var ( // // ErrEndpointAmbiguous define an error when registering path that // already exist. For example, after registering "/:x", registering // "/:y" or "/z" on the same HTTP method will result in ambiguous. // ErrEndpointAmbiguous = errors.New("ambigous endpoint") // // ErrEndpointKeyDuplicate define an error when registering path with // the same keys, for example "/:x/:x". // ErrEndpointKeyDuplicate = errors.New("duplicate key in route") // // ErrEndpointKeyEmpty define an error when path contains an empty // key, for example "/:/y". // ErrEndpointKeyEmpty = errors.New("empty route's key") )
Functions ¶
Types ¶
type Callback ¶
type Callback func(res http.ResponseWriter, req *http.Request, reqBody []byte) (resBody []byte, err error)
Callback define a type of function for handling registered handler.
The function will have the query URL, request multipart form data, and request body ready to be used in req parameter.
The ResponseWriter can be used to write custom header or to write cookies but should not be used to write response body.
The error return type should be instance of StatusError. If error is not nil and not *StatusError, server will response with internal-server-error status code.
type Client ¶ added in v0.15.0
Client is a wrapper for standard http.Client with simplified usabilities, including setting default headers, uncompressing response body.
func NewClient ¶ added in v0.15.0
NewClient create and initialize new Client connection using serverURL to minimize repetition. The serverURL is any path that is static and will never changes during request to server. The headers parameter define default headers that will be set in any request to server. The insecure parameter allow to connect to remote server with unknown certificate authority.
func (*Client) Delete ¶ added in v0.17.0
func (client *Client) Delete(headers http.Header, path string, params url.Values) ( httpRes *http.Response, resBody []byte, err error, )
Delete send the DELETE request to server using path and params as query parameters. On success, it will return the uncompressed response body.
func (*Client) Get ¶ added in v0.15.0
func (client *Client) Get(headers http.Header, path string, params url.Values) ( httpRes *http.Response, resBody []byte, err error, )
Get send the GET request to server using path and params as query parameters. On success, it will return the uncompressed response body.
func (*Client) PostForm ¶ added in v0.15.0
func (client *Client) PostForm(headers http.Header, path string, params url.Values) ( httpRes *http.Response, resBody []byte, err error, )
PostForm send the POST request to path using "application/x-www-form-urlencoded".
func (*Client) PostFormData ¶ added in v0.15.0
func (client *Client) PostFormData( headers http.Header, path string, params map[string][]byte, ) ( httpRes *http.Response, resBody []byte, err error, )
PostFormData send the POST request to path with all parameters is send using "multipart/form-data".
func (*Client) PostJSON ¶ added in v0.15.0
func (client *Client) PostJSON(headers http.Header, path string, params interface{}) ( httpRes *http.Response, resBody []byte, err error, )
PostJSON send the POST request with content type set to "application/json" and params encoded automatically to JSON.
type Endpoint ¶
type Endpoint struct { // Method contains HTTP method, default to GET. Method RequestMethod // Path contains route to be served, default to "/" if its empty. Path string // RequestType contains type of request, default to RequestTypeNone. RequestType RequestType // ResponseType contains type of request, default to ResponseTypeNone. ResponseType ResponseType // Eval define evaluator for route that will be called after global // evaluators and before callback. Eval Evaluator // Call is the main process of route. Call Callback }
Endpoint represent route that will be handled by server. Each route have their own evaluator that will be evaluated after global evaluators from server.
func (*Endpoint) HTTPMethod ¶ added in v0.10.1
HTTPMethod return the string representation of HTTP method as predefined in "net/http".
type Evaluator ¶
Evaluator evaluate the request. If request is invalid, the error will tell the response code and the error message to be written back to client.
type RequestMethod ¶
type RequestMethod int
RequestMethod define type of HTTP method.
const ( RequestMethodGet RequestMethod = 0 RequestMethodConnect RequestMethod = 1 << iota RequestMethodDelete RequestMethodHead RequestMethodOptions RequestMethodPatch RequestMethodPost RequestMethodPut RequestMethodTrace )
List of known HTTP methods.
func (RequestMethod) String ¶ added in v0.18.0
func (rm RequestMethod) String() string
String return the string representation of request method.
type RequestType ¶
type RequestType int
RequestType define type of HTTP request.
const ( RequestTypeNone RequestType = 0 RequestTypeQuery RequestType = 1 << iota RequestTypeForm RequestTypeMultipartForm RequestTypeJSON )
List of valid request type.
type ResponseType ¶
type ResponseType int
ResponseType define type for HTTP response.
const ( ResponseTypeNone ResponseType = 0 ResponseTypeBinary ResponseType = 1 << iota ResponseTypeHTML ResponseTypeJSON ResponseTypePlain ResponseTypeXML )
List of valid response type.
type Server ¶
type Server struct { *http.Server // Memfs contains the content of file systems to be served in memory. // It will be initialized only if ServerOptions's Root is not empty or // if the current directory contains generated Go file from // memfs.GoGenerate. Memfs *memfs.MemFS // contains filtered or unexported fields }
Server define HTTP server.
func NewServer ¶
func NewServer(opts *ServerOptions) (srv *Server, err error)
NewServer create and initialize new HTTP server that serve root directory with custom connection.
func (*Server) RedirectTemp ¶
func (srv *Server) RedirectTemp(res http.ResponseWriter, redirectURL string)
RedirectTemp make the request to temporary redirect (307) to new URL.
func (*Server) RegisterEndpoint ¶ added in v0.14.0
RegisterEndpoint register the Endpoint based on Method. If Method field is not set, it will default to GET. The Endpoint.Call field MUST be set, or it will return an error.
Endpoint with method HEAD and OPTIONS does not have any effect because it already handled automatically by server.
Endpoint with method CONNECT and TRACE will return an error because its not supported yet.
func (*Server) RegisterEvaluator ¶
RegisterEvaluator register HTTP middleware that will be called before Endpoint evalutor and callback is called.
type ServerOptions ¶ added in v0.6.0
type ServerOptions struct { // Root contains path to file system to be served. // This field is optional, if its empty the server will not serve the // local file system, only registered handler. Root string // Address define listen address, using ip:port format. // This field is optional, default to ":80". Address string // Conn contains custom HTTP server connection. // This fields is optional. Conn *http.Server // Includes contains list of regex to include files to be served from // Root. // This field is optional. Includes []string // Excludes contains list of regex to exclude files to be served from // Root. // This field is optional. Excludes []string // CORSAllowOrigins contains global list of cross-site Origin that are // allowed during preflight requests by the OPTIONS method. // The list is case-sensitive. // To allow all Origin, one must add "*" string to the list. CORSAllowOrigins []string // CORSAllowHeaders contains global list of allowed headers during // preflight requests by the OPTIONS method. // The list is case-insensitive. // To allow all headers, one must add "*" string to the list. CORSAllowHeaders []string // CORSExposeHeaders contains list of allowed headers. // This list will be send when browser request OPTIONS without // request-method. CORSExposeHeaders []string // CORSMaxAge gives the value in seconds for how long the response to // the preflight request can be cached for without sending another // preflight request. CORSMaxAge int // CORSAllowCredentials indicates whether or not the actual request // can be made using credentials. CORSAllowCredentials bool // Development if its true, the Root file system is served by reading // the content directly instead of using memory file system. Development bool // contains filtered or unexported fields }
ServerOptions define an options to initialize HTTP server.