Skip to content

Commit cf8f81d

Browse files
authored
Merge pull request #69 from bgreni/persistent-client-connection
Keep persistent connections in client
2 parents e7710b9 + 5fac875 commit cf8f81d

File tree

7 files changed

+52
-54
lines changed

7 files changed

+52
-54
lines changed

lightbug_http/client.mojo

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from lightbug_http.libc import (
1+
from .libc import (
22
c_int,
33
AF_INET,
44
SOCK_STREAM,
@@ -9,29 +9,43 @@ from lightbug_http.libc import (
99
close,
1010
)
1111
from lightbug_http.strings import to_string
12-
from lightbug_http.io.bytes import Bytes
13-
from lightbug_http.utils import ByteReader
14-
from lightbug_http.net import create_connection, default_buffer_size
12+
from lightbug_http.net import default_buffer_size
1513
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
1614
from lightbug_http.header import Headers, HeaderKey
15+
from lightbug_http.net import create_connection, SysConnection
16+
from lightbug_http.io.bytes import Bytes
17+
from lightbug_http.utils import ByteReader
18+
from collections import Dict
1719

1820

1921
struct Client:
2022
var host: StringLiteral
2123
var port: Int
2224
var name: String
2325

26+
var _connections: Dict[String, SysConnection]
27+
2428
fn __init__(inout self) raises:
2529
self.host = "127.0.0.1"
2630
self.port = 8888
2731
self.name = "lightbug_http_client"
32+
self._connections = Dict[String, SysConnection]()
2833

2934
fn __init__(inout self, host: StringLiteral, port: Int) raises:
3035
self.host = host
3136
self.port = port
3237
self.name = "lightbug_http_client"
38+
self._connections = Dict[String, SysConnection]()
3339

34-
fn do(self, owned req: HTTPRequest) raises -> HTTPResponse:
40+
fn __del__(owned self):
41+
for conn in self._connections.values():
42+
try:
43+
conn[].close()
44+
except:
45+
# TODO: Add an optional debug log entry here
46+
pass
47+
48+
fn do(inout self, owned req: HTTPRequest) raises -> HTTPResponse:
3549
"""
3650
The `do` method is responsible for sending an HTTP request to a server and receiving the corresponding response.
3751
@@ -83,31 +97,47 @@ struct Client:
8397
else:
8498
port = 80
8599

86-
# TODO: Actually handle persistent connections
87-
var conn = create_connection(socket(AF_INET, SOCK_STREAM, 0), host_str, port)
100+
var conn: SysConnection
101+
var cached_connection = False
102+
if host_str in self._connections:
103+
conn = self._connections[host_str]
104+
cached_connection = True
105+
else:
106+
conn = create_connection(socket(AF_INET, SOCK_STREAM, 0), host_str, port)
107+
self._connections[host_str] = conn
108+
88109
var bytes_sent = conn.write(encode(req))
89110
if bytes_sent == -1:
111+
# Maybe peer reset ungracefully, so try a fresh connection
112+
self._close_conn(host_str)
113+
if cached_connection:
114+
return self.do(req^)
90115
raise Error("Failed to send message")
91-
116+
92117
var new_buf = Bytes(capacity=default_buffer_size)
93118
var bytes_recv = conn.read(new_buf)
94119

95120
if bytes_recv == 0:
96-
conn.close()
121+
self._close_conn(host_str)
122+
if cached_connection:
123+
return self.do(req^)
124+
raise Error("No response received")
97125
try:
98126
var res = HTTPResponse.from_bytes(new_buf^)
99127
if res.is_redirect():
100-
conn.close()
128+
self._close_conn(host_str)
101129
return self._handle_redirect(req^, res^)
130+
if res.connection_close():
131+
self._close_conn(host_str)
102132
return res
103133
except e:
104-
conn.close()
134+
self._close_conn(host_str)
105135
raise e
106136

107137
return HTTPResponse(Bytes())
108138

109139
fn _handle_redirect(
110-
self, owned original_req: HTTPRequest, owned original_response: HTTPResponse
140+
inout self, owned original_req: HTTPRequest, owned original_response: HTTPResponse
111141
) raises -> HTTPResponse:
112142
var new_uri: URI
113143
var new_location = original_response.headers[HeaderKey.LOCATION]
@@ -119,3 +149,7 @@ struct Client:
119149
new_uri.path = new_location
120150
original_req.uri = new_uri
121151
return self.do(original_req^)
152+
153+
fn _close_conn(inout self, host: String) raises:
154+
self._connections[host].close()
155+
_ = self._connections.pop(host)

lightbug_http/header.mojo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct HeaderKey:
1515
alias DATE = "date"
1616
alias LOCATION = "location"
1717
alias HOST = "host"
18+
alias SERVER = "server"
1819

1920

2021
@value

lightbug_http/http/request.mojo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ from lightbug_http.strings import (
1515
to_string,
1616
)
1717

18+
1819
@value
1920
struct HTTPRequest(Formattable, Stringable):
2021
var headers: Headers

lightbug_http/http/response.mojo

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,10 @@ struct HTTPResponse(Formattable, Stringable):
125125
self.set_content_length(len(self.body_raw))
126126

127127
fn format_to(self, inout writer: Formatter):
128-
writer.write(
129-
self.protocol,
130-
whitespace,
131-
self.status_code,
132-
whitespace,
133-
self.status_text,
134-
lineBreak,
135-
"server: lightbug_http",
136-
lineBreak,
137-
)
128+
writer.write(self.protocol, whitespace, self.status_code, whitespace, self.status_text, lineBreak)
129+
130+
if HeaderKey.SERVER not in self.headers:
131+
writer.write("server: lightbug_http", lineBreak)
138132

139133
self.headers.format_to(writer)
140134

magic.lock

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ environments:
1212
- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda
1313
- conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda
1414
- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.7-unix_pyh707e725_0.conda
15-
- conda: https://repo.prefix.dev/mojo-community/linux-64/gojo-0.1.9-hb0f4dca_0.conda
1615
- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda
1716
- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda
1817
- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.2-pyhd8ed1ab_0.conda
@@ -71,7 +70,6 @@ environments:
7170
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda
7271
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.8.30-hf0a4a13_0.conda
7372
- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.7-unix_pyh707e725_0.conda
74-
- conda: https://repo.prefix.dev/mojo-community/osx-arm64/gojo-0.1.9-h60d57d3_0.conda
7573
- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda
7674
- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda
7775
- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.2-pyhd8ed1ab_0.conda
@@ -216,34 +214,6 @@ packages:
216214
license_family: BSD
217215
size: 84437
218216
timestamp: 1692311973840
219-
- kind: conda
220-
name: gojo
221-
version: 0.1.9
222-
build: h60d57d3_0
223-
subdir: osx-arm64
224-
url: https://repo.prefix.dev/mojo-community/osx-arm64/gojo-0.1.9-h60d57d3_0.conda
225-
sha256: 4c268d0d8d5f1b78a547e78a3db9e8037143918cd1d696f5adb5db55942cef5e
226-
depends:
227-
- max >=24.5.0,<24.6.0
228-
arch: arm64
229-
platform: osx
230-
license: MIT
231-
size: 1009999
232-
timestamp: 1726268309700
233-
- kind: conda
234-
name: gojo
235-
version: 0.1.9
236-
build: hb0f4dca_0
237-
subdir: linux-64
238-
url: https://repo.prefix.dev/mojo-community/linux-64/gojo-0.1.9-hb0f4dca_0.conda
239-
sha256: 9a49e21b4269368a6d906769bd041b8b91b99da3375d7944f7d8ddd73392c2f0
240-
depends:
241-
- max >=24.5.0,<24.6.0
242-
arch: x86_64
243-
platform: linux
244-
license: MIT
245-
size: 1011206
246-
timestamp: 1726268249824
247217
- kind: conda
248218
name: importlib-metadata
249219
version: 8.5.0

mojoproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,4 @@ format = { cmd = "magic run mojo format -l 120 lightbug_http" }
1616

1717
[dependencies]
1818
max = ">=24.5.0,<25"
19-
gojo = "0.1.9"
2019
small_time = "0.1.3"

recipes/recipe.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ build:
1919
requirements:
2020
run:
2121
- max >=24.5.0
22-
- gojo == 0.1.9
2322
- small_time == 0.1.3
2423

2524
about:

0 commit comments

Comments
 (0)