Skip to content

Commit 5282ed5

Browse files
committed
Merge branch 'main' into nightly
2 parents be415c9 + bd14814 commit 5282ed5

27 files changed

+258
-304
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ jobs:
1414
run: |
1515
curl -ssL https://magic.modular.com | bash
1616
source $HOME/.bash_profile
17-
magic run mojo run_tests.mojo
17+
magic run test
1818

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ install_id
1212
.magic
1313

1414
# Rattler
15-
output
15+
output
16+
17+
# misc
18+
.vscode

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ Once you have a Mojo project set up locally,
6767
```
6868
or import individual structs and functions, e.g.
6969
```mojo
70-
from lightbug_http.http import HTTPService, HTTPRequest, HTTPResponse, OK, NotFound
70+
from lightbug_http.service import HTTPService
71+
from lightbug_http.http import HTTPRequest, HTTPResponse, OK, NotFound
7172
```
7273
there are some default handlers you can play with:
7374
```mojo

bench.mojo

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@ from lightbug_http.header import Headers, Header
44
from lightbug_http.utils import ByteReader, ByteWriter
55
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
66
from lightbug_http.uri import URI
7-
from tests.utils import (
8-
TestStruct,
9-
FakeResponder,
10-
new_fake_listener,
11-
FakeServer,
12-
)
137

