Skip to content

Commit 32810e4

Browse files
authored
Merge pull request #28 from ZacHooper/feature/http-client
improve/refactor http client
2 parents 3a70135 + 9b4e098 commit 32810e4

File tree

8 files changed

+395
-62
lines changed

8 files changed

+395
-62
lines changed

lightbug_http/header.mojo

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ struct RequestHeader:
4444
self.raw_headers = Bytes()
4545
self.__trailer = Bytes()
4646

47+
fn __init__(inout self, host: String) -> None:
48+
self.disable_normalization = False
49+
self.no_http_1_1 = False
50+
self.__connection_close = False
51+
self.content_length = 0
52+
self.content_length_bytes = Bytes()
53+
self.__method = Bytes()
54+
self.__request_uri = Bytes()
55+
self.proto = Bytes()
56+
self.__host = host._buffer
57+
self.__content_type = Bytes()
58+
self.__user_agent = Bytes()
59+
self.raw_headers = Bytes()
60+
self.__trailer = Bytes()
61+
4762
fn __init__(inout self, rawheaders: Bytes) -> None:
4863
self.disable_normalization = False
4964
self.no_http_1_1 = False

lightbug_http/http.mojo

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ struct HTTPRequest(Request):
8383
var disable_redirect_path_normalization: Bool
8484

8585
fn __init__(inout self, uri: URI):
86+
self.header = RequestHeader(String("127.0.0.1"))
87+
self.__uri = uri
88+
self.body_raw = Bytes()
89+
self.parsed_uri = False
90+
self.server_is_tls = False
91+
self.timeout = Duration()
92+
self.disable_redirect_path_normalization = False
93+
94+
fn __init__(inout self, uri: URI, headers: RequestHeader):
8695
self.header = RequestHeader()
8796
self.__uri = uri
8897
self.body_raw = Bytes()
@@ -227,6 +236,48 @@ fn OK(body: Bytes, content_type: String) -> HTTPResponse:
227236
)
228237

229238

