Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Way to Read Request Body As String #459

Closed
rylev opened this issue Nov 5, 2014 · 61 comments
Closed

Way to Read Request Body As String #459

rylev opened this issue Nov 5, 2014 · 61 comments

Comments

@rylev
Copy link

rylev commented Nov 5, 2014

I need to a way to access the body of a request as a raw string. Plug supports this functionality (https://github.com/elixir-lang/plug/blob/master/lib/plug/conn.ex#L418-L448) but as the docs say this can only be done once. Once the body is consumed, trying to read the body will result in an empty string.

I believe that if you use a Phoenix controller, reading the body as string is impossible. If a client sends a request with the content-type set to application/json Phoenix will automatically parse the body as params meaning any future attempts by client code to access the body will result in an empty string.

Is there any way for client code inside of a controller to access the body as a raw string?

@chrismccord
Copy link
Member

Something like this should work:

defmodule MyApp.Router
  ...
  pipeline :browser do
    plug :copy_req_body
    plug :super
  end
  ...

  defp copy_req_body(conn, _) do
    Plug.Conn.put_private(conn, :my_app_body, Plug.Con..read_body(conn))
  end
end

defmodule MyAppController do
  ...
  def show(conn, params) do
    the_body = conn.private[:my_app_body]
  end
end

Note we don't update the conn with the read state, so it can be read downstream by the plug parsers. Let me know if that works.

@rylev
Copy link
Author

rylev commented Nov 5, 2014

@chrismccord thanks for the help. I had to modify your answer a bit (as my app is an API not a backend for a browser application). It still didn't work, let me know if my answer should work or if I messed something up:

defmodule MyApp.Router
    ...
    pipeline :api do
      plug :copy_req_body
      plug :super
    end
    pipe_through :api
   ...
end
# The rest is the same

Inside of copy_req_body the body is still an empty string. However, in the log output the parameters are still populated correctly.

@chrismccord
Copy link
Member

@rylev ah, please try this:

defmodule MyApp.Router
    ...
    pipeline :before do
      plug :copy_req_body
      plug :super
    end
   ...
end

@rylev
Copy link
Author

rylev commented Nov 5, 2014

@chrismccord that works :-) thanks! Just wondering: Is this the best way to handle this use case? I'm guessing the need for the raw body is seldom enough that this work around is fine, but could there be a nice API for optionally storing the raw body on the conn?

@chrismccord
Copy link
Member

We should at least come up with a way to make it more performant since you don't need to read the request body on every single request. The easiest solution would be to match on the path_info. Let me get back to my desk and I'll have a better solution.

@chrismccord
Copy link
Member

Something like this would be a better approach outside of a mechanism in plug to keep the raw body around. This dance only reads the body twice for the API stack. Not beautiful, but I think it works for this less common use-case. Please reopen if you have issues. Thanks!

 defmodule MyApp.Router
  ...
  @api_mount "/api"

  pipeline :before do
    plug MyApp.Plugs.CopyReqBody, mount: @api_mount
    plug :super
  end

  scope @api_mount do
    pipe_through :api
    ...
  end
end

defmodule MyApp.Plugs.CopyReqBody do
  import Plug.Conn

  def init(mount: "/" <> mount), do: mount

  defp call(%Plug.Conn{path_info: [mount | _rest]}, mount) do
    put_private(conn, :my_app_body, read_body(conn))
  end
  defp call(conn, _), do: conn
end

defmodule MyAppController do
  ...
  def show(conn, params) do
    the_body = conn.private[:my_app_body]
  end
end

@hamiltop
Copy link

Is this still a valid approach?

@chrismccord
Copy link
Member

yup

@hamiltop
Copy link

I'm a little confused about :before and :super in the example:

pipeline :before do
    plug MyApp.Plugs.CopyReqBody, mount: @api_mount
    plug :super
end

Are these a special pipeline and plug that don't exist anymore? Or should I be including [:before] in my pipe_through calls? What's :super?

When I just add it to the existing pipelines, I get the body as an empty string, as we reported before you suggested :before.

@chrismccord
Copy link
Member

