Skip to content

Commit

Permalink
sync
Browse files Browse the repository at this point in the history
  • Loading branch information
thatstoasty committed Jan 28, 2025
2 parents 6102aa1 + 369005e commit 5a816ac
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 15 deletions.
23 changes: 12 additions & 11 deletions benchmark/bench.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,19 @@ fn lightbug_benchmark_request_parse(mut b: Bencher):
fn lightbug_benchmark_request_encode(mut b: Bencher):
@always_inline
@parameter
fn request_encode():
try:
var req = HTTPRequest(
URI.parse("http://127.0.0.1:8080/some-path"),
headers=headers_struct,
body=body_bytes,
)
_ = encode(req^)
except e:
print("request_encode failed", e)
fn request_encode() raises:
var uri = URI.parse("http://127.0.0.1:8080/some-path")
var req = HTTPRequest(
uri=uri,
headers=headers_struct,
body=body_bytes,
)
_ = encode(req^)

b.iter[request_encode]()
try:
b.iter[request_encode]()
except e:
print("failed to encode request, error: ", e)


@parameter
Expand Down
5 changes: 4 additions & 1 deletion lightbug_http/client.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ struct Client:
raise Error("Client._handle_redirect: `Location` header was not received in the response.")

if new_location and new_location.startswith("http"):
new_uri = URI.parse(new_location)
try:
new_uri = URI.parse(new_location)
except e:
raise Error("Client._handle_redirect: Failed to parse the new URI: " + str(e))
original_request.headers[HeaderKey.HOST] = new_uri.host
else:
new_uri = original_request.uri
Expand Down
36 changes: 33 additions & 3 deletions lightbug_http/uri.mojo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from utils import Variant, StringSlice
from memory import Span
from collections import Optional
from collections import Optional, Dict
from lightbug_http.io.bytes import Bytes, bytes, ByteReader, Constant
from lightbug_http.strings import (
strSlash,
Expand All @@ -12,6 +12,20 @@ from lightbug_http.strings import (
https,
)

alias QueryMap = Dict[String, String]


struct QueryDelimiters:
alias STRING_START = "?"
alias ITEM = "&"
alias ITEM_ASSIGN = "="


struct URIDelimiters:
alias SCHEMA = "://"
alias PATH = strSlash
alias ROOT_PATH = strSlash


@value
struct Scheme(Hashable, EqualityComparable, Representable, Stringable, Writable):
Expand Down Expand Up @@ -62,6 +76,7 @@ struct URI(Writable, Stringable, Representable):
var scheme: String
var path: String
var query_string: String
var queries: QueryMap
var _hash: String
var host: String
var port: Optional[UInt16]
Expand Down Expand Up @@ -131,11 +146,26 @@ struct URI(Writable, Stringable, Representable):
# TODO: Handle fragments for anchors
query = str(reader.read_bytes()[1:])

var queries = QueryMap()
if query:
var query_items = query.split(QueryDelimiters.ITEM)

for item in query_items:
var key_val = item[].split(QueryDelimiters.ITEM_ASSIGN, 1)

if key_val[0]:
queries[key_val[0]] = ""
if len(key_val) == 2:
# TODO: Query values are going to be URI encoded strings and should be decoded as part of the
# query processing
queries[key_val[0]] = key_val[1]

return URI(
_original_path=path,
scheme=scheme,
path=path,
query_string=query,
queries=queries,
_hash="",
host=host,
port=port,
Expand All @@ -146,9 +176,9 @@ struct URI(Writable, Stringable, Representable):
)

fn __str__(self) -> String:
var result = String.write(self.scheme, "://", self.host, self.path)
var result = String.write(self.scheme, URIDelimiters.SCHEMA, self.host, self.path)
if len(self.query_string) > 0:
result.write("?", self.query_string)
result.write(QueryDelimiters.STRING_START, self.query_string)
return result^

fn __repr__(self) -> String:
Expand Down
58 changes: 58 additions & 0 deletions tests/lightbug_http/test_uri.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,64 @@ def test_uri_parse_http_with_query_string():
testing.assert_equal(uri._original_path, "/job")
testing.assert_equal(uri.request_uri, "/job?title=engineer")
testing.assert_equal(uri.query_string, "title=engineer")
testing.assert_equal(uri.queries["title"], "engineer")


def test_uri_parse_multiple_query_parameters():
var uri = URI.parse("http://example.com/search?q=python&page=1&limit=20")
testing.assert_equal(uri.scheme, "http")
testing.assert_equal(uri.host, "example.com")
testing.assert_equal(uri.path, "/search")
testing.assert_equal(uri.query_string, "q=python&page=1&limit=20")
testing.assert_equal(uri.queries["q"], "python")
testing.assert_equal(uri.queries["page"], "1")
testing.assert_equal(uri.queries["limit"], "20")
testing.assert_equal(uri.request_uri, "/search?q=python&page=1&limit=20")


def test_uri_parse_query_with_special_characters():
var uri = URI.parse("https://example.com/path?name=John+Doe&email=john%40example.com")
testing.assert_equal(uri.scheme, "https")
testing.assert_equal(uri.host, "example.com")
testing.assert_equal(uri.path, "/path")
testing.assert_equal(uri.query_string, "name=John+Doe&email=john%40example.com")
# testing.assert_equal(uri.queries["name"], "John Doe") - fails, contains John+Doe
# testing.assert_equal(uri.queries["email"], "[email protected]") - fails, contains john%40example.com


def test_uri_parse_empty_query_values():
var uri = URI.parse("http://example.com/api?key=&token=&empty")
testing.assert_equal(uri.query_string, "key=&token=&empty")
testing.assert_equal(uri.queries["key"], "")
testing.assert_equal(uri.queries["token"], "")
testing.assert_equal(uri.queries["empty"], "")


def test_uri_parse_complex_query():
var uri = URI.parse("https://example.com/search?q=test&filter[category]=books&filter[price]=10-20&sort=desc&page=1")
testing.assert_equal(uri.scheme, "https")
testing.assert_equal(uri.host, "example.com")
testing.assert_equal(uri.path, "/search")
testing.assert_equal(uri.query_string, "q=test&filter[category]=books&filter[price]=10-20&sort=desc&page=1")
testing.assert_equal(uri.queries["q"], "test")
testing.assert_equal(uri.queries["filter[category]"], "books")
testing.assert_equal(uri.queries["filter[price]"], "10-20")
testing.assert_equal(uri.queries["sort"], "desc")
testing.assert_equal(uri.queries["page"], "1")


def test_uri_parse_query_with_unicode():
var uri = URI.parse("http://example.com/search?q=%E2%82%AC&lang=%F0%9F%87%A9%F0%9F%87%AA")
testing.assert_equal(uri.query_string, "q=%E2%82%AC&lang=%F0%9F%87%A9%F0%9F%87%AA")
# testing.assert_equal(uri.queries["q"], "€") - fails, contains %E2%82%AC
# testing.assert_equal(uri.queries["lang"], "🇩🇪") - fails, contains %F0%9F%87%A9%F0%9F%87%AA


# def test_uri_parse_query_with_fragments():
# var uri = URI.parse("http://example.com/page?id=123#section1")
# testing.assert_equal(uri.query_string, "id=123")
# testing.assert_equal(uri.queries["id"], "123")
# testing.assert_equal(...) - how do we treat fragments?


def test_uri_parse_no_scheme():
Expand Down

0 comments on commit 5a816ac

Please sign in to comment.