{"id":13819809,"url":"https://github.com/xgfone/ship","last_synced_at":"2025-07-29T19:33:28.466Z","repository":{"id":57484751,"uuid":"160064008","full_name":"xgfone/ship","owner":"xgfone","description":" A flexible, powerful, high performance and minimalist Go Web HTTP router framework.","archived":false,"fork":false,"pushed_at":"2023-05-05T02:22:12.000Z","size":731,"stargazers_count":49,"open_issues_count":4,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-19T19:43:41.770Z","etag":null,"topics":["go","go-mux","go-route","go-router","golang","http","http-router","http-routing","mux","route","router","routes"],"latest_commit_sha":null,"homepage":"https://github.com/xgfone/ship","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/xgfone.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-02T15:57:15.000Z","updated_at":"2024-11-16T11:50:52.000Z","dependencies_parsed_at":"2024-05-28T04:01:51.543Z","dependency_job_id":"0bb35209-956c-4217-b372-5e4a030fa195","html_url":"https://github.com/xgfone/ship","commit_stats":null,"previous_names":[],"tags_count":62,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xgfone%2Fship","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xgfone%2Fship/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xgfone%2Fship/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xgfone%2Fship/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xgfone","download_url":"https://codeload.github.com/xgfone/ship/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228042004,"owners_count":17860352,"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":["go","go-mux","go-route","go-router","golang","http","http-router","http-routing","mux","route","router","routes"],"created_at":"2024-08-04T08:00:53.244Z","updated_at":"2024-12-04T04:19:29.362Z","avatar_url":"https://github.com/xgfone.png","language":"Go","readme":"# ship [![Build Status](https://github.com/xgfone/ship/actions/workflows/go.yml/badge.svg)](https://github.com/xgfone/ship/actions/workflows/go.yml) [![GoDoc](https://pkg.go.dev/badge/github.com/xgfone/ship/v5)](https://pkg.go.dev/github.com/xgfone/ship/v5) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square)](https://raw.githubusercontent.com/xgfone/ship/master/LICENSE)\n\n`ship` is a flexible, powerful, high performance and minimalist Go Web HTTP router framework supporting Go `1.11+`. It is inspired by [echo](https://github.com/labstack/echo) and [httprouter](https://github.com/julienschmidt/httprouter). Thanks for those contributors.\n\n\n## Features\n- Support the url parameter.\n- Support the session manager.\n- Support the customized router manager.\n- Support the pre-route and route middlewares.\n- Support the route group builder to build the route.\n- Support the mulit-virtual hosts and the default host.\n- Support the exact, prefix, suffix and regexp hostname.\n- Support the binding of the request data, such as body and query.\n- Support the renderer, such as the HTML template.\n- ......\n\n\n### Components\n- `Ship` is the pure router framework based on the method and the path, including `Middleware`, `Context`, `Router`, etc.\n- `HostManager` and `HostHandler` are the vhost manager and the standard http handler with the vhost manager.\n- `Runner` is the runner to start the http server with the standard http handler.\n\n\n## Install\n\n```shell\ngo get -u github.com/xgfone/ship/v5\n```\n\n\n## Quick Start\n\n```go\n// example.go\npackage main\n\nimport (\n\t\"github.com/xgfone/ship/v5\"\n\t\"github.com/xgfone/ship/v5/middleware\"\n)\n\nfunc main() {\n\trouter := ship.New()\n\trouter.Use(middleware.Logger(), middleware.Recover()) // Use the middlewares.\n\n\trouter.Route(\"/ping\").GET(func(c *ship.Context) error {\n\t\treturn c.JSON(200, map[string]interface{}{\"message\": \"pong\"})\n\t})\n\n\tgroup := router.Group(\"/group\")\n\tgroup.Route(\"/ping\").GET(func(c *ship.Context) error {\n\t\treturn c.Text(200, \"group\")\n\t})\n\n\tsubgroup := group.Group(\"/subgroup\")\n\tsubgroup.Route(\"/ping\").GET(func(c *ship.Context) error {\n\t\treturn c.Text(200, \"subgroup\")\n\t})\n\n\t// Start the HTTP server.\n\tship.StartServer(\":8080\", router)\n\t// or\n\t// http.ListenAndServe(\":8080\", router)\n}\n```\n\n```shell\n$ go run example.go\n```\n\n```shell\n$ curl http://127.0.0.1:8080/ping\n{\"message\":\"pong\"}\n\n$ curl http://127.0.0.1:8080/group/ping\ngroup\n\n$ curl http://127.0.0.1:8080/group/subgroup/ping\nsubgroup\n```\n\n### Route Path\nThe route path supports the parameters like `:paramName`, `*` or `*restParamName`.\n\n- `/path/to/route` only matches the path `/path/to/route`.\n- `/path/:param1/to` matches the path `/path/abc/to`, `/path/xyz/to`, etc. And `:param1` is equal to `abc` or `xyz`.\n- `/path/:param1/to/:param2` matches the path `/path/p11/to/p21`, `/path/p12/to/p22`, etc. And `:parma1` is equal to `p11` or `p12`, and `:param2` is equal to `p12` or `p22`.\n- `/path/to/*` or `/path/to/*all` matches the path `/path/to/abc`, `/path/to/abc/efg`, `/path/to/xyz`, `/path/to/xyz/123`, etc. And `*` or `*all` is equal to `abc`, `abc/efg`, `xyz`, or `xzy/123`. **Notice:** `*` or `*restParamName` must be the last one of the route path.\n- `/path/:param/to/*` matches the path `/path/abc/to/efg`, `/path/abc/to/efg/123`, etc. And `:param` is equal to `abc`, and `*` is equal to `efg` or `efg/123`\n\nFor the parameter, it can be accessed by `Context.Param(paramName)`.\n\n- For `*`, the parameter name is `*`, like `Context.Param(\"*\")`.\n- For `*restParamName`, the parameter name is `restParamName`, like `Context.Param(restParamName)`.\n\n\n## API Example\n\n### Route Builder\n#### Using `CONNECT`, `HEAD`, `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `OPTIONS`\n\n```go\nfunc main() {\n\trouter := ship.New()\n\trouter.Route(\"/path/get\").GET(getHandler)\n\trouter.Route(\"/path/put\").PUT(putHandler)\n\trouter.Route(\"/path/head\").HEAD(headHandler)\n\trouter.Route(\"/path/post\").POST(postHandler)\n\trouter.Route(\"/path/patch\").PATCH(patchHandler)\n\trouter.Route(\"/path/delete\").DELETE(deleteHandler)\n\trouter.Route(\"/path/option\").OPTIONS(optionHandler)\n\trouter.Route(\"/path/connect\").CONNECT(connectHandler)\n\tship.StartServer(\":8080\", router)\n}\n```\n\nNotice: you can register the same handler with more than one method by `Route(path string).Method(handler Handler, method ...string)`.\n\n\n#### Cascade the registered routes\n\n```go\nfunc main() {\n\trouter := ship.New()\n\trouter.Route(\"/path/to\").GET(getHandler).POST(postHandler).DELETE(deleteHandler)\n\tship.StartServer(\":8080\", router)\n}\n```\n\n\n#### Use the mapping of the route methods\n```go\nfunc main() {\n\trouter := ship.New()\n\trouter.Route(\"/path/to\").Map(map[string]ship.Handler{\n\t\t\"GET\": getHandler,\n\t\t\"POST\": postHandler,\n\t\t\"DELETE\": deleteHandler,\n\t})\n\tship.StartServer(\":8080\", router)\n}\n```\n\n\n#### Name the route\nWhen registering the route, it can be named with a name.\n\n```go\nfunc main() {\n\trouter := ship.New()\n\trouter.Route(\"/path/:id\").Name(\"get_url\").GET(func(c *ship.Context) error {\n\t\tfmt.Println(c.URL(\"get_url\", c.Param(\"id\")))\n\t\treturn nil\n\t})\n\tship.StartServer(\":8080\", router)\n}\n```\n\n\n#### Use the route group\n\n```go\npackage main\n\nimport (\n\t\"github.com/xgfone/ship/v5\"\n\t\"github.com/xgfone/ship/v5/middleware\"\n)\n\n// MyAuthMiddleware returns a middleare to authenticate the request.\nfunc MyAuthMiddleware() ship.Middleware {\n\treturn func(next ship.Handler) ship.Handler {\n\t\treturn func(c *ship.Context) error {\n\t\t\t// TODO: authenticate the request.\n\t\t\treturn next(c)\n\t\t}\n\t}\n}\n\nfunc main() {\n\trouter := ship.New()\n\trouter.Use(middleware.Logger(), middleware.Recover())\n\n\t// v1 Group, which will inherit the middlewares of the parent router.\n\tv1 := router.Group(\"/v1\")\n\tv1.Route(\"/get\").GET(func(c *ship.Context) error { return nil }) // Route: GET /v1/get\n\n\t// v2 Group, which won't inherit the middlewares of the parent router.\n\tv2 := router.Group(\"/v2\").ResetMiddlewares(MyAuthMiddleware())\n\tv2.Route(\"/post\").POST(func(c *ship.Context) error { return nil }) // Route: POST /v2/post\n\n\t// For sub-group of v2 Group.\n\tv2g := v2.Group(\"/child\")\n\tv2g.Route(\"/path\").GET(func(c *ship.Context) error { return nil }) // Route: GET /v2/child/path\n\n\tship.StartServer(\":8080\", router)\n}\n```\n\n#### Filter the unacceptable routes\n```go\npackage main\n\nimport (\n\t\"strings\"\n\n\t\"github.com/xgfone/ship/v5\"\n)\n\nfunc filter(ri ship.Route) bool {\n\tif ri.Name == \"\" || !strings.HasPrefix(ri.Path, \"/prefix/\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc main() {\n\thandler := func(c *ship.Context) error { return nil }\n\n\trouter := ship.New()\n\trouter.RouteFilter = filter // Don't register the router without name.\n\n\trouter.Group(\"/prefix\").Route(\"/name\").Name(\"test\").GET(handler) // Register the route\n\trouter.Group(\"/prefix\").Route(\"/noname\").GET(handler)            // Don't register the route\n\trouter.Route(\"/no_group\").GET(handler)                           // Don't register the route\n\n\tship.StartServer(\":8080\", router)\n}\n```\n\n#### Modify the route before registering it\n```go\npackage main\n\nimport \"github.com/xgfone/ship/v5\"\n\nfunc modifier(ri ship.Route) ship.Route {\n\tri.Path = \"/prefix\" + ri.Path\n\treturn ri\n}\n\nfunc main() {\n\thandler := func(c *ship.Context) error { return nil }\n\n\trouter := ship.New()\n\trouter.RouteModifier = modifier\n\trouter.Route(\"/path\").Name(\"test\").GET(handler) // Register the path as \"/prefix/path\".\n\n\tship.StartServer(\":8080\", router)\n}\n```\n\n\n### Use `Middleware`\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/xgfone/ship/v5\"\n\t\"github.com/xgfone/ship/v5/middleware\"\n)\n\n// RemovePathPrefix returns a middleware to remove the prefix from the request path.\nfunc RemovePathPrefix(prefix string) ship.Middleware {\n\tif len(prefix) \u003c 2 || prefix[len(prefix)-1] == '/' {\n\t\tpanic(fmt.Errorf(\"invalid prefix: '%s'\", prefix))\n\t}\n\n\treturn func(next ship.Handler) ship.Handler {\n\t\treturn func(c *ship.Context) error {\n\t\t\treq := c.Request()\n\t\t\treq.URL.Path = strings.TrimPrefix(req.URL.Path, prefix)\n\t\t\treturn next(c)\n\t\t}\n\t}\n}\n\nfunc main() {\n\trouter := ship.New()\n\n\t// Execute the middlewares before finding the route.\n\trouter.Pre(RemovePathPrefix(\"/static\"))\n\n\t// Execute the middlewares after finding the route.\n\trouter.Use(middleware.Logger(), middleware.Recover())\n\n\thandler := func(c *ship.Context) error { return nil }\n\trouter.Route(\"/path1\").GET(handler)\n\trouter.Route(\"/path2\").GET(handler)\n\trouter.Route(\"/path3\").GET(handler)\n\n\tship.StartServer(\":8080\", router)\n}\n```\n\n### Use the virtual host\n\n```go\npackage main\n\nimport (\n\t\"github.com/xgfone/ship/v5\"\n)\n\nfunc main() {\n\tvhosts := ship.NewHostManagerHandler(nil)\n\n\t_default := ship.New()\n\t_default.Route(\"/\").GET(func(c *ship.Context) error { return c.Text(200, \"default\") })\n\tvhosts.SetDefaultHost(\"\", _default)\n\n\t// Exact Match Host\n\tvhost1 := ship.New()\n\tvhost1.Route(\"/\").GET(func(c *ship.Context) error { return c.Text(200, \"vhost1\") })\n\tvhosts.AddHost(\"www.host1.example.com\", vhost1)\n\n\t// Suffix Match Host\n\tvhost2 := ship.New()\n\tvhost2.Route(\"/\").GET(func(c *ship.Context) error { return c.Text(200, \"vhost2\") })\n\tvhosts.AddHost(\"*.host2.example.com\", vhost2)\n\n\t// Prefix Match Host\n\tvhost3 := ship.New()\n\tvhost3.Route(\"/\").GET(func(c *ship.Context) error { return c.Text(200, \"vhost3\") })\n\tvhosts.AddHost(\"www.host3.*\", vhost3)\n\n\t// Regexp Match Host by using Go regexp package\n\tvhost4 := ship.New()\n\tvhost4.Route(\"/\").GET(func(c *ship.Context) error { return c.Text(200, \"vhost4\") })\n\tvhosts.AddHost(`www\\.[a-zA-z0-9]+\\.example\\.com`, vhost4)\n\n\tship.StartServer(\":8080\", vhosts)\n}\n```\n\n```shell\n$ curl http://127.0.0.1:8080/\ndefault\n\n$ curl http://127.0.0.1:8080/ -H 'Host: www.host1.example.com' # Exact\nvhost1\n\n$ curl http://127.0.0.1:8080/ -H 'Host: www.host2.example.com' # Suffix\nvhost2\n\n$ curl http://127.0.0.1:8080/ -H 'Host: www.host3.example.com' # Prefix\nvhost3\n\n$ curl http://127.0.0.1:8080/ -H 'Host: www.host4.example.com' # Regexp\nvhost4\n```\n\n### Handle the complex response\n\n```go\npackage main\n\nimport \"github.com/xgfone/ship/v5\"\n\nfunc responder(c *ship.Context, args ...interface{}) error {\n\tswitch len(args) {\n\tcase 0:\n\t\treturn c.NoContent(200)\n\tcase 1:\n\t\tswitch v := args[0].(type) {\n\t\tcase int:\n\t\t\treturn c.NoContent(v)\n\t\tcase string:\n\t\t\treturn c.Text(200, v)\n\t\t}\n\tcase 2:\n\t\tswitch v0 := args[0].(type) {\n\t\tcase int:\n\t\t\treturn c.Text(v0, \"%v\", args[1])\n\t\t}\n\t}\n\treturn c.NoContent(500)\n}\n\nfunc main() {\n\trouter := ship.New()\n\trouter.Responder = responder\n\trouter.Route(\"/path1\").GET(func(c *ship.Context) error { return c.Respond() })\n\trouter.Route(\"/path2\").GET(func(c *ship.Context) error { return c.Respond(200) })\n\trouter.Route(\"/path3\").GET(func(c *ship.Context) error { return c.Respond(\"Hello, World\") })\n\trouter.Route(\"/path4\").GET(func(c *ship.Context) error { return c.Respond(200, \"Hello, World\") })\n\tship.StartServer(\":8080\", router)\n}\n```\n\n### Bind JSON, XML or Form data from the request payload\n```go\npackage main\n\nimport \"github.com/xgfone/ship/v5\"\n\n// Login is the login information.\ntype Login struct {\n\tUsername string `json:\"username\" xml:\"username\"`\n\tPassword string `json:\"password\" xml:\"password\"`\n}\n\nfunc main() {\n\trouter := ship.Default()\n\trouter.Route(\"/login\").POST(func(c *ship.Context) (err error) {\n\t\tvar login Login\n\t\tif err = c.Bind(\u0026login); err != nil {\n\t\t\treturn ship.ErrBadRequest.New(err)\n\t\t}\n\t\treturn c.Text(200, \"username=%s, password=%s\", login.Username, login.Password)\n\t})\n\n\tship.StartServer(\":8080\", router)\n}\n```\n\n```shell\n$ curl http://127.0.0.1:8080/login \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"username\":\"xgfone\",\"password\":\"123456\"}'\nusername=xgfone, password=123456\n\n$ curl http://127.0.0.1:8080/login \\\n    -H 'Content-Type: application/xml' \\\n    -d '\u003clogin\u003e\u003cusername\u003exgfone\u003c/username\u003e\u003cpassword\u003e123456\u003c/password\u003e\u003c/login\u003e'\nusername=xgfone, password=123456\n```\n\n### Render HTML template\n\nIn the directory `/path/to/templates`, there is a template file named `index.tmpl` as follow:\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\u003c/head\u003e\n    \u003cbody\u003e\n        This is the body content: \u003c/pre\u003e{{ . }}\u003c/pre\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n```go\npackage main\n\nimport (\n\t\"github.com/xgfone/ship/v5\"\n\t\"github.com/xgfone/ship/v5/render/template\"\n)\n\nfunc main() {\n\t// It will recursively load all the files in the directory as the templates.\n\tloader := template.NewDirLoader(\"/path/to/templates\")\n\ttmplRender := template.NewHTMLTemplateRender(loader)\n\n\trouter := ship.Default()\n\trouter.Renderer.(*ship.MuxRenderer).Add(\".tmpl\", tmplRender)\n\trouter.Route(\"/html\").GET(func(c *ship.Context) error {\n\t\treturn c.RenderOk(\"index.tmpl\", \"Hello World\")\n\t})\n\n\t// Start the HTTP server.\n\tship.StartServer(\":8080\", router)\n}\n```\n\nWhen accessing `http://127.0.0.1:8080/html`, it returns\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\u003c/head\u003e\n    \u003cbody\u003e\n        This is the body content: \u003c/pre\u003eHello World\u003c/pre\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\n\n## Route Management\n\n`ship` supply a default implementation based on [Radix tree](https://en.wikipedia.org/wiki/Radix_tree) to manage the route with **Zero Garbage** (See [Benchmark](#benchmark)), which refers to [echo](https://github.com/labstack/echo), that's, [`NewRouter()`](https://pkg.go.dev/github.com/xgfone/ship/v5/router/echo?tab=doc#NewRouter).\n\nYou can appoint your own implementation by implementing the interface [`Router`](https://pkg.go.dev/github.com/xgfone/ship/v5/router?tab=doc#Router).\n\n```go\ntype Router interface {\n\t// Range traverses all the registered routes.\n\tRange(func(name, path, method string, handler interface{}))\n\n\t// Path generates a url path by the path name and parameters.\n\t//\n\t// Return \"\" if there is not the route path named name.\n\tPath(name string, params ...interface{}) string\n\n\t// Add adds the route and returns the number of the parameters\n\t// if there are the parameters in the route path.\n\t//\n\t// name is the name of the path, which is optional and must be unique\n\t// if not empty.\n\t//\n\t// If method is empty, handler is the handler of all the methods supported\n\t// by the implementation. Or, it is only that of the given method.\n\t//\n\t// For the parameter in the path, the format is determined by the implementation.\n\tAdd(name, path, method string, handler interface{}) (paramNum int, err error)\n\n\t// Del deletes the given route.\n\t//\n\t// If method is empty, deletes all the routes associated with the path.\n\t// Or, only delete the given method for the path.\n\tDel(path, method string) (err error)\n\n\t// Match matches the route by path and method, puts the path parameters\n\t// into pnames and pvalues, then returns the handler and the number\n\t// of the path paramethers.\n\t//\n\t// If pnames or pvalues is empty, it will ignore the path paramethers\n\t// when finding the route handler.\n\t//\n\t// Return (nil, 0) if not found the route handler.\n\tMatch(path, method string, pnames, pvalues []string) (handler interface{}, pn int)\n}\n```\n\n```go\nfunc main() {\n\tNewMyRouter := func() (router ship.Router) {\n\t\t// TODO: new a Router.\n\t\treturn\n\t}\n\n\trouter := ship.New()\n\trouter.Router = NewMyRouter()\n\t// ...\n}\n```\n\n\n## Benchmark\n\n```\nHP Laptop 14s-dr2014TU\ngo:     1.16.4\ngoos:   windows\ngoarch: amd64\nmemory: 16GB DDR4-3200\ncpu:    11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz\n```\n\n\n|            Framework          | Version\n|-------------------------------|---------\n| `github.com/gin-gonic/gin`    | v1.7.2\n| `github.com/labstack/echo/v4` | v4.4.0\n| `github.com/xgfone/ship/v5`   | v5.0.0\n\n\n|           Function           |  ops   | ns/op | B/opt | allocs/op\n|------------------------------|--------|-------|-------|-----------\n| Benchmark**Echo**Static-8        |  43269 | 27676 |  2056 | 157\n| Benchmark**Echo**GitHubAPI-8     |  29738 | 40773 |  2788 | 203\n| Benchmark**Echo**GplusAPI-8      | 668731 |  1967 |   207 |  13\n| Benchmark**Echo**ParseAPI-8      | 362774 |  3369 |   398 |  26\n| Benchmark**Gin**Static-8         |  47384 | 24037 |  8267 | 157\n| Benchmark**Gin**GitHubAPI-8      |  33747 | 34648 | 10771 | 203\n| Benchmark**Gin**GplusAPI-8       | 598628 |  1830 |   681 |  13\n| Benchmark**Gin**ParseAPI-8       | 356298 |  3314 |  1442 |  26\n| Benchmark**ShipEcho**Static-8    |  51788 | 23219 |   668 | **0**\n| Benchmark**ShipEcho**GitHubAPI-8 |  32854 | 35759 |  1054 | **0**\n| Benchmark**ShipEcho**GplusAPI-8  | 746049 |  1809 |    92 | **0**\n| Benchmark**ShipEcho**ParseAPI-8  | 396067 |  3310 |   174 | **0**\n\n\n|             Function           |    ops   | ns/op | B/opt | allocs/op\n|--------------------------------|----------|-------|-------|-----------\n| BenchmarkShipWithoutVHost-8    | 19691887 | 54.53 |   0   |    0\n| BenchmarkShipWithExactVHost-8  | 17158249 | 64.19 |   0   |    0\n| BenchmarkShipWithPrefixVHost-8 | 13445091 | 90.81 |   0   |    0\n| BenchmarkShipWithRegexpVHost-8 |  4668913 | 248.0 |   0   |    0\n","funding_links":[],"categories":["Go"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxgfone%2Fship","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxgfone%2Fship","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxgfone%2Fship/lists"}