-
Notifications
You must be signed in to change notification settings - Fork 68
Alternate Downloads.jl-based backend (alternative to HTTP.jl) #396
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
Changes from all commits
d4c8f91
cae56c4
9b917fa
c8329a9
e66dca9
96a96ea
2fd84d4
8311723
31e126d
383eced
d979da4
bffb900
a549d87
a370fd1
93d4a29
be7adb1
3bf0a31
10d81ee
73c0bbf
5d50a61
9783836
9c2da08
1c6c1bf
8d63853
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,115 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| struct DownloadsBackend <: AWS.AbstractBackend | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downloader::Union{Nothing, Downloads.Downloader} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DownloadsBackend() = DownloadsBackend(nothing) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const AWS_DOWNLOADER = Ref{Union{Nothing, Downloader}}(nothing) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const AWS_DOWNLOAD_LOCK = ReentrantLock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Here we mimic Download.jl's own setup for using a global downloader. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # We do this to have our own downloader (separate from Downloads.jl's global downloader) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # because we add a hook to avoid redirects in order to try to match the HTTPBackend's | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # implementation, and we don't want to mutate the global downloader from Downloads.jl. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # https://github.com/JuliaLang/Downloads.jl/blob/84e948c02b8a0625552a764bf90f7d2ee97c949c/src/Downloads.jl#L293-L301 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function get_downloader(downloader=nothing) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lock(AWS_DOWNLOAD_LOCK) do | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| yield() # let other downloads finish | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downloader isa Downloader && return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downloader = AWS_DOWNLOADER[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downloader isa Downloader && return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| D = Downloader() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| D.easy_hook = (easy, info) -> Curl.setopt(easy, Curl.CURLOPT_FOLLOWLOCATION, false) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AWS_DOWNLOADER[] = D | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return downloader | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+28
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We may want to use the formulation from JuliaLang/Downloads.jl#136 instead
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we can leave it until it gets merged upstream |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # https://github.com/JuliaWeb/HTTP.jl/blob/2a03ca76376162ffc3423ba7f15bd6d966edff9b/src/MessageRequest.jl#L84-L85 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body_length(x::AbstractVector{UInt8}) = length(x) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body_length(x::AbstractString) = sizeof(x) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
mattBrzezinski marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| read_body(x::IOBuffer) = take!(x) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function read_body(x::IO) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| close(x) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return read(x) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was just reading through this, I don't understand
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a workaround specifically for I'm no expert when it comes to the various
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think perhaps we just need to use Perhaps for this case, the newly available — but strangely named — But even so, I'm not sure that AWS should be unilaterally closing or shutting down the write side of the the stream at all — to me that seems to be the business of the code which passes in the stream. For example, if reading from a pipe, presumably you want to keep reading until the writing side decides it's done? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, seems like whoever was filling the buffer needs to call
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Well, we only do it if
I’m not sure who that is- Downloads.jl fills it but I don’t think they should be closing it (because of exactly what @c42f just said). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, somebody was wrong before, and now AWS.jl is now possibly also very bad at stream EOF handling here too. Whoever created the stream is generally responsible for marking when it is done, or passing it to another function which will take care of that. I suspect Downloads.jl absolutely should be copying the EOF marker from the underlying stream also. It is out-of-band data, but likely just as relevant as the in-band bytes.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Perhaps I've misunderstood what's going on here, but is the problem in Downloads.jl then? Shouldn't they close the write side when writing is done?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I 100% don't understand what's going on. But let me try to summarize the current behavior (including this PR). SummaryThe HTTPBackend only sets a
If I'm understanding then, I think Case 2 is probematic for the DownloadsBackend, and we should just not try to return bytes in this case, matching the behavior of the HTTP backend. I.e. we should not be closing the stream in this case. Case 3: I don't really know if we should close the stream or not. I think ideally this case wouldn't exist, because I don't think there really should be a So: if that analysis is right, Downloads.jl is fine, and we have at least one problematic case here. But I'll admit to definitely not fully understanding the semantics of how streams should work (and the manual isn't very helpful in this regard), though the discussion here helps me a bit. Though possibly, HTTP.jl and Downloads.jl should be closing the streams whenever they are passed and the backend is done populating them? I think maybe that's what @vtjnash is saying in #396 (comment). In which case most/all of these cases are wrong for both backends. Appendix: Following the path of
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function get_object( | |
| Bucket, | |
| Key, | |
| params::AbstractDict{String}; | |
| aws_config::AbstractAWSConfig=global_aws_config(), | |
| ) | |
| return s3("GET", "/$(Bucket)/$(Key)", params; aws_config=aws_config) | |
| end |
params which is a dictionary with additional parametersparams to build Request, changing the name from params to args: Lines 213 to 217 in 181c340
| request = Request(; | |
| _extract_common_kw_args(service, args)..., | |
| request_method=request_method, | |
| content=_pop!(args, "body", ""), | |
| ) |
AWS.jl/src/utilities/utilities.jl
Lines 65 to 77 in 181c340
| function _extract_common_kw_args(service, args) | |
| return ( | |
| service=service.signing_name, | |
| api_version=service.api_version, | |
| return_stream=_pop!(args, "return_stream", false), | |
| return_raw=_pop!(args, "return_raw", false), | |
| response_stream=_pop!(args, "response_stream", nothing), | |
| headers=LittleDict{String,String}(_pop!(args, "headers", [])), | |
| http_options=_pop!(args, "http_options", LittleDict{Symbol,String}()), | |
| response_dict_type=_pop!(args, "response_dict_type", LittleDict), | |
| backend=_pop!(args, "backend", DEFAULT_BACKEND[]), | |
| ) | |
| end |
Request object: AWS.jl/src/utilities/request.jl
Lines 26 to 42 in 181c340
| Base.@kwdef mutable struct Request | |
| service::String | |
| api_version::String | |
| request_method::String | |
| headers::AbstractDict{String,String} = LittleDict{String,String}() | |
| content::Union{String,Vector{UInt8}} = "" | |
| resource::String = "" | |
| url::String = "" | |
| return_stream::Bool = false | |
| response_stream::Union{<:IO,Nothing} = nothing | |
| http_options::AbstractDict{Symbol,<:Any} = LittleDict{Symbol,String}() | |
| return_raw::Bool = false | |
| response_dict_type::Type{<:AbstractDict} = LittleDict | |
| backend::AbstractBackend = DEFAULT_BACKEND[] | |
| end |
submit_request: AWS.jl/src/utilities/request.jl
Lines 59 to 61 in 181c340
| function submit_request( | |
| aws::AbstractAWSConfig, request::Request; return_headers::Bool=false | |
| ) |
Here, what happens depends on which backend we are using. This PR is about the new DownloadsBackend.
Using the DownloadsBackend
submit_requestcalls_http_requestand if we are using aDownloadsBackend, we end up at the method in question,AWS.jl/src/utilities/downloads_backend.jl
Line 41 in 181c340
function _http_request(backend::DownloadsBackend, request) - Here, if user has not passed a
response_stream, we create a default one:AWS.jl/src/utilities/downloads_backend.jl
Lines 58 to 60 in 181c340
if request.response_stream === nothing request.response_stream = IOBuffer() end - And if they have not specified
return_stream==true, we arrange to collect the bytes from the response stream, including possibly closing theresponse_stream:AWS.jl/src/utilities/downloads_backend.jl
Lines 70 to 74 in 181c340
body_arg = if request.request_method == "HEAD" || request.return_stream () -> NamedTuple() else () -> (; body=read_body(request.response_stream)) end
Using the HTTPBackend
If the user is using the existant HTTP.jl backend, what happens?
submit_requestcalls_http_requestwith an HTTPBackend, leading us to this method:AWS.jl/src/utilities/request.jl
Lines 186 to 217 in 181c340
function _http_request(http_backend::HTTPBackend, request::Request) http_options = merge(http_backend.http_options, request.http_options) @repeat 4 try http_stack = HTTP.stack(; redirect=false, retry=false, aws_authorization=false) if request.return_stream && request.response_stream === nothing request.response_stream = Base.BufferStream() end return @mock HTTP.request( http_stack, request.request_method, HTTP.URI(request.url), HTTP.mkheaders(request.headers), request.content; require_ssl_verification=false, response_stream=request.response_stream, http_options..., ) catch e # Base.IOError is needed because HTTP.jl can often have errors that aren't # caught and wrapped in an HTTP.IOError # https://github.com/JuliaWeb/HTTP.jl/issues/382 @delay_retry if isa(e, Sockets.DNSError) || isa(e, HTTP.ParseError) || isa(e, HTTP.IOError) || isa(e, Base.IOError) || (isa(e, HTTP.StatusError) && _http_status(e) >= 500) end end end - There we create a
response_streamif both the user has not passed one and they ask forreturn_stream=true:AWS.jl/src/utilities/request.jl
Lines 192 to 194 in 181c340
if request.return_stream && request.response_stream === nothing request.response_stream = Base.BufferStream() end - We pass the possibly-
nothingrequest.response_streamtoHTTP.request:AWS.jl/src/utilities/request.jl
Line 203 in 181c340
response_stream=request.response_stream, - HTTP.jl will give us back a
bodyif they were not given aresponse_stream, otherwise they put the bytes in the stream: https://github.com/JuliaWeb/HTTP.jl/blob/a2b467e24c9bbd45691f9d0f57b57ee7463bd15a/src/HTTP.jl#L77-L78
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've filed #433 so we don't forget about this
Uh oh!
There was an error while loading. Please reload this page.