239+
fn encode(req: HTTPRequest) raises -> Bytes:
240+
var res_str = String()
241+
var protocol = strHttp11
242+
var current_time = String()
243+
244+
var builder = StringBuilder()
245+
246+
_ = builder.write(req.header.method())
247+
_ = builder.write_string(String(" "))
248+
_ = builder.write(req.header.request_uri())
249+
_ = builder.write_string(String(" "))
250+
_ = builder.write(protocol)
251+
_ = builder.write_string(String("\r\n"))
252+
253+
_ = builder.write_string(String("Host: " + req.host()))
254+
_ = builder.write_string(String("\r\n"))
255+
256+
if len(req.body_raw) > 0:
257+
_ = builder.write_string(String("Content-Type: "))
258+
_ = builder.write(req.header.content_type())
259+
_ = builder.write_string(String("\r\n"))
260+
261+
_ = builder.write_string(String("Content-Length: "))
262+
_ = builder.write_string(String(len(req.body_raw)))
263+
_ = builder.write_string(String("\r\n"))
264+
265+
_ = builder.write_string(String("Connection: "))
266+
if req.connection_close():
267+
_ = builder.write_string(String("close"))
268+
else:
269+
_ = builder.write_string(String("keep-alive"))
270+
_ = builder.write_string(String("\r\n"))
271+
272+
_ = builder.write_string(String("\r\n"))
273+
if len(req.body_raw) > 0:
274+
_ = builder.write_string(String("\r\n"))
275+
_ = builder.write(req.body_raw)
276+
277+
# Currently the server is expecting a null terminated string for conn.send().
278+
return builder.get_null_terminated_bytes()
279+
280+
230281
fn encode(res: HTTPResponse) raises -> Bytes:
231282
var res_str = String()
232283
var protocol = strHttp11
@@ -236,38 +287,45 @@ fn encode(res: HTTPResponse) raises -> Bytes:
236287
except e:
237288
print("Error getting current time: " + str(e))
238289
current_time = str(now())
290+
239291
var builder = StringBuilder()
292+
240293
_ = builder.write(protocol)
241294
_ = builder.write_string(String(" "))
242295
_ = builder.write_string(String(res.header.status_code()))
243296
_ = builder.write_string(String(" "))
244297
_ = builder.write(res.header.status_message())
245298
_ = builder.write_string(String("\r\n"))
299+
246300
_ = builder.write_string(String("Server: lightbug_http"))
247301
_ = builder.write_string(String("\r\n"))
302+
248303
_ = builder.write_string(String("Content-Type: "))
249304
_ = builder.write(res.header.content_type())
305+
_ = builder.write_string(String("\r\n"))
306+
250307
# TODO: propagate charset
251308
# res_str += String("; charset=utf-8")
252-
_ = builder.write_string(String("\r\n"))
253-
_ = builder.write_string(String("Content-Length: "))
254-
# TODO: fix this
255-
_ = builder.write_string(String(len(res.body_raw)))
256-
_ = builder.write_string(String("\r\n"))
309+
310+
if len(res.body_raw) > 0:
311+
_ = builder.write_string(String("Content-Length: "))
312+
_ = builder.write_string(String(len(res.body_raw)))
313+
_ = builder.write_string(String("\r\n"))
314+
257315
_ = builder.write_string(String("Connection: "))
258316
if res.connection_close():
259317
_ = builder.write_string(String("close"))
260318
else:
261319
_ = builder.write_string(String("keep-alive"))
262320
_ = builder.write_string(String("\r\n"))
321+
263322
_ = builder.write_string(String("Date: "))
264323
_ = builder.write_string(String(current_time))
265-
_ = builder.write_string(String("\r\n"))
266-
_ = builder.write_string(String("\r\n"))
267-
# _ = builder.write_string("<div>hello frend</div>")
268-
# _ = builder.write(String("\r\n"))
269-
# _ = builder.write(res.body())
270-
_ = builder.write(res.body_raw)
324+
325+
if len(res.body_raw) > 0:
326+
_ = builder.write_string(String("\r\n"))
327+
_ = builder.write_string(String("\r\n"))
328+
_ = builder.write(res.body_raw)
271329

272330
# Currently the server is expecting a null terminated string for conn.send().
273331
return builder.get_null_terminated_bytes()

lightbug_http/strings.mojo

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ from lightbug_http.io.bytes import Bytes
22

33
alias strSlash = String("/").as_bytes()
44
alias strHttp = String("http").as_bytes()
5+
alias http = String("http")
56
alias strHttps = String("https").as_bytes()
7+
alias https = String("https")
68
alias strHttp11 = String("HTTP/1.1").as_bytes()
79
alias strHttp10 = String("HTTP/1.0").as_bytes()
810

lightbug_http/sys/client.mojo

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,24 @@
11
from lightbug_http.client import Client
2-
from lightbug_http.http import HTTPRequest, HTTPResponse
2+
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
3+
from lightbug_http.sys.net import create_connection
4+
from lightbug_http.io.bytes import Bytes
35
from external.libc import (
4-
c_void,
56
c_int,
6-
c_uint,
7-
c_char,
8-
sockaddr,
9-
sockaddr_in,
107
AF_INET,
118
SOCK_STREAM,
12-
SHUT_RDWR,
13-
htons,
14-
inet_pton,
15-
to_char_ptr,
169
socket,
1710
connect,
1811
send,
1912
recv,
20-
shutdown,
2113
close,
2214
)
2315

16+
2417
struct MojoClient(Client):
2518
var fd: c_int
26-
var name: String
27-
2819
var host: StringLiteral
2920
var port: Int
21+
var name: String
3022

