gear

package module
v0.0.0-...-5930cac Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2024 License: MIT Imports: 14 Imported by: 0

README

gear

Golang gears for web.

Gear is about two things: middleware and encoding(binding).

  1. Middleware

    A middleware is a function that wraps another HTTP handler and performs some action before or after the wrapped handler executes.

    A middleware in Gear is the Middleware interface, the wrapping is implemented by the Wrap and other Wrap... functions.

    Here are some examples:

    Logging:

    var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Handle the request
    })
    
    handler = gear.Wrap(handler, gear.Logger(nil))
    

    or if you want to log the entire server:

    var server = &http.Server{}
    gear.WrapServer(server)
    

    or listen and serve with logger:

    gear.ListenAndServe("", nil, gear.Logger(nil))
    

    Doing authentication:

    var authMiddleware = gear.MiddlewareFunc(func(g *gear.Gear, next func(*gear.Gear)) {
        if !adminAuth(g.R) {
            // Authentication failed, sends 401 and skips the handler.
            g.Code(http.StatusUnauthorized)
            return
        }
        // Executes the handler.
        next(g)
    })
    
    gear.NewGroup("/admin", nil, authMiddleware).
        HandleFunc("action1", func(w http.ResponseWriter, r *http.Request) {
            // Do action1 of administrator
        }).
        HandleFunc("action2", func(w http.ResponseWriter, r *http.Request) {
            // Do action2 of administrator
        })
    gear.ListenAndServe("", nil)
    

    More examples.

  2. Encoding

    TODO:

Documentation

Overview

Package gear is the implementation of Gear framewrok.

Gear is about two things: middleware and encoding(binding).

1. Middleware

A middleware is a function that wraps another HTTP handler and performs some action before or after the wrapped handler executes.

A middleware in Gear is the Middleware interface, the wrapping is implemented by the Wrap and other Wrap... functions.

For example the following code add logging to a handler:

var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	// Handle the request
})

handler = gear.Wrap(handler, gear.Logger(nil))

or if you want to log the entire server:

var server = &http.Server{}
gear.WrapServer(server)

or listen and serve with logger:

gear.ListenAndServe("", nil, gear.Logger(nil))

Here is another example of doing authentication:

var authMiddleware = gear.MiddlewareFunc(func(g *gear.Gear, next func(*gear.Gear)) {
	if !adminAuth(g.R) {
		// Authentication failed, sends 401 and skips the handler.
		g.Code(http.StatusUnauthorized)
		return
	}
	// Executes the handler.
	next(g)
})

gear.NewGroup("/admin", nil, authMiddleware).
	HandleFunc("action1", func(w http.ResponseWriter, r *http.Request) {
		// Do action1 of administrator
	}).
	HandleFunc("action2", func(w http.ResponseWriter, r *http.Request) {
		// Do action2 of administrator
	})
gear.ListenAndServe("", nil)

See package examples section for more examples.

Index

Examples

Constants

View Source
const (
	// LoggerMethodKey is the key used by [Logger] for the HTTP method of HTTP request.
	// The associated Value is a string.
	LoggerMethodKey = "method"
	// LoggerMethodKey is the key used by [Logger] for the host of HTTP request.
	// The associated Value is a string.
	LoggerHostKey = "host"
	// LoggerMethodKey is the key used by [Logger] for the URL of HTTP request.
	// The associated Value is a string.
	LoggerURLKey = "URL"
	// LoggerMethodKey is the group key used by [Logger] for the header of HTTP request.
	// The associated Value in group is a string.
	LoggerHeaderKey = "header"
)

Variables

View Source
var RawLogger *slog.Logger = slog.Default()

RawLogger used by Gear. Do not set a nil Logger, using log level to control output. See NoLog.

Functions

func ListenAndServe

func ListenAndServe(addr string, handler http.Handler, middlewares ...Middleware) error

