{"id":13539426,"url":"https://github.com/heroku/vegur","last_synced_at":"2025-04-02T06:30:55.195Z","repository":{"id":11685564,"uuid":"14196656","full_name":"heroku/vegur","owner":"heroku","description":"Vegur: HTTP Proxy Library","archived":false,"fork":false,"pushed_at":"2022-03-19T00:05:59.000Z","size":5764,"stargazers_count":630,"open_issues_count":8,"forks_count":39,"subscribers_count":114,"default_branch":"master","last_synced_at":"2024-12-27T05:06:47.600Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/heroku.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null}},"created_at":"2013-11-07T07:02:39.000Z","updated_at":"2024-12-10T09:55:12.000Z","dependencies_parsed_at":"2022-09-16T20:21:25.405Z","dependency_job_id":null,"html_url":"https://github.com/heroku/vegur","commit_stats":null,"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fvegur","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fvegur/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fvegur/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fvegur/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heroku","download_url":"https://codeload.github.com/heroku/vegur/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246767691,"owners_count":20830532,"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":[],"created_at":"2024-08-01T09:01:25.707Z","updated_at":"2025-04-02T06:30:53.820Z","avatar_url":"https://github.com/heroku.png","language":"Erlang","funding_links":[],"categories":["\u003ca id=\"1a9934198e37d6d06b881705b863afc8\"\u003e\u003c/a\u003e通信\u0026\u0026代理\u0026\u0026反向代理\u0026\u0026隧道","Erlang","\u003ca id=\"d03d494700077f6a65092985c06bf8e8\"\u003e\u003c/a\u003e工具","others"],"sub_categories":["\u003ca id=\"56acb7c49c828d4715dce57410d490d1\"\u003e\u003c/a\u003e未分类-Proxy","\u003ca id=\"b6f25145e99ea944cbb528a24afaa0be\"\u003e\u003c/a\u003eHTTP\u0026\u0026HTTPS"],"readme":"# Vegur\n\n[![Build Status](https://travis-ci.org/heroku/vegur.svg?branch=master)](https://travis-ci.org/heroku/vegur)\n\nHeroku's proxy library based on a forked Cowboy frontend (Cowboyku). This\nlibrary handles proxying in Heroku's routing stack\n\n![Illfær vegur](http://i.imgur.com/lwRxWDz.jpg)\n\nAnd how do you pronounce vegur? Like [this](https://soundcloud.com/omarkj/vegur).\n\n## Build\n\n    $ rebar3 compile\n\n## Test\n\n    $ rebar3 ct\n\n## Writing a Router\n\nVegur is a *proxy* application, meaning that it takes care of receiving HTTP\nrequests and forwarding them to another server; similarly for responses.\n\nWhat it isn't is a *router*, meaning that it will not handle choosing which\nnodes to send traffic to, nor will it actually track what backends are\navailable. This task is left to the user of the library, by writing a router\ncallback module.\n\n`src/vegur_stub.erl`, which provides an example implementation of the callback\nmodule that has to be used to implement routing logic, can be used as a source\nof information.\n\n\n### Demo reverse-proxy\n\nTo set up a reverse-proxy that does load balancing locally, we'll first set up\ntwo toy servers:\n\n```bash\n$ while true; do ( BODY=$(date); echo -e \"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: ${#BODY}\\r\\n\\r\\n$BODY\" | nc -l -p 8081 ); done\n$ while true; do ( BODY=$(date); echo -e \"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: ${#BODY}\\r\\n\\r\\n$BODY\" | nc -l -p 8082 ); done\n```\n\nThese have the same behaviour and will do the exact same thing, except one is\non port 8081 and the other is on port 8082. You can try reaching them from your\nbrowser.\n\nTo make things simple, I'm going to hardcode both back-ends directly in the\nsource module:\n\n```erlang\n-module(toy_router).\n-behaviour(vegur_interface).\n-export([init/2,\n         terminate/3,\n         lookup_domain_name/3,\n         checkout_service/3,\n         checkin_service/6,\n         service_backend/3,\n         feature/2,\n         additional_headers/4,\n         error_page/4]).\n\n-record(state, {tries = [] :: list()}).\n```\n\nThis is our list of exported functions, along with the behaviour they implement\n(`vegur_interface`), and a record defining the internal state of each router\ninvocation. We track a single value, `tries`, which will be useful to\nmake sure we don't end up in an infinite loop if we ever have no backends\nalive.\n\nAn important thing to note is that this `toy_router` module will be called once\nper request and is decentralized with nothing shared, unlike a node-unique\n`gen_server`.\n\nNow for the implementation of specific callbacks, documented in\n`src/vegur_stub.erl`:\n\n```erlang\ninit(_AcceptTime, Upstream) -\u003e\n    {ok, Upstream, #state{}}. % state initialization here.\n\nlookup_domain_name(_ReqDomain, Upstream, State) -\u003e\n    %% hardcoded values, we don't care about the domain\n    Servers = [{1, {127,0,0,1}, 8081},\n               {2, {127,0,0,1}, 8082}],\n    {ok, Servers, Upstream, State}.\n```\n\nFrom there on, we then can fill in the checkin/checkout logic. We technically\nhave a limitation of one request at a time per server, but we won't track\nthese limitations outside of a limited number of connection retries.\n\n```erlang\ncheckout_service(Servers, Upstream, State=#state{tries=Tried}) -\u003e\n    Available = Servers -- Tried,\n    case Available of\n        [] -\u003e\n            {error, all_blocked, Upstream, State};\n        _ -\u003e\n            N = rand:uniform(length(Available)),\n            Pick = lists:nth(N, Available),\n            {service, Pick, Upstream, State#state{tries=[Pick | Tried]}}\n    end.\n\nservice_backend({_Id, IP, Port}, Upstream, State) -\u003e\n    %% Extract the IP:PORT from the chosen server.\n    %% To enable keep-alive, use:\n    %% `{{keepalive, {default, {IP,Port}}}, Upstream, State}'\n    %% To force the use of a new keepalive connection, use:\n    %% `{{keepalive, {new, {IP,Port}}}, Upstream, State}'\n    %% Otherwise, no keepalive is done to the back-end:\n    {{IP, Port}, Upstream, State}.\n\ncheckin_service(_Servers, _Pick, _Phase, _ServState, Upstream, State) -\u003e\n    %% if we tracked total connections, we would decrement the counters here\n    {ok, Upstream, State}.\n```\n\nWe're also going to enable none of the features and add no headers in either\ndirection because this is a basic demo:\n\n```erlang\nfeature(_WhoCares, State) -\u003e\n    {disabled, State}.\n\nadditional_headers(_Direction, _Log, _Upstream, State) -\u003e\n    {[], State}.\n```\n\nAnd error pages. For now we only care about the one we return, which is `all_blocked`:\n\n```erlang\nerror_page(all_blocked, _DomainGroup, Upstream, State) -\u003e\n    {{502, [], \u003c\u003c\u003e\u003e}, Upstream, State}; % Bad Gateway\n```\n\nAnd then the default ones, which I define broadly:\n\n```erlang\n%% Vegur-returned errors that should be handled no matter what.\n%% Full list in src/vegur_stub.erl\nerror_page({upstream, _Reason}, _DomainGroup, Upstream, HandlerState) -\u003e\n    %% Blame the caller\n    {{400, [], \u003c\u003c\u003e\u003e}, Upstream, HandlerState};\nerror_page({downstream, _Reason}, _DomainGroup, Upstream, HandlerState) -\u003e\n    %% Blame the server\n    {{500, [], \u003c\u003c\u003e\u003e}, Upstream, HandlerState};\nerror_page({undefined, _Reason}, _DomainGroup, Upstream, HandlerState) -\u003e\n    %% Who knows who was to blame!\n    {{500, [], \u003c\u003c\u003e\u003e}, Upstream, HandlerState};\n%% Specific error codes from middleware\nerror_page(empty_host, _DomainGroup, Upstream, HandlerState) -\u003e\n    {{400, [], \u003c\u003c\u003e\u003e}, Upstream, HandlerState};\nerror_page(bad_request, _DomainGroup, Upstream, HandlerState) -\u003e\n    {{400, [], \u003c\u003c\u003e\u003e}, Upstream, HandlerState};\nerror_page(expectation_failed, _DomainGroup, Upstream, HandlerState) -\u003e\n    {{417, [], \u003c\u003c\u003e\u003e}, Upstream, HandlerState};\n%% Catch-all\nerror_page(_, _DomainGroup, Upstream, HandlerState) -\u003e\n    {{500, [], \u003c\u003c\u003e\u003e}, Upstream, HandlerState}.\n```\n\nAnd then terminate without doing anything special (we don't have state\nto tear down, for example):\n\n```erlang\nterminate(_, _, _) -\u003e\n    ok.\n```\n\nAnd then we're done. Compile all that stuff:\n\n```bash\n$ rebar3 shell\nErlang/OTP 17 [erts-6.0] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]\n\nEshell V6.0  (abort with ^G)\n1\u003e c(\"demo/toy_router\"), application:ensure_all_started(vegur), vegur:start_http(8080, toy_router, [{middlewares, vegur:default_middlewares()}]).\n{ok,\u003c0.62.0\u003e}\n```\n\nYou can then call localhost:8080 and see the request routed to either of your\nnetcat servers.\n\nCongratulations, you have a working reverse-load balancer and/or proxy/router\ncombo running. You can shut down either server. The other should take the load,\nand if it also fails, the user would get an error since nothing is left\navailable.\n\n## Behaviour\n\nThere are multiple specific HTTP behaviours that have been chosen/implemented\nin this proxying software. The list is maintained at\nhttps://devcenter.heroku.com/articles/http-routing\n\n## Configuration\n\n### OTP Configuration\n\nThe configuration can be passed following the standard Erlang/OTP application logic.\n\n- `{acceptors, pos_integer()}`: number of HTTP acceptors expected. Defaults to\n  `1024`.\n- `{max_connections, pos_integer()}`: max number of active HTTP connections\n  (inbound). Defaults to `100000`.\n- `{request_id_name, binary()}`: Vegur will read a request id header and pass\n  it on to the proxied request. It will also automatically insert a header with\n  a request id if none is present. This item configures the name of such an ID,\n  and defaults to `X-Request-Id`.\n- `{request_id_max_size, pos_integer()}`: The request Id submitted can be\n  forced to have a maximal size, after which it is considered invalid and a new\n  one is generated. Defaults to `200`.\n- `{start_time_header, binary()}`: Vegur will insert a header representing the\n  epoch at which the request started based on the current node's clock. This\n  allows to specify the name of that header. Defaults to `X-Request-Start`.\n- `{connect_time_header, binary()}`: A header is added noting the time it took\n  to establish a connection to the back-end node provided. This allows to set\n  the name of this header. Defaults to `Connect-Time`.\n- `{route_time_header, binary()}`: A header is added noting the time it took\n  the routing callback module to make its decision. This allows to set the name\n  of this header. Defaults to `Total-Route-Time`.\n- `{idle_timeout, non_neg_integer()}`: Maximal period of inactivity during a\n  session, in seconds. Defaults to 55.\n- `{downstream_connect_timeout, timeout()}`: Maximal time period to wait before\n  abandoning the connection to a backend, in milliseconds. Defaults to 5000ms.\n- `{downstream_timeout, non_neg_integer()}`: Maximal time period to wait before\n  abandonning the wait for a response after a request has been forwarded to a\n  back-end, in seconds. Defaults to 30. This value is purely for the initial\n  response, after which `idle_timeout` takes over as a value.\n- `{client_tcp_buffer_limit, pos_integer()}`: Size of the TCP buffer for the\n  socket to the backend server, in bytes. Defaults to `1048576` (`1024*1024`\n  bytes).\n- `{max_client_status_length, pos_integer()}`: Maximal size of the status line\n  of the client response, in bytes. Defaults to `8192`.\n- `{max_client_header_length, pos_integer()}`: Maximal size of a given response\n  header line, in bytes. Defaults to `524288`, or 512kb.\n- `{max_client_cookie_length, pos_integer()}`: Maximal size of a cookie in a\n  response, in bytes. Defaults to `8192`.\n- `{extra_socket_options, [gen_tcp:option()]}`: Allows to set additional\n  TCP options useful for configuration (such as `nodelay` or `raw` options).\n\n### Server Configuration\n\nThe HTTP servers themselves can also have their own configuration in a\nper-listener manner. The following options are valid when passed to\n`vegur:start/5`:\n\n- `{max_request_line_length, pos_integer()}`: Maximal line size for the HTTP\n  request. Defaults to 8192. Note that this value may be disregarded if the\n  entire line managed to fit within the confines of a single HTTP packet or\n  `recv` operation.\n- `{max_header_name_length, pos_integer()}`: Maximal length for header names in\n  HTTP requests. Defaults to `1000`. Note that this value may be disregarded if\n  the entire line managed to fit within the confines of a single HTTP packet or\n  `recv` operation.\n- `{max_header_value_length, pos_integer()}`: Maximal length for the value of a\n  header in HTTP requests. Defaults to `8192`. Note that this value may be\n  disregarded if the entire line managed to fit within the confines of a single\n  HTTP packet or `recv` operation.\n- `{max_headers, pos_integer()}`: number of HTTP headers allowed in a single\n  request. Defaults to 1000.\n- `{timeout, timeout()}`: Delay, in milliseconds, after which a connection is\n  closed for inactivity. This delay also specifies the maximal time that an\n  idle connection being pre-opened by some service for efficiency reasons will\n  remain open without receiving a request on it.\n\nIt is recommended that options regarding header sizes for the HTTP listener\nmatch the options for the `max_cookie_length` in the OTP options to avoid the\npainful case of a backend setting a cookie that cannot be sent back by the end\nclient.\n\n## Middlewares ##\n\nVegur supports a middleware interface that can be configured when booting\nthe application. These can be configured by setting the `middlewares` option:\n\n```erlang\nvegur:start_http(Port, CallbackMod, [{middlewares, Middlewares}]),\nvegur:start_proxy(Port, CallbackMod, [{middlewares, Middlewares}]),\n```\n\nThe middlewares value should always contain, at the very least, the result of\n`vegur:default_middlewares()`, which implements some required functionality.\n\nFor example, the following middlewares are the default ones:\n\n- `vegur_validate_headers`: ensures the presence of `Host` headers, and that\n  `content-length` headers are legitimate without duplication;\n- `vegur_lookup_domain_middleware`: calls the callback module to do domain\n  lookups and keeps it in state;\n- `vegur_continue_middleware`: handles `expect: 100-continue` headers\n  conditionally depending on the `feature` configured by the callback\n  module;\n- `vegur_upgrade_middleware`: detects if the request needs an `upgrade`\n  (for example, websockets) and sets internal state for the proxy to\n  properly handle this once it negotiates headers with the back-end;\n- `vegur_lookup_service_middleware`: calls the callback module to pick\n  a back-end for the current domain;\n- `vegur_proxy_middleware`: actually proxies the request\n\nThe order is important, and as defined, default middlewares *must* be\nkept for a lot of functionality (from safety to actual proxying) to\nactually work.\n\nCustom middlewares can still be added throughout the chain by adding\nthem to the list.\n\n### Defining middlewares\n\nThe middlewares included are standard `cowboyku` (`cowboy` ~0.9)\nmiddlewares and respect the same interface.\n\nThere's a single callback defined:\n\n```erlang\nexecute(Req, Env)\n    -\u003e {ok, Req, Env}\n     | {suspend, module(), atom(), [any()]}\n     | {halt, Req}\n     | {error, cowboyku:http_status(), Req}\n    when Req::cowboyku_req:req(), Env::env().\n```\n\nFor example, a middleware implementing some custom form of\nauthentication where a secret token is required to access\ndata could be devised to work like:\n\n```erlang\nmodule(validate_custom_auth).\n-behaviour(cowboyku_middleware).\n-export([execute/2]).\n\n-define(TOKEN, \u003c\u003c\"abcdef\"\u003e\u003e. % this is really unsafe\n\nexecute(Req, Env) -\u003e\n    case cowboyku_req:header(\u003c\u003c\"my-token\"\u003e\u003e, Req) of\n        {?TOKEN, Req2} -\u003e\n            {ok, Req2, Env};\n        {_, Req2} -\u003e\n            {HTTPCode, Req3} = vegur_utils:handle_error(bad_token, Req2),\n            {error, HTTPCode, Req3}\n    end.\n```\n\nCalling `vegur_utils:handle_error(Reason, Req)` will redirect\nthe error to the `Callback:error_page/4` callback, letting the\ncustom callback module set its own HTTP status, handle logging,\nand do whatever processing it needs before stopping the request.\n\n\n## Logs and statistics being collected\n\n* `domain_lookup`\n * Time it takes to lookup the domain in the domain service.\n* `service_lookup`\n * Time it takes to lookup a service to connect to.\n* `connect_time`\n * Time it takes to connect to the backend server.\n*  `pre_connect`\n * Timestamp before connecting to the backend server\n* `connection_accepted`\n * Timestamp when connection is accepted\n\n\n\n## Behaviour\n\n### Added Headers\n\nAll headers are considered to be case-insensitive, as per the HTTP\nSpecification, but will be camel-cased by default. A few of them are added by\nVegur.\n\n- `X-Forwarded-For`: the originating IP address of the client connecting to the\n  proxy\n- `X-Forwarded-Proto`: the originating protocol of the HTTP request (example:\n  https). This is detected based on the incoming port, so using port 8080 will\n  not add this header.\n- `X-Forwarded-Port`: the originating port of the HTTP request (example: 443)\n- `X-Request-Start`: unix timestamp (milliseconds) when the request was\n  received by the proxy\n- `X-Request-Id`: the HTTP Request ID\n- `Via`: a code name for the vegur proxy, with the value `vegur: 1.1`\n- `Server`: will be added to the response (using our forked `cowboy`) if the\n  endpoint didn't add it first.\n\n### Protocol Details\n\nThe vegur proxy only supports HTTP/1.0 and HTTP/1.1 clients. HTTP/0.9 and\nearlier are no longer supported. SPDY and HTTP/2.0 are not supported at this\npoint.\n\nThe proxy's behavior is to be as compliant as possible with the HTTP/1.1\nspecifications. Special exceptions must be made for HTTP/1.0 however:\n\n- The proxy will advertise itself as using HTTP/1.1 regardless whether the\n  client uses HTTP/1.0 or not.\n- It is the proxy's responsibility to convert a chunked response to a regular\n  HTTP response. In order to do so without accumulating potentially gigabytes\n  of data, the response to the client will be delimited by the termination\n  of the connection\n  (See [Point 4.4.5](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4))\n- The router will assume that the client wants to close the connection on each\n  request (no keep-alive).\n- An HTTP/1.0 client may send a request with an explicit `connection:keep-alive`\n  header. Despite the keep-alive mechanism not being defined back in 1.0 (it\n  was ad-hoc), the router makes the assumption that the behavior requested is\n  similar to the HTTP/1.1 behavior at this point.\n\nOther details:\n\n- No caching done by the proxy\n- Websockets (and the general `upgrade` mechanism) are supported\n- Responses are not compressed on behalf of the application\n- All HTTP methods are supported, except `CONNECT`.\n- `Expect: 100-continue` requests can be automatically answered to with `100\n  Continue` or forwarded to the application based on the `feature` routing\n  callback function.\n- Only `100-continue` is accepted as a value for `expect` headers. In case any\n  other value is encountered, the proxy responds with `417 Expectation Failed`\n- The proxy will ignore `Connection: close` on a `100 Continue` and only honor\n  it after it receives the final response. Note however, that because\n  `Connection: close` is a hop-by-hop mechanism, the proxy will not necessarily\n  close the connection to the client, and may not forward it.\n- By default, the proxy will close all connections to the back-ends after each\n  request, but will honor keep-alive to the client when possible. Support\n  for keep-alive to the back-end can be enabled by returning the right values\n  out of the `service_backend` callback.\n- The proxy will return a configurable error code if the server returns a `100\n  Continue` following an initial `100 Continue` response. The proxy does not\n  yet support infinite `1xx` streams.\n- In the case of chunked encoding and `content-length` both being present in\n  the request, the router will [give precedence to chunked\n  encoding](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4).\n- If multiple `content-length` fields are present, and that they have the same\n  length, they will be merged into a single `content-length` header\n- If a `content-length` header contains multiple values (`content-length: 15,24`)\n  or a request contains multiple content-length headers with multiple values,\n  the request will be denied with a code `400`.\n- Headers are restricted to 8192 bytes per line (and 1000 bytes for the header\n  name)\n- Hop-by-hop headers will be stripped to avoid confusion\n- At most, 1000 headers are allowed per request\n- The request line of the HTTP request is limited to 8192 bytes\n\nSpecifically for responses:\n\n\n- Hop-by-hop headers will be stripped to avoid confusion\n- Headers are restricted to 512kb per line\n- Cookies are explicitly restricted to 8192 bytes. This is to protect against\n  common restrictions (for example, imposed by CDNs) that rarely accept larger\n  cookie values. In such cases, a developer could accidentally set large cookies,\n  which would be submitted back to the user, who would then see all of his or her\n  requests denied.\n- The status line (`HTTP/1.1 200 OK`) is restricted to 8192 bytes in length,\n  must have a 3-digit response code and contain a string explaining the code,\n  as per RFC.\n\nAdditionally, while HTTP/1.1 requests and responses are expected to be\nkeep-alive by default, if the initial request had an explicit `connection: close`\nheader from the router to the backend, the backend can send a response\ndelimited by the connection termination, without a specific content-encoding\nnor an explicit content-length.\n\nEven though the `HEAD` HTTP verb does not require a response body to be\nsent over the line and ends at the response headers, `HEAD` requests are\nexplicitly made to work with `101 Switching Protocols` responses. A backend that\ndoesn't want to upgrade should send a different status code, and the connection\nwill not be upgraded.\n\n## Not Supported\n\n- SPDY\n- HTTP/2.x\n- `Expect` headers with any content other than 100-continue (yields a `417`)\n- HTTP Extensions such as WEBDAV, relying on additional 1xx status responses\n- A HEAD, 1xx, 204, or 304 response which specifies a content-length or chunked\n  encoding will result in the proxy forwarding such headers, but not the body\n  that may or may not be coming with the response.\n- Header line endings other than CRLF (`\\r\\n`)\n- Caching of HTTP Content\n- Caching the HTTP versions of backends\n- Long-standing preallocated idle connections. The limit is set to 1 minute\n  before an idle connection is closed.\n- HTTP/1.0 routing without a `Host` header, even when the full path is\n  submitted in the request line.\n\n## Contributing\n\nAll contributed work must have:\n\n- Tests\n- Documentation\n- Rationale\n- Proper commit description.\n\nA good commit message should include a rationale for the change, along with the\nexisting, expected, and new behaviour.\n\nAll contributed work will be reviewed before being merged (or rejected).\n\nThis proxy is used in production with existing apps, and a commitment to\nbackwards compatibility (or just working in the real world) is in place.\n\n## Architecture Guidelines\n\nMost of the request validation is done through the usage of middlewares.\nThe middlewares we use are implemented through `midjan`, which wraps some\noperations traditionally done by `cowboyku` in order to have more control\nover vital parts of a request/response whenever the RFC is different\nbetween servers and proxies.\n\nAll middleware modules have their name terminated by `_middleware`.\n\nThe proxy is then split into 5 major parts maintained in this directory:\n\n1. `vegur_proxy_middleware`, which handles the high-level request/response\n   patterns.\n2. `vegur_proxy`, which handles the low-level HTTP coordination between\n   requests and responses, and technicalities of socket management, header\n   reconciliation, etc.\n3. `vegur_client`, a small HTTP client to call back-ends\n4. Supporting sub-states of HTTP, such as the chunked parser and the bytepipe\n   (used for upgrades), each having its own module (`vegur_chunked` and\n   `vegur_bytepipe`)\n5. Supporting modules, such as functional logging modules, midjan translators,\n   and so on (`vegur_req_log`, `vegur_midjan_translator`).\n\n\n## Reference Material\n\n* [HTTP Made Easy](http://www.jmarshall.com/easy/http/)\n* [HTTP/1.0 RFC](http://www.isi.edu/in-notes/rfc1945.txt)\n* [HTTP/1.1 RFC (original)](https://www.ietf.org/rfc/rfc2068.txt)\n* [HTTP/1.1 RFC (updated)](https://www.ietf.org/rfc/rfc2616.txt)\n* [HTTP/1.1 RFCs (updated again, by HTTPbis)](https://datatracker.ietf.org/wg/httpbis/documents/),\n  particularly the [Messaging RFC](https://datatracker.ietf.org/doc/rfc7230/), and\n  the [Semantics RFC](https://datatracker.ietf.org/doc/rfc7231/)\n* [HTTPbis Wiki](http://trac.tools.ietf.org/wg/httpbis/trac/wiki)\n* [The Cowboy Guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide/introduction)\n* [Key differences between HTTP/1.0 and 1.1](http://www8.org/w8-papers/5c-protocols/key/key.html)\n* [What Proxies Must Do](https://www.mnot.net/blog/2011/07/11/what_proxies_must_do)\n* [Heroku's HTTP Routing](https://devcenter.heroku.com/articles/http-routing)\n\n## Changelog\n\n- 2.0.5: `Expect` header can be empty\n- 2.0.4: vegur_client returns error on invalid encoding types\n- 2.0.3: reinstate `X-Forwarded-Host` as too much stuff breaks without it\n- 2.0.2: drop duplicate `Host` headers and `X-Forwarded-Host` for cache issues\n- 2.0.1: enable `SO_REUSEADDR` on connections to backend to support more connections\n- 2.0.0: adding support for keepalive to the backend, dropping support for OTP 16 and 17\n- 1.1.1: minor refactoring, typespecs and documentation changes\n- 1.1.0: initial support for PROXY protocol v2\n- 1.0.0: first stable release\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheroku%2Fvegur","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheroku%2Fvegur","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheroku%2Fvegur/lists"}