{"id":20793909,"url":"https://github.com/teal-finance/garcon","last_synced_at":"2025-05-06T00:14:53.813Z","repository":{"id":43172539,"uuid":"418265739","full_name":"teal-finance/garcon","owner":"teal-finance","description":"Golang web toolbox for API and static website including HTTP server, middlewares, JWT, CORS, OPA, Cookies, Prometheus exporter, Rate Limiter compatible with Go standards and dozens of Go routers","archived":false,"fork":false,"pushed_at":"2024-07-15T10:38:58.000Z","size":886,"stargazers_count":11,"open_issues_count":7,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-06T00:14:49.885Z","etag":null,"topics":["autorization","bearer","browser-fingerprint","chat-notification","coockie","cors","cors-middleware","file-server","golang","http-logger","http-server","jwt","middleware","middleware-collection","pprof","rate-limiter","static-website"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/teal-finance/garcon","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teal-finance.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"security.go","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-17T22:00:36.000Z","updated_at":"2024-07-15T10:38:12.000Z","dependencies_parsed_at":"2024-06-10T12:49:31.480Z","dependency_job_id":"ce28e91d-f6d4-4e57-ba9d-1d7eb58d9f82","html_url":"https://github.com/teal-finance/garcon","commit_stats":null,"previous_names":["teal-finance/server"],"tags_count":105,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teal-finance%2Fgarcon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teal-finance%2Fgarcon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teal-finance%2Fgarcon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teal-finance%2Fgarcon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teal-finance","download_url":"https://codeload.github.com/teal-finance/garcon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252596430,"owners_count":21773846,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autorization","bearer","browser-fingerprint","chat-notification","coockie","cors","cors-middleware","file-server","golang","http-logger","http-server","jwt","middleware","middleware-collection","pprof","rate-limiter","static-website"],"created_at":"2024-11-17T16:12:17.155Z","updated_at":"2025-05-06T00:14:53.793Z","avatar_url":"https://github.com/teal-finance.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Teal.Finance/Garcon\n\n| ![logo](examples/www/myapp/images/garcon.png) | Garcon works with all HTTP routers ans middleware respecting the Go HTTP standards. Garcon provides the batteries: static website server, contact-form backend, API helpers, debugging helpers (PProf), Git version, exporter server (Prometheus), health endpoints (Kubernetes), URI sanitization and middleware: rate-limiter, JWT cookies, CORS, traffic logs, OPA…\u003cbr\u003e[![Go Reference](examples/www/myapp/images/go-ref.svg \"Go documentation for Garcon\")](https://pkg.go.dev/github.com/teal-finance/garcon) [![Go Report Card](https://goreportcard.com/badge/github.com/teal-finance/garcon)](https://goreportcard.com/report/github.com/teal-finance/garcon) |\n| --------------------------------------------- |:--------- |\n\n## Motivation\n\nMany projects often start with one of the many nice HTTP routers and middleware already available and develop their own middleware, debugging server, API helpers... At Teal.Finance, we decided to share in one place (here) all our stuff in the idea to let other projects go faster.\n\n## Middleware\n\nOur Middleware are very easy to setup. They respect the Go standards. Thus you can easily use them with the HTTP router of your choice and chained them with other middleware:\n\n- `MiddlewareLogRequest` Log incoming requests (with or without browser fingerprint)\n- `MiddlewareLogDuration` Log processing time\n- `MiddlewareExportTrafficMetrics` Export web traffic metrics\n- `MiddlewareRejectUnprintableURI` Reject request with unwanted characters\n- `MiddlewareRateLimiter` Limit incoming request to prevent flooding\n- `MiddlewareServerHeader` Add the \"Server\" HTTP header in the response\n- `JWTChecker` JWT management using HttpOnly cookie or Authorization header\n- `IncorruptibleChecker` Session cookie with [Incorruptible](https://github.com/teal-finance/incorruptible) token\n- `MiddlewareCORS` Cross-Origin Resource Sharing (CORS)\n- `MiddlewareOPA` Authenticate from Datalog/Rego files using [Open Policy Agent](https://www.openpolicyagent.org)\n- `MiddlewareSecureHTTPHeader` Set some HTTP header to increase the web security\n\n```go\ng := garcon.New()\nmiddleware = garcon.NewChain(\n    g.MiddlewareRejectUnprintableURI(),\n    g.MiddlewareLogRequest(),\n    g.MiddlewareRateLimiter())\nrouter  := ... you choose\nhandler := middleware.Then(router)\nserver  := http.Server{Addr: \":8080\", Handler: handler}\nserver.ListenAndServe()\n```\n\n## Other features\n\n- Static web files server supporting Brotli and AVIF\n- Metrics server exporting data to Prometheus (or other compatible monitoring tool)\n- Health status server for Kubernetes liveness and readiness probes\n- PProf server for debugging purpose\n- Serialize JSON responses, including the error messages\n- Chained middleware (fork of [justinas/alice](https://github.com/justinas/alice))\n- Chained round trip handlers\n- Retrieve Git version, branch and commit from build flags and Go module information\n\n## Basic example\n\n```go\ng := garcon.New()\n\n// chain some middleware\nmiddleware = garcon.NewChain(\n    g.MiddlewareRejectUnprintableURI(),\n    g.MiddlewareLogRequests(),\n    g.MiddlewareRateLimiter())\n\n// use the HTTP router library of your choice, here we use Chi \nrouter := chi.NewRouter()\n\n// static website, automatically sends the Brotli-compressed file if present and supported by the browser\nws := g.NewStaticWebServer(\"/var/www\")\nrouter.Get(\"/\", ws.ServeFile(\"index.html\", \"text/html; charset=utf-8\"))\nrouter.Get(\"/favicon.ico\", ws.ServeFile(\"favicon.ico\", \"image/x-icon\"))\nrouter.Get(\"/js/*\", ws.ServeDir(\"text/javascript; charset=utf-8\"))\nrouter.Get(\"/css/*\", ws.ServeDir(\"text/css; charset=utf-8\"))\nrouter.Get(\"/images/*\", ws.ServeImages()) // automatically sends AVIF if present and supported by the browser\n\n// receive contact-forms on your chat channel on the fly\ncf := g.NewContactForm(\"/\")\nrouter.Post(\"/\", cf.Notify(\"https://mattermost.com/hooks/qite178czotd5\"))\n\n// Git version and last commit date (HTML or JSON depending on the \"Accept\" header)\nrouter.Get(\"/version\", garcon.ServeVersion())\n\n// return a JSON message\nrouter.Get(\"/reserved\", g.Writer.NotImplemented)\nrouter.NotFound(g.Writer.InvalidPath)\n\nhandler := middleware.Then(router)\nserver := http.Server{Addr: \":8080\", Handler: handler}\nserver.ListenAndServe()\n```\n\n## Incorruptible middleware\n\nGarcon uses the [Incorruptible](https://github.com/teal-finance/incorruptible) package\nto create/verify session cookie.\n\n```go\npackage main\n\nimport \"github.com/teal-finance/garcon\"\n\nfunc main() {\n    g, _ := garcon.New(\n        garcon.WithURLs(\"https://my-company.com\"),\n        garcon.WithDev())\n\n    aes128Key = \"00112233445566778899aabbccddeeff\"\n    maxAge := 3600\n    setIP := true\n    ck := g.IncorruptibleChecker(aes128Key, maxAge, setIP)\n\n    router := chi.NewRouter()\n\n    // website with static files directory\n    ws := g.NewStaticWebServer(\"/var/www\")\n\n    // ck.Set =\u003e set the cookie when visiting /\n    router.With(ck.Set).Get(\"/\", ws.ServeFile(\"index.html\", \"text/html; charset=utf-8\"))\n\n    // ck.Chk =\u003e reject request with invalid JWT cookie\n    router.With(ck.Chk).Get(\"/js/*\", ws.ServeDir(\"text/javascript; charset=utf-8\"))\n    router.With(ck.Chk).Get(\"/assets/*\", ws.ServeAssets())\n\n    // ck.Vet =\u003e accepts valid JWT in either the cookie or the Authorization header\n    router.With(ck.Vet).Post(\"/api/items\", myFunctionHandler)\n\n    server := http.Server{Addr: \":8080\", Handler: router}\n    server.ListenAndServe()\n}\n```\n\n## JWT middleware\n\nThe JWT and Incorruptible checkers share a common interface,\n`TokenChecker`, providing the same middleware: `Set()`, `Chk()` and `Vet()`.\n\n```go\npackage main\n\nimport \"github.com/teal-finance/garcon\"\n\nfunc main() {\n    g, _ := garcon.New(\n        garcon.WithURLs(\"https://my-company.com\"),\n        garcon.WithDev())\n\n    hmacSHA256Key := \"9d2e0a02121179a3c3de1b035ae1355b1548781c8ce8538a1dc0853a12dfb13d\"\n    ck := g.JWTChecker(hmacSHA256Key, \"FreePlan\", 10, \"PremiumPlan\", 100)\n\n    router := chi.NewRouter()\n\n    // website with static files directory\n    ws := g.NewStaticWebServer(\"/var/www\")\n\n    // ck.Set =\u003e set the cookie when visiting /\n    router.With(ck.Set).Get(\"/\", ws.ServeFile(\"index.html\", \"text/html; charset=utf-8\"))\n\n    // ck.Chk =\u003e reject request with invalid JWT cookie\n    router.With(ck.Chk).Get(\"/js/*\", ws.ServeDir(\"text/javascript; charset=utf-8\"))\n    router.With(ck.Chk).Get(\"/assets/*\", ws.ServeAssets())\n\n    // ck.Vet =\u003e accepts valid JWT in either the cookie or the Authorization header\n    router.With(ck.Vet).Post(\"/api/items\", myFunctionHandler)\n\n    server := http.Server{Addr: \":8080\", Handler: router}\n    server.ListenAndServe()\n}\n```\n\n## Who use Garcon\n\nIn production, this library is used by\n[Rainbow](https://github.com/teal-finance/rainbow),\n[Quid](https://github.com/teal-finance/quid)\nand other internal projects at Teal.Finance.\n\nPlease propose a [Pull Request](https://github.com/teal-finance/garcon/pulls) to add here your project that also uses Garcon.\n\nSee a complete real example in the repo\n[github.com/teal-finance/rainbow](https://github.com/teal-finance/rainbow/blob/main/cmd/server/main.go).\n\n## CPU profiling\n\nMoreover, Garcon simplifies investigation on CPU and memory consumption issues\nthanks to \u003chttps://github.com/pkg/profile\u003e.\n\nIn your code, add `defer garcon.ProbeCPU.Stop()` that will write the `cpu.pprof` file.\n\n```go\nimport \"github.com/teal-finance/garcon\"\n\nfunc myFunctionConsumingLotsOfCPU() {\n    defer garcon.ProbeCPU.Stop()\n\n    // ... lots of sub-functions\n}\n```\n\nRun `pprof` and browse your `cpu.pprof` file:\n\n```sh\ngo run github.com/google/pprof@latest -http=: cpu.pprof\n```\n\n## Complete example\n\nSee the [complete example](examples/complete)\nenabling almost of the Garcon features.\nBelow is a simplified extract:\n\n```go\npackage main\n\nimport \"github.com/teal-finance/garcon\"\n\nfunc main() {\n    defer garcon.ProbeCPU().Stop() // collects the CPU-profile and writes it in the file \"cpu.pprof\"\n\n    garcon.LogVersion()     // log the Git version\n    garcon.SetVersionFlag() // the -version flag prints the Git version\n    jwt := flag.Bool(\"jwt\", false, \"Use JWT in lieu of the Incorruptible token\")\n    flag.Parse()\n\n    g := garcon.New(\n        garcon.WithURLs(\"https://my-company.co\"),\n        garcon.WithDocURL(\"/doc\"),\n        garcon.WithPProf(8093))\n\n    ic := g.IncorruptibleChecker(aes128Key, 60, true)\n    jc := g.JWTChecker(hmacSHA256Key, \"FreePlan\", 10, \"PremiumPlan\", 100)\n\n    middleware, connState := g.StartExporter(9093)\n    middleware = middleware.Append(\n        g.MiddlewareRejectUnprintableURI(),\n        g.MiddlewareLogRequests(\"fingerprint\"),\n        g.MiddlewareRateLimiter(10, 30),\n        g.MiddlewareServerHeader(\"MyApp\"),\n        g.MiddlewareCORS(),\n        g.MiddlewareOPA(\"auth.rego\"),\n        g.MiddlewareLogDuration(true()))\n\n    router := chi.NewRouter()\n\n    // website with static files directory\n    ws := g.NewStaticWebServer(\"/var/www\")\n    router.With(ic.Set).With(jc.Set).Get(\"/\", ws.ServeFile(\"index.html\", \"text/html; charset=utf-8\"))\n    router.With(ic.Chk).Get(\"/assets/*\", ws.ServeAssets())\n\n    // Contact-form\n    cf := g.NewContactForm(\"/\")\n    router.Post(\"/\", cf.Notify(\"https://mattermost.com/hooks/qite178czotd5\"))\n\n    // API\n    router.With(jc.Vet).Post(\"/api/items\", myFunctionHandler)\n\n    handler := middleware.Then(router)\n    server := garcon.Server(handler, 8080, connState)\n    garcon.ListenAndServe(\u0026server)\n}\n```\n\n### 1. Run the [complete example](examples/complete/main.go)\n\n```sh\ncd garcon\ngo run -race ./examples/complete\n```\n\n```log\ngarcon ℹ️  Probing CPU. To visualize the profile: pprof -http=: cpu.pprof\n2022/09/14 18:43:05 profile: cpu profiling enabled, cpu.pprof\ngarcon 🎬  Version: devel\ngarcon ℹ️  Enable PProf endpoints: http://localhost:8093/debug/pprof\nincorr 🔒  DevMode accepts missing/invalid token from http://localhost:8080/myapp\nincorr 🔒  cookie myapp Domain=localhost Path=/myapp Max-Age=60 Secure=false SameSite=3 HttpOnly=true Value=0 bytes\ngarcon ℹ️  Prometheus export http://localhost:9093 namespace=myapp\ngarcon 🔒  CORS Allow origin prefixes: [http://localhost:8080 http://localhost: http://192.168.1.]\ngarcon 🔒  CORS Methods: [GET POST DELETE]\ngarcon 🔒  CORS Headers: [Origin Content-Type Authorization]\ngarcon 🔒  CORS Credentials=true MaxAge=86400\nincorr 🔒  Middleware Incorruptible.Set cookie \"myapp\" MaxAge=60 setIP=true\nincorr 🔒  Middleware Incorruptible.Set cookie \"myapp\" MaxAge=60 setIP=true\nincorr 🔒  Middleware Incorruptible.Chk cookie DevMode= true\nincorr 🔒  Middleware Incorruptible.Chk cookie DevMode= true\nincorr 🔒  Middleware Incorruptible.Chk cookie DevMode= true\nincorr 🔒  Middleware Incorruptible.Chk cookie DevMode= true\ngarcon ℹ️  Middleware WebForm redirects to http://localhost:8080/myapp\ngarcon ℹ️  empty URL =\u003e use the LogNotifier\nincorr 🔒  Middleware Incorruptible.Set cookie \"myapp\" MaxAge=60 setIP=true\nincorr 🔒  Middleware Incorruptible.Vet cookie/bearer DevMode= true\nincorr 🔒  Middleware Incorruptible.Vet cookie/bearer DevMode= true\nincorr 🔒  Middleware Incorruptible.Vet cookie/bearer DevMode= true\ngarcon ℹ️  MiddlewareLogDurationSafe: logs requester IP, sanitized URL and duration\ngarcon ℹ️  MiddlewareServerHeader sets the HTTP header Server=MyApp-devel in the responses\ngarcon ℹ️  MiddlewareRateLimiter burst=40 rate=2.67/s\ngarcon ℹ️  MiddlewareLogFingerprint: \n1. Accept-Language, the language preferred by the user. \n2. User-Agent, name and version of the browser and OS. \n3. R=Referer, the website from which the request originated. \n4. A=Accept, the content types the browser prefers. \n5. E=Accept-Encoding, the compression formats the browser supports. \n6. Connection, can be empty, \"keep-alive\" or \"close\". \n7. Cache-Control, how the browser is caching data. \n8. URI=Upgrade-Insecure-Requests, the browser can upgrade from HTTP to HTTPS. \n9. Via avoids request loops and identifies protocol capabilities. \n10. Authorization or Cookie (both should not be present at the same time). \n11. DNT (Do Not Track) is being dropped by web browsers.\ngarcon ℹ️  MiddlewareRejectUnprintableURI rejects URI having line breaks or unprintable characters\napp    🎬  -------------- Open http://localhost:8080/myapp --------------\ngarcon 📰  Server listening on http://localhost:8080\n```\n\n### 2. Embedded PProf server\n\nVisit the PProf server at \u003chttp://localhost:8093/debug/pprof\u003e providing the following endpoints:\n\n- \u003chttp://localhost:8093/debug/pprof/cmdline\u003e - Command line arguments\n- \u003chttp://localhost:8093/debug/pprof/profile\u003e - CPU profile\n- \u003chttp://localhost:8093/debug/pprof/allocs\u003e - Memory allocations from start\n- \u003chttp://localhost:8093/debug/pprof/heap\u003e - Current memory allocations\n- \u003chttp://localhost:8093/debug/pprof/trace\u003e - Current program trace\n- \u003chttp://localhost:8093/debug/pprof/goroutine\u003e - Traces of all current threads (goroutines)\n- \u003chttp://localhost:8093/debug/pprof/block\u003e - Traces of blocking threads\n- \u003chttp://localhost:8093/debug/pprof/mutex\u003e - Traces of threads with contended mutex\n- \u003chttp://localhost:8093/debug/pprof/threadcreate\u003e - Traces of threads creating a new thread\n\nPProf is easy to use with `curl` or `wget`:\n\n```sh\n( cd ~ ; go get -u github.com/google/pprof )\n\ncurl http://localhost:8093/debug/pprof/allocs \u003e allocs.pprof\npprof -http=: allocs.pprof\n\nwget http://localhost:8093/debug/pprof/heap\npprof -http=: heap\n\nwget http://localhost:8093/debug/pprof/goroutine\npprof -http=: goroutine\n```\n\nSee the [PProf post](https://go.dev/blog/pprof) (2013) for further explanations.\n\n### 3. Exporter server\n\nTo facilitate the prod management, metrics and health state are communicated.\nTools like Prometheus and Kubernetes collect every N seconds this information depending on the endpoint:\n\n- \u003chttp://localhost:9093/metrics\u003e to communicate internal metrics\n\n- \u003chttp://localhost:9093/health\u003e [liveness probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#define-a-liveness-http-request)\n  reponds `\"200 OK\"` if it is *healthy*.\n  Here *healthy* means the application is running\n  and can access to its dependencies (e.g. database),\n  the application does no need to be killed/restarted.\n  \n- \u003chttp://localhost:9093/ready\u003e [readiness probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#define-readiness-probes)\n  reponds `\"200 OK\"` if it is *ready to receive traffic*.\n  Here *ready to receive traffic* means the application is *healthy*,\n  the initialization phase is completed\n  and valid requests do not result in errors.\n\nKubernetes uses that readiness status to orchestrate the deployment.\nThe default update policy is to update one pod at a time:\nKubernetes waits for the new pod to be *ready to receive traffic* before updating the next one.\n\n### 4. Static website server\n\n:warning: **WARNING: This section is outdated!** :warning:\n\nThe [complete example](examples/complete/main.go) is running.\n\nOpen \u003chttp://localhost:8080/myapp\u003e with your browser, and play with the API endpoints.\n\nThe resources and API endpoints are protected with a HttpOnly cookie.\nThe [complete example](examples/complete/main.go) sets the cookie to browsers visiting the `index.html`.\n\n```go\nfunc handler(gw garcon.Writer, jc *jwtperm.Checker) http.Handler {\n    r := chi.NewRouter()\n\n    // Static website files\n    ws := garcon.WebServer{Dir: \"examples/www\", Writer: gw}\n    r.With(jc.SetCookie).Get(\"/\", ws.ServeFile(\"index.html\", \"text/html; charset=utf-8\"))\n    r.With(jc.SetCookie).Get(\"/favicon.ico\", ws.ServeFile(\"favicon.ico\", \"image/x-icon\"))\n    r.With(jc.ChkCookie).Get(\"/js/*\", ws.ServeDir(\"text/javascript; charset=utf-8\"))\n    r.With(jc.ChkCookie).Get(\"/css/*\", ws.ServeDir(\"text/css; charset=utf-8\"))\n    r.With(jc.ChkCookie).Get(\"/images/*\", ws.ServeImages())\n\n    // API\n    r.With(jc.ChkJWT).Get(\"/api/v1/items\", items)\n    r.With(jc.ChkJWT).Get(\"/api/v1/ducks\", gw.NotImplemented)\n\n    // Other endpoints\n    r.NotFound(gw.InvalidPath)\n\n    return r\n}\n```\n\n### 5. Enable Authentication\n\n:warning: **WARNING: This section is outdated!** :warning:\n\nRestart again the [complete example](examples/complete/main.go) with authentication enabled.\n\nAttention, in this example we use two redundant middleware pieces using the same JWT: `jwtperm` and `opa`.\nThis is just an example, don't be confused.\n\n```sh\ngo run -race ./examples/complete -auth\n```\n\n```log\n2021/12/02 08:09:47 Prometheus export http://localhost:9093\n2021/12/02 08:09:47 CORS: Set origin prefixes: [http://localhost:8080 http://localhost: http://192.168.1.]\n2021/12/02 08:09:47 CORS: Methods=[GET] Headers=[Origin Accept Content-Type Authorization Cookie] Credentials=true MaxAge=86400\n2021/12/02 08:09:47 JWT not required for dev. origins: [http://localhost:8080 http://localhost: http://192.168.1.]\n2021/12/02 08:09:47 Enable PProf endpoints: http://localhost:8093/debug/pprof\n2021/12/02 08:09:47 Create cookie plan=FreePlan domain=localhost secure=false jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ5ODd9.5tJk2NoHxkG0o_owtMleBcUaR8z1vRx4rxRRqtZUc_Q\n2021/12/02 08:09:47 Create cookie plan=PremiumPlan domain=localhost secure=false jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiUHJlbWl1bVBsYW4iLCJleHAiOjE2Njk5NjQ5ODd9.ifKhbmxQQ64NweL5aQDb_42tvKHwqiEKD-vxHO3KzsM\n2021/12/02 08:09:47 OPA: load \"examples/sample-auth.rego\"\n2021/12/02 08:09:47 Middleware OPA: map[sample-auth.rego:package auth\n\ndefault allow = false\ntokens := {\"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ0ODh9.elDm_t4vezVgEmS8UFFo_spLJTts7JWybzbyO_aYV3Y\"} { true }\nallow = true { __local0__ = input.token; data.auth.tokens[__local0__] }]\n2021/12/02 08:09:47 Middleware response HTTP header: Set Server MyBackendName-1.2.0\n2021/12/02 08:09:47 MiddlewareRateLimiter burst=100 rate=5/s\n2021/12/02 08:09:47 Middleware logger: requester IP and requested URL\n2021/12/02 08:09:47 Server listening on http://localhost:8080\n```\n\n### 6. Default HTTP request headers\n\nTest the API with `curl`:\n\n```sh\ncurl -D - http://localhost:8080/myapp/api/v1/items\n```\n\n```yaml\nHTTP/1.1 401 Unauthorized\nContent-Type: application/json\nServer: MyBackendName-1.2.0\nVary: Origin\nDate: Thu, 02 Dec 2021 07:06:20 GMT\nContent-Length: 84\n\n{\"error\":\"Unauthorized\",\n\"path\":\"/api/v1/items\",\n\"doc\":\"http://localhost:8080/myapp/doc\"}\n```\n\nThe corresponding garcon logs:\n\n```log\n2021/12/02 08:06:20 in  127.0.0.1:42888 GET /api/v1/items\n[cors] 2021/12/02 08:06:20 Handler: Actual request\n[cors] 2021/12/02 08:06:20   Actual request no headers added: missing origin\n2021/12/02 08:06:20 OPA unauthorize 127.0.0.1:42888 /api/v1/items\n2021/12/02 08:06:20 out 127.0.0.1:42888 GET /api/v1/items 1.426916ms c=1\n```\n\nThe CORS logs can be disabled by passing `debug=false` in `cors.Handler(origins, false)`.\n\nThe value `c=1` measures the web traffic (current active HTTP connections).\n\n### 7. With Authorization header\n\n```sh\ncurl -D - http://localhost:8080/myapp/api/v1/items -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ0ODh9.elDm_t4vezVgEmS8UFFo_spLJTts7JWybzbyO_aYV3Y'\n```\n\n```yaml\nHTTP/1.1 200 OK\nContent-Type: application/json\nServer: MyBackendName-1.2.0\nVary: Origin\nDate: Thu, 02 Dec 2021 07:10:37 GMT\nContent-Length: 25\n\n[\"item1\",\"item2\",\"item3\"]\n```\n\nThe corresponding garcon logs:\n\n```log\n2021/12/02 08:10:37 in  127.0.0.1:42892 GET /api/v1/items\n[cors] 2021/12/02 08:10:37 Handler: Actual request\n[cors] 2021/12/02 08:10:37   Actual request no headers added: missing origin\n2021/12/02 08:10:37 Authorization header has JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuIjoiRnJlZVBsYW4iLCJleHAiOjE2Njk5NjQ0ODh9.elDm_t4vezVgEmS8UFFo_spLJTts7JWybzbyO_aYV3Y\n2021/12/02 08:10:37 JWT Claims: {FreePlan  {  [] 2022-12-02 08:01:28 +0100 CET \u003cnil\u003e \u003cnil\u003e invalid cookie}}\n2021/12/02 08:10:37 JWT has the FreePlan Namespace\n2021/12/02 08:10:37 JWT Permission: {10}\n2021/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\n```\n\n## Low-level example\n\n:warning: **WARNING: This section is outdated!** :warning:\n\nSee the [low-level example](examples/low-level/main.go).\n\nThe following code is a bit different to the stuff done\nby the complete function `Garcon.Run()` presented in the previous chapter.\nThe following code is intended to show\nGarcon can be customized to meet your specific requirements.\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"net\"\n    \"net/http\"\n    \"time\"\n\n    \"github.com/go-chi/chi/v5\"\n    \"github.com/teal-finance/garcon\"\n)\n\n// Garcon settings\nconst apiDoc = \"https://my-dns.co/doc\"\nconst allowedProdOrigin = \"https://my-dns.co\"\nconst allowedDevOrigins = \"http://localhost: http://192.168.1.\"\nconst serverHeader = \"MyBackendName-1.2.0\"\nconst authCfg = \"examples/sample-auth.rego\"\nconst pprofPort = 8093\nconst expPort = 9093\nconst burst, reqMinute = 10, 30\nconst devMode = true\n\nfunc main() {\n    if devMode {\n        // the following line collects the CPU-profile and writes it in the file \"cpu.pprof\"\n        defer garcon.ProbeCPU().Stop()\n    }\n\n    garcon.StartPProfServer(pprofPort)\n\n    // Uniformize error responses with API doc\n    gw := garcon.NewWriter(apiDoc)\n\n    middleware, connState := setMiddlewares(gw)\n\n    // Handles both REST API and static web files\n    h := handler(gw)\n    h = middleware.Then(h)\n\n    runServer(h, connState)\n}\n\nfunc setMiddlewares(gw garcon.Writer) (middleware garcon.Chain, connState func(net.Conn, http.ConnState)) {\n // Start an exporter/health server in background if export port \u003e 0.\n // This server is for use with Kubernetes and Prometheus-like monitoring tools.\n    middleware, connState = garcon.StartExporter(expPort, devMode)\n\n    // Limit the input request rate per IP\n    reqLimiter := garcon.NewReqLimiter(gw, burst, reqMinute, devMode)\n\n    corsConfig := allowedProdOrigin\n    if devMode {\n        corsConfig += \" \" + allowedDevOrigins\n    }\n\n    allowedOrigins := garcon.SplitClean(corsConfig)\n\n    middleware = middleware.Append(\n        reqLimiter.Limit,\n        garcon.ServerHeader(serverHeader),\n        cors.Handler(allowedOrigins, devMode),\n    )\n\n    // Endpoint authentication rules (Open Policy Agent)\n    files := garcon.SplitClean(authCfg)\n    policy, err := garcon.NewPolicy(gw, files)\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    if policy.Ready() {\n        middleware = middleware.Append(policy.Auth)\n    }\n\n    return middleware, connState\n}\n\n// runServer runs in foreground the main server.\nfunc runServer(h http.Handler, connState func(net.Conn, http.ConnState)) {\n    const mainPort = \"8080\"\n\n    server := http.Server{\n        Addr:              \":\" + mainPort,\n        Handler:           h,\n        TLSConfig:         nil,\n        ReadTimeout:       1 * time.Second,\n        ReadHeaderTimeout: 1 * time.Second,\n        WriteTimeout:      1 * time.Second,\n        IdleTimeout:       1 * time.Second,\n        MaxHeaderBytes:    222,\n        TLSNextProto:      nil,\n        ConnState:         connState,\n        ErrorLog:          log.Default(),\n        BaseContext:       nil,\n        ConnContext:       nil,\n    }\n\n    log.Print(\"Server listening on http://localhost\", server.Addr)\n\n    log.Fatal(server.ListenAndServe())\n}\n\n// handler creates the mapping between the endpoints and the handler functions.\nfunc handler(gw garcon.Writer) http.Handler {\n    r := chi.NewRouter()\n\n    // Website with static files\n    ws := g.NewStaticWebServer(\"/var/www\")\n    r.Get(\"/\", ws.ServeFile(\"index.html\", \"text/html; charset=utf-8\"))\n    r.Get(\"/js/*\", ws.ServeDir(\"text/javascript; charset=utf-8\"))\n    r.Get(\"/css/*\", ws.ServeDir(\"text/css; charset=utf-8\"))\n    r.Get(\"/images/*\", ws.ServeImages())\n\n    // API\n    r.Get(\"/api/v1/items\", items)\n    r.Get(\"/api/v1/ducks\", gw.NotImplemented)\n\n    // Other endpoints\n    r.NotFound(gw.InvalidPath)\n\n    return r\n}\n\nfunc items(w http.ResponseWriter, _ *http.Request) {\n    w.Header().Set(\"Content-Type\", \"application/json\")\n    w.Write([]byte(`[\"item1\",\"item2\",\"item3\"]`))\n}\n```\n\n## KeyStore example\n\nThe example KeyStore implements a key/value datastore\nproviding private storage for each client identified by its unique IP.\n\n```sh\ncd garcon\ngo run -race ./examples/keystore\n```\n\nThen open \u003chttp://localhost:8080\u003e to learn more about the implemented features.\n\n## ✨ Contributions Welcome\n\nThis project needs your help to become better.\nPlease propose your enhancements,\nor even a further refactoring.\n\nWe welcome contributions in many forms,\nand there's always plenty to do!\n\n## 🗣️ Feedback\n\nIf you have some suggestions, or need a new feature,\nplease contact us, using the\n[issues](https://github.com/teal-finance/garcon/issues),\nor at Teal.Finance@pm.me or\n[@TealFinance](https://twitter.com/TealFinance).\n\nFeel free to propose a\n[Pull Request](https://github.com/teal-finance/garcon/pulls),\nyour contributions are welcome. :wink:\n\n## 🗽 Copyright and license\n\nCopyright (c) 2021 Teal.Finance/Garcon contributors\n\nTeal.Finance/Garcon is free software, and can be redistributed\nand/or modified under the terms of the MIT License.\nSPDX-License-Identifier: MIT\n\nTeal.Finance/Garcon is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty\nof MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nSee the [LICENSE](LICENSE) file (alongside the source files)\nor \u003chttps://opensource.org/licenses/MIT\u003e.\n\n## See also\n\n- \u003chttps://github.com/kambahr/go-webstandard\u003e\n- \u003chttps://github.com/go-aah/aah\u003e\n- \u003chttps://github.com/xyproto/algernon\u003e\n- \u003chttps://github.com/kataras/iris\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteal-finance%2Fgarcon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteal-finance%2Fgarcon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteal-finance%2Fgarcon/lists"}