Skip to content

Commit

Permalink
Cache Parser in ConnectionPool.
Browse files Browse the repository at this point in the history
Make stack dynamically configurable.
  • Loading branch information
samoconnor committed Dec 13, 2017
1 parent 808b577 commit 4b601f5
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 63 deletions.
19 changes: 13 additions & 6 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,18 +273,15 @@ const DefaultStack =
RetryLayer{
ExceptionLayer{
MessageLayer{
ConnectionLayer{ConnectionPool.Connection,
#ConnectLayer{
ConnectionPoolLayer{
SocketLayer
}}}}}}}}

request(method::String, uri, headers=[], body=""; kw...) =
request(HTTP.DefaultStack, method, uri, headers, body; kw...)
```

Note that the `ConnectLayer`'s optional first parameter is a connection wrapper
type. If it was omitted then `ConnectionLayer` would use raw socket types from
the `Connect` module directly.


## Redirect Layer

Expand Down Expand Up @@ -348,7 +345,17 @@ This layer:
- Creates a [`HTTP.Messages.Response`](@ref) object to hold the response.


## Connection Layer
## Connect Layer

Source: `ConnectionRequest.jl`

Alternative to Connection Pool Layer below.

This layer calls [`HTTP.Connect.getconnection`](@ref)
to get a non-pooled socket.


## Connection Pool Layer

Source: `ConnectionRequest.jl`

Expand Down
5 changes: 4 additions & 1 deletion src/Connect.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
module Connect

export getconnection
export getconnection, getparser

using MbedTLS: SSLConfig, SSLContext, setup!, associate!, hostname!, handshake!

import ..Parsers.Parser
import ..@debug, ..DEBUG_LEVEL


Expand Down Expand Up @@ -38,5 +39,7 @@ function getconnection(::Type{SSLContext}, host::AbstractString,
return io
end

getparser(::IO) = Parser()


end # module Connect
14 changes: 10 additions & 4 deletions src/ConnectionPool.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
module ConnectionPool

export getconnection
export getconnection, getparser

using ..IOExtras

import ..@debug, ..DEBUG_LEVEL
import MbedTLS.SSLContext
import ..Connect.getconnection
import ..Connect: getconnection, getparser
import ..Parsers.Parser


const ByteView = typeof(view(UInt8[], 1:0))
Expand All @@ -19,7 +20,7 @@ A `TCPSocket` or `SSLContext` connection to a HTTP `host` and `port`.
- `host::String`
- `port::String`
- `io::T`
- `io::T`, the `TCPSocket` or `SSLContext.
- `excess::ByteView`, left over bytes read from the connection after
the end of a response message. These bytes are probably the start of the
next response message.
Expand All @@ -29,6 +30,7 @@ A `TCPSocket` or `SSLContext` connection to a HTTP `host` and `port`.
the first Response must be read before another Request can be written.
- `readcount::Int`, number of Response Messages that have been read.
- `readdone::Condition`, signals that an entire Response Messages has been read.
- -`parser::Parser`, reuse a `Parser` when this `Connection` is reused.
"""

mutable struct Connection{T <: IO} <: IO
Expand All @@ -39,12 +41,13 @@ mutable struct Connection{T <: IO} <: IO
writecount::Int
readcount::Int
readdone::Condition
parser::Parser
end

isbusy(c::Connection) = c.writecount - c.readcount > 1

Connection{T}(host::AbstractString, port::AbstractString, io::T) where T <: IO =
Connection{T}(host, port, io, view(UInt8[], 1:0), 0, 0, Condition())
Connection{T}(host, port, io, view(UInt8[], 1:0), 0, 0, Condition(), Parser())

const noconnection = Connection{TCPSocket}("","",TCPSocket())

Expand Down Expand Up @@ -168,6 +171,9 @@ function getconnection(::Type{Connection{T}},
end


getparser(c::Connection) = c.parser


function Base.show(io::IO, c::Connection)
print(io, c.host, ":",
c.port != "" ? c.port : Int(peerport(c)), ":",
Expand Down
33 changes: 21 additions & 12 deletions src/ConnectionRequest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ module ConnectionRequest
import ..Layer, ..RequestStack.request
using ..URIs
using ..Messages
using ..Connect
using ..ConnectionPool
using MbedTLS.SSLContext


abstract type ConnectionLayer{Connection, Next <: Layer} <: Layer end
export ConnectionLayer
abstract type ConnectionPoolLayer{Next <: Layer} <: Layer end
export ConnectionPoolLayer


sockettype(uri::URI) = uri.scheme == "https" ? SSLContext : TCPSocket


"""
Expand All @@ -17,20 +20,26 @@ export ConnectionLayer
Get a `Connection` for a `URI`, send a `Request` and fill in a `Response`.
"""

function request(::Type{ConnectionLayer{Connection, Next}},
uri::URI, req::Request, res::Response;
kw...) where Next where Connection

Socket = uri.scheme == "https" ? SSLContext : TCPSocket
function request(::Type{ConnectionPoolLayer{Next}},
uri::URI, req::Request, res::Response; kw...) where Next

io = getconnection(Connection{Socket}, uri.host, uri.port; kw...)
Connection = ConnectionPool.Connection{sockettype(uri)}
io = getconnection(Connection, uri.host, uri.port; kw...)

return request(Next, io, req, res)
end

# If no `Connection` wrapper type is provided, `Union` acts as a no-op.
request(::Type{ConnectionLayer{Next}}, a...; kw...) where Next <: Layer =
request(ConnectionLayer{Union, Next}, a...; kw...)

abstract type ConnectLayer{Next <: Layer} <: Layer end
export ConnectLayer

function request(::Type{ConnectLayer{Next}},
uri::URI, req::Request, res::Response; kw...) where Next

io = getconnection(sockettype(uri), uri.host, uri.port; kw...)

return request(Next, io, req, res)
end


end # module ConnectionRequest
51 changes: 35 additions & 16 deletions src/HTTP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ end
abstract type Layer end

module RequestStack

import ..HTTP
request(m::String, a...; kw...) = request(HTTP.DefaultStack, m, a...; kw...)
#request(m::String, a...; kw...) = request(HTTP.DefaultStack, m, a...; kw...)
request(m::String, a...; kw...) = request(HTTP.stack(;kw...), m, a...; kw...)

end

#FIXME
Expand All @@ -52,11 +55,9 @@ include("multipart.jl")

include("Parsers.jl")
import .Parsers.ParsingError
include("Messages.jl")

include("Connect.jl")
include("ConnectionPool.jl")

include("Messages.jl")

include("types.jl")
include("client.jl")
Expand All @@ -69,6 +70,8 @@ using .Nitrogen

#include("precompile.jl")



function __init__()
global const DEFAULT_CLIENT = Client()
end
Expand All @@ -94,21 +97,37 @@ using .CanonicalizeRequest
include("RedirectRequest.jl")
using .RedirectRequest

const NoLayer = Union

function stack(;redirect=true,
basicauthorization=false,
cookies=false,
retry=true,
statusexception=true,
connectionpool=true,
kw...)

(redirect ? RedirectLayer : NoLayer){
(basicauthorization ? BasicAuthLayer : NoLayer){
(cookies ? CookieLayer : NoLayer){
(retry ? RetryLayer : NoLayer){
(statusexception ? ExceptionLayer : NoLayer){
MessageLayer{
(connectionpool ? ConnectionPoolLayer : ConnectLayer){
SocketLayer
}}}}}}}
end

const DefaultStack =
RedirectLayer{
#CanonicalizeLayer{
BasicAuthLayer{
CookieLayer{
RetryLayer{
ExceptionLayer{
MessageLayer{
ConnectionLayer{ConnectionPool.Connection,
SocketLayer
}}}}}}}#}

const MinimalStack = MessageLayer{ConnectLayer{SocketLayer}}
const DefaultStack = stack()

function _precompile_()
ccall(:jl_generating_output, Cint, ()) == 1 || return nothing

@assert precompile(HTTP.RequestStack.request,
(Type{DefaultStack}, String, String, Vector{Pair{String,String}}, String))
end
_precompile_()

end # module
#=
Expand Down
17 changes: 10 additions & 7 deletions src/Messages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ using ..IOExtras
using ..Pairs
using ..Parsers
import ..Parsers
import ..ConnectionPool

import ..@debug, ..DEBUG_LEVEL

Expand Down Expand Up @@ -76,12 +77,12 @@ mutable struct Response
status::Int16
headers::Vector{Pair{String,String}}
body::Body
parent
complete::Condition
parent
end

Response(status::Int=0, headers=[]; body=Body(), parent=nothing) =
Response(v"1.1", status, headers, body, parent, Condition())
Response(v"1.1", status, headers, body, Condition(), parent)

Response(bytes) = read!(IOBuffer(bytes), Response())
Base.parse(::Type{Response}, str::AbstractString) = Response(str)
Expand Down Expand Up @@ -320,12 +321,12 @@ end


"""
Parser(::Message)
connectparser(::Message, ::Parser)
Create a parser that stores parsed data into a `Message`.
Configure a `Parser` to store parsed data into this `Message`.
"""
function Parsers.Parser(m::Message)
p = Parser()
function connectparser(m::Message, p::Parser)
reset!(p)
p.onbodyfragment = x->write(m.body, x)
p.onheader = x->appendheader(m, x)
p.onheaderscomplete = x->readstartline!(m, x)
Expand All @@ -342,7 +343,9 @@ Read data from `io` into a `Message` struct.
"""

