router

package
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2026 License: MIT Imports: 23 Imported by: 0

README

router

Package router provides a simple & efficient parameterized HTTP router.

An HTTP router allows you to map an HTTP request method and path to a specific function. A parameterized HTTP router allows you to designate specific portions of the request path as a parameter, which can later be fetched during the request itself.

This package allows you modify the routing table ad-hoc, even while the server is running.

Install

This package is provided by 'git.ecn.io/ian/w3', so add that to your go.mod file:

go get git.ecn.io/ian/w3

Documentation

Overview

Package router provides a simple & efficient parameterized HTTP router.

An HTTP router allows you to map an HTTP request method and path to a specific function. A parameterized HTTP router allows you to designate specific portions of the request path as a parameter, which can later be fetched during the request itself.

This package allows you modify the routing table ad-hoc, even while the server is running.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ReadTimeout       time.Duration
	ReadHeaderTimeout time.Duration
	WriteTimeout      time.Duration
	IdleTimeout       time.Duration
)
View Source
var DefaultStaticOptions = StaticOptions{
	GenerateDirectoryListing: true,
	IndexFileNames:           []string{"index.htm", "index.html"},
	CacheMaxAge:              24 * time.Hour,
	GenerateETag:             true,
	IncludeLastModifiedDate:  true,
}

Default options for static requests

Functions

func ServeHTTPRange

func ServeHTTPRange(options ServeHTTPRangeOptions) error

ServeHTTPRange serve an HTTP range

Types

type ByteRange

type ByteRange struct {
	Start int64
	End   int64
}

ByteRange describes a range of offsets for reading from a byte slice.

There are thee possibilities for byte ranges: (Start=100, End=200) Read data from offset 100 until offset 200. (Start=100, End=-1) Read all remaining data from offset 100 until the end of the reader. (Start=-1, End=100) Read the last 100 bytes of the reader.

func ParseRangeHeader

func ParseRangeHeader(value string) []ByteRange

ParseRangeHeader will parse the value from the HTTP ranges header and return a slice of byte ranges, or nil if the headers value is malformed.

func (ByteRange) ContentRangeValue

func (br ByteRange) ContentRangeValue(total uint64) string

ContentRangeValue will return a value sutible for the content-range header

func (ByteRange) Length

func (br ByteRange) Length(total uint64) uint64

Length return the length of data represented by this byte range

type Handle

type Handle func(http.ResponseWriter, *Request)

Handle describes the signature for a handle of a path

type IMime

type IMime interface {
	// GetMime is called with the path to a file when the router needs a value for the "Content-Type" header.
	// This method must be thread-safe and should not panic. If an error occurs you must return
	// "application/octet-stream".
	//
	// filePath is guaranteed to exist and be readable by the running process.
	GetMime(filePath string) string
}

IMime describes an interface for an MIME detection system

var MimeGetter IMime = &extensionMimeGetterType{}

MimeGetter the implementation used to determine the value of the "Content-Type" header for static files. By default this uses a simple implantation that guesses the MIME type based on the extension in the files name.

type Request

type Request struct {
	*http.Request

	// A map of any parameters from the router path mapped to their values from the request path
	Parameters map[string]string
}

Request describes an HTTP request

type RequestResult added in v1.1.1

type RequestResult struct {
	// The HTTP method from the request
	Method string
	// The URL from the request
	URL *url.URL
	// The host from the request
	Host string
	// The remote address from the request
	RemoteAddr string
	// The headers written to the response
	Header http.Header
	// The status code of the response
	StatusCode int
	// The elapsed time that this request took
	Elapsed time.Duration
	// The panic reason if a panic occured during the handle of the request
	Panic any
}

RequestResult describes the result of a HTTP request

type ServeHTTPRangeOptions

type ServeHTTPRangeOptions struct {
	// Any additional headers to append to the request.
	// Do not specify a content-type here, instead use the MIMEType property.
	Headers map[string]string
	// Cookies to set on the response
	Cookies []http.Cookie
	// Byte ranges from the HTTP request
	Ranges []ByteRange
	// The incoming reader, must support seeking
	Reader io.ReadSeeker
	// The total length of the data
	TotalLength uint64
	// The content type of the data
	MIMEType string
	// The outgoing HTTP response writer
	Writer http.ResponseWriter
}

