7

I'm having issues attempting to upload a file to a web service using Faraday.

My code:

conn = Faraday.new('http://myapi') do |f|
  f.request :multipart
end

payload = { :file => Faraday::UploadIO.new('...', 'image/jpeg') }

conn.post('/', payload)

Nothing seems to happen after attempting the post. When I inspect the response this is what I'm seeing:

#<Faraday::Response:0x007fd9e5903870 @env={:method=>:post, :body=>#<Faraday::CompositeReadIO:0x007fd9e5903a50 @parts=[#<Parts::FilePart:0x007fd9e5903e38 @head="-------------RubyMultipartPost\r\nContent-Disposition: form-data; name=\"file\"; filename=\"indymade-badge-horizontal.png\"\r\nContent-Length: 7821\r\nContent-Type: image/png\r\nContent-Transfer-Encoding: binary\r\n\r\n", @foot="\r\n", @length=8026, @io=#<CompositeReadIO:0x007fd9e5903b68 @ios=[#<StringIO:0x007fd9e5903bb8>, #<UploadIO:0x007fd9e514a3b8 @content_type="image/png", @original_filename="indymade-badge-horizontal.png", @local_path="/Users/anthonator/Downloads/indymade-badge-horizontal.png", @io=#<File:/Users/anthonator/Downloads/indymade-badge-horizontal.png>, @opts={}>, #<StringIO:0x007fd9e5903b90>], @index=0>>, #<Parts::EpiloguePart:0x007fd9e5903ac8 @part="-------------RubyMultipartPost--\r\n\r\n", @io=#<StringIO:0x007fd9e5903a78>>], @ios=[#<CompositeReadIO:0x007fd9e5903b68 @ios=[#<StringIO:0x007fd9e5903bb8>, #<UploadIO:0x007fd9e514a3b8 @content_type="image/png", @original_filename="indymade-badge-horizontal.png", @local_path="/Users/anthonator/Downloads/indymade-badge-horizontal.png", @io=#<File:/Users/anthonator/Downloads/indymade-badge-horizontal.png>, @opts={}>, #<StringIO:0x007fd9e5903b90>], @index=0>, #<StringIO:0x007fd9e5903a78>], @index=0>, :url=>#<URI::HTTPS:0x007fd9e5909d60 URL:https://myapi>, :request_headers=>{"User-Agent"=>"Faraday v0.8.7", "Content-Type"=>"multipart/form-data;boundary=-----------RubyMultipartPost", "Content-Length"=>"8062"}, :parallel_manager=>nil, :request=>{:proxy=>nil, :boundary=>"-----------RubyMultipartPost"}, :ssl=>{}, :response=>#<Faraday::Response:0x007fd9e5903870 ...>}, @on_complete_callbacks=[]>
irb(main):065:0> response.inspect
=> "#<Faraday::Response:0x007fd9e5903870 @env={:method=>:post, :body=>#<Faraday::CompositeReadIO:0x007fd9e5903a50 @parts=[#<Parts::FilePart:0x007fd9e5903e38 @head=\"-------------RubyMultipartPost\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"indymade-badge-horizontal.png\\\"\\r\\nContent-Length: 7821\\r\\nContent-Type: image/png\\r\\nContent-Transfer-Encoding: binary\\r\\n\\r\\n\", @foot=\"\\r\\n\", @length=8026, @io=#<CompositeReadIO:0x007fd9e5903b68 @ios=[#<StringIO:0x007fd9e5903bb8>, #<UploadIO:0x007fd9e514a3b8 @content_type=\"image/png\", @original_filename=\"indymade-badge-horizontal.png\", @local_path=\"/Users/anthonator/Downloads/indymade-badge-horizontal.png\", @io=#<File:/Users/anthonator/Downloads/indymade-badge-horizontal.png>, @opts={}>, #<StringIO:0x007fd9e5903b90>], @index=0>>, #<Parts::EpiloguePart:0x007fd9e5903ac8 @part=\"-------------RubyMultipartPost--\\r\\n\\r\\n\", @io=#<StringIO:0x007fd9e5903a78>>], @ios=[#<CompositeReadIO:0x007fd9e5903b68 @ios=[#<StringIO:0x007fd9e5903bb8>, #<UploadIO:0x007fd9e514a3b8 @content_type=\"image/png\", @original_filename=\"indymade-badge-horizontal.png\", @local_path=\"/Users/anthonator/Downloads/indymade-badge-horizontal.png\", @io=#<File:/Users/anthonator/Downloads/indymade-badge-horizontal.png>, @opts={}>, #<StringIO:0x007fd9e5903b90>], @index=0>, #<StringIO:0x007fd9e5903a78>], @index=0>, :url=>#<URI::HTTPS:0x007fd9e5909d60 URL:https://myapi>, :request_headers=>{\"User-Agent\"=>\"Faraday v0.8.7\", \"Content-Type\"=>\"multipart/form-data;boundary=-----------RubyMultipartPost\", \"Content-Length\"=>\"8062\"}, :parallel_manager=>nil, :request=>{:proxy=>nil, :boundary=>\"-----------RubyMultipartPost\"}, :ssl=>{}, :response=>#<Faraday::Response:0x007fd9e5903870 ...>}, @on_complete_callbacks=[]>"

