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

How to customize Content-Disposition for the file in a multipart request? #165

Closed
jondkinney opened this issue Jun 7, 2012 · 6 comments
Closed

Comments

@jondkinney
Copy link

I tried to use Faraday to connect to the DocuSign API which requires a multipart post including a PDF to be signed. However, I was unable to get the request built up as their API required and had to drop down the Net::HTTP to get it done. I could see sections of the multipart-post gem that looked like what I would need to tap into (The FilePart class in the Parts module) but Faraday didn't seem to be using that portion of the gem code when building up the request (at least my pry breakpoint never got hit inside that method).

This is my working Net:HTTP post request... is this possible with Faraday? Of particular note is the documentid parameter that they require in the file's Content-Disposition. Passwords/keys changed for security reasons.

require "net/http"
require "uri"

BOUNDARY = "myboundary"

uri = URI.parse("https://demo.docusign.net/restapi/v1/accounts/123456789/envelopes")
file = "test.pdf"

post_body = ""
post_body << "\r\n"
post_body << "--#{BOUNDARY}\r\n"
post_body << "Content-Type: application/json\r\n"
post_body << "Content-Disposition: form-data\r\n"
post_body << "\r\n"
post_body << "{
  \"emailBlurb\": \"eblurb\",
  \"emailSubject\": \"esubj\",
  \"documents\": [
    {
      \"documentId\": \"1\",
      \"name\": \"#{File.basename(file)}\"
    }
  ],
  \"recipients\": {
    \"signers\": [
      {
        \"email\": \"someone@gmail.com\",
        \"name\": \"FunGuy\",
        \"recipientId\": \"1\"
      }
    ]
  },
  \"status\": \"sent\"
}"
post_body << "\r\n"
post_body << "--#{BOUNDARY}\r\n"
post_body << "Content-Type: application/pdf\r\n"
post_body << "Content-Disposition: file; filename=\"#{File.basename(file)}\"; documentid=1\r\n"
post_body << "\r\n"
post_body << File.read(file)
post_body << "\r\n"
post_body << "--#{BOUNDARY}--\r\n"

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

headers = {
  "X-DocuSign-Authentication" => "<DocuSignCredentials><Username>me@you.com</Username><Password>redacted</Password><IntegratorKey>COMP-129fjghs-cb34-4ab6-87ec-59db47d34b32</IntegratorKey></DocuSignCredentials>",
  "Content-Type" => "multipart/form-data, boundary=#{BOUNDARY}",
  "Accept" => "application/json",
  "Content-Length" => "#{post_body.length}"
}

request = Net::HTTP::Post.new(uri.request_uri, headers)
request.body = post_body

response = http.request(request)

puts response.body

This was the Faraday code I was trying to use:

require 'faraday'

conn = Faraday.new(:url => 'https://demo.docusign.net/restapi/v1/',
  :headers => {
    "X-DocuSign-Authentication" => "<DocuSignCredentials><Username>me@you.com</Username><Password>redacted</Password><IntegratorKey>COMP-129fjghs-cb34-4ab6-87ec-59db47d34b32</IntegratorKey></DocuSignCredentials>",
    "Accept" => "application/json",
    "Content-Type" => "application/json"
  }) do |builder|
    builder.request  :multipart
    builder.adapter  :net_http
end

payload = { :file => Faraday::UploadIO.new('test.pdf','application/pdf') }

response = conn.post("accounts/123456789/envelopes", payload) do |req|
  req.body = "{
  \"emailBlurb\": \"eblurb\",
  \"emailSubject\": \"esubj\",
  \"documents\": [
    {
      \"documentId\": \"1\",
      \"name\": \"#{payload[:file].original_filename}\"
    }
  ],
  \"recipients\": {
    \"signers\": [
      {
        \"email\": \"someone@gmail.com\",
        \"name\": \"FunGuy\",
        \"recipientId\": \"1\"
      }
    ]
  },
  \"status\": \"sent\"
}
"
end

puts response.body

The error I received from their API was that no documents were attached, presumably because I was unable to specify the documentid as required.

Any help/guidance would be greatly appreciated!

Thanks,
-Jon

@mislav
Copy link
Contributor

mislav commented Jun 7, 2012

Hey, this does seem like something you can't do with Faraday at the moment. Keep using your workaround, but I'll look into how we can enable this, and update this ticket.

@jondkinney
Copy link
Author

Awesome, thanks Mislav!

@jondkinney
Copy link
Author

Upon further investigation it seems that I can add some options to the UploadIO

payload = { :file => Faraday::UploadIO.new('test.pdf', "application/pdf", 'test.pdf', "Content-Disposition" => "file; documentId=1") }

Which if the FileParts class of the multipart-gem gets hit should let me inject that documentid into the Content-Disposition. However, it doesn't seem like that portion of the multipart-post gem is getting invoked on my local system even though I swear it should be.

What am I missing? Why isn't Faraday utilizing that portion of the multipart-post gem? It's like it's not even detecting that it's a multipart post... can you not have a custom body and a payload at the same time?

@shanepinnell
Copy link

Anyone have any luck with this?

@iMacTia
Copy link
Member

iMacTia commented Jul 21, 2017

As it was already pointed out, this is currently not managed by the multipart middleware.
You can see the middleware here.
I'm not personally an expert on Multipart requests so appreciate if anyone wants to jump in to add support for this 😄

@technoweenie
Copy link
Member

The multipart-post gem doesn't really give you a place to set this in a safe way. However, the solution with Faraday::UploadIO should work just fine.

payload = { :file => Faraday::UploadIO.new('test.pdf', "application/pdf", 'test.pdf', "Content-Disposition" => "file; documentId=1") }

That Content-Disposition value gets thrown in, untouched, right here. Here's a sample dumped to STDOUT by a simple tweak of the current tests.

-------------RubyMultipartPost-d4d88346bd5fabb730c3fe0de769af9f
Content-Disposition: file; documentId=1; name="b[c]"; filename="multipart_spec.rb"
Content-Length: 2231
Content-Type: text/x-ruby
Content-Transfer-Encoding: binary

# frozen_string_literal: true
...

Here's the diff I used:

diff --git a/lib/faraday/upload_io.rb b/lib/faraday/upload_io.rb
index e16fd7a..8db4cfa 100644
--- a/lib/faraday/upload_io.rb
+++ b/lib/faraday/upload_io.rb
@@ -42,6 +42,7 @@ module Faraday
         if (result = io.read(length))
           got_result ||= !result.nil?
           result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
+          puts "MULTIPART: #{result}"
           outbuf << result
           length -= result.length if length
           break if length&.zero?
diff --git a/spec/faraday/request/multipart_spec.rb b/spec/faraday/request/multipart_spec.rb
index 809d9e0..1e48ee8 100644
--- a/spec/faraday/request/multipart_spec.rb
+++ b/spec/faraday/request/multipart_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Faraday::Request::Multipart do
       b.adapter :test do |stub|
         stub.post('/echo') do |env|
           posted_as = env[:request_headers]['Content-Type']
+          puts env[:body].read
           [200, { 'Content-Type' => posted_as }, env[:body]]
         end
       end
@@ -54,7 +55,7 @@ RSpec.describe Faraday::Request::Multipart do
       {
         a: 1,
         b: {
-          c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby'),
+          c: Faraday::UploadIO.new(__FILE__, 'text/x-ruby', nil, 'Content-Disposition' => 'file; documentId=1'),
           d: 2
         }
       }

Closing this, as there's no real bug other than the poor way the multipart-post gem was shoehorned in without documentation. I'm going to do the following:

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

No branches or pull requests

5 participants