Documentation
¶
Overview ¶
Package w3 is a full-featured HTTP router and server for Go applications, suitable for serving static files, REST APIs, and more.
Web includes these features:
- HTTP range support
- Static file serving
- Directory listings
- Per-IP rate limiting
- Per-request contextual data
Web offers four APIs for developers to choose from:
API ¶
API provides everything you need to build poweful REST APIs using JSON. Define your routes and easily accept and return data as JSON.
Example:
router := w3.New("[::]:8080")
router.API.Get("/users", getUsers, options)
router.API.Get("/users/:username", getUsers, options)
For more information, see the documentation of w3.API.
HTTPEasy ¶
HTTPEasy provides a straightforward interface to accept HTTP requets and return data.
Example:
router := w3.New("[::]:8080")
router.HTTPEasy.Get("/index.html", getIndex, options)
router.HTTPEasy.Get("/cat.jpg", getKitty, options)
For more information, see the documentation of w3.HTTPEasy.
HTTP ¶
HTTP provides full access to the original HTTP request, allowing you total control over the response, whatever that may be.
Example:
router := w3.New("[::]:8080")
router.HTTP.Get("/index.html", getIndex, options)
router.HTTP.Get("/cat.jpg", getKitty, options)
For more information, see the documentation of w3.HTTP.
Example (Authentication) ¶
package main
import (
"net/http"
"time"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
type User struct {
Username string `json:"username"`
}
// Login
loginHandle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
// Do any authentication logic here
// Assuming the user authenticated successfully...
response.Cookies = []http.Cookie{
{
Name: "session",
Value: "1",
Path: "/",
Expires: time.Now().AddDate(0, 0, 1),
},
}
return true, nil
}
unauthenticatedOptions := &w3.HandleOptions{}
server.API.GET("/login", loginHandle, unauthenticatedOptions)
// Get User Info
getUserHandle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
user := request.UserData.(User)
return user, nil
}
authenticatedOptions := &w3.HandleOptions{
// The authenticate method is where you validate that a request if from an authenticated, or simple "logged in"
// user. In this example, we validate that a cookie is present.
// Any data returned by this method is provided into the request handler as Request.UserData
// Returning nil results in an HTTP 403 response
AuthenticateMethod: func(request *http.Request) any {
cookie, err := request.Cookie("session")
if err != nil || cookie == nil {
return nil
}
if cookie.Value != "1" {
return nil
}
return map[string]string{
"foo": "bar",
}
},
}
// Notice that we used a different HandleOptions instance with our AuthenticateMethod
// an options without any AuthenticateMethod is considered unauthenticated
server.API.GET("/user", getUserHandle, authenticatedOptions)
if err := server.Start(); err != nil {
panic(err)
}
}
Example (File) ¶
package main
import (
"os"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
f, err := os.Open("/foo/bar")
if err != nil {
return &w3.HTTPResponse{
Status: 500,
}
}
return &w3.HTTPResponse{
Reader: f,
}
}
options := &w3.HandleOptions{}
server.HTTPEasy.GET("/file", handle, options)
if err := server.Start(); err != nil {
panic(err)
}
}
Example (Json) ¶
package main
import (
"time"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
return time.Now().Unix(), nil
}
options := &w3.HandleOptions{}
server.API.GET("/time", handle, options)
if err := server.Start(); err != nil {
panic(err)
}
}
Example (Ratelimit) ¶
package main
import (
"net/http"
"time"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
// Restrict each connecting IP address to a maximum of 5 requests per second
server.Options.MaxRequestsPerSecond = 5
// Handle called when a request is rejected due to rate limiting
server.SetRateLimitedHandle(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(429)
w.Write([]byte("Too many requests"))
})
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
return time.Now().Unix(), nil
}
options := &w3.HandleOptions{}
server.API.GET("/time", handle, options)
if err := server.Start(); err != nil {
panic(err)
}
}
Example (Telemetry) ¶
package main
import (
"time"
"git.ecn.io/ian/w3"
"git.ecn.io/ian/w3/router"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
server.SetPostRequestHandle(func(r *router.RequestResult) {
_ = r.Method // The HTTP method from the request
_ = r.URL // The URL from the request
_ = r.Host // The host from the request
_ = r.RemoteAddr // The remote address from the request
_ = r.Header // The headers written to the response
_ = r.StatusCode // The status code of the response
_ = r.Elapsed // The elapsed time that this request took
_ = r.Panic // The panic reason if a panic occured during the handle of the request
})
loginHandle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
// Assume this is a long-running task
time.Sleep(5 * time.Second)
return true, nil
}
server.API.GET("/example", loginHandle, &w3.HandleOptions{})
if err := server.Start(); err != nil {
panic(err)
}
}
Example (Unixsocket) ¶
package main
import (
"net"
"time"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
l, err := net.Listen("unix", "/example.socket")
if err != nil {
panic(err)
}
server := w3.NewListener(l, logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
return time.Now().Unix(), nil
}
options := &w3.HandleOptions{}
server.API.GET("/time", handle, options)
if err := server.Start(); err != nil {
panic(err)
}
}
Index ¶
- Variables
- func MustURL(s string) *url.URL
- func RealRemoteAddr(r *http.Request) net.IP
- type API
- func (a API) DELETE(path string, handle APIHandle, options *HandleOptions)
- func (a API) GET(path string, handle APIHandle, options *HandleOptions)
- func (a API) HEAD(path string, handle APIHandle, options *HandleOptions)
- func (a API) OPTIONS(path string, handle APIHandle, options *HandleOptions)
- func (a API) PATCH(path string, handle APIHandle, options *HandleOptions)
- func (a API) POST(path string, handle APIHandle, options *HandleOptions)
- func (a API) PUT(path string, handle APIHandle, options *HandleOptions)
- type APIHandle
- type APIResponse
- type Decoder
- type Error
- type HTTP
- func (h HTTP) DELETE(path string, handle HTTPHandle, options *HandleOptions)
- func (h HTTP) GET(path string, handle HTTPHandle, options *HandleOptions)
- func (h HTTP) HEAD(path string, handle HTTPHandle, options *HandleOptions)
- func (h HTTP) OPTIONS(path string, handle HTTPHandle, options *HandleOptions)
- func (h HTTP) PATCH(path string, handle HTTPHandle, options *HandleOptions)
- func (h HTTP) POST(path string, handle HTTPHandle, options *HandleOptions)
- func (h HTTP) PUT(path string, handle HTTPHandle, options *HandleOptions)
- type HTTPEasy
- func (h HTTPEasy) DELETE(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) GET(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) GETHEAD(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) HEAD(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) OPTIONS(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) PATCH(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) POST(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) PUT(path string, handle HTTPEasyHandle, options *HandleOptions)
- func (h HTTPEasy) Static(path string, directory string, options *StaticOptions)
- func (h HTTPEasy) StaticFS(path string, filesystem fs.FS, fsRoot string, options *StaticOptions)
- type HTTPEasyHandle
- type HTTPHandle
- type HTTPResponse
- type HandleOptions
- type JSONResponse
- type MockRequestParameters
- type MockResponseWriter
- type Request
- type Server
- func (s *Server) SetMethodNotAllowedHandle(h func(w http.ResponseWriter, r *http.Request))
- func (s *Server) SetNotFoundHandle(h func(w http.ResponseWriter, r *http.Request))
- func (s *Server) SetPostRequestHandle(h func(r *router.RequestResult))
- func (s *Server) SetRateLimitedHandle(h func(w http.ResponseWriter, r *http.Request))
- func (s *Server) Start() error
- func (s *Server) Stop()
- type ServerOptions
- type StaticOptions
Examples ¶
- Package (Authentication)
- Package (File)
- Package (Json)
- Package (Ratelimit)
- Package (Telemetry)
- Package (Unixsocket)
- API.DELETE
- API.GET
- API.HEAD
- API.OPTIONS
- API.PATCH
- API.POST
- API.PUT
- HTTP.DELETE
- HTTP.GET
- HTTP.HEAD
- HTTP.OPTIONS
- HTTP.PATCH
- HTTP.POST
- HTTP.PUT
- HTTPEasy.DELETE
- HTTPEasy.GET
- HTTPEasy.GETHEAD
- HTTPEasy.HEAD
- HTTPEasy.OPTIONS
- HTTPEasy.PATCH
- HTTPEasy.POST
- HTTPEasy.PUT
- HTTPEasy.Static
- Request.DecodeJSON
- Request.RealRemoteAddr
- ValidationError
Constants ¶
This section is empty.
Variables ¶
var CommonErrors = struct { NotFound *Error BadRequest *Error Unauthorized *Error Forbidden *Error ServerError *Error TooManyRequests *Error }{ NotFound: &Error{ Code: 404, Message: "Not Found", }, BadRequest: &Error{ Code: 400, Message: "Bad Request", }, Unauthorized: &Error{ Code: 403, Message: "Unauthorized", }, Forbidden: &Error{ Code: 403, Message: "Forbidden", }, ServerError: &Error{ Code: 500, Message: "Server Error", }, TooManyRequests: &Error{ Code: 429, Message: "Too Many Requests", }, }
CommonErrors are common errors types suitable for API endpoints
var DefaultStaticOptions = StaticOptions{ StaticOptions: router.DefaultStaticOptions, }
Default values for StaticOptions when nil is passed.
Functions ¶
func MustURL ¶ added in v1.1.0
MustURL is a convenience call to URL.Parse that panics if the URL is invalid. This should only be used in places where a hard-coded URL is being used.
func RealRemoteAddr ¶
RealRemoteAddr will try to get the real IP address of the incoming connection taking proxies into consideration. This function looks for the `X-Real-IP`, `X-Forwarded-For`, and `CF-Connecting-IP` headers, and if those don't exist will return the remote address of the connection.
Will never return nil, if it is unable to get a valid address it will return 0.0.0.0
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API describes a JSON API server. API handles return data or an error, and all responses are wrapped in a common response object; w3.JSONResponse.
func (API) DELETE ¶
func (a API) DELETE(path string, handle APIHandle, options *HandleOptions)
DELETE register a new HTTP DELETE request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
username := request.Parameters["username"]
return map[string]string{
"username": username,
}, nil
}
server.API.DELETE("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (API) GET ¶
func (a API) GET(path string, handle APIHandle, options *HandleOptions)
GET register a new HTTP GET request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
username := request.Parameters["username"]
return map[string]string{
"username": username,
}, nil
}
server.API.GET("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (API) HEAD ¶
func (a API) HEAD(path string, handle APIHandle, options *HandleOptions)
HEAD register a new HTTP HEAD request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
return nil, nil
}
server.API.HEAD("/users/user/", handle, &w3.HandleOptions{})
server.Start()
}
func (API) OPTIONS ¶
func (a API) OPTIONS(path string, handle APIHandle, options *HandleOptions)
OPTIONS register a new HTTP OPTIONS request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
return nil, nil
}
server.API.OPTIONS("/users/user/", handle, &w3.HandleOptions{})
server.Start()
}
func (API) PATCH ¶
func (a API) PATCH(path string, handle APIHandle, options *HandleOptions)
PATCH register a new HTTP PATCH request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
type userRequestType struct {
FirstName string `json:"first_name"`
}
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
username := request.Parameters["username"]
params := userRequestType{}
if err := request.DecodeJSON(¶ms); err != nil {
return nil, err
}
return map[string]string{
"first_name": params.FirstName,
"username": username,
}, nil
}
server.API.PATCH("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (API) POST ¶
func (a API) POST(path string, handle APIHandle, options *HandleOptions)
POST register a new HTTP POST request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
type userRequestType struct {
FirstName string `json:"first_name"`
}
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
username := request.Parameters["username"]
params := userRequestType{}
if err := request.DecodeJSON(¶ms); err != nil {
return nil, err
}
return map[string]string{
"first_name": params.FirstName,
"username": username,
}, nil
}
server.API.POST("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (API) PUT ¶
func (a API) PUT(path string, handle APIHandle, options *HandleOptions)
PUT register a new HTTP PUT request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
type userRequestType struct {
FirstName string `json:"first_name"`
}
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
username := request.Parameters["username"]
params := userRequestType{}
if err := request.DecodeJSON(¶ms); err != nil {
return nil, err
}
return map[string]string{
"first_name": params.FirstName,
"username": username,
}, nil
}
server.API.PUT("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
type APIHandle ¶
type APIHandle func(request *Request, response *APIResponse) (any, *Error)
APIHandle describes a method signature for handling an API request
type APIResponse ¶
type APIResponse struct {
// Additional headers to append to the response.
Headers http.Header
// Cookies to set on the response.
Cookies []http.Cookie
}
APIResponse describes additional response properties for API handles
type Error ¶
Error describes an API error object
func ValidationError ¶
ValidationError convenience method to make an error object for validation errors
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
username := request.Parameters["username"]
return nil, w3.ValidationError("No user with username %s", username)
}
server.API.GET("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
type HTTP ¶
type HTTP struct {
// contains filtered or unexported fields
}
HTTP describes an HTTP server. HTTP handles are exposed to the raw http request and response writers.
func (HTTP) DELETE ¶
func (h HTTP) DELETE(path string, handle HTTPHandle, options *HandleOptions)
DELETE register a new HTTP DELETE request handle
Example ¶
package main
import (
"net/http"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(w http.ResponseWriter, r *w3.Request) {
username := r.Parameters["username"]
w.Header().Set("X-Username", username)
w.WriteHeader(200)
}
server.HTTP.DELETE("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTP) GET ¶
func (h HTTP) GET(path string, handle HTTPHandle, options *HandleOptions)
GET register a new HTTP GET request handle
Example ¶
package main
import (
"fmt"
"io"
"net/http"
"os"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(w http.ResponseWriter, r *w3.Request) {
f, _ := os.Open("/foo/bar")
info, _ := f.Stat()
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size()))
io.Copy(w, f)
}
server.HTTP.GET("/users/user", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTP) HEAD ¶
func (h HTTP) HEAD(path string, handle HTTPHandle, options *HandleOptions)
HEAD register a new HTTP HEAD request handle
Example ¶
package main
import (
"net/http"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(w http.ResponseWriter, r *w3.Request) {
w.Header().Set("X-Fancy-Header", "Some value")
w.WriteHeader(204)
}
server.HTTP.HEAD("/users/user", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTP) OPTIONS ¶
func (h HTTP) OPTIONS(path string, handle HTTPHandle, options *HandleOptions)
OPTIONS register a new HTTP OPTIONS request handle
Example ¶
package main
import (
"net/http"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(w http.ResponseWriter, r *w3.Request) {
w.Header().Set("X-Fancy-Header", "Some value")
w.WriteHeader(200)
}
server.HTTP.OPTIONS("/users/user", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTP) PATCH ¶
func (h HTTP) PATCH(path string, handle HTTPHandle, options *HandleOptions)
PATCH register a new HTTP PATCH request handle
Example ¶
package main
import (
"net/http"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(w http.ResponseWriter, r *w3.Request) {
username := r.Parameters["username"]
w.Header().Set("X-Username", username)
w.WriteHeader(200)
}
server.HTTP.PATCH("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTP) POST ¶
func (h HTTP) POST(path string, handle HTTPHandle, options *HandleOptions)
POST register a new HTTP POST request handle
Example ¶
package main
import (
"net/http"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(w http.ResponseWriter, r *w3.Request) {
username := r.Parameters["username"]
w.Header().Set("X-Username", username)
w.WriteHeader(200)
}
server.HTTP.POST("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTP) PUT ¶
func (h HTTP) PUT(path string, handle HTTPHandle, options *HandleOptions)
PUT register a new HTTP PUT request handle
Example ¶
package main
import (
"net/http"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(w http.ResponseWriter, r *w3.Request) {
username := r.Parameters["username"]
w.Header().Set("X-Username", username)
w.WriteHeader(200)
}
server.HTTP.PUT("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
type HTTPEasy ¶
type HTTPEasy struct {
// contains filtered or unexported fields
}
HTTPEasy describes a simple to use HTTP router. HTTPEasy handles are expected to return a reader and specify the content type and length themselves.
HTTP abstracts many features away from the caller, providing a simpler experience when a only a simple HTTP server is needed. If you require more control, use the HTTP router.
The HTTPEasy server supports HTTP range requests, should the client request it and the application provide a supported Reader io.ReadSeekCloser.
func (HTTPEasy) DELETE ¶
func (h HTTPEasy) DELETE(path string, handle HTTPEasyHandle, options *HandleOptions)
DELETE register a new HTTP DELETE request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
username := request.Parameters["username"]
return &w3.HTTPResponse{
Headers: map[string]string{
"X-Username": username,
},
}
}
server.HTTPEasy.DELETE("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) GET ¶
func (h HTTPEasy) GET(path string, handle HTTPEasyHandle, options *HandleOptions)
GET register a new HTTP GET request handle
Example ¶
package main
import (
"os"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
f, err := os.Open("/foo/bar")
info, ierr := f.Stat()
if err != nil || ierr != nil {
return &w3.HTTPResponse{
Status: 500,
}
}
return &w3.HTTPResponse{
Reader: f, // The file will be closed automatically
ContentType: "text/plain",
ContentLength: uint64(info.Size()),
}
}
server.HTTPEasy.GET("/users/user", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) GETHEAD ¶
func (h HTTPEasy) GETHEAD(path string, handle HTTPEasyHandle, options *HandleOptions)
GETHEAD registers both an HTTP GET and HTTP HEAD request handle. Equal to calling HTTPEasy.GET and HTTPEasy.HEAD.
Handle responses can always return a reader, it will automatically be ignored for HEAD requests.
Example ¶
package main
import (
"os"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
f, err := os.Open("/foo/bar")
info, ierr := f.Stat()
if err != nil || ierr != nil {
return &w3.HTTPResponse{
Status: 500,
}
}
return &w3.HTTPResponse{
Reader: f, // the file will not be read for HTTP HEAD requests, but it will be closed.
ContentType: "text/plain",
ContentLength: uint64(info.Size()),
}
}
server.HTTPEasy.GETHEAD("/users/user", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) HEAD ¶
func (h HTTPEasy) HEAD(path string, handle HTTPEasyHandle, options *HandleOptions)
HEAD register a new HTTP HEAD request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
return &w3.HTTPResponse{
Headers: map[string]string{
"X-Fancy-Header": "some value",
},
}
}
server.HTTPEasy.HEAD("/users/user", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) OPTIONS ¶
func (h HTTPEasy) OPTIONS(path string, handle HTTPEasyHandle, options *HandleOptions)
OPTIONS register a new HTTP OPTIONS request handle
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
return &w3.HTTPResponse{
Headers: map[string]string{
"X-Fancy-Header": "some value",
},
}
}
server.HTTPEasy.OPTIONS("/users/user", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) PATCH ¶
func (h HTTPEasy) PATCH(path string, handle HTTPEasyHandle, options *HandleOptions)
PATCH register a new HTTP PATCH request handle
Example ¶
package main
import (
"os"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
username := request.Parameters["username"]
f, err := os.Open("/foo/bar")
if err != nil {
return &w3.HTTPResponse{
Status: 500,
}
}
return &w3.HTTPResponse{
Headers: map[string]string{
"X-Username": username,
},
Reader: f,
}
}
server.HTTPEasy.PATCH("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) POST ¶
func (h HTTPEasy) POST(path string, handle HTTPEasyHandle, options *HandleOptions)
POST register a new HTTP POST request handle
Example ¶
package main
import (
"os"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
username := request.Parameters["username"]
f, err := os.Open("/foo/bar")
if err != nil {
return &w3.HTTPResponse{
Status: 500,
}
}
return &w3.HTTPResponse{
Headers: map[string]string{
"X-Username": username,
},
Reader: f,
}
}
server.HTTPEasy.POST("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) PUT ¶
func (h HTTPEasy) PUT(path string, handle HTTPEasyHandle, options *HandleOptions)
PUT register a new HTTP PUT request handle
Example ¶
package main
import (
"os"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request) *w3.HTTPResponse {
username := request.Parameters["username"]
f, err := os.Open("/foo/bar")
if err != nil {
return &w3.HTTPResponse{
Status: 500,
}
}
return &w3.HTTPResponse{
Headers: map[string]string{
"X-Username": username,
},
Reader: f,
}
}
server.HTTPEasy.PUT("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (HTTPEasy) Static ¶
func (h HTTPEasy) Static(path string, directory string, options *StaticOptions)
Static registers a GET and HEAD handle for all requests under path to serve any files matching the directory.
For example:
directory = /usr/share/www/ path = /static/ Request for '/static/image.jpg' would read file '/usr/share/www/image.jpg'
Will panic if any handle is registered under path. Attempting to register a new handle under path after calling Static 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.
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
server.HTTPEasy.Static("/static/*", "/path/to/static/files", nil)
server.Start()
}
func (HTTPEasy) StaticFS ¶
StaticFS registers a GET and HEAD handle for all requests under path to serve any files reading from the given filesystem.
For example:
directory = /resources/ fsRoot = /static/ Request for '/static/image.jpg' would read file '/resources/image.jpg'
Will panic if any handle is registered under path. Attempting to register a new handle under path after calling Static 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.
type HTTPEasyHandle ¶
type HTTPEasyHandle func(request *Request) *HTTPResponse
HTTPEasyHandle describes a method signature for handling an HTTP request
type HTTPHandle ¶
type HTTPHandle func(w http.ResponseWriter, r *Request)
HTTPHandle describes a method signature for handling an HTTP request
type HTTPResponse ¶
type HTTPResponse struct {
// The reader for the response. Will be closed when the HTTP response is finished. Can be nil.
//
// If a io.ReadSeekCloser is provided then ranged data may be provided for an HTTP range request.
Reader io.ReadCloser
// The status code for the response. If 0 then 200 is implied.
Status int
// Additional headers to append to the response.
Headers map[string]string
// Cookies to set on the response.
Cookies []http.Cookie
// The content type of the response. Will overwrite any 'content-type' header in Headers.
ContentType string
// The length of the content. Will overwrite any 'content-length' header in Headers.
ContentLength uint64
}
HTTPResponse describes an HTTP response
type HandleOptions ¶
type HandleOptions struct {
// AuthenticateMethod method called to determine if a request is properly authenticated or not. If a method is
// provided, then it is called for each incoming request. The value returned by this method is passed as the
// UserData field of a [w3.Request]. Returning nil signals an unauthenticated request, which will be handled by
// the UnauthorizedMethod (if provided) or a default handle. If the AuthenticateMethod is not provided, then the
// UserData field is nil.
AuthenticateMethod func(request *http.Request) any
// PreHandle is an optional method that is called immediately upon receiving the HTTP request, before authentication
// and before rate limit checks. This method allows servers to provide early handling of a request before any
// processing happens.
//
// The returned error is only used as a nil check, the value of any error isn't used. If an error is returned then
// no more processing is performed. It is assumed that a response will have been written to w.
//
// If nil is returned then the request will continue normally, no status should have been written to w. Any headers
// added may be overwritten by the handle.
PreHandle func(w http.ResponseWriter, request *http.Request) error
// which allows you to customize the response seen by the user.
// If omitted, a default handle is used.
UnauthorizedMethod func(w http.ResponseWriter, request *http.Request)
// MaxBodyLength defines the maximum length accepted for any HTTP request body. Requests that exceed this limit will
// receive a "413 Payload Too Large" response. The default value of 0 will not reject requests with large bodies.
MaxBodyLength uint64
// DontLogRequests if true then requests to this handle are not logged
DontLogRequests bool
}
HandleOptions describes options for a route
type JSONResponse ¶
type JSONResponse struct {
// The actual data of the response
Data any `json:"data,omitempty"`
// If an error occured, details about the error
Error *Error `json:"error,omitempty"`
}
JSONResponse describes an API response object
type MockRequestParameters ¶
type MockRequestParameters struct {
// User data to be passed into the handler. May be nil.
UserData any
// URL parameters (not query parameters) to be populated into the request.Params object in the handler. May be nil.
Parameters map[string]string
// Object to be encoded with JSON as the body. May be nil. Exclusive to Body.
JSONBody any
// Body data. May be nil. Exclusive to JSONBody.
Body io.ReadCloser
// Optional HTTP request to pass to the handler.
Request *http.Request
}
Parameters for creating a mock request for uses in tests
type MockResponseWriter ¶ added in v1.1.0
MockResponseWriter is an implementation of the http.ResponseWriter interface meant for use in unit tests. It also implements http.Flusher.
func NewMockResponseWriter ¶ added in v1.1.0
func NewMockResponseWriter() *MockResponseWriter
NewMockResponseWriter creates a new response writer suitable for use in unit tests.
func (*MockResponseWriter) Flush ¶ added in v1.1.0
func (rw *MockResponseWriter) Flush()
func (*MockResponseWriter) Header ¶ added in v1.1.0
func (rw *MockResponseWriter) Header() http.Header
func (*MockResponseWriter) Write ¶ added in v1.1.0
func (rw *MockResponseWriter) Write(b []byte) (int, error)
func (*MockResponseWriter) WriteHeader ¶ added in v1.1.0
func (rw *MockResponseWriter) WriteHeader(statusCode int)
type Request ¶
type Request struct {
*http.Request
// URL path parameters (not query parameters). Keys do not include the ':' or '*'.
Parameters map[string]string
// User data provided from the result of the AuthenticateRequest method on the handle options
UserData any
}
Request describes an API request
func MockRequest ¶
func MockRequest(parameters MockRequestParameters) *Request
MockRequest will generate a mock request for testing your handlers. Will panic for invalid parameters.
func (Request) DecodeJSON ¶
DecodeJSON unmarshal the JSON body to the provided interface.
Equal to calling:
r.Decode(v, json.NewDecoder(r.HTTP.Body))
Example ¶
package main
import (
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
type userRequestType struct {
FirstName string `json:"first_name"`
}
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
username := request.Parameters["username"]
params := userRequestType{}
if err := request.DecodeJSON(¶ms); err != nil {
return nil, err
}
return map[string]string{
"first_name": params.FirstName,
"username": username,
}, nil
}
server.API.POST("/users/user/:username", handle, &w3.HandleOptions{})
server.Start()
}
func (Request) RealRemoteAddr ¶
RealRemoteAddr will try to get the real IP address of the incoming connection taking proxies into consideration. This function looks for the `X-Real-IP`, `X-Forwarded-For`, and `CF-Connecting-IP` headers, and if those don't exist will return the remote address of the connection.
Will never return nil, if it is unable to get a valid address it will return 0.0.0.0
Example ¶
package main
import (
"fmt"
"git.ecn.io/ian/w3"
"github.com/ecnepsnai/logtic"
)
func main() {
server := w3.New("127.0.0.1:8080", logtic.Log.Connect("HTTP"))
handle := func(request *w3.Request, response *w3.APIResponse) (any, *w3.Error) {
clientAddr := request.RealRemoteAddr().String()
fmt.Printf("%s\n", clientAddr)
return clientAddr, nil
}
server.API.POST("/ip/my_ip", handle, &w3.HandleOptions{})
server.Start()
}
type Server ¶
type Server struct {
// The socket address that the server is listening on. Only populated if the server was created with w3.New().
BindAddress *string
// The port that this server is listening on. Only populated if the server was created with w3.New().
ListenPort uint16
// The JSON API server. API handles return data or an error, and all responses are wrapped in a common
// response object; [w3.JSONResponse].
API API
// HTTPEasy describes a easy HTTP server. HTTPEasy handles are expected to return a reader and specify the content
// type and length themselves.
//
// The HTTPEasy server supports HTTP range requests, should the client request it and the application provide a
// supported Reader [io.ReadSeekCloser].
HTTPEasy HTTPEasy
// The HTTP server. HTTP handles are exposed to the raw http request and response writers.
HTTP HTTP
// Additional options for the server
Options ServerOptions
// contains filtered or unexported fields
}
Server describes an web server
func New ¶
New create a new server object that will bind to the provided address. Does not accept incoming connections until the server is started. Bind address must be in the format of "address:port", such as "localhost:8080" or "0.0.0.0:8080".
func NewListener ¶
NewListener creates a new server object that will use the given listener. Does not accept incoming connections until the server is started.
func (*Server) SetMethodNotAllowedHandle ¶ added in v1.1.1
func (s *Server) SetMethodNotAllowedHandle(h func(w http.ResponseWriter, r *http.Request))
SetMethodNotAllowedHandle will set the handle called when a request that did match a router but with the incorrect method occurs. Defaults to a plain HTTP 405 with "Method not allowed" as the body.
func (*Server) SetNotFoundHandle ¶ added in v1.1.1
func (s *Server) SetNotFoundHandle(h func(w http.ResponseWriter, r *http.Request))
SetNotFoundHandle will set the handle called when a request that does not match a registered path occurs. Defaults to a plain HTTP 404 with "Not found" as the body.
func (*Server) SetPostRequestHandle ¶ added in v1.1.1
func (s *Server) SetPostRequestHandle(h func(r *router.RequestResult))
SetPostRequestHandle will set the handle called after every request is processed, no matter if the request was successful or not.
func (*Server) SetRateLimitedHandle ¶ added in v1.1.1
func (s *Server) SetRateLimitedHandle(h func(w http.ResponseWriter, r *http.Request))
SetRateLimitedHandle will set the handle called when a request exceed the configured maximum per second limit. Defaults to a plain HTTP 429 with "Too many requests" as the body.
type ServerOptions ¶
type ServerOptions struct {
// Specify the maximum number of requests any given client IP address can make per second. Requests that are rate
// limited will call the RateLimitedHandler, which you can override to customize the response.
// Setting this to 0 disables rate limiting.
MaxRequestsPerSecond int
// The level to use when logging out HTTP requests. Maps to github.com/ecnepsnai/logtic levels. Defaults to Debug.
RequestLogLevel logtic.LogLevel
// If true then the server will not try to reply with chunked data for an HTTP range request
IgnoreHTTPRangeRequests bool
}
type StaticOptions ¶ added in v1.0.1
type StaticOptions struct {
router.StaticOptions
}
Additional options for Static and StaticFS handles.