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

Issue with multipart/form data request with json body #769

Closed
sushildamdhere opened this issue Feb 8, 2018 · 4 comments · Fixed by #1014 or #1017 · May be fixed by trainline-eu/faraday#1
Closed

Issue with multipart/form data request with json body #769

sushildamdhere opened this issue Feb 8, 2018 · 4 comments · Fixed by #1014 or #1017 · May be fixed by trainline-eu/faraday#1

Comments

@sushildamdhere
Copy link

sushildamdhere commented Feb 8, 2018

Basic Info

  • Faraday Version: Faraday v0.12.2 & v0.14.0
  • Ruby Version: 2.3.3

Issue description

When trying to POST multipart data (file & json), remote server is not recognizing json data parameter and failing with validation error as it is required parameter.

connection = Faraday.new(url: "#{uri.scheme}://#{uri.host}:#{uri.port}") do |faraday|
              faraday.request :multipart
              faraday.request :url_encoded

              faraday.adapter :net_http
            end

request_data = { file: Faraday::UploadIO.new('somefile.png', 'image/png'), 
                 jsonBody: JSON.dump({ id: 'foo', name: 'bar' }) }

response = connection.post(uri.request_uri) do |request|
  request = Ff::Api::RequestHeaders.set(request, self.api_options)
  request.headers['content-type'] = 'multipart/form-data; boundary=-----------RubyMultipartPost'
  request.body = request_data
end

Does anyone see any obvious mistake here?

@iMacTia
Copy link
Member

iMacTia commented Feb 8, 2018

Hi @ff-sushild,
unfortunately the Faraday middleware for multipart doesn't currently allow to mix JSON with files.
If you send a multipart request, all the "body parts" (the parameters) will be missing the "content-type" header, so the server won't be able to automatically detect the content type.

I believe I've identified the issue in this line. The Part constructor accepts a forth parameter headers that should allow to do what you need, but we're not providing this at the moment.

Main reason for this is that mixing JSON and multipart is not really RESTful, so if you don't have the possibility to change the server implementation, a PR to fix this is more then welcome

However if you CAN change the server, you have other 2 options and I would strongly suggest to go for one of these:

  1. Always force the server to parse the request body as JSON.
  2. Send your files as JSON items and avoid the multipart/form-data request entirely (this works by encoding binary files in Base64). Unfortunately I only found a java example with a quick search, but I wouldn't be surprised if there's a gem for that.
  3. Use a 2 phases approach (file and JSON body on separate requests) example.

Let me know what do you end up doing

@sushildamdhere
Copy link
Author

Thanks @iMacTia, this is really very helpful to find the RCA and the solution. Will let you know where I end up with. Most probably will have to patch multipart for now and fixing the backend implementation for long term.

@iMacTia
Copy link
Member

iMacTia commented Feb 9, 2018

@ff-sushild sounds good! Glad I could help 👍
Totally agree with your approach

@jeremyisr
Copy link
Contributor

jeremyisr commented Aug 28, 2019

Hello, would you take a PR implementing the patch suggested here ? https://stackoverflow.com/questions/48679598/faraday-multipart-request-with-json-file-data?rq=1

class Faraday::Request::Multipart
  def create_multipart(env, params)
    boundary = env.request.boundary
    parts = process_params(params) do |key, value|
      if (JSON.parse(value) rescue false)
        Faraday::Parts::Part.new(boundary, key, value, 'Content-Type' => 'application/json')
      else
        Faraday::Parts::Part.new(boundary, key, value)
      end
    end
    parts << Faraday::Parts::EpiloguePart.new(boundary)

    body = Faraday::CompositeReadIO.new(parts)
    env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
    return body
  end
end

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