ListenAndServe calls http.ListenAndServe(addr, Wrap(handler, middlewares...)). If handler is nil, http.DefaultServeMux wil be used.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		var g = gear.G(r)
		// Use g here.
		_ = g
	})
	gear.ListenAndServe(":8080", nil)
}

func ListenAndServeTLS

func ListenAndServeTLS(addr, certFile, keyFile string, handler http.Handler, middlewares ...Middleware) error

ListenAndServe calls http.ListenAndServeTLS(addr, certFile, keyFile, Wrap(handler, middlewares...)). If handler is nil, http.DefaultServeMux wil be used.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		var g = gear.G(r)
		// Use g here.
		_ = g
	})
	gear.ListenAndServeTLS(":8080", "certfile", "keyfile", nil)
}

func Log

func Log(level slog.Level, msg string, args ...any)

Log logs at level with RawLogger.

func LogD

func LogD(msg string, args ...any)

LogD logs at slog.LevelDebug with RawLogger.

func LogE

func LogE(msg string, args ...any)

LogE logs at slog.LevelError with RawLogger.

func LogI

func LogI(msg string, args ...any)

LogI logs at slog.LevelInfo with RawLogger.

func LogIfErr

func LogIfErr(err error) error

LogIfErr logs err at slog.LevelError with RawLogger if err != nil. The log message has attribute {"err":err}. LogIfErr returns err. This function is convenient to log non-nil return value. For example:

LogIfErr(g.JSON(v))
Example
package main

import (
	"github.com/mkch/gear"
)

func main() {
	var (
		g *gear.Gear
		v any
	) // From somewhere else.
	gear.LogIfErr(g.JSON(v))
}

func LogIfErrT

func LogIfErrT[T any](ret T, err error) error

LogIfErrT logs ret and err at slog.LevelError with RawLogger if err != nil. The log message has attribute {"ret": ret, "err":err}. LogIfErrorT returns err. This function is convenient to log non-nil return value. For example:

LogIfErrT(fmt.Println("msg"))
Example
package main

import (
	"fmt"

	"github.com/mkch/gear"
)

func main() {
	gear.LogIfErrT(fmt.Println("msg"))
}

func LogW

func LogW(msg string, args ...any)

LogW logs at slog.LevelWarn with RawLogger.

func NewTestServer

func NewTestServer(handler http.Handler, middlewares ...Middleware) *httptest.Server

Server calls httptest.NewServer() with Wrap(handler, middlewares...)).

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	var server = gear.NewTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var g = gear.G(r)
		// Use g here.
		_ = g
	}))
	defer server.Close()
}

func NoLog

func NoLog() *slog.Logger

NoLog returns a Logger discards all messages and has a level of -99. The following code disables message logging to a certain extent:

RawLogger = NoLog()

func Wrap

func Wrap(handler http.Handler, middlewares ...Middleware) http.Handler

Wrap wraps handler and adds Gear to it. If handler is nil, http.DefaultServeMux will be used. Parameter middlewares will be added to the result Handler. Middlewares will be served in reversed order of addition, so panic recovery middleware should be added last to catch all panics.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	var handler http.Handler = gear.Wrap(
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			var g = gear.G(r)
			// Use g here.
			_ = g
		}))
	http.Handle("/", handler)
}

func WrapFunc

func WrapFunc(f func(w http.ResponseWriter, r *http.Request), middlewares ...Middleware) http.Handler

WrapFunc wraps f to a handler and adds Gear to it. If f is nil, http.DefaultServeMux.ServeHTTP will be used. Parameter middlewares will be added to the result Handler. Middlewares will be served in reversed order of addition , so panic recovery middleware should be added last to catch all panics.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	var handler http.Handler = gear.WrapFunc(
		func(w http.ResponseWriter, r *http.Request) {
			var g = gear.G(r)
			// Use g here.
			_ = g
		})
	http.Handle("/", handler)
}

func WrapServer

func WrapServer(server *http.Server, middlewares ...Middleware) *http.Server