function Base.read!(io::IO, m::Message)
read!(io, Parser(m))
parser = ConnectionPool.getparser(io)
connectparser(m, parser)
read!(io, parser)
close(m.body)
return m
end
Expand Down
10 changes: 5 additions & 5 deletions src/Parsers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

module Parsers

export Parser, parse!,
export Parser, parse!, reset!,
messagecomplete, headerscomplete, waitingforeof,
ParsingError, ParsingErrorCode

Expand Down Expand Up @@ -134,7 +134,7 @@ end
Create an unconfigured `Parser`.
"""

Parser() = Parser(false, x->nothing, x->nothing, ()->nothing,
Parser() = Parser(false, x->nothing, x->nothing, x->nothing,
s_start_req_or_res, 0, 0, 0, 0,
IOBuffer(), IOBuffer(), Message())

Expand Down Expand Up @@ -271,7 +271,7 @@ macro errorifstrict(cond)
end

macro passert(cond)
enable_passert ? esc(:(@assert($cond))) : :()
enable_passert ? esc(:(@assert $cond)) : :()
end

macro methodstate(meth, i, char)
Expand Down Expand Up @@ -781,7 +781,7 @@ function parse!(parser::Parser, bytes::ByteView)::Int
parser.header_state = h_general
end
else
error("Unknown header_state")
@err HPE_INVALID_INTERNAL_STATE
end
p += 1
end
Expand Down Expand Up @@ -874,7 +874,7 @@ function parse!(parser::Parser, bytes::ByteView)::Int
p = crlf == 0 ? len : p + crlf - 2

elseif h == h_connection || h == h_transfer_encoding
error("Shouldn't get here.")
@err HPE_INVALID_INTERNAL_STATE
elseif h == h_content_length
t = UInt64(0)
if ch == ' '
Expand Down
2 changes: 1 addition & 1 deletion src/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function request(client::Client, method, uri::URI;
h = [k => v for (k,v) in headers]

if stream
push!(args, :response_stream => BufferStream())
push!(args, (:response_stream, BufferStream()))
end

if isa(body, Dict)
Expand Down
6 changes: 3 additions & 3 deletions test/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ for sch in ("http", "https")
@test HTTP.status(HTTP.get("$sch://httpbin.org/ip")) == 200
@test HTTP.status(HTTP.head("$sch://httpbin.org/ip")) == 200
@test HTTP.status(HTTP.options("$sch://httpbin.org/ip")) == 200
@test HTTP.status(HTTP.post("$sch://httpbin.org/ip"; statusraise=false)) == 405
@test HTTP.status(HTTP.post("$sch://httpbin.org/ip"; statusexception=false)) == 405
@test HTTP.status(HTTP.post("$sch://httpbin.org/post")) == 200
@test HTTP.status(HTTP.put("$sch://httpbin.org/put")) == 200
@test HTTP.status(HTTP.delete("$sch://httpbin.org/delete")) == 200
Expand All @@ -29,10 +29,10 @@ for sch in ("http", "https")

println("cookie requests")
empty!(HTTP.CookieRequest.default_cookiejar)
r = HTTP.get("$sch://httpbin.org/cookies")
r = HTTP.get("$sch://httpbin.org/cookies", cookies=true)
body = String(take!(r))
@test body == "{\n \"cookies\": {}\n}\n"
r = HTTP.get("$sch://httpbin.org/cookies/set?hey=sailor&foo=bar")
r = HTTP.get("$sch://httpbin.org/cookies/set?hey=sailor&foo=bar", cookies=true)
@test HTTP.status(r) == 200
body = String(take!(r))
@test body == "{\n \"cookies\": {\n \"foo\": \"bar\", \n \"hey\": \"sailor\"\n }\n}\n"
Expand Down
Loading

0 comments on commit 4b601f5

Please sign in to comment.