diff --git a/src/clientlayers/StreamRequest.jl b/src/clientlayers/StreamRequest.jl index 9d8c48a4..3f7d1f7f 100644 --- a/src/clientlayers/StreamRequest.jl +++ b/src/clientlayers/StreamRequest.jl @@ -1,6 +1,7 @@ module StreamRequest using ..IOExtras, ..Messages, ..Streams, ..Connections, ..Strings, ..RedirectRequest, ..Exceptions +using ..Messages: isredirect, allow_redirects, redirectlimitreached, header using CodecZlib, URIs using SimpleBufferStream: BufferStream using ConcurrentUtilities: @samethreadpool_spawn @@ -143,9 +144,21 @@ function readbody(stream::Stream, res::Response, decompress::Union{Nothing, Bool end end +# Check if this redirect response will be followed +# Based on the logic in RedirectRequest.redirectlayer +function willredirect(res::Response) + req = res.request + return ( + isredirect(res) && + allow_redirects(req) && + !redirectlimitreached(req) && + header(res, "Location") != "" + ) +end + function readbody!(stream::Stream, res::Response, buf_or_stream, lock) n = 0 - if !iserror(res) + if !iserror(res) && !willredirect(res) if isbytes(res.body) if length(res.body) > 0 # user-provided buffer to read response body into @@ -179,6 +192,7 @@ function readbody!(stream::Stream, res::Response, buf_or_stream, lock) # read the response body into the request context so that it can be # read by the user if they want to or set later if # we end up not retrying/redirecting/etc. + # This handles both error responses and redirect responses that will be followed Base.@lock lock begin res.request.context[:response_body] = read(buf_or_stream) end diff --git a/test/client.jl b/test/client.jl index ad76ad9f..fc96c184 100644 --- a/test/client.jl +++ b/test/client.jl @@ -287,6 +287,73 @@ end @test isok(HTTP.request(read_method, "https://$httpbin/redirect-to?url=http%3A%2F%2Fgoogle.com", socket_type_tls=tls)) end + @testset "Client Redirect Response Stream" begin + # Test that redirect response bodies are not written to response_stream + # (Issue #1165: https://github.com/JuliaWeb/HTTP.jl/issues/1165) + server = nothing + try + server = HTTP.listen!("127.0.0.1", 8123) do http + if http.message.target == "/final" + HTTP.setstatus(http, 200) + HTTP.startwrite(http) + HTTP.write(http, "final_response") + else + HTTP.setstatus(http, 301) + HTTP.setheader(http, "Location" => "/final") + HTTP.startwrite(http) + HTTP.write(http, "redirect_response") + end + return + end + + # Test with response_stream - should only contain final response + buffer = IOBuffer() + resp = HTTP.get("http://127.0.0.1:8123"; response_stream=buffer) + result = String(take!(buffer)) + @test result == "final_response" + @test resp.status == 200 + + # Test multiple redirects - should only contain final response + server2 = HTTP.listen!("127.0.0.1", 8124) do http + if http.message.target == "/final" + HTTP.setstatus(http, 200) + HTTP.startwrite(http) + HTTP.write(http, "final") + elseif http.message.target == "/redirect2" + HTTP.setstatus(http, 302) + HTTP.setheader(http, "Location" => "/final") + HTTP.startwrite(http) + HTTP.write(http, "second_redirect") + else + HTTP.setstatus(http, 301) + HTTP.setheader(http, "Location" => "/redirect2") + HTTP.startwrite(http) + HTTP.write(http, "first_redirect") + end + return + end + + try + buffer2 = IOBuffer() + HTTP.get("http://127.0.0.1:8124"; response_stream=buffer2) + result2 = String(take!(buffer2)) + @test result2 == "final" + finally + close(server2) + end + + # Test with redirect=false - should include redirect body + buffer3 = IOBuffer() + resp3 = HTTP.get("http://127.0.0.1:8123"; response_stream=buffer3, redirect=false) + result3 = String(take!(buffer3)) + @test result3 == "redirect_response" + @test resp3.status == 301 + + finally + server !== nothing && close(server) + end + end + @testset "Client Basic Auth" begin @test isok(HTTP.get("https://user:pwd@$httpbin/basic-auth/user/pwd", socket_type_tls=tls)) @test isok(HTTP.get("https://user:pwd@$httpbin/hidden-basic-auth/user/pwd", socket_type_tls=tls))