3123
fn __init__(inout self) raises:
3224
self.fd = socket(AF_INET, SOCK_STREAM, 0)
@@ -39,14 +31,35 @@ struct MojoClient(Client):
3931
self.host = host
4032
self.port = port
4133
self.name = "lightbug_http_client"
42-
43-
fn close(self) raises:
44-
_ = shutdown(self.fd, SHUT_RDWR)
45-
var close_status = close(self.fd)
46-
if close_status == -1:
47-
print("Failed to close new_sockfd")
4834

4935
fn do(self, req: HTTPRequest) raises -> HTTPResponse:
36+
"""
37+
The `do` method is responsible for sending an HTTP request to a server and receiving the corresponding response.
38+
39+
It performs the following steps:
40+
1. Creates a connection to the server specified in the request.
41+
2. Sends the request body using the connection.
42+
3. Receives the response from the server.
43+
4. Closes the connection.
44+
5. Returns the received response as an `HTTPResponse` object.
45+
46+
Note: The code assumes that the `HTTPRequest` object passed as an argument has a valid URI with a host and port specified.
47+
48+
Parameters
49+
----------
50+
req : HTTPRequest :
51+
An `HTTPRequest` object representing the request to be sent.
52+
53+
Returns
54+
-------
55+
HTTPResponse :
56+
The received response.
57+
58+
Raises
59+
------
60+
Error :
61+
If there is a failure in sending or receiving the message.
62+
"""
5063
var uri = req.uri()
5164
try:
5265
_ = uri.parse()
@@ -58,35 +71,43 @@ struct MojoClient(Client):
5871
if host == "":
5972
raise Error("URI is nil")
6073
var is_tls = False
74+
6175
if uri.is_https():
6276
is_tls = True
6377

64-
var host_port = host.split(":")
65-
var host_str = host_port[0]
66-
67-
var ip_buf = Pointer[c_void].alloc(4)
68-
var conv_status = inet_pton(AF_INET, to_char_ptr(host_str), ip_buf)
69-
var raw_ip = ip_buf.bitcast[c_uint]().load()
78+
var host_str: String
79+
var port: Int
7080

71-
var port = atol(host_port[1])
81+
if host.__contains__(":"):
82+
var host_port = host.split(":")
83+
host_str = host_port[0]
84+
port = atol(host_port[1])
85+
else:
86+
host_str = host
87+
if is_tls:
88+
port = 443
89+
else:
90+
port = 80
7291

73-
var bin_port = htons(UInt16(port))
92+
var conn = create_connection(self.fd, host_str, port)
7493

75-
var ai = sockaddr_in(AF_INET, bin_port, raw_ip, StaticTuple[c_char, 8]())
76-
var ai_ptr = Pointer[sockaddr_in].address_of(ai).bitcast[sockaddr]()
94+
var req_encoded = encode(req)
95+
var bytes_sent = conn.write(req_encoded)
96+
if bytes_sent == -1:
97+
raise Error("Failed to send message")
7798

78-
if connect(self.fd, ai_ptr, sizeof[sockaddr_in]()) == -1:
79-
_ = shutdown(self.fd, SHUT_RDWR)
80-
raise Error("Connection error") # Ensure to exit if connection fails
99+
var response: String = ""
100+
var new_buf = Bytes()
81101

82-
var bytes_sent = send(self.fd, to_char_ptr(req.body_raw), len(req.body_raw), 0)
83-
if bytes_sent == -1:
84-
print("Failed to send message")
102+
while True:
103+
var bytes_recv = conn.read(new_buf)
104+
if bytes_recv == -1:
105+
raise Error("Failed to receive message")
106+
elif bytes_recv == 0:
107+
break
108+
else:
109+
response += String(new_buf)
85110

86-
var buf_size = 1024
87-
var buf = Pointer[UInt8]().alloc(buf_size)
88-
var bytes_recv = recv(self.fd, buf, buf_size, 0)
89-
var bytes_str = String(buf.bitcast[Int8](), bytes_recv)
90-
_ = close(self.fd)
111+
conn.close()
91112

92-
return HTTPResponse(bytes_str._buffer)
113+
return HTTPResponse(response._buffer)

0 commit comments

Comments
 (0)