Skip to content

Commit

Permalink
Merge branch 'main' into nightly
Browse files Browse the repository at this point in the history
  • Loading branch information
saviorand committed Sep 27, 2024
2 parents be415c9 + bd14814 commit 5282ed5
Show file tree
Hide file tree
Showing 27 changed files with 258 additions and 304 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ jobs:
run: |
curl -ssL https://magic.modular.com | bash
source $HOME/.bash_profile
magic run mojo run_tests.mojo
magic run test
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ install_id
.magic

# Rattler
output
output

# misc
.vscode
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ Once you have a Mojo project set up locally,
```
or import individual structs and functions, e.g.
```mojo
from lightbug_http.http import HTTPService, HTTPRequest, HTTPResponse, OK, NotFound
from lightbug_http.service import HTTPService
from lightbug_http.http import HTTPRequest, HTTPResponse, OK, NotFound
```
there are some default handlers you can play with:
```mojo
Expand Down
46 changes: 0 additions & 46 deletions bench.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ from lightbug_http.header import Headers, Header
from lightbug_http.utils import ByteReader, ByteWriter
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
from lightbug_http.uri import URI
from tests.utils import (
TestStruct,
FakeResponder,
new_fake_listener,
FakeServer,
)

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

b.iter[header_parse]()


fn lightbug_benchmark_server():
var server_report = benchmark.run[run_fake_server](max_iters=1)
print("Server: ")
server_report.print(benchmark.Unit.ms)


fn lightbug_benchmark_misc() -> None:
var direct_set_report = benchmark.run[init_test_and_set_a_direct](
max_iters=1
)

var recreating_set_report = benchmark.run[init_test_and_set_a_copy](
max_iters=1
)

print("Direct set: ")
direct_set_report.print(benchmark.Unit.ms)
print("Recreating set: ")
recreating_set_report.print(benchmark.Unit.ms)


var GetRequest = HTTPRequest(URI.parse("http://127.0.0.1/path")[URI])


fn run_fake_server():
var handler = FakeResponder()
var listener = new_fake_listener(2, encode(GetRequest))
var server = FakeServer(listener, handler)
server.serve()


fn init_test_and_set_a_copy() -> None:
var test = TestStruct("a", "b")
_ = test.set_a_copy("c")


fn init_test_and_set_a_direct() -> None:
var test = TestStruct("a", "b")
_ = test.set_a_direct("c")
8 changes: 3 additions & 5 deletions lightbug_http/header.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ struct HeaderKey:
alias CONTENT_LENGTH = "content-length"
alias CONTENT_ENCODING = "content-encoding"
alias DATE = "date"
alias LOCATION = "location"
alias HOST = "host"


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

fn content_length(self) -> Int:
if HeaderKey.CONTENT_LENGTH not in self:
return 0
try:
return int(self[HeaderKey.CONTENT_LENGTH])
except:
return 0

fn parse_raw(
inout self, inout r: ByteReader
) raises -> (String, String, String):
fn parse_raw(inout self, inout r: ByteReader) raises -> (String, String, String):
var first_byte = r.peek()
if not first_byte:
raise Error("Failed to read first byte from response header")
Expand Down
84 changes: 53 additions & 31 deletions lightbug_http/http.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ fn encode(owned res: HTTPResponse) -> Bytes:
return res._encoded()


struct StatusCode:
alias OK = 200
alias MOVED_PERMANENTLY = 301
alias FOUND = 302
alias TEMPORARY_REDIRECT = 307
alias PERMANENT_REDIRECT = 308
alias NOT_FOUND = 404


@value
struct HTTPRequest(Formattable, Stringable):
var headers: Headers
Expand All @@ -48,9 +57,7 @@ struct HTTPRequest(Formattable, Stringable):
var timeout: Duration

@staticmethod
fn from_bytes(
addr: String, max_body_size: Int, owned b: Bytes
) raises -> HTTPRequest:
fn from_bytes(addr: String, max_body_size: Int, owned b: Bytes) raises -> HTTPRequest:
var reader = ByteReader(b^)
var headers = Headers()
var method: String
Expand All @@ -65,16 +72,10 @@ struct HTTPRequest(Formattable, Stringable):

var content_length = headers.content_length()