ServeHTTPRangeOptions options for serving an HTTP range request

type Server

type Server struct {
	// Optional channel to recieve a signal when this server is ready, meaning that HTTP requests sent to it will be
	// processed.
	Ready chan bool
	// contains filtered or unexported fields
}

Server describes a server. Do not initialize a new copy of a Server{}, but instead use router.New(logtic.Log.Connect("HTTP"))

func New

func New(log *logtic.Source) *Server

New will initialize a new Server instance and return it. This does not start the server.

func (*Server) Handle

func (s *Server) Handle(method, path string, handler Handle)

Handle registers a handler for an HTTP request of method to path.

Method must be a valid HTTP method, in all caps. Path must always begin with a forward slash /. Will panic on invalid vales. Will panic if registering a duplicate method & path.

Handle may be called even while the server is listening and is thread-safe.

Any segment that begins with a colon (:) will be parameterized. The value of all parameters for the path will be populated into the Parameters map included in the Request object in the handler. For example:

handle path  = "/widgets/:widget_id/cost/:currency"
request path = "/widgets/1234/cost/cad"
parameters   = { "widget_id": "1234", "currency": "cad" }

Any segment that begins with an astreisk (*) will be parameterized as well, however, unlike colon parameters, these will include the entire remaining path as the value of the parameter. Multiple methods can be registered for the same wildcard path, provided they use the same parameter name. Any segments included after the parameter name are ignored. For example

handle path  = "/proxy/*url"
request path = "/proxy/some/multi/segmented/value"
parameters   = { "url": "some/multi/segmented/value" }

Parameter segments are exclusive, meaning you can not have a static segment at the same position as a parameterized element. For example, these both will panic:

// This panics because /all occupies the same segment as the parameter :username
server.Handle("GET", "/users/:username", ...)
server.Handle("GET", "/users/all", ...)

// This panics because /user/id occupied the same segment as the wildcard parameter *param
server.Handle("GET", "/users/*param", ...)
server.Handle("GET", "/users/user/id", ...)

Paths that end with a slash are unique to those that don't. For example, these would be considered unique by the router:

server.Handle("GET", "/users/all/", ...)
server.Handle("GET", "/users/all", ...)
Example
package main

import (
	"net/http"

	"git.ecn.io/ian/w3/router"
	"github.com/ecnepsnai/logtic"
)

func main() {
	server := router.New(logtic.Log.Connect("HTTP"))
	server.Handle("GET", "/hello/:greeting", func(rw http.ResponseWriter, r *router.Request) {
		rw.Write([]byte("Hello, " + r.Parameters["greeting"]))
	})
}

func (*Server) ListenAndServe

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

ListenAndServe will listen for HTTP requests on the specified socket address. Valid addresses are typically in the form of: <IP Address>:<Port Number>. For IPv6 addresses, wrap the address in brackets.

An error will only be returned if there was an error listening or the listener was closed.

Example
package main

import (
	"git.ecn.io/ian/w3/router"
	"github.com/ecnepsnai/logtic"
)

func main() {
	server := router.New(logtic.Log.Connect("HTTP"))
	server.ListenAndServe("127.0.0.1:8080")
}

func (*Server) RemoveHandle

func (s *Server) RemoveHandle(method, path string)

RemoveHandle will remove any handler for the given method and path. If no handle exists, it does nothing. If both method and path are * it removes everything from the routing table.

Note that parameter names are not considered when removing a path. For example, you may register a path with `/:username` and remove it with `/:something_else`.

This can be called even while the server is listening and is thread-safe.

Example
package main

import (
	"net/http"

	"git.ecn.io/ian/w3/router"
	"github.com/ecnepsnai/logtic"
)

func main() {
	server := router.New(logtic.Log.Connect("HTTP"))
	server.Handle("GET", "/hello/:greeting", func(rw http.ResponseWriter, r *router.Request) {
		rw.Write([]byte("Hello, " + r.Parameters["greeting"]))
	})
	server.RemoveHandle("GET", "/hello/:greeting")
	server.RemoveHandle("*", "*") // Will remove everything from the routing table!
}

func (*Server) Serve

func (s *Server) Serve(listener net.Listener) error

Serve will listen for HTTP requests on the given listener.

An error will only be returned if there was an error listening or the listener was abruptly closed.

Example
package main

