Skip to content

Commit 16fcc12

Browse files
committed
update byte handling
1 parent 34f31bd commit 16fcc12

25 files changed

+581
-484
lines changed

benchmark/bench.mojo

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ from memory import Span
22
from benchmark import *
33
from lightbug_http.io.bytes import bytes, Bytes
44
from lightbug_http.header import Headers, Header
5-
from lightbug_http.utils import ByteReader, ByteWriter
5+
from lightbug_http.io.bytes import ByteReader, ByteWriter
66
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
77
from lightbug_http.uri import URI
88

@@ -11,9 +11,7 @@ alias headers = "GET /index.html HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mo
1111
alias body = "I am the body of an HTTP request" * 5
1212
alias body_bytes = bytes(body)
1313
alias Request = "GET /index.html HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/html\r\nContent-Length: 1234\r\nConnection: close\r\nTrailer: end-of-message\r\n\r\n" + body
14-
alias Response = "HTTP/1.1 200 OK\r\nserver: lightbug_http\r\ncontent-type:"
15-
" application/octet-stream\r\nconnection: keep-alive\r\ncontent-length:"
16-
" 13\r\ndate: 2024-06-02T13:41:50.766880+00:00\r\n\r\n" + body
14+
alias Response = "HTTP/1.1 200 OK\r\nserver: lightbug_http\r\ncontent-type: application/octet-stream\r\nconnection: keep-alive\r\ncontent-length: 13\r\ndate: 2024-06-02T13:41:50.766880+00:00\r\n\r\n" + body
1715

1816

1917
fn main():
@@ -26,24 +24,12 @@ fn run_benchmark():
2624
config.verbose_timing = True
2725
config.tabular_view = True
2826
var m = Bench(config)
29-
m.bench_function[lightbug_benchmark_header_encode](
30-
BenchId("HeaderEncode")
31-
)
32-
m.bench_function[lightbug_benchmark_header_parse](
33-
BenchId("HeaderParse")
34-
)
35-
m.bench_function[lightbug_benchmark_request_encode](
36-
BenchId("RequestEncode")
37-
)
38-
m.bench_function[lightbug_benchmark_request_parse](
39-
BenchId("RequestParse")
40-
)
41-
m.bench_function[lightbug_benchmark_response_encode](
42-
BenchId("ResponseEncode")
43-
)
44-
m.bench_function[lightbug_benchmark_response_parse](
45-
BenchId("ResponseParse")
46-
)
27+
m.bench_function[lightbug_benchmark_header_encode](BenchId("HeaderEncode"))
28+
m.bench_function[lightbug_benchmark_header_parse](BenchId("HeaderParse"))
29+
m.bench_function[lightbug_benchmark_request_encode](BenchId("RequestEncode"))
30+
m.bench_function[lightbug_benchmark_request_parse](BenchId("RequestParse"))
31+
m.bench_function[lightbug_benchmark_response_encode](BenchId("ResponseEncode"))
32+
m.bench_function[lightbug_benchmark_response_parse](BenchId("ResponseParse"))
4733
m.dump_report()
4834
except:
4935
print("failed to start benchmark")
@@ -100,12 +86,15 @@ fn lightbug_benchmark_request_encode(mut b: Bencher):
10086
@always_inline
10187
@parameter
10288
fn request_encode():
103-
var req = HTTPRequest(
104-
URI.parse("http://127.0.0.1:8080/some-path"),
105-
headers=headers_struct,
106-
body=body_bytes,
107-
)
108-
_ = encode(req^)
89+
try:
90+
var req = HTTPRequest(
91+
URI.parse("http://127.0.0.1:8080/some-path"),
92+
headers=headers_struct,
93+
body=body_bytes,
94+
)
95+
_ = encode(req^)
96+
except e:
97+
print("request_encode failed", e)
10998

11099
b.iter[request_encode]()
111100

@@ -130,8 +119,7 @@ fn lightbug_benchmark_header_parse(mut b: Bencher):
130119
var header = Headers()
131120
var reader = ByteReader(headers.as_bytes())
132121
_ = header.parse_raw(reader)
133-
except:
134-
print("failed")
122+
except e:
123+
print("failed", e)
135124

136125
b.iter[header_parse]()
137-
File renamed without changes.

