Skip to content

Commit

Permalink
Change handler factory creation of HTTPClient (issue #102)
Browse files Browse the repository at this point in the history
The handler factory was supplied in the apply method of a client
together with the Payload for the request. If the client
already had a session for the new host, port and scheme, no new
session was created and the supplied handler factory wasn't used.

The new design makes it clear, that one client uses the same handler
factory for all requests it makes. If different handler factories are
needed, different clients need to be created.
  • Loading branch information
sacovo committed Jan 21, 2024
1 parent 6775d62 commit 18653a3
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 34 deletions.
12 changes: 9 additions & 3 deletions examples/httpget/httpget.pony
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,19 @@ actor _GetWork
env.exitcode(1)
end

// The Client manages all links.
let client = HTTPClient(TCPConnectAuth(env.root), consume sslctx where keepalive_timeout_secs = timeout.u32())
// The Notify Factory will create HTTPHandlers as required. It is
// done this way because we do not know exactly when an HTTPSession
// is created - they can be re-used.
let dumpMaker = recover val NotifyFactory.create(this) end

// The Client manages all links.
let client = HTTPClient(
TCPConnectAuth(env.root),
dumpMaker,
consume sslctx
where keepalive_timeout_secs = timeout.u32()
)

try
// Start building a GET request.
let req = Payload.request("GET", url)
Expand All @@ -119,7 +125,7 @@ actor _GetWork
end

// Submit the request
let sentreq = client(consume req, dumpMaker)?
let sentreq = client(consume req)?

// Could send body data via `sentreq`, if it was a POST
else
Expand Down
4 changes: 2 additions & 2 deletions http/_test.pony
Original file line number Diff line number Diff line change
Expand Up @@ -435,13 +435,13 @@ class \nodoc\ iso _HTTPConnTest is UnitTest
let url = URL.build(us)?
h.log("url.string()=" + url.string())
let hf = _HTTPConnTestHandlerFactory(h)
client = recover iso HTTPClient(TCPConnectAuth(h.env.root)) end
client = recover iso HTTPClient(TCPConnectAuth(h.env.root), hf) end

for _ in Range(0, loops) do
let payload: Payload iso = Payload.request("GET", url)
payload.set_length(0)
try
(client as HTTPClient iso)(consume payload, hf)?
(client as HTTPClient iso)(consume payload)?
end
end
else
Expand Down
6 changes: 2 additions & 4 deletions http/_test_client.pony
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,14 @@ class \nodoc\ iso _ClientStreamTransferTest is UnitTest
try
let client = HTTPClient(
TCPConnectAuth(_h.env.root),
_StreamTransferHandlerFactory(_h),
None
where keepalive_timeout_secs = U32(2)
)
(let host, let port) = listen.local_address().name()?
_h.log("connecting to server at " + host + ":" + port)
let req = Payload.request("GET", URL.build("http://" + host + ":" + port + "/bla")?)
client(
consume req,
_StreamTransferHandlerFactory(_h)
)?
client(consume req)?
else
_h.fail("request building failed")
end
Expand Down
15 changes: 6 additions & 9 deletions http/_test_client_error_handling.pony
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,15 @@ class \nodoc\ iso _ConnectionClosedTest is UnitTest
try
let client = HTTPClient(
TCPConnectAuth(_h.env.root),
_ConnectionClosedHandlerFactory(_h),
None
where keepalive_timeout_secs = U32(2)
)
(let host, let port) = listen.local_address().name()?
let req = Payload.request("GET",
URL.build("http://" + host + ":" + port + "/bla")?)
req.add_chunk("CHUNK")
client(
consume req,
_ConnectionClosedHandlerFactory(_h)
)?
client(consume req)?
else
_h.fail("request building failed")
end
Expand Down Expand Up @@ -123,13 +121,14 @@ actor \nodoc\ _Connecter
try
let client = HTTPClient(
TCPConnectAuth(_h.env.root),
_ConnectFailedHandlerFactory(_h),
None
where keepalive_timeout_secs = U32(2)
)
let req = Payload.request("GET",
URL.build("http://" + host + ":" + port' + "/bla")?)
req.add_chunk("CHUNK")
client(consume req, _ConnectFailedHandlerFactory(_h))?
client(consume req)?
else
_h.fail("request building failed")
end
Expand Down Expand Up @@ -303,17 +302,15 @@ class \nodoc\ iso _SSLAuthFailedTest is UnitTest
end
let client = HTTPClient(
TCPConnectAuth(_h.env.root),
_SSLAuthFailedHandlerFactory(_h),
ssl_ctx
where keepalive_timeout_secs = U32(2)
)
let req = Payload.request(
"GET",
URL.build("https://" + host + ":" + port + "/bla")?)
req.add_chunk("CHUNK")
client(
consume req,
_SSLAuthFailedHandlerFactory(_h)
)?
client(consume req)?
else
_h.fail("request building failed")
end
Expand Down
28 changes: 14 additions & 14 deletions http/http_client.pony
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,24 @@ class HTTPClient
let _pipeline: Bool
let _keepalive_timeout_secs: U32
let _sessions: Map[_HostService, _ClientConnection] = _sessions.create()
let _handlermaker: HandlerFactory val

new create(
auth: TCPConnectAuth,
handlermaker: HandlerFactory val,
sslctx: (SSLContext | None) = None,
pipeline: Bool = true,
keepalive_timeout_secs: U32 = 0)
=>
"""
Create the context in which all HTTP sessions will originate.
Create the context in which all HTTP sessions will originate. The `handlermaker`
is used to create the `HTTPHandler` that is applied with each received
payload after making a request. All requests made with one client are created
using the same handler factory, if you need different handlers for different
requests, you need to create different clients.
Parameters:
- keepalive_timeout_secs: Use TCP Keepalive and check if the other side is down
every `keepalive_timeout_secs` seconds.
"""
Expand All @@ -40,12 +47,9 @@ class HTTPClient

_pipeline = pipeline
_keepalive_timeout_secs = keepalive_timeout_secs
_handlermaker = handlermaker

fun ref apply(
request: Payload trn,
handlermaker: HandlerFactory val)
: Payload val ?
=>
fun ref apply(request: Payload trn) : Payload val ? =>
"""
Schedule a request on an HTTP session. If a new connection is created,
a new instance of the application's Receive Handler will be created
Expand All @@ -54,7 +58,7 @@ class HTTPClient
This is useful in Stream and Chunked transfer modes, so that the
application can follow up with calls to `Client.send_body`.
"""
let session = _get_session(request.url, handlermaker)?
let session = _get_session(request.url)?
let mode = request.transfer_mode
request.session = session
let valrequest: Payload val = consume request
Expand All @@ -80,11 +84,7 @@ class HTTPClient
end
*/

fun ref _get_session(
url: URL,
handlermaker: HandlerFactory val)
: _ClientConnection ?
=>
fun ref _get_session(url: URL) : _ClientConnection ? =>
"""
Gets or creates an HTTP Session for the given URL. If a new session
is created, a new Receive Handler instance is created too.
Expand All @@ -100,10 +100,10 @@ class HTTPClient
match url.scheme
| "http" =>
_ClientConnection(_auth, hs.host, hs.service,
None, _pipeline, _keepalive_timeout_secs, handlermaker)
None, _pipeline, _keepalive_timeout_secs, _handlermaker)
| "https" =>
_ClientConnection(_auth, hs.host, hs.service,
_sslctx, _pipeline, _keepalive_timeout_secs, handlermaker)
_sslctx, _pipeline, _keepalive_timeout_secs, _handlermaker)
else
error
end
Expand Down
4 changes: 2 additions & 2 deletions http/test_netssl_105_regression.pony
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ class \nodoc\ iso _NetSSL105RegressionTest is UnitTest
try
let url = URL.build("https://echo.sacovo.ch")?
let auth = TCPConnectAuth(h.env.root)
let client = HTTPClient(auth)
let client = HTTPClient(auth, _NetSSL105RegressionHandlerFactory(h))
let payload = Payload.request("GET", url)
client.apply(consume payload, _NetSSL105RegressionHandlerFactory(h))?
client.apply(consume payload)?
else
h.fail("Unable to setup test")
end

0 comments on commit 18653a3

Please sign in to comment.