The body is being set to a CompositeReadIO object and it seems to never be sending out a request.

Wayne Conrad
  • 103,207
  • 26
  • 155
  • 191
anthonator
  • 4,915
  • 7
  • 36
  • 50
  • 1
    In the arguments to `UploadIO.new`, are you also adding the mime type? e.g. `"image/jpeg"`. Also, the first argument to `post` should be the path section of the URL, so if it was the root route (haha) of the myapi site, it should be `conn.post('/', payload)`. Does that change anything? – ian May 24 '13 at 02:46
  • Yes, I am speecifying the mime-type, should have put that in the example. – anthonator Jun 01 '13 at 00:00

4 Answers4

22

It turns out that I needed to specify an adapter. Here's the code that ended up working.

conn = Faraday.new('http://myapi') do |f|
  f.request :multipart
  f.request :url_encoded
  f.adapter :net_http # This is what ended up making it work
end

payload = { :file => Faraday::UploadIO.new('...', 'image/jpeg') }

conn.post('/', payload)
anthonator
  • 4,915
  • 7
  • 36
  • 50
  • 1
    Faraday::UploadIO is depreceated. how can this be used in 2023? – Bergrebell Mar 27 '23 at 10:22
  • looks like faraday now depends on https://github.com/lostisland/faraday-multipart for uploading files (as far as i understand it) and then use `Faraday::Multipart::FilePart` instead of `Faraday::UploadIO` – Bergrebell Mar 27 '23 at 10:33
7

Something like this resolved my problem:

conn = Faraday.new(url: URL) do |faraday|
  faraday.request :multipart #make sure this is set before url_encoded
  faraday.request :url_encoded
  faraday.adapter Faraday.default_adapter
end

file = Faraday::UploadIO.new(
  params[:image].tempfile.path,
  params[:image].content_type,
  params[:image].original_filename
)

payload = { :file => file }

conn.post('/', payload)
  • This is exactly what I was looking for, noting that I used the base URL in the connection setup, and the actual URI path in the `.post()` call. – David Krider Jan 30 '20 at 20:55
1

Faraday after v0.16.0: https://lostisland.github.io/faraday/middleware/multipart

"Multipart" middleware detects files and encodes with "multipart/form-data":

payload[:profile_pic] = Faraday::FilePart.new('/path/to/avatar.jpg', 'image/jpeg')

conn.put '/profile', payload
Oshan Wisumperuma
  • 1,808
  • 1
  • 18
  • 32
0

If you're using Faraday with the default net_http adapter and don't want to upload files with the multipart middleware, you can stream them instead:

conn = Faraday.new(url: "https://example.org")

conn.put(
  "/upload",
  File.open(src_path, "rb"),
  {
    "Content-Length" => File.size(src_path).to_s,
    "Transfer-Encoding" => "chunked",
    "Content-Type" => "application/octet-stream"
  }
)

This uses Net::HTTPGenericRequest#body_stream under the hood.

(tested with Faraday v2.2.0)

alexpls
  • 1,914
  • 20
  • 29