Webmock: Posting a tempfile tries to rewind a closed stream
I'm sure you're loving all these webmock issues:
```rb
require "webmock"
require "httpx"
require "httpx/adapters/webmock"
include WebMock::API
WebMock.enable!
def open(path)
open_tempfile do |file|
download path, file
yield file
end
end
def open_tempfile
file = Tempfile.open("tmp")
begin
yield file
ensure
file.close!
end
end
def download(path, file)
file.binmode
download_impl(path) { |chunk| file.write(chunk) }
file.flush
file.rewind
end
def download_impl(path, &block)
File.open(path, "rb") do |file|
while data = file.read(5242880)
yield data
end
end
end
open(__FILE__) do |file|
HTTPX.post("https://example.com", form: { file: file })
end
```
This results in webmock trying to close an already closed stream:
```
$ ruby test.rb
/home/earlopain/.rbenv/versions/3.3.5/lib/ruby/3.3.0/delegate.rb:349:in `rewind': closed stream (IOError)
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/3.3.0/delegate.rb:349:in `block in delegating_block'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/transcoder/multipart/encoder.rb:39:in `block in rewind'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/transcoder/multipart/encoder.rb:37:in `each'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/transcoder/multipart/encoder.rb:37:in `each_with_object'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/transcoder/multipart/encoder.rb:37:in `rewind'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/transcoder/multipart/encoder.rb:24:in `to_s'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/3.3.0/delegate.rb:87:in `method_missing'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/adapters/webmock.rb:23:in `build_webmock_request_signature'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/adapters/webmock.rb:104:in `send'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:132:in `block in send_request'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:130:in `catch'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:130:in `send_request'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:254:in `block in _send_requests'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:253:in `each'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:253:in `_send_requests'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:245:in `send_requests'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/session.rb:72:in `request'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/chainable.rb:17:in `request'
from /home/earlopain/.rbenv/versions/3.3.5/lib/ruby/gems/3.3.0/gems/httpx-1.3.3/lib/httpx/chainable.rb:10:in `post'
from test.rb:40:in `block in <main>'
from test.rb:11:in `block in open'
from test.rb:18:in `open_tempfile'
from test.rb:9:in `open'
from test.rb:39:in `<main>'
```
This is caused by the last webmock fix in https://gitlab.com/os85/httpx/-/merge_requests/352. The code above is how Rails ActiveStorage works with the DiskService and real code would look more along the lines of this:
```rb
user.avatar.open do |file|
HTTPX.post("https://example.com", form: { file: file })
end
```
I believe the code that is making trouble here is this: https://gitlab.com/os85/httpx/-/blob/91b9e13cd07247609d8504409c7c3628e8279ece/lib/httpx/transcoder/multipart/encoder.rb#L38
A "real" Tempfile created with `Tempfile.open`/`Tempfile.new` is not `is_a?(File)`. That at least is the most immediate place to look at for a cause. Simply changing the condition to `if (val.is_a?(File) || val.is_a?(Tempfile)) && val.closed?` does work but I'm not sure if that is a proper fix.
issue