import (
	"net"

	"git.ecn.io/ian/w3/router"
	"github.com/ecnepsnai/logtic"
)

func main() {
	server := router.New(logtic.Log.Connect("HTTP"))
	l, _ := net.Listen("tcp", "[::1]:8080")
	server.Serve(l)
}

func (*Server) ServeFS

func (s *Server) ServeFS(filesystem fs.FS, fsRoot, urlRoot string, options *StaticOptions)

ServeFS registers a handler for all requests under urlRoot to serve any files matching the same path the provided filesystem. If you're trying to serve files found on-disk, it's recommended that you use ServeFiles instead.

Files will be served from the root of the filesystem. Use fsRoot to define a transparent root for all requests to be prepended with.

Follows all the sames rules and notes about ServeFiles, however one limitation of ServeFS is HTTP Range requests are not supported.

func (*Server) ServeFiles

func (s *Server) ServeFiles(localRoot string, urlRoot string, options *StaticOptions)

ServeFiles registers a GET and HEAD handler for all requests under urlRoot to serve any files matching the same path in a local filesystem directory localRoot.

For example:

localRoot = /usr/share/www/
urlRoot   = /static/

Request for '/static/image.jpg' would read file '/usr/share/www/image.jpg'

Will panic if any handle is registered under urlRoot. Attempting to register a new handle under urlRoot after calling ServeFiles will panic.

Caching will be enabled by default for all files served by this router. The mtime of the file will be used for the Last-Modified date.

By default, the server will use the file extension (if any) to determine the MIME type for the response. You may use your own MIME detection by implementing the IMime interface and setting MimeGetter.

The server will also instruct clients to cache files served for up-to 1 day. You can control this with the CacheMaxAge variable.

When a directory is requested, the router will look for an index file with the name from the IndexFileName variable. If no file is found, a directory listing will automatically be generated. You can control this with the GenerateDirectoryListing variable.

Example
package main

import (
	"git.ecn.io/ian/w3/router"
	"github.com/ecnepsnai/logtic"
)

func main() {
	server := router.New(logtic.Log.Connect("HTTP"))

	localDirectory := "/usr/share/http" // Directory to serve files from
	urlRoot := "/assets/"               // Top-level path for all requests to be directed to the local directory

	server.ServeFiles(localDirectory, urlRoot, nil)
	// Now any HTTP GET or HEAD requests to /assets/ will read files from /usr/share/http.
	// For example:
	// HTTP GET "/assets/index.html" will read file "/usr/share/http/index.html"
}

func (*Server) SetMethodNotAllowedHandle

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

SetMethodNotAllowedHandle will set the handle called when a request comes in for a known path but not the correct method.

A default handle is set when the server is created.

func (*Server) SetNotFoundHandle

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

SetNotFoundHandle will set the handle called when a request that did not match any registered path comes in.

A default handle is set when the server is created.

func (*Server) SetPostRequestHandle added in v1.1.1

func (s *Server) SetPostRequestHandle(handle func(r *RequestResult))

SetPostRequestHandle will set the handle called after every request has finished. The parameter to the handle function includes information about the request and its result.

func (*Server) Stop

func (s *Server) Stop()

Stop will stop the server. Server.ListenAndServe or Server.Serve will return net.ErrClosed. Does nothing if the was not listening or was already stopped.

type StaticOptions added in v1.0.1

type StaticOptions struct {
	// If the router should generate a directory listing for static directories that do not have
	// an index file (see also IndexFileName)
	GenerateDirectoryListing bool
	// The names used when searching a directory for an index file
	IndexFileNames []string
	// The amount of time browsers may consider static content to be fresh.
	// Set this to 0 to not include a "Cache-Control" header for static requests.
	CacheMaxAge time.Duration
	// If etags should be generated automatically for the files. etags are generated whenever a file
	// is requested and cached. If the files modified date changes, the cache is refreshed.
	GenerateETag bool
	// If the last modified date header should be included in the response.
	IncludeLastModifiedDate bool
	// Prefer to use this date as the value of the Last Modified header rather than refer to the
	// file's metadata. When using an embedded FS this is required as otherwise the date will be
	// go's zero date.
	OverrideLastModifiedDate *time.Time
}

Additional options for static handles. Recommended to take a copy of DefaultStaticOptions.

Jump to

Keyboard shortcuts

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