Skip to content

Commit

Permalink
update byte handling
Browse files Browse the repository at this point in the history
  • Loading branch information
thatstoasty committed Jan 25, 2025
1 parent 34f31bd commit 16fcc12
Show file tree
Hide file tree
Showing 25 changed files with 581 additions and 484 deletions.
50 changes: 19 additions & 31 deletions benchmark/bench.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ from memory import Span
from benchmark import *
from lightbug_http.io.bytes import bytes, Bytes
from lightbug_http.header import Headers, Header
from lightbug_http.utils import ByteReader, ByteWriter
from lightbug_http.io.bytes import ByteReader, ByteWriter
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
from lightbug_http.uri import URI

Expand All @@ -11,9 +11,7 @@ alias headers = "GET /index.html HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mo
alias body = "I am the body of an HTTP request" * 5
alias body_bytes = bytes(body)
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
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
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


fn main():
Expand All @@ -26,24 +24,12 @@ fn run_benchmark():
config.verbose_timing = True
config.tabular_view = True
var m = Bench(config)
m.bench_function[lightbug_benchmark_header_encode](
BenchId("HeaderEncode")
)
m.bench_function[lightbug_benchmark_header_parse](
BenchId("HeaderParse")
)
m.bench_function[lightbug_benchmark_request_encode](
BenchId("RequestEncode")
)
m.bench_function[lightbug_benchmark_request_parse](
BenchId("RequestParse")
)
m.bench_function[lightbug_benchmark_response_encode](
BenchId("ResponseEncode")
)
m.bench_function[lightbug_benchmark_response_parse](
BenchId("ResponseParse")
)
m.bench_function[lightbug_benchmark_header_encode](BenchId("HeaderEncode"))
m.bench_function[lightbug_benchmark_header_parse](BenchId("HeaderParse"))
m.bench_function[lightbug_benchmark_request_encode](BenchId("RequestEncode"))
m.bench_function[lightbug_benchmark_request_parse](BenchId("RequestParse"))
m.bench_function[lightbug_benchmark_response_encode](BenchId("ResponseEncode"))
m.bench_function[lightbug_benchmark_response_parse](BenchId("ResponseParse"))
m.dump_report()
except:
print("failed to start benchmark")
Expand Down Expand Up @@ -100,12 +86,15 @@ fn lightbug_benchmark_request_encode(mut b: Bencher):
@always_inline
@parameter
fn request_encode():
var req = HTTPRequest(
URI.parse("http://127.0.0.1:8080/some-path"),
headers=headers_struct,
body=body_bytes,
)
_ = encode(req^)
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)

b.iter[request_encode]()

Expand All @@ -130,8 +119,7 @@ fn lightbug_benchmark_header_parse(mut b: Bencher):
var header = Headers()
var reader = ByteReader(headers.as_bytes())
_ = header.parse_raw(reader)
except:
print("failed")
except e:
print("failed", e)

b.iter[header_parse]()

File renamed without changes.
113 changes: 113 additions & 0 deletions lightbug_http/_logger.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from sys.param_env import env_get_string


struct LogLevel:
alias FATAL = 0
alias ERROR = 1
alias WARN = 2
alias INFO = 3
alias DEBUG = 4


fn get_log_level() -> Int:
"""Returns the log level based on the parameter environment variable `LOG_LEVEL`.
Returns:
The log level.
"""
alias level = env_get_string["LB_LOG_LEVEL", "INFO"]()
if level == "INFO":
return LogLevel.INFO
elif level == "WARN":
return LogLevel.WARN
elif level == "ERROR":
return LogLevel.ERROR
elif level == "DEBUG":
return LogLevel.DEBUG
elif level == "FATAL":
return LogLevel.FATAL
else:
return LogLevel.INFO


alias LOG_LEVEL = get_log_level()
"""Logger level determined by the `LB_LOG_LEVEL` param environment variable.
When building or running the application, you can set `LB_LOG_LEVEL` by providing the the following option:
```bash
mojo build ... -D LB_LOG_LEVEL=DEBUG
# or
mojo ... -D LB_LOG_LEVEL=DEBUG
```
"""


@value
struct Logger[level: Int]:
alias STDOUT = 1
alias STDERR = 2

fn _log_message[event_level: Int](self, message: String):
@parameter
if level >= event_level:

@parameter
if event_level < LogLevel.WARN:
# Write to stderr if FATAL or ERROR
print(message, file=Self.STDERR)
else:
print(message)

fn info[*Ts: Writable](self, *messages: *Ts):
var msg = String.write("\033[36mINFO\033[0m - ")

@parameter
fn write_message[T: Writable](message: T):
msg.write(message, " ")

messages.each[write_message]()
self._log_message[LogLevel.INFO](msg)

fn warn[*Ts: Writable](self, *messages: *Ts):
var msg = String.write("\033[33mWARN\033[0m - ")

@parameter
fn write_message[T: Writable](message: T):
msg.write(message, " ")

messages.each[write_message]()
self._log_message[LogLevel.WARN](msg)

fn error[*Ts: Writable](self, *messages: *Ts):
var msg = String.write("\033[31mERROR\033[0m - ")

@parameter
fn write_message[T: Writable](message: T):
msg.write(message, " ")

messages.each[write_message]()
self._log_message[LogLevel.ERROR](msg)

fn debug[*Ts: Writable](self, *messages: *Ts):
var msg = String.write("\033[34mDEBUG\033[0m - ")

@parameter
fn write_message[T: Writable](message: T):
msg.write(message, " ")

messages.each[write_message]()
self._log_message[LogLevel.DEBUG](msg)

fn fatal[*Ts: Writable](self, *messages: *Ts):
var msg = String.write("\033[35mFATAL\033[0m - ")

