Skip to content

Commit 5a816ac

Browse files
committed
sync
2 parents 6102aa1 + 369005e commit 5a816ac

File tree

4 files changed

+107
-15
lines changed

4 files changed

+107
-15
lines changed

benchmark/bench.mojo

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,19 @@ fn lightbug_benchmark_request_parse(mut b: Bencher):
8585
fn lightbug_benchmark_request_encode(mut b: Bencher):
8686
@always_inline
8787
@parameter
88-
fn request_encode():
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)
88+
fn request_encode() raises:
89+
var uri = URI.parse("http://127.0.0.1:8080/some-path")
90+
var req = HTTPRequest(
91+
uri=uri,
92+
headers=headers_struct,
93+
body=body_bytes,
94+
)
95+
_ = encode(req^)
9896

99-
b.iter[request_encode]()
97+
try:
98+
b.iter[request_encode]()
99+
except e:
100+
print("failed to encode request, error: ", e)
100101

101102

102103
@parameter

lightbug_http/client.mojo

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ struct Client:
139139
raise Error("Client._handle_redirect: `Location` header was not received in the response.")
140140

141141
if new_location and new_location.startswith("http"):
142-
new_uri = URI.parse(new_location)
142+
try:
143+
new_uri = URI.parse(new_location)
144+
except e:
145+
raise Error("Client._handle_redirect: Failed to parse the new URI: " + str(e))
143146
original_request.headers[HeaderKey.HOST] = new_uri.host
144147
else:
145148
new_uri = original_request.uri

