-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
By changing all components to use an IO object as a base, we can implement a common IO interface for all components, which delegates to the underlying IO object. This enables streaming multipart data into the request body, avoiding loading the whole multipart data into memory when File parts are backed by File objects. See httprb/http#409 for the new streaming API.
- Loading branch information
Showing
11 changed files
with
373 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# frozen_string_literal: true | ||
|
||
module HTTP | ||
module FormData | ||
# Provides IO interface across multiple IO files. | ||
class CompositeIO | ||
# @param [Array<IO>] ios Array of IO objects | ||
def initialize(*ios) | ||
@ios = ios.flatten | ||
@index = 0 | ||
end | ||
|
||
# Reads and returns list of | ||
# | ||
# @param [Integer] length Number of bytes to retrieve | ||
# @param [String] outbuf String to be replaced with retrieved data | ||
# | ||
# @return [String] | ||
def read(length = nil, outbuf = nil) | ||
outbuf = outbuf.to_s.replace("") | ||
|
||
while current_io | ||
data = current_io.read(length) | ||
outbuf << data.to_s | ||
length -= data.to_s.length if length | ||
|
||
break if length == 0 | ||
|
||
advance_io | ||
end | ||
|
||
return nil if length && outbuf.empty? | ||
|
||
outbuf | ||
end | ||
|
||
def rewind | ||
@ios.each(&:rewind) | ||
@index = 0 | ||
end | ||
|
||
def size | ||
@size ||= @ios.map(&:size).inject(0, :+) | ||
end | ||
|
||
private | ||
|
||
def current_io | ||
@ios[@index] | ||
end | ||
|
||
def advance_io | ||
@index += 1 | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# frozen_string_literal: true | ||
|
||
module HTTP | ||
module FormData | ||
# Common behaviour for objects defined by an IO object. | ||
module Readable | ||
# Returns IO content. | ||
# | ||
# @return [String] | ||
def to_s | ||
rewind | ||
read | ||
end | ||
|
||
# Reads and returns part of IO content. | ||
# | ||
# @param [Integer] length Number of bytes to retrieve | ||
# @param [String] outbuf String to be replaced with retrieved data | ||
# | ||
# @return [String, nil] | ||
def read(length = nil, outbuf = nil) | ||
@io.read(length, outbuf) | ||
end | ||
|
||
# Returns IO size. | ||
# | ||
# @return [Integer] | ||
def size | ||
@io.size | ||
end | ||
|
||
# Rewinds the IO. | ||
def rewind | ||
@io.rewind | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe HTTP::FormData::CompositeIO do | ||
let(:ios) { ["Hello", " ", "", "world", "!"].map { |string| StringIO.new(string) } } | ||
subject(:composite_io) { HTTP::FormData::CompositeIO.new *ios } | ||
|
||
describe "#read" do | ||
it "reads all data" do | ||
expect(composite_io.read).to eq "Hello world!" | ||
end | ||
|
||
it "reads partial data" do | ||
expect(composite_io.read(3)).to eq "Hel" | ||
expect(composite_io.read(2)).to eq "lo" | ||
expect(composite_io.read(1)).to eq " " | ||
expect(composite_io.read(6)).to eq "world!" | ||
end | ||
|
||
it "returns empty string when no data was retrieved" do | ||
composite_io.read | ||
expect(composite_io.read).to eq "" | ||
end | ||
|
||
it "returns nil when no partial data was retrieved" do | ||
composite_io.read | ||
expect(composite_io.read(3)).to eq nil | ||
end | ||
|
||
it "reads partial data with a buffer" do | ||
outbuf = String.new | ||
expect(composite_io.read(3, outbuf)).to eq "Hel" | ||
expect(composite_io.read(2, outbuf)).to eq "lo" | ||
expect(composite_io.read(1, outbuf)).to eq " " | ||
expect(composite_io.read(6, outbuf)).to eq "world!" | ||
end | ||
|
||
it "fills the buffer with retrieved content" do | ||
outbuf = String.new | ||
composite_io.read(3, outbuf) | ||
expect(outbuf).to eq "Hel" | ||
composite_io.read(2, outbuf) | ||
expect(outbuf).to eq "lo" | ||
composite_io.read(1, outbuf) | ||
expect(outbuf).to eq " " | ||
composite_io.read(6, outbuf) | ||
expect(outbuf).to eq "world!" | ||
end | ||
|
||
it "returns nil when no partial data was retrieved with a buffer" do | ||
outbuf = String.new("content") | ||
composite_io.read | ||
expect(composite_io.read(3, outbuf)).to eq nil | ||
expect(outbuf).to eq "" | ||
end | ||
end | ||
|
||
describe "#rewind" do | ||
it "rewinds all IOs" do | ||
composite_io.read | ||
composite_io.rewind | ||
expect(composite_io.read).to eq "Hello world!" | ||
end | ||
end | ||
|
||
describe "#size" do | ||
it "returns sum of all IO sizes" do | ||
expect(composite_io.size).to eq 12 | ||
end | ||
|
||
it "returns 0 when there are no IOs" do | ||
empty_composite_io = HTTP::FormData::CompositeIO.new | ||
expect(empty_composite_io.size).to eq 0 | ||
end | ||
end | ||
end |
Oops, something went wrong.