Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ This is not production ready yet. We're aiming to keep up with new developments

Lightbug currently has the following features:
- [x] Pure Mojo networking! No dependencies on Python by default
- [x] Set up a server to listen on a given host/port
- [x] TCP-based server and client implementation
- [x] Assign your own custom handler to a route
- [x] Craft HTTP requests and responses with built-in primitives
- [x] Everything is fully typed, with no `def` functions used


We're working on support for the following (contributors welcome!):
- [ ] [SSL/HTTPS support](https://github.com/saviorand/lightbug_http/issues/20)
- [ ] UDP support
- [ ] [Better error handling](https://github.com/saviorand/lightbug_http/issues/3), [improved form/multipart and JSON support](https://github.com/saviorand/lightbug_http/issues/4)
- [ ] [Multiple simultaneous connections](https://github.com/saviorand/lightbug_http/issues/5), [parallelization and performance optimizations](https://github.com/saviorand/lightbug_http/issues/6)
- [ ] [WebSockets](https://github.com/saviorand/lightbug_http/issues/7), [HTTP 2.0 support](https://github.com/saviorand/lightbug_http/issues/8)
- [ ] [WebSockets](https://github.com/saviorand/lightbug_http/issues/7), [HTTP 2.0/3.0 support](https://github.com/saviorand/lightbug_http/issues/8)
- [ ] [ASGI spec conformance](https://github.com/saviorand/lightbug_http/issues/17)

The test coverage is also something we're working on.
Expand All @@ -63,11 +64,11 @@ Once you have Mojo set up locally,
git clone https://github.com/saviorand/lightbug_http.git
```
2. Switch to the project directory:
```bash
```sh
cd lightbug_http
```
then run:
```bash
```sh
mojo lightbug.🔥
```

Expand Down Expand Up @@ -126,6 +127,37 @@ Once you have Mojo set up locally,

<p align="right">(<a href="#readme-top">back to top</a>)</p>

### Using the client

Create a file, e.g `client.mojo` with the following code:

```mojo
from lightbug_http.http import HTTPRequest
from lightbug_http.uri import URI
from lightbug_http.sys.client import MojoClient

fn test_request(inout client: MojoClient) raises -> None:
var uri = URI("http://httpbin.org/")
var request = HTTPRequest(uri)
var response = client.do(request)

# print status code
print("Response:", response.header.status_code())

# print various parsed headers
print("Header", response.header.content_length())

# print body
print(String(response.get_body()))


fn main() raises -> None:
var client = MojoClient()
test_request(client)
```

Pure Mojo-based client is available by default. This client is also used internally for testing the server.

## Switching between pure Mojo and Python implementations
By default, Lightbug uses the pure Mojo implementation for networking. To use Python's `socket` library instead, just import the `PythonServer` instead of the `SysServer` with the following line:
```mojo
Expand Down
7 changes: 5 additions & 2 deletions lightbug_http/http.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ fn OK(body: Bytes, content_type: String) -> HTTPResponse:
)


fn encode(req: HTTPRequest) raises -> Bytes:
fn encode(req: HTTPRequest, uri: URI) raises -> Bytes:
var res_str = String()
var protocol = strHttp11
var current_time = String()
Expand All @@ -241,7 +241,10 @@ fn encode(req: HTTPRequest) raises -> Bytes:

_ = builder.write(req.header.method())
_ = builder.write_string(String(" "))
_ = builder.write(req.header.request_uri())
if len(uri.request_uri()) > 1:
_ = builder.write_string(uri.request_uri())
else:
_ = builder.write_string("/")
_ = builder.write_string(String(" "))
_ = builder.write(protocol)
_ = builder.write_string(String("\r\n"))
Expand Down
2 changes: 1 addition & 1 deletion lightbug_http/sys/client.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ struct MojoClient(Client):

var conn = create_connection(self.fd, host_str, port)

var req_encoded = encode(req)
var req_encoded = encode(req, uri)
var bytes_sent = conn.write(req_encoded)
if bytes_sent == -1:
raise Error("Failed to send message")
Expand Down
4 changes: 1 addition & 3 deletions lightbug_http/sys/net.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ struct addrinfo_unix(AnAddrInfo):
Returns:
UInt32 - The IP address.
"""
var host_ptr = to_char_ptr(host)
var host_ptr = to_char_ptr(String(host))
var servinfo = Pointer[Self]().alloc(1)
servinfo.store(Self())

Expand Down Expand Up @@ -420,8 +420,6 @@ fn create_connection(sock: c_int, host: String, port: UInt16) raises -> SysConne
_ = shutdown(sock, SHUT_RDWR)
raise Error("Failed to connect to server")

print("Connected to server at " + host + ":" + port.__str__())

var laddr = TCPAddr()
var raddr = TCPAddr(host, int(port))
var conn = SysConnection(sock, laddr, raddr)
Expand Down
61 changes: 23 additions & 38 deletions lightbug_http/uri.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -186,54 +186,39 @@ struct URI:

# Defaults to HTTP/1.1
var proto_str = String(strHttp11)

# Parse requestURI
var n = raw_uri.rfind(" ")
if n < 0:
n = len(raw_uri)
elif n == 0:
raise Error("Request URI cannot be empty")
else:
var proto = raw_uri[n + 1 :]
if proto != strHttp11:
proto_str = proto

var request_uri = raw_uri[:n]

# Parse host from requestURI
n = request_uri.find("://")

var is_https = False

if n >= 0:
var host_and_port = request_uri[n + 3 :]

if request_uri[:n] == https:
# Parse the protocol
var proto_end = raw_uri.find("://")
var remainder_uri: String
if proto_end >= 0:
proto_str = raw_uri[:proto_end]
if proto_str == https:
is_https = True

n = host_and_port.find("/")
if n >= 0:
self.__host = host_and_port[:n]._buffer
request_uri = request_uri[n + 3 :]
else:
self.__host = host_and_port._buffer
request_uri = strSlash
remainder_uri = raw_uri[proto_end + 3:]
else:
n = request_uri.find("/")
if n >= 0:
self.__host = request_uri[:n]._buffer
request_uri = request_uri[n:]
else:
self.__host = request_uri._buffer
request_uri = strSlash
raise Error("Invalid URI: Missing protocol")

# Parse the host and optional port
var path_start = remainder_uri.find("/")
var host_and_port: String
var request_uri: String
if path_start >= 0:
host_and_port = remainder_uri[:path_start]
request_uri = remainder_uri[path_start:]
self.__host = host_and_port[:path_start]._buffer
else:
host_and_port = remainder_uri
request_uri = strSlash
self.__host = host_and_port._buffer

if is_https:
_ = self.set_scheme(https)
else:
_ = self.set_scheme(http)

# Parse path
n = request_uri.find("?")
var n = request_uri.find("?")
if n >= 0:
self.__path_original = request_uri[:n]._buffer
self.__query_string = request_uri[n + 1 :]._buffer
Expand Down