Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
7ca86c0
refactoring
thatstoasty Dec 5, 2025
dc2dcde
bump to new nightly
thatstoasty Dec 5, 2025
108f169
pull in smalltime
thatstoasty Dec 5, 2025
770a3cb
squashing copies
thatstoasty Dec 6, 2025
caa3c0b
wip typed throws
thatstoasty Dec 6, 2025
a2f3f89
sockaddr wrapper
thatstoasty Dec 7, 2025
84a5681
start adding pico
thatstoasty Dec 9, 2025
e611a30
use comptime calculated byte constants
thatstoasty Dec 9, 2025
0f52d13
string ptr
thatstoasty Dec 9, 2025
0768708
clean up some tuple returns
thatstoasty Dec 10, 2025
6b37585
checking out benchmarks with fixed pico test
thatstoasty Dec 12, 2025
7f3ac21
first pass
thatstoasty Dec 13, 2025
928e849
remove error handler
saviorand Dec 16, 2025
8ed3407
remove empty tests
saviorand Dec 16, 2025
a519b14
remove unused vars in server.mojo
saviorand Dec 16, 2025
cc247bf
remove unused parameter
saviorand Dec 16, 2025
fd4b9c7
wip new server logic
saviorand Dec 16, 2025
b8db7ba
add connection state comment
saviorand Dec 16, 2025
23216aa
remove logger
saviorand Dec 16, 2025
89f2ce1
fix server compilation errors
saviorand Dec 19, 2025
a9fcc76
fix lightbug warning
thatstoasty Dec 19, 2025
9205bf0
remove state enum
saviorand Dec 19, 2025
bebde35
use an error variant instead of struct
saviorand Dec 20, 2025
307ceaf
wip new server code
saviorand Dec 22, 2025
3372465
split http parsing into separate files
saviorand Dec 23, 2025
23be5d2
split tests and update imports
saviorand Dec 23, 2025
259cf3f
fixing socket errors
saviorand Dec 23, 2025
0270af1
add socket error enum
thatstoasty Dec 23, 2025
657020b
remove old server
saviorand Dec 24, 2025
d68dd56
remove zerocopybuffer
saviorand Dec 24, 2025
ebc647f
add provision pool and move connectionstate
saviorand Dec 24, 2025
2d3de43
run mojo format
saviorand Dec 24, 2025
d8773c4
fix owning list and move chunked decoder functions to methods
thatstoasty Dec 24, 2025
869994a
clean up comments and naming
saviorand Dec 25, 2025
72795b3
add todos
saviorand Dec 25, 2025
02e7c30
add errorwithcause
saviorand Dec 25, 2025
40c3c05
remove server config comment
saviorand Dec 25, 2025
e39507c
add utils
saviorand Dec 25, 2025
384929c
move find_all to strings
saviorand Dec 25, 2025
60a7982
remove errorwithcause
saviorand Dec 25, 2025
a1d7d32
wip first pass custom errors in c socket
saviorand Dec 26, 2025
5f7a75d
wip propagating custom errors
saviorand Dec 27, 2025
6749138
make server compile
saviorand Dec 27, 2025
0dbdf0e
add implicit init
saviorand Dec 27, 2025
06a5cf7
adding more custom errors
saviorand Dec 29, 2025
34bef62
simplify provisionerror and format
saviorand Dec 29, 2025
aa8cb2b
use descriptive error names
saviorand Dec 29, 2025
e470a19
wip more typed errors in c socket
saviorand Dec 29, 2025
3706742
add remaining custom errors in c socket
saviorand Dec 29, 2025
3490ba6
use senderror in socket.mojo
saviorand Dec 29, 2025
84f659c
use more specific errors in socket
saviorand Dec 30, 2025
dcf9d28
use custom errors in request and response
saviorand Dec 30, 2025
00bda3f
use csocketerror in socket init
saviorand Dec 30, 2025
60a052a
raise einval on shutdown
saviorand Dec 30, 2025
99a1fd6
raise einval in udp
saviorand Dec 30, 2025
a7d4705
add custom error to create_connection
saviorand Dec 30, 2025
b83fd6c
refactor socket.close error handling
saviorand Dec 30, 2025
64ccedc
remove generic errors from c socket error types
saviorand Dec 30, 2025
cd886cf
add typed errors to address and network
saviorand Dec 30, 2025
01818e6
remove getaddrinfoerror variant
saviorand Dec 30, 2025
028599e
use a custom type in c getaddrinfo
saviorand Dec 30, 2025
37d484a
wip fixing errno errors
saviorand Jan 1, 2026
aadd77c
add ConnectEINPROGRESS error to c socket
saviorand Jan 1, 2026
3c63a3b
use custom errors in header
saviorand Jan 1, 2026
235104d
use custom error in cookie
saviorand Jan 1, 2026
11223f9
fix typo in cookie separator
saviorand Jan 1, 2026
540a592
remove generic SocketError
saviorand Jan 1, 2026
068c4b8
wip refactoring request parsing
saviorand Jan 3, 2026
379fb23
fix compilation errors
saviorand Jan 3, 2026
7cea141
make parsing safer
saviorand Jan 3, 2026
184972d
use bytereader in parsing
saviorand Jan 3, 2026
58b96e1
rename safe functions to try
saviorand Jan 3, 2026
5bd272c
rename parsing functions for clarity
saviorand Jan 3, 2026
4734511
experiment with httplint and date format fix
saviorand Jan 3, 2026
e8cb1c1
add httplint to ci
saviorand Jan 3, 2026
704cb96
remove httplint server
saviorand Jan 4, 2026
ce991c0
use http conformance instead of httplint
saviorand Jan 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ jobs:
pixi run integration_tests_py
pixi run integration_tests_external
pixi run integration_tests_udp
pixi run http_conformance
10 changes: 8 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ repos:
hooks:
- id: mojo-format
name: mojo-format
entry: pixi run mojo format -l 120
entry: pixi run format
language: system
files: '\.(mojo|🔥)$'
pass_filenames: false
stages: [pre-commit]
# - id: mojo-docs
# name: mojo-docs
# entry: pixi run lint_docs
# language: system
# pass_filenames: false
# stages: [pre-commit]
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
<br/>
[![Join our Discord][discord-shield]][discord-url]
[![Contributors Welcome][contributors-shield]][contributors-url]


