diff --git a/src/DebugRequest.jl b/src/DebugRequest.jl new file mode 100644 index 000000000..d8e2d692f --- /dev/null +++ b/src/DebugRequest.jl @@ -0,0 +1,32 @@ +module DebugRequest + +import ..Layer, ..request +using ..IOExtras +import ..ConnectionPool: ByteView, byteview + + +include("IODebug.jl") + + +""" + request(DebugLayer, ::IO, ::Request, body) -> HTTP.Response + +Wrap the `IO` stream in an `IODebug` stream and print Message data. +""" +abstract type DebugLayer{Next <:Layer} <: Layer end +export DebugLayer + + +function request(::Type{DebugLayer{Next}}, io::IO, req, body; kw...) where Next + + iod = IODebug(io) + + try + return request(Next, iod, req, body; kw...) + finally + print(STDOUT, iod) + end +end + + +end # module DebugRequest diff --git a/src/HTTP.jl b/src/HTTP.jl index d71723ebf..2476ff721 100644 --- a/src/HTTP.jl +++ b/src/HTTP.jl @@ -423,6 +423,7 @@ include("ExceptionRequest.jl"); using .ExceptionRequest import .ExceptionRequest.StatusError include("RetryRequest.jl"); using .RetryRequest include("ConnectionRequest.jl"); using .ConnectionRequest +include("DebugRequest.jl"); using .DebugRequest include("StreamRequest.jl"); using .StreamRequest include("ContentTypeRequest.jl"); using .ContentTypeDetection @@ -545,6 +546,7 @@ function stack(;redirect=true, status_exception=true, readtimeout=0, detect_content_type=false, + verbose=0, kw...) NoLayer = Union @@ -559,9 +561,10 @@ function stack(;redirect=true, (retry ? RetryLayer : NoLayer){ (status_exception ? ExceptionLayer : NoLayer){ ConnectionPoolLayer{ + (verbose >= 3 ? DebugLayer : NoLayer){ (readtimeout > 0 ? TimeoutLayer : NoLayer){ StreamLayer - }}}}}}}}}}} + }}}}}}}}}}}} end include("client.jl") diff --git a/src/IODebug.jl b/src/IODebug.jl new file mode 100644 index 000000000..4cdd0b8d7 --- /dev/null +++ b/src/IODebug.jl @@ -0,0 +1,71 @@ +struct IODebug{T <: IO} <: IO + io::T + log::Vector{Tuple{String,String}} +end + +IODebug(io::T) where T <: IO = IODebug{T}(io, []) + +logwrite(iod::IODebug, x) = push!(iod.log, ("➡️ ", x)) +logread(iod::IODebug, x) = push!(iod.log, ("⬅️ ", x)) +logunread(iod::IODebug, x) = push!(iod.log, ("♻️ ", x)) + +Base.write(iod::IODebug, a...) = (logwrite(iod, join(a)); write(iod.io, a...)) + +Base.write(iod::IODebug, x::String) = (logwrite(iod, x); write(iod.io, x)) + +Base.unsafe_write(iod::IODebug, x::Ptr{UInt8}, n::UInt) = + (logwrite(iod, unsafe_string(x,n)); + unsafe_write(iod.io, x, n)) + +Base.read(iod::IODebug, n) = + (r = read(iod.io, n); + logread(iod, String(r)); r) + +Base.readavailable(iod::IODebug) = + (r = readavailable(iod.io); + logread(iod, String(r)); r) + +IOExtras.unread!(iod::IODebug, bytes) = + (logunread(iod, String(bytes)); + unread!(iod.io, bytes)) + +Base.eof(iod::IODebug) = eof(iod.io) +Base.close(iod::IODebug) = close(iod.io) +Base.isopen(iod::IODebug) = isopen(iod.io) +Base.iswritable(iod::IODebug) = iswritable(iod.io) +Base.isreadable(iod::IODebug) = isreadable(iod.io) +IOExtras.startread(iod::IODebug) = startread(iod.io) +IOExtras.startwrite(iod::IODebug) = startwrite(iod.io) +IOExtras.closeread(iod::IODebug) = closeread(iod.io) +IOExtras.closewrite(iod::IODebug) = closewrite(iod.io) + +@static if isdefined(Base, :bytesavailable) + Base.bytesavailable(iod::IODebug) = bytesavailable(iod.io) +else + Base.nb_available(iod::IODebug) = nb_available(iod.io) +end + +function Base.show(io::IO, iod::IODebug) + lock(io) + println(io, "$(typeof(iod)):\nio: $(iod.io)") + prevop = "" + for (operation, bytes) in iod.log + if prevop != "" && prevop != operation + println(io) + end + prefix = rpad(operation, 5) + i = j = 1 + while i < length(bytes) + j = findnext(bytes, '\n', i) + if j == nothing || j == 0 + j = length(bytes) + end + println(io, prefix, "\"", escape_string(bytes[i:j]), "\"") + prefix = " " + i = nextind(bytes, j) + end + prevop = operation + end + println(io) + unlock(io) +end