if (
content_length > 0
and max_body_size > 0
and content_length > max_body_size
):
if content_length > 0 and max_body_size > 0 and content_length > max_body_size:
raise Error("Request body too large")

var request = HTTPRequest(
uri, headers=headers, method=method, protocol=protocol
)
var request = HTTPRequest(uri, headers=headers, method=method, protocol=protocol)

try:
request.read_body(reader, content_length, max_body_size)
Expand Down Expand Up @@ -103,6 +104,8 @@ struct HTTPRequest(Formattable, Stringable):
self.set_content_length(len(body))
if HeaderKey.CONNECTION not in self.headers:
self.set_connection_close()
if HeaderKey.HOST not in self.headers:
self.headers[HeaderKey.HOST] = uri.host

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

@always_inline
fn read_body(
inout self, inout r: ByteReader, content_length: Int, max_body_size: Int
) raises -> None:
fn read_body(inout self, inout r: ByteReader, content_length: Int, max_body_size: Int) raises -> None:
if content_length > max_body_size:
raise Error("Request body too large")

r.consume(self.body_raw)
r.consume(self.body_raw, content_length)
self.set_content_length(content_length)

fn format_to(self, inout writer: Formatter):
writer.write(self.method, whitespace)
path = self.uri.path if len(self.uri.path) > 1 else strSlash
if len(self.uri.query_string) > 0:
path += "?" + self.uri.query_string

writer.write(path)

writer.write(
self.method,
whitespace,
self.uri.path if len(self.uri.path) > 1 else strSlash,
whitespace,
self.protocol,
lineBreak,
Expand All @@ -147,6 +152,8 @@ struct HTTPRequest(Formattable, Stringable):
writer.write(self.method)
writer.write(whitespace)
var path = self.uri.path if len(self.uri.path) > 1 else strSlash
if len(self.uri.query_string) > 0:
path += "?" + self.uri.query_string
writer.write(path)
writer.write(whitespace)
writer.write(self.protocol)
Expand Down Expand Up @@ -215,8 +222,16 @@ struct HTTPResponse(Formattable, Stringable):
self.status_text = status_text
self.protocol = protocol
self.body_raw = body_bytes
self.set_connection_keep_alive()
self.set_content_length(len(body_bytes))
if HeaderKey.CONNECTION not in self.headers:
self.set_connection_keep_alive()
if HeaderKey.CONTENT_LENGTH not in self.headers:
self.set_content_length(len(body_bytes))
if HeaderKey.DATE not in self.headers:
try:
var current_time = now(utc=True).__str__()
self.headers[HeaderKey.DATE] = current_time
except:
pass

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

@always_inline
fn content_length(inout self) -> Int:
try:
return int(self.headers[HeaderKey.CONTENT_LENGTH])
except:
return 0

fn is_redirect(self) -> Bool:
return (
self.status_code == StatusCode.MOVED_PERMANENTLY
or self.status_code == StatusCode.FOUND
or self.status_code == StatusCode.TEMPORARY_REDIRECT
or self.status_code == StatusCode.PERMANENT_REDIRECT
)

@always_inline
fn read_body(inout self, inout r: ByteReader) raises -> None:
r.consume(self.body_raw)
r.consume(self.body_raw, self.content_length())
self.set_content_length(len(self.body_raw))

fn format_to(self, inout writer: Formatter):
writer.write(
Expand All @@ -252,13 +283,6 @@ struct HTTPResponse(Formattable, Stringable):
lineBreak,
)

if HeaderKey.DATE not in self.headers:
try:
var current_time = now(utc=True).__str__()
write_header(writer, HeaderKey.DATE, current_time)
except:
pass

self.headers.format_to(writer)

writer.write(lineBreak)
Expand Down Expand Up @@ -326,9 +350,7 @@ fn OK(body: Bytes, content_type: String) -> HTTPResponse:
)


fn OK(
body: Bytes, content_type: String, content_encoding: String
) -> HTTPResponse:
fn OK(body: Bytes, content_type: String, content_encoding: String) -> HTTPResponse:
return HTTPResponse(
headers=Headers(
Header(HeaderKey.CONTENT_TYPE, content_type),
Expand Down
Loading

0 comments on commit 5282ed5

Please sign in to comment.