{"id":37197552,"url":"https://github.com/ixtendio/gofre","last_synced_at":"2026-01-14T22:57:45.004Z","repository":{"id":62866107,"uuid":"527301169","full_name":"ixtendio/gofre","owner":"ixtendio","description":"A fast and low memory consumption web framework for Go with middleware support and without third-party dependencies","archived":false,"fork":false,"pushed_at":"2023-01-10T20:19:13.000Z","size":5761,"stargazers_count":15,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-01T12:45:30.237Z","etag":null,"topics":["authorization","cors","csrf-protection","framework","go","go-template","golang","json","middleware","mux-router","oauth2","rbac","server-side-events","sse"],"latest_commit_sha":null,"homepage":"","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/ixtendio.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":null,"support":null}},"created_at":"2022-08-21T19:03:55.000Z","updated_at":"2025-07-26T13:18:48.000Z","dependencies_parsed_at":"2023-02-08T20:01:10.087Z","dependency_job_id":null,"html_url":"https://github.com/ixtendio/gofre","commit_stats":null,"previous_names":["ixtendio/gow"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/ixtendio/gofre","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixtendio%2Fgofre","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixtendio%2Fgofre/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixtendio%2Fgofre/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixtendio%2Fgofre/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ixtendio","download_url":"https://codeload.github.com/ixtendio/gofre/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ixtendio%2Fgofre/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28437582,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T22:37:52.437Z","status":"ssl_error","status_checked_at":"2026-01-14T22:37:31.496Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["authorization","cors","csrf-protection","framework","go","go-template","golang","json","middleware","mux-router","oauth2","rbac","server-side-events","sse"],"created_at":"2026-01-14T22:57:44.273Z","updated_at":"2026-01-14T22:57:44.959Z","avatar_url":"https://github.com/ixtendio.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eGOFre - A Sweet Web Framework for Go\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n\u003cimg alt=\"GOFre\" src=\"docs/img/logo.png\" /\u003e\n\u003c/p\u003e\n\n_GOFree[^1]_ is a fast and low memory consumption web framework for Go, without third-party dependencies, that makes the development of web\napplications a joy.  _GOFre_ integrates with `http.Server` and supports the standard Go HTTP handlers: `http.Handler`\nand `http.HandlerFunc`.\n\nThis framework was developed around simplicity of usage, extensibility and low memory consumption and offers the\nfollowing features:\n\n* **Path pattern matching** - including regex, path variable extraction and validation\n* **Middleware** - pre and post request interceptors\n* **Templating** - including static resources\n* **Authentication** - OAUTH2 flow included for GitHub and Google\n* **Authorization** - RBAC implementation\n* **SSE (Server Sent-Events)**\n* **Security** - CSRF Middleware protection\n\n## Installation\n\nYou can install this repo with `go get`:\n\n```sh\ngo get github.com/ixtendio/gofre\n```\n\n## Usage\n\n```go\ngofreMux, err := gofre.NewMuxHandlerWithDefaultConfig()\nif err != nil {\n    log.Fatalf(\"Failed to create GOFre mux handler, err: %v\", err)\n}\n\n// JSON with vars path\ngofreMux.HandleGet(\"/hello/{firstName}/{lastName}\", func(ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n    return response.JsonHttpResponseOK(map[string]string{\n        \"firstName\": mc.PathVar(\"firstName\"),\n        \"lastName\":   mc.PathVar(\"lastName\"),\n    }), nil\n})\n\nhttpServer := http.Server{\n    Addr:              \":8080\",\n    Handler:           gofreMux,\n}\nif err := httpServer.ListenAndServe(); err != nil {\n    log.Fatalf(\"Failed starting the HTTP server, err: %v\", err)\n}\n```\n\nTo see the response, execute:\n\n```shell\ncurl -vvv \"https://localhost:8080/hello/John/Doe\"\n```\n\n## Architecture Overview\n\n_GOFre_ has the following components:\n\n* **MatchingContext** - an object that encapsulates the initial `http.Request` and the path variables, if exists\n* **HttpResponse** - an object that encapsulates the response and knows how to write it back to the client\n* **Handler** - a function that receives a `Context` and an `HttpRequest` and returns an `HttpResponse` or an `error`\n* **Middleware** - a function that receives a `Handler` and returns another `Handler`\n* **Router** - an object that knows how to parse the `http.Request` and to route it to the corresponding `Handler`\n\n![Architecture](docs/img/gofre-architecture.png)\n\n### Path Pattern Matching\n\n_GOFre_ supports a complex path matching where the most specific pattern is chosen first.\n\nSupported path patterns matching:\n\n1. **exact matching**  - `/a/b/c`\n2. **capture variable without constraints**\n    1. `/a/{b}/{c}`\n        1. `/a/john/doe` =\u003e b: john, c: doe\n3. **capture variable with constraints**\n    1. `/a/{uuid:^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$}` - UUID\n       matching\n        1. `/a/zyw3040f-0f1c-4e98-b71c-d3cd61213f90` =\u003e false (z, x and w are not part of UUID regex)\n        2. `/a/fbd3040f-0f1c-4e98-b71c-d3cd61213f90` =\u003e true\n    2. `/a/{number:^[0-9]{3}$}` - number with 3 digits\n        1. `/a/12` =\u003e false\n        1. `/a/123` =\u003e true\n        1. `/a/012` =\u003e true\n        1. `/a/0124` =\u003e false\n4. **literal match regex**\n    1. **\u0026ast;** - matches any number of characters or a single segment path\n        1. `/a/abc*hij`\n            1. `/a/abcdhij` =\u003e true\n            2. `/a/abcdefghij` =\u003e true 3`/a/abcdefgij` =\u003e false (the path doesn't end with `hij`)\n        2. `/a/*/c`\n            1. `/a/b/c` =\u003e true\n            2. `/a/b/c/c` =\u003e false (a maximum of 3 path segments is allowed and we have 4)\n        3. `/a/abc*hij/*`\n            1. `/a/abcdefghij/abc` =\u003e true\n            2. `/a/abcdefghij/abc/xyz` =\u003e false (`*` matches a single path segment and we have 2 `abc/xyz`)\n    2. **?** - matches a single character\n        1. `/a/abc?hij`\n            1. `/a/abcdhij` =\u003e true\n            2. `/a/abcdehij` =\u003e false (the character `e` will not match)\n5. **greedy match**\n    1. **\u0026ast;\u0026ast;** - matches multiple path segments\n        1. `/a/**/z`\n            1. `/a/b/c/d/e/f/z` =\u003e true\n            1. `/a/b/c/d/e/f` =\u003e false (the path should end in `/z`)\n        2. `/a/**`\n            1. `/a/b/c/d/e/f` =\u003e true\n\nCompared to other libraries, _GOFre_ does not require you to declare the path patterns in a specific order so that the\nmatch can work as you expect. Because of the **greedy match**, which introduced complexity in path matching algorithm,\nin order to sort the patterns from the most specific to most generic, maximum 19 request path segments are accepted. (\nfor example, www.domain.com/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/)\n\nFor example, these path matching patterns (assuming we handle only GET requests) can be declared in any order in your\ncode:\n\n1. `/users/john/{lastName}`\n2. `/users/john/doe`\n3. `/users/*/doe`\n4. `/users/**`\n\nHere are some URL's example with their matched pattern:\n\n* `https://www.website.com/users/john/doe` - the second pattern will match\n* `https://www.website.com/users/john/wick` - the first pattern will match, where the lastName will be `wick`\n* `https://www.website.com/users/jane/doe` - the third pattern will match\n* `https://www.website.com/users/john/jane/doe` - the forth pattern will match\n\n_GOFre_ also includes support for greedy path matching: `**`\n\n* `/users/**/doe` - matches any path that starts with `/users` and ends with `/doe`\n* `/users/**` - matches any path that starts with `/users`\n\nThe path matching can be **case-sensitive** (default) or **case-insensitive**.\n\nIf two path patterns of the same type that match the same URL are registered, then the framework will panic. For\nexample:\n\n* `/a/{b}`\n* `/a/{d}`\n\nMoreover, the following two patterns are accepted by the framework although the second one will never be executed. (This\nis a limitation of the path matching that might be solved in future releases.)\n\n* `/a/{b}`\n* `/a/*`\n\n### Middlewares\n\nA _middleware_ is a function that intercepts a request. The function receives a _Handler_ as an argument and returns\nanother _Handler_.\n\nThere are two ways to register the middlewares:\n\n* **common registration** - applied to all handlers\n* **per handler registration** - applied for a single handler only\n\nExample:\n\n```go \ngofreMux.CommonMiddlewares(func(handler handler.Handler) handler.Handler {\n  return func(ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n      log.Println(\"Common middleware 1 - before processing the request\")\n      resp, err := handler(ctx, mc)\n      log.Println(\"Common middleware 1 - after processing the request\")\n      return resp, err\n  }\n}, func(handler handler.Handler) handler.Handler {\n  return func(ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n      log.Println(\"Common middleware 2 - before processing the request\")\n      resp, err := handler(ctx, mc)\n      log.Println(\"Common middleware 2 - after processing the request\")\n      return resp, err\n  }\n})\n\ngofreMux.HandleGet(\"/handlers\", func(ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n  log.Println(\"Request handling\")\n  return response.PlainTextHttpResponseOK(\"ok\"), nil\n}, func(handler handler.Handler) handler.Handler {\n  return func(ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n      log.Println(\"Custom middleware 1 - before processing the request\")\n      resp, err := handler(ctx, mc)\n      log.Println(\"Custom middleware 1 - after processing the request\")\n      return resp, err\n  }\n}, func(handler handler.Handler) handler.Handler {\n  return func(ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n      log.Println(\"Custom middleware 2 - before processing the request\")\n      resp, err := handler(ctx, mc)\n      log.Println(\"Custom middleware 2 - after processing the request\")\n      return resp, err\n  }\n})\n```\n\nIf we execute `curl -vvv \"https://localhost:8080/handlers\"`, we should see the following lines in the console:\n\n```text\nCommon middleware 1 - before processing the request\nCommon middleware 2 - before processing the request\nCustom middleware 1 - before processing the request\nCustom middleware 2 - before processing the request\nRequest handling\nCustom middleware 2 - after processing the request\nCustom middleware 1 - after processing the request\nCommon middleware 2 - after processing the request\nCommon middleware 1 - after processing the request\n```\n\nThe _middleware_ package includes the following implementations:\n\n* **PanicRecover** - handles the panic and convert it to an error\n* **ErrResponse** - converts an error to an HTTP answer\n* **CSRFPrevention** - provides basic CSRF protection for a web application\n* **Cors** - enable client-side cross-origin requests by implementing W3C's CORS\n* **CompressResponse** - enable compression for HTTP response as long as the client accept it\n* **AuthorizeAll**, **AuthorizeAny** - provides basic RBAC authorization (authentication is required in this case)\n* **SecurityPrincipalSupplier** - provides an `auth.SecurityPrincipal` supplier callback\n* **RequestDumper** - dumps the request (before processing) and the corresponding response in JSON format\n\n### Data Sharing Between Middlewares\n\nA middleware can share data with the next one in the chain using the request **context.Context**. The context has two\npurposes:\n\n1. to notify when the client close the TCP connection or when some request timeouts occurred\n2. to share key-value data\n\nLooking at this example:\n\n```go\ngofreMux.HandleGet(\"/security/authorize/{permission}\", func (ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n        return response.JsonHttpResponseOK(map[string]string{\"authorized\": \"true\"}), nil\n    }, func (handler handler.Handler) handler.Handler {\n        // authentication middleware\n        return func (ctx context.Context, mc path.MatchingContext) (resp response.HttpResponse, err error) {\n            permission, err := auth.ParsePermission(\"domain/subdomain/resource:\" + mc.PathVar(\"permission\"))\n            if err != nil {\n                return nil, err\n            }\n            ctx = context.WithValue(ctx, auth.SecurityPrincipalCtxKey, auth.User{\n                Groups: []auth.Group{{\n                    Roles: []auth.Role{{\n                        AllowedPermissions: []auth.Permission{permission},\n                    }},\n                }},\n            })\n            return handler(ctx, mc)\n        }\n    }, middleware.AuthorizeAll(auth.Permission{Scope: \"domain/subdomain/resource\", Access: auth.AccessDelete}))\n```\n\nwe see how the authentication middleware wraps the authenticated user in the context using `context.WithValue` so that\nthe next middleware, in our case **AuthorizeAll**, can use it.\n\n## Sub-Routing\n\nIn some cases it might be necessary to create a shallow clone of a mux handler. To do this the following two methods can\nbe used:\n\n1. `Clone` - creates a new MuxHandler that will inherit all the settings from the parent\n2. `RouteUsingPathPrefix` - creates a new MuxHandler that will inherit all the settings from the parent, excepting the\n   path prefix which will be concatenated to the parent path prefix\n\nAn important aspect to these methods is that, the new common middlewares added to the new `MuxHandler` will not be\nshared with the parent.\n\n### Use-Cases for `Clone`\n\n```go\ngofreMux = gofre.NewMuxHandlerWithDefaultConfig()\ngofreMux.Clone().HandleGet(\"/health\", healthHandler)\n// the common middlewares will not be applied to the /health endpoint\ngofreMux.CommonMiddlewares(...)\ngofreMux.HandleGet(\"/api1\", api1Handler)\ngofreMux.HandleGet(\"/api2\", api2Handler)\n```\n\n### Use-Cases for `RouteUsingPathPrefix`\n\n```go\ngofreMux = gofre.NewMuxHandlerWithDefaultConfig()\ngofreMux.CommonMiddlewares(...)\n\n// we create a usersMux that will handle all the request with path prefix /users (examples: GET:/users, GET:/users/{userId}, POST:/users/{userId})\nusersMux = gofreMux.RouteUsingPathPrefix(\"/users\")\nusersMux.CommonMiddlewares(...)\nusersMux.HandlePost(\"/{userId}\", createUserHandler)\nusersMux.HandleGet(\"/{userId}\", getUserHandler)\nusersMux.HandleGet(\"\", getAllUsersHandler)\n```\n\n## Templating and Static Resources\n\n_GOFre_ can be configured to serve GO HTML templates and static resources. This can be done through a configuration\nobject passed at instantiation:\n\n```go\ngofreConfig := \u0026gofre.Config{\n        CaseInsensitivePathMatch: false,\n        ContextPath:              \"/\",\n        ResourcesConfig: \u0026gofre.ResourcesConfig{\n        TemplatesPathPattern: \"examples/resources/templates/*.html\",\n        AssetsDirPath:        \"./examples/resources/assets\",\n        AssetsMappingPath:    \"assets\",\n    },\n    ErrLogFunc: func (err error) {\n        log.Printf(\"An error occurred: %v\", err)\n    },\n}\n```\n\nBy default `ResourcesConfig` is nil, meaning that the framework will not support templating or static resources.\n\nYou can customize the template path pattern, the assets dir path and the assets mapping path if you want. If not, then\nthe default values will be applied. For example:\n\n```go\nresourcesConfig := gofre.NewDefaultResourcesConfig()\n```\n\nis equivalent to:\n\n```go\nresourcesConfig := \u0026gofre.ResourcesConfig{\n    TemplatesPathPattern: \"resources/templates/*.html\",\n    AssetsDirPath:        \"./resources/assets\",\n    AssetsMappingPath:    \"assets\",\n    Template:             *template.Template\n}\n```\n\nAn endpoint that returns an HTML template can be specified in this way:\n\n```go\ngofreMux.HandleGet(\"/\", func (ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n    templateData := struct{}{}\n    return response.TemplateHttpResponseOK(gofreMux.ExecutableTemplate(), \"index.html\", templateData), nil\n})\n```\n\nIn case you want to use only static resources, without templating then, you can use `response.NilTemplate` as follows:\n\n```go\nresourcesConfig := \u0026gofre.ResourcesConfig{\n    Template: response.NilTemplate{}\n}\n```\n\nThe rest of the config fields will be initialized with the default values at MuxHandler creation.\n\n## Authorization\n\n_GOFre_ provides an RBAC implementation for user authorization. The following objects are available:\n\n* **auth.SecurityPrincipal** - represents any managed identity that is requesting access to a resource (a user, a\n  service principal, etc.)\n* **auth.Permission** - a permission has:\n    * **Scope** - describes where an action can be performed. A scope might have a maximum of 3 levels (domain,\n      subdomain and resource) separated by a separator (default `/`). The levels can be specific or generic: **\u0026ast;**.\n      Scopes should be structured in a parent-child relationship. Each level of the hierarchy makes the scope more\n      specific.\n    * **Access** - specifies what actions can be applied to a resource like: view, create, update, delete, etc.\n* **Role** - a collection of allowed and denied permissions. The denied permissions check has a higher priority than the\n  allowed one.\n* **User** - implements `auth.SecurityPrincipal` and represents an authenticated person.\n\nFor example, a user with this permission:\n\n```go\nauth.Permission{\n    Scope: \"admin/timesheet/team1\",\n    Access: auth.AccessCreate | auth.AccessApprove\n}\n```\n\nwill be allowed to create and approve any timesheet for team1 using the admin dashboard\n\nwhile this permission:\n\n```go\nauth.Permission{\n    Scope: \"admin/timesheet/*\",\n    Access: auth.AccessCreate | auth.AccessApprove\n}\n```\n\ngives access to create and approve any timesheet for any team using the admin dashboard.\n\nThe definition of the permissions scopes is application-specific.\n\n## Authentication\n\nThe authorization works as long as an **auth.SecurityPrincipal** exists on the request **context.Context**.\n\nFor user authentication, the framework provides the OAUTH2 flow integration with:\n\n* **GitHub**\n* **Google**\n\nThe authenticated user roles are out of this scope.\n\nThe following code enables the OAUTH2 flow:\n\n```go\n// OAUTH2 flow with user details extraction\ngofreMux.HandleOAUTH2(oauth.Config{\n    WebsiteUrl:       \"https://www.domain.com\",\n    FetchUserDetails: true,\n    Providers: []oauth.Provider{\n        oauth.GitHubProvider{\n        ClientId:     os.Getenv(\"GITHUB_OAUTH_CLIENT_ID\"),\n        ClientSecret: os.Getenv(\"GITHUB_OAUTH_CLIENT_SECRET\"),\n    },\n    oauth.GoogleProvider{\n        ClientId:     os.Getenv(\"GOOGLE_OAUTH_CLIENT_ID\"),\n        ClientSecret: os.Getenv(\"GOOGLE_OAUTH_CLIENT_SECRET\"),\n        Scopes:       []string{\"openid\"},\n    }},\n    CacheConfig: oauth.CacheConfig{\n        Cache:             cache.NewInMemory(),\n        KeyExpirationTime: 1 * time.Minute,\n    },\n}, func (ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n    accessToken := oauth.GetAccessTokenFromContext(ctx)\n    securityPrincipal := auth.GetSecurityPrincipalFromContext(ctx)\n    //todo here you have to enrich the securityPrincipal with the roles from the database and to add it again on the context\n})\n```\n\n### Custom Authentication\n\n_GOFre_ provides a middleware: `middleware.SecurityPrincipalSupplier` that allows you to load a\ncustom `auth.SecurityPrincipal`. The returned security principal will be added on the `context.Context` to be shared\nwith the next middlewares.\n\n```go\nmiddleware.SecurityPrincipalSupplier(func (ctx context.Context, mc path.MatchingContext) (auth.SecurityPrincipal, error) {\n    sp, err := //load the SecurityPrincipal from JWT token, Database, Cookies, etc\n    return sp, err\n})\n```\n\n\u003e **Note**\n\u003e\n\u003e This example uses a cache in memory which works as long as you have a single server running, or if you use sticky session on your Load Balancer, in case of multiple running servers.\n\n## SSE (Server Sent-Events)\n\nThe [Server Sent-Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) is a web technology over\nHTTP2 (supported also by HTTP1 with limitations) that makes it possible for a server to send new data to a web page at\nany time by pushing messages. The difference between SSE and Web-Sockets are:\n\n* SSE is unidirectional (server to client) while web-sockets is bidirectional\n* SSE supports only text data while web-sockets supports binary data\n* all popular browsers natively support SSE, including automatic reconnection when the connection is lost\n\nExample of pushing a new message per second to the client:\n\n```go\ngofreMux, _ := gofre.NewMuxHandlerWithDefaultConfig()\n\ngofreMux.HandleGet(\"/sse\", func (ctx context.Context, mc path.MatchingContext) (response.HttpResponse, error) {\n    return response.SSEHttpResponse(func (ctx context.Context, lastEventId string) \u003c-chan response.ServerSentEvent {\n        ch := make(chan response.ServerSentEvent)\n        go func () {\n            ticker := time.NewTicker(1 * time.Second)\n            defer ticker.Stop()\n            defer close(ch)\n            var id int\n            for {\n                select {\n                    case \u003c-ctx.Done():\n                        return\n                    case \u003c-ticker.C:\n                        ch \u003c- response.ServerSentEvent{\n                            Name:  \"message\",\n                            Id:    \"msg_\" + strconv.Itoa(id),\n                            Data:  []string{\"message \" + strconv.Itoa(id)},\n                            Retry: 0,\n                        }\n                        id++\n                }\n            }\n        }()\n    \n        return ch\n    }), nil\n})\n\nhttpServer := http.Server{\n    Addr:              \":8080\",\n    Handler:           gofreMux,\n    WriteTimeout:      5 * time.Minute, //this long timeout it's necessary for SSE\n}\n\nif err := httpServer.ListenAndServeTLS(\"./examples/certs/key.crt\", \"./examples/certs/key.key\"); err != nil {\n    log.Fatalf(\"Failed to start the server, err: %v\", err)\n}\n```\n\nThe HTTP server `WriteTimeout` should be big enough to avoid client reconnection. Anyway, major popular browsers support\nautomatic reconnection.\n\u003e **Note**\n\u003e\n\u003e SSE works only over TLS\n\n# Performance\n\nThe framework was tested using the following use-cases:\n1. serving static resources\n2. serving URL's with path variables\n3. serving concurrent requests\n\nand these are the results:\n\n```text\ngoos: darwin\ncpu: Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz\nBenchmark_GofreStatic-8                  \t 3324294\t       353.6 ns/op\t      64 B/op\t       1 allocs/op\nBenchmark_GofreVarCapture-8              \t 2742657\t       438.4 ns/op\t      64 B/op\t       1 allocs/op\nBenchmark_GofreVarCapture_Concurrent-8   \t 5540001\t       207.5 ns/op\t      70 B/op\t       1 allocs/op\n```\n\n## Performance comparison with other frameworks\n\n![Performance - Path Capture Variables (multi-thread)](docs/img/performance-path-capture-variables-multi-thread.png)\n![Performance - Path Capture Variables (single-thread)](docs/img/performance-path-capture-variables-single-thread.png)\n![Performance - Static Resources (single-thread)](docs/img/performance-static-resources-single-thread.png)\n\nThe [benchmark](https://github.com/ixtendio/go-web-frameworks-benchmark) was executed on `MacOS Intel(R) Core(TM) i7-4980HQ CPU @ 2.80GHz`\n\n# Run the Examples\n\nA list with all examples can be found in the **examples** folder. To start the local server, execute:\n\n1. `cd examples`\n2. build and start the web server\n    1. For MacOS `make run-osx`\n    2. For Linux `make run`\n\nIn the browser, open the following URL: `https://locahost:8080`\n\n\u003e **Note**\n\u003e\n\u003e To run the OAUTH2 flows you need to create the OAUTH apps on **GitHub** \u0026 **Google** and to expose the _clientId_ and the _clientSecret_ as environment variables:\n\u003e\n\u003e **GitHub**: `GITHUB_OAUTH_CLIENT_ID` and `GITHUB_OAUTH_CLIENT_SECRET`\n\u003e\n\u003e **Google**: `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET`\n\n[^1]: Gofri (singular: **gofre**) are waffles in Italy and can be found in the Piedmontese cuisine: they are light and\ncrispy in texture.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fixtendio%2Fgofre","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fixtendio%2Fgofre","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fixtendio%2Fgofre/lists"}