Skip to content

Commit

Permalink
Merge pull request #67 from bgreni/split-http-module
Browse files Browse the repository at this point in the history
Improve code organization
  • Loading branch information
saviorand authored Oct 23, 2024
2 parents bd14814 + ff57ab6 commit f10c091
Show file tree
Hide file tree
Showing 21 changed files with 1,154 additions and 1,216 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ Once you have a Mojo project set up locally,
```
6. Start a server listening on a port with your service like so.
```mojo
from lightbug_http import Welcome, SysServer
from lightbug_http import Welcome, Server

fn main() raises:
var server = SysServer()
var server = Server()
var handler = Welcome()
server.listen_and_serve("0.0.0.0:8080", handler)
```
Expand Down Expand Up @@ -180,9 +180,9 @@ Create a file, e.g `client.mojo` with the following code. Run `magic run mojo cl

```mojo
from lightbug_http import *
from lightbug_http.sys.client import MojoClient
from lightbug_http.client import Client
fn test_request(inout client: MojoClient) raises -> None:
fn test_request(inout client: Client) raises -> None:
var uri = URI.parse_raises("http://httpbin.org/status/404")
var headers = Header("Host", "httpbin.org")
Expand All @@ -207,7 +207,7 @@ fn test_request(inout client: MojoClient) raises -> None:
fn main() -> None:
try:
var client = MojoClient()
var client = Client()
test_request(client)
except e:
print(e)
Expand All @@ -216,7 +216,7 @@ fn main() -> None:
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:
By default, Lightbug uses the pure Mojo implementation for networking. To use Python's `socket` library instead, just import the `PythonServer` instead of the `Server` with the following line:
```mojo
from lightbug_http.python.server import PythonServer
```
Expand Down
4 changes: 2 additions & 2 deletions bench_server.mojo
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from lightbug_http.sys.server import SysServer
from lightbug_http.server import Server
from lightbug_http.service import TechEmpowerRouter


def main():
try:
var server = SysServer(tcp_keep_alive=True)
var server = Server(tcp_keep_alive=True)
var handler = TechEmpowerRouter()
server.listen_and_serve("0.0.0.0:8080", handler)
except e:
Expand Down
10 changes: 5 additions & 5 deletions client.mojo
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from lightbug_http import *
from lightbug_http.sys.client import MojoClient
from lightbug_http.client import Client


fn test_request(inout client: MojoClient) raises -> None:
var uri = URI.parse_raises("http://httpbin.org/status/404")
var headers = Header("Host", "httpbin.org")
fn test_request(inout client: Client) raises -> None:
var uri = URI.parse_raises("http://google.com")
var headers = Headers(Header("Host", "google.com"), Header("User-Agent", "curl/8.1.2"), Header("Accept", "*/*"))

var request = HTTPRequest(uri, headers)
var response = client.do(request^)
Expand All @@ -27,7 +27,7 @@ fn test_request(inout client: MojoClient) raises -> None:

fn main() -> None:
try:
var client = MojoClient()
var client = Client()
test_request(client)
except e:
print(e)
4 changes: 2 additions & 2 deletions lightbug.🔥
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from lightbug_http import Welcome, SysServer
from lightbug_http import Welcome, Server

fn main() raises:
var server = SysServer()
var server = Server()
var handler = Welcome()
server.listen_and_serve("0.0.0.0:8080", handler)
2 changes: 1 addition & 1 deletion lightbug_http/__init__.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ from lightbug_http.http import HTTPRequest, HTTPResponse, OK, NotFound
from lightbug_http.uri import URI
from lightbug_http.header import Header, Headers, HeaderKey
from lightbug_http.service import HTTPService, Welcome
from lightbug_http.sys.server import SysServer
from lightbug_http.server import Server
from lightbug_http.strings import to_string


Expand Down
119 changes: 114 additions & 5 deletions lightbug_http/client.mojo
Original file line number Diff line number Diff line change
@@ -1,12 +1,121 @@
from lightbug_http.http import HTTPRequest, HTTPResponse
from lightbug_http.libc import (
c_int,
AF_INET,
SOCK_STREAM,
socket,
connect,
send,
recv,
close,
)
from lightbug_http.strings import to_string
from lightbug_http.io.bytes import Bytes
from lightbug_http.utils import ByteReader
from lightbug_http.net import create_connection, default_buffer_size
from lightbug_http.http import HTTPRequest, HTTPResponse, encode
from lightbug_http.header import Headers, HeaderKey


trait Client:
struct Client:
var host: StringLiteral
var port: Int
var name: String

fn __init__(inout self) raises:
...
self.host = "127.0.0.1"
self.port = 8888
self.name = "lightbug_http_client"

fn __init__(inout self, host: StringLiteral, port: Int) raises:
...
self.host = host
self.port = port
self.name = "lightbug_http_client"

fn do(self, owned req: HTTPRequest) raises -> HTTPResponse:
...
"""
The `do` method is responsible for sending an HTTP request to a server and receiving the corresponding response.
It performs the following steps:
1. Creates a connection to the server specified in the request.
2. Sends the request body using the connection.
3. Receives the response from the server.
4. Closes the connection.
5. Returns the received response as an `HTTPResponse` object.
Note: The code assumes that the `HTTPRequest` object passed as an argument has a valid URI with a host and port specified.
Parameters
----------
req : HTTPRequest :
An `HTTPRequest` object representing the request to be sent.
Returns
-------
HTTPResponse :
The received response.
Raises
------
Error :
If there is a failure in sending or receiving the message.
"""
var uri = req.uri
var host = uri.host

if host == "":
raise Error("URI is nil")
var is_tls = False

if uri.is_https():
is_tls = True

var host_str: String
var port: Int

if ":" in host:
var host_port = host.split(":")
host_str = host_port[0]
port = atol(host_port[1])
else:
host_str = host
if is_tls:
port = 443
else:
port = 80

# TODO: Actually handle persistent connections
var conn = create_connection(socket(AF_INET, SOCK_STREAM, 0), host_str, port)
var bytes_sent = conn.write(encode(req))
if bytes_sent == -1:
raise Error("Failed to send message")

var new_buf = Bytes(capacity=default_buffer_size)
var bytes_recv = conn.read(new_buf)

if bytes_recv == 0:
conn.close()
try:
var res = HTTPResponse.from_bytes(new_buf^)
if res.is_redirect():
conn.close()
return self._handle_redirect(req^, res^)
return res
except e:
conn.close()
raise e

return HTTPResponse(Bytes())

fn _handle_redirect(
self, owned original_req: HTTPRequest, owned original_response: HTTPResponse
) raises -> HTTPResponse:
var new_uri: URI
var new_location = original_response.headers[HeaderKey.LOCATION]
if new_location.startswith("http"):
new_uri = URI.parse_raises(new_location)
original_req.headers[HeaderKey.HOST] = new_uri.host
else:
new_uri = original_req.uri
new_uri.path = new_location
original_req.uri = new_uri
return self.do(original_req^)
3 changes: 2 additions & 1 deletion lightbug_http/header.mojo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collections import Dict
from lightbug_http.io.bytes import Bytes, Byte
from lightbug_http.strings import BytesConstant
from collections import Dict
from lightbug_http.utils import ByteReader, ByteWriter, is_newline, is_space
from lightbug_http.strings import rChar, nChar, lineBreak, to_string

Expand All @@ -11,6 +11,7 @@ struct HeaderKey:
alias CONTENT_TYPE = "content-type"
alias CONTENT_LENGTH = "content-length"
alias CONTENT_ENCODING = "content-encoding"
alias TRANSFER_ENCODING = "transfer-encoding"
alias DATE = "date"
alias LOCATION = "location"
alias HOST = "host"
Expand Down
Loading

0 comments on commit f10c091

Please sign in to comment.