From cfb41a98e58f438a862de18edf5e79ab7749580b Mon Sep 17 00:00:00 2001 From: Chris de Graaf Date: Wed, 28 Oct 2020 21:56:10 -0500 Subject: [PATCH] Use HTTP default layer functions instead of Cassette ...for a huge speedup. --- Project.toml | 8 ++----- src/BrokenRecord.jl | 48 ++++++++++++++++++++++++++--------------- src/playback.jl | 52 ++++++++++++++++----------------------------- src/recording.jl | 22 ++++++++++++------- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Project.toml b/Project.toml index 3cf679e..c43a769 100644 --- a/Project.toml +++ b/Project.toml @@ -1,19 +1,15 @@ name = "BrokenRecord" uuid = "bdd55f5b-6e67-4da1-a080-6086e55655a0" authors = ["Chris de Graaf and contributors"] -version = "0.1.2" +version = "0.1.3" [deps] BSON = "fbb218c0-5317-5bc6-957e-2ee96dd4b1f0" -Cassette = "7057c7e9-c182-5462-911a-8362d720325c" HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" -Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" [compat] BSON = "0.2" -Cassette = "0.3" -HTTP = "^0.8.15" -Suppressor = "0.2" +HTTP = "0.9" julia = "1" [extras] diff --git a/src/BrokenRecord.jl b/src/BrokenRecord.jl index 5ba9905..2bd2635 100644 --- a/src/BrokenRecord.jl +++ b/src/BrokenRecord.jl @@ -2,13 +2,11 @@ module BrokenRecord export playback -using Core: kwftype +using Base.Threads: nthreads, threadid using BSON: bson, load -using Cassette: Cassette, overdub, prehook, @context -using HTTP: HTTP, Header, URI, body_was_streamed, header, mkheaders, nobody, request, - request_uri -using Suppressor: @suppress +using HTTP: HTTP, Header, Layer, Response, URI, body_was_streamed, header, insert_default!, + mkheaders, nobody, remove_default!, request, request_uri, stack, top_layer const FORMAT = v"1" const DEFAULTS = Dict( @@ -16,15 +14,21 @@ const DEFAULTS = Dict( :ignore_headers => [], :ignore_query => [], ) - -function __init__() - # Hiding the stacktrace from Cassette#174. - ctx = RecordingCtx(; metadata=(; responses=[])) - @suppress try overdub(ctx, () -> HTTP.get("https://httpbin.org/get")) catch end +const STATE = map(1:nthreads()) do i + (; responses=Response[], ignore_headers=String[], ignore_query=String[]) end drop_keys(keys) = p -> !(p.first in keys) +get_state() = STATE[threadid()] + +function reset_state() + state = get_state() + empty!(state.responses) + empty!(state.ignore_headers) + empty!(state.ignore_query) +end + """ configure!(; path=nothing, ignore_headers=nothing, ignore_query=nothing) @@ -54,17 +58,27 @@ function playback( f, path; ignore_headers=DEFAULTS[:ignore_headers], ignore_query=DEFAULTS[:ignore_query], ) - metadata = (; responses=[], ignore_headers=ignore_headers, ignore_query=ignore_query) + reset_state() + state = get_state() + append!(state.ignore_headers, ignore_headers) + append!(state.ignore_query, ignore_query) + path = joinpath(DEFAULTS[:path], path) - ctx = if isfile(path) - data = load(path) - PlaybackCtx(; metadata=(; metadata..., responses=data[:responses])) + before_layer, custom_layer = if isfile(path) + top_layer(stack()), PlaybackLayer else - RecordingCtx(; metadata=metadata) + Union{}, RecordingLayer + end + + insert_default!(before_layer, custom_layer) + before(custom_layer, path) + result = try + f() + finally + remove_default!(before_layer, custom_layer) + after(custom_layer, path) end - result = overdub(ctx, f) - after(ctx, path) return result end diff --git a/src/playback.jl b/src/playback.jl index b9bb841..0a839a1 100644 --- a/src/playback.jl +++ b/src/playback.jl @@ -1,50 +1,34 @@ -@context PlaybackCtx +abstract type PlaybackLayer{Next <: Layer} <: Layer{Next} end -const HTTPMethod = Union{AbstractString, Symbol} const NoQuery = Dict{SubString{String}, SubString{String}} -function Cassette.prehook( - ctx::PlaybackCtx, ::kwftype(typeof(request)), kwargs, ::typeof(request), - m::HTTPMethod, u, h=Header[], b=nobody, -) - prehook( - ctx, - request, - m, - request_uri(u, get(kwargs, :query, nothing)), - get(kwargs, :headers, h), - get(kwargs, :body, b), - ) +function before(::Type{<:PlaybackLayer}, path) + data = load(path) + state = get_state() + append!(state.responses, data[:responses]) end -function Cassette.prehook( - ctx::PlaybackCtx, ::typeof(request), m::HTTPMethod, u, h=Header[], body=nobody, -) +function HTTP.request(::Type{<:PlaybackLayer}, m, u, h=Header[], b=nobody; kwargs...) method = string(m) - uri = request_uri(u, nothing) - headers = mkheaders(h) - isempty(ctx.metadata.responses) && error("No responses remaining in the data file") - response = ctx.metadata.responses[1] + uri = request_uri(u, get(kwargs, :query, nothing)) + headers = mkheaders(get(kwargs, :headers, h)) + body = get(kwargs, :body, b) + state = get_state() + isempty(state) && error("No responses remaining in the data file") + response = popfirst!(state.responses) request = response.request check_body(request, body) check_method(request, method) - check_headers(request, headers; ignore=ctx.metadata.ignore_headers) - check_uri(request, uri; ignore=ctx.metadata.ignore_query) + check_headers(request, headers; ignore=state.ignore_headers) + check_uri(request, uri; ignore=state.ignore_query) + return response end -function Cassette.overdub( - ctx::PlaybackCtx, ::kwftype(typeof(request)), k, ::typeof(request), - m::HTTPMethod, u, h=Header[], b=nobody, -) - return overdub(ctx, request, m, u, h, b) +function after(::Type{<:PlaybackLayer}, path) + state = get_state() + isempty(state.responses) || error("Found unused responses") end -Cassette.overdub(ctx::PlaybackCtx, ::typeof(request), ::HTTPMethod, u, h, b) = - popfirst!(ctx.metadata.responses) - -after(ctx::PlaybackCtx, path) = - isempty(ctx.metadata.responses) || error("Found unused responses") - parse_query(uri) = isempty(uri.query) ? NoQuery() : Dict(split.(split(uri.query, '&'), '=')) function check_body(request, body) diff --git a/src/recording.jl b/src/recording.jl index 79cae2b..00211ff 100644 --- a/src/recording.jl +++ b/src/recording.jl @@ -1,15 +1,21 @@ -@context RecordingCtx +abstract type RecordingLayer{Next <: Layer} <: Layer{Next} end -Cassette.posthook(ctx::RecordingCtx, resp, ::typeof(request), ::Type{Union{}}, args...) = - push!(ctx.metadata.responses, deepcopy(resp)) +before(::Type{<:RecordingLayer}, path) = nothing -function after(ctx::RecordingCtx, path) - for resp in ctx.metadata.responses - filter!(drop_keys(ctx.metadata.ignore_headers), resp.request.headers) - resp.request.target = filter_query(resp.request, ctx.metadata.ignore_query) +function HTTP.request(::Type{RecordingLayer{Next}}, resp) where Next + state = get_state() + push!(state.responses, deepcopy(resp)) + return request(Next, resp) +end + +function after(::Type{<:RecordingLayer}, path) + state = get_state() + for resp in state.responses + filter!(drop_keys(state.ignore_headers), resp.request.headers) + resp.request.target = filter_query(resp.request, state.ignore_query) end mkpath(dirname(path)) - bson(path; responses=ctx.metadata.responses, format=FORMAT) + bson(path; responses=state.responses, format=FORMAT) end function filter_query(request, ignore)