{"id":13508205,"url":"https://github.com/AntonFagerberg/rackla","last_synced_at":"2025-03-30T10:30:44.250Z","repository":{"id":30439902,"uuid":"33993188","full_name":"AntonFagerberg/rackla","owner":"AntonFagerberg","description":"Open Source API Gateway in Elixir","archived":false,"fork":false,"pushed_at":"2016-09-25T14:00:00.000Z","size":266,"stargazers_count":268,"open_issues_count":0,"forks_count":26,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-29T10:09:10.387Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/AntonFagerberg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2015-04-15T12:44:52.000Z","updated_at":"2025-03-12T14:40:46.000Z","dependencies_parsed_at":"2022-07-31T10:18:21.201Z","dependency_job_id":null,"html_url":"https://github.com/AntonFagerberg/rackla","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonFagerberg%2Frackla","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonFagerberg%2Frackla/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonFagerberg%2Frackla/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AntonFagerberg%2Frackla/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AntonFagerberg","download_url":"https://codeload.github.com/AntonFagerberg/rackla/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246168111,"owners_count":20734390,"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-01T02:00:49.715Z","updated_at":"2025-03-30T10:30:44.202Z","avatar_url":"https://github.com/AntonFagerberg.png","language":"Elixir","funding_links":[],"categories":["Frameworks"],"sub_categories":[],"readme":"# Rackla\n\nRackla is an open source framework for building API gateways. When we say API gateway, we mean to proxy and potentially enhance the communication by transforming the data sent over HTTP between servers and  clients (such as browsers). The communication can be enhanced by throwing away unnecessary data, concatenating multiple requests into a single request or converting the data between different formats. Another possibility is to change existing APIs so that they work in a different way, or merge any amount of existing APIs into a single new one.\n\n![API Gateway](http://www.antonfagerberg.com/images/projects/api-gateway.png)\n\nWith Rackla, you can asynchronously execute multiple HTTP-requests and transform them in any way you want. The results, encapsulated in the `Rackla` type, can be transformed with well known functions such as `map`, `flat_map` and `reduce`. By using the pipe operator in Elixir, you can express your new API end-points as pipelines which start with requests that are piped in to transforming functions and finally piped into a response.\n\nRackla builds on [Plug](https://github.com/elixir-lang/plug) in order to expose new end-points and communicate with clients over HTTP. Internally, it uses [Hackney](https://github.com/benoitc/hackney) to make HTTP requests and [Poison](https://github.com/devinus/poison) for dealing with JSON. A big thank you to everyone involved in these projects!\n\n[The documentation is also available online on HexDocs](http://hexdocs.pm/rackla/).\n\n(This project was initially created as part of my masters's thesis: [Optimising clients with API gateways](https://lup.lub.lu.se/student-papers/search/publication/5469608).)\n\n## Skeleton implementation - ready to use\nYou can clone the [Rackla Skeleton](https://github.com/AntonFagerberg/rackla_skeleton) project in order to get a complete working API gateway with runnable example end-points and tests. The skeleton project contains everything you need to easily get started - it contains all \"infrastructure\" needed to easily expose (and use) your end-points, deploy your API gateway to a cloud service such as Heroku or build a Docker image. \n\n## Using Rackla as a library\nIn  `mix.exs`, add `:rackla` and `:cowboy` as dependencies:\n\n```elixir\ndefp deps do\n  [\n    {:rackla, \"~\u003e 1.2\"},\n    {:cowboy, \"~\u003e 1.0\"} # Or your web server of choice (which works with Plug)\n  ]\nend\n```\n\nand add `:rackla` and `:cowboy` to applications:\n\n```elixir\ndef application do\n  [applications: [:logger, :rackla, :cowboy]]\nend\n```\n\nYou should read [Plug's documentation](https://github.com/elixir-lang/plug) about defining a router and setting up supervised handlers. You can always look at the [Rackla Skeleton](https://github.com/AntonFagerberg/rackla_skeleton) project for inspiration about how to set everything up.\n\n([Remix](https://github.com/AgilionApps/remix) is a nice library you can use to get hot reloading during development.)\n\n## Example usages\n\n### OpenWeatherMap API (JSON)\n*OpenWeatherMap has started requiring that you sign up and get an API key, the example below does not reflect that.*\n\nOpenWeatherMap has an API with the following end-point that we're going to use: `http://api.openweathermap.org/data/2.5/weather?q=Malmo,SE`. That end-point lets us specify one city to retrieve weather data from, defined by: `?q=Malmo,SE` (found at the end of the URL). \n\nThis will return the weather data for Malmö in Sweden:\n\n```json\n{\n  \"coord\":{\n    \"lon\":13,\n    \"lat\":55.6\n  },\n  \"sys\":{\n    \"message\":0.0071,\n    \"country\":\"Sweden\",\n    \"sunrise\":1432262690,\n    \"sunset\":1432322670\n  },\n  \"weather\":[\n    {\n       \"id\":802,\n       \"main\":\"Clouds\",\n       \"description\":\"scattered clouds\",\n       \"icon\":\"03d\"\n    }\n  ],\n  \"base\":\"stations\",\n  \"main\":{\n    \"temp\":288.737,\n    \"temp_min\":288.737,\n    \"temp_max\":288.737,\n    \"pressure\":1030.12,\n    \"sea_level\":1035.87,\n    \"grnd_level\":1030.12,\n    \"humidity\":58\n  },\n  \"wind\":{\n    \"speed\":5.36,\n    \"deg\":248.501\n  },\n  \"clouds\":{\n    \"all\":36\n  },\n  \"dt\":1432303003,\n  \"id\":2712995,\n  \"name\":\"Malmo\",\n  \"cod\":200\n}\n```\n\nWhat we're interested in is the `temp` value stored in `main`, and the `name` field.\nThe goal with the new end-point in our API gateway is to take an arbitrary amount of cities\nseparated by `|` in the query string, such as: `/temperature?malmo,se|halmstad,se|copenhagen,dk|san francisco,us|stockholm,se` and return a list of temperatures for these cities.\n\nWe want our JSON response to look like this:\n\n```json\n[\n   {\n      \"Malmo\":289.751\n   },\n   {\n      \"Halmstad\":286.751\n   },\n   {\n      \"Copenhagen\":287.487\n   },\n   {\n      \"San Francisco\":285.087\n   },\n   {\n      \"Stockholm\":288.801\n   }\n]\n```\n\nAnd this is how the code in Rackla will look like (explained below):\n\n```elixir\nget \"/temperature\" do\n  temperature_extractor = fn(http_response) -\u003e\n    case http_response do\n      {:error, reason} -\u003e\n        \"HTTP request failed because: #{reason}\"\n      \n      ok_response -\u003e\n        case Poison.decode(ok_response) do\n          {:ok, json_decoded} -\u003e\n            Map.put(%{}, json_decoded[\"name\"], json_decoded[\"main\"][\"temp\"])\n\n          {:error, reason} -\u003e\n            \"Failed to decode response because: #{inspect(reason)}\"\n        end\n    end\n  end\n\n  conn.query_string\n  |\u003e String.split(\"|\")\n  |\u003e Enum.map(\u0026(\"http://api.openweathermap.org/data/2.5/weather?q=#{\u00261}\"))\n  |\u003e Rackla.request\n  |\u003e Rackla.map(temperature_extractor)\n  |\u003e Rackla.response(json: true, compress: true)\nend\n```\n\nLet us walk through the code and explain what is happening here. First we define our endpoint `/temperature` as we would normally do in Plug. Then we define a function which we'll use in the `Rackla.map` function in the pipeline, let's get back to it later and first look at the pipeline defined at the bottom of our end-point.\n\nHere's what the pipeline will do:\n\n * Get the query string from `conn` (implicitly provided by Plug), in this example it will be the string: `\"malmo,se|halmstad,se|copenhagen,dk|san francisco,us|stockholm,se\"`.\n * Split the string on `|` to get a list instead: `[\"malmo,se\", \"halmstad,se\", \"copenhagen,dk\", \"san francisco,us\", \"stockholm,se\"]`.\n * Map over the list and convert the city strings into a list of URLs to the OpenWeatherMap API.\n * Request all URLs, this will return a `Rackla` type which will contain (when ready) the response or an `:error` tuple on failure for each URL.\n * Map over the results using our function `temperature_extractor` (explained below).\n * Respond to the client. We use the options `:json` to encode our response in JSON format and set the appropriate headers (this will take the Elixir map type returned in `temperature_extractor` and convert it to a JSON map and put all responses in a JSON list). We can also use `:compress` in order to compress the result with gzip compression (when `:compress` is `true`, Rackla will check the request headers to make sure that the client accepts gzip - you can also set it to `:force` to always respond with gzip).\n \nSo let's walk through what the function `temperature_extractor` does. First of all, we pattern match to make sure that our HTTP request hasn't failed. If it has failed, we simply return a string with the reason for the failure. If our HTTP request has succeed, we try to decode it from JSON format using the library Poison. If the decoding is successful, we create a new Elixir map containing the name and the temperature for the response. This will be, for example, `%{\"Malmo\" =\u003e 289.751}` in one of the responses. The `response` function will later be able to encode this Elixir map into a JSON map automatically.\n\nDone! That's all we need to do to make it work!\n\n### Instagram (Base64 encoded images)\nIn this example, we will instead of providing an API, actually serve an entire HTML page that we can view in our browser. While serving a full HTML page is not really Rackla's main goal, this example illustrates how we can make recursive requests and work with chunked responses.\n\n```elixir\nget \"/instagram\" do\n  \"\u003c!doctype html\u003e\u003chtml lang=\\\"en\\\"\u003e\u003chead\u003e\u003c/head\u003e\u003cbody\u003e\"\n  |\u003e Rackla.just\n  |\u003e Rackla.response\n\n  \"https://api.instagram.com/v1/users/self/feed?count=50\u0026access_token=\" \u003c\u003e conn.query_string\n  |\u003e Rackla.request\n  |\u003e Rackla.flat_map(fn(response) -\u003e\n    case response do\n      {:error, error} -\u003e\n        Rackla.just(error)\n\n      _ -\u003e\n        case Poison.decode(response) do\n          {:ok, json} -\u003e\n            json\n            |\u003e Map.get(\"data\")\n            |\u003e Enum.map(\u0026(\u00261[\"images\"][\"standard_resolution\"][\"url\"]))\n            |\u003e Rackla.request\n            |\u003e Rackla.map(fn(img_data) -\u003e\n              case img_data do\n                {:error, error} -\u003e\n                  error\n\n                _ -\u003e\n                  \"\u003cimg src=\\\"data:image/jpeg;base64,#{Base.encode64(img_data)}\\\" height=\\\"150px\\\" width=\\\"150px\\\"\u003e\"\n              end\n            end)\n\n          {:error, _} -\u003e\n            Rackla.just(response)\n        end\n    end\n  end)\n  |\u003e Rackla.response\n\n  \"\u003c/body\u003e\u003c/html\u003e\"\n  |\u003e Rackla.just\n  |\u003e Rackla.response\nend\n```\n    \nOnce again, let's go through the code to see what is happening. We start by exposing the end-point `/instagram` as we normally do in Plug. Then we define the first of three pipelines. We store some HTML code in a string, convert it into a `Rackla` type with the function `just` and use `response` to send it to the client. \n\nAfter we've responded with the HTML code, we can move on to the big middle pipeline. In it, we will call the an Instagram API end-point - the actual response can be seen here: [instagram.com/developer/endpoints/users/#get_users_feed](https://instagram.com/developer/endpoints/users/#get_users_feed). We have to pass an access token to the Instagram API so we let the user supply it via the query string in the browser and add it to the Instagram URL. We call `request` with the URL and then use `flat_map`. The reason for using `flat_map` is because it gives us an easy way to create new requests based on the responses from previous requests. \n\nIf we get a response from the Instagram API, we decode it from JSON format to an Elixir data structure with Poison. We then extract the list stored in the key `\"data\"` in the Instagram response. This will give us a list of items from our Instagram feed. We then map over these items and extract the URL for the images in standard resolution which will give us a list of URLs pointing to images. We can now pipe this list in to the `request` function to fetch all these images. \n\nNow, we map over the results - the results will now be binary image data! We can take this binary image data, Base64 encode it and place it inside a HTML image tag. By doing so, the browser can render the response chunks as images directly on our page. In the the \"outer\" pipeline, we end it with the `response` function which will send the HTML image tags to the client, in this case the browser. (It is important to notice that `response` is only used in the outer pipeline and not in the inner pipeline created inside `flat_map`).\n\nFinally, we create the last pipeline which will send the closing HTML tags.\n\nWhat is cool about this approach is that the image requests are executed concurrently. This means that the image HTML tags will be sent to the client as soon as they are available (as soon as we get a response from any of the image requests). We only make one request to our API gateway from the client and we only send one response to the client from our API gateway but the images will be sent in chunks so they will show up one after another in the browser just like if we requested them individually.\n\nWe will also notice, in this example, that the order is nondeterministic - meaning that we will (most likely) get a different order in which the images are sent every time we refresh the page. If we wanted to preserve the ordering of the images, we could either send the ordering with the chunks and let the client code render the images on the correct position - or we could set the `:sync` option to `true` in `response` which would then wait for the responses and then send them in the appropriate order.\n\n### Basic authentication\nSending the username and password as headers:\n\n```elixir\nget \"/auth-example\" do\n  url = \"http://some-url.com\"\n  headers = %{\"Authorization\" =\u003e \"Basic #{Base.encode64(\"username:password\")}\"}\n\n  %Rackla.Request{url: url, headers: headers}\n  |\u003e request\n  |\u003e response\nend\n```\n\nAdding them as part of the URL:\n\n```elixir\nget \"/auth-example\" do\n  username = \"my_username\"\n  password = \"my_password\"\n  \n  \"http://#{username}:#{password}@some-url.com\"\n  |\u003e request\n  |\u003e response\nend\n```\n\n### Using a SOCKS5 Proxy or HTTP (Connect) Tunnel\nFor more detailed information about using proxies, see the documentation for `Rackla.Proxy`.\n\n#### SOCKS5 using request setting\n```elixir\n%Rackla.Request{url: \"http://api.ipify.org\", options: %{proxy: %Rackla.Proxy{type: :socks5, host: \"localhost\", port: 8080}}} \n|\u003e Rackla.request \n|\u003e Rackla.collect\n```\n\n#### HTTP Tunnel using global setting\n```elixir\n\"http://api.ipify.org\" \n|\u003e Rackla.request(proxy: %Rackla.Proxy{type: :connect, host: \"localhost\", port: 8080}) \n|\u003e Rackla.collect\n```\n\n### Adding processing time to a JSON response\n```elixir\nget \"/resp-time\" do\n  current_millis = fn() -\u003e\n    {mega, sec, micro} = :os.timestamp\n    (mega*1000000 + sec)*1000 + round(micro/1000)\n  end\n\n  urls = [\"http://date.jsontest.com/\", \"http://api.openweathermap.org/data/2.5/weather?q=Malmo,se\"]\n\n  start_time = current_millis.()\n\n  urls\n  |\u003e request\n  |\u003e map(fn(http_response) -\u003e\n\n    end_time = current_millis.() - start_time\n\n    case http_response do\n      {:error, reason} -\u003e\n        \"HTTP request failed because: #{reason} in time #{end_time}\"\n\n      ok_response -\u003e\n        case Poison.decode(ok_response) do\n          {:ok, json_decoded} -\u003e\n            Map.put(json_decoded, \"response_time\", end_time)\n\n          {:error, reason} -\u003e\n            \"Failed to decode response because: #{inspect(reason)} in time #{end_time}\"\n        end\n    end\n  end)\n  |\u003e response(json: true)\nend\n```\n\n### Reverse proxy / Request forwarding\n```elixir\nget \"/test/a-simple-request-proxy\" do\n  # You should check for errors\n  {:ok, the_request} = incoming_request()\n  \n  the_request\n  |\u003e Map.put(:url, \"http://new-url.com\")\n  |\u003e request\n  |\u003e response\nend\n```\n\n### More examples\nA collection of smaller example end-points can be found in found [lib/rackla/router.ex](https://github.com/AntonFagerberg/rackla/blob/master/lib/router.ex) which illustrates additional techniques that can be used in Rackla.\n\n## The Rackla type\n`Rackla` is also the name of the type used in all of Rackla's functions. Internally, it consists of a list of Elixir processes which communicate with message passing according to a protocol defined inside Rackla (these processes can themselves contain even more nested `Rackla` types). The `Rackla` type should never be modified directly!\n\nThe `Rackla` type is created with `request` which converts one or many HTTP requests to a single `Rackla` type. You can also encapsulate normal Elixir types in a `Rackla` type with the functions `just` or `just_list`.\n\nMost functions, like `map`, `flat_map` and `reduce`, defined in Rackla will take a `Rackla` type and return a new `Rackla` type.\n\nThe `response` function converts the `Rackla` type to a HTTP response which is sent to the client by utilizing `Plug`. You can also convert a `Rackla` type into \"normal\" Elixir types with the function `collect`.\n\nIt is important to note that once a `Rackla` type has been used, it is no longer valid:\n\n```elixir\na = Rackla.just(1)\nb = a |\u003e Rackla.map(\u0026(\u00261 + 1))\n# a is now \"dead\" and can't be used anymore\n```\n\nUnder normal circumstances, the `Rackla` type should be \"invisible\". Think of it as the box which the data is transported inside and that all the functions you use automatically opens the box, takes out the value for you and then puts it in a new box when you're done with it.\n\n(Or simply think of it as a monad if you're comfortable with that.)\n\n## Function overview\n\n[The documentation is also available online](http://hexdocs.pm/rackla/).\n\n### request\nTakes a single string (URL) or a `Rackla.Request` struct and  executes a HTTP \nrequest to the defined server. You can, by using the  `Rackla.Request` struct,\nspecify more advanced options for your request such  as which HTTP verb to use\nbut also individual connection timeout limits etc.  You can also call this \nfunction with a list of strings or `Rackla.Request` structs in order to \nperform multiple requests concurrently.\n\nThis function will return a `Rackla` type which will contain the results \nfrom the request(s) once available or an `:error` tuple in case of failures\nsuch non-responding servers or DNS lookup failures. Per default, on success, it \nwill only contain the response payload but the entire response can be used by \nsetting the option `:full` to true.\n\nOptions:\n\n * `:full` - If set to true, the `Rackla` type will contain a `Rackla.Response`\n struct with the status code, headers and body (payload), default: false.\n * `:connect_timeout` - Connection timeout limit in milliseconds, default: \n `5_000`.\n * `:receive_timeout` - Receive timeout limit in milliseconds, default: \n `5_000`.\n * `:insecure` - If set to true, SSL certificates will not be checked, \n default: `false`.\n \nIf you specify any options in a `Rackla.Request` struct, these will overwrite\nthe options passed to the `request` function for that specific request.\n\n### map\nReturns a new `Rackla` type, where each encapsulated item is the result of \ninvoking `fun` on each corresponding encapsulated item.\n\nExample:\n\n```elixir\nRackla.just_list([1,2,3]) |\u003e Rackla.map(fn(x) -\u003e x * 2 end) |\u003e Rackla.collect\n[2, 4, 6]\n```\n    \n### flat_map\nTakes a `Rackla` type, applies the specified function to each of the \nelements encapsulated in it and returns a new `Rackla` type with the \nresults. The given function must return a `Rackla` type.\n\nThis function is useful when you want to create a new request pipeline based\non the results of a previous request. In those cases, you can use \n`Rackla.flat_map` to access the response from a request and call \n`Rackla.request` inside the function since `Rackla.request` returns a \n`Rackla` type.\n\nExample:\n\n```elixir\nRackla.just_list([1,2,3]) |\u003e Rackla.flat_map(fn(x) -\u003e Rackla.just(x * 2) end) |\u003e Rackla.collect\n[2, 4, 6]\n```  \n\n### reduce\nInvokes `fun` for each element in the `Rackla` type passing that element and\nthe accumulator `acc` as arguments. `fun`s return value is stored in `acc`. The \nfirst element of the collection is used as the initial value of `acc` (you can \nalso use `Rackla.reduce/3` and specify your own accumulator). Returns the \naccumulated value inside a `Rackla` type.\n\nExample:\n\n```elixir\nRackla.just_list([1,2,3]) |\u003e Rackla.reduce(fn (x, acc) -\u003e x + acc end) |\u003e Rackla.collect\n6\n```\n\n### just\nTakes any type an encapsulates it in a `Rackla` type.\n\nExample:\n\n```elixir\nRackla.just([1,2,3]) |\u003e Rackla.map(\u0026IO.inspect/1)\n[1, 2, 3]\n```\n\n### just_list\nTakes a list of and encapsulates each of the containing elements separately \nin a `Rackla` type.\n\nExample:\n\n```elixir\nRackla.just_list([1,2,3]) |\u003e Rackla.map(\u0026IO.inspect/1)\n3\n2\n1\n```\n\n### collect\nReturns the element encapsulated inside a `Rackla` type, or a list of \nelements in case the `Rackla` type contains many elements.\n\nExample:\n\n```elixir\nRackla.just_list([1,2,3]) |\u003e Rackla.collect\n[1,2,3]\n```\n\n### join\nReturns a new `Rackla` type by joining the encapsulated elements from two\n`Rackla` types.\n\nExample:\n\n```elixir\nRackla.join(Rackla.just(1), Rackla.just(2)) |\u003e Rackla.collect\n[1, 2]\n```\n    \n### response\nConverts a `Rackla` type to a HTTP response and send it to the client by\nusing `Plug.Conn`. The `Plug.Conn` will be taken implicitly by looking for a \nvariable named `conn`. If you want to specify which `Plug.Conn` to use, you \ncan use `Rackla.response_conn`.\n\nStrings will be sent as is to the client. If the `Rackla` type contains any \nother type such as a list, it will be converted into a string by using `inspect`\non it. You can also convert Elixir data types to JSON format by setting the\noption `:json` to true.\n\nUsing this macro is the same as writing:\n    `conn = response_conn(rackla, conn, options)`\n\nOptions:\n \n * `:compress` - Compresses the response by applying a gzip compression to it.\n When this option is used, the entire response has to be sent in one chunk. \n You can't reuse the `conn` to send any more data after `Rackla.response` with\n `:compress` set to `true` has been invoked. When set to `true`, Rackla will\n check the request header `content-encoding` to make sure the client accepts\n gzip responses. If you want to respond with gzip without checking the\n request headers, you can set `:compress` to `:force`.\n * `:json` - If set to true, the encapsulated elements will be converted into\n a JSON encoded string before they are sent to the client. This will also set\n the header \"Content-Type\" to the appropriate \"application/json; charset=utf-8\".\n \n### incoming_request\nConvert an incoming request (from `Plug`) to a `Rackla.Request`.\nIf `options` is specified, it will be added to the `Rackla.Request`.\nFor valid options, see documentation for `Rackla.Request`.\n\nReturns either `{:ok, Rackla.Request}` or `{:error, reason}` as per \n`:gen_tcp.recv/2`.\n\nThe `Plug.Conn` will be taken implicitly by looking for a variable named \n`conn`. If you want to specify which `Plug.Conn` to use, you can use \n`Rackla.incoming_request_conn`.\n\nUsing this macro is the same as writing:\n `conn = incoming_request_conn(conn, options)`\n\nFrom `Plug.Conn` documentation:\nBecause the request body can be of any size, reading the body will only work \nonce, as Plug will not cache the result of these operations. If you need to \naccess the body multiple times, it is your responsibility to store it. Finally \nkeep in mind some plugs like Plug.Parsers may read the body, so the body may \nbe unavailable after being accessed by such plugs.\n\n## License\nRackla source code is released under Apache 2 License. Check LICENSE file for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAntonFagerberg%2Frackla","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAntonFagerberg%2Frackla","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAntonFagerberg%2Frackla/lists"}