garcon

package module
v0.20.1 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2022 License: MIT Imports: 50 Imported by: 7

README

Teal.Finance/Garcon

logo Opinionated boilerplate all-in-one HTTP server with rate-limiter, Cookies, JWT, CORS, OPA, web traffic, Prometheus export, PProf… for API and static website.
Go Reference Go Report Card

This library is used by Rainbow and other internal projects at Teal.Finance.

Please propose a Pull Request to add here your project that also uses Garcon.

Features

Garcon includes the following middleware pieces:

  • Logging of incoming requests ;
  • Rate-limiter to prevent requests flooding ;
  • JWT management using HttpOnly cookie or Authorization header ;
  • Cross-Origin Resource Sharing (CORS) ;
  • Authentication rules based on Datalog/Rego files using Open Policy Agent ;
  • Web traffic metrics.

Garcon also provides the following features:

  • HTTP/REST server for API endpoints (compatible with any Go-standard HTTP handlers) ;
  • File server intended for static web files supporting Brotli and AVIF data ;
  • Metrics server exporting data to Prometheus (or other compatible monitoring tool) ;
  • PProf server for debugging purpose ;
  • Error response in JSON format ;
  • Chained middleware (fork of justinas/alice).

CPU profiling

Moreover, Garcon provides a helper feature defer ProbeCPU.Stop() to investigate CPU consumption issues thanks to https://github.com/pkg/profile.

In you code, add defer ProbeCPU.Stop() that will write the cpu.pprof file.

import "github.com/teal-finance/garcon/pprof"

func myFunctionConsumingLotsOfCPU() {
    defer pprof.ProbeCPU.Stop()

    // ... lots of sub-functions
}

Install pprof and browse your cpu.pprof file:

cd ~/go
go get -u github.com/google/pprof
cd -
pprof -http=: cpu.pprof

Examples

See also a complete real example in the repo github.com/teal-finance/rainbow.

High-level example

The following code uses the high-level function Garcon.RunServer().

package main

import "github.com/teal-finance/garcon"