Sorry, ignore :before and :super as they are long gone, but the plug approach is still the same. You can plug the CopyReq body above your Plug.Parsers declaration in your endpoint and it will copy the body before the parsers consume it

@hamiltop
Copy link

Ahhh. That did the trick. For future reference by the community, a bare minimum solution (in the endpoint.ex):

  defp copy_req_body(conn, _) do
    {:ok, body, _} = Plug.Conn.read_body(conn)
    Plug.Conn.put_private(conn, :raw_body, body)
  end
  plug :copy_req_body
  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

As stated, that doesn't let you filter on only certain endpoints and can be pretty wasteful, but it's enough of a bare minimum example that anyone should be able to work with it.

Thanks!

@hamiltop
Copy link

New problem encountered: This only works with a body size of 1024 or less (it hangs with 1025). Not capturing the body works fine with > 1024, but if we capture the body we hang when we try to parse it later.

@lancehalvorsen
Copy link
Member

Seems like you can configure the length and read length for Plug.Conn.read_body/2, but the defaults should be fine for this case. Is it possible that you are changing the defaults somewhere?

http://hexdocs.pm/plug/Plug.Conn.html#read_body/2

Edit: "Is it possible that the defaults are being reset somewhere."

@hamiltop
Copy link

My hypothesis is that read_body can only be called once. However, if the body is less than 1024, it's prefetched and then the read-once doesn't apply. However, once you have to fetch the next pit of the body, the first 1024 is thrown out. This causes the later read_body to timeout. Still digging through source code to confirm, but "Because the request body can be of any size, reading the body will only work once, as Plug will not cache the result of these operations."

@ksol
Copy link

ksol commented Nov 7, 2015

I tend to concur with @hamiltop. I was testing something very similar (reading & storing the entire body), and was trying to get it to support when :more is returned. Once the whole body has been read (ie :ok is returned), the subsequent call hangs for a few seconds then returns a timeout.

@endeepak
Copy link

I added the code suggested above and facing same :timeout issue as @hamiltop & @ksol. Stack trace