lightbug_http/uri.mojo

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from utils import Variant, StringSlice
22
from memory import Span
3-
from collections import Optional
3+
from collections import Optional, Dict
44
from lightbug_http.io.bytes import Bytes, bytes, ByteReader, Constant
55
from lightbug_http.strings import (
66
strSlash,
@@ -12,6 +12,20 @@ from lightbug_http.strings import (
1212
https,
1313
)
1414

15+
alias QueryMap = Dict[String, String]
16+
17+
18+
struct QueryDelimiters:
19+
alias STRING_START = "?"
20+
alias ITEM = "&"
21+
alias ITEM_ASSIGN = "="
22+
23+
24+
struct URIDelimiters:
25+
alias SCHEMA = "://"
26+
alias PATH = strSlash
27+
alias ROOT_PATH = strSlash
28+
1529

1630
@value
1731
struct Scheme(Hashable, EqualityComparable, Representable, Stringable, Writable):
@@ -62,6 +76,7 @@ struct URI(Writable, Stringable, Representable):
6276
var scheme: String
6377
var path: String
6478
var query_string: String
79+
var queries: QueryMap
6580
var _hash: String
6681
var host: String
6782
var port: Optional[UInt16]
@@ -131,11 +146,26 @@ struct URI(Writable, Stringable, Representable):
131146
# TODO: Handle fragments for anchors
132147
query = str(reader.read_bytes()[1:])
133148

149+
var queries = QueryMap()
150+
if query:
151+
var query_items = query.split(QueryDelimiters.ITEM)
152+
153+
for item in query_items:
154+
var key_val = item[].split(QueryDelimiters.ITEM_ASSIGN, 1)
155+
156+
if key_val[0]:
157+
queries[key_val[0]] = ""
158+
if len(key_val) == 2:
159+
# TODO: Query values are going to be URI encoded strings and should be decoded as part of the
160+
# query processing
161+
queries[key_val[0]] = key_val[1]
162+
134163
return URI(
135164
_original_path=path,
136165
scheme=scheme,
137166
path=path,
138167
query_string=query,
168+
queries=queries,
139169
_hash="",
140170
host=host,
141171
port=port,
@@ -146,9 +176,9 @@ struct URI(Writable, Stringable, Representable):
146176
)
147177

148178
fn __str__(self) -> String:
149-
var result = String.write(self.scheme, "://", self.host, self.path)
179+
var result = String.write(self.scheme, URIDelimiters.SCHEMA, self.host, self.path)
150180
if len(self.query_string) > 0:
151-
result.write("?", self.query_string)
181+
result.write(QueryDelimiters.STRING_START, self.query_string)
152182
return result^
153183

154184
fn __repr__(self) -> String:

tests/lightbug_http/test_uri.mojo

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,64 @@ def test_uri_parse_http_with_query_string():
9090
testing.assert_equal(uri._original_path, "/job")
9191
testing.assert_equal(uri.request_uri, "/job?title=engineer")
9292
testing.assert_equal(uri.query_string, "title=engineer")
93+
testing.assert_equal(uri.queries["title"], "engineer")
94+
95+
96+
def test_uri_parse_multiple_query_parameters():
97+
var uri = URI.parse("http://example.com/search?q=python&page=1&limit=20")
98+
testing.assert_equal(uri.scheme, "http")
99+
testing.assert_equal(uri.host, "example.com")
100+
testing.assert_equal(uri.path, "/search")
101+
testing.assert_equal(uri.query_string, "q=python&page=1&limit=20")
102+
testing.assert_equal(uri.queries["q"], "python")
103+
testing.assert_equal(uri.queries["page"], "1")
104+
testing.assert_equal(uri.queries["limit"], "20")
105+
testing.assert_equal(uri.request_uri, "/search?q=python&page=1&limit=20")
106+
107+
108+
def test_uri_parse_query_with_special_characters():
109+
var uri = URI.parse("https://example.com/path?name=John+Doe&email=john%40example.com")
110+
testing.assert_equal(uri.scheme, "https")
111+
testing.assert_equal(uri.host, "example.com")
112+
testing.assert_equal(uri.path, "/path")
113+
testing.assert_equal(uri.query_string, "name=John+Doe&email=john%40example.com")
114+
# testing.assert_equal(uri.queries["name"], "John Doe") - fails, contains John+Doe
115+
# testing.assert_equal(uri.queries["email"], "john@example.com") - fails, contains john%40example.com
116+
117+
118+
def test_uri_parse_empty_query_values():
119+
var uri = URI.parse("http://example.com/api?key=&token=&empty")
120+
testing.assert_equal(uri.query_string, "key=&token=&empty")
121+
testing.assert_equal(uri.queries["key"], "")
122+
testing.assert_equal(uri.queries["token"], "")
123+
testing.assert_equal(uri.queries["empty"], "")
124+
125+
126+
def test_uri_parse_complex_query():
127+
var uri = URI.parse("https://example.com/search?q=test&filter[category]=books&filter[price]=10-20&sort=desc&page=1")
128+
testing.assert_equal(uri.scheme, "https")
129+
testing.assert_equal(uri.host, "example.com")
130+
testing.assert_equal(uri.path, "/search")
131+
testing.assert_equal(uri.query_string, "q=test&filter[category]=books&filter[price]=10-20&sort=desc&page=1")
132+
testing.assert_equal(uri.queries["q"], "test")
133+
testing.assert_equal(uri.queries["filter[category]"], "books")
134+
testing.assert_equal(uri.queries["filter[price]"], "10-20")
135+
testing.assert_equal(uri.queries["sort"], "desc")
136+
testing.assert_equal(uri.queries["page"], "1")
137+
138+
139+
def test_uri_parse_query_with_unicode():
140+
var uri = URI.parse("http://example.com/search?q=%E2%82%AC&lang=%F0%9F%87%A9%F0%9F%87%AA")
141+
testing.assert_equal(uri.query_string, "q=%E2%82%AC&lang=%F0%9F%87%A9%F0%9F%87%AA")
142+
# testing.assert_equal(uri.queries["q"], "€") - fails, contains %E2%82%AC
143+
# testing.assert_equal(uri.queries["lang"], "🇩🇪") - fails, contains %F0%9F%87%A9%F0%9F%87%AA
144+
145+
146+
# def test_uri_parse_query_with_fragments():
147+
# var uri = URI.parse("http://example.com/page?id=123#section1")
148+
# testing.assert_equal(uri.query_string, "id=123")
149+
# testing.assert_equal(uri.queries["id"], "123")
150+
# testing.assert_equal(...) - how do we treat fragments?
93151

94152

95153
def test_uri_parse_no_scheme():

0 commit comments

Comments
 (0)