WrapServer wraps server.Handler using Wrap() and returns server itself.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		var g = gear.G(r)
		// Use g here.
		_ = g
	})
	var server = gear.WrapServer(&http.Server{})
	server.ListenAndServe()
}

Types

type Gear

type Gear struct {
	R *http.Request       // R of this request.
	W http.ResponseWriter // W of this request.
	// contains filtered or unexported fields
}

Gear, the core of this framework.

func G

func G(r *http.Request) *Gear

G retrives the Gear in r. It panics if no Gear.

func (*Gear) Code

func (g *Gear) Code(code int)

Code writes code and status text using http.Code().

func (*Gear) ContextValue

func (g *Gear) ContextValue(key any) any

ContextValue returns the request context value associated with key, or nil if no value is associated with key.

func (*Gear) DecodeBody

func (g *Gear) DecodeBody(v any) error

DecodeBody parses body and stores the result in the value pointed to by v. This method is a shortcut of encoding.DecodeBody(g.R, nil, v). See encoding.DecodeBody for more details.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		type User struct {
			Name string `map:"user"`
			ID   int    `map:"id"`
		}
		var user User
		gear.G(r).DecodeBody(&user)
		// If the client posts a body:
		//
		// {
		//   "user": "USER1",
		//   "id": 100,
		// }
		//
		// user equals User{"USER1", 100}
	})
}

func (*Gear) DecodeForm

func (g *Gear) DecodeForm(v any) error

DecodeFrom calls g.R.ParseForm(), decodes g.R.Form and stores the result in the value pointed by v. See encoding.DecodeForm for more details. Call ParseMultipartForm() of the request to include values in multi-part form.

func (*Gear) DecodeHeader

func (g *Gear) DecodeHeader(v any) error

DecodeHeader decodes g.R.Header and stores the result in the value pointed by v. See encoding.DecodeForm for more details.

func (*Gear) DecodeQuery

func (g *Gear) DecodeQuery(v any) error

DecodeQuery decodes r.URL.Query() and stores the result in the value pointed by v. See encoding.DecodeForm for more details.

func (*Gear) JSON

func (g *Gear) JSON(v any) error

JSON writes JSON encoding of v to the response.

func (*Gear) JSONResponse

func (g *Gear) JSONResponse(code int, v any) error

JSONResponse writes code and JSON encoding of v to the response.

func (*Gear) MustDecodeBody

func (g *Gear) MustDecodeBody(v any) (err error)

MustDecodeBody calls Gear.DecodeBody. If DecodeBody returns an error, MustDecodeBody returns it but also writes a http.StatusBadRequest response and stops the middleware processing.

func (*Gear) MustDecodeForm

func (g *Gear) MustDecodeForm(v any) (err error)

MustDecodeForm calls Gear.DecodeForm. If DecodeForm returns an error, MustDecodeForm returns it but also writes a http.StatusBadRequest response and stops the middleware processing.

func (*Gear) MustDecodeHeader

func (g *Gear) MustDecodeHeader(v any) (err error)

MustDecodeHeader calls Gear.DecodeHeader. If DecodeHeader returns an error, MustDecodeHeader returns it but also writes a http.StatusBadRequest response and stops the middleware processing.

func (*Gear) MustDecodeQuery

func (g *Gear) MustDecodeQuery(v any) (err error)

MustDecodeQuery calls Gear.DecodeQuery. If DecodeQuery returns an error, MustDecodeHeader returns it but also writes a http.StatusBadRequest response and stops the middleware processing.

func (*Gear) SetContextValue

func (g *Gear) SetContextValue(key, val any)

SetContextValue sets the request context value associated with key to val.

func (*Gear) Stop

func (g *Gear) Stop()

Stop stops further middleware processing. Current middleware is unaffected.

func (*Gear) String

func (g *Gear) String(body string) error

String writes and body to the response.

func (*Gear) StringResponse

func (g *Gear) StringResponse(code int, body string) error

StringResponse writes code and body to the response.

func (*Gear) StringResponsef

