Skip to content

Commit

Permalink
Add The Ability To Specify A Different MIME Type (#14)
Browse files Browse the repository at this point in the history
There are many different mime types for multipart
requests and this change provides the option to
specify a different one.

If a multipart mime type is not specified the
middleware will default to multipart/form-data.

This change also changes the structure of the
middleware a bit. It changes the parent from
UrlEncoded to inherit from Middleware directly
instead. This is because we were overriding most
of the UrlEncoded methods anyway so bringing last
remaining methods over and removing that link in
the dependency chain seemed to make sense.

Another structural code change was to move the
methods that didn't need to be public under the
private keyword. The intent of this was to help
future developers know what they can change
without worrying breaking any external
dependencies.
  • Loading branch information
juna-nb authored Nov 22, 2024
1 parent b843898 commit 4f68590
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 4 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ conn = Faraday.new(...) do |f|
end
```

If you need to [specify a different content type for the multipart
request](https://www.iana.org/assignments/media-types/media-types.xhtml#multipart),
you can do so by providing the `content_type` option but it must start with
`multipart/`
otherwise it will default to `multipart/form-data`:

```ruby
conn = Faraday.new(...) do |f|
f.request :multipart, content_type: 'multipart/mixed'
# ...
end
```

Payload can be a mix of POST data and multipart values.

Expand Down
40 changes: 36 additions & 4 deletions lib/faraday/multipart/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
module Faraday
module Multipart
# Middleware for supporting multi-part requests.
class Middleware < Faraday::Request::UrlEncoded
class Middleware < Faraday::Middleware
CONTENT_TYPE = 'Content-Type'
DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'

self.mime_type = 'multipart/form-data'

def initialize(app = nil, options = {})
super(app)
@options = options
Expand All @@ -28,15 +27,37 @@ def call(env)
@app.call env
end

private

# @param env [Faraday::Env]
# @yield [request_body] Body of the request
def match_content_type(env)
return unless process_request?(env)

env.request_headers[CONTENT_TYPE] ||= mime_type
return if env.body.respond_to?(:to_str) || env.body.respond_to?(:read)

yield(env.body)
end

# @param env [Faraday::Env]
def process_request?(env)
type = request_type(env)
env.body.respond_to?(:each_key) && !env.body.empty? && (
(type.empty? && has_multipart?(env.body)) ||
(type == self.class.mime_type)
(type == mime_type)
)
end

# @param env [Faraday::Env]
#
# @return [String]
def request_type(env)
type = env.request_headers[CONTENT_TYPE].to_s
type = type.split(';', 2).first if type.index(';')
type
end

# Returns true if obj is an enumerable with values that are multipart.
#
# @param obj [Object]
Expand Down Expand Up @@ -97,6 +118,17 @@ def process_params(params, prefix = nil, pieces = nil, &block)
end
end
end

# Determines and provides the multipart mime type for the request.
#
# @return [String] the multipart mime type
def mime_type
@mime_type ||= if @options[:content_type].to_s.match?(%r{\Amultipart/.+})
@options[:content_type].to_s
else
'multipart/form-data'
end
end
end
end
end
45 changes: 45 additions & 0 deletions spec/faraday/multipart/middleware_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -315,4 +315,49 @@
expect(response.body).not_to include('name="b[]"')
end
end

context 'when passing content_type option' do
let(:payload) do
{
xml: Faraday::Multipart::ParamPart.new('<xml><value /></xml>', 'text/xml'),
io: Faraday::Multipart::FilePart.new(StringIO.new('io-content'), 'application/octet-stream')
}
end

context 'when a multipart mime type is provided' do
let(:options) { { content_type: 'multipart/mixed' } }

it_behaves_like 'a multipart request'

it 'uses the provided mime type' do
response = conn.post('/echo', payload)

expect(response.headers['Content-Type']).to start_with('multipart/mixed')
end
end

context 'when a non-multipart mime type is provided' do
let(:options) { { content_type: 'application/json' } }

it_behaves_like 'a multipart request'

it 'uses the default mime type' do
response = conn.post('/echo', payload)

expect(response.headers['Content-Type']).to start_with('multipart/form-data')
end
end

context 'when no multipart mime type is provided' do
let(:options) { {} }

it_behaves_like 'a multipart request'

it 'uses the default mime type' do
response = conn.post('/echo', payload)

expect(response.headers['Content-Type']).to start_with('multipart/form-data')
end
end
end
end

0 comments on commit 4f68590

Please sign in to comment.