lightbug_http/_logger.mojo

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from sys.param_env import env_get_string
2+
3+
4+
struct LogLevel:
5+
alias FATAL = 0
6+
alias ERROR = 1
7+
alias WARN = 2
8+
alias INFO = 3
9+
alias DEBUG = 4
10+
11+
12+
fn get_log_level() -> Int:
13+
"""Returns the log level based on the parameter environment variable `LOG_LEVEL`.
14+
15+
Returns:
16+
The log level.
17+
"""
18+
alias level = env_get_string["LB_LOG_LEVEL", "INFO"]()
19+
if level == "INFO":
20+
return LogLevel.INFO
21+
elif level == "WARN":
22+
return LogLevel.WARN
23+
elif level == "ERROR":
24+
return LogLevel.ERROR
25+
elif level == "DEBUG":
26+
return LogLevel.DEBUG
27+
elif level == "FATAL":
28+
return LogLevel.FATAL
29+
else:
30+
return LogLevel.INFO
31+
32+
33+
alias LOG_LEVEL = get_log_level()
34+
"""Logger level determined by the `LB_LOG_LEVEL` param environment variable.
35+
36+
When building or running the application, you can set `LB_LOG_LEVEL` by providing the the following option:
37+
38+
```bash
39+
mojo build ... -D LB_LOG_LEVEL=DEBUG
40+
# or
41+
mojo ... -D LB_LOG_LEVEL=DEBUG
42+
```
43+
"""
44+
45+
46+
@value
47+
struct Logger[level: Int]:
48+
alias STDOUT = 1
49+
alias STDERR = 2
50+
51+
fn _log_message[event_level: Int](self, message: String):
52+
@parameter
53+
if level >= event_level:
54+
55+
@parameter
56+
if event_level < LogLevel.WARN:
57+
# Write to stderr if FATAL or ERROR
58+
print(message, file=Self.STDERR)
59+
else:
60+
print(message)
61+
62+
fn info[*Ts: Writable](self, *messages: *Ts):
63+
var msg = String.write("\033[36mINFO\033[0m - ")
64+
65+
@parameter
66+
fn write_message[T: Writable](message: T):
67+
msg.write(message, " ")
68+
69+
messages.each[write_message]()
70+
self._log_message[LogLevel.INFO](msg)
71+
72+
fn warn[*Ts: Writable](self, *messages: *Ts):
73+
var msg = String.write("\033[33mWARN\033[0m - ")
74+
75+
@parameter
76+
fn write_message[T: Writable](message: T):
77+
msg.write(message, " ")
78+
79+
messages.each[write_message]()
80+
self._log_message[LogLevel.WARN](msg)
81+
82+
fn error[*Ts: Writable](self, *messages: *Ts):
83+
var msg = String.write("\033[31mERROR\033[0m - ")
84+
85+
@parameter
86+
fn write_message[T: Writable](message: T):
87+
msg.write(message, " ")
88+
89+
messages.each[write_message]()
90+
self._log_message[LogLevel.ERROR](msg)
91+
92+
fn debug[*Ts: Writable](self, *messages: *Ts):
93+
var msg = String.write("\033[34mDEBUG\033[0m - ")
94+
95+
@parameter
96+
fn write_message[T: Writable](message: T):
97+
msg.write(message, " ")
98+
99+
messages.each[write_message]()
100+
self._log_message[LogLevel.DEBUG](msg)
101+
102+
fn fatal[*Ts: Writable](self, *messages: *Ts):
103+
var msg = String.write("\033[35mFATAL\033[0m - ")
104+
105+
@parameter
106+
fn write_message[T: Writable](message: T):
107+
msg.write(message, " ")
108+
109+
messages.each[write_message]()
110+
self._log_message[LogLevel.FATAL](msg)
111+
112+
113+
alias logger = Logger[LOG_LEVEL]()
File renamed without changes.

lightbug_http/client.mojo

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,10 @@ from lightbug_http.net import default_buffer_size
55
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
66
from lightbug_http.header import Headers, HeaderKey
77
from lightbug_http.net import create_connection, TCPConnection
8-
from lightbug_http.io.bytes import Bytes
9-
from lightbug_http.utils import ByteReader, logger
10-
from lightbug_http.pool_manager import PoolManager, Scheme, PoolKey
11-
12-
13-
fn parse_host_and_port(source: String, is_tls: Bool) raises -> (String, UInt16):
14-
"""Parses the host and port from a given string.
15-
16-
Args:
17-
source: The host uri to parse.
18-
is_tls: A boolean indicating whether the connection is secure.
19-
20-
Returns:
21-
A tuple containing the host and port.
22-
"""
23-
if source.count(":") != 1:
24-
var port: UInt16 = 443 if is_tls else 80
25-
return source, port
26-
27-
var result = source.split(":")
28-
return result[0], UInt16(atol(result[1]))
8+
from lightbug_http.io.bytes import Bytes, ByteReader
9+
from lightbug_http._logger import logger
10+
from lightbug_http.pool_manager import PoolManager, PoolKey
11+
from lightbug_http.uri import URI, Scheme
2912

3013