func (g *Gear) StringResponsef(code int, format string, a ...any) error

StringResponsef writes code and then call fmt.Fprintf() to write the formated string.

func (*Gear) Write

func (g *Gear) Write(r io.Reader) error

Write copies data from r to the response.

func (*Gear) XML

func (g *Gear) XML(v any) error

XML writes XML encoding of v to the response.

func (*Gear) XMLResponse

func (g *Gear) XMLResponse(code int, v any) error

XMLResponse writes code and JSON encoding of v to the response.

type Group

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

Group is prefix of a group of urls registered to http.ServeMux.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func adminAuth() bool { return false }

func op1() {}

func main() {
	gear.NewGroup("/admin", nil, gear.MiddlewareFunc(func(g *gear.Gear, next func(*gear.Gear)) {
		// Handle admin authentication
		if !adminAuth() {
			http.Error(g.W, "", http.StatusUnauthorized)
			return
		}
		// OK, go ahead.
		next(g)
	})).Handle("op1", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// The request path will be /admin/op1
		op1() // Do the operation.
	}))
}

func NewGroup

func NewGroup(prefix string, mux *http.ServeMux, middlewares ...Middleware) *Group

NewGroup create a prefix of URLs on mux. When any URL has the prefix is requested, middlewares of group handle the request before URL handler. If mux is nil, http.DefaultServeMux will be used.

func (*Group) Group

func (parent *Group) Group(prefix string, middlewares ...Middleware) *Group

Group creates a new URL prefix: path.Join(parent.prefix, prefix). When any URL has the prefix is requested, middlewares of parent group handle the request before the new group.

func (*Group) Handle

func (group *Group) Handle(pattern string, handler http.Handler, middlewares ...Middleware) *Group

Handle registers handler for a pattern which is the group prefix joined (path.Join) pattern parameter. The handler and middlewares are wrapped(see Wrap) before registering. Group's middlewares take precedence over the wrapped handler here. If handler is nil, an empty handler will be used.

func (*Group) HandleFunc

func (group *Group) HandleFunc(pattern string, f func(w http.ResponseWriter, r *http.Request), middlewares ...Middleware) *Group

HandleFunc converts f to http.HandlerFunc and then call [Handle].

type LoggerOptions

type LoggerOptions struct {
	// Keys are the keys to log. Keys is a set of strings.
	// Zero value means all Logger keys available(See LoggerMethodKey etc).
	Keys map[string]bool
	// HeaderKeys are the keys of HTTP header to log.
	// HeaderKeys are only used when LoggerHeaderKey is in Keys.
	// Zero value means not logging any header value.
	HeaderKeys []string
	// Attrs can be used to generate the slog.Attr slice to log for r.
	// If Attrs is not nil, all fields above are ignored, the Logger just
	// calls LogAttrs() to log the return value of this function.
	// This function should not retain or modify r.
	Attrs func(r *http.Request) []slog.Attr
}

LoggerOptions are options for Logger. A zero LoggerOptions consists entirely of zero values.

type Middleware

type Middleware interface {
	// Serve serves http and optionally pass control to next middleware by calling next(g).
	Serve(g *Gear, next func(*Gear))
}

Middleware is a middleware used in Gear framework.

Example
package main

import (
	"fmt"
	"log"

	"github.com/mkch/gear"
)

// LogMiddleware is a middleware to log HTTP message.
type LogMiddleware log.Logger

// Serve implements gear.Middleware.
func (l *LogMiddleware) Serve(g *gear.Gear, next func(*gear.Gear)) {
	fmt.Printf("%v %v", g.R.Method, g.R.URL)
	// Call the real handler.
	next(g)
}

func main() {
	// Use LogMiddleware.
	gear.ListenAndServe(":80", nil, (*LogMiddleware)(log.Default()))
}

func Logger

func Logger(opt *LoggerOptions) Middleware

Logger returns a Middleware to log HTTP access log. If opt is nil, the default options are used.

Log level: LevelInfo

Log attributes:

"msg": "HTTP"
"method": request.Method
"host": request.Host
"URL": request.URL
"header.headerKey": request.Header[headerKey]
Example
package main

import (
	"github.com/mkch/gear"
)

func main() {
	// logger logs HTTP method, host, URL(by default) and User-Agent header for request.
	logger := gear.Logger(&gear.LoggerOptions{
		HeaderKeys: []string{"User-Agent"},
	})
	gear.ListenAndServe(":http", nil, logger)
}

func MiddlewareFuncWitName

func MiddlewareFuncWitName(f func(g *Gear, next func(*Gear)), name string) Middleware

MiddlewareFuncWitName is an adapter to allow the use of ordinary functions as Middleware. Parameter name will be used as the name of Middleware.

func PanicRecovery

func PanicRecovery(addStack bool) Middleware

PanicRecovery returns a Middleware which recovers from panics, logs a LevelError message "recovered from panic" and sends 500 responses. The "value" attribute is set to panic value. If addStack is true, "stack" attribute is set to the string representation of the call stack. Panic recovery middleware should be added as the last middleware to catch all panics.

Example
package main

import (
	"github.com/mkch/gear"
)

func main() {
	gear.ListenAndServe(":80", nil, gear.PanicRecovery(true))
}

type MiddlewareFunc

type MiddlewareFunc func(g *Gear, next func(*Gear))

MiddlewareFunc is an adapter to allow the use of ordinary functions as Middleware. If f is a function with the appropriate signature, MiddlewareFunc(f) is a Middleware that calls f.

Example
package main

import (
	"log"

	"github.com/mkch/gear"
)

func main() {
	var logMiddleware = gear.MiddlewareFuncWitName(func(g *gear.Gear, next func(*gear.Gear)) {
		// Pre-processing.
		log.Printf("Before request: Path=%v", g.R.URL.Path)
		// Call the real handler.
		next(g)
		// Post-processing.
		log.Printf("After request: Path=%v", g.R.URL.Path)
	}, "logger")
	gear.ListenAndServe(":80", nil, logMiddleware)
}

func (MiddlewareFunc) Serve

func (m MiddlewareFunc) Serve(g *Gear, next func(*Gear))

Serve implements Serve() method of Middleware.

type MiddlewareName

type MiddlewareName interface {
	MiddlewareName() string
}

MiddlewareName is an optional interface to be implemented by a Middleware. If a Middleware implements this interface, MiddlewareName() will be used to get it's name, or the reflect type name will be used.

type PathInterceptor

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

PathInterceptor is a Middleware intercepting requests with matching URLs.

Example
package main

import (
	"net/http"

	"github.com/mkch/gear"
)

func main() {
	var handler = gear.MiddlewareFunc(func(g *gear.Gear, next func(*gear.Gear)) {
		// Do admin authentication.
		var authOK bool
		if !authOK {
			http.Error(g.W, "", http.StatusUnauthorized)
			g.Stop()
		}
	})
	// "/admin" and all paths starts with "/admin/" will be intercepted by handler.
	gear.ListenAndServe(":80", nil, gear.NewPathInterceptor("/admin", handler))
}

func NewPathInterceptor

func NewPathInterceptor(prefix string, handler Middleware) *PathInterceptor

NewPathInterceptor returns a PathInterceptor which executes handler when the path of request URL contains prefix.

func (*PathInterceptor) Serve

func (m *PathInterceptor) Serve(g *Gear, next func(*Gear))

Serve implements Serve() method of Middleware.

Directories

Path Synopsis
Package encoding implements data encoding and decoding used in Gear.
Package encoding implements data encoding and decoding used in Gear.
Package impl contains implementation details of Gear.
Package impl contains implementation details of Gear.
geartest
Package geartest contains test utilities used in Gear.
Package geartest contains test utilities used in Gear.
Package validator provides a generic interface for value validation.
Package validator provides a generic interface for value validation.
goplayground module

Jump to

Keyboard shortcuts

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