@parameter
fn write_message[T: Writable](message: T):
msg.write(message, " ")

messages.each[write_message]()
self._log_message[LogLevel.FATAL](msg)


alias logger = Logger[LOG_LEVEL]()
File renamed without changes.
35 changes: 10 additions & 25 deletions lightbug_http/client.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,10 @@ from lightbug_http.net import default_buffer_size
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
from lightbug_http.header import Headers, HeaderKey
from lightbug_http.net import create_connection, TCPConnection
from lightbug_http.io.bytes import Bytes
from lightbug_http.utils import ByteReader, logger
from lightbug_http.pool_manager import PoolManager, Scheme, PoolKey


fn parse_host_and_port(source: String, is_tls: Bool) raises -> (String, UInt16):
"""Parses the host and port from a given string.
Args:
source: The host uri to parse.
is_tls: A boolean indicating whether the connection is secure.
Returns:
A tuple containing the host and port.
"""
if source.count(":") != 1:
var port: UInt16 = 443 if is_tls else 80
return source, port

var result = source.split(":")
return result[0], UInt16(atol(result[1]))
from lightbug_http.io.bytes import Bytes, ByteReader
from lightbug_http._logger import logger
from lightbug_http.pool_manager import PoolManager, PoolKey
from lightbug_http.uri import URI, Scheme


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

var is_tls = False
var scheme = Scheme.HTTP
if request.uri.is_https():
is_tls = True
scheme = Scheme.HTTPS

host, port = parse_host_and_port(request.uri.host, is_tls)
var pool_key = PoolKey(host, port, scheme)
var uri = URI.parse(request.uri.host)
var pool_key = PoolKey(uri.host, uri.port.value(), scheme)
var cached_connection = False
var conn: TCPConnection
try:
conn = self._connections.take(pool_key)
cached_connection = True
except e:
if str(e) == "PoolManager.take: Key not found.":
conn = create_connection(host, port)
conn = create_connection(uri.host, uri.port.value())
else:
logger.error(e)
raise Error("Client.do: Failed to create a connection to host.")
Expand Down
2 changes: 1 addition & 1 deletion lightbug_http/cookie/request_cookie_jar.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from small_time import SmallTime, TimeZone
from small_time.small_time import strptime
from lightbug_http.strings import to_string, lineBreak
from lightbug_http.header import HeaderKey, write_header
from lightbug_http.utils import ByteReader, ByteWriter, is_newline, is_space
from lightbug_http.io.bytes import ByteReader, ByteWriter, is_newline, is_space


@value
Expand Down
2 changes: 1 addition & 1 deletion lightbug_http/cookie/response_cookie_jar.mojo
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections import Optional, List, Dict, KeyElement
from lightbug_http.strings import to_string
from lightbug_http.header import HeaderKey, write_header
from lightbug_http.utils import ByteWriter
from lightbug_http.io.bytes import ByteWriter


@value
Expand Down
12 changes: 6 additions & 6 deletions lightbug_http/header.mojo
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from collections import Dict, Optional
from memory import Span
from lightbug_http.io.bytes import Bytes, Byte
from lightbug_http.io.bytes import Bytes, ByteReader, ByteWriter, is_newline, is_space
from lightbug_http.strings import BytesConstant
from lightbug_http.utils import ByteReader, ByteWriter, is_newline, is_space, logger
from lightbug_http._logger import logger
from lightbug_http.strings import rChar, nChar, lineBreak, to_string


Expand Down Expand Up @@ -103,13 +103,13 @@ struct Headers(Writable, Stringable):
r.increment()
# TODO (bgreni): Handle possible trailing whitespace
var value = r.read_line()
var k = to_string(key).lower()
var k = str(key).lower()
if k == HeaderKey.SET_COOKIE:
cookies.append(to_string(value))
cookies.append(str(value))
continue

self._inner[k] = to_string(value)
return (to_string(first), to_string(second), to_string(third), cookies)
self._inner[k] = str(value)
return (str(first), str(second), str(third), cookies)

fn write_to[T: Writer, //](self, mut writer: T):
for header in self._inner.items():
Expand Down
14 changes: 9 additions & 5 deletions lightbug_http/http/request.mojo
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from memory import Span
from lightbug_http.io.bytes import Bytes, bytes, Byte
from lightbug_http.io.bytes import Bytes, bytes, ByteReader, ByteWriter
from lightbug_http.header import Headers, HeaderKey, Header, write_header
from lightbug_http.cookie import RequestCookieJar
from lightbug_http.uri import URI
from lightbug_http.utils import ByteReader, ByteWriter, logger
from lightbug_http._logger import logger
from lightbug_http.io.sync import Duration
from lightbug_http.strings import (
strHttp11,
Expand Down Expand Up @@ -86,7 +86,11 @@ struct HTTPRequest(Writable, Stringable):
if HeaderKey.CONNECTION not in self.headers:
self.headers[HeaderKey.CONNECTION] = "keep-alive"
if HeaderKey.HOST not in self.headers:
self.headers[HeaderKey.HOST] = uri.host
if uri.port:
var host = String.write(uri.host, ":", str(uri.port.value()))
self.headers[HeaderKey.HOST] = host
else:
self.headers[HeaderKey.HOST] = uri.host

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

self.body_raw = r.read_bytes(content_length)
self.body_raw = r.read_bytes(content_length).to_bytes()
self.set_content_length(content_length)

fn write_to[T: Writer, //](self, mut writer: T):
Expand Down Expand Up @@ -152,7 +156,7 @@ struct HTTPRequest(Writable, Stringable):
lineBreak,
)
writer.consuming_write(self^.body_raw)
return writer.consume()
return writer^.consume()

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

0 comments on commit 16fcc12

Please sign in to comment.