3114
struct Client:
@@ -71,24 +54,26 @@ struct Client:
7154
Error: If there is a failure in sending or receiving the message.
7255
"""
7356
if request.uri.host == "":
74-
raise Error("Client.do: Request failed because the host field is empty.")
57+
raise Error("Client.do: Host must not be empty.")
58+
if not request.uri.port:
59+
raise Error("Client.do: You must specify the port to connect on.")
7560

7661
var is_tls = False
7762
var scheme = Scheme.HTTP
7863
if request.uri.is_https():
7964
is_tls = True
8065
scheme = Scheme.HTTPS
8166

82-
host, port = parse_host_and_port(request.uri.host, is_tls)
83-
var pool_key = PoolKey(host, port, scheme)
67+
var uri = URI.parse(request.uri.host)
68+
var pool_key = PoolKey(uri.host, uri.port.value(), scheme)
8469
var cached_connection = False
8570
var conn: TCPConnection
8671
try:
8772
conn = self._connections.take(pool_key)
8873
cached_connection = True
8974
except e:
9075
if str(e) == "PoolManager.take: Key not found.":
91-
conn = create_connection(host, port)
76+
conn = create_connection(uri.host, uri.port.value())
9277
else:
9378
logger.error(e)
9479
raise Error("Client.do: Failed to create a connection to host.")

lightbug_http/cookie/request_cookie_jar.mojo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ from small_time import SmallTime, TimeZone
33
from small_time.small_time import strptime
44
from lightbug_http.strings import to_string, lineBreak
55
from lightbug_http.header import HeaderKey, write_header
6-
from lightbug_http.utils import ByteReader, ByteWriter, is_newline, is_space
6+
from lightbug_http.io.bytes import ByteReader, ByteWriter, is_newline, is_space
77

88

99
@value

lightbug_http/cookie/response_cookie_jar.mojo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections import Optional, List, Dict, KeyElement
22
from lightbug_http.strings import to_string
33
from lightbug_http.header import HeaderKey, write_header
4-
from lightbug_http.utils import ByteWriter
4+
from lightbug_http.io.bytes import ByteWriter
55

66

77
@value

lightbug_http/header.mojo

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from collections import Dict, Optional
22
from memory import Span
3-
from lightbug_http.io.bytes import Bytes, Byte
3+
from lightbug_http.io.bytes import Bytes, ByteReader, ByteWriter, is_newline, is_space
44
from lightbug_http.strings import BytesConstant
5-
from lightbug_http.utils import ByteReader, ByteWriter, is_newline, is_space, logger
5+
from lightbug_http._logger import logger
66
from lightbug_http.strings import rChar, nChar, lineBreak, to_string
77

88

@@ -103,13 +103,13 @@ struct Headers(Writable, Stringable):
103103
r.increment()
104104
# TODO (bgreni): Handle possible trailing whitespace
105105
var value = r.read_line()
106-
var k = to_string(key).lower()
106+
var k = str(key).lower()
107107
if k == HeaderKey.SET_COOKIE:
108-
cookies.append(to_string(value))
108+
cookies.append(str(value))
109109
continue
110110

111-
self._inner[k] = to_string(value)
112-
return (to_string(first), to_string(second), to_string(third), cookies)
111+
self._inner[k] = str(value)
112+
return (str(first), str(second), str(third), cookies)
113113

114114
fn write_to[T: Writer, //](self, mut writer: T):
115115
for header in self._inner.items():

lightbug_http/http/request.mojo

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from memory import Span
2-
from lightbug_http.io.bytes import Bytes, bytes, Byte
2+
from lightbug_http.io.bytes import Bytes, bytes, ByteReader, ByteWriter
33
from lightbug_http.header import Headers, HeaderKey, Header, write_header
44
from lightbug_http.cookie import RequestCookieJar
55
from lightbug_http.uri import URI
6-
from lightbug_http.utils import ByteReader, ByteWriter, logger
6+
from lightbug_http._logger import logger
77
from lightbug_http.io.sync import Duration
88
from lightbug_http.strings import (
99
strHttp11,
@@ -86,7 +86,11 @@ struct HTTPRequest(Writable, Stringable):
8686
if HeaderKey.CONNECTION not in self.headers:
8787
self.headers[HeaderKey.CONNECTION] = "keep-alive"
8888
if HeaderKey.HOST not in self.headers:
89-
self.headers[HeaderKey.HOST] = uri.host
89+
if uri.port:
90+
var host = String.write(uri.host, ":", str(uri.port.value()))
91+
self.headers[HeaderKey.HOST] = host
92+
else:
93+
self.headers[HeaderKey.HOST] = uri.host
9094

9195
fn get_body(self) -> StringSlice[__origin_of(self.body_raw)]:
9296
return StringSlice(unsafe_from_utf8=Span(self.body_raw))
@@ -108,7 +112,7 @@ struct HTTPRequest(Writable, Stringable):
108112
if content_length > max_body_size:
109113
raise Error("Request body too large")
110114

111-
self.body_raw = r.read_bytes(content_length)
115+
self.body_raw = r.read_bytes(content_length).to_bytes()
112116
self.set_content_length(content_length)
113117

114118
fn write_to[T: Writer, //](self, mut writer: T):
@@ -152,7 +156,7 @@ struct HTTPRequest(Writable, Stringable):
152156
lineBreak,
153157
)
154158
writer.consuming_write(self^.body_raw)
155-
return writer.consume()
159+
return writer^.consume()
156160

157161
fn __str__(self) -> String:
158162
return String.write(self)

0 commit comments

Comments
 (0)