Skip to content

Commit 988c351

Browse files
omusoxinabox
andauthored
Use BufferStream as a sacrificial I/O (#475)
* Use BufferStream as sacrificial I/O * Set project version to 1.63.1 * Use `expected` in tests Co-authored-by: mattBrzezinski <[email protected]> * Avoid intermediate read Co-authored-by: Lyndon White <[email protected]>
1 parent ca3ed0c commit 988c351

File tree

4 files changed

+72
-20
lines changed

4 files changed

+72
-20
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "AWS"
22
uuid = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc"
33
license = "MIT"
4-
version = "1.63.0"
4+
version = "1.63.1"
55

66
[deps]
77
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

src/utilities/request.jl

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ function _http_request(http_backend::HTTPBackend, request::Request, response_str
158158
# stream. Effectively, this works as if we're not using streaming I/O at all but we
159159
# will keep using the `response_stream` kwarg to ensure we aren't relying on the
160160
# response's body being populated.
161-
buffer = IOBuffer()
161+
buffer = Base.BufferStream()
162162

163163
r = try
164164
@mock HTTP.request(
@@ -172,25 +172,13 @@ function _http_request(http_backend::HTTPBackend, request::Request, response_str
172172
http_options...,
173173
)
174174
finally
175-
# Read directly from the buffer's data as the I/O stream is closed by HTTP.jl.
176-
# We need to be careful to always write the buffer data to the `response_stream`
177-
# when an exception occurs as this data contains details about the AWS error.
178-
data = if isopen(buffer)
179-
take!(buffer)
180-
else
181-
# When closed the `IOBuffer` size will be set to 0. In order to retrieve the
182-
# raw data we need to determine the size from the first '\0' character.
183-
first_null = findfirst(==(0x00), buffer.data)
184-
last_index = if first_null !== nothing
185-
first_null - 1
186-
else
187-
lastindex(buffer.data)
188-
end
189-
190-
buffer.data[firstindex(buffer.data):last_index]
191-
end
175+
# We're unable to read from the `Base.BufferStream` until it has been closed.
176+
# HTTP.jl will close passed in `response_stream` keyword. This ensures that it
177+
# is always closed (e.g. HTTP.jl 0.9.15)
178+
close(buffer)
192179

193-
write(response_stream, data)
180+
# Transfer the contents of the `BufferStream` into `response_stream` variable.
181+
write(response_stream, buffer)
194182
end
195183

196184
return @mock Response(r, response_stream)

test/issues.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ try
124124
end
125125
end
126126

127+
@testset "issue 474" begin
128+
body = "foo\0bar"
129+
expected = Vector{UInt8}(body)
130+
file_name = "null.txt"
131+
132+
try
133+
S3.put_object(BUCKET_NAME, file_name, Dict("body" => body))
134+
135+
raw = S3.get_object(BUCKET_NAME, file_name, Dict("return_raw" => true))
136+
@test raw == expected
137+
138+
stream = S3.get_object(BUCKET_NAME, file_name, Dict("return_stream" => true))
139+
if AWS.DEFAULT_BACKEND[] isa AWS.HTTPBackend
140+
@test stream isa Base.BufferStream
141+
@test !isopen(stream)
142+
143+
if !isopen(stream)
144+
@test read(stream) == expected
145+
end
146+
else
147+
@test stream isa IOBuffer
148+
@test isopen(stream)
149+
150+
seekstart(stream)
151+
@test read(stream) == expected
152+
end
153+
finally
154+
S3.delete_object(BUCKET_NAME, file_name)
155+
end
156+
end
157+
127158
finally
128159
S3.delete_bucket(BUCKET_NAME)
129160
end

test/minio.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ try
6565
# Duplicated testset from "test/issues.jl". Useful for testing outside the CI. Ideally,
6666
# the tests should be revised such that local testing works without having to duplicate
6767
# testsets.
68+
6869
@testset "issue 466" begin
6970
file_name = "hang.txt"
7071

@@ -104,6 +105,38 @@ try
104105
end
105106
end
106107

108+
@testset "issue 474" begin
109+
body = "foo\0bar"
110+
expected = Vector{UInt8}(body)
111+
file_name = "null.txt"
112+
bucket_name = "anewbucket"
113+
114+
try
115+
S3.put_object(bucket_name, file_name, Dict("body" => body))
116+
117+
raw = S3.get_object(bucket_name, file_name, Dict("return_raw" => true))
118+
@test raw isa Vector{UInt8}
119+
@test raw == expected
120+
121+
stream = S3.get_object(bucket_name, file_name, Dict("return_stream" => true))
122+
if AWS.DEFAULT_BACKEND[] isa AWS.HTTPBackend
123+
@test stream isa Base.BufferStream
124+
@test !isopen(stream)
125+
126+
if !isopen(stream)
127+
@test read(stream) == expected
128+
end
129+
else
130+
@test stream isa IOBuffer
131+
@test isopen(stream)
132+
seekstart(stream)
133+
@test read(stream) == expected
134+
end
135+
finally
136+
S3.delete_object(bucket_name, file_name)
137+
end
138+
end
139+
107140
finally
108141
# Delete all objects and the bucket
109142
objs = S3.list_objects_v2("anewbucket")

0 commit comments

Comments
 (0)