From 037d74ebe5860cb13c25b0c7e7d42e22aefd64fd Mon Sep 17 00:00:00 2001 From: Valentin Erokhin <37780080+saviorand@users.noreply.github.com> Date: Thu, 23 May 2024 21:27:46 +0000 Subject: [PATCH 1/4] improve uri handling --- lightbug_http/http.mojo | 4 +-- lightbug_http/sys/client.mojo | 2 +- lightbug_http/sys/net.mojo | 4 +-- lightbug_http/uri.mojo | 61 +++++++++++++---------------------- 4 files changed, 27 insertions(+), 44 deletions(-) diff --git a/lightbug_http/http.mojo b/lightbug_http/http.mojo index d25932da..c1813fc4 100644 --- a/lightbug_http/http.mojo +++ b/lightbug_http/http.mojo @@ -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() @@ -241,7 +241,7 @@ fn encode(req: HTTPRequest) raises -> Bytes: _ = builder.write(req.header.method()) _ = builder.write_string(String(" ")) - _ = builder.write(req.header.request_uri()) + _ = builder.write(uri.request_uri()) _ = builder.write_string(String(" ")) _ = builder.write(protocol) _ = builder.write_string(String("\r\n")) diff --git a/lightbug_http/sys/client.mojo b/lightbug_http/sys/client.mojo index ad417ed8..94b96f75 100644 --- a/lightbug_http/sys/client.mojo +++ b/lightbug_http/sys/client.mojo @@ -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") diff --git a/lightbug_http/sys/net.mojo b/lightbug_http/sys/net.mojo index fa326ae8..622c24f4 100644 --- a/lightbug_http/sys/net.mojo +++ b/lightbug_http/sys/net.mojo @@ -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()) @@ -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) diff --git a/lightbug_http/uri.mojo b/lightbug_http/uri.mojo index daea9bdb..2cdf48e4 100644 --- a/lightbug_http/uri.mojo +++ b/lightbug_http/uri.mojo @@ -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: + 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:] 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 + host_and_port = remainder_uri + request_uri = strSlash # Assume root if no path is provided + + self.__host = host_and_port[:path_start]._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 From 7ca20d370db988f4d6847d9c8b0567d5cb843c7d Mon Sep 17 00:00:00 2001 From: Valentin Erokhin <37780080+saviorand@users.noreply.github.com> Date: Sat, 25 May 2024 14:02:07 +0000 Subject: [PATCH 2/4] fix request uri parsing --- client.mojo | 28 ++++++++++++++++++++++++++++ lightbug_http/http.mojo | 5 ++++- lightbug_http/uri.mojo | 6 +++--- 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 client.mojo diff --git a/client.mojo b/client.mojo new file mode 100644 index 00000000..78082c65 --- /dev/null +++ b/client.mojo @@ -0,0 +1,28 @@ +from lightbug_http.http import HTTPRequest +from lightbug_http.uri import URI +from lightbug_http.sys.client import MojoClient + +fn test_request_simple_url(inout client: MojoClient) raises -> None: + """ + Test making a simple GET request without parameters. + Validate that we get a 200 OK response. + """ + 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() + print("Testing URL request") + test_request_simple_url(client) + print("Done") \ No newline at end of file diff --git a/lightbug_http/http.mojo b/lightbug_http/http.mojo index c1813fc4..6b481a6c 100644 --- a/lightbug_http/http.mojo +++ b/lightbug_http/http.mojo @@ -241,7 +241,10 @@ fn encode(req: HTTPRequest, uri: URI) raises -> Bytes: _ = builder.write(req.header.method()) _ = builder.write_string(String(" ")) - _ = builder.write(uri.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")) diff --git a/lightbug_http/uri.mojo b/lightbug_http/uri.mojo index 2cdf48e4..66913be2 100644 --- a/lightbug_http/uri.mojo +++ b/lightbug_http/uri.mojo @@ -206,11 +206,11 @@ struct URI: 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 # Assume root if no path is provided - - self.__host = host_and_port[:path_start]._buffer + request_uri = strSlash + self.__host = host_and_port._buffer if is_https: _ = self.set_scheme(https) From 7b88444d8f84fb94a8516b3f6b0c9b1dfea3dc5b Mon Sep 17 00:00:00 2001 From: Val Date: Sat, 25 May 2024 16:10:14 +0200 Subject: [PATCH 3/4] update the readme --- README.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e8c9a263..06ca5a71 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ 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 @@ -38,9 +38,10 @@ Lightbug currently has the following features: 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. @@ -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.🔥 ``` @@ -126,6 +127,37 @@ Once you have Mojo set up locally,

(back to top)

+### 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 From 12dd28eadc4312f5a3235b77c1424c1208362e89 Mon Sep 17 00:00:00 2001 From: Val Date: Sat, 25 May 2024 16:11:48 +0200 Subject: [PATCH 4/4] remove client test --- client.mojo | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 client.mojo diff --git a/client.mojo b/client.mojo deleted file mode 100644 index 78082c65..00000000 --- a/client.mojo +++ /dev/null @@ -1,28 +0,0 @@ -from lightbug_http.http import HTTPRequest -from lightbug_http.uri import URI -from lightbug_http.sys.client import MojoClient - -fn test_request_simple_url(inout client: MojoClient) raises -> None: - """ - Test making a simple GET request without parameters. - Validate that we get a 200 OK response. - """ - 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() - print("Testing URL request") - test_request_simple_url(client) - print("Done") \ No newline at end of file