148
alias headers = bytes(
159
"""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"""
@@ -148,43 +142,3 @@ fn lightbug_benchmark_header_parse(inout b: Bencher):
148142

149143
b.iter[header_parse]()
150144

151-
152-
fn lightbug_benchmark_server():
153-
var server_report = benchmark.run[run_fake_server](max_iters=1)
154-
print("Server: ")
155-
server_report.print(benchmark.Unit.ms)
156-
157-
158-
fn lightbug_benchmark_misc() -> None:
159-
var direct_set_report = benchmark.run[init_test_and_set_a_direct](
160-
max_iters=1
161-
)
162-
163-
var recreating_set_report = benchmark.run[init_test_and_set_a_copy](
164-
max_iters=1
165-
)
166-
167-
print("Direct set: ")
168-
direct_set_report.print(benchmark.Unit.ms)
169-
print("Recreating set: ")
170-
recreating_set_report.print(benchmark.Unit.ms)
171-
172-
173-
var GetRequest = HTTPRequest(URI.parse("http://127.0.0.1/path")[URI])
174-
175-
176-
fn run_fake_server():
177-
var handler = FakeResponder()
178-
var listener = new_fake_listener(2, encode(GetRequest))
179-
var server = FakeServer(listener, handler)
180-
server.serve()
181-
182-
183-
fn init_test_and_set_a_copy() -> None:
184-
var test = TestStruct("a", "b")
185-
_ = test.set_a_copy("c")
186-
187-
188-
fn init_test_and_set_a_direct() -> None:
189-
var test = TestStruct("a", "b")
190-
_ = test.set_a_direct("c")

lightbug_http/header.mojo

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ struct HeaderKey:
1212
alias CONTENT_LENGTH = "content-length"
1313
alias CONTENT_ENCODING = "content-encoding"
1414
alias DATE = "date"
15+
alias LOCATION = "location"
16+
alias HOST = "host"
1517

1618

1719
@value
@@ -70,16 +72,12 @@ struct Headers(Formattable, Stringable):
7072
self._inner[key.lower()] = value
7173

7274
fn content_length(self) -> Int:
73-
if HeaderKey.CONTENT_LENGTH not in self:
74-
return 0
7575
try:
7676
return int(self[HeaderKey.CONTENT_LENGTH])
7777
except:
7878
return 0
7979

80-
fn parse_raw(
81-
inout self, inout r: ByteReader
82-
) raises -> (String, String, String):
80+
fn parse_raw(inout self, inout r: ByteReader) raises -> (String, String, String):
8381
var first_byte = r.peek()
8482
if not first_byte:
8583
raise Error("Failed to read first byte from response header")

lightbug_http/http.mojo

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ fn encode(owned res: HTTPResponse) -> Bytes:
3535
return res._encoded()
3636

3737

38+
struct StatusCode:
39+
alias OK = 200
40+
alias MOVED_PERMANENTLY = 301
41+
alias FOUND = 302
42+
alias TEMPORARY_REDIRECT = 307
43+
alias PERMANENT_REDIRECT = 308
44+
alias NOT_FOUND = 404
45+
46+
3847
@value
3948
struct HTTPRequest(Formattable, Stringable):
4049
var headers: Headers
@@ -48,9 +57,7 @@ struct HTTPRequest(Formattable, Stringable):
4857
var timeout: Duration
4958

5059
@staticmethod
51-
fn from_bytes(
52-
addr: String, max_body_size: Int, owned b: Bytes
53-
) raises -> HTTPRequest:
60+
fn from_bytes(addr: String, max_body_size: Int, owned b: Bytes) raises -> HTTPRequest:
5461
var reader = ByteReader(b^)
5562
var headers = Headers()
5663
var method: String
@@ -65,16 +72,10 @@ struct HTTPRequest(Formattable, Stringable):
6572

6673
var content_length = headers.content_length()
6774

68-
if (
69-
content_length > 0
70-
and max_body_size > 0
71-
and content_length > max_body_size
72-
):
75+
if content_length > 0 and max_body_size > 0 and content_length > max_body_size:
7376
raise Error("Request body too large")
7477

75-
var request = HTTPRequest(
76-
uri, headers=headers, method=method, protocol=protocol
77-
)
78+
var request = HTTPRequest(uri, headers=headers, method=method, protocol=protocol)
7879

7980
try:
8081
request.read_body(reader, content_length, max_body_size)
@@ -103,6 +104,8 @@ struct HTTPRequest(Formattable, Stringable):
103104
self.set_content_length(len(body))
104105
if HeaderKey.CONNECTION not in self.headers:
105106
self.set_connection_close()
107+
if HeaderKey.HOST not in self.headers:
108+
self.headers[HeaderKey.HOST] = uri.host
106109

107110
fn set_connection_close(inout self):
108111
self.headers[HeaderKey.CONNECTION] = "close"
@@ -114,20 +117,22 @@ struct HTTPRequest(Formattable, Stringable):
114117
return self.headers[HeaderKey.CONNECTION] == "close"
115118

116119
@always_inline
117-
fn read_body(
118-
inout self, inout r: ByteReader, content_length: Int, max_body_size: Int
119-
) raises -> None:
120+
fn read_body(inout self, inout r: ByteReader, content_length: Int, max_body_size: Int) raises -> None:
120121
if content_length > max_body_size:
121122
raise Error("Request body too large")
122123

123-
r.consume(self.body_raw)
124+
r.consume(self.body_raw, content_length)
124125
self.set_content_length(content_length)
125126

126127
fn format_to(self, inout writer: Formatter):
128+
writer.write(self.method, whitespace)
129+
path = self.uri.path if len(self.uri.path) > 1 else strSlash
130+
if len(self.uri.query_string) > 0:
131+
path += "?" + self.uri.query_string
132+
133+
writer.write(path)
134+
127135
writer.write(
128-
self.method,
129-
whitespace,
130-
self.uri.path if len(self.uri.path) > 1 else strSlash,
131136
whitespace,
132137
self.protocol,
133138
lineBreak,
@@ -147,6 +152,8 @@ struct HTTPRequest(Formattable, Stringable):
147152
writer.write(self.method)
148153
writer.write(whitespace)
149154
var path = self.uri.path if len(self.uri.path) > 1 else strSlash
155+
if len(self.uri.query_string) > 0:
156+
path += "?" + self.uri.query_string
150157
writer.write(path)
151158
writer.write(whitespace)
152159
writer.write(self.protocol)
@@ -215,8 +222,16 @@ struct HTTPResponse(Formattable, Stringable):
215222
self.status_text = status_text
216223
self.protocol = protocol
217224
self.body_raw = body_bytes
218-
self.set_connection_keep_alive()
219-
self.set_content_length(len(body_bytes))
225+
if HeaderKey.CONNECTION not in self.headers:
226+
self.set_connection_keep_alive()
227+
if HeaderKey.CONTENT_LENGTH not in self.headers:
228+
self.set_content_length(len(body_bytes))
229+
if HeaderKey.DATE not in self.headers:
230+
try:
231+
var current_time = now(utc=True).__str__()
232+
self.headers[HeaderKey.DATE] = current_time
233+
except:
234+
pass
220235

221236
fn get_body_bytes(self) -> Bytes:
222237
return self.body_raw
@@ -236,9 +251,25 @@ struct HTTPResponse(Formattable, Stringable):
236251
fn set_content_length(inout self, l: Int):
237252
self.headers[HeaderKey.CONTENT_LENGTH] = str(l)
238253

254+
@always_inline
255+
fn content_length(inout self) -> Int:
256+
try:
257+
return int(self.headers[HeaderKey.CONTENT_LENGTH])
258+
except:
259+
return 0
260+
261+
fn is_redirect(self) -> Bool:
262+
return (
263+
self.status_code == StatusCode.MOVED_PERMANENTLY
264+
or self.status_code == StatusCode.FOUND
265+
or self.status_code == StatusCode.TEMPORARY_REDIRECT
266+
or self.status_code == StatusCode.PERMANENT_REDIRECT
267+
)
268+
239269
@always_inline
240270
fn read_body(inout self, inout r: ByteReader) raises -> None:
241-
r.consume(self.body_raw)
271+
r.consume(self.body_raw, self.content_length())
272+
self.set_content_length(len(self.body_raw))
242273

243274
fn format_to(self, inout writer: Formatter):
244275
writer.write(
@@ -252,13 +283,6 @@ struct HTTPResponse(Formattable, Stringable):
252283
lineBreak,
253284
)
254285

255-
if HeaderKey.DATE not in self.headers:
256-
try:
257-
var current_time = now(utc=True).__str__()
258-
write_header(writer, HeaderKey.DATE, current_time)
259-
except:
260-
pass
261-
262286
self.headers.format_to(writer)
263287

264288
writer.write(lineBreak)
@@ -326,9 +350,7 @@ fn OK(body: Bytes, content_type: String) -> HTTPResponse:
326350
)
327351

328352

329-
fn OK(
330-
body: Bytes, content_type: String, content_encoding: String
331-
) -> HTTPResponse:
353+
fn OK(body: Bytes, content_type: String, content_encoding: String) -> HTTPResponse:
332354
return HTTPResponse(
333355
headers=Headers(
334356
Header(HeaderKey.CONTENT_TYPE, content_type),

0 commit comments

Comments
 (0)