</p>
</div>

## Overview

Lightbug is a simple and sweet HTTP framework for Mojo that builds on best practice from systems programming, such as the Golang [FastHTTP](https://github.com/valyala/fasthttp/) and Rust [may_minihttp](https://github.com/Xudong-Huang/may_minihttp/).
Lightbug is a simple and sweet HTTP framework for Mojo that builds on best practice from systems programming, such as the Golang [FastHTTP](https://github.com/valyala/fasthttp/) and Rust [may_minihttp](https://github.com/Xudong-Huang/may_minihttp/).

This is not production ready yet. We're aiming to keep up with new developments in Mojo, but it might take some time to get to a point when this is safe to use in real-world applications.

Expand All @@ -45,7 +45,7 @@ Lightbug currently has the following features:
<!-- GETTING STARTED -->
## Getting Started

The only hard dependency for `lightbug_http` is Mojo.
The only hard dependency for `lightbug_http` is Mojo.
Learn how to get up and running with Mojo on the [Modular website](https://www.modular.com/max/mojo).
Once you have a Mojo project set up locally,

Expand All @@ -70,7 +70,7 @@ Once you have a Mojo project set up locally,
from lightbug_http import *
```

or import individual structs and functions, e.g.
or import individual structs and functions, e.g.

```mojo
from lightbug_http.service import HTTPService
Expand Down Expand Up @@ -234,12 +234,12 @@ from lightbug_http.connection import dial_udp
from lightbug_http.address import UDPAddr
from utils import StringSlice

alias test_string = "Hello, lightbug!"
comptime test_string = "Hello, lightbug!"

fn main() raises:
print("Dialing UDP server...")
alias host = "127.0.0.1"
alias port = 12000
comptime host = "127.0.0.1"
comptime port = 12000
var udp = dial_udp(host, port)

print("Sending " + str(len(test_string)) + " messages to the server...")
Expand Down Expand Up @@ -287,12 +287,12 @@ We're working on support for the following (contributors welcome!):

The plan is to get to a feature set similar to Python frameworks like [Starlette](https://github.com/encode/starlette), but with better performance.

Our vision is to develop three libraries, with `lightbug_http` (this repo) as a starting point:
Our vision is to develop three libraries, with `lightbug_http` (this repo) as a starting point:
- `lightbug_http` - Lightweight and simple HTTP framework, basic networking primitives
- [`lightbug_api`](https://github.com/saviorand/lightbug_api) - Tools to make great APIs fast, with OpenAPI support and automated docs
- `lightbug_web` - (release date TBD) Full-stack web framework for Mojo, similar to NextJS or SvelteKit

The idea is to get to a point where the entire codebase of a simple modern web application can be written in Mojo.
The idea is to get to a point where the entire codebase of a simple modern web application can be written in Mojo.

We don't make any promises, though -- this is just a vision, and whether we get there or not depends on many factors, including the support of the community.

Expand Down
77 changes: 50 additions & 27 deletions benchmark/bench.mojo
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from lightbug_http.header import Header, Headers
from lightbug_http.io.bytes import ByteReader, Bytes, ByteWriter
from lightbug_http.server import default_max_request_body_size, default_max_request_uri_length
from lightbug_http.uri import URI
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.io.bytes import ByteReader, ByteWriter
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
from lightbug_http.uri import URI
from lightbug_http.server import default_max_request_body_size, default_max_request_uri_length

alias headers = "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"

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
comptime headers = "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"

comptime body = "I am the body of an HTTP request" * 5
comptime body_bytes = body.as_bytes()
comptime 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
comptime 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 @@ -24,18 +25,30 @@ fn run_benchmark():
var config = BenchConfig()
config.verbose_timing = 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")


alias headers_struct = Headers(
comptime headers_struct = Headers(
Header("Content-Type", "application/json"),
Header("Content-Length", "1234"),
Header("Connection", "close"),
Expand All @@ -49,7 +62,9 @@ fn lightbug_benchmark_response_encode(mut b: Bencher):
@always_inline
@parameter
fn response_encode():
var res = HTTPResponse(body.as_bytes(), headers=materialize[headers_struct]())
var res = HTTPResponse(
body.as_bytes(), headers=materialize[headers_struct]()
)
_ = encode(res^)

b.iter[response_encode]()
Expand All @@ -74,7 +89,12 @@ fn lightbug_benchmark_request_parse(mut b: Bencher):
@parameter
fn request_parse():
try:
_ = HTTPRequest.from_bytes("127.0.0.1/path", default_max_request_body_size, default_max_request_uri_length, Request.as_bytes())
_ = HTTPRequest.from_bytes(
"127.0.0.1/path",
default_max_request_body_size,
default_max_request_uri_length,
Request.as_bytes(),
)
except:
pass

Expand All @@ -86,13 +106,16 @@ fn lightbug_benchmark_request_encode(mut b: Bencher):
@always_inline
@parameter
fn request_encode() raises:
var uri = URI.parse("http://127.0.0.1:8080/some-path")
var req = HTTPRequest(
uri=uri,
headers=materialize[headers_struct](),
body=materialize[body_bytes](),
)
_ = encode(req^)
try:
var req = HTTPRequest(
uri=URI.parse("http://127.0.0.1:8080/some-path"),
headers=materialize[headers_struct](),
body=List[Byte](materialize[body_bytes]()),
)
_ = encode(req^)
except e:
print("failed to encode request, error: ", e)
raise Error("failed to encode request")

try:
b.iter[request_encode]()
Expand All @@ -119,7 +142,7 @@ fn lightbug_benchmark_header_parse(mut b: Bencher):
try:
var header = Headers()
var reader = ByteReader(headers.as_bytes())
_ = header.parse_raw(reader)
_ = header.parse_raw_request(reader)
except e:
print("failed", e)

Expand Down
12 changes: 4 additions & 8 deletions benchmark/bench_server.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ from lightbug_http.server import Server
from lightbug_http.service import TechEmpowerRouter


def main():
try:
var server = Server(tcp_keep_alive=True)
var handler = TechEmpowerRouter()
server.listen_and_serve("localhost:8080", handler)
except e:
print("Error starting server: " + e.__str__())
return
fn main() raises:
var server = Server(tcp_keep_alive=True)
var handler = TechEmpowerRouter()
server.listen_and_serve("localhost:8080", handler)
3 changes: 2 additions & 1 deletion client.mojo
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from lightbug_http import *
from lightbug_http.client import Client

from lightbug_http import *


fn test_request(mut client: Client) raises -> None:
var uri = URI.parse("google.com")
Expand Down
6 changes: 4 additions & 2 deletions lightbug.🔥
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from lightbug_http import Welcome, Server
from lightbug_http import Welcome
from lightbug_http.server import Server
from os.env import getenv


fn main() raises:
var server = Server()
var handler = Welcome()

var host = getenv("DEFAULT_SERVER_HOST", "localhost")
var port = getenv("DEFAULT_SERVER_PORT", "8080")

Expand Down
17 changes: 5 additions & 12 deletions lightbug_http/__init__.mojo
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
from lightbug_http.http import (
HTTPRequest,
HTTPResponse,
OK,
NotFound,
SeeOther,
StatusCode,
)
from lightbug_http.header import Header, HeaderKey, Headers
from lightbug_http.server import Server
from lightbug_http.service import Counter, HTTPService, Welcome
from lightbug_http.uri import URI
from lightbug_http.header import Header, Headers, HeaderKey

from lightbug_http.cookie import Cookie, RequestCookieJar, ResponseCookieJar
from lightbug_http.service import HTTPService, Welcome, Counter
from lightbug_http.server import Server
from lightbug_http.strings import to_string
from lightbug_http.http import OK, HTTPRequest, HTTPResponse, NotFound, SeeOther, StatusCode
Loading