2015-11-11T03:40:26.862054+00:00 app[web.1]: 03:40:26.861 request_id=59bb986b-303f-4376-be5b-f4225a089321 [info] POST /stub_urls/1dd4e5b067ad90adb1cc169c
2015-11-11T03:40:41.864557+00:00 app[web.1]: 03:40:41.864 [error] #PID<0.345.0> running StubOnWeb.Endpoint terminated
2015-11-11T03:40:41.864561+00:00 app[web.1]: Server: stubonweb.herokuapp.com:80 (http)
2015-11-11T03:40:41.864562+00:00 app[web.1]: Request: POST /stub_urls/1dd4e5b067ad90adb1cc169c
2015-11-11T03:40:41.864563+00:00 app[web.1]: ** (exit) an exception was raised:
2015-11-11T03:40:41.864564+00:00 app[web.1]:     ** (CaseClauseError) no case clause matching: {:error, :timeout}
2015-11-11T03:40:41.864565+00:00 app[web.1]:         (plug) lib/plug/parsers/urlencoded.ex:10: Plug.Parsers.URLENCODED.parse/5
2015-11-11T03:40:41.864565+00:00 app[web.1]:         (plug) lib/plug/parsers.ex:186: Plug.Parsers.reduce/6
2015-11-11T03:40:41.864566+00:00 app[web.1]:         (stub_on_web) lib/stub_on_web/endpoint.ex:1: StubOnWeb.Endpoint.phoenix_pipeline/1
2015-11-11T03:40:41.864567+00:00 app[web.1]:         (stub_on_web) lib/phoenix/endpoint/render_errors.ex:34: StubOnWeb.Endpoint.call/2
2015-11-11T03:40:41.864568+00:00 app[web.1]:         (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
2015-11-11T03:40:41.864569+00:00 app[web.1]:         (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4
2015-11-11T03:40:41.854441+00:00 heroku[router]: at=info method=POST path="/stub_urls/1dd4e5b067ad90adb1cc169c" host=stubonweb.herokuapp.com request_id=59bb986b-303f-4376-be5b-f4225a089321 fwd="106.216.176.210" dyno=web.1 connect=0ms service=15003ms status=500 bytes=243

More context available on http://stackoverflow.com/questions/33637418/elixir-phoenix-on-heroku-timeout-error-after-15-seconds and ninenines/cowboy#833 (comment)

Source Code: https://github.com/endeepak/stub_on_web

@chrismccord
Copy link
Member

@endeepak To be clear, this only happens to you when you try to read/copy the request body, and does not happen if you use normal Plug.Parsers without the custom body copy?

@endeepak
Copy link

@chrismccord Yes. This issue started after adding code to copy request body. Other observations which might help

  • This issue happens intermittently on my Heroku deployment. The request succeeded without issue twice in 10 attempts, specifically first 2 requests after switching network
  • This issue does not happen (or hard to reproduce) when I run on local with same prod config

@chrismccord
Copy link
Member

The "workaround" for now would be to not copy the request body. What is your usecase for needing the raw body? Also, I'm not positive this is a real cowboy issue without digging into how plug itself chunks the body reads.

@endeepak
Copy link

StubOnWeb is a stub http server which needs to record request and response for calls made to stubbed urls. Hence the workaround to not copy won't help. I'll dig into the code and try few stuff. Thanks for the help. If I find anything useful, I'll update here.

@hamiltop
Copy link

My use case was signed requests from github webhooks. I needed the raw body
to verify the signature. I ended up not using Phoenix for that endpoint and
just built a normal plug that halts the connection.

On Tue, Nov 10, 2015 at 9:14 PM Deepak Narayana Rao <
notifications@github.com> wrote:

StubOnWeb is a stub http server which needs to record request and response
for calls made to stubbed urls. Hence the workaround to not copy won't
help. I'll dig into the code and try few stuff. Thanks for the help. If I
find anything useful, I'll update here.


Reply to this email directly or view it on GitHub
#459 (comment)
.

@hunterboerner
Copy link

My use case is actually the same as @hamiltop's. GH Webhooks are a blast! /s

@hamiltop
Copy link

@chrismccord
Copy link
Member

Perhaps the simplest short-term solution would be to have a custom parser(s) that copies the body?

plug Plug.Parsers,
    parsers: [MyApp.Parsers.URLENCODED, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

...
defmodule MyApp.Parsers.URLENCODED do
  @moduledoc """
  Parses urlencoded request body, and optionally copies the raw body

  Copies raw body to `:raw_body` private assign when `:copy_raw_body` 
  private assign is true
  """

  @behaviour Plug.Parsers
  alias Plug.Conn

  def parse(conn, "application", "x-www-form-urlencoded", _headers, opts) do
    case Conn.read_body(conn, opts) do
      {:ok, body, conn} ->
        Plug.Conn.Utils.validate_utf8!(body, "urlencoded body")
        decoded_body = Plug.Conn.Query.decode(body)
        if conn.private[:copy_raw_body] do
          {:ok, decoded_body, Plug.Conn.put_private(conn, :raw_body, body)}
        else
          {:ok, decoded_body, conn}
        end
      {:more, _data, conn} ->
        {:error, :too_large, conn}
    end
  end

  def parse(conn, _type, _subtype, _headers, _opts) do
    {:next, conn}
  end
end

@hunterboerner
Copy link

@chrismccord this was my temp fix: https://github.com/rabbit-ci/backend/blob/002b9b6daba0803aad4d4bba84c28b3827c4ba8b/apps/rabbitci_core/lib/rabbitci_core/json_parser.ex

@endeepak
Copy link

Thanks @chrismccord, your solution works for now. I'll keep an eye on this thread for the proper fix.
Thanks @hunterboerner, I have changed JSON parser also on similar lines.

For reference, commit with this fix endeepak/stub_on_web@4719255

Gazler pushed a commit that referenced this issue Sep 6, 2017
Fix typo in model testing guide
@ch-andrewrhyne
Copy link

I'm having trouble with this when implementing a stripe webhook and handing authentication. The body appears to be empty, which is needed for Stripe.Webhook.construct_event/3

https://github.com/sikanhe/stripe-elixir

Any ideas?

@hamiltop
Copy link

@ch-andrewrhyne here's a super rough implementation of github webhooks that reads the body. it's just a Plug, no phoenix, but does the job: https://github.com/hamiltop/ashes/blob/master/lib/ashes/github_webhook_plug.ex

@ch-andrewrhyne
Copy link

Useful, but my issue is that I don't know how to access the raw body on the conn within my plug that I am using to authenticate stripe webhook requests. It appears that Phoenix has already parsed the body by the time my plug is invoked. Is there a way to stash away the raw body before Phoenix processes the JSON?

@TechMagister
Copy link

@ch-andrewrhyne here is the solution I use : https://gist.github.com/TechMagister/1a01b22c205309238617fb1fef1603a4

It's not perfect but it's the less intrusive way I found.

@grounded-warrior
Copy link

@ch-andrewrhyne did you get this working? I am running into the same issue

@atomkirk
Copy link
Contributor

Sharing my solution for those working with stripe webhooks:

https://gist.github.com/atomkirk/e05fbab86f34331ffe812a1b98f63851

@curthasselschwert
Copy link

Sharing my solution in case someone is looking for a lite-touch way to do this. My use case was similar to others here: verifying a Shopify request. I added a plug in Endpoint that reads the body only on specific paths, and then removes the content-type header from the request. Removing the content-type header effectively skips the Parsers plug. This was fine in my scenario because I just wanted to get a signature for the raw body. If you need to parse the body, you may want to store the content-type in conn.priv so it can be accessed later.

In MyAppWeb.Endpoint:

defmodule MyAppWeb.Endpoint do
  ...

  defp put_raw_req_body(conn, _) do
    case String.match?(conn.request_path, ~r[shopify/webhooks/*]) do
      false -> conn
      true ->
        {:ok, body, _} = Plug.Conn.read_body(conn)

        conn
        |> Plug.Conn.put_private(:raw_body, body)
        |> Plug.Conn.delete_req_header("content-type")
    end
  end

  plug :put_raw_req_body

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

  ...
end

@josevalim
Copy link
Member

Thanks @curthasselschwert! Note you can use path.info instead of request path and that should be quite faster:

case conn.path_info, ~r[shopify/webhooks/*]) do
  ["shopify", "webhooks" | _] ->
        {:ok, body, conn} = Plug.Conn.read_body(conn)

        conn
        |> Plug.Conn.put_private(:raw_body, body)
        |> Plug.Conn.delete_req_header("content-type")
  _ ->
    conn
end

Also note that Plug v1.6 will allow this to be done via an option to Plug.Parsers.

@nitin21
Copy link

nitin21 commented Jul 24, 2018

I have encountered this problem as well. @curthasselschwert when I try Plug.Conn.read_body(conn), I get an empty string in my body. What is this body supposed to be? The body_params? Any idea why it could be empty? I am definitely sending data and I can see it in body_params under conn.

@ryanwinchester
Copy link

ryanwinchester commented Jul 30, 2018

@nitin21 the body can only be read once, and it is usually done in a Parser plug. Please see the new-ish :body_reader option on Plug.Parsers added in v1.5.1 of Plug:

https://hexdocs.pm/plug/Plug.Parsers.html#module-custom-body-reader

@marcusbaguley
Copy link

My problem was interesting and solved using the above information. I will state it here incase something else is going on in the guts of handling a web request.
POSTing application/xml to a controller, and doing a conn.read_body would work locally, but deployed to ECS behind ALB it would intermittently fail with ALB 502 errors. (Perhaps 1 out of 5 requests, and only when executed in quick succession with request bodies > 1k).

These failed requests would not be logged by Phoenix - even adding onrequest and onresponse Cowboy hooks did not log the request. I cannot explain this but the fix was to create an explicate Plug.Parsers for application/xml - and do the read_body in the Parser - and not in the controller.

Another strange thing I noticed, (and only behind an AWS ALB / ECS) if you POST application/xml (with no explicate MIME handler / rejection), and never do a read_body the request is never processed, ie - the client (curl), just hangs until you kill it - this is perhaps a DoS vector.

In local mode (curl to Docker/Phoenix) none of these errors could be replicated.

@minhajuddin
Copy link

For future searchers, Plug now has an elegant solution to this problem which was added by this PR: elixir-plug/plug#698 which allows you to do the following

defmodule AuditorWeb.CacheBodyReader do
  def read_body(conn, opts) do
    {:ok, body, conn} = Plug.Conn.read_body(conn, opts)
    body = [body | conn.private[:raw_body] || []]
    conn = Plug.Conn.put_private(conn, :raw_body, body)
    {:ok, body, conn}
  end

  def read_cached_body(conn) do
    conn.private[:raw_body]
  end
end

# endpoint.ex
  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    body_reader: {AuditorWeb.CacheBodyReader, :read_body, []},
    json_decoder: Phoenix.json_library()

@coladarci
Copy link

@minhajuddin this was a great end to this thread as I, too, was bit by the "verify request" problem. In your example above, what's this line doing: body = [body | conn.private[:raw_body] || []]

I ask because {:ok, body, conn} = Plug.Conn.read_body(conn, opts) returns a simple string, but read_cached_body returns a list w/ a single string (in my use-case). Should we be protecting against times when there are multiple elements in that list?

Thanks for posting this - was about to go down a very dark path!

@minhajuddin
Copy link

@coladarci Plug.Conn.read_body has the following spec:

read_body(t, Keyword.t) :: {:ok, binary, t} | {:more, binary, t} | {:error, term}`

So, for larger requests this function will be called multiple times as the request is read over multiple calls to Plug.Conn.req_body, that is why are creating an iolist with the request body.

@coladarci
Copy link

Right, but won't {:ok, body, conn} = Plug.Conn.read_body(conn, opts) blow up in the event you get a {:more, binary, t} ?

@minhajuddin
Copy link

hmm, you are right it would blow up, that code might need some rework :)

@jochasinga
Copy link

I need to a way to access the body of a request as a raw string. Plug supports this functionality (https://github.com/elixir-lang/plug/blob/master/lib/plug/conn.ex#L418-L448) but as the docs say this can only be done once. Once the body is consumed, trying to read the body will result in an empty string.

I believe that if you use a Phoenix controller, reading the body as string is impossible. If a client sends a request with the content-type set to application/json Phoenix will automatically parse the body as params meaning any future attempts by client code to access the body will result in an empty string.

Is there any way for client code inside of a controller to access the body as a raw string?

If the body of a request is just JSON one could just decode the map within the controller instead of trapping it in a plug. This means it's simple and can be done in the controller.

defmodule MyAppWeb.RawJsonController do
    # ...
    def get_raw(conn, params) do 
        raw_body = params |> Jason.encode!
    end
end

Kind of a waste to backtrace but if the body expected is small then it's an option.

@coladarci
Copy link

@jochasinga - this is an entire thread around exactly that question and the answers right above your comment are still accurate.

To provide an update on where we ended up:

While the out of the box phoenix puts parsing inside the endpoint, we decided to move that parsing into our router so we can apply different parsing for different groups of routes via pipe_through.

For example, a set of routes that all need to do some stupid verification of the raw body (note @jochasinga the raw body is NOT guaranteed to be the same as re-encoding the params), we can then add our custom plug to that pipeline to save the raw body for later use:

defmodule Plugs.CacheBodyReader do
  def read_body(conn, opts) do
    {:ok, body, conn} = Plug.Conn.read_body(conn, opts)
    conn = Plug.Conn.put_private(conn, :raw_body, body)
    {:ok, body, conn}
  end

  def read_cached_body(conn) do
    conn.private[:raw_body]
  end
end

And then: pipe_through [:parsed_with_cached_body] in our router.

At which point, anywhere who has a conn (i.e for you, in your controller, or for us, in our auth plugs) can access the raw body by simply calling Plugs.CacheBodyReader.read_cached_body(conn).

It's also worth noting that there are reasons Phoenix didn't opt to keep this raw body around, so if you only need it in certain places, it's probably wise to only add this special parsing to those places.

Hope this helps!

@jochasinga
Copy link

jochasinga commented May 10, 2019

@coladarci can you please define

@jochasinga the raw body is NOT guaranteed to be the same as re-encoding the params)

I understand this but in case of JSON they are the same. And this means you won't have to carry the raw body around because you only re-decode it once for that controller.

I wanted to include it for completeness, but I did acknowledged the plug solution and even go with it for my project.

@coladarci
Copy link

coladarci commented May 10, 2019

What I meant was that the raw body has escaped spaces, newlines and the order of the keys is whatever the client sent, etc. The moment you parse it, you lose most, if not all, of that information.

@lud
Copy link

lud commented Jan 15, 2021

For those who end up here trying to understand why their body was missing some whitespace: For me it was because I tested with curl and its -d (--data) option. I now use --data-binary and I can see that cowboy receives and reads the expected body.

@stefanchrobot
Copy link
Contributor

In our use case we need to verify an incoming webhook request by doing SHA256(app secret + raw request body). Since we just dump the webhooks and process them asynchronously, we need a way to store the raw body. Using the suggestions above, here's what we did:

  • Add custom body reader:
defmodule MyAppWeb.Plugs.CachingBodyReader do
  @moduledoc """
  A body reader that caches raw request body for later use.

  This module is intended to be used as the `:body_reader` option of `Plug.Parsers`.
  Note that caching is only enabled for specific paths. See `enabled_for?/1`.
  """

  @raw_body_key :my_app_raw_body

  def read_body(%Plug.Conn{} = conn, opts \\ []) do
    case Plug.Conn.read_body(conn, opts) do
      {:ok, binary, conn} ->
        {:ok, binary, maybe_store_body_chunk(conn, binary)}

      {:more, binary, conn} ->
        {:more, binary, maybe_store_body_chunk(conn, binary)}

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp enabled_for?(conn) do
    case conn.path_info do
      ["webhooks" | _rest] -> true
      _ -> false
    end
  end

  defp maybe_store_body_chunk(conn, chunk) do
    if enabled_for?(conn) do
      store_body_chunk(conn, chunk)
    else
      conn
    end
  end

  def store_body_chunk(%Plug.Conn{} = conn, chunk) when is_binary(chunk) do
    chunks = conn.private[@raw_body_key] || []
    Plug.Conn.put_private(conn, @raw_body_key, [chunk | chunks])
  end

  def get_raw_body(%Plug.Conn{} = conn) do
    case conn.private[@raw_body_key] do
      nil -> nil
      chunks -> chunks |> Enum.reverse() |> Enum.join("")
    end
  end
end
  • Use it in Plug.Parsers:
  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Phoenix.json_library(),
    body_reader: {MyAppWeb.Plugs.CachingBodyReader, :read_body, []}
  • Pull the raw body when needed:
MyAppWeb.Plugs.CachingBodyReader.get_raw_body(conn)

@CamelCafeRider
Copy link

Ahhh. That did the trick. For future reference by the community, a bare minimum solution (in the endpoint.ex):

  defp copy_req_body(conn, _) do
    {:ok, body, _} = Plug.Conn.read_body(conn)
    Plug.Conn.put_private(conn, :raw_body, body)
  end
  plug :copy_req_body
  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

As stated, that doesn't let you filter on only certain endpoints and can be pretty wasteful, but it's enough of a bare minimum example that anyone should be able to work with it.

Thanks!

@hamiltop Thank you. You saved me some hours.

oliverswitzer pushed a commit to geometerio/kohort-browser-extension that referenced this issue May 2, 2022
…ss-origin issues you cannot send application/json without chrome trying to send a preflight OPTIONS request, which blows up... so we need to either a) figure out what to do to allow cross-origin requests (not sure where phoenix sees the request as coming from at the moment, it happens inside of the chrome extension backgroud script) or b) figure out how to read the request body as plain text (phoenix doesnt make this that easy... heres an issue that talks about how to do it phoenixframework/phoenix#459)
@Clivern
Copy link

Clivern commented May 24, 2023

I used this to drop some url parameters and works fine

body = Map.drop(params, ["project", "environment"]) |> Jason.encode!

You need to install https://github.com/michalmuskala/jason

@lewazo
Copy link

lewazo commented Sep 14, 2023

What would be a reliable way to do the same but for multipart requests? The doc for body_reader states it can't be used with multipart, but does not state any alternatives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests