-
Notifications
You must be signed in to change notification settings - Fork 341
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
Add support for multipart requests. #87
Conversation
You can pass a Multipart struct as the body. | ||
|
||
Example: | ||
```ex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this example is missing alias Tesla.Multipart
. It could be misleading for someone who read only a readme.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'll make the change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks!
Whoo this is really cool! |
I updated the docs with the suggestion. Are there any other changes you would like to see? |
Do you require any changes or can we get this merged? |
I'm aware that it's been a long time since the PR was created, but it's big one and therefore it requires a lot of free time to review it properly. I'll try over the weekend if other duties won't prevent me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is great!
We are looking at improving Swagger Codegen for elixir clients and want to be able to support multipart file uploads with autogenerated clients.
One other issue is that this doesn't play nicely with the Tesla.Middleware.JSON
middleware. Currently, it will try to serialize the Tesla.Multipart
struct and fail on a {file, "name"}
tuple if you added a file to your request.
I think we can tell the JSON middleware that a body that is a Multipart
struct should not be encodable?
.
lib/tesla/adapter/hackney.ex
Outdated
@@ -19,11 +21,16 @@ if Code.ensure_loaded?(:hackney) do | |||
end | |||
defp request(method, url, headers, %Stream{} = body, opts), do: request_stream(method, url, headers, body, opts) | |||
defp request(method, url, headers, body, opts) when is_function(body), do: request_stream(method, url, headers, body, opts) | |||
defp request(method, url, headers, %Multipart{} = mp, opts) do | |||
headers = Keyword.merge(headers, Multipart.headers(mp)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
headers
may not always be a keyword list. When creating an API client, you can add a middleware that provides headers like:
plug Tesla.Middleware.Headers, %{"Authorization" => "token xyz"}
This is translated to a list of 2-tuples where the first element is a string (so it's not a keyword list).
This example yields:
(ArgumentError) expected a keyword list as the first argument, got: [{'authorization', 'token xyz'}]
lib/tesla/adapter/httpc.ex
Outdated
@@ -40,6 +41,15 @@ defmodule Tesla.Adapter.Httpc do | |||
:httpc.request(method, {url, headers}, http_opts, opts) | |||
end | |||
|
|||
defp request(method, url, headers, _content_type, %Multipart{} = mp, opts) do | |||
headers = Keyword.merge(headers, Multipart.headers(mp)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here about headers necessarily being a keyword list.
lib/tesla/adapter/ibrowse.ex
Outdated
@@ -21,6 +22,13 @@ if Code.ensure_loaded?(:ibrowse) do | |||
) | |||
end | |||
|
|||
defp request(url, headers, method, %Multipart{} = mp, opts) do | |||
headers = Keyword.merge(headers, Multipart.headers(mp)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here about headers necessarily being a keyword list.
Multipart fixes
I think that list of possible opts should be added to docs for Also found 2 issues:
Anyway big 👍 from me @teamon what do you think about merging this and working on further improvements in separate PRs? |
got green light for merge 💚 |
There is a to
@ijcd Have you tried https://hex.pm/packages/mime ? |
I don't recall if I tried that one. It looks like :mimerl came in with hackney for me, possibly, since benoitc works on both. It's probably better to go with the Elixir one as it looks more recently updated and maintained. It's only used for guessing the mime-type on upload. |
I reached this post because I am trying to use elixir-google-api and I was messing around with it in the repl/iex. This is what happened to me:
How is it possible to completely halt the repl? Why isn't it "just crashing"? I now see what I am doing wrong: I am setting "Hello from Elixir!" as the "path".. but it blows my mind that the repl can hang like that, as if it were stuck in some "low-level" I am running in Windows and these were the commands I run: {:ok, token} = Goth.Token.for_scope("https://www.googleapis.com/auth/cloud-platform")
conn = GoogleApi.Storage.V1.Connection.new(token.token)
GoogleApi.Storage.V1.Api.Objects.storage_objects_insert_simple(conn, "--my--bucket--id--", "multipart", %GoogleApi.Storage.V1.Model.Object{:"contentType" => "plain/text", :"name" => "upload.txt"}, "Hello from Elixir!", []) My intention was to "upload" a file called Cheers. |
Thanks for letting me know elixir-google-api uses tesla! 🎉 You don't need to create a tmp file - you can use About the hanging - can you check with raw tesla? Does it hang at |
@teamon your suggestion was spot on! I had to locally create a function that uses About the freezing thing, it must be something wrong with my setup (I'm looking at you Windows!). If I run I have access to a linux VM, so I'll try too see if it also hangs in there (in there). Massive kudos for your hint 👍 |
Happy to help! 🕺 In case you still have some problems please open a separate issue, good luck! |
@fdbeirao Fabio, pls, how can you uplaod a file into a bucket folder (in case you need to upload to /my-bucket/my-folder/file.txt) ? |
@henry-hz Henry, in order to place the file in a "folder" I specify the file_name as the full path with folder, separated by This defp storage_objects_insert_from_payload(
connection,
bucket,
content_type,
file_name,
file_payload
),
do:
%{}
|> method(:post)
|> url("/upload/storage/v1/b/#{bucket}/o")
|> add_param(:headers, "content-type", content_type)
|> add_param(:query, :uploadType, "media")
|> add_param(:query, :name, file_name)
|> add_param(:body, :body, file_payload)
|> Enum.into([])
|> (&Connection.request(connection, &1)).()
|> decode(%GoogleApi.Storage.V1.Model.Object{}) Let me know if this helped you. I haven't touched that code for a while. |
I needed multipart support so I took a stab at it. Feedback welcome.