func main() {
    g, _ := garcon.New(
        garcon.WithURLs("http://localhost:8080/myapp"),
        garcon.WithDocURL("/doc"), // URL --> http://localhost:8080/myapp/doc
        garcon.WithServerHeader("MyBackendName"),
        garcon.WithJWT(hmacSHA256, "FreePlan", 10, "PremiumPlan", 100),
        garcon.WithOPA("auth.rego"),
        garcon.WithLimiter(10, 30),
        garcon.WithPProf(8093),
        garcon.WithProm(9093),
        garcon.WithDev(),
    )

    h := handler(g.ErrWriter, g.Checker)

    g.Run(h, 8080)
}
1. Run the high-level example
go build -race ./examples/high-level && ./high-level
2022/01/29 17:31:26 Prometheus export http://localhost:9093
2022/01/29 17:31:26 CORS: Set origin prefixes: [http://localhost:8080 http://localhost: http://192.168.1.]
2022/01/29 17:31:26 CORS: Methods=[GET POST] Headers=[Origin Accept Content-Type Authorization Cookie] Credentials=true MaxAge=86400
2022/01/29 17:31:26 Enable PProf endpoints: http://localhost:8093/debug/pprof
2022/01/29 17:31:26 Create cookie plan=FreePlan domain=localhost secure=false myapp=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2NzUwMDk4ODZ9.hiQQuFxNghrrCvvzEsXzN1lWTavL09Plx0dhFynrBxc
2022/01/29 17:31:26 Create cookie plan=PremiumPlan domain=localhost secure=false myapp=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiUHJlbWl1bVBsYW4iLCJleHAiOjE2NzUwMDk4ODZ9.iP587iHjhLmX_8yMhuQfKu9q7qLbLE7UX-UgkL_VYhE
2022/01/29 17:31:26 JWT not required for dev. origins: [http://localhost:8080 http://localhost: http://192.168.1.]
2022/01/29 17:31:26 Middleware response HTTP header: Set Server MyApp-1.2.0
2022/01/29 17:31:26 Middleware RateLimiter: burst=100 rate=5/s
2022/01/29 17:31:26 Middleware logger: requester IP and requested URL
2022/01/29 17:31:26 Server listening on http://localhost:8080
2. Embedded PProf server

Visit the PProf server at http://localhost:8093/debug/pprof providing the following endpoints:

PProf is easy to use with curl or wget:

( cd ~ ; go get -u github.com/google/pprof )

curl http://localhost:8093/debug/pprof/allocs > allocs.pprof
pprof -http=: allocs.pprof

wget http://localhost:8093/debug/pprof/heap
pprof -http=: heap

wget http://localhost:8093/debug/pprof/goroutine
pprof -http=: goroutine

See the PProf post (2013) for further explanations.

3. Embedded metrics server

The export port http://localhost:9093/metrics is for the monitoring tools like Prometheus.

4. Static website server

The high-level example is running.

Open http://localhost:8080/myapp with your browser, and play with the API endpoints.

The resources and API endpoints are protected with a HttpOnly cookie. The high-level example sets the cookie to browsers visiting the index.html.

func handler(errWriter garcon.ErrWriter, jc *jwtperm.Checker) http.Handler {
    r := chi.NewRouter()

    // Static website files
    ws := webserver.WebServer{Dir: "examples/www", ErrWriter: errWriter}
    r.With(jc.SetCookie).Get("/", ws.ServeFile("index.html", "text/html; charset=utf-8"))
    r.With(jc.SetCookie).Get("/favicon.ico", ws.ServeFile("favicon.ico", "image/x-icon"))
    r.With(jc.ChkCookie).Get("/js/*", ws.ServeDir("text/javascript; charset=utf-8"))
    r.With(jc.ChkCookie).Get("/css/*", ws.ServeDir("text/css; charset=utf-8"))
    r.With(jc.ChkCookie).Get("/images/*", ws.ServeImages())

    // API
    r.With(jc.ChkJWT).Get("/api/v1/items", items)
    r.With(jc.ChkJWT).Get("/api/v1/ducks", errWriter.NotImplemented)

    // Other endpoints
    r.NotFound(errWriter.InvalidPath)

    return r
}
5. Enable Authentication

Restart again the high-level example with authentication enabled.

Attention, in this example we use two redundant middleware pieces using the same JWT: jwtperm and opa. This is just an example, don't be confused.

go build -race ./examples/high-level && ./high-level -auth
2021/12/02 08:09:47 Prometheus export http://localhost:9093
2021/12/02 08:09:47 CORS: Set origin prefixes: [http://localhost:8080 http://localhost: http://192.168.1.]
2021/12/02 08:09:47 CORS: Methods=[GET] Headers=[Origin Accept Content-Type Authorization Cookie] Credentials=true MaxAge=86400
2021/12/02 08:09:47 JWT not required for dev. origins: [http://localhost:8080 http://localhost: http://192.168.1.]
2021/12/02 08:09:47 Enable PProf endpoints: http://localhost:8093/debug/pprof
2021/12/02 08:09:47 Create cookie plan=FreePlan domain=localhost secure=false jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ5ODd9.5tJk2NoHxkG0o_owtMleBcUaR8z1vRx4rxRRqtZUc_Q
2021/12/02 08:09:47 Create cookie plan=PremiumPlan domain=localhost secure=false jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiUHJlbWl1bVBsYW4iLCJleHAiOjE2Njk5NjQ5ODd9.ifKhbmxQQ64NweL5aQDb_42tvKHwqiEKD-vxHO3KzsM
2021/12/02 08:09:47 OPA: load "examples/sample-auth.rego"
2021/12/02 08:09:47 Middleware OPA: map[sample-auth.rego:package auth

default allow = false
tokens := {"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ0ODh9.elDm_t4vezVgEmS8UFFo_spLJTts7JWybzbyO_aYV3Y"} { true }
allow = true { __local0__ = input.token; data.auth.tokens[__local0__] }]
2021/12/02 08:09:47 Middleware response HTTP header: Set Server MyBackendName-1.2.0
2021/12/02 08:09:47 Middleware RateLimiter: burst=100 rate=5/s
2021/12/02 08:09:47 Middleware logger: requester IP and requested URL
2021/12/02 08:09:47 Server listening on http://localhost:8080
6. Default HTTP request headers

Test the API with curl:

curl -D - http://localhost:8080/myapp/api/v1/items
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Server: MyBackendName-1.2.0
Vary: Origin
X-Content-Type-Options: nosniff
Date: Thu, 02 Dec 2021 07:06:20 GMT
Content-Length: 84

{"error":"Unauthorized",
"path":"/api/v1/items",
"doc":"http://localhost:8080/myapp/doc"}

The corresponding garcon logs:

2021/12/02 08:06:20 in  127.0.0.1:42888 GET /api/v1/items
[cors] 2021/12/02 08:06:20 Handler: Actual request
[cors] 2021/12/02 08:06:20   Actual request no headers added: missing origin
2021/12/02 08:06:20 OPA unauthorize 127.0.0.1:42888 /api/v1/items
2021/12/02 08:06:20 out 127.0.0.1:42888 GET /api/v1/items 1.426916ms c=1

The CORS logs can be disabled by passing debug=false in cors.Handler(origins, false).

The value c=1 measures the web traffic (current active HTTP connections).

7. With Authorization header
curl -D - http://localhost:8080/myapp/api/v1/items -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ0ODh9.elDm_t4vezVgEmS8UFFo_spLJTts7JWybzbyO_aYV3Y'
HTTP/1.1 200 OK
Content-Type: application/json
Server: MyBackendName-1.2.0
Vary: Origin
Date: Thu, 02 Dec 2021 07:10:37 GMT
Content-Length: 25

["item1","item2","item3"]

The corresponding garcon logs:

2021/12/02 08:10:37 in  127.0.0.1:42892 GET /api/v1/items
[cors] 2021/12/02 08:10:37 Handler: Actual request
[cors] 2021/12/02 08:10:37   Actual request no headers added: missing origin
2021/12/02 08:10:37 Authorization header has JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ0ODh9.elDm_t4vezVgEmS8UFFo_spLJTts7JWybzbyO_aYV3Y
2021/12/02 08:10:37 JWT Claims: {FreePlan  {  [] 2022-12-02 08:01:28 +0100 CET <nil> <nil> invalid cookie}}
2021/12/02 08:10:37 JWT has the FreePlan Namespace
2021/12/02 08:10:37 JWT Permission: {10}
2021/12/02 08:10:37 out 127.0.0.1:42892 GET /api/v1/items 1.984568ms c=1 a=1 i=0 h=0

Low-level example

WARNING: This chapter is outdated!

See the low-level example.

The following code is a bit different to the stuff done by the high-level function Garcon.Run() presented in the previous chapter. The following code is intended to show Garcon can be customized to meet your specific requirements.

package main

import (
    "log"
    "net"
    "net/http"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/teal-finance/garcon"
)

// Garcon settings
const apiDoc = "https://my-dns.co/doc"
const allowedProdOrigin = "https://my-dns.co"
const allowedDevOrigins = "http://localhost: http://192.168.1."
const serverHeader = "MyBackendName-1.2.0"
const authCfg = "examples/sample-auth.rego"
const pprofPort = 8093
const expPort = 9093
const burst, reqMinute = 10, 30
const devMode = true

func main() {
    if devMode {
        // the following line collects the CPU-profile and writes it in the file "cpu.pprof"
        defer garcon.ProbeCPU().Stop()
    }

    garcon.StartPProfServer(pprofPort)

    // Uniformize error responses with API doc
    errWriter := garcon.NewErrWriter(apiDoc)

    chain, connState := setMiddlewares(errWriter)

    // Handles both REST API and static web files
    h := handler(errWriter)
    h = chain.Then(h)

    runServer(h, connState)
}

func setMiddlewares(errWriter garcon.ErrWriter) (chain garcon.Chain, connState func(net.Conn, http.ConnState)) {
    // Start a metrics server in background if export port > 0.
    // The metrics server is for use with Prometheus or another compatible monitoring tool.
    metrics := garcon.Metrics{}
    chain, connState = garcon.StartMetricsServer(expPort, devMode)

    // Limit the input request rate per IP
    reqLimiter := garcon.NewReqLimiter(burst, reqMinute, devMode, errWriter)

    corsConfig := allowedProdOrigin
    if devMode {
        corsConfig += " " + allowedDevOrigins
    }

    allowedOrigins := garcon.SplitClean(corsConfig)

    chain = chain.Append(
        reqLimiter.Limit,
        garcon.ServerHeader(serverHeader),
        cors.Handler(allowedOrigins, devMode),
    )

    // Endpoint authentication rules (Open Policy Agent)
    files := garcon.SplitClean(authCfg)
    policy, err := garcon.NewPolicy(files, errWriter)
    if err != nil {
        log.Fatal(err)
    }

    if policy.Ready() {
        chain = chain.Append(policy.Auth)
    }

    return chain, connState
}

// runServer runs in foreground the main server.
func runServer(h http.Handler, connState func(net.Conn, http.ConnState)) {
    const mainPort = "8080"

    server := http.Server{
        Addr:              ":" + mainPort,
        Handler:           h,
        TLSConfig:         nil,
        ReadTimeout:       1 * time.Second,
        ReadHeaderTimeout: 1 * time.Second,
        WriteTimeout:      1 * time.Second,
        IdleTimeout:       1 * time.Second,
        MaxHeaderBytes:    222,
        TLSNextProto:      nil,
        ConnState:         connState,
        ErrorLog:          log.Default(),
        BaseContext:       nil,
        ConnContext:       nil,
    }

    log.Print("Server listening on http://localhost", server.Addr)

    log.Fatal(server.ListenAndServe())
}

// handler creates the mapping between the endpoints and the handler functions.
func handler(errWriter garcon.ErrWriter) http.Handler {
    r := chi.NewRouter()

    // Website with static files
    ws := garcon.NewStaticWebServer("examples/www", errWriter)
    r.Get("/", ws.ServeFile("index.html", "text/html; charset=utf-8"))
    r.Get("/js/*", ws.ServeDir("text/javascript; charset=utf-8"))
    r.Get("/css/*", ws.ServeDir("text/css; charset=utf-8"))
    r.Get("/images/*", ws.ServeImages())

    // API
    r.Get("/api/v1/items", items)
    r.Get("/api/v1/ducks", errWriter.NotImplemented)

    // Other endpoints
    r.NotFound(errWriter.InvalidPath)

    return r
}

func items(w http.ResponseWriter, _ *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    _, _ = w.Write([]byte(`["item1","item2","item3"]`))
}

KeyStore example

The example KeyStore implements a key/value datastore providing private storage for each client identified by its unique IP.

go build ./examples/keystore
./keystore

Then open http://localhost:8080 to learn more about the implemented features.

✨ Contributions Welcome

This project needs your help to become better. Please propose your enhancements, or even a further refactoring.

We welcome contributions in many forms, and there's always plenty to do!

🗣️ Feedback

If you have some suggestions, or need a new feature, please contact us, using the issues, or at Teal.Finance@pm.me or @TealFinance.

Feel free to propose a Pull Request, your contributions are welcome. 😉

Copyright (c) 2021 Teal.Finance/Garcon contributors

Teal.Finance/Garcon is free software, and can be redistributed and/or modified under the terms of the MIT License. SPDX-License-Identifier: MIT

Teal.Finance/Garcon is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the LICENSE file (alongside the source files) or https://opensource.org/licenses/MIT.

See also

Documentation

Overview

Package garcon is a server for API and static website including middlewares to manage rate-limit, Cookies, JWT, CORS, OPA, web traffic, Prometheus export and PProf.

Index

Constants

View Source
const (

	// DefaultPlan is the plan name in absence of "permissions" parametric parameters.
	DefaultPlan = "VIP"
	// DefaultPerm is the perm value in absence of "permissions" parametric parameters.
	DefaultPerm = 1
)
View Source
const FingerprintExplanation = `` /* 683-byte string literal not displayed */

FingerprintExplanation provides a description of the logged HTTP headers.

Variables

View Source
var (
	ErrExpiredToken    = errors.New("expired or invalid refresh token")
	ErrJWTSignature    = errors.New("JWT signature mismatch")
	ErrNoAuthorization = errors.New("provide your JWT within the 'Authorization Bearer' HTTP header")
	ErrNoBase64JWT     = errors.New("the token claims (second part of the JWT) is not base64-valid")
	ErrNoBearer        = errors.New("malformed HTTP Authorization, must be Bearer")
)
View Source
var DevOrigins = []*url.URL{
	{Scheme: "http", Host: "localhost:"},
	{Scheme: "http", Host: "192.168.1."},
}

DevOrigins provides the development origins: - yarn run vite --port 3000 - yarn run vite preview --port 5000 - localhost:8085 on multi devices: web auto-reload using https://github.com/synw/fwr - flutter run --web-port=8080 - 192.168.1.x + any port on tablet: mobile app using fast builtin auto-reload.

View Source
var ErrEmptyOPAFilename = errors.New("OPA: missing filename")
View Source
var ErrNonPrintable = errors.New("non-printable")

V is set using the following link flag `-ldflags`:

v="$(git describe --tags --always --broken)"
go build -ldflags="-X 'github.com/teal-finance/garcon.V=$v'" ./cmd/main/package

The following formats the version as "v1.2.0-my-branch+3". The trailing "+3" is the number of commits since v1.2.0. If no tag in Git repo, $t is the current commit long SHA1.

t="$(git describe --tags --abbrev=0 --always)"
b="$(git branch --show-current)"
[[ $b == main ]] && b="" || b="-$b"
n="$(git rev-list --count "$t"..)"
[[ $n == 0 ]] && n="" || n="+$n"
go build -ldflags="-X 'github.com/teal-finance/garcon.V=$t$b$n'" ./cmd/main/package

Functions

func AppendPrefixes added in v0.5.1

func AppendPrefixes(origins []string, prefixes ...string) []string

func AppendURLs added in v0.9.0

func AppendURLs(urls []*url.URL, prefixes ...*url.URL) []*url.URL

func CORSHandler added in v0.17.0

func CORSHandler(origins []string, debug bool) func(next http.Handler) http.Handler

CORSHandler uses restrictive CORS values.

func ConvertSize added in v0.17.0

func ConvertSize(sizeInBytes int) string

ConvertSize converts a size in bytes into the most appropriate unit among KiB, MiB, GiB, TiB, PiB and EiB. 1 KiB is 1024 bytes as defined by the ISO/IEC 80000-13:2008 standard. See: https://wikiless.org/wiki/ISO%2FIEC_80000#Units_of_the_ISO_and_IEC_80000_series

func ConvertSize64 added in v0.17.0

func ConvertSize64(sizeInBytes int64) string

ConvertSize64 is similar ConvertSize but takes in input an int64.

func DefaultContactSettings added in v0.16.0

func DefaultContactSettings() map[string][2]int

DefaultContactSettings is compliant with standard names for web form input fields: https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#inappropriate-for-the-control

func DefaultFileSettings added in v0.16.0

func DefaultFileSettings() map[string][2]int

DefaultFileSettings sets FileLimits with only "file".

func FingerprintMD added in v0.17.0

func FingerprintMD(r *http.Request) string

FingerprintMD provide the browser fingerprint in markdown format. Attention: read the .

func InsertSchema added in v0.17.0

func InsertSchema(origins []string)

func InvalidPath added in v0.17.0

func InvalidPath(w http.ResponseWriter, r *http.Request)

func LoadPolicy added in v0.17.0

func LoadPolicy(filenames []string) (*ast.Compiler, error)

LoadPolicy checks the Rego filenames and loads them to build the OPA compiler.

func LogDuration added in v0.19.0

func LogDuration(next http.Handler) http.Handler

LogDuration logs the requested URL along with the time to handle it.

func LogRequest added in v0.17.0

func LogRequest(next http.Handler) http.Handler

LogRequest is the middleware to log the requester IP and the requested URL.

func LogRequestFingerprint added in v0.17.0

func LogRequestFingerprint(next http.Handler) http.Handler

LogRequestFingerprint is the middleware to log incoming HTTP request and browser fingerprint.

func LogSafeDuration added in v0.19.0

func LogSafeDuration(next http.Handler) http.Handler

LogSafeDuration is similar to LogDuration but also sanitizes the URL.

func LogSafeRequest added in v0.19.0

func LogSafeRequest(next http.Handler) http.Handler

LogSafeRequest is similar to LogRequest but sanitize the URL.

func LogSafeRequestFingerprint added in v0.19.0

func LogSafeRequestFingerprint(next http.Handler) http.Handler

LogSafeRequestFingerprint is similar to LogRequestFingerprint but sanitize the URL.

func LogVersion added in v0.20.0

func LogVersion()

LogVersion logs the version and (Git) commit information.

func NewHash added in v0.17.0

func NewHash() (hash.Hash, error)

NewHash uses HighwayHash is a hashing algorithm enabling high speed (especially on AMD64).

func NotImplemented added in v0.17.0

func NotImplemented(w http.ResponseWriter, r *http.Request)

func Obfuscate added in v0.17.0

func Obfuscate(str string) (string, error)

Obfuscate hashes the input string to prevent logging sensitive information.

func OriginsFromURLs added in v0.9.0

func OriginsFromURLs(urls []*url.URL) []string

func ParseURLs added in v0.9.3

func ParseURLs(origins []string) []*url.URL

func PrintVersion added in v0.17.0

func PrintVersion(program string)

PrintVersion prints the version and (Git) commit information.

func Printable added in v0.17.0

func Printable(s string) int

Printable returns the position (index) of a Carriage Return "\r", or a Line Feed "\n", or any other ASCII control code (except space), or, as well as, bn invalid UTF-8 code. Printable returns -1 if the string is safely printable preventing log injection.

func PrintableRune added in v0.17.0

func PrintableRune(r rune) bool

PrintableRune returns false if rune is a Carriage Return "\r", or a Line Feed "\n", or another ASCII control code (except space), or an invalid UTF-8 code. PrintableRune can be used to prevent log injection.

func Printables added in v0.17.0

func Printables(array []string) int

Printables returns -1 when all the strings are printable else returns the position of the rejected character.

func ProbeCPU added in v0.17.0

func ProbeCPU() interface{ Stop() }

ProbeCPU is used like the following:

defer pprof.ProbeCPU.Stop()

When the caller reaches its function end, the defer executes Stop() that writes the file "cpu.pprof". To visualize "cpu.pprof" use the pprof tool:

cd ~/go
go get -u github.com/google/pprof
cd -
pprof -http=: cpu.pprof

or using one single command line:

go run github.com/google/pprof@latest -http=: cpu.pprof

func RejectInvalidURI added in v0.17.0

func RejectInvalidURI(next http.Handler) http.Handler

RejectInvalidURI rejects HTTP requests having a Carriage Return "\r" or a Line Feed "\n" within the URI to prevent log injection.

func SafeHeader added in v0.17.0

func SafeHeader(r *http.Request, header string) string

SafeHeader stringifies a safe list of HTTP header values.

func Sanitize added in v0.17.0

func Sanitize(str string) string

Sanitize replaces control codes by the tofu symbol and invalid UTF-8 codes by the replacement character. Sanitize can be used to prevent log injection. Inspired from: https://wikiless.org/wiki/Replacement_character#Replacement_character https://graphicdesign.stackexchange.com/q/108297

func ServerHeader

func ServerHeader(version string) func(next http.Handler) http.Handler

ServerHeader sets the Server HTTP header in the response.

func SetCustomVersionFlag added in v0.20.0

func SetCustomVersionFlag(fs *flag.FlagSet, flagName, program string)

SetCustomVersionFlag register PrintVersion() for the version flag.

Example with default values:

import "github.com/teal-finance/garcon"

func main() {
     garcon.SetCustomVersionFlag(nil, "", "")
     flag.Parse()
}

Example with custom values values:

import "github.com/teal-finance/garcon"

func main() {
     garcon.SetCustomVersionFlag(nil, "v", "MyApp")
     flag.Parse()
}

func SetVersionFlag added in v0.17.0

func SetVersionFlag()

SetVersionFlag defines -version flag to print the version stored in V. See SetCustomVersionFlag for a more flexibility.

func SplitClean

func SplitClean(values string) []string

SplitClean splits the values and trim them.

func StartPProfServer added in v0.17.0

func StartPProfServer(port int)

StartPProfServer starts a PProf server in background. Endpoints usage example:

curl http://localhost:6063/debug/pprof/allocs > allocs.pprof
pprof -http=: allocs.pprof

wget http://localhost:31415/debug/pprof/goroutine
pprof -http=: goroutine

wget http://localhost:31415/debug/pprof/heap
pprof -http=: heap

wget http://localhost:31415/debug/pprof/trace
pprof -http=: trace

func TraversalPath added in v0.17.0

func TraversalPath(w http.ResponseWriter, r *http.Request) bool

TraversalPath returns true when path contains ".." to prevent path traversal attack.

func Value added in v0.12.3

func Value(r *http.Request, key, header string) (string, error)

Value returns the /endpoint/{key} (URL path) else the "key" form (HTTP body) else the "key" query string (URL) else the HTTP header.

func Values added in v0.12.3

func Values(r *http.Request, key string) ([]string, error)

func Version added in v0.17.0

func Version(program string) string

Version format is "Program-1.2.3". If the program argument is empty, the format is "v1.2.3". If V is empty, Version uses the main module version.

func VersionInfo added in v0.20.0

func VersionInfo(program string) []string

PrintVersion computes the version and (Git) commit information.

func WriteJSONErr added in v0.17.0

func WriteJSONErr(w http.ResponseWriter, r *http.Request, statusCode int, a ...any)

Types

type Chain added in v0.17.0

type Chain []Middleware

Chain acts as a list of http.Handler middleware. Chain is effectively immutable: once created, it will always hold the same set of middleware in the same order.

func NewChain added in v0.17.0

func NewChain(chain ...Middleware) Chain

NewChain creates a new chain, memorizing the given list of middleware. NewChain serves no other function, middleware is only constructed upon a call to Then().

func StartMetricsServer added in v0.17.0

func StartMetricsServer(port int, namespace Namespace) (Chain, func(net.Conn, http.ConnState))

StartMetricsServer creates and starts the Prometheus export server.

func (Chain) Append added in v0.17.0

func (c Chain) Append(chain ...Middleware) Chain

Append extends a chain, adding the specified middleware as the last ones in the request flow.

chain := chain.New(m1, m2)
chain = chain.Append(m3, m4)
// requests in chain go m1 -> m2 -> m3 -> m4

func (Chain) Then added in v0.17.0

func (c Chain) Then(handler http.Handler) http.Handler

Then chains the middleware and returns the final http.Handler.

chain.New(m1, m2, m3).Then(h)

is equivalent to:

m1(m2(m3(h)))

When the request comes in, it will be passed to m1, then m2, then m3 and finally, the given handler (assuming every middleware calls the following one).

A chain can be safely reused by calling Then() several times.

chain := chain.New(rateLimitHandler, csrfHandler)
indexPipe = chain.Then(indexHandler)
authPipe  = chain.Then(authHandler)

Note: every call to Then() calls all middleware pieces. Thus several instances of the same middleware will be created when a chain is reused in this previous example. For proper middleware, this should cause no problem.

Then() treats nil as http.DefaultServeMux.

func (Chain) ThenFunc added in v0.17.0

func (c Chain) ThenFunc(fn http.HandlerFunc) http.Handler

ThenFunc works identically to Then, but takes a HandlerFunc instead of a Handler.

The following two statements are equivalent:

c.Then(http.HandlerFunc(fn))
c.ThenFunc(fn)

ThenFunc provides all the guarantees of Then.

type ErrWriter added in v0.17.0

type ErrWriter string

ErrWriter enables writing useful JSON error message in the HTTP response body.

func NewErrWriter added in v0.17.0

func NewErrWriter(docURL string) ErrWriter

NewErrWriter creates a ErrWriter structure.

func (ErrWriter) InvalidPath added in v0.17.0

func (errWriter ErrWriter) InvalidPath(w http.ResponseWriter, r *http.Request)

func (ErrWriter) NotImplemented added in v0.17.0

func (errWriter ErrWriter) NotImplemented(w http.ResponseWriter, r *http.Request)

func (ErrWriter) Write added in v0.17.0

func (errWriter ErrWriter) Write(w http.ResponseWriter, r *http.Request, statusCode int, messages ...any)

Write is a fast pretty-JSON marshaler dedicated to the HTTP error response. Write extends the JSON content when more than two messages are provided.

func (ErrWriter) WriteSafeJSONErr added in v0.17.0

func (errWriter ErrWriter) WriteSafeJSONErr(w http.ResponseWriter, r *http.Request, statusCode int, messages ...any)

WriteSafeJSONErr is a safe alternative to Write, may be slower despite the easyjson generated code. WriteSafeJSONErr concatenates all messages in "error" field.

type Garcon

type Garcon struct {
	Namespace      Namespace
	ConnState      func(net.Conn, http.ConnState)
	Checker        TokenChecker
	ErrWriter      ErrWriter
	AllowedOrigins []string
	Middlewares    Chain
}

func New added in v0.6.0

func New(opts ...Option) (*Garcon, error)

func (*Garcon) NewJWTChecker added in v0.6.0

func (g *Garcon) NewJWTChecker(urls []*url.URL, secretKey []byte, planPerm ...any) *JWTChecker

func (*Garcon) NewSessionToken added in v0.13.1

func (g *Garcon) NewSessionToken(urls []*url.URL, secretKey []byte, expiry time.Duration, setIP bool) *incorruptible.Incorruptible

func (*Garcon) Run added in v0.4.0

func (g *Garcon) Run(h http.Handler, port int) error

Run runs the HTTP server(s) in foreground. Optionally it also starts a metrics server in background (if export port > 0). The metrics server is for use with Prometheus or another compatible monitoring tool.

type JWTChecker added in v0.20.0

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

func NewJWTChecker added in v0.20.0

func NewJWTChecker(urls []*url.URL, errWriter ErrWriter, secretKey []byte, permissions ...any) *JWTChecker

func (*JWTChecker) Chk added in v0.20.0

func (ck *JWTChecker) Chk(next http.Handler) http.Handler

Chk only accepts HTTP requests having a valid cookie. Then, Chk puts the permission (of the JWT) in the request context.

func (*JWTChecker) Cookie added in v0.20.0

func (ck *JWTChecker) Cookie(i int) *http.Cookie

Cookie returns the internal cookie (for test purpose).

func (*JWTChecker) NewCookie added in v0.20.0

func (ck *JWTChecker) NewCookie(name, plan, user string, secure bool, dns, dir string) http.Cookie

func (*JWTChecker) PermFromBearerOrCookie added in v0.20.0

func (ck *JWTChecker) PermFromBearerOrCookie(r *http.Request) (Perm, []any)

func (*JWTChecker) PermFromCookie added in v0.20.0

func (ck *JWTChecker) PermFromCookie(r *http.Request) (Perm, []any)

func (*JWTChecker) PermFromJWT added in v0.20.0

func (ck *JWTChecker) PermFromJWT(JWT string) (Perm, []any)

func (*JWTChecker) Set added in v0.20.0

func (ck *JWTChecker) Set(next http.Handler) http.Handler

Set puts a HttpOnly cookie in the HTTP response header when no valid cookie is present. The new cookie conveys the JWT of the first plan. Set also puts the permission from the JWT in the request context.

func (*JWTChecker) Vet added in v0.20.0

func (ck *JWTChecker) Vet(next http.Handler) http.Handler

Vet only accepts the HTTP request having a valid JWT. The JWT can be either in the cookie or in the first "Authorization" header. Then, Vet puts the permission (of the JWT) in the request context.

type Middleware added in v0.17.0

type Middleware func(http.Handler) http.Handler

Middleware is a constructor function returning a http.Handler.

type Namespace added in v0.19.0

type Namespace string

Namespace holds the Prometheus namespace.

func NewNamespace added in v0.20.0

func NewNamespace(str string) Namespace

NewNamespace extract the wider "[a-zA-Z0-9_]+" string from the end of str. If str is a path or an URL, keep the last basename. Example: keep "myapp" from "https://example.com/path/myapp/"

func (*Namespace) SetPromNamingRule added in v0.20.0

func (ns *Namespace) SetPromNamingRule()

SetPromNamingRule verifies Prom naming rules for namespace and fixes it if necessary. valid namespace = [a-zA-Z][a-zA-Z0-9_]* https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels

func (Namespace) String added in v0.20.0

func (ns Namespace) String() string

type Option added in v0.6.0

type Option func(*parameters)

func WithDev added in v0.6.0

func WithDev(enable ...bool) Option

func WithDocURL added in v0.6.0

func WithDocURL(docURL string) Option

func WithIncorruptible added in v0.13.4

func WithIncorruptible(secretKeyHex string, expiry time.Duration, setIP bool) Option

WithIncorruptible enables the "session" cookies based on fast and tiny token. WithIncorruptible requires WithURLs() to set the Cookie name, secure, domain and path. WithIncorruptible is not compatible with WithJWT: use only one of them.

func WithJWT added in v0.6.0

func WithJWT(secretKeyHex string, planPerm ...any) Option

WithJWT requires WithURLs() to set the Cookie name, secure, domain and path. WithJWT is not compatible with WithTkn: use only one of them.

func WithLimiter added in v0.6.0

func WithLimiter(values ...int) Option

func WithNamespace added in v0.20.0

func WithNamespace(namespace string) Option

func WithOPA added in v0.6.0

func WithOPA(opaFilenames ...string) Option

func WithPProf added in v0.6.0

func WithPProf(port int) Option

func WithProm added in v0.6.0

func WithProm(port int, namespace string) Option

func WithReqLogs added in v0.6.1

func WithReqLogs(verbosity ...int) Option

func WithServerHeader added in v0.6.0

func WithServerHeader(program string) Option

func WithURLs added in v0.9.0

func WithURLs(addresses ...string) Option

type Perm added in v0.17.0

type Perm struct {
	Value int
}

func PermFromCtx added in v0.20.0

func PermFromCtx(r *http.Request) Perm

PermFromCtx gets the permission information from the request context.

func (Perm) PutInCtx added in v0.20.0

func (perm Perm) PutInCtx(r *http.Request) *http.Request

PutInCtx stores the permission info within the request context.

type Policy added in v0.17.0

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

Policy manages the Open Policy Agent (OPA). see https://www.openpolicyagent.org/docs/edge/integration/#integrating-with-the-go-api

func NewPolicy added in v0.17.0

func NewPolicy(filenames []string, errWriter ErrWriter) (Policy, error)

NewPolicy creates a new Policy by loading rego files.

func (Policy) AuthOPA added in v0.17.0

func (opa Policy) AuthOPA(next http.Handler) http.Handler

AuthOPA is the HTTP middleware to check endpoint authorization.

type ReqLimiter added in v0.17.0

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

func NewReqLimiter added in v0.17.0

func NewReqLimiter(maxReqBurst, maxReqPerMinute int, devMode bool, errWriter ErrWriter) ReqLimiter

func (*ReqLimiter) LimitRate added in v0.17.0

func (rl *ReqLimiter) LimitRate(next http.Handler) http.Handler

type StaticWebServer added in v0.16.0

type StaticWebServer struct {
	Dir       string
	ErrWriter ErrWriter
}

StaticWebServer is a webserver serving static files among HTML, CSS, JS and popular image formats.

func NewStaticWebServer added in v0.16.0

func NewStaticWebServer(dir string, errWriter ErrWriter) StaticWebServer

func (*StaticWebServer) ServeAssets added in v0.16.0

func (ws *StaticWebServer) ServeAssets() func(w http.ResponseWriter, r *http.Request)

ServeAssets detects the Content-Type depending on the asset extension.

func (*StaticWebServer) ServeDir added in v0.16.0

func (ws *StaticWebServer) ServeDir(contentType string) func(w http.ResponseWriter, r *http.Request)

ServeDir handles the static files using the same Content-Type.

func (*StaticWebServer) ServeFile added in v0.16.0

func (ws *StaticWebServer) ServeFile(urlPath, contentType string) func(w http.ResponseWriter, r *http.Request)

ServeFile handles one specific file (and its specific Content-Type).

func (*StaticWebServer) ServeImages added in v0.16.0

func (ws *StaticWebServer) ServeImages() func(w http.ResponseWriter, r *http.Request)

ServeImages detects the Content-Type depending on the image extension.

type TokenChecker added in v0.13.0

type TokenChecker interface {
	// Cookie returns the internal cookie (for test purpose).
	Cookie(i int) *http.Cookie

	// Set sets a cookie in the response when the request has no valid token.
	// Set searches the token in a cookie and in the first "Authorization" header.
	// Finally, Set stores the token attributes in the request context.
	Set(next http.Handler) http.Handler

	// Chk accepts requests only if it has a valid cookie.
	// Chk does not verify the "Authorization" header.
	// See the Vet() function to also verify the "Authorization" header.
	// Chk also stores the token attributes in the request context.
	// In dev. testing, Chk accepts any request but does not store invalid tokens.
	Chk(next http.Handler) http.Handler

	// Vet accepts requests having a valid token either in
	// the cookie or in the first "Authorization" header.
	// Vet also stores the decoded token in the request context.
	// In dev. testing, Vet accepts any request but does not store invalid tokens.
	Vet(next http.Handler) http.Handler
}

type WebForm added in v0.16.0

type WebForm struct {
	ErrWriter ErrWriter
	Notifier  notifier.Notifier
	Redirect  string

	// TextLimits are used as security limits
	// to avoid being flooded by large web forms
	// and unexpected field names.
	// The map key is the input field name.
	// The map value is a pair of integers:
	// the max length and the max line breaks.
	// Use 0 to disable any limit.
	TextLimits map[string][2]int

	// FileLimits is similar to TextLimits
	// but for uploaded files.
	// The map value is a pair of integers:
	// the max size in runes of one file
	// and the max occurrences having same field name.
	// Use 0 to disable any limit.
	FileLimits map[string][2]int

	// MaxTotalLength includes the
	// form fields and browser fingerprints.
	MaxTotalLength int
	// contains filtered or unexported fields
}

func NewContactForm added in v0.16.0

func NewContactForm(redirectURL, notifierURL string, errWriter ErrWriter) WebForm

func (*WebForm) NotifyWebForm added in v0.17.0

func (form *WebForm) NotifyWebForm() func(w http.ResponseWriter, r *http.Request)

NotifyWebForm registers a web-form middleware that structures the filled form into markdown format and sends it to the Notifier.

Directories

Path Synopsis
examples
chi
Package timex extends the standard package time.
Package timex extends the standard package time.

Jump to

Keyboard shortcuts

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