{"id":13825802,"url":"https://github.com/tallarium/reverse_proxy_plug","last_synced_at":"2025-05-16T00:07:24.636Z","repository":{"id":42618241,"uuid":"143184188","full_name":"tallarium/reverse_proxy_plug","owner":"tallarium","description":"🔛 an Elixir reverse proxy Plug with HTTP/2, chunked transfer and path proxying support","archived":false,"fork":false,"pushed_at":"2025-04-23T11:00:47.000Z","size":442,"stargazers_count":303,"open_issues_count":19,"forks_count":69,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-23T12:19:41.323Z","etag":null,"topics":["chunked-transmission","elixir","http","http-server","plug","proxy","reverse-proxy","reverse-proxy-server"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/tallarium.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.md","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,"zenodo":null},"funding":{"github":["mwhitworth"]}},"created_at":"2018-08-01T16:54:50.000Z","updated_at":"2025-04-23T11:00:50.000Z","dependencies_parsed_at":"2024-01-07T22:48:47.379Z","dependency_job_id":"2855ff87-dcc7-4c87-bad6-80492635dac9","html_url":"https://github.com/tallarium/reverse_proxy_plug","commit_stats":{"total_commits":179,"total_committers":23,"mean_commits":7.782608695652174,"dds":0.8324022346368716,"last_synced_commit":"2d39390ea0b153b7cf98f45faac40e7f6c1ce0a9"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tallarium%2Freverse_proxy_plug","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tallarium%2Freverse_proxy_plug/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tallarium%2Freverse_proxy_plug/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tallarium%2Freverse_proxy_plug/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tallarium","download_url":"https://codeload.github.com/tallarium/reverse_proxy_plug/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254442854,"owners_count":22071878,"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":["chunked-transmission","elixir","http","http-server","plug","proxy","reverse-proxy","reverse-proxy-server"],"created_at":"2024-08-04T09:01:27.318Z","updated_at":"2025-05-16T00:07:19.559Z","avatar_url":"https://github.com/tallarium.png","language":"Elixir","readme":"# ReverseProxyPlug\n\n[![Module Version](https://img.shields.io/hexpm/v/reverse_proxy_plug.svg)](https://hex.pm/packages/reverse_proxy_plug)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/reverse_proxy_plug/)\n[![Total Download](https://img.shields.io/hexpm/dt/reverse_proxy_plug.svg)](https://hex.pm/packages/reverse_proxy_plug)\n[![License](https://img.shields.io/hexpm/l/reverse_proxy_plug.svg)](https://github.com/tallarium/reverse_proxy_plug/blob/master/LICENSE.md)\n[![Last Updated](https://img.shields.io/github/last-commit/tallarium/reverse_proxy_plug.svg)](https://github.com/tallarium/reverse_proxy_plug/commits/master)\n\nA reverse proxy plug for proxying a request to another URL using a choice of\nElixir HTTP client libraries. Perfect when you need to transparently proxy\nrequests to another service but also need to have full programmatic control\nover the outgoing requests.\n\nThis project grew out of a fork of\n[elixir-reverse-proxy](https://github.com/slogsdon/elixir-reverse-proxy).\nAdvantages over the original include more flexible upstreams, zero-delay\nchunked transfer encoding support, HTTP2 support with Cowboy 2, several options\nfor HTTP client libraries and focus on being a composable Plug instead of\nproviding a standalone reverse proxy application.\n\n## Installation\n\nAdd `reverse_proxy_plug` to your list of dependencies in `mix.exs`:\n```elixir\ndef deps do\n  [\n    {:reverse_proxy_plug, \"~\u003e 3.0\"}\n  ]\nend\n```\n\nThen add an HTTP client library, one of:\n- [HTTPoison](https://hex.pm/packages/httpoison)\n- [Tesla](https://hex.pm/packages/tesla)\n- [Finch](https://hex.pm/packages/finch)\n- [Req](https://hex.pm/packages/req)\n\nand configure depending on your choice, e.g.:\n\n```elixir\nconfig :reverse_proxy_plug, :http_client, ReverseProxyPlug.HTTPClient.Adapters.HTTPoison\n```\n\nYou can also set the config as a per-plug basis, which will override any global config.\n\n```elixir\nplug ReverseProxyPlug, client: ReverseProxyPlug.HTTPClient.Adapters.Tesla\n```\n\nEither of those must be set, otherwise the system will attempt to default to the HTTPoison\nadapter or raise if it's not present.\n\n## Usage\n\nThe plug works best when used with\n[`Plug.Router.forward/2`](https://hexdocs.pm/plug/Plug.Router.html#forward/2).\nDrop this line into your Plug router:\n\n```elixir\nforward(\"/foo\", to: ReverseProxyPlug, upstream: \"//example.com/bar\")\n```\n\nNow all requests matching `/foo` will be proxied to the upstream. For\nexample, a request to `/foo/baz` made over HTTP will result in a request to\n`http://example.com/bar/baz`.\n\nYou can also specify the scheme or choose a port:\n```elixir\nforward(\"/foo\", to: ReverseProxyPlug, upstream: \"https://example.com:4200/bar\")\n```\n\nThe `:upstream` option should be a well formed URI parseable by [`URI.parse/1`](https://hexdocs.pm/elixir/URI.html#parse/1),\nor a function with zero or one arity which returns one. If it is a function, it will be\nevaluated for every request. If the function is arity one, the `Conn` struct will be\npassed to it, in order to have more flexibility in dynamic routing.\n\n### Modifying the client request body\n\nYou can modify various aspects of the client request by simply modifying the\n`Conn` struct. In case you want to modify the request body, fetch it using\n`Conn.read_body/2`, make your changes, and leave it under\n`Conn.assigns[:raw_body]`. ReverseProxyPlug will use that as the request body.\nIn case a custom raw body is not present, ReverseProxyPlug will fetch it from\nthe `Conn` struct directly.\n\n## Configuration options\n\n### Custom HTTP methods\n\nOnly standard HTTP methods in \"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"CONNECT\", \"OPTIONS\",\n\"TRACE\" and \"PATCH\" will be forwarded by default. You can specific define other custom\nHTTP methods in keyword :custom_http_methods.\n\n```elixir\nforward(\"/foo\", to: ReverseProxyPlug, upstream: \"//example.com/bar\", custom_http_methods: [:XMETHOD])\n```\n\n### Preserve host header\n\nA reverse HTTP proxy often has to preserve the original `Host` request header\nwhen making a request to the upstream origin. Presenting a different `Host` to\nthe upstream server can lead to issues related to cookies, redirects, and\nincorrect routing that can become a security concern.\n\nSome HTTP proxies send the original `Host` value in other headers, like\n`Forwarded` or `X-Forwarded-Host`, but those are only useful if the upstream\napplication is coded to read those headers.\n\nBy default, ReverseProxyPlug does not preserve the original header nor does it\nsend the original header in any other form. Use `:preserve_host_header` to make\nupstream requests with the same `Host` header as in the original request.\n\n```elixir\nforward(\"/foo\", to: ReverseProxyPlug, upstream: \"//example.com\", preserve_host_header: true)\n```\n\n### Normalize headers for upstream request\n\nAn upstream request will downcase all request header names by default (`ReverseProxyPlug.downcase_headers/1`)\n\nYou can override this behaviour by passing your own `normalize_headers/1`, which can transform\na list of headers - a list of `{\"header\", \"value\"}` tuples - and return them in the form desired.\nFor instance, you may want to drop certain headers in the upstream request, beyond the usual hop-by-hop\nheaders.\n\n### Response mode\n\n`ReverseProxyPlug` supports two response modes:\n\n- `:stream` (default) - The response from the plug will always be chunk\nencoded. If the upstream server sends a chunked response, ReverseProxyPlug\nwill pass chunks to the clients as soon as they arrive, resulting in zero\ndelay. Not all adapters support the `:stream` response mode currently.\n\n- `:buffer` - The plug will wait until the whole response is received from\nthe upstream server, at which point it will send it to the client using\n`Conn.send_resp`. This allows for processing the response before sending it\nback using `Conn.register_before_send`.\n\nYou can choose the response mode by passing a `:response_mode` option:\n```elixir\nforward(\"/foo\", to: ReverseProxyPlug, response_mode: :buffer, upstream: \"//example.com/bar\")\n```\n\n### Response header processing mode\n\nYou can specify the behaviour of how headers from the upstream response are incorporated\ninto the response that is sent from `reverse_proxy_plug`:\n- `:replace` - use `Conn.put_resp_header` to overwrite any existing headers present\n- `:prepend` - use `Conn.prepend_resp_header` to prepend headers from the upstream to existing\nresponse headers in `conn`.\n\nThe defaults differ per response mode - `:stream_headers_mode` defaults to `:replace`, `:buffer_headers_mode`\nto `:prepend`.\n\n### Client options\n\nYou can pass options to the configured HTTP client. Valid options depend on the HTTP client used.\n\n```elixir\nforward(\"/foo\", to: ReverseProxyPlug, upstream: \"//example.com\", client_options: [timeout: 2000])\n```\n\n### Callback for connection errors\n\nBy default, `ReverseProxyPlug` will automatically respond with 502 Bad Gateway\nin case of network error. To inspect the HTTPoison error that caused the\nresponse, you can pass an `:error_callback` option.\n\n```elixir\nplug(ReverseProxyPlug,\n  upstream: \"example.com\",\n  error_callback: fn error -\u003e Logger.error(\"Network error: #{inspect(error)}\") end\n)\n```\n\nIf you wish to handle the response directly, you can provide a function with\narity 2 where the connection will be passed as the second argument:\n\n```elixir\nplug(ReverseProxyPlug,\n  upstream: \"example.com\",\n  error_callback: fn error, conn -\u003e\n    Logger.error(\"Network error: #{inspect(error)}\")\n    Plug.Conn.send_resp(conn, :internal_server_error, \"something went wrong\")\n  end)\n)\n```\n\nYou can also provide a MFA (module, function, arguments) tuple, to which the\nerror will be inserted as the last argument:\n\n```elixir\nplug(ReverseProxyPlug,\n  upstream: \"example.com\",\n  error_callback: {MyErrorHandler, :handle_proxy_error, [\"example.com\"]}\n)\n```\n\nIf the function specified by the MFA tuple supports two additional arguments,\nthe error and connection will inserted as the last two arguments, respectively.\n\n### Callbacks for responses in streaming mode\n\nIn order to add special handling for responses with particular statuses instead\nof passing them on to the client as usual, provide the `:status_callbacks`\noption with a map from status code to handler:\n\n```elixir\nplug(ReverseProxyPlug,\n  upstream: \"example.com\",\n  status_callbacks: %{404 =\u003e \u0026handle_404/2}\n)\n```\n\nThe handler is called as soon as an `HTTPoison.AsyncStatus` message with the\ngiven status is received, taking the `Plug.Conn` and the options given to\n`ReverseProxyPlug`. It must then consume all the remaining incoming HTTPoison\nasynchronous response parts, respond to the client and return the `Plug.Conn`.\n\n`:status_callbacks` must only be given when `:response_mode` is `:stream`,\nwhich is the default.\n\n## Usage in Phoenix\n\nThe Phoenix default autogenerated project assumes that you'll want to\nparse all request bodies coming to your Phoenix server and puts `Plug.Parsers`\ndirectly in your `endpoint.ex`. If you're using something like ReverseProxyPlug,\nthis is likely not what you want — in this case you'll want to move Plug.Parsers\nout of your endpoint and into specific router pipelines or routes themselves.\n\nOr you can extract the raw request body with a\n[custom body reader](https://hexdocs.pm/plug/1.6.0/Plug.Parsers.html#module-custom-body-reader)\nin your `endpoint.ex`:\n```elixir\nplug Plug.Parsers,\n  body_reader: {CacheBodyReader, :read_body, []},\n  # ...\n```\nand store it in the `Conn` struct with custom plug `cache_body_reader.ex`:\n```elixir\ndefmodule CacheBodyReader do\n  @moduledoc \"\"\"\n  Inspired by https://hexdocs.pm/plug/1.6.0/Plug.Parsers.html#module-custom-body-reader\n  \"\"\"\n\n  alias Plug.Conn\n\n  @doc \"\"\"\n  Read the raw body and store it for later use in the connection.\n  It ignores the updated connection returned by `Plug.Conn.read_body/2` to not break CSRF.\n  \"\"\"\n  @spec read_body(Conn.t(), Plug.opts()) :: {:ok, String.t(), Conn.t()}\n  def read_body(%Conn{request_path: \"/api/\" \u003c\u003e _} = conn, opts) do\n    {:ok, body, _conn} = Conn.read_body(conn, opts)\n    conn = update_in(conn.assigns[:raw_body], \u0026[body | \u00261 || []])\n    {:ok, body, conn}\n  end\n\n  def read_body(conn, opts), do: Conn.read_body(conn, opts)\nend\n```\nwhich then allows you to use the [Phoenix.Router.forward/4](https://hexdocs.pm/phoenix/Phoenix.Router.html#forward/4)\nin the `router.ex`:\n```elixir\n  scope \"/api\" do\n    pipe_through :api\n\n    forward \"/foo\", ReverseProxyPlug,\n      upstream: \u0026Settings.foo_url/0,\n      error_callback: \u0026__MODULE__.log_reverse_proxy_error/1\n\n    def log_reverse_proxy_error(error) do\n      Logger.warn(\"ReverseProxyPlug network error: #{inspect(error)}\")\n    end\n  end\n```\n\n## Copyright and License\n\nCopyright (c) 2018 Tallarium Technologies\n\nReverseProxyPlug is released under the [MIT License](./LICENSE.md).\n","funding_links":["https://github.com/sponsors/mwhitworth"],"categories":["\u003ca id=\"01e6651181d405ecdcd92a452989e7e0\"\u003e\u003c/a\u003e工具","\u003ca name=\"Elixir\"\u003e\u003c/a\u003eElixir"],"sub_categories":["\u003ca id=\"e9f97504fbd14c8bb4154bd0680e9e62\"\u003e\u003c/a\u003e反向代理"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftallarium%2Freverse_proxy_plug","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftallarium%2Freverse_proxy_plug","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftallarium%2Freverse_proxy_plug/lists"}