{"id":16686677,"url":"https://github.com/prasannavl/mchain","last_synced_at":"2025-04-10T00:16:35.463Z","repository":{"id":139663715,"uuid":"99208040","full_name":"prasannavl/mchain","owner":"prasannavl","description":"A super tiny go package that handles middleware chaining in it's most minimal form","archived":false,"fork":false,"pushed_at":"2019-10-11T12:15:28.000Z","size":54,"stargazers_count":28,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-24T02:02:58.613Z","etag":null,"topics":["chaining","golang","http","middleware"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/prasannavl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-03T08:12:23.000Z","updated_at":"2021-09-14T16:59:59.000Z","dependencies_parsed_at":"2023-07-23T09:15:13.361Z","dependency_job_id":null,"html_url":"https://github.com/prasannavl/mchain","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prasannavl%2Fmchain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prasannavl%2Fmchain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prasannavl%2Fmchain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/prasannavl%2Fmchain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/prasannavl","download_url":"https://codeload.github.com/prasannavl/mchain/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248131315,"owners_count":21052819,"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":["chaining","golang","http","middleware"],"created_at":"2024-10-12T15:06:34.668Z","updated_at":"2025-04-10T00:16:35.450Z","avatar_url":"https://github.com/prasannavl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mchain\n\nA super tiny go package that handles middleware chaining in it's most minimal form. \n\n**Documentation:** `Read the source, Luke` - it's tiny.  \n(Start with `mchain.go` and then `builder.go` - That should conceptually be everything)\n\n## Get\n\n`go get -u github.com/prasannavl/mchain`\n\n(See `Related` section below of libraries as real-life examples)\n\n## Standard middlewares\n\n```go\ntype HttpMiddleware func(http.Handler) http.Handler\n\ntype HttpChain struct {\n\tMiddlewares []HttpMiddleware\n}\n```\n\nThat's about it. It's even simpler than the very neat `alice` package. However, the HttpChain provides no `Append`, `Extend` like methods. They are cleanly separated into a builder - `HttpChainBuilder`, that provides all the composition. So, now the `Middlewares` field is public, and `HttpChain` can be transparently passed around, cloned, extended at will just using slicing primitives.\n\n## mchain middlewares\n\nThe standard middleware pattern looks fine, however proves very difficult to chain error handling cleanly. So `mchain` provides this as the alternative middleware:\n\n```go\ntype Middleware func(Handler) Handler\n\ntype Chain struct {\n\tMiddlewares []Middleware\n}\n\ntype Handler interface {\n\tServeHTTP(http.ResponseWriter, *http.Request) error\n}\n\ntype HandlerFunc func(http.ResponseWriter, *http.Request) error\n\nfunc (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {\n\treturn f(w, r)\n}\n```\n\nActually, that's almost the entire `mchain.go` file - along with some std helpers for the same. Very simple. This allows clean error handling. Errors can be used to communicate error status code as well. A pattern that can be easily achieved with `HttpError` from simple error composers like [`prasannavl/goerror`](https://www.github.com/prasannavl/goerror)\n\nThis aligns with Go's idiomatic way of error handling.\n\n```go\nerr := h.ServeHTTP(w, r)\nif err != nil {\n// handle error\n}\n```\n\nI personally think somewhere on it's way - the standard library team got stuck in the choice between simplicity and consistency - and they seem to have chosen the former. And now it's stuck - you can't just go back and change the standard way even if the other is deemed better.\n\nBut thankfully, you don't have to choose. You can combine both :)\n\n`mchain` brings this pattern with almost no overhead. And it has a set of conversation functions that provide two way conversions between the standard `net/http` package, and `mchain`, like `FromHttp` and `ToHttp` in the `mconv` sub-package for middlewares, `FromHttp`, and `ToHttp` in the `hconv` for handlers - that allows both to coexist, and mix and match both types of handlers.\n\n\n### Pure Middleware\n\n```go\nfunc RequestDurationHandler(next mchain.Handler) mchain.Handler {\n\tf := func(w http.ResponseWriter, r *http.Request) error {\n\t\tc := reqcontext.FromRequest(r)\n\t\tc.StartTime = time.Now()\n\t\terr := next.ServeHTTP(w, r)\n\t\tc.EndTime = time.Now()\n\t\treturn err\n\t}\n\treturn mchain.HandlerFunc(f)\n}\n```\n\nWhen you want purity, you can use that. But that's too much boilerplate for everyday use. So, moving on to a helper.\n\n### Simple Middleware\n\n```go\nfunc(w http.ResponseWriter, r *http.Request, next *Handler) error {\n\t\tc := reqcontext.FromRequest(r)\n\t\tc.StartTime = time.Now()\n\t\terr := next.ServeHTTP(w, r)\n\t\tc.EndTime = time.Now()\n\t\treturn err\n}\n```\n\nIf you've used Negroni - you'll recognize that instantly. This is called using the helper `FromSimple` in the `mconv` sub-package that simply converts this pattern into the pure form. Infact, this is also provided for pure http middleware (`HttpFromSimple` in `hconv`), so you can make it similar to negroni middleware.\n\nIf you however, don't like this, there's no need to use this. This is nothing more than a simple type alias.\n\n\n### Example\n\n```go\n\nfunc newAppHandler(host string) http.Handler {\n\tc := appcontext.AppContext{Services: appcontext.Services{}}\n\n\treturn builder.Create(\n\t\t// An existing http handler based middleware\n\t\tmconv.FromHttp(c.HandlerWithContext, nil),\n\t\tmiddleware.RequestContextInitHandler,\n\t\tmiddleware.RequestLogHandler,\n\t\tmiddleware.RequestDurationHandler,\n\t).\n\tHandler(hconv.FromHttp(CreateActionHandler(host))).\n\tBuildHttp(nil)\n}\n\nfunc newHttpAppHandler(host string) http.Handler {\n\tc := appcontext.AppContext{Services: appcontext.Services{}}\n\n\treturn builder.CreateHttp(\n\t\tc.HandlerWithContext,\n\t\tstandardmiddleware.RequestContextInitHandler,\n\t\tstandardmiddleware.RequestLogHandler,\n\t\tstandardmiddleware.RequestDurationHandler,\n\t).\n\tHandler(CreateActionHandler(host)).\n\tBuild()\n}\n\nfunc CreateActionHandler(host string) http.Handler {\n\tf := func(w http.ResponseWriter, r *http.Request) {\n\t\tdata := struct {\n\t\t\tMessage string\n\t\t\tDate    time.Time\n\t\t}{\n\t\t\tfmt.Sprintf(\"Hello world from %s\", host),\n\t\t\ttime.Now(),\n\t\t}\n\t\trender.JSON(w, r, \u0026data)\n\t}\n\treturn http.HandlerFunc(f)\n}\n\n```\n\n## Why return errors along with the handler?\n\nSee `fileserver` in the related section for a real-life example.\nConsider a similar middleware setup to above example,\n\nWith `net/http` middleware chain:\n\n```go\nfunc RequestIDMustInitHandler(next http.Handler) http.Handler {\n\tf := func(w http.ResponseWriter, r *http.Request) {\n\t\tc := FromRequest(r)\n\t\tif _, ok := r.Header[RequestIDHeaderKey]; ok {\n\t\t\thttp.Error(w, fmt.Sprintf(\"error: illegal header (%s)\", RequestIDHeaderKey), 400)\n\t\t\treturn\n\t\t}\n\t\tvar uid uuid.UUID\n\t\tmustNewUUID(\u0026uid)\n\t\tc.RequestID = uid\n\t\tnext.ServeHTTP(w, r)\n\t}\n\treturn http.HandlerFunc(f)\n}\n```\n\nThe problem? `http.Error` writes directly. What if this was a JSON api, or a gRPC based API? Writing a plain text error is a problem. Or alternatively, you need to write an exclusive error handling method that's used across everywhere that has intimate knowledge of the pipeline path.\n\nNow, using `mchain` handlers:\n\n```go\nfunc RequestIDMustInitHandler(next mchain.Handler) mchain.Handler {\n\tf := func(w http.ResponseWriter, r *http.Request) error {\n\t\tc := FromRequest(r)\n\t\tif _, ok := r.Header[RequestIDHeaderKey]; ok {\n\t\t\tmsg := fmt.Sprintf(\"error: illegal header (%s)\", RequestIDHeaderKey)\n\t\t\treturn errors.New(msg)\n\t\t\t// However, a better way would be to use the\n\t\t\t// goerror package that communicates error\n\t\t\t// along with status codes, in a clean way.\n\t\t\t//\n\t\t\t// return httperror.New(400, msg, true)\n\t\t}\n\t\tvar uid uuid.UUID\n\t\tmustNewUUID(\u0026uid)\n\t\tc.RequestID = uid\n\t\treturn next.ServeHTTP(w, r)\n\t}\n\treturn mchain.HandlerFunc(f)\n}\n```\n\nNow, the errors can be handled up the middleware chain with an error handler that knows how to format the error the way it has to. Works naturally with the chain, without thinking about how to handle the error in every aspect of the middleware - when in doubt, pass it up the chain.\n\n## But this differs from the `net/http` standard, and it's a sin!\n\nWhile standards are not set in stone, standards are great. I love standards. And standards evolve. But not without experimentation. Meanwhile, I do the best I can to keep things composable and interoperable :) \n\nAnd if you're one of those who want perfect standards, it might be helpful to be aware that `net/http` itself does make you voilate `W3C HTTP standards` in a few places depending on how you use it, because it just handles a few errors internally and has no way to communicate them to it's parent handlers. \n\n## Related\n\n- **fileserver:** https://github.com/prasannavl/go-gluons/blob/master/http/fileserver - Reimplementation of Go's http file server that properly returns errors instead of having it's logic inter-mingled. This allows nice directory listing handling, and error handling with ease.  \n- **handlerutils:** https://github.com/prasannavl/go-gluons/tree/master/http/handlerutils - Handler helpers that ease a lot of boiler plate for common cases.\n- **chainutils:** https://github.com/prasannavl/go-gluons/tree/master/http/chainutils - Middleware chaining helpers that ease boilerplate.\n- **middleware:** https://github.com/prasannavl/go-gluons/tree/master/http/middleware - Some middlewares that are helpful.  \n- **mroute:** https://github.com/prasannavl/mroute - A fork of goji router for mchain with addons.  \n- **mrouter:** https://github.com/prasannavl/mrouter - A fork of httprouter for mchain.  \n- **hostrouter:** https://github.com/prasannavl/go-gluons/blob/master/http/hostrouter/ - A router that handles hosts switching between the most efficient representations on the fly.\n\nLicense\n---\n\nThis project is licensed under either of the following, at your choice:\n\n* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0))\n* GPL 3.0 license ([LICENSE-GPL](LICENSE-GPL) or [https://opensource.org/licenses/GPL-3.0](https://opensource.org/licenses/GPL-3.0))\n\nCode of Conduct\n---\n\nContribution to the LiquidState project is organized under the terms of the Contributor Covenant, and as such the maintainer [@prasannavl](https://github.com/prasannavl) promises to intervene to uphold that code of conduct.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprasannavl%2Fmchain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprasannavl%2Fmchain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprasannavl%2Fmchain/lists"}