diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml new file mode 100644 index 00000000..9a5b2ec6 --- /dev/null +++ b/.github/workflows/branch.yml @@ -0,0 +1,19 @@ +name: Branch workflow + +on: + push: + branches: + - '*' + pull_request: + branches: + - '*' + +permissions: + contents: write + +jobs: + test: + uses: ./.github/workflows/test.yml + + package: + uses: ./.github/workflows/package.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 08d8ef3a..bc85c0b5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,45 +1,22 @@ -name: Main pipeline +name: Main workflow on: push: - branches: - - main + branches: [main] permissions: contents: write jobs: - setup: - name: Setup environment and install dependencies - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - name: Install modular - run: | - curl -s https://get.modular.com | sh - - modular auth examples - - name: Install Mojo - run: modular install mojo - - name: Add to PATH - run: echo "/home/runner/.modular/pkg/packages.modular.com_mojo/bin" >> $GITHUB_PATH test: - name: Run tests - runs-on: ubuntu-latest - needs: setup - steps: - - name: Run the test suite - run: mojo run_tests.mojo + uses: ./.github/workflows/test.yml + package: - name: Create package - runs-on: ubuntu-latest - needs: setup - steps: - - name: Run the package command - run: mojo package lightbug_http -o lightbug_http.mojopkg - - name: Upload package to release - uses: svenstaro/upload-release-action@v2 - with: - file: lightbug_http.mojopkg - tag: latest-build - overwrite: true + uses: ./.github/workflows/package.yml + + publish: + uses: ./.github/workflows/publish.yml + secrets: + PREFIX_API_KEY: ${{ secrets.PREFIX_API_KEY }} + + diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 00000000..fac7cce5 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,23 @@ +name: Create package + +on: + workflow_call: + +jobs: + package: + name: Package + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Run the package command + run: | + curl -ssL https://magic.modular.com | bash + source $HOME/.bash_profile + magic run mojo package lightbug_http -o lightbug_http.mojopkg + + - name: Upload package as artifact + uses: actions/upload-artifact@v4 + with: + name: lightbug_http-package + path: lightbug_http.mojopkg diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..2245947c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,44 @@ +name: Build and publish + +on: + workflow_call: + secrets: + PREFIX_API_KEY: + required: true + +jobs: + publish: + name: Publish package + strategy: + matrix: + include: + - { target: linux-64, os: ubuntu-latest } + - { target: osx-arm64, os: macos-14 } + fail-fast: false + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + defaults: + run: + shell: bash + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Build package for target platform + env: + TARGET_PLATFORM: ${{ matrix.target }} + PREFIX_API_KEY: ${{ secrets.PREFIX_API_KEY }} + CONDA_BLD_PATH: ${{ runner.workspace }}/.rattler + run: | + curl -ssL https://magic.modular.com | bash + source $HOME/.bash_profile + + # Temporary method to fetch the rattler binary. + RATTLER_BINARY="rattler-build-aarch64-apple-darwin" + if [[ $TARGET_PLATFORM == "linux-64" ]]; then RATTLER_BINARY="rattler-build-x86_64-unknown-linux-musl"; fi + curl -SL --progress-bar https://github.com/prefix-dev/rattler-build/releases/latest/download/${RATTLER_BINARY} -o rattler-build + chmod +x rattler-build + + # Build and push + magic run build --target-platform=$TARGET_PLATFORM + magic run publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..9b9d1e6a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release tag pipeline + +on: + push: + tags: + - '*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + test: + uses: ./.github/workflows/test.yml + + package: + uses: ./.github/workflows/package.yml + + publish: + uses: ./.github/workflows/publish.yml + secrets: + PREFIX_API_KEY: ${{ secrets.PREFIX_API_KEY }} + + upload-to-release: + name: Upload to release + needs: package + runs-on: ubuntu-latest + steps: + - name: Get the version + id: get_version + run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + + - uses: actions/download-artifact@v4 + with: + name: lightbug_http-package + + - name: Upload package to release + uses: svenstaro/upload-release-action@v2 + with: + file: lightbug_http.mojopkg + tag: ${{ steps.get_version.outputs.VERSION }} + overwrite: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..6807fd5c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,18 @@ +name: Run the testing suite + +on: + workflow_call: + +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Run the test suite + run: | + curl -ssL https://magic.modular.com | bash + source $HOME/.bash_profile + magic run mojo run_tests.mojo + diff --git a/.gitignore b/.gitignore index e9cf84f7..06727c22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,15 @@ *.📦 +*.mojopkg .DS_Store .mojoenv -install_id \ No newline at end of file +install_id + +# pixi environments +.pixi +*.egg-info + +# magic environments +.magic + +# Rattler +output \ No newline at end of file diff --git a/.mojoenv.example b/.mojoenv.example deleted file mode 100644 index 48ab80bd..00000000 --- a/.mojoenv.example +++ /dev/null @@ -1 +0,0 @@ -MOJO_AUTH= \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..1afdaab3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: local + hooks: + - id: mojo-format + name: mojo-format + entry: magic run mojo format -l 120 + language: system + files: '\.(mojo|🔥)$' + stages: [commit] \ No newline at end of file diff --git a/README.md b/README.md index fa1d558d..b2cc4456 100644 --- a/README.md +++ b/README.md @@ -35,147 +35,181 @@ Lightbug currently has the following features: - [x] Craft HTTP requests and responses with built-in primitives - [x] Everything is fully typed, with no `def` functions used + ### Check Out These Mojo Libraries: -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/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. - -The plan is to get to a feature set similar to Python frameworks like [Starlette](https://github.com/encode/starlette), but with better performance. - +- Logging - [@toasty/stump](https://github.com/thatstoasty/stump) +- CLI and Terminal - [@toasty/prism](https://github.com/thatstoasty/prism), [@toasty/mog](https://github.com/thatstoasty/mog) +- Date/Time - [@mojoto/morrow](https://github.com/mojoto/morrow.mojo) and [@toasty/small-time](https://github.com/thatstoasty/small-time)

(back to top)

## Getting Started -The only hard dependencies for `lightbug_http` are Mojo and [Git](https://docs.github.com/en/get-started/getting-started-with-git). -Learn how to get up and running with Mojo on the [Modular website](https://www.modular.com/max/mojo). The Docker installation was removed with the changes in Modular CLI. It will be available once Modular provides needed functionality for Docker setups. - -Once you have Mojo set up locally, +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, -1. Clone the repo - ```sh - git clone https://github.com/saviorand/lightbug_http.git - ``` -2. Switch to the project directory: - ```sh - cd lightbug_http +1. Add the `mojo-community` channel to your `mojoproject.toml`, e.g: + ```toml + [project] + channels = ["conda-forge", "https://conda.modular.com/max", "https://repo.prefix.dev/mojo-community"] ``` - then run: - ```sh - mojo lightbug.🔥 +2. Add `lightbug_http` as a dependency: + ```toml + [dependencies] + lightbug_http = ">=0.1.4" ``` - - Open `localhost:8080` in your browser. You should see a welcome screen. - - Congrats 🥳 You're using Lightbug! -2. Add your handler in `lightbug.🔥` by passing a struct that satisfies the following trait: +3. Run `magic install` at the root of your project, where `mojoproject.toml` is located +4. Lightbug should now be installed as a dependency. You can import all the default imports at once, e.g: + ```mojo + from lightbug_http import * + ``` + or import individual structs and functions, e.g. + ```mojo + from lightbug_http.http import HTTPService, HTTPRequest, HTTPResponse, OK, NotFound + ``` + there are some default handlers you can play with: + ```mojo + from lightbug_http.service import Printer # prints request details to console + from lightbug_http.service import Welcome # serves an HTML file with an image (currently requires manually adding files to static folder, details below) + from lightbug_http.service import ExampleRouter # serves /, /first, /second, and /echo routes + ``` +5. Add your handler in `lightbug.🔥` by passing a struct that satisfies the following trait: ```mojo trait HTTPService: fn func(self, req: HTTPRequest) raises -> HTTPResponse: ... ``` - For example, to make a `Printer` service that simply prints the request to console: + For example, to make a `Printer` service that prints some details about the request to console: ```mojo - @value - struct Printer(HTTPService): - fn func(self, req: HTTPRequest) raises -> HTTPResponse: - var body = req.body_raw - print(String(body)) + from lightbug_http import * - return OK(body) - ``` - Routing is not in scope for this library, but you can easily set up routes yourself: - ```mojo - @value - struct ExampleRouter(HTTPService): - fn func(self, req: HTTPRequest) raises -> HTTPResponse: - var body = req.body_raw - var uri = req.uri() - - if uri.path() == "/": - print("I'm on the index path!") - if uri.path() == "/first": - print("I'm on /first!") - elif uri.path() == "/second": - print("I'm on /second!") - elif uri.path() == "/echo": - print(String(body)) - - return OK(body) + @value + struct Printer(HTTPService): + fn func(self, req: HTTPRequest) raises -> HTTPResponse: + var uri = req.uri + print("Request URI: ", to_string(uri.request_uri)) + + var header = req.headers + print("Request protocol: ", req.protocol) + print("Request method: ", req.method) + print( + "Request Content-Type: ", to_string(header[HeaderKey.CONTENT_TYPE]) + ) + + var body = req.body_raw + print("Request Body: ", to_string(body)) + + return OK(body) ``` +6. Start a server listening on a port with your service like so. + ```mojo + from lightbug_http import Welcome, SysServer + + fn main() raises: + var server = SysServer() + var handler = Welcome() + server.listen_and_serve("0.0.0.0:8080", handler) + ``` +Feel free to change the settings in `listen_and_serve()` to serve on a particular host and port. + +Now send a request `0.0.0.0:8080`. You should see some details about the request printed out to the console. - We plan to add routing in a future library called `lightbug_api`, see [Roadmap](#roadmap) for more details. -3. Run `mojo lightbug.🔥`. This will start up a server listening on `localhost:8080`. Or, if you prefer to import the server into your own app: - ```mojo - from lightbug_http.sys.server import SysServer - from lightbug_http.service import Printer +Congrats 🥳 You're using Lightbug! - fn main() raises: - var server = SysServer() - var handler = Printer() - server.listen_and_serve("0.0.0.0:8080", handler) - ``` - Feel free to change the settings in `listen_and_serve()` to serve on a particular host and port. +Routing is not in scope for this library, but you can easily set up routes yourself: +```mojo +from lightbug_http import * + +@value +struct ExampleRouter(HTTPService): + fn func(self, req: HTTPRequest) raises -> HTTPResponse: + var body = req.body_raw + var uri = req.uri + + if uri.path == "/": + print("I'm on the index path!") + if uri.path == "/first": + print("I'm on /first!") + elif uri.path == "/second": + print("I'm on /second!") + elif uri.path == "/echo": + print(to_string(body)) + + return OK(body) +``` + +We plan to add more advanced routing functionality in a future library called `lightbug_api`, see [Roadmap](#roadmap) for more details. +

(back to top)

### Serving static files -The default welcome screen shows an example of how to serve files like images or HTML using Lightbug. Mojo has built-in `open`, `read` and `read_bytes` methods that you can use to read files from e.g. a `static` directory and serve them on a route: +The default welcome screen shows an example of how to serve files like images or HTML using Lightbug. Mojo has built-in `open`, `read` and `read_bytes` methods that you can use to read files and serve them on a route. Assuming you copy an html file and image from the Lightbug repo into a `static` directory at the root of your repo: ```mojo +from lightbug_http import * + @value struct Welcome(HTTPService): fn func(self, req: HTTPRequest) raises -> HTTPResponse: - var uri = req.uri() + var uri = req.uri - if uri.path() == "/": + if uri.path == "/": var html: Bytes with open("static/lightbug_welcome.html", "r") as f: html = f.read_bytes() return OK(html, "text/html; charset=utf-8") - - if uri.path() == "/logo.png": + + if uri.path == "/logo.png": var image: Bytes with open("static/logo.png", "r") as f: image = f.read_bytes() return OK(image, "image/png") - - return NotFound(uri.path()) + + return NotFound(uri.path) ``` ### Using the client -Create a file, e.g `client.mojo` with the following code. Run `mojo client.mojo` to execute the request to a given URL. +Create a file, e.g `client.mojo` with the following code. Run `magic run mojo client.mojo` to execute the request to a given URL. ```mojo +from lightbug_http import * +from lightbug_http.sys.client import MojoClient + fn test_request(inout client: MojoClient) raises -> None: - var uri = URI("http://httpbin.org/status/404") - var request = HTTPRequest(uri) - var response = client.do(request) + var uri = URI.parse_raises("http://httpbin.org/status/404") + var headers = Header("Host", "httpbin.org") - # print status code - print("Response:", response.header.status_code()) + var request = HTTPRequest(uri, headers) + var response = client.do(request^) - # print raw headers - # print("Headers:", response.header.headers()) + # print status code + print("Response:", response.status_code) # print parsed headers (only some are parsed for now) - print("Content-Type:", String(response.header.content_type())) - print("Content-Length", response.header.content_length()) - print("Connection:", response.header.connection_close()) - print("Server:", String(response.header.server())) + print("Content-Type:", response.headers["Content-Type"]) + print("Content-Length", response.headers["Content-Length"]) + print("Server:", to_string(response.headers["Server"])) + + print( + "Is connection set to connection-close? ", response.connection_close() + ) # print body - print(String(response.get_body())) + print(to_string(response.body_raw)) + + +fn main() -> None: + try: + var client = MojoClient() + test_request(client) + except e: + print(e) ``` Pure Mojo-based client is available by default. This client is also used internally for testing the server. @@ -186,6 +220,7 @@ By default, Lightbug uses the pure Mojo implementation for networking. To use Py from lightbug_http.python.server import PythonServer ``` You can then use all the regular server commands in the same way as with the default server. +Note: as of September, 2024, `PythonServer` and `PythonClient` throw a compilation error when starting. There's an open [issue](https://github.com/saviorand/lightbug_http/issues/41) to fix this - contributions welcome! ## Roadmap @@ -194,6 +229,18 @@ You can then use all the regular server commands in the same way as with the def Logo +We're working on support for the following (contributors welcome!): + +- [ ] [WebSocket Support](https://github.com/saviorand/lightbug_http/pull/57) + - [ ] [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) + - [ ] [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 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: - `lightbug_http` - HTTP infrastructure and basic API development - `lightbug_api` - (coming later in 2024!) Tools to make great APIs fast, with support for OpenAPI spec and domain driven design diff --git a/bench.mojo b/bench.mojo index 4ae5dfad..744a1d2d 100644 --- a/bench.mojo +++ b/bench.mojo @@ -1,24 +1,152 @@ -import benchmark -from lightbug_http.sys.server import SysServer -from lightbug_http.python.server import PythonServer -from lightbug_http.service import TechEmpowerRouter +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.http import HTTPRequest, HTTPResponse, encode +from lightbug_http.uri import URI from tests.utils import ( TestStruct, FakeResponder, new_fake_listener, FakeServer, - getRequest, ) +alias headers = bytes( + """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 = bytes(String("I am the body of an HTTP request") * 5) +alias Request = bytes( + """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 = bytes( + "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(): + run_benchmark() + + +fn run_benchmark(): try: - var server = SysServer(tcp_keep_alive=True) - var handler = TechEmpowerRouter() - server.listen_and_serve("0.0.0.0:8080", handler) - except e: - print("Error starting server: " + e.__str__()) - return + var config = BenchConfig(warmup_iters=100) + 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.dump_report() + except: + print("failed to start benchmark") + + +var headers_struct = Headers( + Header("Content-Type", "application/json"), + Header("Content-Length", "1234"), + Header("Connection", "close"), + Header("Date", "some-datetime"), + Header("SomeHeader", "SomeValue"), +) + + +@parameter +fn lightbug_benchmark_response_encode(inout b: Bencher): + @always_inline + @parameter + fn response_encode(): + var res = HTTPResponse(body, headers=headers_struct) + _ = encode(res^) + + b.iter[response_encode]() + + +@parameter +fn lightbug_benchmark_response_parse(inout b: Bencher): + @always_inline + @parameter + fn response_parse(): + var res = Response + try: + _ = HTTPResponse.from_bytes(res^) + except: + pass + + b.iter[response_parse]() + + +@parameter +fn lightbug_benchmark_request_parse(inout b: Bencher): + @always_inline + @parameter + fn request_parse(): + var r = Request + try: + _ = HTTPRequest.from_bytes("127.0.0.1/path", 4096, r^) + except: + pass + + b.iter[request_parse]() + + +@parameter +fn lightbug_benchmark_request_encode(inout b: Bencher): + @always_inline + @parameter + fn request_encode(): + var req = HTTPRequest( + URI.parse("http://127.0.0.1:8080/some-path")[URI], + headers=headers_struct, + body=body, + ) + _ = encode(req^) + + b.iter[request_encode]() + + +@parameter +fn lightbug_benchmark_header_encode(inout b: Bencher): + @always_inline + @parameter + fn header_encode(): + var b = ByteWriter() + var h = headers_struct + h.encode_to(b) + + b.iter[header_encode]() + + +@parameter +fn lightbug_benchmark_header_parse(inout b: Bencher): + @always_inline + @parameter + fn header_parse(): + try: + var b = headers + var header = Headers() + var reader = ByteReader(b^) + _ = header.parse_raw(reader) + except: + print("failed") + + b.iter[header_parse]() fn lightbug_benchmark_server(): @@ -28,9 +156,13 @@ fn lightbug_benchmark_server(): fn lightbug_benchmark_misc() -> None: - var direct_set_report = benchmark.run[init_test_and_set_a_direct](max_iters=1) + var direct_set_report = benchmark.run[init_test_and_set_a_direct]( + max_iters=1 + ) - var recreating_set_report = benchmark.run[init_test_and_set_a_copy](max_iters=1) + var recreating_set_report = benchmark.run[init_test_and_set_a_copy]( + max_iters=1 + ) print("Direct set: ") direct_set_report.print(benchmark.Unit.ms) @@ -38,9 +170,12 @@ fn lightbug_benchmark_misc() -> None: recreating_set_report.print(benchmark.Unit.ms) +var GetRequest = HTTPRequest(URI.parse("http://127.0.0.1/path")[URI]) + + fn run_fake_server(): var handler = FakeResponder() - var listener = new_fake_listener(2, getRequest) + var listener = new_fake_listener(2, encode(GetRequest)) var server = FakeServer(listener, handler) server.serve() diff --git a/bench_server.mojo b/bench_server.mojo new file mode 100644 index 00000000..eefdba40 --- /dev/null +++ b/bench_server.mojo @@ -0,0 +1,12 @@ +from lightbug_http.sys.server import SysServer +from lightbug_http.service import TechEmpowerRouter + + +def main(): + try: + var server = SysServer(tcp_keep_alive=True) + var handler = TechEmpowerRouter() + server.listen_and_serve("0.0.0.0:8080", handler) + except e: + print("Error starting server: " + e.__str__()) + return diff --git a/client.mojo b/client.mojo index 2208c4ed..3f91c506 100644 --- a/client.mojo +++ b/client.mojo @@ -1,36 +1,33 @@ -from lightbug_http.http import HTTPRequest, encode -from lightbug_http.header import RequestHeader -from lightbug_http.uri import URI +from lightbug_http import * from lightbug_http.sys.client import MojoClient -fn test_request(inout client: MojoClient) raises -> None: - var uri = URI("http://httpbin.org/status/404") - try: - uri.parse() - except e: - print("error parsing uri: " + e.__str__()) +fn test_request(inout client: MojoClient) raises -> None: + var uri = URI.parse_raises("http://httpbin.org/status/404") + var headers = Header("Host", "httpbin.org") - var request = HTTPRequest(uri) - var response = client.do(request) + var request = HTTPRequest(uri, headers) + var response = client.do(request^) # print status code - print("Response:", response.header.status_code()) - - # print raw headers - # print("Headers:", response.header.headers()) + print("Response:", response.status_code) # print parsed headers (only some are parsed for now) - print("Content-Type:", String(response.header.content_type())) - print("Content-Length", response.header.content_length()) - print("Server:", String(response.header.server())) + print("Content-Type:", response.headers["Content-Type"]) + print("Content-Length", response.headers["Content-Length"]) + print("Server:", to_string(response.headers["Server"])) - print("Is connection set to connection-close? ", response.header.connection_close()) + print( + "Is connection set to connection-close? ", response.connection_close() + ) # print body - print(String(response.get_body_bytes())) + print(to_string(response.body_raw)) -fn main() raises -> None: - var client = MojoClient() - test_request(client) \ No newline at end of file +fn main() -> None: + try: + var client = MojoClient() + test_request(client) + except e: + print(e) diff --git a/external/__init__.mojo b/external/__init__.mojo deleted file mode 100644 index e69de29b..00000000 diff --git a/external/gojo/__init__.mojo b/external/gojo/__init__.mojo deleted file mode 100644 index e69de29b..00000000 diff --git a/external/gojo/bufio/__init__.mojo b/external/gojo/bufio/__init__.mojo deleted file mode 100644 index c501992e..00000000 --- a/external/gojo/bufio/__init__.mojo +++ /dev/null @@ -1,2 +0,0 @@ -from .bufio import Reader, Writer, ReadWriter -from .scan import Scanner, scan_words, scan_bytes, scan_lines diff --git a/external/gojo/bufio/bufio.mojo b/external/gojo/bufio/bufio.mojo deleted file mode 100644 index b1d0dee5..00000000 --- a/external/gojo/bufio/bufio.mojo +++ /dev/null @@ -1,938 +0,0 @@ -import ..io -from ..builtins import copy, panic -from ..builtins.bytes import UInt8, index_byte -from ..strings import StringBuilder - -alias MIN_READ_BUFFER_SIZE = 16 -alias MAX_CONSECUTIVE_EMPTY_READS = 100 -alias DEFAULT_BUF_SIZE = 8200 - -alias ERR_INVALID_UNREAD_BYTE = "bufio: invalid use of unread_byte" -alias ERR_INVALID_UNREAD_RUNE = "bufio: invalid use of unread_rune" -alias ERR_BUFFER_FULL = "bufio: buffer full" -alias ERR_NEGATIVE_COUNT = "bufio: negative count" -alias ERR_NEGATIVE_READ = "bufio: reader returned negative count from Read" -alias ERR_NEGATIVE_WRITE = "bufio: writer returned negative count from write" - - -# buffered input -struct Reader[R: io.Reader](Sized, io.Reader, io.ByteReader, io.ByteScanner): - """Implements buffering for an io.Reader object.""" - - var buf: List[UInt8] - var reader: R # reader provided by the client - var read_pos: Int - var write_pos: Int # buf read and write positions - var last_byte: Int # last byte read for unread_byte; -1 means invalid - var last_rune_size: Int # size of last rune read for unread_rune; -1 means invalid - var err: Error - - fn __init__( - inout self, - owned reader: R, - buf: List[UInt8] = List[UInt8](capacity=DEFAULT_BUF_SIZE), - read_pos: Int = 0, - write_pos: Int = 0, - last_byte: Int = -1, - last_rune_size: Int = -1, - ): - self.buf = buf - self.reader = reader^ - self.read_pos = read_pos - self.write_pos = write_pos - self.last_byte = last_byte - self.last_rune_size = last_rune_size - self.err = Error() - - fn __moveinit__(inout self, owned existing: Self): - self.buf = existing.buf^ - self.reader = existing.reader^ - self.read_pos = existing.read_pos - self.write_pos = existing.write_pos - self.last_byte = existing.last_byte - self.last_rune_size = existing.last_rune_size - self.err = existing.err^ - - # size returns the size of the underlying buffer in bytes. - fn __len__(self) -> Int: - return len(self.buf) - - # reset discards any buffered data, resets all state, and switches - # the buffered reader to read from r. - # Calling reset on the zero value of [Reader] initializes the internal buffer - # to the default size. - # Calling self.reset(b) (that is, resetting a [Reader] to itself) does nothing. - # fn reset[R: io.Reader](self, reader: R): - # # If a Reader r is passed to NewReader, NewReader will return r. - # # Different layers of code may do that, and then later pass r - # # to reset. Avoid infinite recursion in that case. - # if self == reader: - # return - - # # if self.buf == nil: - # # self.buf = make(List[UInt8], DEFAULT_BUF_SIZE) - - # self.reset(self.buf, r) - - fn reset(inout self, buf: List[UInt8], owned reader: R): - self = Reader[R]( - buf=buf, - reader=reader^, - last_byte=-1, - last_rune_size=-1, - ) - - fn fill(inout self): - """Reads a new chunk into the buffer.""" - # Slide existing data to beginning. - if self.read_pos > 0: - var current_capacity = self.buf.capacity - self.buf = self.buf[self.read_pos : self.write_pos] - self.buf.reserve(current_capacity) - self.write_pos -= self.read_pos - self.read_pos = 0 - - # Compares to the length of the entire List[UInt8] object, including 0 initialized positions. - # IE. var b = List[UInt8](capacity=8200), then trying to write at b[8200] and onwards will fail. - if self.write_pos >= self.buf.capacity: - panic("bufio.Reader: tried to fill full buffer") - - # Read new data: try a limited number of times. - var i: Int = MAX_CONSECUTIVE_EMPTY_READS - while i > 0: - # TODO: Using temp until slicing can return a Reference - var temp = List[UInt8](capacity=DEFAULT_BUF_SIZE) - var bytes_read: Int - var err: Error - bytes_read, err = self.reader.read(temp) - if bytes_read < 0: - panic(ERR_NEGATIVE_READ) - - bytes_read = copy(self.buf, temp, self.write_pos) - self.write_pos += bytes_read - - if err: - self.err = err - return - - if bytes_read > 0: - return - - i -= 1 - - self.err = Error(io.ERR_NO_PROGRESS) - - fn read_error(inout self) -> Error: - if not self.err: - return Error() - - var err = self.err - self.err = Error() - return err - - fn peek(inout self, number_of_bytes: Int) -> (List[UInt8], Error): - """Returns the next n bytes without advancing the reader. The bytes stop - being valid at the next read call. If Peek returns fewer than n bytes, it - also returns an error explaining why the read is short. The error is - [ERR_BUFFER_FULL] if number_of_bytes is larger than b's buffer size. - - Calling Peek prevents a [Reader.unread_byte] or [Reader.unread_rune] call from succeeding - until the next read operation. - - Args: - number_of_bytes: The number of bytes to peek. - """ - if number_of_bytes < 0: - return List[UInt8](), Error(ERR_NEGATIVE_COUNT) - - self.last_byte = -1 - self.last_rune_size = -1 - - while self.write_pos - self.read_pos < number_of_bytes and self.write_pos - self.read_pos < self.buf.capacity: - self.fill() # self.write_pos-self.read_pos < self.buf.capacity => buffer is not full - - if number_of_bytes > self.buf.capacity: - return self.buf[self.read_pos : self.write_pos], Error(ERR_BUFFER_FULL) - - # 0 <= n <= self.buf.capacity - var err = Error() - var available_space = self.write_pos - self.read_pos - if available_space < number_of_bytes: - # not enough data in buffer - err = self.read_error() - if not err: - err = Error(ERR_BUFFER_FULL) - - return self.buf[self.read_pos : self.read_pos + number_of_bytes], err - - fn discard(inout self, number_of_bytes: Int) -> (Int, Error): - """Discard skips the next n bytes, returning the number of bytes discarded. - - If Discard skips fewer than n bytes, it also returns an error. - If 0 <= number_of_bytes <= self.buffered(), Discard is guaranteed to succeed without - reading from the underlying io.Reader. - """ - if number_of_bytes < 0: - return 0, Error(ERR_NEGATIVE_COUNT) - - if number_of_bytes == 0: - return 0, Error() - - self.last_byte = -1 - self.last_rune_size = -1 - - var remain = number_of_bytes - while True: - var skip = self.buffered() - if skip == 0: - self.fill() - skip = self.buffered() - - if skip > remain: - skip = remain - - self.read_pos += skip - remain -= skip - if remain == 0: - return number_of_bytes, Error() - - fn read(inout self, inout dest: List[UInt8]) -> (Int, Error): - """Reads data into dest. - It returns the number of bytes read into dest. - The bytes are taken from at most one Read on the underlying [Reader], - hence n may be less than len(src). - To read exactly len(src) bytes, use io.ReadFull(b, src). - If the underlying [Reader] can return a non-zero count with io.EOF, - then this Read method can do so as well; see the [io.Reader] docs.""" - var space_available = dest.capacity - len(dest) - if space_available == 0: - if self.buffered() > 0: - return 0, Error() - return 0, self.read_error() - - var bytes_read: Int = 0 - if self.read_pos == self.write_pos: - if space_available >= len(self.buf): - # Large read, empty buffer. - # Read directly into dest to avoid copy. - var bytes_read: Int - var err: Error - bytes_read, err = self.reader.read(dest) - - self.err = err - if bytes_read < 0: - panic(ERR_NEGATIVE_READ) - - if bytes_read > 0: - self.last_byte = int(dest[bytes_read - 1]) - self.last_rune_size = -1 - - return bytes_read, self.read_error() - - # One read. - # Do not use self.fill, which will loop. - self.read_pos = 0 - self.write_pos = 0 - var bytes_read: Int - var err: Error - bytes_read, err = self.reader.read(self.buf) - - if bytes_read < 0: - panic(ERR_NEGATIVE_READ) - - if bytes_read == 0: - return 0, self.read_error() - - self.write_pos += bytes_read - - # copy as much as we can - # Note: if the slice panics here, it is probably because - # the underlying reader returned a bad count. See issue 49795. - bytes_read = copy(dest, self.buf[self.read_pos : self.write_pos]) - self.read_pos += bytes_read - self.last_byte = int(self.buf[self.read_pos - 1]) - self.last_rune_size = -1 - return bytes_read, Error() - - fn read_byte(inout self) -> (UInt8, Error): - """Reads and returns a single byte from the internal buffer. If no byte is available, returns an error.""" - self.last_rune_size = -1 - while self.read_pos == self.write_pos: - if self.err: - return UInt8(0), self.read_error() - self.fill() # buffer is empty - - var c = self.buf[self.read_pos] - self.read_pos += 1 - self.last_byte = int(c) - return c, Error() - - fn unread_byte(inout self) -> Error: - """Unreads the last byte. Only the most recently read byte can be unread. - - unread_byte returns an error if the most recent method called on the - [Reader] was not a read operation. Notably, [Reader.peek], [Reader.discard], and [Reader.write_to] are not - considered read operations. - """ - if self.last_byte < 0 or self.read_pos == 0 and self.write_pos > 0: - return Error(ERR_INVALID_UNREAD_BYTE) - - # self.read_pos > 0 or self.write_pos == 0 - if self.read_pos > 0: - self.read_pos -= 1 - else: - # self.read_pos == 0 and self.write_pos == 0 - self.write_pos = 1 - - self.buf[self.read_pos] = self.last_byte - self.last_byte = -1 - self.last_rune_size = -1 - return Error() - - # # read_rune reads a single UTF-8 encoded Unicode character and returns the - # # rune and its size in bytes. If the encoded rune is invalid, it consumes one byte - # # and returns unicode.ReplacementChar (U+FFFD) with a size of 1. - # fn read_rune(inout self) (r rune, size int, err error): - # for self.read_pos+utf8.UTFMax > self.write_pos and !utf8.FullRune(self.buf[self.read_pos:self.write_pos]) and self.err == nil and self.write_pos-self.read_pos < self.buf.capacity: - # self.fill() # self.write_pos-self.read_pos < len(buf) => buffer is not full - - # self.last_rune_size = -1 - # if self.read_pos == self.write_pos: - # return 0, 0, self.read_poseadErr() - - # r, size = rune(self.buf[self.read_pos]), 1 - # if r >= utf8.RuneSelf: - # r, size = utf8.DecodeRune(self.buf[self.read_pos:self.write_pos]) - - # self.read_pos += size - # self.last_byte = int(self.buf[self.read_pos-1]) - # self.last_rune_size = size - # return r, size, nil - - # # unread_rune unreads the last rune. If the most recent method called on - # # the [Reader] was not a [Reader.read_rune], [Reader.unread_rune] returns an error. (In this - # # regard it is stricter than [Reader.unread_byte], which will unread the last byte - # # from any read operation.) - # fn unread_rune() error: - # if self.last_rune_size < 0 or self.read_pos < self.last_rune_size: - # return ERR_INVALID_UNREAD_RUNE - - # self.read_pos -= self.last_rune_size - # self.last_byte = -1 - # self.last_rune_size = -1 - # return nil - - fn buffered(self) -> Int: - """Returns the number of bytes that can be read from the current buffer. - - Returns: - The number of bytes that can be read from the current buffer. - """ - return self.write_pos - self.read_pos - - fn read_slice(inout self, delim: UInt8) -> (List[UInt8], Error): - """Reads until the first occurrence of delim in the input, - returning a slice pointing at the bytes in the buffer. It includes the first occurrence of the delimiter. - The bytes stop being valid at the next read. - If read_slice encounters an error before finding a delimiter, - it returns all the data in the buffer and the error itself (often io.EOF). - read_slice fails with error [ERR_BUFFER_FULL] if the buffer fills without a delim. - Because the data returned from read_slice will be overwritten - by the next I/O operation, most clients should use - [Reader.read_bytes] or read_string instead. - read_slice returns err != nil if and only if line does not end in delim. - - Args: - delim: The delimiter to search for. - - Returns: - The List[UInt8] from the internal buffer. - """ - var err = Error() - var s = 0 # search start index - var line: List[UInt8] = List[UInt8](capacity=DEFAULT_BUF_SIZE) - while True: - # Search buffer. - var i = index_byte(self.buf[self.read_pos + s : self.write_pos], delim) - if i >= 0: - i += s - line = self.buf[self.read_pos : self.read_pos + i + 1] - self.read_pos += i + 1 - break - - # Pending error? - if self.err: - line = self.buf[self.read_pos : self.write_pos] - self.read_pos = self.write_pos - err = self.read_error() - break - - # Buffer full? - if self.buffered() >= self.buf.capacity: - self.read_pos = self.write_pos - line = self.buf - err = Error(ERR_BUFFER_FULL) - break - - s = self.write_pos - self.read_pos # do not rescan area we scanned before - self.fill() # buffer is not full - - # Handle last byte, if any. - var i = len(line) - 1 - if i >= 0: - self.last_byte = int(line[i]) - self.last_rune_size = -1 - - return line, err - - fn read_line(inout self) raises -> (List[UInt8], Bool): - """Low-level line-reading primitive. Most callers should use - [Reader.read_bytes]('\n') or [Reader.read_string]('\n') instead or use a [Scanner]. - - read_line tries to return a single line, not including the end-of-line bytes. - If the line was too long for the buffer then isPrefix is set and the - beginning of the line is returned. The rest of the line will be returned - from future calls. isPrefix will be false when returning the last fragment - of the line. The returned buffer is only valid until the next call to - read_line. read_line either returns a non-nil line or it returns an error, - never both. - - The text returned from read_line does not include the line end ("\r\n" or "\n"). - No indication or error is given if the input ends without a final line end. - Calling [Reader.unread_byte] after read_line will always unread the last byte read - (possibly a character belonging to the line end) even if that byte is not - part of the line returned by read_line. - """ - var line: List[UInt8] - var err: Error - line, err = self.read_slice(ord("\n")) - - if err and str(err) == ERR_BUFFER_FULL: - # Handle the case where "\r\n" straddles the buffer. - if len(line) > 0 and line[len(line) - 1] == ord("\r"): - # Put the '\r' back on buf and drop it from line. - # Let the next call to read_line check for "\r\n". - if self.read_pos == 0: - # should be unreachable - raise Error("bufio: tried to rewind past start of buffer") - - self.read_pos -= 1 - line = line[: len(line) - 1] - return line, True - - if len(line) == 0: - return line, False - - if line[len(line) - 1] == ord("\n"): - var drop = 1 - if len(line) > 1 and line[len(line) - 2] == ord("\r"): - drop = 2 - - line = line[: len(line) - drop] - - return line, False - - fn collect_fragments(inout self, delim: UInt8) -> (List[List[UInt8]], List[UInt8], Int, Error): - """Reads until the first occurrence of delim in the input. It - returns (slice of full buffers, remaining bytes before delim, total number - of bytes in the combined first two elements, error). - - Args: - delim: The delimiter to search for. - """ - # Use read_slice to look for delim, accumulating full buffers. - var err = Error() - var full_buffers = List[List[UInt8]]() - var total_len = 0 - var frag = List[UInt8](capacity=8200) - while True: - frag, err = self.read_slice(delim) - if not err: - break - - var read_slice_error = err - if str(read_slice_error) != ERR_BUFFER_FULL: - err = read_slice_error - break - - # Make a copy of the buffer. - var buf = List[UInt8](frag) - full_buffers.append(buf) - total_len += len(buf) - - total_len += len(frag) - return full_buffers, frag, total_len, err - - fn read_bytes(inout self, delim: UInt8) -> (List[UInt8], Error): - """Reads until the first occurrence of delim in the input, - returning a slice containing the data up to and including the delimiter. - If read_bytes encounters an error before finding a delimiter, - it returns the data read before the error and the error itself (often io.EOF). - read_bytes returns err != nil if and only if the returned data does not end in - delim. - For simple uses, a Scanner may be more convenient. - - Args: - delim: The delimiter to search for. - - Returns: - The List[UInt8] from the internal buffer. - """ - var full: List[List[UInt8]] - var frag: List[UInt8] - var n: Int - var err: Error - full, frag, n, err = self.collect_fragments(delim) - - # Allocate new buffer to hold the full pieces and the fragment. - var buf = List[UInt8](capacity=n) - n = 0 - - # copy full pieces and fragment in. - for i in range(len(full)): - var buffer = full[i] - n += copy(buf, buffer, n) - - _ = copy(buf, frag, n) - - return buf, err - - fn read_string(inout self, delim: UInt8) -> (String, Error): - """Reads until the first occurrence of delim in the input, - returning a string containing the data up to and including the delimiter. - If read_string encounters an error before finding a delimiter, - it returns the data read before the error and the error itself (often io.EOF). - read_string returns err != nil if and only if the returned data does not end in - delim. - For simple uses, a Scanner may be more convenient. - - Args: - delim: The delimiter to search for. - - Returns: - The String from the internal buffer. - """ - var full: List[List[UInt8]] - var frag: List[UInt8] - var n: Int - var err: Error - full, frag, n, err = self.collect_fragments(delim) - - # Allocate new buffer to hold the full pieces and the fragment. - var buf = StringBuilder(capacity=n) - - # copy full pieces and fragment in. - for i in range(len(full)): - var buffer = full[i] - _ = buf.write(Span(buffer)) - - _ = buf.write(Span(frag)) - return str(buf), err - - # fn write_to[W: io.Writer](inout self, inout writer: W) -> (Int64, Error): - # """Writes the internal buffer to the writer. This may make multiple calls to the [Reader.Read] method of the underlying [Reader]. - # If the underlying reader supports the [Reader.WriteTo] method, - # this calls the underlying [Reader.WriteTo] without buffering. - # write_to implements io.WriterTo. - - # Args: - # writer: The writer to write to. - - # Returns: - # The number of bytes written. - # """ - # self.last_byte = -1 - # self.last_rune_size = -1 - - # var bytes_written: Int64 - # var err: Error - # bytes_written, err = self.write_buf(writer) - # if err: - # return bytes_written, err - - # # internal buffer not full, fill before writing to writer - # if (self.write_pos - self.read_pos) < self.buf.capacity: - # self.fill() - - # while self.read_pos < self.write_pos: - # # self.read_pos < self.write_pos => buffer is not empty - # var bw: Int64 - # var err: Error - # bw, err = self.write_buf(writer) - # bytes_written += bw - - # self.fill() # buffer is empty - - # return bytes_written, Error() - - # fn write_buf[W: io.Writer](inout self, inout writer: W) -> (Int64, Error): - # """Writes the [Reader]'s buffer to the writer. - - # Args: - # writer: The writer to write to. - - # Returns: - # The number of bytes written. - # """ - # # Nothing to write - # if self.read_pos == self.write_pos: - # return Int64(0), Error() - - # # Write the buffer to the writer, if we hit EOF it's fine. That's not a failure condition. - # var bytes_written: Int - # var err: Error - # var buf_to_write = self.buf[self.read_pos : self.write_pos] - # bytes_written, err = writer.write(Span(buf_to_write)) - # if err: - # return Int64(bytes_written), err - - # if bytes_written < 0: - # panic(ERR_NEGATIVE_WRITE) - - # self.read_pos += bytes_written - # return Int64(bytes_written), Error() - - -# fn new_reader_size[R: io.Reader](owned reader: R, size: Int) -> Reader[R]: -# """Returns a new [Reader] whose buffer has at least the specified -# size. If the argument io.Reader is already a [Reader] with large enough -# size, it returns the underlying [Reader]. - -# Args: -# reader: The reader to read from. -# size: The size of the buffer. - -# Returns: -# The new [Reader]. -# """ -# # # Is it already a Reader? -# # b, ok := rd.(*Reader) -# # if ok and self.buf.capacity >= size: -# # return b - -# var r = Reader(reader ^) -# r.reset(List[UInt8](capacity=max(size, MIN_READ_BUFFER_SIZE)), reader ^) -# return r - - -# fn new_reader[R: io.Reader](reader: R) -> Reader[R]: -# """Returns a new [Reader] whose buffer has the default size. - -# Args: -# reader: The reader to read from. - -# Returns: -# The new [Reader]. -# """ -# return new_reader_size(reader, DEFAULT_BUF_SIZE) - - -# buffered output -# TODO: Reader and Writer maybe should not take ownership of the underlying reader/writer? Seems okay for now. -struct Writer[W: io.Writer](Sized, io.Writer, io.ByteWriter, io.StringWriter): - """Implements buffering for an [io.Writer] object. - # If an error occurs writing to a [Writer], no more data will be - # accepted and all subsequent writes, and [Writer.flush], will return the error. - # After all data has been written, the client should call the - # [Writer.flush] method to guarantee all data has been forwarded to - # the underlying [io.Writer].""" - - var buf: List[UInt8] - var bytes_written: Int - var writer: W - var err: Error - - fn __init__( - inout self, - owned writer: W, - buf: List[UInt8] = List[UInt8](capacity=DEFAULT_BUF_SIZE), - bytes_written: Int = 0, - ): - self.buf = buf - self.bytes_written = bytes_written - self.writer = writer^ - self.err = Error() - - fn __moveinit__(inout self, owned existing: Self): - self.buf = existing.buf^ - self.bytes_written = existing.bytes_written - self.writer = existing.writer^ - self.err = existing.err^ - - fn __len__(self) -> Int: - """Returns the size of the underlying buffer in bytes.""" - return len(self.buf) - - fn reset(inout self, owned writer: W): - """Discards any unflushed buffered data, clears any error, and - resets b to write its output to w. - Calling reset on the zero value of [Writer] initializes the internal buffer - to the default size. - Calling w.reset(w) (that is, resetting a [Writer] to itself) does nothing. - - Args: - writer: The writer to write to. - """ - # # If a Writer w is passed to new_writer, new_writer will return w. - # # Different layers of code may do that, and then later pass w - # # to reset. Avoid infinite recursion in that case. - # if self == writer: - # return - - # if self.buf == nil: - # self.buf = make(List[UInt8], DEFAULT_BUF_SIZE) - - self.err = Error() - self.bytes_written = 0 - self.writer = writer^ - - fn flush(inout self) -> Error: - """Writes any buffered data to the underlying [io.Writer].""" - # Prior to attempting to flush, check if there's a pre-existing error or if there's nothing to flush. - var err = Error() - if self.err: - return self.err - if self.bytes_written == 0: - return err - - var bytes_written: Int = 0 - bytes_written, err = self.writer.write(Span(self.buf[0 : self.bytes_written])) - - # If the write was short, set a short write error and try to shift up the remaining bytes. - if bytes_written < self.bytes_written and not err: - err = Error(io.ERR_SHORT_WRITE) - - if err: - if bytes_written > 0 and bytes_written < self.bytes_written: - _ = copy(self.buf, self.buf[bytes_written : self.bytes_written]) - - self.bytes_written -= bytes_written - self.err = err - return err - - # Reset the buffer - self.buf = List[UInt8](capacity=self.buf.capacity) - self.bytes_written = 0 - return err - - fn available(self) -> Int: - """Returns how many bytes are unused in the buffer.""" - return self.buf.capacity - len(self.buf) - - fn available_buffer(self) raises -> List[UInt8]: - """Returns an empty buffer with self.available() capacity. - This buffer is intended to be appended to and - passed to an immediately succeeding [Writer.write] call. - The buffer is only valid until the next write operation on self. - - Returns: - An empty buffer with self.available() capacity. - """ - return self.buf[self.bytes_written :][:0] - - fn buffered(self) -> Int: - """Returns the number of bytes that have been written into the current buffer. - - Returns: - The number of bytes that have been written into the current buffer. - """ - return self.bytes_written - - fn write(inout self, src: Span[UInt8]) -> (Int, Error): - """Writes the contents of src into the buffer. - It returns the number of bytes written. - If nn < len(src), it also returns an error explaining - why the write is short. - - Args: - src: The bytes to write. - - Returns: - The number of bytes written. - """ - var total_bytes_written: Int = 0 - var src_copy = src - var err = Error() - while len(src_copy) > self.available() and not self.err: - var bytes_written: Int = 0 - if self.buffered() == 0: - # Large write, empty buffer. - # write directly from p to avoid copy. - bytes_written, err = self.writer.write(src_copy) - self.err = err - else: - bytes_written = copy(self.buf, src_copy, self.bytes_written) - self.bytes_written += bytes_written - _ = self.flush() - - total_bytes_written += bytes_written - src_copy = src_copy[bytes_written : len(src_copy)] - - if self.err: - return total_bytes_written, self.err - - var n = copy(self.buf, src_copy, self.bytes_written) - self.bytes_written += n - total_bytes_written += n - return total_bytes_written, err - - fn write_byte(inout self, src: UInt8) -> (Int, Error): - """Writes a single byte to the internal buffer. - - Args: - src: The byte to write. - """ - if self.err: - return 0, self.err - # If buffer is full, flush to the underlying writer. - var err = self.flush() - if self.available() <= 0 and err: - return 0, self.err - - self.buf.append(src) - self.bytes_written += 1 - - return 1, Error() - - # # WriteRune writes a single Unicode code point, returning - # # the number of bytes written and any error. - # fn WriteRune(r rune) (size int, err error): - # # Compare as uint32 to correctly handle negative runes. - # if uint32(r) < utf8.RuneSelf: - # err = self.write_posriteByte(byte(r)) - # if err != nil: - # return 0, err - - # return 1, nil - - # if self.err != nil: - # return 0, self.err - - # n := self.available() - # if n < utf8.UTFMax: - # if self.flush(); self.err != nil: - # return 0, self.err - - # n = self.available() - # if n < utf8.UTFMax: - # # Can only happen if buffer is silly small. - # return self.write_posriteString(string(r)) - - # size = utf8.EncodeRune(self.buf[self.bytes_written:], r) - # self.bytes_written += size - # return size, nil - - fn write_string(inout self, src: String) -> (Int, Error): - """Writes a string to the internal buffer. - It returns the number of bytes written. - If the count is less than len(s), it also returns an error explaining - why the write is short. - - Args: - src: The string to write. - - Returns: - The number of bytes written. - """ - return self.write(src.as_bytes_slice()) - - fn read_from[R: io.Reader](inout self, inout reader: R) -> (Int64, Error): - """Implements [io.ReaderFrom]. If the underlying writer - supports the read_from method, this calls the underlying read_from. - If there is buffered data and an underlying read_from, this fills - the buffer and writes it before calling read_from. - - Args: - reader: The reader to read from. - - Returns: - The number of bytes read. - """ - if self.err: - return Int64(0), self.err - - var bytes_read: Int = 0 - var total_bytes_written: Int64 = 0 - var err = Error() - while True: - if self.available() == 0: - var err = self.flush() - if err: - return total_bytes_written, err - - var nr = 0 - while nr < MAX_CONSECUTIVE_EMPTY_READS: - # TODO: should really be using a slice that returns refs and not a copy. - # Read into remaining unused space in the buffer. We need to reserve capacity for the slice otherwise read will never hit EOF. - var sl = self.buf[self.bytes_written : len(self.buf)] - sl.reserve(self.buf.capacity) - bytes_read, err = reader.read(sl) - if bytes_read > 0: - bytes_read = copy(self.buf, sl, self.bytes_written) - - if bytes_read != 0 or err: - break - nr += 1 - - if nr == MAX_CONSECUTIVE_EMPTY_READS: - return Int64(bytes_read), Error(io.ERR_NO_PROGRESS) - - self.bytes_written += bytes_read - total_bytes_written += Int64(bytes_read) - if err: - break - - if err and str(err) == io.EOF: - # If we filled the buffer exactly, flush preemptively. - if self.available() == 0: - err = self.flush() - else: - err = Error() - - return total_bytes_written, Error() - - -fn new_writer_size[W: io.Writer](owned writer: W, size: Int) -> Writer[W]: - """Returns a new [Writer] whose buffer has at least the specified - size. If the argument io.Writer is already a [Writer] with large enough - size, it returns the underlying [Writer].""" - # Is it already a Writer? - # b, ok := w.(*Writer) - # if ok and self.buf.capacity >= size: - # return b - - var buf_size = size - if buf_size <= 0: - buf_size = DEFAULT_BUF_SIZE - - return Writer[W]( - buf=List[UInt8](capacity=size), - writer=writer^, - bytes_written=0, - ) - - -fn new_writer[W: io.Writer](owned writer: W) -> Writer[W]: - """Returns a new [Writer] whose buffer has the default size. - # If the argument io.Writer is already a [Writer] with large enough buffer size, - # it returns the underlying [Writer].""" - return new_writer_size[W](writer^, DEFAULT_BUF_SIZE) - - -# buffered input and output -struct ReadWriter[R: io.Reader, W: io.Writer](): - """ReadWriter stores pointers to a [Reader] and a [Writer]. - It implements [io.ReadWriter].""" - - var reader: R - var writer: W - - fn __init__(inout self, owned reader: R, owned writer: W): - self.reader = reader^ - self.writer = writer^ - - -# new_read_writer -fn new_read_writer[R: io.Reader, W: io.Writer](owned reader: R, owned writer: W) -> ReadWriter[R, W]: - """Allocates a new [ReadWriter] that dispatches to r and w.""" - return ReadWriter[R, W](reader^, writer^) diff --git a/external/gojo/bufio/scan.mojo b/external/gojo/bufio/scan.mojo deleted file mode 100644 index 046cc87b..00000000 --- a/external/gojo/bufio/scan.mojo +++ /dev/null @@ -1,458 +0,0 @@ -import math -from collections import Optional -import ..io -from ..builtins import copy, panic, Error -from ..builtins.bytes import Byte, index_byte -from .bufio import MAX_CONSECUTIVE_EMPTY_READS - - -alias MAX_INT: Int = 2147483647 - - -struct Scanner[R: io.Reader](): - """Scanner provides a convenient Interface for reading data such as - a file of newline-delimited lines of text. Successive calls to - the [Scanner.Scan] method will step through the 'tokens' of a file, skipping - the bytes between the tokens. The specification of a token is - defined by a split function of type [SplitFunction]; the default split - function breaks the input Into lines with line termination stripped. [Scanner.split] - fntions are defined in this package for scanning a file Into - lines, bytes, UTF-8-encoded runes, and space-delimited words. The - client may instead provide a custom split function. - - Scanning stops unrecoverably at EOF, the first I/O error, or a token too - large to fit in the [Scanner.buffer]. When a scan stops, the reader may have - advanced arbitrarily far past the last token. Programs that need more - control over error handling or large tokens, or must run sequential scans - on a reader, should use [bufio.Reader] instead.""" - - var reader: R # The reader provided by the client. - var split: SplitFunction # The function to split the tokens. - var max_token_size: Int # Maximum size of a token; modified by tests. - var token: List[Byte] # Last token returned by split. - var buf: List[Byte] # buffer used as argument to split. - var start: Int # First non-processed byte in buf. - var end: Int # End of data in buf. - var empties: Int # Count of successive empty tokens. - var scan_called: Bool # Scan has been called; buffer is in use. - var done: Bool # Scan has finished. - var err: Error - - fn __init__( - inout self, - owned reader: R, - split: SplitFunction = scan_lines, - max_token_size: Int = MAX_SCAN_TOKEN_SIZE, - token: List[Byte] = List[Byte](capacity=io.BUFFER_SIZE), - buf: List[Byte] = List[Byte](capacity=io.BUFFER_SIZE), - start: Int = 0, - end: Int = 0, - empties: Int = 0, - scan_called: Bool = False, - done: Bool = False, - ): - self.reader = reader^ - self.split = split - self.max_token_size = max_token_size - self.token = token - self.buf = buf - self.start = start - self.end = end - self.empties = empties - self.scan_called = scan_called - self.done = done - self.err = Error() - - fn current_token_as_bytes(self) -> List[Byte]: - """Returns the most recent token generated by a call to [Scanner.Scan]. - The underlying array may point to data that will be overwritten - by a subsequent call to Scan. It does no allocation. - """ - return self.token - - fn current_token(self) -> String: - """Returns the most recent token generated by a call to [Scanner.Scan] - as a newly allocated string holding its bytes.""" - return String(self.token) - - fn scan(inout self) raises -> Bool: - """Advances the [Scanner] to the next token, which will then be - available through the [Scanner.current_token_as_bytes] or [Scanner.current_token] method. - It returns False when there are no more tokens, either by reaching the end of the input or an error. - After Scan returns False, the [Scanner.Err] method will return any error that - occurred during scanning, except if it was [io.EOF], [Scanner.Err]. - Scan raises an Error if the split function returns too many empty - tokens without advancing the input. This is a common error mode for - scanners. - """ - if self.done: - return False - - self.scan_called = True - # Loop until we have a token. - while True: - # See if we can get a token with what we already have. - # If we've run out of data but have an error, give the split function - # a chance to recover any remaining, possibly empty token. - if (self.end > self.start) or self.err: - var advance: Int - var token = List[Byte](capacity=io.BUFFER_SIZE) - var err = Error() - var at_eof = False - if self.err: - at_eof = True - advance, token, err = self.split(self.buf[self.start : self.end], at_eof) - if err: - if str(err) == str(ERR_FINAL_TOKEN): - self.token = token - self.done = True - # When token is not nil, it means the scanning stops - # with a trailing token, and thus the return value - # should be True to indicate the existence of the token. - return len(token) != 0 - - self.set_err(err) - return False - - if not self.advance(advance): - return False - - self.token = token - if len(token) != 0: - if not self.err or advance > 0: - self.empties = 0 - else: - # Returning tokens not advancing input at EOF. - self.empties += 1 - if self.empties > MAX_CONSECUTIVE_EMPTY_READS: - panic("bufio.Scan: too many empty tokens without progressing") - - return True - - # We cannot generate a token with what we are holding. - # If we've already hit EOF or an I/O error, we are done. - if self.err: - # Shut it down. - self.start = 0 - self.end = 0 - return False - - # Must read more data. - # First, shift data to beginning of buffer if there's lots of empty space - # or space is needed. - if self.start > 0 and (self.end == len(self.buf) or self.start > int(len(self.buf) / 2)): - _ = copy(self.buf, self.buf[self.start : self.end]) - self.end -= self.start - self.start = 0 - - # Is the buffer full? If so, resize. - if self.end == len(self.buf): - # Guarantee no overflow in the multiplication below. - if len(self.buf) >= self.max_token_size or len(self.buf) > int(MAX_INT / 2): - self.set_err(Error(str(ERR_TOO_LONG))) - return False - - var new_size = len(self.buf) * 2 - if new_size == 0: - new_size = START_BUF_SIZE - - # Make a new List[Byte] buffer and copy the elements in - new_size = min(new_size, self.max_token_size) - var new_buf = List[Byte](capacity=new_size) - _ = copy(new_buf, self.buf[self.start : self.end]) - self.buf = new_buf - self.end -= self.start - self.start = 0 - - # Finally we can read some input. Make sure we don't get stuck with - # a misbehaving Reader. Officially we don't need to do this, but let's - # be extra careful: Scanner is for safe, simple jobs. - var loop = 0 - while True: - var bytes_read: Int - var sl = self.buf[self.end : len(self.buf)] - var err: Error - - # Catch any reader errors and set the internal error field to that err instead of bubbling it up. - bytes_read, err = self.reader.read(sl) - _ = copy(self.buf, sl, self.end) - if bytes_read < 0 or len(self.buf) - self.end < bytes_read: - self.set_err(Error(str(ERR_BAD_READ_COUNT))) - break - - self.end += bytes_read - if err: - self.set_err(err) - break - - if bytes_read > 0: - self.empties = 0 - break - - loop += 1 - if loop > MAX_CONSECUTIVE_EMPTY_READS: - self.set_err(Error(io.ERR_NO_PROGRESS)) - break - - fn set_err(inout self, err: Error): - """Set the internal error field to the provided error. - - Args: - err: The error to set. - """ - if self.err: - var value = str(self.err) - if value == "" or value == io.EOF: - self.err = err - else: - self.err = err - - fn advance(inout self, n: Int) -> Bool: - """Consumes n bytes of the buffer. It reports whether the advance was legal. - - Args: - n: The number of bytes to advance the buffer by. - - Returns: - True if the advance was legal, False otherwise. - """ - if n < 0: - self.set_err(Error(str(ERR_NEGATIVE_ADVANCE))) - return False - - if n > self.end - self.start: - self.set_err(Error(str(ERR_ADVANCE_TOO_FAR))) - return False - - self.start += n - return True - - fn buffer(inout self, buf: List[Byte], max: Int) raises: - """Sets the initial buffer to use when scanning - and the maximum size of buffer that may be allocated during scanning. - The maximum token size must be less than the larger of max and cap(buf). - If max <= cap(buf), [Scanner.Scan] will use this buffer only and do no allocation. - - By default, [Scanner.Scan] uses an Internal buffer and sets the - maximum token size to [MAX_SCAN_TOKEN_SIZE]. - - buffer raises an Error if it is called after scanning has started. - - Args: - buf: The buffer to use when scanning. - max: The maximum size of buffer that may be allocated during scanning. - - Raises: - Error: If called after scanning has started. - """ - if self.scan_called: - raise Error("buffer called after Scan") - - # self.buf = buf[0:buf.capacity()] - self.max_token_size = max - - # # split sets the split function for the [Scanner]. - # # The default split function is [scan_lines]. - # # - # # split panics if it is called after scanning has started. - # fn split(inout self, split_function: SplitFunction) raises: - # if self.scan_called: - # raise Error("split called after Scan") - - # self.split = split_function - - -# SplitFunction is the signature of the split function used to tokenize the -# input. The arguments are an initial substring of the remaining unprocessed -# data and a flag, at_eof, that reports whether the [Reader] has no more data -# to give. The return values are the number of bytes to advance the input -# and the next token to return to the user, if any, plus an error, if any. -# -# Scanning stops if the function returns an error, in which case some of -# the input may be discarded. If that error is [ERR_FINAL_TOKEN], scanning -# stops with no error. A non-nil token delivered with [ERR_FINAL_TOKEN] -# will be the last token, and a nil token with [ERR_FINAL_TOKEN] -# immediately stops the scanning. -# -# Otherwise, the [Scanner] advances the input. If the token is not nil, -# the [Scanner] returns it to the user. If the token is nil, the -# Scanner reads more data and continues scanning; if there is no more -# data--if at_eof was True--the [Scanner] returns. If the data does not -# yet hold a complete token, for instance if it has no newline while -# scanning lines, a [SplitFunction] can return (0, nil, nil) to signal the -# [Scanner] to read more data Into the slice and try again with a -# longer slice starting at the same poInt in the input. -# -# The function is never called with an empty data slice unless at_eof -# is True. If at_eof is True, however, data may be non-empty and, -# as always, holds unprocessed text. -alias SplitFunction = fn (data: List[Byte], at_eof: Bool) -> (Int, List[Byte], Error) - -# # Errors returned by Scanner. -alias ERR_TOO_LONG = Error("bufio.Scanner: token too long") -alias ERR_NEGATIVE_ADVANCE = Error("bufio.Scanner: SplitFunction returns negative advance count") -alias ERR_ADVANCE_TOO_FAR = Error("bufio.Scanner: SplitFunction returns advance count beyond input") -alias ERR_BAD_READ_COUNT = Error("bufio.Scanner: Read returned impossible count") -# ERR_FINAL_TOKEN is a special sentinel error value. It is Intended to be -# returned by a split function to indicate that the scanning should stop -# with no error. If the token being delivered with this error is not nil, -# the token is the last token. -# -# The value is useful to stop processing early or when it is necessary to -# deliver a final empty token (which is different from a nil token). -# One could achieve the same behavior with a custom error value but -# providing one here is tidier. -# See the emptyFinalToken example for a use of this value. -alias ERR_FINAL_TOKEN = Error("final token") - - -# MAX_SCAN_TOKEN_SIZE is the maximum size used to buffer a token -# unless the user provides an explicit buffer with [Scanner.buffer]. -# The actual maximum token size may be smaller as the buffer -# may need to include, for instance, a newline. -alias MAX_SCAN_TOKEN_SIZE = 64 * 1024 -alias START_BUF_SIZE = 8200 # Size of initial allocation for buffer. - - -fn new_scanner[R: io.Reader](owned reader: R) -> Scanner[R]: - """Returns a new [Scanner] to read from r. - The split function defaults to [scan_lines].""" - return Scanner(reader^) - - -###### split functions ###### -fn scan_bytes(data: List[Byte], at_eof: Bool) -> (Int, List[Byte], Error): - """Split function for a [Scanner] that returns each byte as a token.""" - if at_eof and data.capacity == 0: - return 0, List[Byte](), Error() - - return 1, data[0:1], Error() - - -# var errorRune = List[Byte](string(utf8.RuneError)) - -# # ScanRunes is a split function for a [Scanner] that returns each -# # UTF-8-encoded rune as a token. The sequence of runes returned is -# # equivalent to that from a range loop over the input as a string, which -# # means that erroneous UTF-8 encodings translate to U+FFFD = "\xef\xbf\xbd". -# # Because of the Scan Interface, this makes it impossible for the client to -# # distinguish correctly encoded replacement runes from encoding errors. -# fn ScanRunes(data List[Byte], at_eof Bool) (advance Int, token List[Byte], err error): -# if at_eof and data.capacity == 0: -# return 0, nil, nil - - -# # Fast path 1: ASCII. -# if data[0] < utf8.RuneSelf: -# return 1, data[0:1], nil - - -# # Fast path 2: Correct UTF-8 decode without error. -# _, width := utf8.DecodeRune(data) -# if width > 1: -# # It's a valid encoding. Width cannot be one for a correctly encoded -# # non-ASCII rune. -# return width, data[0:width], nil - - -# # We know it's an error: we have width==1 and implicitly r==utf8.RuneError. -# # Is the error because there wasn't a full rune to be decoded? -# # FullRune distinguishes correctly between erroneous and incomplete encodings. -# if !at_eof and !utf8.FullRune(data): -# # Incomplete; get more bytes. -# return 0, nil, nil - - -# # We have a real UTF-8 encoding error. Return a properly encoded error rune -# # but advance only one byte. This matches the behavior of a range loop over -# # an incorrectly encoded string. -# return 1, errorRune, nil - - -fn drop_carriage_return(data: List[Byte]) -> List[Byte]: - """Drops a terminal \r from the data. - - Args: - data: The data to strip. - - Returns: - The stripped data. - """ - # In the case of a \r ending without a \n, indexing on -1 doesn't work as it finds a null terminator instead of \r. - if data.capacity > 0 and data[data.capacity - 1] == ord("\r"): - return data[0 : data.capacity - 1] - - return data - - -# TODO: Doing modification of token and err in these split functions, so we don't have to return any memory only types as part of the return tuple. -fn scan_lines(data: List[Byte], at_eof: Bool) -> (Int, List[Byte], Error): - """Split function for a [Scanner] that returns each line of - text, stripped of any trailing end-of-line marker. The returned line may - be empty. The end-of-line marker is one optional carriage return followed - by one mandatory newline. The last non-empty line of input will be returned even if it has no - newline. - - Args: - data: The data to split. - at_eof: Whether the data is at the end of the file. - Returns: - The number of bytes to advance the input. - """ - if at_eof and data.capacity == 0: - return 0, List[Byte](), Error() - - var i = index_byte(data, ord("\n")) - if i >= 0: - # We have a full newline-terminated line. - return i + 1, drop_carriage_return(data[0:i]), Error() - - # If we're at EOF, we have a final, non-terminated line. Return it. - # if at_eof: - return data.capacity, drop_carriage_return(data), Error() - - # Request more data. - # return 0 - - -fn is_space(r: UInt8) -> Bool: - alias ALL_WHITESPACES: String = " \t\n\r\x0b\f" - if chr(int(r)) in ALL_WHITESPACES: - return True - return False - - -# TODO: Handle runes and utf8 decoding. For now, just assuming single byte length. -fn scan_words(data: List[Byte], at_eof: Bool) -> (Int, List[Byte], Error): - """Split function for a [Scanner] that returns each - space-separated word of text, with surrounding spaces deleted. It will - never return an empty string. The definition of space is set by - unicode.IsSpace. - """ - # Skip leading spaces. - var start = 0 - var width = 0 - while start < data.capacity: - width = len(data[0]) - if not is_space(data[0]): - break - - start += width - - # Scan until space, marking end of word. - var i = 0 - width = 0 - start = 0 - while i < data.capacity: - width = len(data[i]) - if is_space(data[i]): - return i + width, data[start:i], Error() - - i += width - - # If we're at EOF, we have a final, non-empty, non-terminated word. Return it. - if at_eof and data.capacity > start: - return data.capacity, data[start:], Error() - - # Request more data. - return start, List[Byte](), Error() diff --git a/external/gojo/builtins/__init__.mojo b/external/gojo/builtins/__init__.mojo deleted file mode 100644 index 3d1e11aa..00000000 --- a/external/gojo/builtins/__init__.mojo +++ /dev/null @@ -1,6 +0,0 @@ -from .bytes import Byte, index_byte, has_suffix, has_prefix, to_string -from .list import equals -from .attributes import cap, copy -from .errors import exit, panic - -alias Rune = Int32 diff --git a/external/gojo/builtins/attributes.mojo b/external/gojo/builtins/attributes.mojo deleted file mode 100644 index 2bf21747..00000000 --- a/external/gojo/builtins/attributes.mojo +++ /dev/null @@ -1,52 +0,0 @@ -fn copy[T: CollectionElement](inout target: List[T], source: List[T], start: Int = 0) -> Int: - """Copies the contents of source into target at the same index. Returns the number of bytes copied. - Added a start parameter to specify the index to start copying into. - - Args: - target: The buffer to copy into. - source: The buffer to copy from. - start: The index to start copying into. - - Returns: - The number of bytes copied. - """ - var count = 0 - - for i in range(len(source)): - if i + start > len(target): - target[i + start] = source[i] - else: - target.append(source[i]) - count += 1 - - return count - - -fn copy[T: CollectionElement](inout target: Span[T, True], source: Span[T], start: Int = 0) -> Int: - """Copies the contents of source into target at the same index. Returns the number of bytes copied. - Added a start parameter to specify the index to start copying into. - - Args: - target: The buffer to copy into. - source: The buffer to copy from. - start: The index to start copying into. - - Returns: - The number of bytes copied. - """ - var count = 0 - - for i in range(len(source)): - target[i + start] = source[i] - count += 1 - - return count - - -fn cap[T: CollectionElement](iterable: List[T]) -> Int: - """Returns the capacity of the List. - - Args: - iterable: The List to get the capacity of. - """ - return iterable.capacity diff --git a/external/gojo/builtins/bytes.mojo b/external/gojo/builtins/bytes.mojo deleted file mode 100644 index d8ba4066..00000000 --- a/external/gojo/builtins/bytes.mojo +++ /dev/null @@ -1,63 +0,0 @@ -alias Byte = UInt8 - - -fn has_prefix(bytes: List[Byte], prefix: List[Byte]) -> Bool: - """Reports whether the List[Byte] struct begins with prefix. - - Args: - bytes: The List[Byte] struct to search. - prefix: The prefix to search for. - - Returns: - True if the List[Byte] struct begins with prefix; otherwise, False. - """ - var len_comparison = len(bytes) >= len(prefix) - var prefix_comparison = equals(bytes[0 : len(prefix)], prefix) - return len_comparison and prefix_comparison - - -fn has_suffix(bytes: List[Byte], suffix: List[Byte]) -> Bool: - """Reports whether the List[Byte] struct ends with suffix. - - Args: - bytes: The List[Byte] struct to search. - suffix: The prefix to search for. - - Returns: - True if the List[Byte] struct ends with suffix; otherwise, False. - """ - var len_comparison = len(bytes) >= len(suffix) - var suffix_comparison = equals(bytes[len(bytes) - len(suffix) : len(bytes)], suffix) - return len_comparison and suffix_comparison - - -fn index_byte(bytes: List[Byte], delim: Byte) -> Int: - """Return the index of the first occurrence of the byte delim. - - Args: - bytes: The List[Byte] struct to search. - delim: The byte to search for. - - Returns: - The index of the first occurrence of the byte delim. - """ - for i in range(len(bytes)): - if bytes[i] == delim: - return i - - return -1 - - -fn to_string(bytes: List[Byte]) -> String: - """Makes a deepcopy of the List[Byte] supplied and converts it to a string. If it's not null terminated, it will append a null byte. - - Args: - bytes: The List[Byte] struct to convert. - - Returns: - The string representation of the List[Byte] struct. - """ - var copy = List[Byte](bytes) - if copy[-1] != 0: - copy.append(0) - return String(copy) diff --git a/external/gojo/builtins/errors.mojo b/external/gojo/builtins/errors.mojo deleted file mode 100644 index 19a0bd10..00000000 --- a/external/gojo/builtins/errors.mojo +++ /dev/null @@ -1,11 +0,0 @@ -from sys import exit - -fn panic[T: Stringable](message: T, code: Int = 1): - """Panics the program with the given message and exit code. - - Args: - message: The message to panic with. - code: The exit code to panic with. - """ - print("panic:", message) - exit(code) diff --git a/external/gojo/bytes/__init__.mojo b/external/gojo/bytes/__init__.mojo deleted file mode 100644 index 15170f2a..00000000 --- a/external/gojo/bytes/__init__.mojo +++ /dev/null @@ -1,2 +0,0 @@ -from .buffer import Buffer, new_buffer -from .reader import Reader, new_reader diff --git a/external/gojo/bytes/buffer.mojo b/external/gojo/bytes/buffer.mojo deleted file mode 100644 index 33c66182..00000000 --- a/external/gojo/bytes/buffer.mojo +++ /dev/null @@ -1,641 +0,0 @@ -import ..io -from ..builtins import cap, copy, Byte, panic, index_byte - - -alias Rune = Int32 - -# SMALL_BUFFER_SIZE is an initial allocation minimal capacity. -alias SMALL_BUFFER_SIZE: Int = 64 - -# The ReadOp constants describe the last action performed on -# the buffer, so that unread_rune and unread_byte can check for -# invalid usage. op_read_runeX constants are chosen such that -# converted to Int they correspond to the rune size that was read. -alias ReadOp = Int8 - -# Don't use iota for these, as the values need to correspond with the -# names and comments, which is easier to see when being explicit. -alias OP_READ: ReadOp = -1 # Any other read operation. -alias OP_INVALID: ReadOp = 0 # Non-read operation. -alias OP_READ_RUNE1: ReadOp = 1 # read rune of size 1. -alias OP_READ_RUNE2: ReadOp = 2 # read rune of size 2. -alias OP_READ_RUNE3: ReadOp = 3 # read rune of size 3. -alias OP_READ_RUNE4: ReadOp = 4 # read rune of size 4. - -alias MAX_INT: Int = 2147483647 -# MIN_READ is the minimum slice size passed to a read call by -# [Buffer.read_from]. As long as the [Buffer] has at least MIN_READ bytes beyond -# what is required to hold the contents of r, read_from will not grow the -# underlying buffer. -alias MIN_READ: Int = 512 - -# ERR_TOO_LARGE is passed to panic if memory cannot be allocated to store data in a buffer. -alias ERR_TOO_LARGE = "buffer.Buffer: too large" -alias ERR_NEGATIVE_READ = "buffer.Buffer: reader returned negative count from read" -alias ERR_SHORT_WRITE = "short write" - - -# TODO: Removed read_from and write_to for now. Until the span arg trait issue is resolved. -# https://github.com/modularml/mojo/issues/2917 -@value -struct Buffer( - Copyable, - Stringable, - Sized, - io.ReadWriter, - io.StringWriter, - io.ByteReader, - io.ByteWriter, - # WriterTo, - # ReaderFrom, -): - """A Buffer is a variable-sized buffer of bytes with [Buffer.read] and [Buffer.write] methods. - The zero value for Buffer is an empty buffer ready to use. - """ - - var buf: List[Byte] # contents are the bytes buf[off : len(buf)] - var off: Int # read at &buf[off], write at &buf[len(buf)] - var last_read: ReadOp # last read operation, so that unread* can work correctly. - - fn __init__(inout self, owned buf: List[Byte]): - self.buf = buf - self.off = 0 - self.last_read = OP_INVALID - - fn bytes(self) -> List[Byte]: - """Returns a slice of length self.buf.capacity holding the unread portion of the buffer. - The slice is valid for use only until the next buffer modification (that is, - only until the next call to a method like [Buffer.read], [Buffer.write], [Buffer.reset], or [Buffer.truncate]). - The slice aliases the buffer content at least until the next buffer modification, - so immediate changes to the slice will affect the result of future reads. - """ - return self.buf[self.off : len(self.buf)] - - # fn available_buffer(self) raises -> List[Byte]: - # """Returns an empty buffer with self.Available() capacity. - # This buffer is intended to be appended to and - # passed to an immediately succeeding [Buffer.write] call. - # The buffer is only valid until the next write operation on self. - # """ - # return self.buf[len(self.buf) :] - - fn __str__(self) -> String: - """Returns the contents of the unread portion of the buffer - as a string. If the [Buffer] is a nil pointer, it returns "". - - To build strings more efficiently, see the strings.Builder type. - - Creates a copy of the readable buffer and returns it as a string. - """ - var valid_bytes = self.buf[self.off : len(self.buf)] - - valid_bytes.append(0) - return String(valid_bytes) - - fn empty(self) -> Bool: - """Reports whether the unread portion of the buffer is empty.""" - return len(self.buf) <= self.off - - fn __len__(self) -> Int: - """Returns the number of bytes of the unread portion of the buffer; - self.buf.capacity == len(self.List[Byte]()).""" - return len(self.buf) - self.off - - fn cap(self) -> Int: - """Cap returns the capacity of the buffer's underlying byte slice, that is, the - total space allocated for the buffer's data.""" - return cap(self.buf) - - fn available(self) -> Int: - """Returns how many bytes are unused in the buffer.""" - return self.buf.capacity - len(self.buf) - - fn truncate(inout self, position: Int) raises: - """Discards all but the first n unread bytes from the buffer - but continues to use the same allocated storage. - It panics if position is negative or greater than the length of the buffer. - - Args: - position: The position to truncate the buffer to. - """ - if position == 0: - self.reset() - return - - self.last_read = OP_INVALID - if position < 0 or position > self.buf.capacity: - raise Error("buffer.Buffer: truncation out of range") - - self.buf = self.buf[: self.off + position] - - fn reset(inout self): - """Resets the buffer to be empty, - but it retains the underlying storage for use by future writes. - reset is the same as [buffer.truncate](0).""" - self.buf = List[Byte](capacity=self.buf.capacity) - self.off = 0 - self.last_read = OP_INVALID - - fn try_grow_by_reslice(inout self, n: Int) -> (Int, Bool): - """Inlineable version of grow for the fast-case where the - internal buffer only needs to be resliced. - It returns the index where bytes should be written and whether it succeeded.""" - var buffer_already_used = len(self.buf) - - if n <= self.buf.capacity - buffer_already_used: - # FIXME: It seems like reslicing in go can extend the length of the slice. Doens't work like that for my get slice impl. - # Instead, just add bytes of len(n) to the end of the buffer for now. - # self.buf = self.buf[: l + n] - self.buf.reserve(self.buf.capacity + n) - return buffer_already_used, True - - return 0, False - - fn grow(inout self, n: Int) -> Int: - """Grows the buffer to guarantee space for n more bytes. - It returns the index where bytes should be written. - If the buffer can't grow it will panic with ERR_TOO_LARGE.""" - var write_at: Int = len(self.buf) - # If buffer is empty, reset to recover space. - if write_at == 0 and self.off != 0: - self.reset() - - # Try to grow by means of a reslice. - var i: Int - var ok: Bool - i, ok = self.try_grow_by_reslice(n) - if ok: - return i - - # If buffer length is 0 and elements being added is less than small_buffer_size, resize the buffer and write from the beginning. - if self.buf.capacity == 0 and n <= SMALL_BUFFER_SIZE: - self.buf.reserve(SMALL_BUFFER_SIZE) - return 0 - - var c = cap(self.buf) - if Float64(n) <= c / 2 - write_at: - # We can slide things down instead of allocating a new - # slice. We only need m+n <= c to slide, but - # we instead var capacity get twice as large so we - # don't spend all our time copying. - _ = copy(self.buf, self.buf[self.off :]) - elif c > MAX_INT - c - n: - panic(ERR_TOO_LARGE) - # TODO: Commented out this branch because growing the slice here and then at the end is redundant? - # else: - # # Add self.off to account for self.buf[:self.off] being sliced off the front. - # # var sl = self.buf[self.off :] - # # self.buf = self.grow_slice(sl, self.off + n) - - # Restore self.off and len(self.buf). - self.off = 0 - # FIXME: It seems like reslicing in go can extend the length of the slice. Doens't work like that for my get slice impl. - # Instead, just add bytes of len(n) to the end of the buffer for now. - # self.buf = self.buf[: m + n] - self.buf.reserve(self.buf.capacity + n) - return write_at - - fn Grow(inout self, n: Int): - """Grows the buffer's capacity, if necessary, to guarantee space for - another n bytes. After grow(n), at least n bytes can be written to the - buffer without another allocation. - If n is negative, grow will panic. - If the buffer can't grow it will panic with [ERR_TOO_LARGE]. - """ - if n < 0: - panic("buffer.Buffer.Grow: negative count") - - var m = self.grow(n) - self.buf = self.buf[:m] - - fn write(inout self, src: Span[Byte]) -> (Int, Error): - """Appends the contents of p to the buffer, growing the buffer as - needed. The return value n is the length of p; err is always nil. If the - buffer becomes too large, write will panic with [ERR_TOO_LARGE]. - - Args: - src: The bytes to write to the buffer. - - Returns: - The number of bytes written to the buffer. - """ - self.last_read = OP_INVALID - var write_at: Int - var ok: Bool - write_at, ok = self.try_grow_by_reslice(len(src)) - if not ok: - write_at = self.grow(len(src)) - - var bytes_written = copy(self.buf, src, write_at) - return bytes_written, Error() - - fn write_string(inout self, src: String) -> (Int, Error): - """Appends the contents of s to the buffer, growing the buffer as - needed. The return value n is the length of s; err is always nil. If the - buffer becomes too large, write_string will panic with [ERR_TOO_LARGE]. - - Args: - src: The bytes to write to the buffer. - - Returns: - The number of bytes written to the buffer. - """ - # self.last_read = OP_INVALID - # var write_at: Int - # var ok: Bool - # write_at, ok = self.try_grow_by_reslice(len(src)) - # if not ok: - # m = self.grow(len(src)) - # var b = self.buf[m:] - return self.write(src.as_bytes_slice()) - - # fn read_from[R: Reader](inout self, inout reader: R) -> (Int64, Error): - # """Reads data from r until EOF and appends it to the buffer, growing - # the buffer as needed. The return value n is the number of bytes read. Any - # error except io.EOF encountered during the read is also returned. If the - # buffer becomes too large, read_from will panic with [ERR_TOO_LARGE]. - - # Args: - # reader: The reader to read from. - - # Returns: - # The number of bytes read from the reader. - # """ - # self.last_read = OP_INVALID - # var total_bytes_read: Int64 = 0 - # while True: - # _ = self.grow(MIN_READ) - - # var span = Span(self.buf) - # var bytes_read: Int - # var err: Error - # bytes_read, err = reader.read(span) - # if bytes_read < 0: - # panic(ERR_NEGATIVE_READ) - - # total_bytes_read += bytes_read - - # var err_message = str(err) - # if err_message != "": - # if err_message == io.EOF: - # return total_bytes_read, Error() - - # return total_bytes_read, err - - fn grow_slice(self, inout b: List[Byte], n: Int) -> List[Byte]: - """Grows b by n, preserving the original content of self. - If the allocation fails, it panics with ERR_TOO_LARGE. - """ - # TODO(http:#golang.org/issue/51462): We should rely on the append-make - # pattern so that the compiler can call runtime.growslice. For example: - # return append(b, make(bytes, n)...) - # This avoids unnecessary zero-ing of the first b.capacity bytes of the - # allocated slice, but this pattern causes b to escape onto the heap. - # - # Instead use the append-make pattern with a nil slice to ensure that - # we allocate buffers rounded up to the closest size class. - var c = b.capacity + n # ensure enough space for n elements - if c < 2 * cap(b): - # The growth rate has historically always been 2x. In the future, - # we could rely purely on append to determine the growth rate. - c = 2 * cap(b) - - var resized_buffer = List[Byte](capacity=c) - _ = copy(resized_buffer, b) - # var b2: List[Byte] = List[Byte]() - # b2._vector.reserve(c) - - # # var b2 = append(bytes(nil), make(bytes, c)...) - # _ = copy(b2, b) - # return b2[:b.capacity] - # b._vector.reserve(c) - return resized_buffer[: b.capacity] - - # fn write_to[W: Writer](inout self, inout writer: W) -> (Int64, Error): - # """Writes data to w until the buffer is drained or an error occurs. - # The return value n is the number of bytes written; it always fits into an - # Int, but it is int64 to match the io.WriterTo trait. Any error - # encountered during the write is also returned. - - # Args: - # writer: The writer to write to. - - # Returns: - # The number of bytes written to the writer. - # """ - # self.last_read = OP_INVALID - # var bytes_to_write = len(self.buf) - # var total_bytes_written: Int64 = 0 - - # if bytes_to_write > 0: - # # TODO: Replace usage of this intermeidate slice when normal slicing, once slice references work. - # var sl = Span(self.buf[self.off : bytes_to_write]) - # var bytes_written: Int - # var err: Error - # bytes_written, err = writer.write(sl) - # if bytes_written > bytes_to_write: - # panic("bytes.Buffer.write_to: invalid write count") - - # self.off += bytes_written - # total_bytes_written = Int64(bytes_written) - - # var err_message = str(err) - # if err_message != "": - # return total_bytes_written, err - - # # all bytes should have been written, by definition of write method in io.Writer - # if bytes_written != bytes_to_write: - # return total_bytes_written, Error(ERR_SHORT_WRITE) - - # # Buffer is now empty; reset. - # self.reset() - # return total_bytes_written, Error() - - fn write_byte(inout self, byte: Byte) -> (Int, Error): - """Appends the byte c to the buffer, growing the buffer as needed. - The returned error is always nil, but is included to match [bufio.Writer]'s - write_byte. If the buffer becomes too large, write_byte will panic with - [ERR_TOO_LARGE]. - - Args: - byte: The byte to write to the buffer. - - Returns: - The number of bytes written to the buffer. - """ - self.last_read = OP_INVALID - var write_at: Int - var ok: Bool - write_at, ok = self.try_grow_by_reslice(1) - if not ok: - write_at = self.grow(1) - - _ = copy(self.buf, List[Byte](byte), write_at) - return write_at, Error() - - # fn write_rune(inout self, r: Rune) -> Int: - # """Appends the UTF-8 encoding of Unicode code point r to the - # buffer, returning its length and an error, which is always nil but is - # included to match [bufio.Writer]'s write_rune. The buffer is grown as needed; - # if it becomes too large, write_rune will panic with [ERR_TOO_LARGE]. - # """ - # # Compare as uint32 to correctly handle negative runes. - # if UInt32(r) < utf8.RuneSelf: - # self.write_byte(Byte(r)) - # return 1 - - # self.last_read = OP_INVALID - # var write_at: Int - # var ok: Bool - # write_at, ok = self.try_grow_by_reslice(utf8.UTFMax) - # if not ok: - # write_at = self.grow(utf8.UTFMax) - - # self.buf = utf8.AppendRune(self.buf[:write_at], r) - # return len(self.buf) - write_at - - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - """Reads the next len(dest) bytes from the buffer or until the buffer - is drained. The return value n is the number of bytes read. If the - buffer has no data to return, err is io.EOF (unless len(dest) is zero); - otherwise it is nil. - - Args: - dest: The buffer to read into. - - Returns: - The number of bytes read from the buffer. - """ - self.last_read = OP_INVALID - if self.empty(): - # Buffer is empty, reset to recover space. - self.reset() - if dest.capacity == 0: - return 0, Error() - return 0, Error(io.EOF) - - var bytes_read = copy(dest, self.buf[self.off : len(self.buf)]) - self.off += bytes_read - if bytes_read > 0: - self.last_read = OP_READ - - return bytes_read, Error() - - fn next(inout self, number_of_bytes: Int) raises -> List[Byte]: - """Returns a slice containing the next n bytes from the buffer, - advancing the buffer as if the bytes had been returned by [Buffer.read]. - If there are fewer than n bytes in the buffer, next returns the entire buffer. - The slice is only valid until the next call to a read or write method. - - Args: - number_of_bytes: The number of bytes to read from the buffer. - - Returns: - A slice containing the next n bytes from the buffer. - """ - self.last_read = OP_INVALID - var m = len(self) - var bytes_to_read = number_of_bytes - if bytes_to_read > m: - bytes_to_read = m - - var data = self.buf[self.off : self.off + bytes_to_read] - self.off += bytes_to_read - if bytes_to_read > 0: - self.last_read = OP_READ - - return data - - fn read_byte(inout self) -> (Byte, Error): - """Reads and returns the next byte from the buffer. - If no byte is available, it returns error io.EOF. - """ - if self.empty(): - # Buffer is empty, reset to recover space. - self.reset() - return Byte(0), Error(io.EOF) - - var byte = self.buf[self.off] - self.off += 1 - self.last_read = OP_READ - - return byte, Error() - - # read_rune reads and returns the next UTF-8-encoded - # Unicode code point from the buffer. - # If no bytes are available, the error returned is io.EOF. - # If the bytes are an erroneous UTF-8 encoding, it - # consumes one byte and returns U+FFFD, 1. - # fn read_rune(self) (r rune, size Int, err error) - # if self.empty() - # # Buffer is empty, reset to recover space. - # self.reset() - # return 0, 0, io.EOF - # - # c := self.buf[self.off] - # if c < utf8.RuneSelf - # self.off+= 1 - # self.last_read = OP_READ_RUNE1 - # return rune(c), 1, nil - # - # r, n := utf8.DecodeRune(self.buf[self.off:]) - # self.off += n - # self.last_read = ReadOp(n) - # return r, n, nil - # - - # unread_rune unreads the last rune returned by [Buffer.read_rune]. - # If the most recent read or write operation on the buffer was - # not a successful [Buffer.read_rune], unread_rune returns an error. (In this regard - # it is stricter than [Buffer.unread_byte], which will unread the last byte - # from any read operation.) - # fn unread_rune(self): - # if self.last_read <= OP_INVALID - # return errors.New("buffer.Buffer: unread_rune: previous operation was not a successful read_rune") - # - # if self.off >= Int(self.last_read) - # self.off -= Int(self.last_read) - # - # self.last_read = OP_INVALID - # return nil - - # var err_unread_byte = errors.New("buffer.Buffer: unread_byte: previous operation was not a successful read") - - fn unread_byte(inout self) -> Error: - """Unreads the last byte returned by the most recent successful - read operation that read at least one byte. If a write has happened since - the last read, if the last read returned an error, or if the read read zero - bytes, unread_byte returns an error. - """ - if self.last_read == OP_INVALID: - return Error("buffer.Buffer: unread_byte: previous operation was not a successful read") - - self.last_read = OP_INVALID - if self.off > 0: - self.off -= 1 - - return Error() - - fn read_bytes(inout self, delim: Byte) -> (List[Byte], Error): - """Reads until the first occurrence of delim in the input, - returning a slice containing the data up to and including the delimiter. - If read_bytes encounters an error before finding a delimiter, - it returns the data read before the error and the error itself (often io.EOF). - read_bytes returns err != nil if and only if the returned data does not end in - delim. - - Args: - delim: The delimiter to read until. - - Returns: - A List[Byte] struct containing the data up to and including the delimiter. - """ - var slice: List[Byte] - var err: Error - slice, err = self.read_slice(delim) - - # return a copy of slice. The buffer's backing array may - # be overwritten by later calls. - var line = List[Byte](capacity=io.BUFFER_SIZE) - for i in range(len(slice)): - line.append(slice[i]) - return line, Error() - - fn read_slice(inout self, delim: Byte) -> (List[Byte], Error): - """Like read_bytes but returns a reference to internal buffer data. - - Args: - delim: The delimiter to read until. - - Returns: - A List[Byte] struct containing the data up to and including the delimiter. - """ - var at_eof = False - var i = index_byte(self.buf[self.off : len(self.buf)], delim) - var end = self.off + i + 1 - - if i < 0: - end = len(self.buf) - at_eof = True - - var line = self.buf[self.off : end] - self.off = end - self.last_read = OP_READ - - if at_eof: - return line, Error(io.EOF) - - return line, Error() - - fn read_string(inout self, delim: Byte) -> (String, Error): - """Reads until the first occurrence of delim in the input, - returning a string containing the data up to and including the delimiter. - If read_string encounters an error before finding a delimiter, - it returns the data read before the error and the error itself (often io.EOF). - read_string returns err != nil if and only if the returned data does not end - in delim. - - Args: - delim: The delimiter to read until. - - Returns: - A string containing the data up to and including the delimiter. - """ - var slice: List[Byte] - var err: Error - slice, err = self.read_slice(delim) - slice.append(0) - return String(slice), err - - -fn new_buffer() -> Buffer: - """Creates and initializes a new [Buffer] using buf as its` - initial contents. The new [Buffer] takes ownership of buf, and the - caller should not use buf after this call. new_buffer is intended to - prepare a [Buffer] to read existing data. It can also be used to set - the initial size of the internal buffer for writing. To do that, - buf should have the desired capacity but a length of zero. - - In most cases, new([Buffer]) (or just declaring a [Buffer] variable) is - sufficient to initialize a [Buffer]. - """ - var b = List[Byte](capacity=io.BUFFER_SIZE) - return Buffer(b^) - - -fn new_buffer(owned buf: List[Byte]) -> Buffer: - """Creates and initializes a new [Buffer] using buf as its` - initial contents. The new [Buffer] takes ownership of buf, and the - caller should not use buf after this call. new_buffer is intended to - prepare a [Buffer] to read existing data. It can also be used to set - the initial size of the internal buffer for writing. To do that, - buf should have the desired capacity but a length of zero. - - In most cases, new([Buffer]) (or just declaring a [Buffer] variable) is - sufficient to initialize a [Buffer]. - - Args: - buf: The bytes to use as the initial contents of the buffer. - - Returns: - A new [Buffer] initialized with the provided bytes. - """ - return Buffer(buf^) - - -fn new_buffer(owned s: String) -> Buffer: - """Creates and initializes a new [Buffer] using string s as its - initial contents. It is intended to prepare a buffer to read an existing - string. - - In most cases, new([Buffer]) (or just declaring a [Buffer] variable) is - sufficient to initialize a [Buffer]. - - Args: - s: The string to use as the initial contents of the buffer. - - Returns: - A new [Buffer] initialized with the provided string. - """ - var bytes_buffer = List[Byte](s.as_bytes()) - return Buffer(bytes_buffer^) diff --git a/external/gojo/bytes/reader.mojo b/external/gojo/bytes/reader.mojo deleted file mode 100644 index 0b91dcdc..00000000 --- a/external/gojo/bytes/reader.mojo +++ /dev/null @@ -1,216 +0,0 @@ -from collections.optional import Optional -from ..builtins import cap, copy, Byte, panic -import ..io - - -@value -struct Reader( - Copyable, - Sized, - io.Reader, - io.ReaderAt, - # io.WriterTo, - io.Seeker, - io.ByteReader, - io.ByteScanner, -): - """A Reader implements the io.Reader, io.ReaderAt, io.WriterTo, io.Seeker, - io.ByteScanner, and io.RuneScanner Interfaces by reading from - a byte slice. - Unlike a [Buffer], a Reader is read-only and supports seeking. - The zero value for Reader operates like a Reader of an empty slice. - """ - - var buffer: List[Byte] - var index: Int64 # current reading index - var prev_rune: Int # index of previous rune; or < 0 - - fn __len__(self) -> Int: - """len returns the number of bytes of the unread portion of the - slice.""" - if self.index >= len(self.buffer): - return 0 - - return int(len(self.buffer) - self.index) - - fn size(self) -> Int: - """Returns the original length of the underlying byte slice. - Size is the number of bytes available for reading via [Reader.ReadAt]. - The result is unaffected by any method calls except [Reader.Reset].""" - return len(self.buffer) - - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - """Reads from the internal buffer into the dest List[Byte] struct. - Implements the [io.Reader] Interface. - - Args: - dest: The destination List[Byte] struct to read into. - - Returns: - Int: The number of bytes read into dest.""" - if self.index >= len(self.buffer): - return 0, Error(io.EOF) - - self.prev_rune = -1 - var unread_bytes = self.buffer[int(self.index) : len(self.buffer)] - var bytes_read = copy(dest, unread_bytes) - - self.index += bytes_read - return bytes_read, Error() - - fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): - """Reads len(dest) bytes into dest beginning at byte offset off. - Implements the [io.ReaderAt] Interface. - - Args: - dest: The destination List[Byte] struct to read into. - off: The offset to start reading from. - - Returns: - Int: The number of bytes read into dest. - """ - # cannot modify state - see io.ReaderAt - if off < 0: - return 0, Error("bytes.Reader.read_at: negative offset") - - if off >= Int64(len(self.buffer)): - return 0, Error(io.EOF) - - var unread_bytes = self.buffer[int(off) : len(self.buffer)] - var bytes_written = copy(dest, unread_bytes) - if bytes_written < len(dest): - return 0, Error(io.EOF) - - return bytes_written, Error() - - fn read_byte(inout self) -> (Byte, Error): - """Reads and returns a single byte from the internal buffer. Implements the [io.ByteReader] Interface.""" - self.prev_rune = -1 - if self.index >= len(self.buffer): - return UInt8(0), Error(io.EOF) - - var byte = self.buffer[int(self.index)] - self.index += 1 - return byte, Error() - - fn unread_byte(inout self) -> Error: - """Unreads the last byte read by moving the read position back by one. - Complements [Reader.read_byte] in implementing the [io.ByteScanner] Interface. - """ - if self.index <= 0: - return Error("bytes.Reader.unread_byte: at beginning of slice") - - self.prev_rune = -1 - self.index -= 1 - - return Error() - - # # read_rune implements the [io.RuneReader] Interface. - # fn read_rune(self) (ch rune, size Int, err error): - # if self.index >= Int64(len(self.buffer)): - # self.prev_rune = -1 - # return 0, 0, io.EOF - - # self.prev_rune = Int(self.index) - # if c := self.buffer[self.index]; c < utf8.RuneSelf: - # self.index+= 1 - # return rune(c), 1, nil - - # ch, size = utf8.DecodeRune(self.buffer[self.index:]) - # self.index += Int64(size) - # return - - # # unread_rune complements [Reader.read_rune] in implementing the [io.RuneScanner] Interface. - # fn unread_rune(self) error: - # if self.index <= 0: - # return errors.New("bytes.Reader.unread_rune: at beginning of slice") - - # if self.prev_rune < 0: - # return errors.New("bytes.Reader.unread_rune: previous operation was not read_rune") - - # self.index = Int64(self.prev_rune) - # self.prev_rune = -1 - # return nil - - fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): - """Moves the read position to the specified offset from the specified whence. - Implements the [io.Seeker] Interface. - - Args: - offset: The offset to move to. - whence: The reference point for offset. - - Returns: - The new position in which the next read will start from. - """ - self.prev_rune = -1 - var position: Int64 = 0 - - if whence == io.SEEK_START: - position = offset - elif whence == io.SEEK_CURRENT: - position = self.index + offset - elif whence == io.SEEK_END: - position = len(self.buffer) + offset - else: - return Int64(0), Error("bytes.Reader.seek: invalid whence") - - if position < 0: - return Int64(0), Error("bytes.Reader.seek: negative position") - - self.index = position - return position, Error() - - # fn write_to[W: io.Writer](inout self, inout writer: W) -> (Int64, Error): - # """Writes data to w until the buffer is drained or an error occurs. - # implements the [io.WriterTo] Interface. - - # Args: - # writer: The writer to write to. - # """ - # self.prev_rune = -1 - # if self.index >= len(self.buffer): - # return Int64(0), Error() - - # var bytes = Span(self.buffer[int(self.index) : len(self.buffer)]) - # var write_count: Int - # var err: Error - # write_count, err = writer.write(bytes) - # if write_count > len(bytes): - # panic("bytes.Reader.write_to: invalid Write count") - - # self.index += write_count - # if write_count != len(bytes): - # return Int64(write_count), Error(io.ERR_SHORT_WRITE) - - # return Int64(write_count), Error() - - fn reset(inout self, buffer: List[Byte]): - """Resets the [Reader.Reader] to be reading from b. - - Args: - buffer: The new buffer to read from. - """ - self.buffer = buffer - self.index = 0 - self.prev_rune = -1 - - -fn new_reader(buffer: List[Byte]) -> Reader: - """Returns a new [Reader.Reader] reading from b. - - Args: - buffer: The new buffer to read from. - - """ - return Reader(buffer, 0, -1) - - -fn new_reader(buffer: String) -> Reader: - """Returns a new [Reader.Reader] reading from b. - - Args: - buffer: The new buffer to read from. - - """ - return Reader(buffer.as_bytes(), 0, -1) diff --git a/external/gojo/fmt/__init__.mojo b/external/gojo/fmt/__init__.mojo deleted file mode 100644 index a4b04e30..00000000 --- a/external/gojo/fmt/__init__.mojo +++ /dev/null @@ -1 +0,0 @@ -from .fmt import sprintf, printf, sprintf_str diff --git a/external/gojo/fmt/fmt.mojo b/external/gojo/fmt/fmt.mojo deleted file mode 100644 index 8997e50b..00000000 --- a/external/gojo/fmt/fmt.mojo +++ /dev/null @@ -1,219 +0,0 @@ -"""Formatting options -General -%v the value in a default format - when printing structs, the plus flag (%+v) adds field names - -Boolean -%t the word true or false - -Integer -%d base 10 -%q a single-quoted character literal. -%x base 16, with lower-case letters for a-f -%X base 16, with upper-case letters for A-F - -Floating-point and complex constituents: -%f decimal point but no exponent, e.g. 123.456 - -String and slice of bytes (treated equivalently with these verbs): -%s the uninterpreted bytes of the string or slice -%q a double-quoted string - -TODO: -- Add support for more formatting options -- Switch to buffered writing to avoid multiple string concatenations -- Add support for width and precision formatting options -- Handle escaping for String's %q -""" - -from utils.variant import Variant -from math import floor -from ..builtins import Byte - -alias Args = Variant[String, Int, Float64, Bool, List[Byte]] - - -fn replace_first(s: String, old: String, new: String) -> String: - """Replace the first occurrence of a substring in a string. - - Args: - s: The original string - old: The substring to be replaced - new: The new substring - - Returns: - The string with the first occurrence of the old substring replaced by the new one. - """ - # Find the first occurrence of the old substring - var index = s.find(old) - - # If the old substring is found, replace it - if index != -1: - return s[:index] + new + s[index + len(old) :] - - # If the old substring is not found, return the original string - return s - - -fn find_first_verb(s: String, verbs: List[String]) -> String: - """Find the first occurrence of a verb in a string. - - Args: - s: The original string - verbs: The list of verbs to search for. - - Returns: - The verb to replace. - """ - var index = -1 - var verb: String = "" - - for v in verbs: - var i = s.find(v[]) - if i != -1 and (index == -1 or i < index): - index = i - verb = v[] - - return verb - - -alias BASE10_TO_BASE16 = List[String]("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f") - - -fn convert_base10_to_base16(value: Int) -> String: - """Converts a base 10 number to base 16. - - Args: - value: Base 10 number. - - Returns: - Base 16 number as a String. - """ - - var val: Float64 = 0.0 - var result: Float64 = value - var base16: String = "" - while result > 1: - var temp = result / 16 - var floor_result = floor(temp) - var remainder = temp - floor_result - result = floor_result - val = 16 * remainder - - base16 = BASE10_TO_BASE16[int(val)] + base16 - - return base16 - - -fn format_string(format: String, arg: String) -> String: - var verb = find_first_verb(format, List[String]("%s", "%q")) - var arg_to_place = arg - if verb == "%q": - arg_to_place = '"' + arg + '"' - - return replace_first(format, String("%s"), arg) - - -fn format_bytes(format: String, arg: List[Byte]) -> String: - var argument = arg - if argument[-1] != 0: - argument.append(0) - - return format_string(format, argument) - - -fn format_integer(format: String, arg: Int) -> String: - var verb = find_first_verb(format, List[String]("%x", "%X", "%d", "%q")) - var arg_to_place = str(arg) - if verb == "%x": - arg_to_place = str(convert_base10_to_base16(arg)).lower() - elif verb == "%X": - arg_to_place = str(convert_base10_to_base16(arg)).upper() - elif verb == "%q": - arg_to_place = "'" + str(arg) + "'" - - return replace_first(format, verb, arg_to_place) - - -fn format_float(format: String, arg: Float64) -> String: - return replace_first(format, str("%f"), str(arg)) - - -fn format_boolean(format: String, arg: Bool) -> String: - var value: String = "False" - if arg: - value = "True" - - return replace_first(format, String("%t"), value) - - -# If the number of arguments does not match the number of format specifiers -alias BadArgCount = "(BAD ARG COUNT)" - - -fn sprintf(formatting: String, *args: Args) -> String: - var text = formatting - var raw_percent_count = formatting.count("%%") * 2 - var formatter_count = formatting.count("%") - raw_percent_count - - if formatter_count != len(args): - return BadArgCount - - for i in range(len(args)): - var argument = args[i] - if argument.isa[String](): - text = format_string(text, argument[String]) - elif argument.isa[List[Byte]](): - text = format_bytes(text, argument[List[Byte]]) - elif argument.isa[Int](): - text = format_integer(text, argument[Int]) - elif argument.isa[Float64](): - text = format_float(text, argument[Float64]) - elif argument.isa[Bool](): - text = format_boolean(text, argument[Bool]) - - return text - - -# TODO: temporary until we have arg packing. -fn sprintf_str(formatting: String, args: List[String]) raises -> String: - var text = formatting - var formatter_count = formatting.count("%") - - if formatter_count > len(args): - raise Error("Not enough arguments for format string") - elif formatter_count < len(args): - raise Error("Too many arguments for format string") - - for i in range(len(args)): - text = format_string(text, args[i]) - - return text - - -fn printf(formatting: String, *args: Args) raises: - var text = formatting - var raw_percent_count = formatting.count("%%") * 2 - var formatter_count = formatting.count("%") - raw_percent_count - - if formatter_count > len(args): - raise Error("Not enough arguments for format string") - elif formatter_count < len(args): - raise Error("Too many arguments for format string") - - for i in range(len(args)): - var argument = args[i] - if argument.isa[String](): - text = format_string(text, argument[String]) - elif argument.isa[List[Byte]](): - text = format_bytes(text, argument[List[Byte]]) - elif argument.isa[Int](): - text = format_integer(text, argument[Int]) - elif argument.isa[Float64](): - text = format_float(text, argument[Float64]) - elif argument.isa[Bool](): - text = format_boolean(text, argument[Bool]) - else: - raise Error("Unknown for argument #" + str(i)) - - print(text) diff --git a/external/gojo/io/__init__.mojo b/external/gojo/io/__init__.mojo deleted file mode 100644 index 74b8a521..00000000 --- a/external/gojo/io/__init__.mojo +++ /dev/null @@ -1,39 +0,0 @@ -from .traits import ( - Reader, - Writer, - Seeker, - Closer, - ReadWriter, - ReadCloser, - WriteCloser, - ReadWriteCloser, - ReadSeeker, - ReadSeekCloser, - WriteSeeker, - ReadWriteSeeker, - ReaderFrom, - WriterReadFrom, - WriterTo, - ReaderWriteTo, - ReaderAt, - WriterAt, - ByteReader, - ByteScanner, - ByteWriter, - RuneReader, - RuneScanner, - StringWriter, - SEEK_START, - SEEK_CURRENT, - SEEK_END, - ERR_SHORT_WRITE, - ERR_NO_PROGRESS, - ERR_SHORT_BUFFER, - EOF, -) -from .io import write_string, read_at_least, read_full, read_all, BUFFER_SIZE - - -alias i1 = __mlir_type.i1 -alias i1_1 = __mlir_attr.`1: i1` -alias i1_0 = __mlir_attr.`0: i1` diff --git a/external/gojo/io/io.mojo b/external/gojo/io/io.mojo deleted file mode 100644 index 61477052..00000000 --- a/external/gojo/io/io.mojo +++ /dev/null @@ -1,442 +0,0 @@ -from collections.optional import Optional -from ..builtins import cap, copy, Byte, panic -from .traits import ERR_UNEXPECTED_EOF - -alias BUFFER_SIZE = 8200 - - -fn write_string[W: Writer](inout writer: W, string: String) -> (Int, Error): - """Writes the contents of the string s to w, which accepts a slice of bytes. - If w implements [StringWriter], [StringWriter.write_string] is invoked directly. - Otherwise, [Writer.write] is called exactly once. - - Args: - writer: The writer to write to. - string: The string to write. - - Returns: - The number of bytes written and an error, if any. - """ - return writer.write(string.as_bytes_slice()) - - -fn write_string[W: StringWriter](inout writer: W, string: String) -> (Int, Error): - """Writes the contents of the string s to w, which accepts a slice of bytes. - If w implements [StringWriter], [StringWriter.write_string] is invoked directly. - Otherwise, [Writer.write] is called exactly once. - - Args: - writer: The writer to write to. - string: The string to write. - - Returns: - The number of bytes written and an error, if any.""" - return writer.write_string(string) - - -fn read_at_least[R: Reader](inout reader: R, inout dest: List[Byte], min: Int) -> (Int, Error): - """Reads from r into buf until it has read at least min bytes. - It returns the number of bytes copied and an error if fewer bytes were read. - The error is EOF only if no bytes were read. - If an EOF happens after reading fewer than min bytes, - read_at_least returns [ERR_UNEXPECTED_EOF]. - If min is greater than the length of buf, read_at_least returns [ERR_SHORT_BUFFER]. - On return, n >= min if and only if err == nil. - If r returns an error having read at least min bytes, the error is dropped. - - Args: - reader: The reader to read from. - dest: The buffer to read into. - min: The minimum number of bytes to read. - - Returns: - The number of bytes read.""" - var error = Error() - if len(dest) < min: - return 0, Error(io.ERR_SHORT_BUFFER) - - var total_bytes_read: Int = 0 - while total_bytes_read < min and not error: - var bytes_read: Int - bytes_read, error = reader.read(dest) - total_bytes_read += bytes_read - - if total_bytes_read >= min: - error = Error() - - elif total_bytes_read > 0 and str(error): - error = Error(ERR_UNEXPECTED_EOF) - - return total_bytes_read, error - - -fn read_full[R: Reader](inout reader: R, inout dest: List[Byte]) -> (Int, Error): - """Reads exactly len(buf) bytes from r into buf. - It returns the number of bytes copied and an error if fewer bytes were read. - The error is EOF only if no bytes were read. - If an EOF happens after reading some but not all the bytes, - read_full returns [ERR_UNEXPECTED_EOF]. - On return, n == len(buf) if and only if err == nil. - If r returns an error having read at least len(buf) bytes, the error is dropped. - """ - return read_at_least(reader, dest, len(dest)) - - -# fn copy_n[W: Writer, R: Reader](dst: W, src: R, n: Int64) raises -> Int64: -# """Copies n bytes (or until an error) from src to dst. -# It returns the number of bytes copied and the earliest -# error encountered while copying. -# On return, written == n if and only if err == nil. - -# If dst implements [ReaderFrom], the copy is implemented using it. -# """ -# var written = copy(dst, LimitReader(src, n)) -# if written == n: -# return n - -# if written < n: -# # src stopped early; must have been EOF. -# raise Error(ERR_UNEXPECTED_EOF) - -# return written - - -# fn copy[W: Writer, R: Reader](dst: W, src: R, n: Int64) -> Int64: -# """copy copies from src to dst until either EOF is reached -# on src or an error occurs. It returns the number of bytes -# copied and the first error encountered while copying, if any. - -# A successful copy returns err == nil, not err == EOF. -# Because copy is defined to read from src until EOF, it does -# not treat an EOF from Read as an error to be reported. - -# If src implements [WriterTo], -# the copy is implemented by calling src.WriteTo(dst). -# Otherwise, if dst implements [ReaderFrom], -# the copy is implemented by calling dst.ReadFrom(src). -# """ -# return copy_buffer(dst, src, nil) - -# # CopyBuffer is identical to copy except that it stages through the -# # provided buffer (if one is required) rather than allocating a -# # temporary one. If buf is nil, one is allocated; otherwise if it has -# # zero length, CopyBuffer panics. -# # -# # If either src implements [WriterTo] or dst implements [ReaderFrom], -# # buf will not be used to perform the copy. -# fn CopyBuffer(dst Writer, src Reader, buf bytes) (written int64, err error) { -# if buf != nil and len(buf) == 0 { -# panic("empty buffer in CopyBuffer") -# } -# return copy_buffer(dst, src, buf) -# } - - -# fn copy_buffer[W: Writer, R: Reader](dst: W, src: R, buf: Span[Byte]) raises -> Int64: -# """Actual implementation of copy and CopyBuffer. -# if buf is nil, one is allocated. -# """ -# var nr: Int -# nr = src.read(buf) -# while True: -# if nr > 0: -# var nw: Int -# nw = dst.write(get_slice(buf, 0, nr)) -# if nw < 0 or nr < nw: -# nw = 0 - -# var written = Int64(nw) -# if nr != nw: -# raise Error(ERR_SHORT_WRITE) - -# return written - - -# fn copy_buffer[W: Writer, R: ReaderWriteTo](dst: W, src: R, buf: Span[Byte]) -> Int64: -# return src.write_to(dst) - - -# fn copy_buffer[W: WriterReadFrom, R: Reader](dst: W, src: R, buf: Span[Byte]) -> Int64: -# return dst.read_from(src) - -# # LimitReader returns a Reader that reads from r -# # but stops with EOF after n bytes. -# # The underlying implementation is a *LimitedReader. -# fn LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} } - -# # A LimitedReader reads from R but limits the amount of -# # data returned to just N bytes. Each call to Read -# # updates N to reflect the new amount remaining. -# # Read returns EOF when N <= 0 or when the underlying R returns EOF. -# struct LimitedReader(): -# var R: Reader # underlying reader -# N int64 # max bytes remaining - -# fn (l *LimitedReader) Read(p bytes) (n Int, err error) { -# if l.N <= 0 { -# return 0, EOF -# } -# if int64(len(p)) > l.N { -# p = p[0:l.N] -# } -# n, err = l.R.Read(p) -# l.N -= int64(n) -# return -# } - -# # NewSectionReader returns a [SectionReader] that reads from r -# # starting at offset off and stops with EOF after n bytes. -# fn NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { -# var remaining int64 -# const maxint64 = 1<<63 - 1 -# if off <= maxint64-n { -# remaining = n + off -# } else { -# # Overflow, with no way to return error. -# # Assume we can read up to an offset of 1<<63 - 1. -# remaining = maxint64 -# } -# return &SectionReader{r, off, off, remaining, n} -# } - -# # SectionReader implements Read, Seek, and ReadAt on a section -# # of an underlying [ReaderAt]. -# type SectionReader struct { -# r ReaderAt # constant after creation -# base int64 # constant after creation -# off int64 -# limit int64 # constant after creation -# n int64 # constant after creation -# } - -# fn (s *SectionReader) Read(p bytes) (n Int, err error) { -# if s.off >= s.limit { -# return 0, EOF -# } -# if max := s.limit - s.off; int64(len(p)) > max { -# p = p[0:max] -# } -# n, err = s.r.ReadAt(p, s.off) -# s.off += int64(n) -# return -# } - -# alias errWhence = "Seek: invalid whence" -# alias errOffset = "Seek: invalid offset" - -# fn (s *SectionReader) Seek(offset int64, whence Int) (int64, error) { -# switch whence { -# default: -# return 0, errWhence -# case SEEK_START: -# offset += s.base -# case SEEK_CURRENT: -# offset += s.off -# case SEEK_END: -# offset += s.limit -# } -# if offset < s.base { -# return 0, errOffset -# } -# s.off = offset -# return offset - s.base, nil -# } - -# fn (s *SectionReader) ReadAt(p bytes, off int64) (n Int, err error) { -# if off < 0 or off >= s.capacity { -# return 0, EOF -# } -# off += s.base -# if max := s.limit - off; int64(len(p)) > max { -# p = p[0:max] -# n, err = s.r.ReadAt(p, off) -# if err == nil { -# err = EOF -# } -# return n, err -# } -# return s.r.ReadAt(p, off) -# } - -# # Size returns the size of the section in bytes. -# fn (s *SectionReader) Size() int64 { return s.limit - s.base } - -# # Outer returns the underlying [ReaderAt] and offsets for the section. -# # -# # The returned values are the same that were passed to [NewSectionReader] -# # when the [SectionReader] was created. -# fn (s *SectionReader) Outer() (r ReaderAt, off int64, n int64) { -# return s.r, s.base, s.n -# } - -# # An OffsetWriter maps writes at offset base to offset base+off in the underlying writer. -# type OffsetWriter struct { -# w WriterAt -# base int64 # the original offset -# off int64 # the current offset -# } - -# # NewOffsetWriter returns an [OffsetWriter] that writes to w -# # starting at offset off. -# fn NewOffsetWriter(w WriterAt, off int64) *OffsetWriter { -# return &OffsetWriter{w, off, off} -# } - -# fn (o *OffsetWriter) Write(p bytes) (n Int, err error) { -# n, err = o.w.WriteAt(p, o.off) -# o.off += int64(n) -# return -# } - -# fn (o *OffsetWriter) WriteAt(p bytes, off int64) (n Int, err error) { -# if off < 0 { -# return 0, errOffset -# } - -# off += o.base -# return o.w.WriteAt(p, off) -# } - -# fn (o *OffsetWriter) Seek(offset int64, whence Int) (int64, error) { -# switch whence { -# default: -# return 0, errWhence -# case SEEK_START: -# offset += o.base -# case SEEK_CURRENT: -# offset += o.off -# } -# if offset < o.base { -# return 0, errOffset -# } -# o.off = offset -# return offset - o.base, nil -# } - -# # TeeReader returns a [Reader] that writes to w what it reads from r. -# # All reads from r performed through it are matched with -# # corresponding writes to w. There is no internal buffering - -# # the write must complete before the read completes. -# # Any error encountered while writing is reported as a read error. -# fn TeeReader(r Reader, w Writer) Reader { -# return &teeReader{r, w} -# } - -# type teeReader struct { -# r Reader -# w Writer -# } - -# fn (t *teeReader) Read(p bytes) (n Int, err error) { -# n, err = t.r.Read(p) -# if n > 0 { -# if n, err := t.w.Write(p[:n]); err != nil { -# return n, err -# } -# } -# return -# } - -# # Discard is a [Writer] on which all Write calls succeed -# # without doing anything. -# var Discard Writer = discard{} - -# type discard struct{} - -# # discard implements ReaderFrom as an optimization so copy to -# # io.Discard can avoid doing unnecessary work. -# var _ ReaderFrom = discard{} - -# fn (discard) Write(p bytes) (Int, error) { -# return len(p), nil -# } - -# fn (discard) write_string(s string) (Int, error) { -# return len(s), nil -# } - -# var blackHolePool = sync.Pool{ -# New: fn() any { -# b := make(bytes, 8192) -# return &b -# }, -# } - -# fn (discard) ReadFrom(r Reader) (n int64, err error) { -# bufp := blackHolePool.Get().(*bytes) -# readSize := 0 -# for { -# readSize, err = r.Read(*bufp) -# n += int64(readSize) -# if err != nil { -# blackHolePool.Put(bufp) -# if err == EOF { -# return n, nil -# } -# return -# } -# } -# } - -# # NopCloser returns a [ReadCloser] with a no-op Close method wrapping -# # the provided [Reader] r. -# # If r implements [WriterTo], the returned [ReadCloser] will implement [WriterTo] -# # by forwarding calls to r. -# fn NopCloser(r Reader) ReadCloser { -# if _, ok := r.(WriterTo); ok { -# return nopCloserWriterTo{r} -# } -# return nopCloser{r} -# } - -# type nopCloser struct { -# Reader -# } - -# fn (nopCloser) Close() error { return nil } - -# type nopCloserWriterTo struct { -# Reader -# } - -# fn (nopCloserWriterTo) Close() error { return nil } - -# fn (c nopCloserWriterTo) WriteTo(w Writer) (n int64, err error) { -# return c.Reader.(WriterTo).WriteTo(w) -# } - - -fn read_all[R: Reader](inout reader: R) -> (List[Byte], Error): - """Reads from r until an error or EOF and returns the data it read. - A successful call returns err == nil, not err == EOF. Because ReadAll is - defined to read from src until EOF, it does not treat an EOF from Read - as an error to be reported. - - Args: - reader: The reader to read from. - - Returns: - The data read.""" - var dest = List[Byte](capacity=BUFFER_SIZE) - var at_eof: Bool = False - - while True: - var temp = List[Byte](capacity=BUFFER_SIZE) - var bytes_read: Int - var err: Error - bytes_read, err = reader.read(temp) - var err_message = str(err) - if err_message != "": - if err_message != EOF: - return dest, err - - at_eof = True - - # If new bytes will overflow the result, resize it. - # if some bytes were written, how do I append before returning result on the last one? - if len(dest) + len(temp) > dest.capacity: - dest.reserve(dest.capacity * 2) - dest.extend(temp) - - if at_eof: - return dest, err diff --git a/external/gojo/io/traits.mojo b/external/gojo/io/traits.mojo deleted file mode 100644 index ff1e8e6d..00000000 --- a/external/gojo/io/traits.mojo +++ /dev/null @@ -1,320 +0,0 @@ -from collections.optional import Optional -from ..builtins import Byte - -alias Rune = Int32 - -# Package io provides basic interfaces to I/O primitives. -# Its primary job is to wrap existing implementations of such primitives, -# such as those in package os, into shared public interfaces that -# abstract the fntionality, plus some other related primitives. -# -# Because these interfaces and primitives wrap lower-level operations with -# various implementations, unless otherwise informed clients should not -# assume they are safe for parallel execution. -# Seek whence values. -alias SEEK_START = 0 # seek relative to the origin of the file -alias SEEK_CURRENT = 1 # seek relative to the current offset -alias SEEK_END = 2 # seek relative to the end - -# ERR_SHORT_WRITE means that a write accepted fewer bytes than requested -# but failed to return an explicit error. -alias ERR_SHORT_WRITE = "short write" - -# ERR_INVALID_WRITE means that a write returned an impossible count. -alias ERR_INVALID_WRITE = "invalid write result" - -# ERR_SHORT_BUFFER means that a read required a longer buffer than was provided. -alias ERR_SHORT_BUFFER = "short buffer" - -# EOF is the error returned by Read when no more input is available. -# (Read must return EOF itself, not an error wrapping EOF, -# because callers will test for EOF using ==.) -# fntions should return EOF only to signal a graceful end of input. -# If the EOF occurs unexpectedly in a structured data stream, -# the appropriate error is either [ERR_UNEXPECTED_EOF] or some other error -# giving more detail. -alias EOF = "EOF" - -# ERR_UNEXPECTED_EOF means that EOF was encountered in the -# middle of reading a fixed-size block or data structure. -alias ERR_UNEXPECTED_EOF = "unexpected EOF" - -# ERR_NO_PROGRESS is returned by some clients of a [Reader] when -# many calls to Read have failed to return any data or error, -# usually the sign of a broken [Reader] implementation. -alias ERR_NO_PROGRESS = "multiple Read calls return no data or error" - - -trait Reader(Movable): - """Reader is the trait that wraps the basic Read method. - - Read reads up to len(p) bytes into p. It returns the number of bytes - read (0 <= n <= len(p)) and any error encountered. Even if Read - returns n < len(p), it may use all of p as scratch space during the call. - If some data is available but not len(p) bytes, Read conventionally - returns what is available instead of waiting for more. - - When Read encounters an error or end-of-file condition after - successfully reading n > 0 bytes, it returns the number of - bytes read. It may return the (non-nil) error from the same call - or return the error (and n == 0) from a subsequent call. - An instance of this general case is that a Reader returning - a non-zero number of bytes at the end of the input stream may - return either err == EOF or err == nil. The next Read should - return 0, EOF. - - Callers should always process the n > 0 bytes returned before - considering the error err. Doing so correctly handles I/O errors - that happen after reading some bytes and also both of the - allowed EOF behaviors. - - If len(p) == 0, Read should always return n == 0. It may return a - non-nil error if some error condition is known, such as EOF. - - Implementations of Read are discouraged from returning a - zero byte count with a nil error, except when len(p) == 0. - Callers should treat a return of 0 and nil as indicating that - nothing happened; in particular it does not indicate EOF. - - Implementations must not retain p.""" - - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - ... - - -trait Writer(Movable): - """Writer is the trait that wraps the basic Write method. - - Write writes len(p) bytes from p to the underlying data stream. - It returns the number of bytes written from p (0 <= n <= len(p)) - and any error encountered that caused the write to stop early. - Write must return a non-nil error if it returns n < len(p). - Write must not modify the slice data, even temporarily. - - Implementations must not retain p. - """ - - fn write(inout self, src: Span[Byte]) -> (Int, Error): - ... - - -trait Closer(Movable): - """ - Closer is the trait that wraps the basic Close method. - - The behavior of Close after the first call is undefined. - Specific implementations may document their own behavior. - """ - - fn close(inout self) -> Error: - ... - - -trait Seeker(Movable): - """ - Seeker is the trait that wraps the basic Seek method. - - Seek sets the offset for the next Read or Write to offset, - interpreted according to whence: - [SEEK_START] means relative to the start of the file, - [SEEK_CURRENT] means relative to the current offset, and - [SEEK_END] means relative to the end - (for example, offset = -2 specifies the penultimate byte of the file). - Seek returns the new offset relative to the start of the - file or an error, if any. - - Seeking to an offset before the start of the file is an error. - Seeking to any positive offset may be allowed, but if the new offset exceeds - the size of the underlying object the behavior of subsequent I/O operations - is implementation-dependent. - """ - - fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): - ... - - -trait ReadWriter(Reader, Writer): - ... - - -trait ReadCloser(Reader, Closer): - ... - - -trait WriteCloser(Writer, Closer): - ... - - -trait ReadWriteCloser(Reader, Writer, Closer): - ... - - -trait ReadSeeker(Reader, Seeker): - ... - - -trait ReadSeekCloser(Reader, Seeker, Closer): - ... - - -trait WriteSeeker(Writer, Seeker): - ... - - -trait ReadWriteSeeker(Reader, Writer, Seeker): - ... - - -trait ReaderFrom: - """ReaderFrom is the trait that wraps the ReadFrom method. - - ReadFrom reads data from r until EOF or error. - The return value n is the number of bytes read. - Any error except EOF encountered during the read is also returned. - - The [copy] function uses [ReaderFrom] if available.""" - - fn read_from[R: Reader](inout self, inout reader: R) -> (Int64, Error): - ... - - -trait WriterReadFrom(Writer, ReaderFrom): - ... - - -trait WriterTo: - """WriterTo is the trait that wraps the WriteTo method. - - WriteTo writes data to w until there's no more data to write or - when an error occurs. The return value n is the number of bytes - written. Any error encountered during the write is also returned. - - The copy function uses WriterTo if available.""" - - fn write_to[W: Writer](inout self, inout writer: W) -> (Int64, Error): - ... - - -trait ReaderWriteTo(Reader, WriterTo): - ... - - -trait ReaderAt: - """ReaderAt is the trait that wraps the basic ReadAt method. - - ReadAt reads len(p) bytes into p starting at offset off in the - underlying input source. It returns the number of bytes - read (0 <= n <= len(p)) and any error encountered. - - When ReadAt returns n < len(p), it returns a non-nil error - explaining why more bytes were not returned. In this respect, - ReadAt is stricter than Read. - - Even if ReadAt returns n < len(p), it may use all of p as scratch - space during the call. If some data is available but not len(p) bytes, - ReadAt blocks until either all the data is available or an error occurs. - In this respect ReadAt is different from Read. - - If the n = len(p) bytes returned by ReadAt are at the end of the - input source, ReadAt may return either err == EOF or err == nil. - - If ReadAt is reading from an input source with a seek offset, - ReadAt should not affect nor be affected by the underlying - seek offset. - - Clients of ReadAt can execute parallel ReadAt calls on the - same input source. - - Implementations must not retain p.""" - - fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): - ... - - -trait WriterAt: - """WriterAt is the trait that wraps the basic WriteAt method. - - WriteAt writes len(p) bytes from p to the underlying data stream - at offset off. It returns the number of bytes written from p (0 <= n <= len(p)) - and any error encountered that caused the write to stop early. - WriteAt must return a non-nil error if it returns n < len(p). - - If WriteAt is writing to a destination with a seek offset, - WriteAt should not affect nor be affected by the underlying - seek offset. - - Clients of WriteAt can execute parallel WriteAt calls on the same - destination if the ranges do not overlap. - - Implementations must not retain p.""" - - fn write_at(self, src: Span[Byte], off: Int64) -> (Int, Error): - ... - - -trait ByteReader: - """ByteReader is the trait that wraps the read_byte method. - - read_byte reads and returns the next byte from the input or - any error encountered. If read_byte returns an error, no input - byte was consumed, and the returned byte value is undefined. - - read_byte provides an efficient trait for byte-at-time - processing. A [Reader] that does not implement ByteReader - can be wrapped using bufio.NewReader to add this method.""" - - fn read_byte(inout self) -> (Byte, Error): - ... - - -trait ByteScanner(ByteReader): - """ByteScanner is the trait that adds the unread_byte method to the - basic read_byte method. - - unread_byte causes the next call to read_byte to return the last byte read. - If the last operation was not a successful call to read_byte, unread_byte may - return an error, unread the last byte read (or the byte prior to the - last-unread byte), or (in implementations that support the [Seeker] trait) - seek to one byte before the current offset.""" - - fn unread_byte(inout self) -> Error: - ... - - -trait ByteWriter: - """ByteWriter is the trait that wraps the write_byte method.""" - - fn write_byte(inout self, byte: Byte) -> (Int, Error): - ... - - -trait RuneReader: - """RuneReader is the trait that wraps the read_rune method. - - read_rune reads a single encoded Unicode character - and returns the rune and its size in bytes. If no character is - available, err will be set.""" - - fn read_rune(inout self) -> (Rune, Int): - ... - - -trait RuneScanner(RuneReader): - """RuneScanner is the trait that adds the unread_rune method to the - basic read_rune method. - - unread_rune causes the next call to read_rune to return the last rune read. - If the last operation was not a successful call to read_rune, unread_rune may - return an error, unread the last rune read (or the rune prior to the - last-unread rune), or (in implementations that support the [Seeker] trait) - seek to the start of the rune before the current offset.""" - - fn unread_rune(inout self) -> Rune: - ... - - -trait StringWriter: - """StringWriter is the trait that wraps the WriteString method.""" - - fn write_string(inout self, src: String) -> (Int, Error): - ... diff --git a/external/gojo/net/__init__.mojo b/external/gojo/net/__init__.mojo deleted file mode 100644 index 25876739..00000000 --- a/external/gojo/net/__init__.mojo +++ /dev/null @@ -1,4 +0,0 @@ -"""Adapted from go's net package - -A good chunk of the leg work here came from the lightbug_http project! https://github.com/saviorand/lightbug_http/tree/main -""" diff --git a/external/gojo/net/address.mojo b/external/gojo/net/address.mojo deleted file mode 100644 index 9bf5a50a..00000000 --- a/external/gojo/net/address.mojo +++ /dev/null @@ -1,145 +0,0 @@ -@value -struct NetworkType: - var value: String - - alias empty = NetworkType("") - alias tcp = NetworkType("tcp") - alias tcp4 = NetworkType("tcp4") - alias tcp6 = NetworkType("tcp6") - alias udp = NetworkType("udp") - alias udp4 = NetworkType("udp4") - alias udp6 = NetworkType("udp6") - alias ip = NetworkType("ip") - alias ip4 = NetworkType("ip4") - alias ip6 = NetworkType("ip6") - alias unix = NetworkType("unix") - - -trait Addr(CollectionElement, Stringable): - fn network(self) -> String: - """Name of the network (for example, "tcp", "udp").""" - ... - - -@value -struct TCPAddr(Addr): - """Addr struct representing a TCP address. - - Args: - ip: IP address. - port: Port number. - zone: IPv6 addressing zone. - """ - - var ip: String - var port: Int - var zone: String # IPv6 addressing zone - - fn __init__(inout self): - self.ip = String("127.0.0.1") - self.port = 8000 - self.zone = "" - - fn __init__(inout self, ip: String, port: Int): - self.ip = ip - self.port = port - self.zone = "" - - fn __str__(self) -> String: - if self.zone != "": - return join_host_port(str(self.ip) + "%" + self.zone, str(self.port)) - return join_host_port(self.ip, str(self.port)) - - fn network(self) -> String: - return NetworkType.tcp.value - - -fn resolve_internet_addr(network: String, address: String) raises -> TCPAddr: - var host: String = "" - var port: String = "" - var portnum: Int = 0 - if ( - network == NetworkType.tcp.value - or network == NetworkType.tcp4.value - or network == NetworkType.tcp6.value - or network == NetworkType.udp.value - or network == NetworkType.udp4.value - or network == NetworkType.udp6.value - ): - if address != "": - var host_port = split_host_port(address) - host = host_port.host - port = str(host_port.port) - portnum = atol(port.__str__()) - elif network == NetworkType.ip.value or network == NetworkType.ip4.value or network == NetworkType.ip6.value: - if address != "": - host = address - elif network == NetworkType.unix.value: - raise Error("Unix addresses not supported yet") - else: - raise Error("unsupported network type: " + network) - return TCPAddr(host, portnum) - - -alias missingPortError = Error("missing port in address") -alias tooManyColonsError = Error("too many colons in address") - - -struct HostPort(Stringable): - var host: String - var port: Int - - fn __init__(inout self, host: String, port: Int): - self.host = host - self.port = port - - fn __str__(self) -> String: - return join_host_port(self.host, str(self.port)) - - -fn join_host_port(host: String, port: String) -> String: - if host.find(":") != -1: # must be IPv6 literal - return "[" + host + "]:" + port - return host + ":" + port - - -fn split_host_port(hostport: String) raises -> HostPort: - var host: String = "" - var port: String = "" - var colon_index = hostport.rfind(":") - var j: Int = 0 - var k: Int = 0 - - if colon_index == -1: - raise missingPortError - if hostport[0] == "[": - var end_bracket_index = hostport.find("]") - if end_bracket_index == -1: - raise Error("missing ']' in address") - if end_bracket_index + 1 == len(hostport): - raise missingPortError - elif end_bracket_index + 1 == colon_index: - host = hostport[1:end_bracket_index] - j = 1 - k = end_bracket_index + 1 - else: - if hostport[end_bracket_index + 1] == ":": - raise tooManyColonsError - else: - raise missingPortError - else: - host = hostport[:colon_index] - if host.find(":") != -1: - raise tooManyColonsError - if hostport[j:].find("[") != -1: - raise Error("unexpected '[' in address") - if hostport[k:].find("]") != -1: - raise Error("unexpected ']' in address") - port = hostport[colon_index + 1 :] - - if port == "": - raise missingPortError - if host == "": - raise Error("missing host") - - return HostPort(host, atol(port)) diff --git a/external/gojo/net/dial.mojo b/external/gojo/net/dial.mojo deleted file mode 100644 index f5719e67..00000000 --- a/external/gojo/net/dial.mojo +++ /dev/null @@ -1,44 +0,0 @@ -from .tcp import TCPAddr, TCPConnection, resolve_internet_addr -from .socket import Socket -from .address import split_host_port - - -@value -struct Dialer: - var local_address: TCPAddr - - fn dial(self, network: String, address: String) raises -> TCPConnection: - var tcp_addr = resolve_internet_addr(network, address) - var socket = Socket(local_address=self.local_address) - socket.connect(tcp_addr.ip, tcp_addr.port) - return TCPConnection(socket^) - - -fn dial_tcp(network: String, remote_address: TCPAddr) raises -> TCPConnection: - """Connects to the address on the named network. - - The network must be "tcp", "tcp4", or "tcp6". - Args: - network: The network type. - remote_address: The remote address to connect to. - - Returns: - The TCP connection. - """ - # TODO: Add conversion of domain name to ip address - return Dialer(remote_address).dial(network, remote_address.ip + ":" + str(remote_address.port)) - - -fn dial_tcp(network: String, remote_address: String) raises -> TCPConnection: - """Connects to the address on the named network. - - The network must be "tcp", "tcp4", or "tcp6". - Args: - network: The network type. - remote_address: The remote address to connect to. - - Returns: - The TCP connection. - """ - var address = split_host_port(remote_address) - return Dialer(TCPAddr(address.host, address.port)).dial(network, remote_address) diff --git a/external/gojo/net/fd.mojo b/external/gojo/net/fd.mojo deleted file mode 100644 index 6e4fc621..00000000 --- a/external/gojo/net/fd.mojo +++ /dev/null @@ -1,77 +0,0 @@ -from collections.optional import Optional -import ..io -from ..builtins import Byte -from ..syscall.file import close -from ..syscall.types import c_char -from ..syscall.net import ( - recv, - send, - strlen, -) - -alias O_RDWR = 0o2 - - -trait FileDescriptorBase(io.Reader, io.Writer, io.Closer): - ... - - -struct FileDescriptor(FileDescriptorBase): - var fd: Int - var is_closed: Bool - - # This takes ownership of a POSIX file descriptor. - fn __moveinit__(inout self, owned existing: Self): - self.fd = existing.fd - self.is_closed = existing.is_closed - - fn __init__(inout self, fd: Int): - self.fd = fd - self.is_closed = False - - fn __del__(owned self): - if not self.is_closed: - var err = self.close() - if err: - print(str(err)) - - fn close(inout self) -> Error: - """Mark the file descriptor as closed.""" - var close_status = close(self.fd) - if close_status == -1: - return Error("FileDescriptor.close: Failed to close socket") - - self.is_closed = True - return Error() - - fn dup(self) -> Self: - """Duplicate the file descriptor.""" - var new_fd = external_call["dup", Int, Int](self.fd) - return Self(new_fd) - - # TODO: Need faster approach to copying data from the file descriptor to the buffer. - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - """Receive data from the file descriptor and write it to the buffer provided.""" - var ptr = Pointer[UInt8]().alloc(dest.capacity) - var bytes_received = recv(self.fd, ptr, dest.capacity, 0) - if bytes_received == -1: - return 0, Error("Failed to receive message from socket.") - - var int8_ptr = ptr.bitcast[Int8]() - for i in range(bytes_received): - dest.append(int8_ptr[i]) - - if bytes_received < dest.capacity: - return bytes_received, Error(io.EOF) - - return bytes_received, Error() - - fn write(inout self, src: List[Byte]) -> (Int, Error): - """Write data from the buffer to the file descriptor.""" - var header_pointer = Pointer[Int8](src.data.address).bitcast[UInt8]() - - var bytes_sent = send(self.fd, header_pointer, strlen(header_pointer), 0) - if bytes_sent == -1: - return 0, Error("Failed to send message") - - return bytes_sent, Error() diff --git a/external/gojo/net/ip.mojo b/external/gojo/net/ip.mojo deleted file mode 100644 index e76d5cc3..00000000 --- a/external/gojo/net/ip.mojo +++ /dev/null @@ -1,179 +0,0 @@ -from utils.variant import Variant -from utils.static_tuple import StaticTuple -from sys.info import os_is_linux, os_is_macos -from ..syscall.types import ( - c_int, - c_char, - c_void, - c_uint, -) -from ..syscall.net import ( - addrinfo, - addrinfo_unix, - AF_INET, - SOCK_STREAM, - AI_PASSIVE, - sockaddr, - sockaddr_in, - htons, - ntohs, - inet_pton, - inet_ntop, - getaddrinfo, - getaddrinfo_unix, - gai_strerror, - to_char_ptr, - c_charptr_to_string, -) - -alias AddrInfo = Variant[addrinfo, addrinfo_unix] - - -fn get_addr_info(host: String) raises -> AddrInfo: - var status: Int32 = 0 - if os_is_macos(): - var servinfo = Pointer[addrinfo]().alloc(1) - servinfo.store(addrinfo()) - var hints = addrinfo() - hints.ai_family = AF_INET - hints.ai_socktype = SOCK_STREAM - hints.ai_flags = AI_PASSIVE - - var host_ptr = to_char_ptr(host) - - var status = getaddrinfo( - host_ptr, - Pointer[UInt8](), - Pointer.address_of(hints), - Pointer.address_of(servinfo), - ) - if status != 0: - print("getaddrinfo failed to execute with status:", status) - var msg_ptr = gai_strerror(c_int(status)) - _ = external_call["printf", c_int, Pointer[c_char], Pointer[c_char]]( - to_char_ptr("gai_strerror: %s"), msg_ptr - ) - var msg = c_charptr_to_string(msg_ptr) - print("getaddrinfo error message: ", msg) - - if not servinfo: - print("servinfo is null") - raise Error("Failed to get address info. Pointer to addrinfo is null.") - - return servinfo.load() - elif os_is_linux(): - var servinfo = Pointer[addrinfo_unix]().alloc(1) - servinfo.store(addrinfo_unix()) - var hints = addrinfo_unix() - hints.ai_family = AF_INET - hints.ai_socktype = SOCK_STREAM - hints.ai_flags = AI_PASSIVE - - var host_ptr = to_char_ptr(host) - - var status = getaddrinfo_unix( - host_ptr, - Pointer[UInt8](), - Pointer.address_of(hints), - Pointer.address_of(servinfo), - ) - if status != 0: - print("getaddrinfo failed to execute with status:", status) - var msg_ptr = gai_strerror(c_int(status)) - _ = external_call["printf", c_int, Pointer[c_char], Pointer[c_char]]( - to_char_ptr("gai_strerror: %s"), msg_ptr - ) - var msg = c_charptr_to_string(msg_ptr) - print("getaddrinfo error message: ", msg) - - if not servinfo: - print("servinfo is null") - raise Error("Failed to get address info. Pointer to addrinfo is null.") - - return servinfo.load() - else: - raise Error("Windows is not supported yet! Sorry!") - - -fn get_ip_address(host: String) raises -> String: - """Get the IP address of a host.""" - # Call getaddrinfo to get the IP address of the host. - var result = get_addr_info(host) - var ai_addr: Pointer[sockaddr] - var address_family: Int32 = 0 - var address_length: UInt32 = 0 - if result.isa[addrinfo](): - var addrinfo = result[addrinfo] - ai_addr = addrinfo.ai_addr - address_family = addrinfo.ai_family - address_length = addrinfo.ai_addrlen - else: - var addrinfo = result[addrinfo_unix] - ai_addr = addrinfo.ai_addr - address_family = addrinfo.ai_family - address_length = addrinfo.ai_addrlen - - if not ai_addr: - print("ai_addr is null") - raise Error("Failed to get IP address. getaddrinfo was called successfully, but ai_addr is null.") - - # Cast sockaddr struct to sockaddr_in struct and convert the binary IP to a string using inet_ntop. - var addr_in = ai_addr.bitcast[sockaddr_in]().load() - - return convert_binary_ip_to_string(addr_in.sin_addr.s_addr, address_family, address_length).strip() - - -fn convert_port_to_binary(port: Int) -> UInt16: - return htons(UInt16(port)) - - -fn convert_binary_port_to_int(port: UInt16) -> Int: - return int(ntohs(port)) - - -fn convert_ip_to_binary(ip_address: String, address_family: Int) -> UInt32: - var ip_buffer = Pointer[c_void].alloc(4) - var status = inet_pton(address_family, to_char_ptr(ip_address), ip_buffer) - if status == -1: - print("Failed to convert IP address to binary") - - return ip_buffer.bitcast[c_uint]().load() - - -fn convert_binary_ip_to_string(owned ip_address: UInt32, address_family: Int32, address_length: UInt32) -> String: - """Convert a binary IP address to a string by calling inet_ntop. - - Args: - ip_address: The binary IP address. - address_family: The address family of the IP address. - address_length: The length of the address. - - Returns: - The IP address as a string. - """ - # It seems like the len of the buffer depends on the length of the string IP. - # Allocating 10 works for localhost (127.0.0.1) which I suspect is 9 bytes + 1 null terminator byte. So max should be 16 (15 + 1). - var ip_buffer = Pointer[c_void].alloc(16) - var ip_address_ptr = Pointer.address_of(ip_address).bitcast[c_void]() - _ = inet_ntop(address_family, ip_address_ptr, ip_buffer, 16) - - var string_buf = ip_buffer.bitcast[Int8]() - var index = 0 - while True: - if string_buf[index] == 0: - break - index += 1 - - return StringRef(string_buf, index) - - -fn build_sockaddr_pointer(ip_address: String, port: Int, address_family: Int) -> Pointer[sockaddr]: - """Build a sockaddr pointer from an IP address and port number. - https://learn.microsoft.com/en-us/windows/win32/winsock/sockaddr-2 - https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-sockaddr_in. - """ - var bin_port = convert_port_to_binary(port) - var bin_ip = convert_ip_to_binary(ip_address, address_family) - - var ai = sockaddr_in(address_family, bin_port, bin_ip, StaticTuple[c_char, 8]()) - return Pointer[sockaddr_in].address_of(ai).bitcast[sockaddr]() diff --git a/external/gojo/net/net.mojo b/external/gojo/net/net.mojo deleted file mode 100644 index 74387d40..00000000 --- a/external/gojo/net/net.mojo +++ /dev/null @@ -1,130 +0,0 @@ -from memory.arc import Arc -import ..io -from ..builtins import Byte -from .socket import Socket -from .address import Addr, TCPAddr - -alias DEFAULT_BUFFER_SIZE = 8200 - - -trait Conn(io.Writer, io.Reader, io.Closer): - fn __init__(inout self, owned socket: Socket): - ... - - """Conn is a generic stream-oriented network connection.""" - - fn local_address(self) -> TCPAddr: - """Returns the local network address, if known.""" - ... - - fn remote_address(self) -> TCPAddr: - """Returns the local network address, if known.""" - ... - - # fn set_deadline(self, t: time.Time) -> Error: - # """Sets the read and write deadlines associated - # with the connection. It is equivalent to calling both - # SetReadDeadline and SetWriteDeadline. - - # A deadline is an absolute time after which I/O operations - # fail instead of blocking. The deadline applies to all future - # and pending I/O, not just the immediately following call to - # read or write. After a deadline has been exceeded, the - # connection can be refreshed by setting a deadline in the future. - - # If the deadline is exceeded a call to read or write or to other - # I/O methods will return an error that wraps os.ErrDeadlineExceeded. - # This can be tested using errors.Is(err, os.ErrDeadlineExceeded). - # The error's Timeout method will return true, but note that there - # are other possible errors for which the Timeout method will - # return true even if the deadline has not been exceeded. - - # An idle timeout can be implemented by repeatedly extending - # the deadline after successful read or write calls. - - # A zero value for t means I/O operations will not time out.""" - # ... - - # fn set_read_deadline(self, t: time.Time) -> Error: - # """Sets the deadline for future read calls - # and any currently-blocked read call. - # A zero value for t means read will not time out.""" - # ... - - # fn set_write_deadline(self, t: time.Time) -> Error: - # """Sets the deadline for future write calls - # and any currently-blocked write call. - # Even if write times out, it may return n > 0, indicating that - # some of the data was successfully written. - # A zero value for t means write will not time out.""" - # ... - - -@value -struct Connection(Conn): - """Connection is a concrete generic stream-oriented network connection. - It is used as the internal connection for structs like TCPConnection. - - Args: - fd: The file descriptor of the connection. - """ - - var fd: Arc[Socket] - - fn __init__(inout self, owned socket: Socket): - self.fd = Arc(socket^) - - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - """Reads data from the underlying file descriptor. - - Args: - dest: The buffer to read data into. - - Returns: - The number of bytes read, or an error if one occurred. - """ - var bytes_written: Int = 0 - var err = Error() - bytes_written, err = self.fd[].read(dest) - if err: - if str(err) != io.EOF: - return 0, err - - return bytes_written, err - - fn write(inout self, src: List[Byte]) -> (Int, Error): - """Writes data to the underlying file descriptor. - - Args: - src: The buffer to read data into. - - Returns: - The number of bytes written, or an error if one occurred. - """ - var bytes_read: Int = 0 - var err = Error() - bytes_read, err = self.fd[].write(src) - if err: - return 0, err - - return bytes_read, err - - fn close(inout self) -> Error: - """Closes the underlying file descriptor. - - Returns: - An error if one occurred, or None if the file descriptor was closed successfully. - """ - return self.fd[].close() - - fn local_address(self) -> TCPAddr: - """Returns the local network address. - The Addr returned is shared by all invocations of local_address, so do not modify it. - """ - return self.fd[].local_address - - fn remote_address(self) -> TCPAddr: - """Returns the remote network address. - The Addr returned is shared by all invocations of remote_address, so do not modify it. - """ - return self.fd[].remote_address diff --git a/external/gojo/net/socket.mojo b/external/gojo/net/socket.mojo deleted file mode 100644 index e019255e..00000000 --- a/external/gojo/net/socket.mojo +++ /dev/null @@ -1,432 +0,0 @@ -from collections.optional import Optional -from ..builtins import Byte -from ..syscall.file import close -from ..syscall.types import ( - c_void, - c_uint, - c_char, - c_int, -) -from ..syscall.net import ( - sockaddr, - sockaddr_in, - addrinfo, - addrinfo_unix, - socklen_t, - socket, - connect, - recv, - send, - shutdown, - inet_pton, - inet_ntoa, - inet_ntop, - to_char_ptr, - htons, - ntohs, - strlen, - getaddrinfo, - getaddrinfo_unix, - gai_strerror, - c_charptr_to_string, - bind, - listen, - accept, - setsockopt, - getsockopt, - getsockname, - getpeername, - AF_INET, - SOCK_STREAM, - SHUT_RDWR, - AI_PASSIVE, - SOL_SOCKET, - SO_REUSEADDR, - SO_RCVTIMEO, -) -from .fd import FileDescriptor, FileDescriptorBase -from .ip import ( - convert_binary_ip_to_string, - build_sockaddr_pointer, - convert_binary_port_to_int, -) -from .address import Addr, TCPAddr, HostPort - -alias SocketClosedError = Error("Socket: Socket is already closed") - - -struct Socket(FileDescriptorBase): - """Represents a network file descriptor. Wraps around a file descriptor and provides network functions. - - Args: - local_address: The local address of the socket (local address if bound). - remote_address: The remote address of the socket (peer's address if connected). - address_family: The address family of the socket. - socket_type: The socket type. - protocol: The protocol. - """ - - var sockfd: FileDescriptor - var address_family: Int - var socket_type: UInt8 - var protocol: UInt8 - var local_address: TCPAddr - var remote_address: TCPAddr - var _closed: Bool - var _is_connected: Bool - - fn __init__( - inout self, - local_address: TCPAddr = TCPAddr(), - remote_address: TCPAddr = TCPAddr(), - address_family: Int = AF_INET, - socket_type: UInt8 = SOCK_STREAM, - protocol: UInt8 = 0, - ) raises: - """Create a new socket object. - - Args: - local_address: The local address of the socket (local address if bound). - remote_address: The remote address of the socket (peer's address if connected). - address_family: The address family of the socket. - socket_type: The socket type. - protocol: The protocol. - """ - self.address_family = address_family - self.socket_type = socket_type - self.protocol = protocol - - var fd = socket(address_family, SOCK_STREAM, 0) - if fd == -1: - raise Error("Socket creation error") - self.sockfd = FileDescriptor(int(fd)) - self.local_address = local_address - self.remote_address = remote_address - self._closed = False - self._is_connected = False - - fn __init__( - inout self, - fd: Int32, - address_family: Int, - socket_type: UInt8, - protocol: UInt8, - local_address: TCPAddr = TCPAddr(), - remote_address: TCPAddr = TCPAddr(), - ): - """ - Create a new socket object when you already have a socket file descriptor. Typically through socket.accept(). - - Args: - fd: The file descriptor of the socket. - address_family: The address family of the socket. - socket_type: The socket type. - protocol: The protocol. - local_address: Local address of socket. - remote_address: Remote address of port. - """ - self.sockfd = FileDescriptor(int(fd)) - self.address_family = address_family - self.socket_type = socket_type - self.protocol = protocol - self.local_address = local_address - self.remote_address = remote_address - self._closed = False - self._is_connected = True - - fn __moveinit__(inout self, owned existing: Self): - self.sockfd = existing.sockfd^ - self.address_family = existing.address_family - self.socket_type = existing.socket_type - self.protocol = existing.protocol - self.local_address = existing.local_address^ - self.remote_address = existing.remote_address^ - self._closed = existing._closed - self._is_connected = existing._is_connected - - # fn __enter__(self) -> Self: - # return self - - # fn __exit__(inout self) raises: - # if self._is_connected: - # self.shutdown() - # if not self._closed: - # self.close() - - fn __del__(owned self): - if self._is_connected: - self.shutdown() - if not self._closed: - var err = self.close() - _ = self.sockfd.fd - if err: - print("Failed to close socket during deletion:", str(err)) - - @always_inline - fn accept(self) raises -> Self: - """Accept a connection. The socket must be bound to an address and listening for connections. - The return value is a connection where conn is a new socket object usable to send and receive data on the connection, - and address is the address bound to the socket on the other end of the connection. - """ - var their_addr_ptr = Pointer[sockaddr].alloc(1) - var sin_size = socklen_t(sizeof[socklen_t]()) - var new_sockfd = accept(self.sockfd.fd, their_addr_ptr, Pointer[socklen_t].address_of(sin_size)) - if new_sockfd == -1: - raise Error("Failed to accept connection") - - var remote = self.get_peer_name() - return Self( - new_sockfd, - self.address_family, - self.socket_type, - self.protocol, - self.local_address, - TCPAddr(remote.host, remote.port), - ) - - fn listen(self, backlog: Int = 0) raises: - """Enable a server to accept connections. - - Args: - backlog: The maximum number of queued connections. Should be at least 0, and the maximum is system-dependent (usually 5). - """ - var queued = backlog - if backlog < 0: - queued = 0 - if listen(self.sockfd.fd, queued) == -1: - raise Error("Failed to listen for connections") - - @always_inline - fn bind(inout self, address: String, port: Int) raises: - """Bind the socket to address. The socket must not already be bound. (The format of address depends on the address family). - - When a socket is created with Socket(), it exists in a name - space (address family) but has no address assigned to it. bind() - assigns the address specified by addr to the socket referred to - by the file descriptor sockfd. addrlen specifies the size, in - bytes, of the address structure pointed to by addr. - Traditionally, this operation is called 'assigning a name to a - socket'. - - Args: - address: String - The IP address to bind the socket to. - port: The port number to bind the socket to. - """ - var sockaddr_pointer = build_sockaddr_pointer(address, port, self.address_family) - - if bind(self.sockfd.fd, sockaddr_pointer, sizeof[sockaddr_in]()) == -1: - _ = shutdown(self.sockfd.fd, SHUT_RDWR) - raise Error("Binding socket failed. Wait a few seconds and try again?") - - var local = self.get_sock_name() - self.local_address = TCPAddr(local.host, local.port) - - @always_inline - fn file_no(self) -> Int32: - """Return the file descriptor of the socket.""" - return self.sockfd.fd - - @always_inline - fn get_sock_name(self) raises -> HostPort: - """Return the address of the socket.""" - if self._closed: - raise SocketClosedError - - # TODO: Add check to see if the socket is bound and error if not. - - var local_address_ptr = Pointer[sockaddr].alloc(1) - var local_address_ptr_size = socklen_t(sizeof[sockaddr]()) - var status = getsockname( - self.sockfd.fd, - local_address_ptr, - Pointer[socklen_t].address_of(local_address_ptr_size), - ) - if status == -1: - raise Error("Socket.get_sock_name: Failed to get address of local socket.") - var addr_in = local_address_ptr.bitcast[sockaddr_in]().load() - - return HostPort( - host=convert_binary_ip_to_string(addr_in.sin_addr.s_addr, AF_INET, 16), - port=convert_binary_port_to_int(addr_in.sin_port), - ) - - fn get_peer_name(self) raises -> HostPort: - """Return the address of the peer connected to the socket.""" - if self._closed: - raise SocketClosedError - - # TODO: Add check to see if the socket is bound and error if not. - var remote_address_ptr = Pointer[sockaddr].alloc(1) - var remote_address_ptr_size = socklen_t(sizeof[sockaddr]()) - var status = getpeername( - self.sockfd.fd, - remote_address_ptr, - Pointer[socklen_t].address_of(remote_address_ptr_size), - ) - if status == -1: - raise Error("Socket.get_peer_name: Failed to get address of remote socket.") - - # Cast sockaddr struct to sockaddr_in to convert binary IP to string. - var addr_in = remote_address_ptr.bitcast[sockaddr_in]().load() - - return HostPort( - host=convert_binary_ip_to_string(addr_in.sin_addr.s_addr, AF_INET, 16), - port=convert_binary_port_to_int(addr_in.sin_port), - ) - - fn get_socket_option(self, option_name: Int) raises -> Int: - """Return the value of the given socket option. - - Args: - option_name: The socket option to get. - """ - var option_value_pointer = Pointer[c_void].alloc(1) - var option_len = socklen_t(sizeof[socklen_t]()) - var option_len_pointer = Pointer.address_of(option_len) - var status = getsockopt( - self.sockfd.fd, - SOL_SOCKET, - option_name, - option_value_pointer, - option_len_pointer, - ) - if status == -1: - raise Error("Socket.get_sock_opt failed with status: " + str(status)) - - return option_value_pointer.bitcast[Int]().load() - - fn set_socket_option(self, option_name: Int, owned option_value: UInt8 = 1) raises: - """Return the value of the given socket option. - - Args: - option_name: The socket option to set. - option_value: The value to set the socket option to. - """ - var option_value_pointer = Pointer[c_void].address_of(option_value) - var option_len = sizeof[socklen_t]() - var status = setsockopt(self.sockfd.fd, SOL_SOCKET, option_name, option_value_pointer, option_len) - if status == -1: - raise Error("Socket.set_sock_opt failed with status: " + str(status)) - - fn connect(inout self, address: String, port: Int) raises: - """Connect to a remote socket at address. - - Args: - address: String - The IP address to connect to. - port: The port number to connect to. - """ - var sockaddr_pointer = build_sockaddr_pointer(address, port, self.address_family) - - if connect(self.sockfd.fd, sockaddr_pointer, sizeof[sockaddr_in]()) == -1: - self.shutdown() - raise Error("Socket.connect: Failed to connect to the remote socket at: " + address + ":" + str(port)) - - var remote = self.get_peer_name() - self.remote_address = TCPAddr(remote.host, remote.port) - - fn write(inout self: Self, src: List[Byte]) -> (Int, Error): - """Send data to the socket. The socket must be connected to a remote socket. - - Args: - src: The data to send. - - Returns: - The number of bytes sent. - """ - var bytes_written: Int - var err: Error - bytes_written, err = self.sockfd.write(src) - if err: - return 0, err - - return bytes_written, Error() - - fn send_all(self, src: List[Byte], max_attempts: Int = 3) raises: - """Send data to the socket. The socket must be connected to a remote socket. - - Args: - src: The data to send. - max_attempts: The maximum number of attempts to send the data. - """ - var header_pointer = src.unsafe_ptr() - var total_bytes_sent = 0 - var attempts = 0 - - # Try to send all the data in the buffer. If it did not send all the data, keep trying but start from the offset of the last successful send. - while total_bytes_sent < len(src): - if attempts > max_attempts: - raise Error("Failed to send message after " + str(max_attempts) + " attempts.") - - var bytes_sent = send( - self.sockfd.fd, - header_pointer.offset(total_bytes_sent), - strlen(header_pointer.offset(total_bytes_sent)), - 0, - ) - if bytes_sent == -1: - raise Error("Failed to send message, wrote" + String(total_bytes_sent) + "bytes before failing.") - total_bytes_sent += bytes_sent - attempts += 1 - - fn send_to(inout self, src: List[Byte], address: String, port: Int) raises -> Int: - """Send data to the a remote address by connecting to the remote socket before sending. - The socket must be not already be connected to a remote socket. - - Args: - src: The data to send. - address: The IP address to connect to. - port: The port number to connect to. - """ - var header_pointer = Pointer[Int8](src.data.address).bitcast[UInt8]() - self.connect(address, port) - var bytes_written: Int - var err: Error - bytes_written, err = self.write(src) - if err: - raise err - return bytes_written - - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - """Receive data from the socket.""" - # Not ideal since we can't use the pointer from the List[Byte] struct directly. So we use a temporary pointer to receive the data. - # Then we copy all the data over. - var bytes_written: Int - var err: Error - bytes_written, err = self.sockfd.read(dest) - if err: - if str(err) != "EOF": - return 0, err - - return bytes_written, Error() - - fn shutdown(self): - _ = shutdown(self.sockfd.fd, SHUT_RDWR) - - fn close(inout self) -> Error: - """Mark the socket closed. - Once that happens, all future operations on the socket object will fail. - The remote end will receive no more data (after queued data is flushed). - """ - self.shutdown() - var err = self.sockfd.close() - if err: - return err - - self._closed = True - return Error() - - # TODO: Trying to set timeout fails, but some other options don't? - # fn get_timeout(self) raises -> Seconds: - # """Return the timeout value for the socket.""" - # return self.get_socket_option(SO_RCVTIMEO) - - # fn set_timeout(self, owned duration: Seconds) raises: - # """Set the timeout value for the socket. - - # Args: - # duration: Seconds - The timeout duration in seconds. - # """ - # self.set_socket_option(SO_RCVTIMEO, duration) - - fn send_file(self, file: FileHandle, offset: Int = 0) raises: - self.send_all(file.read_bytes()) diff --git a/external/gojo/net/tcp.mojo b/external/gojo/net/tcp.mojo deleted file mode 100644 index 433bca95..00000000 --- a/external/gojo/net/tcp.mojo +++ /dev/null @@ -1,207 +0,0 @@ -from ..builtins import Byte -from ..syscall.net import SO_REUSEADDR -from .net import Connection, Conn -from .address import TCPAddr, NetworkType, split_host_port -from .socket import Socket - - -# Time in nanoseconds -alias Duration = Int -alias DEFAULT_BUFFER_SIZE = 8200 -alias DEFAULT_TCP_KEEP_ALIVE = Duration(15 * 1000 * 1000 * 1000) # 15 seconds - - -fn resolve_internet_addr(network: String, address: String) raises -> TCPAddr: - var host: String = "" - var port: String = "" - var portnum: Int = 0 - if ( - network == NetworkType.tcp.value - or network == NetworkType.tcp4.value - or network == NetworkType.tcp6.value - or network == NetworkType.udp.value - or network == NetworkType.udp4.value - or network == NetworkType.udp6.value - ): - if address != "": - var host_port = split_host_port(address) - host = host_port.host - port = str(host_port.port) - portnum = atol(port.__str__()) - elif network == NetworkType.ip.value or network == NetworkType.ip4.value or network == NetworkType.ip6.value: - if address != "": - host = address - elif network == NetworkType.unix.value: - raise Error("Unix addresses not supported yet") - else: - raise Error("unsupported network type: " + network) - return TCPAddr(host, portnum) - - -# TODO: For now listener is paired with TCP until we need to support -# more than one type of Connection or Listener -@value -struct ListenConfig(CollectionElement): - var keep_alive: Duration - - fn listen(self, network: String, address: String) raises -> TCPListener: - var tcp_addr = resolve_internet_addr(network, address) - var socket = Socket(local_address=tcp_addr) - socket.bind(tcp_addr.ip, tcp_addr.port) - socket.set_socket_option(SO_REUSEADDR, 1) - socket.listen() - print(str("Listening on ") + str(socket.local_address)) - return TCPListener(socket^, self, network, address) - - -trait Listener(Movable): - # Raising here because a Result[Optional[Connection], Error] is funky. - fn accept(self) raises -> Connection: - ... - - fn close(inout self) -> Error: - ... - - fn addr(self) raises -> TCPAddr: - ... - - -@value -struct TCPConnection(Conn): - """TCPConn is an implementation of the Conn interface for TCP network connections. - - Args: - connection: The underlying Connection. - """ - - var _connection: Connection - - fn __init__(inout self, connection: Connection): - self._connection = connection - - fn __init__(inout self, owned socket: Socket): - self._connection = Connection(socket^) - - fn __moveinit__(inout self, owned existing: Self): - self._connection = existing._connection^ - - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - """Reads data from the underlying file descriptor. - - Args: - dest: The buffer to read data into. - - Returns: - The number of bytes read, or an error if one occurred. - """ - var bytes_written: Int - var err: Error - bytes_written, err = self._connection.read(dest) - if err: - if str(err) != io.EOF: - return 0, err - - return bytes_written, Error() - - fn write(inout self, src: List[Byte]) -> (Int, Error): - """Writes data to the underlying file descriptor. - - Args: - src: The buffer to read data into. - - Returns: - The number of bytes written, or an error if one occurred. - """ - var bytes_written: Int - var err: Error - bytes_written, err = self._connection.write(src) - if err: - return 0, err - - return bytes_written, Error() - - fn close(inout self) -> Error: - """Closes the underlying file descriptor. - - Returns: - An error if one occurred, or None if the file descriptor was closed successfully. - """ - return self._connection.close() - - fn local_address(self) -> TCPAddr: - """Returns the local network address. - The Addr returned is shared by all invocations of local_address, so do not modify it. - - Returns: - The local network address. - """ - return self._connection.local_address() - - fn remote_address(self) -> TCPAddr: - """Returns the remote network address. - The Addr returned is shared by all invocations of remote_address, so do not modify it. - - Returns: - The remote network address. - """ - return self._connection.remote_address() - - -fn listen_tcp(network: String, local_address: TCPAddr) raises -> TCPListener: - """Creates a new TCP listener. - - Args: - network: The network type. - local_address: The local address to listen on. - """ - return ListenConfig(DEFAULT_TCP_KEEP_ALIVE).listen(network, local_address.ip + ":" + str(local_address.port)) - - -fn listen_tcp(network: String, local_address: String) raises -> TCPListener: - """Creates a new TCP listener. - - Args: - network: The network type. - local_address: The address to listen on. The format is "host:port". - """ - return ListenConfig(DEFAULT_TCP_KEEP_ALIVE).listen(network, local_address) - - -struct TCPListener(Listener): - var _file_descriptor: Socket - var listen_config: ListenConfig - var network_type: String - var address: String - - fn __init__( - inout self, - owned file_descriptor: Socket, - listen_config: ListenConfig, - network_type: String, - address: String, - ): - self._file_descriptor = file_descriptor^ - self.listen_config = listen_config - self.network_type = network_type - self.address = address - - fn __moveinit__(inout self, owned existing: Self): - self._file_descriptor = existing._file_descriptor^ - self.listen_config = existing.listen_config^ - self.network_type = existing.network_type - self.address = existing.address - - fn listen(self) raises -> Self: - return self.listen_config.listen(self.network_type, self.address) - - fn accept(self) raises -> Connection: - return Connection(self._file_descriptor.accept()) - - fn accept_tcp(self) raises -> TCPConnection: - return TCPConnection(self._file_descriptor.accept()) - - fn close(inout self) -> Error: - return self._file_descriptor.close() - - fn addr(self) raises -> TCPAddr: - return resolve_internet_addr(self.network_type, self.address) diff --git a/external/gojo/strings/__init__.mojo b/external/gojo/strings/__init__.mojo deleted file mode 100644 index fbcb44e8..00000000 --- a/external/gojo/strings/__init__.mojo +++ /dev/null @@ -1,2 +0,0 @@ -from .builder import StringBuilder -from .reader import Reader, new_reader diff --git a/external/gojo/strings/builder.mojo b/external/gojo/strings/builder.mojo deleted file mode 100644 index e4bdce99..00000000 --- a/external/gojo/strings/builder.mojo +++ /dev/null @@ -1,124 +0,0 @@ -import ..io -from ..builtins import Byte - - -@value -struct StringBuilder[growth_factor: Float32 = 2](Stringable, Sized, io.Writer, io.StringWriter): - """ - A string builder class that allows for efficient string management and concatenation. - This class is useful when you need to build a string by appending multiple strings - together. The performance increase is not linear. Compared to string concatenation, - I've observed around 20-30x faster for writing and rending ~4KB and up to 2100x-2300x - for ~4MB. This is because it avoids the overhead of creating and destroying many - intermediate strings and performs memcopy operations. - - The result is a more efficient when building larger string concatenations. It - is generally not recommended to use this class for small concatenations such as - a few strings like `a + b + c + d` because the overhead of creating the string - builder and appending the strings is not worth the performance gain. - - Example: - ``` - from strings.builder import StringBuilder - - var sb = StringBuilder() - sb.write_string("Hello ") - sb.write_string("World!") - print(sb) # Hello World! - ``` - """ - - var data: DTypePointer[DType.uint8] - var size: Int - var capacity: Int - - @always_inline - fn __init__(inout self, *, capacity: Int = 8200): - constrained[growth_factor >= 1.25]() - self.data = DTypePointer[DType.uint8]().alloc(capacity) - self.size = 0 - self.capacity = capacity - - @always_inline - fn __del__(owned self): - if self.data: - self.data.free() - - @always_inline - fn __len__(self) -> Int: - """ - Returns the length of the string builder. - - Returns: - The length of the string builder. - """ - return self.size - - @always_inline - fn __str__(self) -> String: - """ - Converts the string builder to a string. - - Returns: - The string representation of the string builder. Returns an empty - string if the string builder is empty. - """ - var copy = DTypePointer[DType.uint8]().alloc(self.size) - memcpy(copy, self.data, self.size) - return StringRef(copy, self.size) - - @always_inline - fn render(self) -> StringSlice[is_mutable=False, lifetime=ImmutableStaticLifetime]: - """ - Return a StringSlice view of the data owned by the builder. - Slightly faster than __str__, 10-20% faster in limited testing. - - Returns: - The string representation of the string builder. Returns an empty string if the string builder is empty. - """ - return StringSlice[is_mutable=False, lifetime=ImmutableStaticLifetime](unsafe_from_utf8_strref=StringRef(self.data, self.size)) - - @always_inline - fn _resize(inout self, capacity: Int) -> None: - """ - Resizes the string builder buffer. - - Args: - capacity: The new capacity of the string builder buffer. - """ - var new_data = DTypePointer[DType.uint8]().alloc(capacity) - memcpy(new_data, self.data, self.size) - self.data.free() - self.data = new_data - self.capacity = capacity - - return None - - @always_inline - fn write(inout self, src: Span[Byte]) -> (Int, Error): - """ - Appends a byte Span to the builder buffer. - - Args: - src: The byte array to append. - """ - if len(src) > self.capacity - self.size: - var new_capacity = int(self.capacity * growth_factor) - if new_capacity < self.capacity + len(src): - new_capacity = self.capacity + len(src) - self._resize(new_capacity) - - memcpy(self.data.offset(self.size), src._data, len(src)) - self.size += len(src) - - return len(src), Error() - - @always_inline - fn write_string(inout self, src: String) -> (Int, Error): - """ - Appends a string to the builder buffer. - - Args: - src: The string to append. - """ - return self.write(src.as_bytes_slice()) diff --git a/external/gojo/strings/reader.mojo b/external/gojo/strings/reader.mojo deleted file mode 100644 index 8d8af287..00000000 --- a/external/gojo/strings/reader.mojo +++ /dev/null @@ -1,240 +0,0 @@ -import ..io -from ..builtins import Byte, copy, panic - - -@value -struct Reader(Sized, io.Reader, io.ReaderAt, io.ByteReader, io.ByteScanner, io.Seeker, io.WriterTo): - """A Reader that implements the [io.Reader], [io.ReaderAt], [io.ByteReader], [io.ByteScanner], [io.Seeker], and [io.WriterTo] traits - by reading from a string. The zero value for Reader operates like a Reader of an empty string. - """ - - var string: String - var read_pos: Int64 # current reading index - var prev_rune: Int # index of previous rune; or < 0 - - fn __init__(inout self, string: String = ""): - self.string = string - self.read_pos = 0 - self.prev_rune = -1 - - fn __len__(self) -> Int: - """Returns the number of bytes of the unread portion of the string. - - Returns: - int: the number of bytes of the unread portion of the string. - """ - if self.read_pos >= Int64(len(self.string)): - return 0 - - return int(Int64(len(self.string)) - self.read_pos) - - fn size(self) -> Int64: - """Returns the original length of the underlying string. - size is the number of bytes available for reading via [Reader.read_at]. - The returned value is always the same and is not affected by calls - to any other method. - - Returns: - The original length of the underlying string. - """ - return Int64(len(self.string)) - - fn read(inout self, inout dest: List[Byte]) -> (Int, Error): - """Reads from the underlying string into the provided List[Byte] object. - Implements the [io.Reader] trait. - - Args: - dest: The destination List[Byte] object to read into. - - Returns: - The number of bytes read into dest. - """ - if self.read_pos >= Int64(len(self.string)): - return 0, Error(io.EOF) - - self.prev_rune = -1 - var bytes_written = copy(dest, self.string[int(self.read_pos) :].as_bytes()) - self.read_pos += Int64(bytes_written) - return bytes_written, Error() - - fn read_at(self, inout dest: List[Byte], off: Int64) -> (Int, Error): - """Reads from the Reader into the dest List[Byte] starting at the offset off. - It returns the number of bytes read into dest and an error if any. - Implements the [io.ReaderAt] trait. - - Args: - dest: The destination List[Byte] object to read into. - off: The byte offset to start reading from. - - Returns: - The number of bytes read into dest. - """ - # cannot modify state - see io.ReaderAt - if off < 0: - return 0, Error("strings.Reader.read_at: negative offset") - - if off >= Int64(len(self.string)): - return 0, Error(io.EOF) - - var error = Error() - var copied_elements_count = copy(dest, self.string[int(off) :].as_bytes()) - if copied_elements_count < len(dest): - error = Error(io.EOF) - - return copied_elements_count, Error() - - fn read_byte(inout self) -> (Byte, Error): - """Reads the next byte from the underlying string. - Implements the [io.ByteReader] trait. - - Returns: - The next byte from the underlying string. - """ - self.prev_rune = -1 - if self.read_pos >= Int64(len(self.string)): - return Byte(0), Error(io.EOF) - - var b = self.string[int(self.read_pos)] - self.read_pos += 1 - return Byte(ord(b)), Error() - - fn unread_byte(inout self) -> Error: - """Unreads the last byte read. Only the most recent byte read can be unread. - Implements the [io.ByteScanner] trait. - """ - if self.read_pos <= 0: - return Error("strings.Reader.unread_byte: at beginning of string") - - self.prev_rune = -1 - self.read_pos -= 1 - - return Error() - - # # read_rune implements the [io.RuneReader] trait. - # fn read_rune() (ch rune, size int, err error): - # if self.read_pos >= Int64(len(self.string)): - # self.prev_rune = -1 - # return 0, 0, io.EOF - - # self.prev_rune = int(self.read_pos) - # if c = self.string[self.read_pos]; c < utf8.RuneSelf: - # self.read_pos += 1 - # return rune(c), 1, nil - - # ch, size = utf8.DecodeRuneInString(self.string[self.read_pos:]) - # self.read_pos += Int64(size) - # return - - # # unread_rune implements the [io.RuneScanner] trait. - # fn unread_rune() error: - # if self.read_pos <= 0: - # return errors.New("strings.Reader.unread_rune: at beginning of string") - - # if self.prev_rune < 0: - # return errors.New("strings.Reader.unread_rune: previous operation was not read_rune") - - # self.read_pos = Int64(self.prev_rune) - # self.prev_rune = -1 - # return nil - - fn seek(inout self, offset: Int64, whence: Int) -> (Int64, Error): - """Seeks to a new position in the underlying string. The next read will start from that position. - Implements the [io.Seeker] trait. - - Args: - offset: The offset to seek to. - whence: The seek mode. It can be one of [io.SEEK_START], [io.SEEK_CURRENT], or [io.SEEK_END]. - - Returns: - The new position in the string. - """ - self.prev_rune = -1 - var position: Int64 = 0 - - if whence == io.SEEK_START: - position = offset - elif whence == io.SEEK_CURRENT: - position = self.read_pos + offset - elif whence == io.SEEK_END: - position = Int64(len(self.string)) + offset - else: - return Int64(0), Error("strings.Reader.seek: invalid whence") - - if position < 0: - return Int64(0), Error("strings.Reader.seek: negative position") - - self.read_pos = position - return position, Error() - - fn write_to[W: io.Writer](inout self, inout writer: W) -> (Int64, Error): - """Writes the remaining portion of the underlying string to the provided writer. - Implements the [io.WriterTo] trait. - - Args: - writer: The writer to write the remaining portion of the string to. - - Returns: - The number of bytes written to the writer. - """ - self.prev_rune = -1 - if self.read_pos >= Int64(len(self.string)): - return Int64(0), Error() - - var chunk_to_write = self.string[int(self.read_pos) :] - var bytes_written: Int - var err: Error - bytes_written, err = io.write_string(writer, chunk_to_write) - if bytes_written > len(chunk_to_write): - panic("strings.Reader.write_to: invalid write_string count") - - self.read_pos += Int64(bytes_written) - if bytes_written != len(chunk_to_write) and not err: - err = Error(io.ERR_SHORT_WRITE) - - return Int64(bytes_written), err - - # TODO: How can I differentiate between the two write_to methods when the writer implements both traits? - # fn write_to[W: io.StringWriter](inout self, inout writer: W) raises -> Int64: - # """Writes the remaining portion of the underlying string to the provided writer. - # Implements the [io.WriterTo] trait. - - # Args: - # writer: The writer to write the remaining portion of the string to. - - # Returns: - # The number of bytes written to the writer. - # """ - # self.prev_rune = -1 - # if self.read_pos >= Int64(len(self.string)): - # return 0 - - # var chunk_to_write = self.string[self.read_pos:] - # var bytes_written = io.write_string(writer, chunk_to_write) - # if bytes_written > len(chunk_to_write): - # raise Error("strings.Reader.write_to: invalid write_string count") - - # self.read_pos += Int64(bytes_written) - # if bytes_written != len(chunk_to_write): - # raise Error(io.ERR_SHORT_WRITE) - - # return Int64(bytes_written) - - fn reset(inout self, string: String): - """Resets the [Reader] to be reading from the beginning of the provided string. - - Args: - string: The string to read from. - """ - self.string = string - self.read_pos = 0 - self.prev_rune = -1 - - -fn new_reader(string: String = "") -> Reader: - """Returns a new [Reader] reading from the provided string. - It is similar to [bytes.new_buffer] but more efficient and non-writable. - - Args: - string: The string to read from. - """ - return Reader(string) diff --git a/external/gojo/syscall/__init__.mojo b/external/gojo/syscall/__init__.mojo deleted file mode 100644 index d59a41f9..00000000 --- a/external/gojo/syscall/__init__.mojo +++ /dev/null @@ -1,24 +0,0 @@ -from .net import FD_STDIN, FD_STDOUT, FD_STDERR - -# Adapted from https://github.com/crisadamo/mojo-Libc . Huge thanks to Cristian! -# C types -alias c_void = UInt8 -alias c_char = UInt8 -alias c_schar = Int8 -alias c_uchar = UInt8 -alias c_short = Int16 -alias c_ushort = UInt16 -alias c_int = Int32 -alias c_uint = UInt32 -alias c_long = Int64 -alias c_ulong = UInt64 -alias c_float = Float32 -alias c_double = Float64 - -# `Int` is known to be machine's width -alias c_size_t = Int -alias c_ssize_t = Int - -alias ptrdiff_t = Int64 -alias intptr_t = Int64 -alias uintptr_t = UInt64 diff --git a/external/gojo/syscall/file.mojo b/external/gojo/syscall/file.mojo deleted file mode 100644 index 77f05a99..00000000 --- a/external/gojo/syscall/file.mojo +++ /dev/null @@ -1,62 +0,0 @@ -from . import c_int, c_char, c_void, c_size_t, c_ssize_t - - -# --- ( File Related Syscalls & Structs )--------------------------------------- -alias O_NONBLOCK = 16384 -alias O_ACCMODE = 3 -alias O_CLOEXEC = 524288 - - -fn close(fildes: c_int) -> c_int: - """Libc POSIX `close` function - Reference: https://man7.org/linux/man-pages/man3/close.3p.html - Fn signature: int close(int fildes). - - Args: - fildes: A File Descriptor to close. - - Returns: - Upon successful completion, 0 shall be returned; otherwise, -1 - shall be returned and errno set to indicate the error. - """ - return external_call["close", c_int, c_int](fildes) - - -fn open[*T: AnyType](path: UnsafePointer[c_char], oflag: c_int) -> c_int: - """Libc POSIX `open` function - Reference: https://man7.org/linux/man-pages/man3/open.3p.html - Fn signature: int open(const char *path, int oflag, ...). - - Args: - path: A pointer to a C string containing the path to open. - oflag: The flags to open the file with. - Returns: - A File Descriptor or -1 in case of failure - """ - return external_call["open", c_int, UnsafePointer[c_char], c_int](path, oflag) # FnName, RetType # Args - - -fn read(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: - """Libc POSIX `read` function - Reference: https://man7.org/linux/man-pages/man3/read.3p.html - Fn signature: sssize_t read(int fildes, void *buf, size_t nbyte). - - Args: fildes: A File Descriptor. - buf: A pointer to a buffer to store the read data. - nbyte: The number of bytes to read. - Returns: The number of bytes read or -1 in case of failure. - """ - return external_call["read", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t](fildes, buf, nbyte) - - -fn write(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: - """Libc POSIX `write` function - Reference: https://man7.org/linux/man-pages/man3/write.3p.html - Fn signature: ssize_t write(int fildes, const void *buf, size_t nbyte). - - Args: fildes: A File Descriptor. - buf: A pointer to a buffer to write. - nbyte: The number of bytes to write. - Returns: The number of bytes written or -1 in case of failure. - """ - return external_call["write", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t](fildes, buf, nbyte) diff --git a/external/gojo/syscall/net.mojo b/external/gojo/syscall/net.mojo deleted file mode 100644 index 2b0901af..00000000 --- a/external/gojo/syscall/net.mojo +++ /dev/null @@ -1,790 +0,0 @@ -from . import c_char, c_int, c_ushort, c_uint, c_size_t, c_ssize_t -from .types import strlen -from .file import O_CLOEXEC, O_NONBLOCK -from utils.static_tuple import StaticTuple - -alias IPPROTO_IPV6 = 41 -alias IPV6_V6ONLY = 26 -alias EPROTONOSUPPORT = 93 - -# Adapted from https://github.com/gabrieldemarmiesse/mojo-stdlib-extensions/ . Huge thanks to Gabriel! - -alias FD_STDIN: c_int = 0 -alias FD_STDOUT: c_int = 1 -alias FD_STDERR: c_int = 2 - -alias SUCCESS = 0 -alias GRND_NONBLOCK: UInt8 = 1 - -alias char_pointer = DTypePointer[DType.uint8] - - -# --- ( error.h Constants )----------------------------------------------------- -alias EPERM = 1 -alias ENOENT = 2 -alias ESRCH = 3 -alias EINTR = 4 -alias EIO = 5 -alias ENXIO = 6 -alias E2BIG = 7 -alias ENOEXEC = 8 -alias EBADF = 9 -alias ECHILD = 10 -alias EAGAIN = 11 -alias ENOMEM = 12 -alias EACCES = 13 -alias EFAULT = 14 -alias ENOTBLK = 15 -alias EBUSY = 16 -alias EEXIST = 17 -alias EXDEV = 18 -alias ENODEV = 19 -alias ENOTDIR = 20 -alias EISDIR = 21 -alias EINVAL = 22 -alias ENFILE = 23 -alias EMFILE = 24 -alias ENOTTY = 25 -alias ETXTBSY = 26 -alias EFBIG = 27 -alias ENOSPC = 28 -alias ESPIPE = 29 -alias EROFS = 30 -alias EMLINK = 31 -alias EPIPE = 32 -alias EDOM = 33 -alias ERANGE = 34 -alias EWOULDBLOCK = EAGAIN - - -fn to_char_ptr(s: String) -> DTypePointer[DType.uint8]: - """Only ASCII-based strings.""" - var ptr = DTypePointer[DType.uint8]().alloc(len(s)) - for i in range(len(s)): - ptr.store(i, ord(s[i])) - return ptr - - -fn c_charptr_to_string(s: DTypePointer[DType.uint8]) -> String: - return String(s, strlen(s)) - - -fn cftob(val: c_int) -> Bool: - """Convert C-like failure (-1) to Bool.""" - return rebind[Bool](val > 0) - - -# --- ( Network Related Constants )--------------------------------------------- -alias sa_family_t = c_ushort -alias socklen_t = c_uint -alias in_addr_t = c_uint -alias in_port_t = c_ushort - -# Address Family Constants -alias AF_UNSPEC = 0 -alias AF_UNIX = 1 -alias AF_LOCAL = AF_UNIX -alias AF_INET = 2 -alias AF_AX25 = 3 -alias AF_IPX = 4 -alias AF_APPLETALK = 5 -alias AF_NETROM = 6 -alias AF_BRIDGE = 7 -alias AF_ATMPVC = 8 -alias AF_X25 = 9 -alias AF_INET6 = 10 -alias AF_ROSE = 11 -alias AF_DECnet = 12 -alias AF_NETBEUI = 13 -alias AF_SECURITY = 14 -alias AF_KEY = 15 -alias AF_NETLINK = 16 -alias AF_ROUTE = AF_NETLINK -alias AF_PACKET = 17 -alias AF_ASH = 18 -alias AF_ECONET = 19 -alias AF_ATMSVC = 20 -alias AF_RDS = 21 -alias AF_SNA = 22 -alias AF_IRDA = 23 -alias AF_PPPOX = 24 -alias AF_WANPIPE = 25 -alias AF_LLC = 26 -alias AF_CAN = 29 -alias AF_TIPC = 30 -alias AF_BLUETOOTH = 31 -alias AF_IUCV = 32 -alias AF_RXRPC = 33 -alias AF_ISDN = 34 -alias AF_PHONET = 35 -alias AF_IEEE802154 = 36 -alias AF_CAIF = 37 -alias AF_ALG = 38 -alias AF_NFC = 39 -alias AF_VSOCK = 40 -alias AF_KCM = 41 -alias AF_QIPCRTR = 42 -alias AF_MAX = 43 - -# Protocol family constants -alias PF_UNSPEC = AF_UNSPEC -alias PF_UNIX = AF_UNIX -alias PF_LOCAL = AF_LOCAL -alias PF_INET = AF_INET -alias PF_AX25 = AF_AX25 -alias PF_IPX = AF_IPX -alias PF_APPLETALK = AF_APPLETALK -alias PF_NETROM = AF_NETROM -alias PF_BRIDGE = AF_BRIDGE -alias PF_ATMPVC = AF_ATMPVC -alias PF_X25 = AF_X25 -alias PF_INET6 = AF_INET6 -alias PF_ROSE = AF_ROSE -alias PF_DECnet = AF_DECnet -alias PF_NETBEUI = AF_NETBEUI -alias PF_SECURITY = AF_SECURITY -alias PF_KEY = AF_KEY -alias PF_NETLINK = AF_NETLINK -alias PF_ROUTE = AF_ROUTE -alias PF_PACKET = AF_PACKET -alias PF_ASH = AF_ASH -alias PF_ECONET = AF_ECONET -alias PF_ATMSVC = AF_ATMSVC -alias PF_RDS = AF_RDS -alias PF_SNA = AF_SNA -alias PF_IRDA = AF_IRDA -alias PF_PPPOX = AF_PPPOX -alias PF_WANPIPE = AF_WANPIPE -alias PF_LLC = AF_LLC -alias PF_CAN = AF_CAN -alias PF_TIPC = AF_TIPC -alias PF_BLUETOOTH = AF_BLUETOOTH -alias PF_IUCV = AF_IUCV -alias PF_RXRPC = AF_RXRPC -alias PF_ISDN = AF_ISDN -alias PF_PHONET = AF_PHONET -alias PF_IEEE802154 = AF_IEEE802154 -alias PF_CAIF = AF_CAIF -alias PF_ALG = AF_ALG -alias PF_NFC = AF_NFC -alias PF_VSOCK = AF_VSOCK -alias PF_KCM = AF_KCM -alias PF_QIPCRTR = AF_QIPCRTR -alias PF_MAX = AF_MAX - -# Socket Type constants -alias SOCK_STREAM = 1 -alias SOCK_DGRAM = 2 -alias SOCK_RAW = 3 -alias SOCK_RDM = 4 -alias SOCK_SEQPACKET = 5 -alias SOCK_DCCP = 6 -alias SOCK_PACKET = 10 -alias SOCK_CLOEXEC = O_CLOEXEC -alias SOCK_NONBLOCK = O_NONBLOCK - -# Address Information -alias AI_PASSIVE = 1 -alias AI_CANONNAME = 2 -alias AI_NUMERICHOST = 4 -alias AI_V4MAPPED = 2048 -alias AI_ALL = 256 -alias AI_ADDRCONFIG = 1024 -alias AI_IDN = 64 - -alias INET_ADDRSTRLEN = 16 -alias INET6_ADDRSTRLEN = 46 - -alias SHUT_RD = 0 -alias SHUT_WR = 1 -alias SHUT_RDWR = 2 - -alias SOL_SOCKET = 65535 - -# Socket Options -alias SO_DEBUG = 1 -alias SO_REUSEADDR = 4 -alias SO_TYPE = 4104 -alias SO_ERROR = 4103 -alias SO_DONTROUTE = 16 -alias SO_BROADCAST = 32 -alias SO_SNDBUF = 4097 -alias SO_RCVBUF = 4098 -alias SO_KEEPALIVE = 8 -alias SO_OOBINLINE = 256 -alias SO_LINGER = 128 -alias SO_REUSEPORT = 512 -alias SO_RCVLOWAT = 4100 -alias SO_SNDLOWAT = 4099 -alias SO_RCVTIMEO = 4102 -alias SO_SNDTIMEO = 4101 -alias SO_RCVTIMEO_OLD = 4102 -alias SO_SNDTIMEO_OLD = 4101 -alias SO_ACCEPTCONN = 2 - -# unsure of these socket options, they weren't available via python -alias SO_NO_CHECK = 11 -alias SO_PRIORITY = 12 -alias SO_BSDCOMPAT = 14 -alias SO_PASSCRED = 16 -alias SO_PEERCRED = 17 -alias SO_SECURITY_AUTHENTICATION = 22 -alias SO_SECURITY_ENCRYPTION_TRANSPORT = 23 -alias SO_SECURITY_ENCRYPTION_NETWORK = 24 -alias SO_BINDTODEVICE = 25 -alias SO_ATTACH_FILTER = 26 -alias SO_DETACH_FILTER = 27 -alias SO_GET_FILTER = SO_ATTACH_FILTER -alias SO_PEERNAME = 28 -alias SO_TIMESTAMP = 29 -alias SO_TIMESTAMP_OLD = 29 -alias SO_PEERSEC = 31 -alias SO_SNDBUFFORCE = 32 -alias SO_RCVBUFFORCE = 33 -alias SO_PASSSEC = 34 -alias SO_TIMESTAMPNS = 35 -alias SO_TIMESTAMPNS_OLD = 35 -alias SO_MARK = 36 -alias SO_TIMESTAMPING = 37 -alias SO_TIMESTAMPING_OLD = 37 -alias SO_PROTOCOL = 38 -alias SO_DOMAIN = 39 -alias SO_RXQ_OVFL = 40 -alias SO_WIFI_STATUS = 41 -alias SCM_WIFI_STATUS = SO_WIFI_STATUS -alias SO_PEEK_OFF = 42 -alias SO_NOFCS = 43 -alias SO_LOCK_FILTER = 44 -alias SO_SELECT_ERR_QUEUE = 45 -alias SO_BUSY_POLL = 46 -alias SO_MAX_PACING_RATE = 47 -alias SO_BPF_EXTENSIONS = 48 -alias SO_INCOMING_CPU = 49 -alias SO_ATTACH_BPF = 50 -alias SO_DETACH_BPF = SO_DETACH_FILTER -alias SO_ATTACH_REUSEPORT_CBPF = 51 -alias SO_ATTACH_REUSEPORT_EBPF = 52 -alias SO_CNX_ADVICE = 53 -alias SCM_TIMESTAMPING_OPT_STATS = 54 -alias SO_MEMINFO = 55 -alias SO_INCOMING_NAPI_ID = 56 -alias SO_COOKIE = 57 -alias SCM_TIMESTAMPING_PKTINFO = 58 -alias SO_PEERGROUPS = 59 -alias SO_ZEROCOPY = 60 -alias SO_TXTIME = 61 -alias SCM_TXTIME = SO_TXTIME -alias SO_BINDTOIFINDEX = 62 -alias SO_TIMESTAMP_NEW = 63 -alias SO_TIMESTAMPNS_NEW = 64 -alias SO_TIMESTAMPING_NEW = 65 -alias SO_RCVTIMEO_NEW = 66 -alias SO_SNDTIMEO_NEW = 67 -alias SO_DETACH_REUSEPORT_BPF = 68 - - -# --- ( Network Related Structs )----------------------------------------------- -@value -@register_passable("trivial") -struct in_addr: - var s_addr: in_addr_t - - -@value -@register_passable("trivial") -struct in6_addr: - var s6_addr: StaticTuple[c_char, 16] - - -@value -@register_passable("trivial") -struct sockaddr: - var sa_family: sa_family_t - var sa_data: StaticTuple[c_char, 14] - - -@value -@register_passable("trivial") -struct sockaddr_in: - var sin_family: sa_family_t - var sin_port: in_port_t - var sin_addr: in_addr - var sin_zero: StaticTuple[c_char, 8] - - -@value -@register_passable("trivial") -struct sockaddr_in6: - var sin6_family: sa_family_t - var sin6_port: in_port_t - var sin6_flowinfo: c_uint - var sin6_addr: in6_addr - var sin6_scope_id: c_uint - - -@value -@register_passable("trivial") -struct addrinfo: - """Struct field ordering can vary based on platform. - For MacOS, I had to swap the order of ai_canonname and ai_addr. - https://stackoverflow.com/questions/53575101/calling-getaddrinfo-directly-from-python-ai-addr-is-null-pointer. - """ - - var ai_flags: c_int - var ai_family: c_int - var ai_socktype: c_int - var ai_protocol: c_int - var ai_addrlen: socklen_t - var ai_canonname: DTypePointer[DType.uint8] - var ai_addr: UnsafePointer[sockaddr] - var ai_next: UnsafePointer[addrinfo] - - fn __init__( - inout self, - ai_flags: c_int = 0, - ai_family: c_int = 0, - ai_socktype: c_int = 0, - ai_protocol: c_int = 0, - ai_addrlen: socklen_t = 0, - ai_canonname: DTypePointer[DType.uint8] = DTypePointer[DType.uint8](), - ai_addr: UnsafePointer[sockaddr] = UnsafePointer[sockaddr](), - ai_next: UnsafePointer[addrinfo] = UnsafePointer[addrinfo](), - ): - self.ai_flags = ai_flags - self.ai_family = ai_family - self.ai_socktype = ai_socktype - self.ai_protocol = ai_protocol - self.ai_addrlen = ai_addrlen - self.ai_canonname = ai_canonname - self.ai_addr = ai_addr - self.ai_next = ai_next - - # fn __init__() -> Self: - # return Self(0, 0, 0, 0, 0, DTypePointer[DType.uint8](), UnsafePointer[sockaddr](), UnsafePointer[addrinfo]()) - - -@value -@register_passable("trivial") -struct addrinfo_unix: - """Struct field ordering can vary based on platform. - For MacOS, I had to swap the order of ai_canonname and ai_addr. - https://stackoverflow.com/questions/53575101/calling-getaddrinfo-directly-from-python-ai-addr-is-null-pointer. - """ - - var ai_flags: c_int - var ai_family: c_int - var ai_socktype: c_int - var ai_protocol: c_int - var ai_addrlen: socklen_t - var ai_addr: UnsafePointer[sockaddr] - var ai_canonname: DTypePointer[DType.uint8] - var ai_next: UnsafePointer[addrinfo] - - fn __init__( - inout self, - ai_flags: c_int = 0, - ai_family: c_int = 0, - ai_socktype: c_int = 0, - ai_protocol: c_int = 0, - ai_addrlen: socklen_t = 0, - ai_canonname: DTypePointer[DType.uint8] = DTypePointer[DType.uint8](), - ai_addr: UnsafePointer[sockaddr] = UnsafePointer[sockaddr](), - ai_next: UnsafePointer[addrinfo] = UnsafePointer[addrinfo](), - ): - self.ai_flags = ai_flags - self.ai_family = ai_family - self.ai_socktype = ai_socktype - self.ai_protocol = ai_protocol - self.ai_addrlen = ai_addrlen - self.ai_canonname = ai_canonname - self.ai_addr = ai_addr - self.ai_next = ai_next - - -# --- ( Network Related Syscalls & Structs )------------------------------------ - - -fn htonl(hostlong: c_uint) -> c_uint: - """Libc POSIX `htonl` function - Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html - Fn signature: uint32_t htonl(uint32_t hostlong). - - Args: hostlong: A 32-bit integer in host byte order. - Returns: The value provided in network byte order. - """ - return external_call["htonl", c_uint, c_uint](hostlong) - - -fn htons(hostshort: c_ushort) -> c_ushort: - """Libc POSIX `htons` function - Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html - Fn signature: uint16_t htons(uint16_t hostshort). - - Args: hostshort: A 16-bit integer in host byte order. - Returns: The value provided in network byte order. - """ - return external_call["htons", c_ushort, c_ushort](hostshort) - - -fn ntohl(netlong: c_uint) -> c_uint: - """Libc POSIX `ntohl` function - Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html - Fn signature: uint32_t ntohl(uint32_t netlong). - - Args: netlong: A 32-bit integer in network byte order. - Returns: The value provided in host byte order. - """ - return external_call["ntohl", c_uint, c_uint](netlong) - - -fn ntohs(netshort: c_ushort) -> c_ushort: - """Libc POSIX `ntohs` function - Reference: https://man7.org/linux/man-pages/man3/htonl.3p.html - Fn signature: uint16_t ntohs(uint16_t netshort). - - Args: netshort: A 16-bit integer in network byte order. - Returns: The value provided in host byte order. - """ - return external_call["ntohs", c_ushort, c_ushort](netshort) - - -fn inet_ntop( - af: c_int, src: DTypePointer[DType.uint8], dst: DTypePointer[DType.uint8], size: socklen_t -) -> DTypePointer[DType.uint8]: - """Libc POSIX `inet_ntop` function - Reference: https://man7.org/linux/man-pages/man3/inet_ntop.3p.html. - Fn signature: const char *inet_ntop(int af, const void *restrict src, char *restrict dst, socklen_t size). - - Args: - af: Address Family see AF_ aliases. - src: A pointer to a binary address. - dst: A pointer to a buffer to store the result. - size: The size of the buffer. - - Returns: - A pointer to the buffer containing the result. - """ - return external_call[ - "inet_ntop", - DTypePointer[DType.uint8], # FnName, RetType - c_int, - DTypePointer[DType.uint8], - DTypePointer[DType.uint8], - socklen_t, # Args - ](af, src, dst, size) - - -fn inet_pton(af: c_int, src: DTypePointer[DType.uint8], dst: DTypePointer[DType.uint8]) -> c_int: - """Libc POSIX `inet_pton` function - Reference: https://man7.org/linux/man-pages/man3/inet_ntop.3p.html - Fn signature: int inet_pton(int af, const char *restrict src, void *restrict dst). - - Args: af: Address Family see AF_ aliases. - src: A pointer to a string containing the address. - dst: A pointer to a buffer to store the result. - Returns: 1 on success, 0 if the input is not a valid address, -1 on error. - """ - return external_call[ - "inet_pton", - c_int, # FnName, RetType - c_int, - DTypePointer[DType.uint8], - DTypePointer[DType.uint8], # Args - ](af, src, dst) - - -fn inet_addr(cp: DTypePointer[DType.uint8]) -> in_addr_t: - """Libc POSIX `inet_addr` function - Reference: https://man7.org/linux/man-pages/man3/inet_addr.3p.html - Fn signature: in_addr_t inet_addr(const char *cp). - - Args: cp: A pointer to a string containing the address. - Returns: The address in network byte order. - """ - return external_call["inet_addr", in_addr_t, DTypePointer[DType.uint8]](cp) - - -fn inet_ntoa(addr: in_addr) -> DTypePointer[DType.uint8]: - """Libc POSIX `inet_ntoa` function - Reference: https://man7.org/linux/man-pages/man3/inet_addr.3p.html - Fn signature: char *inet_ntoa(struct in_addr in). - - Args: in: A pointer to a string containing the address. - Returns: The address in network byte order. - """ - return external_call["inet_ntoa", DTypePointer[DType.uint8], in_addr](addr) - - -fn socket(domain: c_int, type: c_int, protocol: c_int) -> c_int: - """Libc POSIX `socket` function - Reference: https://man7.org/linux/man-pages/man3/socket.3p.html - Fn signature: int socket(int domain, int type, int protocol). - - Args: domain: Address Family see AF_ aliases. - type: Socket Type see SOCK_ aliases. - protocol: The protocol to use. - Returns: A File Descriptor or -1 in case of failure. - """ - return external_call["socket", c_int, c_int, c_int, c_int](domain, type, protocol) # FnName, RetType # Args - - -fn setsockopt( - socket: c_int, - level: c_int, - option_name: c_int, - option_value: DTypePointer[DType.uint8], - option_len: socklen_t, -) -> c_int: - """Libc POSIX `setsockopt` function - Reference: https://man7.org/linux/man-pages/man3/setsockopt.3p.html - Fn signature: int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len). - - Args: - socket: A File Descriptor. - level: The protocol level. - option_name: The option to set. - option_value: A pointer to the value to set. - option_len: The size of the value. - Returns: 0 on success, -1 on error. - """ - return external_call[ - "setsockopt", - c_int, # FnName, RetType - c_int, - c_int, - c_int, - DTypePointer[DType.uint8], - socklen_t, # Args - ](socket, level, option_name, option_value, option_len) - - -fn getsockopt( - socket: c_int, - level: c_int, - option_name: c_int, - option_value: DTypePointer[DType.uint8], - option_len: UnsafePointer[socklen_t], -) -> c_int: - """Libc POSIX `getsockopt` function - Reference: https://man7.org/linux/man-pages/man3/getsockopt.3p.html - Fn signature: int getsockopt(int socket, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len). - - Args: socket: A File Descriptor. - level: The protocol level. - option_name: The option to get. - option_value: A pointer to the value to get. - option_len: DTypePointer to the size of the value. - Returns: 0 on success, -1 on error. - """ - return external_call[ - "getsockopt", - c_int, # FnName, RetType - c_int, - c_int, - c_int, - DTypePointer[DType.uint8], - UnsafePointer[socklen_t], # Args - ](socket, level, option_name, option_value, option_len) - - -fn getsockname(socket: c_int, address: UnsafePointer[sockaddr], address_len: UnsafePointer[socklen_t]) -> c_int: - """Libc POSIX `getsockname` function - Reference: https://man7.org/linux/man-pages/man3/getsockname.3p.html - Fn signature: int getsockname(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len). - - Args: socket: A File Descriptor. - address: A pointer to a buffer to store the address of the peer. - address_len: A pointer to the size of the buffer. - Returns: 0 on success, -1 on error. - """ - return external_call[ - "getsockname", - c_int, # FnName, RetType - c_int, - UnsafePointer[sockaddr], - UnsafePointer[socklen_t], # Args - ](socket, address, address_len) - - -fn getpeername(sockfd: c_int, addr: UnsafePointer[sockaddr], address_len: UnsafePointer[socklen_t]) -> c_int: - """Libc POSIX `getpeername` function - Reference: https://man7.org/linux/man-pages/man2/getpeername.2.html - Fn signature: int getpeername(int socket, struct sockaddr *restrict addr, socklen_t *restrict address_len). - - Args: sockfd: A File Descriptor. - addr: A pointer to a buffer to store the address of the peer. - address_len: A pointer to the size of the buffer. - Returns: 0 on success, -1 on error. - """ - return external_call[ - "getpeername", - c_int, # FnName, RetType - c_int, - UnsafePointer[sockaddr], - UnsafePointer[socklen_t], # Args - ](sockfd, addr, address_len) - - -fn bind(socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen_t) -> c_int: - """Libc POSIX `bind` function - Reference: https://man7.org/linux/man-pages/man3/bind.3p.html - Fn signature: int bind(int socket, const struct sockaddr *address, socklen_t address_len). - """ - return external_call["bind", c_int, c_int, UnsafePointer[sockaddr], socklen_t]( # FnName, RetType # Args - socket, address, address_len - ) - - -fn listen(socket: c_int, backlog: c_int) -> c_int: - """Libc POSIX `listen` function - Reference: https://man7.org/linux/man-pages/man3/listen.3p.html - Fn signature: int listen(int socket, int backlog). - - Args: socket: A File Descriptor. - backlog: The maximum length of the queue of pending connections. - Returns: 0 on success, -1 on error. - """ - return external_call["listen", c_int, c_int, c_int](socket, backlog) - - -fn accept(socket: c_int, address: UnsafePointer[sockaddr], address_len: UnsafePointer[socklen_t]) -> c_int: - """Libc POSIX `accept` function - Reference: https://man7.org/linux/man-pages/man3/accept.3p.html - Fn signature: int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len). - - Args: socket: A File Descriptor. - address: A pointer to a buffer to store the address of the peer. - address_len: A pointer to the size of the buffer. - Returns: A File Descriptor or -1 in case of failure. - """ - return external_call[ - "accept", - c_int, # FnName, RetType - c_int, - UnsafePointer[sockaddr], - UnsafePointer[socklen_t], # Args - ](socket, address, address_len) - - -fn connect(socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen_t) -> c_int: - """Libc POSIX `connect` function - Reference: https://man7.org/linux/man-pages/man3/connect.3p.html - Fn signature: int connect(int socket, const struct sockaddr *address, socklen_t address_len). - - Args: socket: A File Descriptor. - address: A pointer to the address to connect to. - address_len: The size of the address. - Returns: 0 on success, -1 on error. - """ - return external_call["connect", c_int, c_int, UnsafePointer[sockaddr], socklen_t]( # FnName, RetType # Args - socket, address, address_len - ) - - -fn recv(socket: c_int, buffer: DTypePointer[DType.uint8], length: c_size_t, flags: c_int) -> c_ssize_t: - """Libc POSIX `recv` function - Reference: https://man7.org/linux/man-pages/man3/recv.3p.html - Fn signature: ssize_t recv(int socket, void *buffer, size_t length, int flags). - """ - return external_call[ - "recv", - c_ssize_t, # FnName, RetType - c_int, - DTypePointer[DType.uint8], - c_size_t, - c_int, # Args - ](socket, buffer, length, flags) - - -fn send(socket: c_int, buffer: DTypePointer[DType.uint8], length: c_size_t, flags: c_int) -> c_ssize_t: - """Libc POSIX `send` function - Reference: https://man7.org/linux/man-pages/man3/send.3p.html - Fn signature: ssize_t send(int socket, const void *buffer, size_t length, int flags). - - Args: socket: A File Descriptor. - buffer: A pointer to the buffer to send. - length: The size of the buffer. - flags: Flags to control the behaviour of the function. - Returns: The number of bytes sent or -1 in case of failure. - """ - return external_call[ - "send", - c_ssize_t, # FnName, RetType - c_int, - DTypePointer[DType.uint8], - c_size_t, - c_int, # Args - ](socket, buffer, length, flags) - - -fn shutdown(socket: c_int, how: c_int) -> c_int: - """Libc POSIX `shutdown` function - Reference: https://man7.org/linux/man-pages/man3/shutdown.3p.html - Fn signature: int shutdown(int socket, int how). - - Args: socket: A File Descriptor. - how: How to shutdown the socket. - Returns: 0 on success, -1 on error. - """ - return external_call["shutdown", c_int, c_int, c_int](socket, how) # FnName, RetType # Args - - -fn getaddrinfo( - nodename: DTypePointer[DType.uint8], - servname: DTypePointer[DType.uint8], - hints: UnsafePointer[addrinfo], - res: UnsafePointer[UnsafePointer[addrinfo]], -) -> c_int: - """Libc POSIX `getaddrinfo` function - Reference: https://man7.org/linux/man-pages/man3/getaddrinfo.3p.html - Fn signature: int getaddrinfo(const char *restrict nodename, const char *restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res). - """ - return external_call[ - "getaddrinfo", - c_int, # FnName, RetType - DTypePointer[DType.uint8], - DTypePointer[DType.uint8], - UnsafePointer[addrinfo], # Args - UnsafePointer[UnsafePointer[addrinfo]], # Args - ](nodename, servname, hints, res) - - -fn getaddrinfo_unix( - nodename: DTypePointer[DType.uint8], - servname: DTypePointer[DType.uint8], - hints: UnsafePointer[addrinfo_unix], - res: UnsafePointer[UnsafePointer[addrinfo_unix]], -) -> c_int: - """Libc POSIX `getaddrinfo` function - Reference: https://man7.org/linux/man-pages/man3/getaddrinfo.3p.html - Fn signature: int getaddrinfo(const char *restrict nodename, const char *restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res). - """ - return external_call[ - "getaddrinfo", - c_int, # FnName, RetType - DTypePointer[DType.uint8], - DTypePointer[DType.uint8], - UnsafePointer[addrinfo_unix], # Args - UnsafePointer[UnsafePointer[addrinfo_unix]], # Args - ](nodename, servname, hints, res) - - -fn gai_strerror(ecode: c_int) -> DTypePointer[DType.uint8]: - """Libc POSIX `gai_strerror` function - Reference: https://man7.org/linux/man-pages/man3/gai_strerror.3p.html - Fn signature: const char *gai_strerror(int ecode). - - Args: ecode: The error code. - Returns: A pointer to a string describing the error. - """ - return external_call["gai_strerror", DTypePointer[DType.uint8], c_int](ecode) # FnName, RetType # Args - - -# fn inet_pton(address_family: Int, address: String) -> Int: -# var ip_buf_size = 4 -# if address_family == AF_INET6: -# ip_buf_size = 16 - -# var ip_buf = DTypePointer[DType.uint8].alloc(ip_buf_size) -# var conv_status = inet_pton(rebind[c_int](address_family), to_char_ptr(address), ip_buf) -# return int(ip_buf.bitcast[c_uint]().load()) diff --git a/external/gojo/syscall/types.mojo b/external/gojo/syscall/types.mojo deleted file mode 100644 index 6b2c49ad..00000000 --- a/external/gojo/syscall/types.mojo +++ /dev/null @@ -1,9 +0,0 @@ -fn strlen(s: DTypePointer[DType.uint8]) -> c_size_t: - """Libc POSIX `strlen` function - Reference: https://man7.org/linux/man-pages/man3/strlen.3p.html - Fn signature: size_t strlen(const char *s). - - Args: s: A pointer to a C string. - Returns: The length of the string. - """ - return external_call["strlen", c_size_t, DTypePointer[DType.uint8]](s) diff --git a/external/gojo/tests/__init__.mojo b/external/gojo/tests/__init__.mojo deleted file mode 100644 index e69de29b..00000000 diff --git a/external/gojo/tests/wrapper.mojo b/external/gojo/tests/wrapper.mojo deleted file mode 100644 index ee73f268..00000000 --- a/external/gojo/tests/wrapper.mojo +++ /dev/null @@ -1,38 +0,0 @@ -from testing import testing - - -@value -struct MojoTest: - """ - A utility struct for testing. - """ - - var test_name: String - - fn __init__(inout self, test_name: String): - self.test_name = test_name - print("# " + test_name) - - fn assert_true(self, cond: Bool, message: String = ""): - try: - if message == "": - testing.assert_true(cond) - else: - testing.assert_true(cond, message) - except e: - print(e) - - fn assert_false(self, cond: Bool, message: String = ""): - try: - if message == "": - testing.assert_false(cond) - else: - testing.assert_false(cond, message) - except e: - print(e) - - fn assert_equal[T: testing.Testable](self, left: T, right: T): - try: - testing.assert_equal(left, right) - except e: - print(e) \ No newline at end of file diff --git a/external/gojo/unicode/__init__.mojo b/external/gojo/unicode/__init__.mojo deleted file mode 100644 index bd4cba63..00000000 --- a/external/gojo/unicode/__init__.mojo +++ /dev/null @@ -1 +0,0 @@ -from .utf8 import string_iterator, rune_count_in_string diff --git a/external/gojo/unicode/utf8/__init__.mojo b/external/gojo/unicode/utf8/__init__.mojo deleted file mode 100644 index b8732ec8..00000000 --- a/external/gojo/unicode/utf8/__init__.mojo +++ /dev/null @@ -1,4 +0,0 @@ -"""Almost all of the actual implementation in this module was written by @mzaks (https://github.com/mzaks)! -This would not be possible without his help. -""" -from .runes import string_iterator, rune_count_in_string diff --git a/external/gojo/unicode/utf8/runes.mojo b/external/gojo/unicode/utf8/runes.mojo deleted file mode 100644 index 7346162b..00000000 --- a/external/gojo/unicode/utf8/runes.mojo +++ /dev/null @@ -1,334 +0,0 @@ -"""Almost all of the actual implementation in this module was written by @mzaks (https://github.com/mzaks)! -This would not be possible without his help. -""" - -from ...builtins import Rune -from algorithm.functional import vectorize -from memory.unsafe import DTypePointer -from sys.info import simdwidthof -from bit import countl_zero - - -# The default lowest and highest continuation byte. -alias locb = 0b10000000 -alias hicb = 0b10111111 -alias RUNE_SELF = 0x80 # Characters below RuneSelf are represented as themselves in a single byte - - -# acceptRange gives the range of valid values for the second byte in a UTF-8 -# sequence. -@value -struct AcceptRange(CollectionElement): - var lo: UInt8 # lowest value for second byte. - var hi: UInt8 # highest value for second byte. - - -# ACCEPT_RANGES has size 16 to avoid bounds checks in the code that uses it. -alias ACCEPT_RANGES = List[AcceptRange]( - AcceptRange(locb, hicb), - AcceptRange(0xA0, hicb), - AcceptRange(locb, 0x9F), - AcceptRange(0x90, hicb), - AcceptRange(locb, 0x8F), -) - -# These names of these constants are chosen to give nice alignment in the -# table below. The first nibble is an index into acceptRanges or F for -# special one-byte cases. The second nibble is the Rune length or the -# Status for the special one-byte case. -alias xx = 0xF1 # invalid: size 1 -alias as1 = 0xF0 # ASCII: size 1 -alias s1 = 0x02 # accept 0, size 2 -alias s2 = 0x13 # accept 1, size 3 -alias s3 = 0x03 # accept 0, size 3 -alias s4 = 0x23 # accept 2, size 3 -alias s5 = 0x34 # accept 3, size 4 -alias s6 = 0x04 # accept 0, size 4 -alias s7 = 0x44 # accept 4, size 4 - - -# first is information about the first byte in a UTF-8 sequence. -var first = List[UInt8]( - # 1 2 3 4 5 6 7 8 9 A B C D E F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x00-0x0F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x10-0x1F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x20-0x2F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x30-0x3F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x40-0x4F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x50-0x5F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x60-0x6F - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, - as1, # 0x70-0x7F - # 1 2 3 4 5 6 7 8 9 A B C D E F - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, # 0x80-0x8F - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, # 0x90-0x9F - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, # 0xA0-0xAF - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, # 0xB0-0xBF - xx, - xx, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, # 0xC0-0xCF - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, - s1, # 0xD0-0xDF - s2, - s3, - s3, - s3, - s3, - s3, - s3, - s3, - s3, - s3, - s3, - s3, - s3, - s4, - s3, - s3, # 0xE0-0xEF - s5, - s6, - s6, - s6, - s7, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, - xx, # 0xF0-0xFF -) - - -alias simd_width_u8 = simdwidthof[DType.uint8]() - - -fn rune_count_in_string(s: String) -> Int: - """Count the number of runes in a string. - - Args: - s: The string to count runes in. - - Returns: - The number of runes in the string. - """ - var p = DTypePointer[DType.uint8](s.unsafe_uint8_ptr()) - var string_byte_length = len(s) - var result = 0 - - @parameter - fn count[simd_width: Int](offset: Int): - result += int(((p.load[width=simd_width](offset) >> 6) != 0b10).reduce_add()) - - vectorize[count, simd_width_u8](string_byte_length) - return result diff --git a/external/morrow.mojo b/external/morrow.mojo deleted file mode 100644 index 1c040acc..00000000 --- a/external/morrow.mojo +++ /dev/null @@ -1,314 +0,0 @@ -# From Morrow package https://github.com/mojoto/morrow.mojo/tree/cc6625e16829acc55bcea060dd2ea5d6a4b6c676 -# Including like this until better package management is available - -alias _MAX_TIMESTAMP: Int = 32503737600 -alias MAX_TIMESTAMP = _MAX_TIMESTAMP -alias MAX_TIMESTAMP_MS = MAX_TIMESTAMP * 1000 -alias MAX_TIMESTAMP_US = MAX_TIMESTAMP * 1_000_000 - - -@always_inline -fn c_gettimeofday() -> CTimeval: - var tv = CTimeval() - var p_tv = Pointer[CTimeval].address_of(tv) - external_call["gettimeofday", NoneType, Pointer[CTimeval], Int32](p_tv, 0) - return tv - - -@always_inline -fn c_gmtime(owned tv_sec: Int) -> CTm: - var p_tv_sec = Pointer[Int].address_of(tv_sec) - var tm = external_call["gmtime", Pointer[CTm], Pointer[Int]](p_tv_sec).load() - return tm - - -@always_inline -fn c_localtime(owned tv_sec: Int) -> CTm: - var p_tv_sec = Pointer[Int].address_of(tv_sec) - var tm = external_call["localtime", Pointer[CTm], Pointer[Int]](p_tv_sec).load() - return tm - - -@value -struct TimeZone: - var offset: Int - var name: String - - fn __init__(inout self, offset: Int, name: String = ""): - self.offset = offset - self.name = name - - fn __str__(self) -> String: - return self.name - - fn is_none(self) -> Bool: - return self.name == "None" - - @staticmethod - fn none() -> TimeZone: - return TimeZone(0, "None") - - @staticmethod - fn local() -> TimeZone: - var local_t = c_localtime(0) - return TimeZone(int(local_t.tm_gmtoff), "local") - - @staticmethod - fn from_utc(utc_str: String) raises -> TimeZone: - if len(utc_str) == 0: - raise Error("utc_str is empty") - if utc_str == "utc" or utc_str == "UTC" or utc_str == "Z": - return TimeZone(0, "utc") - var p = 3 if len(utc_str) > 3 and utc_str[0:3] == "UTC" else 0 - - var sign = -1 if utc_str[p] == "-" else 1 - if utc_str[p] == "+" or utc_str[p] == "-": - p += 1 - - if ( - len(utc_str) < p + 2 - or not isdigit(ord(utc_str[p])) - or not isdigit(ord(utc_str[p + 1])) - ): - raise Error("utc_str format is invalid") - var hours: Int = atol(utc_str[p : p + 2]) - p += 2 - - var minutes: Int - if len(utc_str) <= p: - minutes = 0 - elif len(utc_str) == p + 3 and utc_str[p] == ":": - minutes = atol(utc_str[p + 1 : p + 3]) - elif len(utc_str) == p + 2 and isdigit(ord(utc_str[p])): - minutes = atol(utc_str[p : p + 2]) - else: - minutes = 0 - raise Error("utc_str format is invalid") - var offset: Int = sign * (hours * 3600 + minutes * 60) - return TimeZone(offset) - - fn format(self) -> String: - var sign: String - var offset_abs: Int - if self.offset < 0: - sign = "-" - offset_abs = -self.offset - else: - sign = "+" - offset_abs = self.offset - var hh = offset_abs // 3600 - var mm = offset_abs % 3600 - return sign + rjust(hh, 2, "0") + ":" + rjust(mm, 2, "0") - - -@value -@register_passable("trivial") -struct CTm: - var tm_sec: Int32 # Seconds - var tm_min: Int32 # Minutes - var tm_hour: Int32 # Hour - var tm_mday: Int32 # Day of the month - var tm_mon: Int32 # Month - var tm_year: Int32 # Year minus 1900 - var tm_wday: Int32 # Day of the week - var tm_yday: Int32 # Day of the year - var tm_isdst: Int32 # Daylight savings flag - var tm_gmtoff: Int64 # localtime zone offset seconds - - fn __init__() -> Self: - return Self { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_gmtoff: 0, - } - - -@value -struct Morrow: - var year: Int - var month: Int - var day: Int - var hour: Int - var minute: Int - var second: Int - var microsecond: Int - var tz: TimeZone - - fn __init__( - inout self, - year: Int, - month: Int, - day: Int, - hour: Int = 0, - minute: Int = 0, - second: Int = 0, - microsecond: Int = 0, - tz: TimeZone = TimeZone.none(), - ) raises: - self.year = year - self.month = month - self.day = day - self.hour = hour - self.minute = minute - self.second = second - self.microsecond = microsecond - self.tz = tz - - fn __str__(self) raises -> String: - return self.isoformat() - - fn isoformat( - self, sep: String = "T", timespec: StringLiteral = "auto" - ) raises -> String: - """Return the time formatted according to ISO. - - The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'. - - If self.tzinfo is not None, the UTC offset is also attached, giving - giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'. - - Optional argument sep specifies the separator between date and - time, default 'T'. - - The optional argument timespec specifies the number of additional - terms of the time to include. Valid options are 'auto', 'hours', - 'minutes', 'seconds', 'milliseconds' and 'microseconds'. - """ - var date_str = ( - rjust(self.year, 4, "0") - + "-" - + rjust(self.month, 2, "0") - + "-" - + rjust(self.day, 2, "0") - ) - var time_str = String("") - if timespec == "auto" or timespec == "microseconds": - time_str = ( - rjust(self.hour, 2, "0") - + ":" - + rjust(self.minute, 2, "0") - + ":" - + rjust(self.second, 2, "0") - + "." - + rjust(self.microsecond, 6, "0") - ) - elif timespec == "milliseconds": - time_str = ( - rjust(self.hour, 2, "0") - + ":" - + rjust(self.minute, 2, "0") - + ":" - + rjust(self.second, 2, "0") - + "." - + rjust(self.microsecond // 1000, 3, "0") - ) - elif timespec == "seconds": - time_str = ( - rjust(self.hour, 2, "0") - + ":" - + rjust(self.minute, 2, "0") - + ":" - + rjust(self.second, 2, "0") - ) - elif timespec == "minutes": - time_str = rjust(self.hour, 2, "0") + ":" + rjust(self.minute, 2, "0") - elif timespec == "hours": - time_str = rjust(self.hour, 2, "0") - else: - raise Error() - if self.tz.is_none(): - return sep.join(date_str, time_str) - else: - return sep.join(date_str, time_str) + self.tz.format() - - @staticmethod - fn now() raises -> Self: - var t = c_gettimeofday() - return Self._fromtimestamp(t, False) - - @staticmethod - fn utcnow() raises -> Self: - var t = c_gettimeofday() - return Self._fromtimestamp(t, True) - - @staticmethod - fn _fromtimestamp(t: CTimeval, utc: Bool) raises -> Self: - var tm: CTm - var tz: TimeZone - if utc: - tm = c_gmtime(t.tv_sec) - tz = TimeZone(0, "UTC") - else: - tm = c_localtime(t.tv_sec) - tz = TimeZone(int(tm.tm_gmtoff), "local") - - var result = Self( - int(tm.tm_year) + 1900, - int(tm.tm_mon) + 1, - int(tm.tm_mday), - int(tm.tm_hour), - int(tm.tm_min), - int(tm.tm_sec), - t.tv_usec, - tz, - ) - return result - - @staticmethod - fn fromtimestamp(timestamp: Float64) raises -> Self: - var timestamp_ = normalize_timestamp(timestamp) - var t = CTimeval(int(timestamp_)) - return Self._fromtimestamp(t, False) - - @staticmethod - fn utcfromtimestamp(timestamp: Float64) raises -> Self: - var timestamp_ = normalize_timestamp(timestamp) - var t = CTimeval(int(timestamp_)) - return Self._fromtimestamp(t, True) - - -@value -@register_passable("trivial") -struct CTimeval: - var tv_sec: Int # Seconds - var tv_usec: Int # Microseconds - - fn __init__(tv_sec: Int = 0, tv_usec: Int = 0) -> Self: - return Self {tv_sec: tv_sec, tv_usec: tv_usec} - - -def normalize_timestamp(timestamp: Float64) -> Float64: - """Normalize millisecond and microsecond timestamps into normal timestamps.""" - if timestamp > MAX_TIMESTAMP: - if timestamp < MAX_TIMESTAMP_MS: - timestamp /= 1000 - elif timestamp < MAX_TIMESTAMP_US: - timestamp /= 1_000_000 - else: - raise Error( - "The specified timestamp " + timestamp.__str__() + "is too large." - ) - return timestamp - - -fn _repeat_string(string: String, n: Int) -> String: - var result: String = "" - for _ in range(n): - result += string - return result - - -fn rjust(string: String, width: Int, fillchar: String = " ") -> String: - var extra = width - len(string) - return _repeat_string(fillchar, extra) + string - - -fn rjust(string: Int, width: Int, fillchar: String = " ") -> String: - return rjust(string.__str__(), width, fillchar) diff --git a/work_in_progress/index.html b/index.html similarity index 100% rename from work_in_progress/index.html rename to index.html diff --git "a/lightbug.\360\237\224\245" "b/lightbug.\360\237\224\245" index ad27aacc..252679b8 100644 --- "a/lightbug.\360\237\224\245" +++ "b/lightbug.\360\237\224\245" @@ -1,4 +1,5 @@ -from lightbug_http import * +from lightbug_http import Welcome, SysServer +from lightbug_http.service import NoUpgrade fn main() raises: var server = SysServer() diff --git a/lightbug_http/__init__.mojo b/lightbug_http/__init__.mojo index 7259a87c..a859cf08 100644 --- a/lightbug_http/__init__.mojo +++ b/lightbug_http/__init__.mojo @@ -1,7 +1,10 @@ -from lightbug_http.http import HTTPRequest, HTTPResponse, OK +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.tests.run import run_tests +from lightbug_http.strings import to_string + trait DefaultConstructible: fn __init__(inout self) raises: diff --git a/lightbug_http/client.mojo b/lightbug_http/client.mojo index de1e4399..c0e3127a 100644 --- a/lightbug_http/client.mojo +++ b/lightbug_http/client.mojo @@ -8,5 +8,5 @@ trait Client: fn __init__(inout self, host: StringLiteral, port: Int) raises: ... - fn do(self, req: HTTPRequest) raises -> HTTPResponse: + fn do(self, owned req: HTTPRequest) raises -> HTTPResponse: ... diff --git a/lightbug_http/error.mojo b/lightbug_http/error.mojo index ab9091d7..546c17c8 100644 --- a/lightbug_http/error.mojo +++ b/lightbug_http/error.mojo @@ -1,9 +1,11 @@ from lightbug_http.http import HTTPResponse -from lightbug_http.header import ResponseHeader from lightbug_http.io.bytes import bytes +alias TODO_MESSAGE = String("TODO").as_bytes() + + # TODO: Custom error handlers provided by the user @value struct ErrorHandler: fn Error(self) -> HTTPResponse: - return HTTPResponse(ResponseHeader(), bytes("TODO")) \ No newline at end of file + return HTTPResponse(TODO_MESSAGE) diff --git a/lightbug_http/header.mojo b/lightbug_http/header.mojo index 43bcf0e9..86bb0459 100644 --- a/lightbug_http/header.mojo +++ b/lightbug_http/header.mojo @@ -1,870 +1,112 @@ -from external.gojo.bufio import Reader -from lightbug_http.strings import ( - strHttp11, - strHttp10, - strSlash, - strMethodGet, - rChar, - nChar, - colonChar, - whitespace, - tab -) -from lightbug_http.io.bytes import Bytes, Byte, BytesView, bytes_equal, bytes, index_byte, compare_case_insensitive, next_line, last_index_byte +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 -alias statusOK = 200 -@value -struct RequestHeader: - var disable_normalization: Bool - var no_http_1_1: Bool - var __connection_close: Bool - var __content_length: Int - var __content_length_bytes: Bytes - var __method: Bytes - var __request_uri: Bytes - var proto: Bytes - var __host: Bytes - var __content_type: Bytes - var __user_agent: Bytes - var __transfer_encoding: Bytes - var raw_headers: Bytes - var __trailer: Bytes - - fn __init__(inout self) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = False - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__method = Bytes() - self.__request_uri = Bytes() - self.proto = Bytes() - self.__host = Bytes() - self.__content_type = Bytes() - self.__user_agent = Bytes() - self.__transfer_encoding = Bytes() - self.raw_headers = Bytes() - self.__trailer = Bytes() - - fn __init__(inout self, host: String) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = False - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__method = Bytes() - self.__request_uri = Bytes() - self.proto = Bytes() - self.__host = bytes(host) - self.__content_type = Bytes() - self.__user_agent = Bytes() - self.__transfer_encoding = Bytes() - self.raw_headers = Bytes() - self.__trailer = Bytes() - - fn __init__(inout self, rawheaders: Bytes) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = False - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__method = Bytes() - self.__request_uri = Bytes() - self.proto = Bytes() - self.__host = Bytes() - self.__content_type = Bytes() - self.__user_agent = Bytes() - self.__transfer_encoding = Bytes() - self.raw_headers = rawheaders - self.__trailer = Bytes() - - fn __init__( - inout self, - disable_normalization: Bool, - no_http_1_1: Bool, - connection_close: Bool, - content_length: Int, - content_length_bytes: Bytes, - method: Bytes, - request_uri: Bytes, - proto: Bytes, - host: Bytes, - content_type: Bytes, - user_agent: Bytes, - transfer_encoding: Bytes, - raw_headers: Bytes, - trailer: Bytes, - ) -> None: - self.disable_normalization = disable_normalization - self.no_http_1_1 = no_http_1_1 - self.__connection_close = connection_close - self.__content_length = content_length - self.__content_length_bytes = content_length_bytes - self.__method = method - self.__request_uri = request_uri - self.proto = proto - self.__host = host - self.__content_type = content_type - self.__user_agent = user_agent - self.__transfer_encoding = transfer_encoding - self.raw_headers = raw_headers - self.__trailer = trailer - - fn set_content_type(inout self, content_type: String) -> Self: - self.__content_type = bytes(content_type) - return self - - fn set_content_type_bytes(inout self, content_type: Bytes) -> Self: - self.__content_type = content_type - return self - - fn content_type(self) -> BytesView: - return BytesView(unsafe_ptr=self.__content_type.unsafe_ptr(), len=self.__content_type.size) - - fn set_host(inout self, host: String) -> Self: - self.__host = bytes(host) - return self - - fn set_host_bytes(inout self, host: Bytes) -> Self: - self.__host = host - return self - - fn host(self) -> BytesView: - return BytesView(unsafe_ptr=self.__host.unsafe_ptr(), len=self.__host.size) - - fn set_user_agent(inout self, user_agent: String) -> Self: - self.__user_agent = bytes(user_agent) - return self - - fn set_user_agent_bytes(inout self, user_agent: Bytes) -> Self: - self.__user_agent = user_agent - return self - - fn user_agent(self) -> BytesView: - return BytesView(unsafe_ptr=self.__user_agent.unsafe_ptr(), len=self.__user_agent.size) - - fn set_method(inout self, method: String) -> Self: - self.__method = bytes(method) - return self - - fn set_method_bytes(inout self, method: Bytes) -> Self: - self.__method = method - return self - - fn method(self) -> BytesView: - if len(self.__method) == 0: - return strMethodGet.as_bytes_slice() - return BytesView(unsafe_ptr=self.__method.unsafe_ptr(), len=self.__method.size) - - fn set_protocol(inout self, proto: String) -> Self: - self.no_http_1_1 = False # hardcoded until HTTP/2 is supported - self.proto = bytes(proto) - return self - - fn set_protocol_bytes(inout self, proto: Bytes) -> Self: - self.no_http_1_1 = False # hardcoded until HTTP/2 is supported - self.proto = proto - return self - - fn protocol_str(self) -> String: - if len(self.proto) == 0: - return strHttp11 - return String(self.proto) - - fn protocol(self) -> BytesView: - if len(self.proto) == 0: - return strHttp11.as_bytes_slice() - return BytesView(unsafe_ptr=self.proto.unsafe_ptr(), len=self.proto.size) - - fn content_length(self) -> Int: - return self.__content_length - - fn set_content_length(inout self, content_length: Int) -> Self: - self.__content_length = content_length - return self - - fn set_content_length_bytes(inout self, content_length: Bytes) -> Self: - self.__content_length_bytes = content_length - return self - - fn set_request_uri(inout self, request_uri: String) -> Self: - self.__request_uri = request_uri.as_bytes_slice() - return self - - fn set_request_uri_bytes(inout self, request_uri: Bytes) -> Self: - self.__request_uri = request_uri - return self - - fn request_uri(self) -> BytesView: - if len(self.__request_uri) <= 1: - return BytesView(unsafe_ptr=strSlash.as_bytes_slice().unsafe_ptr(), len=2) - return BytesView(unsafe_ptr=self.__request_uri.unsafe_ptr(), len=self.__request_uri.size) - - fn set_transfer_encoding(inout self, transfer_encoding: String) -> Self: - self.__transfer_encoding = bytes(transfer_encoding) - return self - - fn set_transfer_encoding_bytes(inout self, transfer_encoding: Bytes) -> Self: - self.__transfer_encoding = transfer_encoding - return self - - fn transfer_encoding(self) -> BytesView: - return BytesView(unsafe_ptr=self.__transfer_encoding.unsafe_ptr(), len=self.__transfer_encoding.size) - - fn set_trailer(inout self, trailer: String) -> Self: - self.__trailer = bytes(trailer) - return self - - fn set_trailer_bytes(inout self, trailer: Bytes) -> Self: - self.__trailer = trailer - return self - - fn trailer(self) -> BytesView: - return BytesView(unsafe_ptr=self.__trailer.unsafe_ptr(), len=self.__trailer.size) - - fn trailer_str(self) -> String: - return String(self.__trailer) - - fn set_connection_close(inout self) -> Self: - self.__connection_close = True - return self - - fn reset_connection_close(inout self) -> Self: - if self.__connection_close == False: - return self - else: - self.__connection_close = False - return self - - fn connection_close(self) -> Bool: - return self.__connection_close - - fn headers(self) -> String: - return String(self.raw_headers) - - fn parse_raw(inout self, inout r: Reader) raises -> Int: - var first_byte = r.peek(1) - if len(first_byte) == 0: - raise Error("Failed to read first byte from request header") - - var buf: Bytes - var e: Error - - buf, e = r.peek(r.buffered()) - if e: - raise Error("Failed to read request header: " + e.__str__()) - if len(buf) == 0: - raise Error("Failed to read request header, empty buffer") - - var end_of_first_line = self.parse_first_line(buf) - - var header_len = self.read_raw_headers(buf[end_of_first_line:]) - - self.parse_headers(buf[end_of_first_line:]) - - return end_of_first_line + header_len - - fn parse_first_line(inout self, buf: Bytes) raises -> Int: - var b_next = buf - var b = Bytes() - while len(b) == 0: - try: - b, b_next = next_line(b_next) - except e: - raise Error("Failed to read first line from request, " + e.__str__()) - - var first_whitespace = index_byte(b, bytes(whitespace, pop=False)[0]) - if first_whitespace <= 0: - raise Error("Could not find HTTP request method in request line: " + String(b)) - - _ = self.set_method_bytes(b[:first_whitespace]) - - var last_whitespace = last_index_byte(b, bytes(whitespace, pop=False)[0]) + 1 +struct HeaderKey: + # TODO: Fill in more of these + alias CONNECTION = "connection" + alias CONTENT_TYPE = "content-type" + alias CONTENT_LENGTH = "content-length" + alias CONTENT_ENCODING = "content-encoding" + alias DATE = "date" - if last_whitespace < 0: - raise Error("Could not find request target or HTTP version in request line: " + String(b)) - elif last_whitespace == 0: - raise Error("Request URI is empty: " + String(b)) - var proto = b[last_whitespace :] - if len(proto) != len(bytes(strHttp11, pop=False)): - raise Error("Invalid protocol, HTTP version not supported: " + String(proto)) - _ = self.set_protocol_bytes(proto) - _ = self.set_request_uri_bytes(b[first_whitespace+1:last_whitespace]) - - return len(buf) - len(b_next) - - fn parse_headers(inout self, buf: Bytes) raises -> None: - _ = self.set_content_length(-2) - var s = headerScanner() - s.set_b(buf) - while s.next(): - if len(s.key()) > 0: - self.parse_header(s.key(), s.value()) - - fn parse_header(inout self, key: Bytes, value: Bytes) raises -> None: - if index_byte(key, bytes(colonChar, pop=False)[0]) == -1 or index_byte(key, bytes(tab, pop=False)[0]) != -1: - raise Error("Invalid header key: " + String(key)) +@value +struct Header: + var key: String + var value: String - var key_first = key[0].__xor__(0x20) - if key_first == bytes("h", pop=False)[0] or key_first == bytes("H", pop=False)[0]: - if compare_case_insensitive(key, bytes("host", pop=False)): - _ = self.set_host_bytes(bytes(value)) - return - elif key_first == bytes("u", pop=False)[0] or key_first == bytes("U", pop=False)[0]: - if compare_case_insensitive(key, bytes("user-agent", pop=False)): - _ = self.set_user_agent_bytes(bytes(value)) - return - elif key_first == bytes("c", pop=False)[0] or key_first == bytes("C", pop=False)[0]: - if compare_case_insensitive(key, bytes("content-type", pop=False)): - _ = self.set_content_type_bytes(bytes(value)) - return - if compare_case_insensitive(key, bytes("content-length", pop=False)): - if self.content_length() != -1: - _ = self.set_content_length(atol(value)) - return - if compare_case_insensitive(key, bytes("connection", pop=False)): - if compare_case_insensitive(bytes(value), bytes("close", pop=False)): - _ = self.set_connection_close() - else: - _ = self.reset_connection_close() - return - elif key_first == bytes("t", pop=False)[0] or key_first == bytes("T", pop=False)[0]: - if compare_case_insensitive(key, bytes("transfer-encoding", pop=False)): - _ = self.set_transfer_encoding_bytes(bytes(value, pop=False)) - return - if compare_case_insensitive(key, bytes("trailer", pop=False)): - _ = self.set_trailer_bytes(bytes(value, pop=False)) - return - if self.content_length() < 0: - _ = self.set_content_length(0) - return +@always_inline +fn write_header(inout writer: Formatter, key: String, value: String): + writer.write(key + ": ", value, lineBreak) - fn read_raw_headers(inout self, buf: Bytes) raises -> Int: - var n = index_byte(buf, bytes(nChar, pop=False)[0]) - if n == -1: - self.raw_headers = self.raw_headers[:0] - raise Error("Failed to find a newline in headers") - - if n == 0 or (n == 1 and (buf[0] == bytes(rChar, pop=False)[0])): - # empty line -> end of headers - return n + 1 - - n += 1 - var b = buf - var m = n - while True: - b = b[m:] - m = index_byte(b, bytes(nChar, pop=False)[0]) - if m == -1: - raise Error("Failed to find a newline in headers") - m += 1 - n += m - if m == 2 and (b[0] == bytes(rChar, pop=False)[0]) or m == 1: - self.raw_headers = self.raw_headers + buf[:n] - return n +@always_inline +fn write_header(inout writer: ByteWriter, key: String, inout value: String): + var k = key + ": " + writer.write(k) + writer.write(value) + writer.write(lineBreak) @value -struct ResponseHeader: - var disable_normalization: Bool - var no_http_1_1: Bool - var __connection_close: Bool - var __status_code: Int - var __status_message: Bytes - var __protocol: Bytes - var __content_length: Int - var __content_length_bytes: Bytes - var __content_type: Bytes - var __content_encoding: Bytes - var __server: Bytes - var __trailer: Bytes - var raw_headers: Bytes - - fn __init__( - inout self, - ) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = False - self.__status_code = 200 - self.__status_message = Bytes() - self.__protocol = Bytes() - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__content_type = Bytes() - self.__content_encoding = Bytes() - self.__server = Bytes() - self.__trailer = Bytes() - self.raw_headers = Bytes() - - fn __init__( - inout self, - raw_headers: Bytes, - ) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = False - self.__status_code = 200 - self.__status_message = Bytes() - self.__protocol = Bytes() - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__content_type = Bytes() - self.__content_encoding = Bytes() - self.__server = Bytes() - self.__trailer = Bytes() - self.raw_headers = raw_headers - - fn __init__( - inout self, - status_code: Int, - status_message: Bytes, - content_type: Bytes, - ) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = False - self.__status_code = status_code - self.__status_message = status_message - self.__protocol = Bytes() - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__content_type = content_type - self.__content_encoding = Bytes() - self.__server = Bytes() - self.__trailer = Bytes() - self.raw_headers = Bytes() - - fn __init__( - inout self, - status_code: Int, - status_message: Bytes, - content_type: Bytes, - content_encoding: Bytes, - ) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = False - self.__status_code = status_code - self.__status_message = status_message - self.__protocol = Bytes() - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__content_type = content_type - self.__content_encoding = content_encoding - self.__server = Bytes() - self.__trailer = Bytes() - self.raw_headers = Bytes() - - fn __init__( - inout self, - connection_close: Bool, - status_code: Int, - status_message: Bytes, - content_type: Bytes, - ) -> None: - self.disable_normalization = False - self.no_http_1_1 = False - self.__connection_close = connection_close - self.__status_code = status_code - self.__status_message = status_message - self.__protocol = Bytes() - self.__content_length = 0 - self.__content_length_bytes = Bytes() - self.__content_type = content_type - self.__content_encoding = Bytes() - self.__server = Bytes() - self.__trailer = Bytes() - self.raw_headers = Bytes() - - fn __init__( - inout self, - disable_normalization: Bool, - no_http_1_1: Bool, - connection_close: Bool, - status_code: Int, - status_message: Bytes, - protocol: Bytes, - content_length: Int, - content_length_bytes: Bytes, - content_type: Bytes, - content_encoding: Bytes, - server: Bytes, - trailer: Bytes, - ) -> None: - self.disable_normalization = disable_normalization - self.no_http_1_1 = no_http_1_1 - self.__connection_close = connection_close - self.__status_code = status_code - self.__status_message = status_message - self.__protocol = protocol - self.__content_length = content_length - self.__content_length_bytes = content_length_bytes - self.__content_type = content_type - self.__content_encoding = content_encoding - self.__server = server - self.__trailer = trailer - self.raw_headers = Bytes() +struct Headers(Formattable, Stringable): + """Represents the header key/values in an http request/response. - fn set_status_code(inout self, code: Int) -> Self: - self.__status_code = code - return self + Header keys are normalized to lowercase + """ - fn status_code(self) -> Int: - if self.__status_code == 0: - return statusOK - return self.__status_code + var _inner: Dict[String, String] - fn set_status_message(inout self, message: Bytes) -> Self: - self.__status_message = message - return self - - fn status_message(self) -> BytesView: - return BytesView(unsafe_ptr=self.__status_message.unsafe_ptr(), len=self.__status_message.size) - - fn status_message_str(self) -> String: - return String(self.status_message()) + fn __init__(inout self): + self._inner = Dict[String, String]() - fn content_type(self) -> BytesView: - return BytesView(unsafe_ptr=self.__content_type.unsafe_ptr(), len=self.__content_type.size) + fn __init__(inout self, owned *headers: Header): + self._inner = Dict[String, String]() + for header in headers: + self[header[].key.lower()] = header[].value - fn set_content_type(inout self, content_type: String) -> Self: - self.__content_type = bytes(content_type) - return self + @always_inline + fn empty(self) -> Bool: + return len(self._inner) == 0 - fn set_content_type_bytes(inout self, content_type: Bytes) -> Self: - self.__content_type = content_type - return self + @always_inline + fn __contains__(self, key: String) -> Bool: + return key.lower() in self._inner - fn content_encoding(self) -> BytesView: - return BytesView(unsafe_ptr=self.__content_encoding.unsafe_ptr(), len=self.__content_encoding.size) + @always_inline + fn __getitem__(self, key: String) -> String: + try: + return self._inner[key.lower()] + except: + return String() - fn set_content_encoding(inout self, content_encoding: String) -> Self: - self.__content_encoding = bytes(content_encoding) - return self + @always_inline + fn __setitem__(inout self, key: String, value: String): + self._inner[key.lower()] = value - fn set_content_encoding_bytes(inout self, content_encoding: Bytes) -> Self: - self.__content_encoding = content_encoding - return self - fn content_length(self) -> Int: - return self.__content_length - - fn set_content_length(inout self, content_length: Int) -> Self: - self.__content_length = content_length - return self - - fn set_content_length_bytes(inout self, content_length: Bytes) -> Self: - self.__content_length_bytes = content_length - return self - - fn server(self) -> BytesView: - return BytesView(unsafe_ptr=self.__server.unsafe_ptr(), len=self.__server.size) - - fn set_server(inout self, server: String) -> Self: - self.__server = bytes(server) - return self - - fn set_server_bytes(inout self, server: Bytes) -> Self: - self.__server = server - return self - - fn set_protocol(inout self, proto: String) -> Self: - self.no_http_1_1 = False # hardcoded until HTTP/2 is supported - self.__protocol = bytes(proto) - return self - - fn set_protocol_bytes(inout self, protocol: Bytes) -> Self: - self.no_http_1_1 = False # hardcoded until HTTP/2 is supported - self.__protocol = protocol - return self - - fn protocol_str(self) -> String: - if len(self.__protocol) == 0: - return strHttp11 - return String(self.__protocol) - - fn protocol(self) -> BytesView: - if len(self.__protocol) == 0: - return BytesView(unsafe_ptr=strHttp11.as_bytes_slice().unsafe_ptr(), len=8) - return BytesView(unsafe_ptr=self.__protocol.unsafe_ptr(), len=self.__protocol.size) - - fn set_trailer(inout self, trailer: String) -> Self: - self.__trailer = bytes(trailer) - return self - - fn set_trailer_bytes(inout self, trailer: Bytes) -> Self: - self.__trailer = trailer - return self - - fn trailer(self) -> BytesView: - return BytesView(unsafe_ptr=self.__trailer.unsafe_ptr(), len=self.__trailer.size) - - fn trailer_str(self) -> String: - return String(self.trailer()) - - fn set_connection_close(inout self) -> Self: - self.__connection_close = True - return self - - fn reset_connection_close(inout self) -> Self: - if self.__connection_close == False: - return self - else: - self.__connection_close = False - return self - - fn connection_close(self) -> Bool: - return self.__connection_close - - fn headers(self) -> String: - return String(self.raw_headers) - - fn parse_raw(inout self, inout r: Reader) raises -> Int: - var first_byte = r.peek(1) - if len(first_byte) == 0: + if HeaderKey.CONTENT_LENGTH not in self: + return 0 + try: + return int(self[HeaderKey.CONTENT_LENGTH]) + except: + return 0 + + fn parse_raw( + inout self, inout r: ByteReader + ) raises -> (String, String, String): + var first_byte = r.peek() + if not first_byte: raise Error("Failed to read first byte from response header") - - var buf: Bytes - var e: Error - - buf, e = r.peek(r.buffered()) - if e: - raise Error("Failed to read response header: " + e.__str__()) - if len(buf) == 0: - raise Error("Failed to read response header, empty buffer") - - var end_of_first_line = self.parse_first_line(buf) - - var header_len = self.read_raw_headers(buf[end_of_first_line:]) - - self.parse_headers(buf[end_of_first_line:]) - - return end_of_first_line + header_len - - fn parse_first_line(inout self, buf: Bytes) raises -> Int: - var b_next = buf - var b = Bytes() - while len(b) == 0: - try: - b, b_next = next_line(b_next) - except e: - raise Error("Failed to read first line from response, " + e.__str__()) - - var first_whitespace = index_byte(b, bytes(whitespace, pop=False)[0]) - if first_whitespace <= 0: - raise Error("Could not find HTTP version in response line: " + String(b)) - - _ = self.set_protocol(b[:first_whitespace+2]) - - var end_of_status_code = first_whitespace+5 # status code is always 3 digits, this calculation includes null terminator - - var status_code = atol(b[first_whitespace+1:end_of_status_code]) - _ = self.set_status_code(status_code) - - var status_text = b[end_of_status_code :] - if len(status_text) > 1: - _ = self.set_status_message(status_text) - - return len(buf) - len(b_next) - - fn parse_headers(inout self, buf: Bytes) raises -> None: - _ = self.set_content_length(-2) - var s = headerScanner() - s.set_b(buf) - - while s.next(): - if len(s.key()) > 0: - self.parse_header(s.key(), s.value()) - - fn parse_header(inout self, key: Bytes, value: Bytes) raises -> None: - if index_byte(key, bytes(colonChar, pop=False)[0]) == -1 or index_byte(key, bytes(tab, pop=False)[0]) != -1: - raise Error("Invalid header key: " + String(key)) - - var key_first = key[0].__xor__(0x20) - - if key_first == bytes("c", pop=False)[0] or key_first == bytes("C", pop=False)[0]: - if compare_case_insensitive(key, bytes("content-type", pop=False)): - _ = self.set_content_type_bytes(bytes(value)) - return - if compare_case_insensitive(key, bytes("content-encoding", pop=False)): - _ = self.set_content_encoding_bytes(bytes(value)) - return - if compare_case_insensitive(key, bytes("content-length", pop=False)): - if self.content_length() != -1: - var content_length = value - _ = self.set_content_length(atol(content_length)) - _ = self.set_content_length_bytes(bytes(content_length)) - return - if compare_case_insensitive(key, bytes("connection", pop=False)): - if compare_case_insensitive(bytes(value), bytes("close", pop=False)): - _ = self.set_connection_close() - else: - _ = self.reset_connection_close() - return - elif key_first == bytes("s", pop=False)[0] or key_first == bytes("S", pop=False)[0]: - if compare_case_insensitive(key, bytes("server", pop=False)): - _ = self.set_server_bytes(bytes(value)) - return - elif key_first == bytes("t", pop=False)[0] or key_first == bytes("T", pop=False)[0]: - if compare_case_insensitive(key, bytes("transfer-encoding", pop=False)): - if not compare_case_insensitive(value, bytes("identity", pop=False)): - _ = self.set_content_length(-1) - return - if compare_case_insensitive(key, bytes("trailer", pop=False)): - _ = self.set_trailer_bytes(bytes(value)) - - fn read_raw_headers(inout self, buf: Bytes) raises -> Int: - var n = index_byte(buf, bytes(nChar, pop=False)[0]) - - if n == -1: - self.raw_headers = self.raw_headers[:0] - raise Error("Failed to find a newline in headers") - - if n == 0 or (n == 1 and (buf[0] == bytes(rChar, pop=False)[0])): - # empty line -> end of headers - return n + 1 - - n += 1 - var b = buf - var m = n - while True: - b = b[m:] - m = index_byte(b, bytes(nChar, pop=False)[0]) - if m == -1: - raise Error("Failed to find a newline in headers") - m += 1 - n += m - if m == 2 and (b[0] == bytes(rChar, pop=False)[0]) or m == 1: - self.raw_headers = self.raw_headers + buf[:n] - return n - -struct headerScanner: - var __b: Bytes - var __key: Bytes - var __value: Bytes - var __subslice_len: Int - var disable_normalization: Bool - var __next_colon: Int - var __next_line: Int - var __initialized: Bool - - fn __init__(inout self) -> None: - self.__b = Bytes() - self.__key = Bytes() - self.__value = Bytes() - self.__subslice_len = 0 - self.disable_normalization = False - self.__next_colon = 0 - self.__next_line = 0 - self.__initialized = False - - fn b(self) -> Bytes: - return self.__b - - fn set_b(inout self, b: Bytes) -> None: - self.__b = b - - fn key(self) -> Bytes: - return self.__key - - fn set_key(inout self, key: Bytes) -> None: - self.__key = key - - fn value(self) -> Bytes: - return self.__value - - fn set_value(inout self, value: Bytes) -> None: - self.__value = value - - fn subslice_len(self) -> Int: - return self.__subslice_len - - fn set_subslice_len(inout self, n: Int) -> None: - self.__subslice_len = n - - fn next_colon(self) -> Int: - return self.__next_colon - - fn set_next_colon(inout self, n: Int) -> None: - self.__next_colon = n - - fn next_line(self) -> Int: - return self.__next_line - - fn set_next_line(inout self, n: Int) -> None: - self.__next_line = n - - fn initialized(self) -> Bool: - return self.__initialized - - fn set_initialized(inout self) -> None: - self.__initialized = True - - fn next(inout self) raises -> Bool: - if not self.initialized(): - self.set_next_colon(-1) - self.set_next_line(-1) - self.set_initialized() - - var b_len = len(self.b()) - - if b_len >= 2 and (self.b()[0] == bytes(rChar, pop=False)[0]) and (self.b()[1] == bytes(nChar, pop=False)[0]): - self.set_b(self.b()[2:]) - self.set_subslice_len(2) - return False - - if b_len >= 1 and (self.b()[0] == bytes(nChar, pop=False)[0]): - self.set_b(self.b()[1:]) - self.set_subslice_len(self.subslice_len() + 1) - return False - - var colon: Int - if self.next_colon() >= 0: - colon = self.next_colon() - self.set_next_colon(-1) - else: - colon = index_byte(self.b(), bytes(colonChar, pop=False)[0]) - var newline = index_byte(self.b(), bytes(nChar, pop=False)[0]) - if newline < 0: - raise Error("Invalid header, did not find a newline at the end of the header") - if newline < colon: - raise Error("Invalid header, found a newline before the colon") - if colon < 0: - raise Error("Invalid header, did not find a colon") - - var jump_to = colon + 1 - self.set_key(self.b()[:jump_to]) - - while len(self.b()) > jump_to and (self.b()[jump_to] == bytes(whitespace, pop=False)[0]): - jump_to += 1 - self.set_next_line(self.next_line() - 1) - - self.set_subslice_len(self.subslice_len() + jump_to) - self.set_b(self.b()[jump_to:]) - - if self.next_line() >= 0: - jump_to = self.next_line() - self.set_next_line(-1) - else: - jump_to = index_byte(self.b(), bytes(nChar, pop=False)[0]) - if jump_to < 0: - raise Error("Invalid header, did not find a newline") - - jump_to += 1 - self.set_value(self.b()[:jump_to]) - self.set_subslice_len(self.subslice_len() + jump_to) - self.set_b(self.b()[jump_to:]) - if jump_to > 0 and (self.value()[jump_to-1] == bytes(rChar, pop=False)[0]): - jump_to -= 1 - while jump_to > 0 and (self.value()[jump_to-1] == bytes(whitespace, pop=False)[0]): - jump_to -= 1 - self.set_value(self.value()[:jump_to]) - - return True - + var first = r.read_word() + r.increment() + var second = r.read_word() + r.increment() + var third = r.read_line() + + while not is_newline(r.peek()): + var key = r.read_until(BytesConstant.colon) + r.increment() + if is_space(r.peek()): + r.increment() + # TODO (bgreni): Handle possible trailing whitespace + var value = r.read_line() + self._inner[to_string(key^).lower()] = to_string(value^) + return (to_string(first^), to_string(second^), to_string(third^)) + + fn format_to(self, inout writer: Formatter): + for header in self._inner.items(): + write_header(writer, header[].key, header[].value) + + fn encode_to(inout self, inout writer: ByteWriter): + for header in self._inner.items(): + write_header(writer, header[].key, header[].value) + + fn __str__(self) -> String: + return to_string(self) diff --git a/lightbug_http/http.mojo b/lightbug_http/http.mojo index af2e08dd..c5109998 100644 --- a/lightbug_http/http.mojo +++ b/lightbug_http/http.mojo @@ -1,430 +1,354 @@ -from time import now -from external.morrow import Morrow -from external.gojo.strings.builder import StringBuilder -from external.gojo.bufio import Reader +from utils.string_slice import StringSlice +from utils import Span +from small_time.small_time import now from lightbug_http.uri import URI -from lightbug_http.io.bytes import Bytes, BytesView, bytes -from lightbug_http.header import RequestHeader, ResponseHeader +from lightbug_http.utils import ByteReader, ByteWriter +from lightbug_http.io.bytes import Bytes, bytes, Byte +from lightbug_http.header import Headers, HeaderKey, Header, write_header from lightbug_http.io.sync import Duration from lightbug_http.net import Addr, TCPAddr -from lightbug_http.strings import strHttp11, strHttp, strSlash, whitespace, rChar, nChar +from lightbug_http.strings import ( + strHttp11, + strHttp, + strSlash, + whitespace, + rChar, + nChar, + lineBreak, + to_string, +) -trait Request: - fn __init__(inout self, uri: URI): - ... - fn __init__( - inout self, - header: RequestHeader, - uri: URI, - body: Bytes, - parsed_uri: Bool, - server_is_tls: Bool, - timeout: Duration, - disable_redirect_path_normalization: Bool, - ): - ... +alias OK_MESSAGE = String("OK").as_bytes() +alias NOT_FOUND_MESSAGE = String("Not Found").as_bytes() +alias TEXT_PLAIN_CONTENT_TYPE = String("text/plain").as_bytes() +alias OCTET_STREAM_CONTENT_TYPE = String("application/octet-stream").as_bytes() - fn set_host(inout self, host: String) -> Self: - ... - fn set_host_bytes(inout self, host: Bytes) -> Self: - ... +@always_inline +fn encode(owned req: HTTPRequest) -> Bytes: + return req._encoded() - fn host(self) -> String: - ... - fn set_request_uri(inout self, request_uri: String) -> Self: - ... +@always_inline +fn encode(owned res: HTTPResponse) -> Bytes: + return res._encoded() - fn set_request_uri_bytes(inout self, request_uri: Bytes) -> Self: - ... - - fn request_uri(inout self) -> String: - ... - - fn set_connection_close(inout self) -> Self: - ... - - fn connection_close(self) -> Bool: - ... - - -trait Response: - fn __init__(inout self, header: ResponseHeader, body: Bytes): - ... - - fn set_status_code(inout self, status_code: Int) -> Self: - ... +@value +struct HTTPRequest(Formattable, Stringable): + var headers: Headers + var uri: URI + var body_raw: Bytes - fn status_code(self) -> Int: - ... + var method: String + var protocol: String - fn set_connection_close(inout self) -> Self: - ... + var server_is_tls: Bool + var timeout: Duration - fn connection_close(self) -> Bool: - ... + @staticmethod + fn from_bytes( + addr: String, max_body_size: Int, owned b: Bytes + ) raises -> HTTPRequest: + var reader = ByteReader(b^) + var headers = Headers() + var method: String + var protocol: String + var uri_str: String + try: + method, uri_str, protocol = headers.parse_raw(reader) + except e: + raise Error("Failed to parse request headers: " + e.__str__()) + + var uri = URI.parse_raises(addr + uri_str) + + var content_length = headers.content_length() + + if ( + content_length > 0 + and max_body_size > 0 + and content_length > max_body_size + ): + raise Error("Request body too large") + var request = HTTPRequest( + uri, headers=headers, method=method, protocol=protocol + ) -@value -struct HTTPRequest(Request): - var header: RequestHeader - var __uri: URI - var body_raw: Bytes + try: + request.read_body(reader, content_length, max_body_size) + except e: + raise Error("Failed to read request body: " + e.__str__()) - var parsed_uri: Bool - var server_is_tls: Bool - var timeout: Duration - var disable_redirect_path_normalization: Bool - - fn __init__(inout self, uri: URI): - self.header = RequestHeader("127.0.0.1") - self.__uri = uri - self.body_raw = Bytes() - self.parsed_uri = False - self.server_is_tls = False - self.timeout = Duration() - self.disable_redirect_path_normalization = False - - fn __init__(inout self, uri: URI, headers: RequestHeader): - self.header = headers - self.__uri = uri - self.body_raw = Bytes() - self.parsed_uri = False - self.server_is_tls = False - self.timeout = Duration() - self.disable_redirect_path_normalization = False - - fn __init__(inout self, uri: URI, buf: Bytes, headers: RequestHeader): - self.header = headers - self.__uri = uri - self.body_raw = buf - self.parsed_uri = False - self.server_is_tls = False - self.timeout = Duration() - self.disable_redirect_path_normalization = False + return request fn __init__( inout self, - header: RequestHeader, uri: URI, - body: Bytes, - parsed_uri: Bool, - server_is_tls: Bool, - timeout: Duration, - disable_redirect_path_normalization: Bool, + headers: Headers = Headers(), + method: String = "GET", + protocol: String = strHttp11, + body: Bytes = Bytes(), + server_is_tls: Bool = False, + timeout: Duration = Duration(), ): - self.header = header - self.__uri = uri + self.headers = headers + self.method = method + self.protocol = protocol + self.uri = uri self.body_raw = body - self.parsed_uri = parsed_uri self.server_is_tls = server_is_tls self.timeout = timeout - self.disable_redirect_path_normalization = disable_redirect_path_normalization + self.set_content_length(len(body)) + if HeaderKey.CONNECTION not in self.headers: + self.set_connection_close() - fn get_body_bytes(self) -> BytesView: - return BytesView(unsafe_ptr=self.body_raw.unsafe_ptr(), len=self.body_raw.size) + fn set_connection_close(inout self): + self.headers[HeaderKey.CONNECTION] = "close" - fn set_body_bytes(inout self, body: Bytes) -> Self: - self.body_raw = body - return self + fn set_content_length(inout self, l: Int): + self.headers[HeaderKey.CONTENT_LENGTH] = str(l) - fn set_host(inout self, host: String) -> Self: - _ = self.__uri.set_host(host) - return self + fn connection_close(self) -> Bool: + return self.headers[HeaderKey.CONNECTION] == "close" - fn set_host_bytes(inout self, host: Bytes) -> Self: - _ = self.__uri.set_host_bytes(host) - return self + @always_inline + fn read_body( + inout self, inout r: ByteReader, content_length: Int, max_body_size: Int + ) raises -> None: + if content_length > max_body_size: + raise Error("Request body too large") - fn host(self) -> String: - return self.__uri.host_str() + r.consume(self.body_raw) + self.set_content_length(content_length) + + fn format_to(self, inout writer: Formatter): + writer.write( + self.method, + whitespace, + self.uri.path if len(self.uri.path) > 1 else strSlash, + whitespace, + self.protocol, + lineBreak, + ) - fn set_request_uri(inout self, request_uri: String) -> Self: - _ = self.header.set_request_uri(request_uri.as_bytes()) - self.parsed_uri = False - return self + self.headers.format_to(writer) + writer.write(lineBreak) + writer.write(to_string(self.body_raw)) - fn set_request_uri_bytes(inout self, request_uri: Bytes) -> Self: - _ = self.header.set_request_uri_bytes(request_uri) - return self + fn _encoded(inout self) -> Bytes: + """Encodes request as bytes. - fn request_uri(inout self) -> String: - if self.parsed_uri: - _ = self.set_request_uri_bytes(self.__uri.request_uri()) - return self.header.request_uri() + This method consumes the data in this request and it should + no longer be considered valid. + """ + var writer = ByteWriter() + writer.write(self.method) + writer.write(whitespace) + var path = self.uri.path if len(self.uri.path) > 1 else strSlash + writer.write(path) + writer.write(whitespace) + writer.write(self.protocol) + writer.write(lineBreak) - fn uri(self) -> URI: - return self.__uri + self.headers.encode_to(writer) + writer.write(lineBreak) - fn set_connection_close(inout self) -> Self: - _ = self.header.set_connection_close() - return self + writer.write(self.body_raw) - fn connection_close(self) -> Bool: - return self.header.connection_close() - - fn read_body(inout self, inout r: Reader, content_length: Int, header_len: Int, max_body_size: Int) raises -> None: - if content_length > max_body_size: - raise Error("Request body too large") + return writer.consume() - _ = r.discard(header_len) + fn __str__(self) -> String: + return to_string(self) - var body_buf: Bytes - body_buf, _ = r.peek(r.buffered()) - - _ = self.set_body_bytes(bytes(body_buf)) @value -struct HTTPResponse(Response): - var header: ResponseHeader - var stream_immediate_header_flush: Bool - var stream_body: Bool +struct HTTPResponse(Formattable, Stringable): + var headers: Headers var body_raw: Bytes var skip_reading_writing_body: Bool var raddr: TCPAddr var laddr: TCPAddr - - fn __init__(inout self, body_bytes: Bytes): - self.header = ResponseHeader( - 200, - bytes("OK"), - bytes("application/octet-stream"), + var __is_upgrade: Bool + + var status_code: Int + var status_text: String + var protocol: String + + @staticmethod + fn from_bytes(owned b: Bytes) raises -> HTTPResponse: + var reader = ByteReader(b^) + + var headers = Headers() + var protocol: String + var status_code: String + var status_text: String + + try: + protocol, status_code, status_text = headers.parse_raw(reader) + except e: + raise Error("Failed to parse response headers: " + e.__str__()) + + var response = HTTPResponse( + Bytes(), + headers=headers, + protocol=protocol, + status_code=int(status_code), + status_text=status_text, ) - self.stream_immediate_header_flush = False - self.stream_body = False - self.body_raw = body_bytes - self.skip_reading_writing_body = False - self.raddr = TCPAddr() - self.laddr = TCPAddr() - fn __init__(inout self, header: ResponseHeader, body_bytes: Bytes): - self.header = header - self.stream_immediate_header_flush = False - self.stream_body = False + try: + response.read_body(reader) + return response + except e: + raise Error("Failed to read request body: " + e.__str__()) + + fn __init__( + inout self, + body_bytes: Bytes, + headers: Headers = Headers(), + status_code: Int = 200, + status_text: String = "OK", + protocol: String = strHttp11, + ): + self.headers = headers + if HeaderKey.CONTENT_TYPE not in self.headers: + self.headers[HeaderKey.CONTENT_TYPE] = "application/octet-stream" + self.status_code = status_code + self.status_text = status_text + self.protocol = protocol self.body_raw = body_bytes self.skip_reading_writing_body = False + self.__is_upgrade = False self.raddr = TCPAddr() self.laddr = TCPAddr() - - fn get_body_bytes(self) -> BytesView: - return BytesView(unsafe_ptr=self.body_raw.unsafe_ptr(), len=self.body_raw.size) + self.set_connection_keep_alive() + self.set_content_length(len(body_bytes)) - fn get_body(self) -> Bytes: + fn get_body_bytes(self) -> Bytes: return self.body_raw - fn set_body_bytes(inout self, body: Bytes) -> Self: - self.body_raw = body - return self - - fn set_status_code(inout self, status_code: Int) -> Self: - _ = self.header.set_status_code(status_code) - return self - - fn status_code(self) -> Int: - return self.header.status_code() + @always_inline + fn set_connection_close(inout self): + self.headers[HeaderKey.CONNECTION] = "close" - fn set_connection_close(inout self) -> Self: - _ = self.header.set_connection_close() - return self + @always_inline + fn set_connection_keep_alive(inout self): + self.headers[HeaderKey.CONNECTION] = "keep-alive" fn connection_close(self) -> Bool: - return self.header.connection_close() - - fn read_body(inout self, inout r: Reader, header_len: Int) raises -> None: - _ = r.discard(header_len) + return self.headers[HeaderKey.CONNECTION] == "close" + + @always_inline + fn set_content_length(inout self, l: Int): + self.headers[HeaderKey.CONTENT_LENGTH] = str(l) + + @always_inline + fn read_body(inout self, inout r: ByteReader) raises -> None: + r.consume(self.body_raw) + + fn format_to(self, inout writer: Formatter): + writer.write( + self.protocol, + whitespace, + self.status_code, + whitespace, + self.status_text, + lineBreak, + "server: lightbug_http", + lineBreak, + ) - var body_buf: Bytes - body_buf, _ = r.peek(r.buffered()) - - _ = self.set_body_bytes(bytes(body_buf)) + if HeaderKey.DATE not in self.headers: + try: + var current_time = now(utc=True).__str__() + write_header(writer, HeaderKey.DATE, current_time) + except: + pass -fn OK(body: StringLiteral) -> HTTPResponse: - return HTTPResponse( - ResponseHeader(200, bytes("OK"), bytes("text/plain")), bytes(body), - ) + self.headers.format_to(writer) + + writer.write(lineBreak) + writer.write(to_string(self.body_raw)) + + fn _encoded(inout self) -> Bytes: + """Encodes response as bytes. + + This method consumes the data in this request and it should + no longer be considered valid. + """ + var writer = ByteWriter() + writer.write(self.protocol) + writer.write(whitespace) + writer.write(bytes(str(self.status_code))) + writer.write(whitespace) + writer.write(self.status_text) + writer.write(lineBreak) + writer.write("server: lightbug_http") + writer.write(lineBreak) + + if HeaderKey.DATE not in self.headers: + try: + var current_time = now(utc=True).__str__() + write_header(writer, HeaderKey.DATE, current_time) + except: + pass + + self.headers.encode_to(writer) + + writer.write(lineBreak) + writer.write(self.body_raw) + + return writer.consume() + + fn __str__(self) -> String: + return to_string(self) -fn OK(body: StringLiteral, content_type: String) -> HTTPResponse: - return HTTPResponse( - ResponseHeader(200, bytes("OK"), bytes(content_type)), bytes(body), - ) fn OK(body: String) -> HTTPResponse: return HTTPResponse( - ResponseHeader(200, bytes("OK"), bytes("text/plain")), bytes(body), + headers=Headers(Header(HeaderKey.CONTENT_TYPE, "text/plain")), + body_bytes=bytes(body), ) + fn OK(body: String, content_type: String) -> HTTPResponse: return HTTPResponse( - ResponseHeader(200, bytes("OK"), bytes(content_type)), bytes(body), + headers=Headers(Header(HeaderKey.CONTENT_TYPE, content_type)), + body_bytes=bytes(body), ) + fn OK(body: Bytes) -> HTTPResponse: return HTTPResponse( - ResponseHeader(200, bytes("OK"), bytes("text/plain")), body, + headers=Headers(Header(HeaderKey.CONTENT_TYPE, "text/plain")), + body_bytes=body, ) + fn OK(body: Bytes, content_type: String) -> HTTPResponse: return HTTPResponse( - ResponseHeader(200, bytes("OK"), bytes(content_type)), body, + headers=Headers(Header(HeaderKey.CONTENT_TYPE, content_type)), + body_bytes=body, ) -fn OK(body: Bytes, content_type: String, content_encoding: String) -> HTTPResponse: + +fn OK( + body: Bytes, content_type: String, content_encoding: String +) -> HTTPResponse: return HTTPResponse( - ResponseHeader(200, bytes("OK"), bytes(content_type), bytes(content_encoding)), body, + headers=Headers( + Header(HeaderKey.CONTENT_TYPE, content_type), + Header(HeaderKey.CONTENT_ENCODING, content_encoding), + ), + body_bytes=body, ) + fn NotFound(path: String) -> HTTPResponse: return HTTPResponse( - ResponseHeader(404, bytes("Not Found"), bytes("text/plain")), bytes("path " + path + " not found"), + status_code=404, + status_text="Not Found", + headers=Headers(Header(HeaderKey.CONTENT_TYPE, "text/plain")), + body_bytes=bytes("path " + path + " not found"), ) - -fn encode(req: HTTPRequest) raises -> StringSlice[False, ImmutableStaticLifetime]: - var builder = StringBuilder() - - _ = builder.write(req.header.method()) - _ = builder.write_string(whitespace) - if len(req.uri().path_bytes()) > 1: - _ = builder.write_string(req.uri().path()) - else: - _ = builder.write_string(strSlash) - _ = builder.write_string(whitespace) - - _ = builder.write(req.header.protocol()) - - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - if len(req.header.host()) > 0: - _ = builder.write_string("Host: ") - _ = builder.write(req.header.host()) - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - if len(req.body_raw) > 0: - if len(req.header.content_type()) > 0: - _ = builder.write_string("Content-Type: ") - _ = builder.write(req.header.content_type()) - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - _ = builder.write_string("Content-Length: ") - _ = builder.write_string(len(req.body_raw).__str__()) - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - _ = builder.write_string("Connection: ") - if req.connection_close(): - _ = builder.write_string("close") - else: - _ = builder.write_string("keep-alive") - - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - if len(req.body_raw) > 0: - _ = builder.write(req.get_body_bytes()) - - return StringSlice[False, ImmutableStaticLifetime](unsafe_from_utf8_ptr=builder.render().unsafe_ptr(), len=builder.size) - - -fn encode(res: HTTPResponse) raises -> Bytes: - var current_time = String() - try: - current_time = Morrow.utcnow().__str__() - except e: - print("Error getting current time: " + str(e)) - current_time = str(now()) - - var builder = StringBuilder() - - _ = builder.write(res.header.protocol()) - _ = builder.write_string(whitespace) - _ = builder.write_string(res.header.status_code().__str__()) - _ = builder.write_string(whitespace) - _ = builder.write(res.header.status_message()) - - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - _ = builder.write_string("Server: lightbug_http") - - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - _ = builder.write_string("Content-Type: ") - _ = builder.write(res.header.content_type()) - - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - if len(res.header.content_encoding()) > 0: - _ = builder.write_string("Content-Encoding: ") - _ = builder.write(res.header.content_encoding()) - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - if len(res.body_raw) > 0: - _ = builder.write_string("Content-Length: ") - _ = builder.write_string(len(res.body_raw).__str__()) - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - else: - _ = builder.write_string("Content-Length: 0") - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - _ = builder.write_string("Connection: ") - if res.connection_close(): - _ = builder.write_string("close") - else: - _ = builder.write_string("keep-alive") - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - _ = builder.write_string("Date: ") - _ = builder.write_string(current_time) - - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - _ = builder.write_string(rChar) - _ = builder.write_string(nChar) - - if len(res.body_raw) > 0: - _ = builder.write(res.get_body_bytes()) - - return builder.render().as_bytes_slice() - -fn split_http_string(buf: Bytes) raises -> (String, String, String): - var request = String(buf) - - var request_first_line_headers_body = request.split("\r\n\r\n") - - if len(request_first_line_headers_body) == 0: - raise Error("Invalid HTTP string, did not find a double newline") - - var request_first_line_headers = request_first_line_headers_body[0] - - var request_body = String() - - if len(request_first_line_headers_body) > 1: - request_body = request_first_line_headers_body[1] - - var request_first_line_headers_list = request_first_line_headers.split("\r\n", 1) - - var request_first_line = String() - var request_headers = String() - - if len(request_first_line_headers_list) == 0: - raise Error("Invalid HTTP string, did not find a newline in the first line") - - if len(request_first_line_headers_list) == 1: - request_first_line = request_first_line_headers_list[0] - else: - request_first_line = request_first_line_headers_list[0] - request_headers = request_first_line_headers_list[1] - - return (request_first_line, request_headers, request_body) \ No newline at end of file diff --git a/lightbug_http/io/bytes.mojo b/lightbug_http/io/bytes.mojo index ab97a17a..e5eb0362 100644 --- a/lightbug_http/io/bytes.mojo +++ b/lightbug_http/io/bytes.mojo @@ -1,38 +1,23 @@ from python import PythonObject -from lightbug_http.strings import nChar, rChar +from lightbug_http.strings import nChar, rChar, to_string alias Byte = UInt8 -alias Bytes = List[Byte] -alias BytesView = Span[is_mutable=False, T=Byte, lifetime=ImmutableStaticLifetime] +alias Bytes = List[Byte, True] -fn bytes(s: StringLiteral, pop: Bool = True) -> Bytes: - # This is currently null-terminated, which we don't want in HTTP responses - var buf = String(s)._buffer - if pop: - _ = buf.pop() - return buf -fn bytes(s: String, pop: Bool = True) -> Bytes: - # This is currently null-terminated, which we don't want in HTTP responses - var buf = s._buffer - if pop: - _ = buf.pop() - return buf +@always_inline +fn byte(s: String) -> Byte: + return ord(s) + + +@always_inline +fn bytes(s: String) -> Bytes: + return s.as_bytes() -fn bytes_equal(a: Bytes, b: Bytes) -> Bool: - return String(a) == String(b) -fn index_byte(buf: Bytes, c: Byte) -> Int: - for i in range(len(buf)): - if buf[i] == c: - return i - return -1 +fn bytes_equal(a: Bytes, b: Bytes) -> Bool: + return to_string(a) == to_string(b) -fn last_index_byte(buf: Bytes, c: Byte) -> Int: - for i in range(len(buf)-1, -1, -1): - if buf[i] == c: - return i - return -1 fn compare_case_insensitive(a: Bytes, b: Bytes) -> Bool: if len(a) != len(b): @@ -42,37 +27,34 @@ fn compare_case_insensitive(a: Bytes, b: Bytes) -> Bool: return False return True -fn next_line(b: Bytes) raises -> (Bytes, Bytes): - var n_next = index_byte(b, bytes(nChar, pop=False)[0]) - if n_next < 0: - raise Error("next_line: newline not found") - var n = n_next - if n > 0 and (b[n-1] == bytes(rChar, pop=False)[0]): - n -= 1 - return (b[:n+1], b[n_next+1:]) @value @register_passable("trivial") struct UnsafeString: - var data: Pointer[UInt8] + var data: UnsafePointer[UInt8] var len: Int - fn __init__(str: StringLiteral) -> UnsafeString: + fn __init__(inout self) -> None: + self.data = UnsafePointer[UInt8]() + self.len = 0 + + fn __init__(inout self, str: StringLiteral) -> None: var l = str.__len__() var s = String(str) - var p = Pointer[UInt8].alloc(l) + var p = UnsafePointer[UInt8].alloc(l) for i in range(l): p.store(i, s._buffer[i]) - return UnsafeString(p, l) + self.data = p + self.len = l - fn __init__(str: String) -> UnsafeString: + fn __init__(inout self, str: String) -> None: var l = str.__len__() - var p = Pointer[UInt8].alloc(l) + var p = UnsafePointer[UInt8].alloc(l) for i in range(l): p.store(i, str._buffer[i]) - return UnsafeString(p, l) + self.data = p + self.len = l fn to_string(self) -> String: var s = String(self.data, self.len) return s - diff --git a/external/libc.mojo b/lightbug_http/libc.mojo similarity index 78% rename from external/libc.mojo rename to lightbug_http/libc.mojo index 2ce9a339..7915fe16 100644 --- a/external/libc.mojo +++ b/lightbug_http/libc.mojo @@ -1,6 +1,8 @@ from utils import StaticTuple -from lightbug_http.io.bytes import Bytes from sys.ffi import external_call +from sys.info import sizeof +from memory import memcpy +from lightbug_http.io.bytes import Bytes alias IPPROTO_IPV6 = 41 alias IPV6_V6ONLY = 26 @@ -86,12 +88,14 @@ fn to_char_ptr(s: String) -> UnsafePointer[c_char]: ptr[i] = ord(s[i]) return ptr + fn to_char_ptr(s: Bytes) -> UnsafePointer[c_char]: var ptr = UnsafePointer[c_char]().alloc(len(s)) for i in range(len(s)): ptr[i] = int(s[i]) return ptr + fn c_charptr_to_string(s: UnsafePointer[c_char]) -> String: return String(s.bitcast[UInt8](), strlen(s)) @@ -427,7 +431,10 @@ fn ntohs(netshort: c_ushort) -> c_ushort: fn inet_ntop( - af: c_int, src: UnsafePointer[c_void], dst: UnsafePointer[c_char], size: socklen_t + af: c_int, + src: UnsafePointer[c_void], + dst: UnsafePointer[c_char], + size: socklen_t, ) -> UnsafePointer[c_char]: """Libc POSIX `inet_ntop` function Reference: https://man7.org/linux/man-pages/man3/inet_ntop.3p.html. @@ -452,7 +459,9 @@ fn inet_ntop( ](af, src, dst, size) -fn inet_pton(af: c_int, src: UnsafePointer[c_char], dst: UnsafePointer[c_void]) -> c_int: +fn inet_pton( + af: c_int, src: UnsafePointer[c_char], dst: UnsafePointer[c_void] +) -> c_int: """Libc POSIX `inet_pton` function Reference: https://man7.org/linux/man-pages/man3/inet_ntop.3p.html Fn signature: int inet_pton(int af, const char *restrict src, void *restrict dst). @@ -464,10 +473,10 @@ fn inet_pton(af: c_int, src: UnsafePointer[c_char], dst: UnsafePointer[c_void]) """ return external_call[ "inet_pton", - c_int, # FnName, RetType + c_int, c_int, UnsafePointer[c_char], - UnsafePointer[c_void], # Args + UnsafePointer[c_void], ](af, src, dst) @@ -536,9 +545,49 @@ fn setsockopt( socklen_t, # Args ](socket, level, option_name, option_value, option_len) +fn fcntl(fd: c_int, cmd: c_int, arg: c_int = 0) -> c_int: + """Libc POSIX `fcntl` function + Reference: https + Fn signature: int fcntl(int fd, int cmd, int arg). + + Args: fd: A File Descriptor. + cmd: The command to execute. + arg: The argument for the command. + Returns: The result of the command. + """ + return external_call["fcntl", c_int, c_int, c_int, c_int](fd, cmd, arg) + +fn getsockopt( + socket: c_int, + level: c_int, + option_name: c_int, + option_value: UnsafePointer[c_void], + option_len: UnsafePointer[socklen_t], +) -> c_int: + """Libc POSIX `getsockopt` function + Reference: https://man7.org/linux + + Args: socket: A File Descriptor. + level: The protocol level. + option_name: The option to get. + option_value: A UnsafePointer to the value to get. + option_len: A UnsafePointer to the size of the value. + Returns: 0 on success, -1 on error. + """ + return external_call[ + "getsockopt", + c_int, # FnName, RetType + c_int, + c_int, + c_int, + UnsafePointer[c_void], + UnsafePointer[socklen_t], # Args + ](socket, level, option_name, option_value, option_len) fn getsockname( - socket: c_int, address: UnsafePointer[sockaddr], address_len: UnsafePointer[socklen_t] + socket: c_int, + address: UnsafePointer[sockaddr], + address_len: UnsafePointer[socklen_t], ) -> c_int: """Libc POSIX `getsockname` function Reference: https://man7.org/linux/man-pages/man3/getsockname.3p.html @@ -559,7 +608,9 @@ fn getsockname( fn getpeername( - sockfd: c_int, addr: UnsafePointer[sockaddr], address_len: UnsafePointer[socklen_t] + sockfd: c_int, + addr: UnsafePointer[sockaddr], + address_len: UnsafePointer[socklen_t], ) -> c_int: """Libc POSIX `getpeername` function Reference: https://man7.org/linux/man-pages/man2/getpeername.2.html @@ -579,13 +630,15 @@ fn getpeername( ](sockfd, addr, address_len) -fn bind(socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen_t) -> c_int: +fn bind( + socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen_t +) -> c_int: """Libc POSIX `bind` function Reference: https://man7.org/linux/man-pages/man3/bind.3p.html Fn signature: int bind(int socket, const struct sockaddr *address, socklen_t address_len). """ return external_call[ - "bind", c_int, c_int, UnsafePointer[sockaddr], socklen_t # FnName, RetType # Args + "bind", c_int, c_int, UnsafePointer[sockaddr], socklen_t ](socket, address, address_len) @@ -602,7 +655,9 @@ fn listen(socket: c_int, backlog: c_int) -> c_int: fn accept( - socket: c_int, address: UnsafePointer[sockaddr], address_len: UnsafePointer[socklen_t] + socket: c_int, + address: UnsafePointer[sockaddr], + address_len: UnsafePointer[socklen_t], ) -> c_int: """Libc POSIX `accept` function Reference: https://man7.org/linux/man-pages/man3/accept.3p.html @@ -622,7 +677,9 @@ fn accept( ](socket, address, address_len) -fn connect(socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen_t) -> c_int: +fn connect( + socket: c_int, address: Reference[sockaddr], address_len: socklen_t +) -> c_int: """Libc POSIX `connect` function Reference: https://man7.org/linux/man-pages/man3/connect.3p.html Fn signature: int connect(int socket, const struct sockaddr *address, socklen_t address_len). @@ -632,26 +689,7 @@ fn connect(socket: c_int, address: UnsafePointer[sockaddr], address_len: socklen address_len: The size of the address. Returns: 0 on success, -1 on error. """ - return external_call[ - "connect", c_int, c_int, UnsafePointer[sockaddr], socklen_t # FnName, RetType # Args - ](socket, address, address_len) - - -# fn recv( -# socket: c_int, buffer: UnsafePointer[c_void], length: c_size_t, flags: c_int -# ) -> c_ssize_t: -# """Libc POSIX `recv` function -# Reference: https://man7.org/linux/man-pages/man3/recv.3p.html -# Fn signature: ssize_t recv(int socket, void *buffer, size_t length, int flags). -# """ -# return external_call[ -# "recv", -# c_ssize_t, # FnName, RetType -# c_int, -# UnsafePointer[c_void], -# c_size_t, -# c_int, # Args -# ](socket, buffer, length, flags) + return external_call["connect", c_int](socket, address, address_len) fn recv( @@ -673,6 +711,7 @@ fn recv( c_int, # Args ](socket, buffer, length, flags) + fn send( socket: c_int, buffer: UnsafePointer[c_void], length: c_size_t, flags: c_int ) -> c_ssize_t: @@ -686,14 +725,7 @@ fn send( flags: Flags to control the behaviour of the function. Returns: The number of bytes sent or -1 in case of failure. """ - return external_call[ - "send", - c_ssize_t, # FnName, RetType - c_int, - UnsafePointer[c_void], - c_size_t, - c_int, # Args - ](socket, buffer, length, flags) + return external_call["send", c_ssize_t](socket, buffer, length, flags) fn shutdown(socket: c_int, how: c_int) -> c_int: @@ -705,7 +737,9 @@ fn shutdown(socket: c_int, how: c_int) -> c_int: how: How to shutdown the socket. Returns: 0 on success, -1 on error. """ - return external_call["shutdown", c_int, c_int, c_int]( # FnName, RetType # Args + return external_call[ + "shutdown", c_int, c_int, c_int + ]( # FnName, RetType # Args socket, how ) @@ -776,7 +810,9 @@ fn close(fildes: c_int) -> c_int: return external_call["close", c_int, c_int](fildes) -fn open[*T: AnyType](path: UnsafePointer[c_char], oflag: c_int, *args: *T) -> c_int: +fn open[ + *T: AnyType +](path: UnsafePointer[c_char], oflag: c_int, *args: *T) -> c_int: """Libc POSIX `open` function Reference: https://man7.org/linux/man-pages/man3/open.3p.html Fn signature: int open(const char *path, int oflag, ...). @@ -806,7 +842,6 @@ fn printf[*T: AnyType](format: UnsafePointer[c_char], *args: *T) -> c_int: ](format, args) - fn read(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: """Libc POSIX `read` function Reference: https://man7.org/linux/man-pages/man3/read.3p.html @@ -817,9 +852,9 @@ fn read(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: nbyte: The number of bytes to read. Returns: The number of bytes read or -1 in case of failure. """ - return external_call["read", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t]( - fildes, buf, nbyte - ) + return external_call[ + "read", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t + ](fildes, buf, nbyte) fn write(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: @@ -832,13 +867,80 @@ fn write(fildes: c_int, buf: UnsafePointer[c_void], nbyte: c_size_t) -> c_int: nbyte: The number of bytes to write. Returns: The number of bytes written or -1 in case of failure. """ - return external_call["write", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t]( - fildes, buf, nbyte - ) + return external_call[ + "write", c_ssize_t, c_int, UnsafePointer[c_void], c_size_t + ](fildes, buf, nbyte) -# --- ( Testing Functions ) ---------------------------------------------------- +struct timeval: + var tv_sec: Int64 + var tv_usec: Int64 + fn __init__(inout self, seconds: Int64, microseconds: Int64): + self.tv_sec = seconds + self.tv_usec = microseconds + +@value +struct fd_set: + var fds_bits: StaticTuple[Int64, 16] + + fn __init__(inout self): + self.fds_bits = StaticTuple[Int64, 16]() + for i in range(16): + self.fds_bits[i] = 0 + + fn set(inout self, fd: Int): + var word = fd // 64 + var bit = fd % 64 + self.fds_bits[word] |= (1 << bit) + + fn clear(inout self, fd: Int): + var word = fd // 64 + var bit = fd % 64 + self.fds_bits[word] &= ~(1 << bit) + + fn is_set(self, fd: Int) -> Bool: + var word = fd // 64 + var bit = fd % 64 + var result = (self.fds_bits[word] & (1 << bit)) != 0 + return result + + fn clear_all(inout self): + for i in range(16): + self.fds_bits[i] = 0 + + fn print_bits(self): + for i in range(16): + print("Word", i, ":", bin(self.fds_bits[i])) + + +fn select( + nfds: c_int, + readfds: UnsafePointer[fd_set], + writefds: UnsafePointer[fd_set], + exceptfds: UnsafePointer[fd_set], + timeout: UnsafePointer[timeval], +) -> c_int: + """Libc POSIX `select` function + Reference: https://man7.org/linux + Fn signature: int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout). + + Args: nfds: The highest-numbered file descriptor in any of the three sets, plus 1. + readfds: A UnsafePointer to the set of file descriptors to read from. + writefds: A UnsafePointer to the set of file descriptors to write to. + exceptfds: A UnsafePointer to the set of file descriptors to check for exceptions. + timeout: A UnsafePointer to a timeval struct to set a timeout. + Returns: The number of file descriptors in the sets or -1 in case of failure. + """ + return external_call[ + "select", + c_int, # FnName, RetType + c_int, + UnsafePointer[fd_set], + UnsafePointer[fd_set], + UnsafePointer[fd_set], + UnsafePointer[timeval], # Args + ](nfds, readfds, writefds, exceptfds, timeout) fn __test_getaddrinfo__(): var ip_addr = "127.0.0.1" @@ -851,7 +953,6 @@ fn __test_getaddrinfo__(): hints.ai_family = AF_INET hints.ai_socktype = SOCK_STREAM hints.ai_flags = AI_PASSIVE - # var hints_ptr = var status = getaddrinfo( to_char_ptr(ip_addr), @@ -860,133 +961,8 @@ fn __test_getaddrinfo__(): UnsafePointer.address_of(servinfo), ) var msg_ptr = gai_strerror(c_int(status)) - _ = external_call["printf", c_int, UnsafePointer[c_char], UnsafePointer[c_char]]( - to_char_ptr("gai_strerror: %s"), msg_ptr - ) + _ = external_call[ + "printf", c_int, UnsafePointer[c_char], UnsafePointer[c_char] + ](to_char_ptr("gai_strerror: %s"), msg_ptr) var msg = c_charptr_to_string(msg_ptr) print("getaddrinfo satus: " + msg) - - -# fn __test_socket_client__(): -# var ip_addr = "127.0.0.1" # The server's hostname or IP address -# var port = 8080 # The port used by the server -# var address_family = AF_INET - -# var ip_buf = UnsafePointer[c_void].alloc(4) -# var conv_status = inet_pton(address_family, to_char_ptr(ip_addr), ip_buf) -# var raw_ip = ip_buf.bitcast[c_uint]() - -# print("inet_pton: " + raw_ip.__str__() + " :: status: " + conv_status.__str__()) - -# var bin_port = htons(UInt16(port)) -# print("htons: " + "\n" + bin_port.__str__()) - -# var ai = sockaddr_in(address_family, bin_port, raw_ip, StaticTuple[c_char, 8]()) -# var ai_ptr = UnsafePointer[sockaddr_in].address_of(ai).bitcast[sockaddr]() - -# var sockfd = socket(address_family, SOCK_STREAM, 0) -# if sockfd == -1: -# print("Socket creation error") -# print("sockfd: " + "\n" + sockfd.__str__()) - -# if connect(sockfd, ai_ptr, sizeof[sockaddr_in]()) == -1: -# _ = shutdown(sockfd, SHUT_RDWR) -# print("Connection error") -# return # Ensure to exit if connection fails - -# var msg = to_char_ptr("Hello, world Server") -# var bytes_sent = send(sockfd, msg, strlen(msg), 0) -# if bytes_sent == -1: -# print("Failed to send message") -# else: -# print("Message sent") -# var buf_size = 1024 -# var buf = UnsafePointer[UInt8]().alloc(buf_size) -# var bytes_recv = recv(sockfd, buf, buf_size, 0) -# if bytes_recv == -1: -# print("Failed to receive message") -# else: -# print("Received Message: ") -# print(String(buf.bitcast[UInt8](), bytes_recv)) - -# _ = shutdown(sockfd, SHUT_RDWR) -# var close_status = close(sockfd) -# if close_status == -1: -# print("Failed to close socket") - - -# fn __test_socket_server__() raises: -# var ip_addr = "127.0.0.1" -# var port = 8083 - -# var address_family = AF_INET -# var ip_buf_size = 4 -# if address_family == AF_INET6: -# ip_buf_size = 16 - -# var ip_buf = UnsafePointer[c_void].alloc(ip_buf_size) -# var conv_status = inet_pton(address_family, to_char_ptr(ip_addr), ip_buf) -# var raw_ip = ip_buf.bitcast[c_uint]() -# print("inet_pton: " + raw_ip.__str__() + " :: status: " + conv_status.__str__()) - -# var bin_port = htons(UInt16(port)) -# print("htons: " + "\n" + bin_port.__str__()) - -# var ai = sockaddr_in(address_family, bin_port, raw_ip, StaticTuple[c_char, 8]()) -# var ai_ptr = UnsafePointer[sockaddr_in].address_of(ai).bitcast[sockaddr]() - -# var sockfd = socket(address_family, SOCK_STREAM, 0) -# if sockfd == -1: -# print("Socket creation error") -# print("sockfd: " + "\n" + sockfd.__str__()) - -# var yes: Int = 1 -# if ( -# setsockopt( -# sockfd, -# SOL_SOCKET, -# SO_REUSEADDR, -# UnsafePointer[Int].address_of(yes).bitcast[c_void](), -# sizeof[Int](), -# ) -# == -1 -# ): -# print("set socket options failed") - -# if bind(sockfd, ai_ptr, sizeof[sockaddr_in]()) == -1: -# # close(sockfd) -# _ = shutdown(sockfd, SHUT_RDWR) -# print("Binding socket failed. Wait a few seconds and try again?") - -# if listen(sockfd, c_int(128)) == -1: -# print("Listen failed.\n on sockfd " + sockfd.__str__()) - -# print( -# "server: started at " -# + ip_addr -# + ":" -# + port.__str__() -# + " on sockfd " -# + sockfd.__str__() -# + "Waiting for connections..." -# ) - -# var their_addr_ptr = UnsafePointer[sockaddr].alloc(1) -# var sin_size = socklen_t(sizeof[socklen_t]()) -# var new_sockfd = accept( -# sockfd, their_addr_ptr, UnsafePointer[socklen_t].address_of(sin_size) -# ) -# if new_sockfd == -1: -# print("Accept failed") -# # close(sockfd) -# _ = shutdown(sockfd, SHUT_RDWR) - -# var msg = "Hello, Mojo!" -# if send(new_sockfd, to_char_ptr(msg).bitcast[c_void](), len(msg), 0) == -1: -# print("Failed to send response") -# print("Message sent succesfully") -# _ = shutdown(sockfd, SHUT_RDWR) - -# var close_status = close(new_sockfd) -# if close_status == -1: -# print("Failed to close new_sockfd") diff --git a/lightbug_http/net.mojo b/lightbug_http/net.mojo index f7ab1a26..11631311 100644 --- a/lightbug_http/net.mojo +++ b/lightbug_http/net.mojo @@ -1,8 +1,9 @@ +from sys.info import sizeof from lightbug_http.strings import NetworkType from lightbug_http.io.bytes import Bytes from lightbug_http.io.sync import Duration from lightbug_http.sys.net import SysConnection -from external.libc import ( +from .libc import ( c_void, AF_INET, sockaddr, @@ -11,7 +12,7 @@ from external.libc import ( getsockname, getpeername, ntohs, - inet_ntop + inet_ntop, ) alias default_buffer_size = 4096 @@ -58,7 +59,7 @@ trait Listener(Movable): ... -trait Connection(Movable): +trait Connection(CollectionElement): fn __init__(inout self, laddr: String, raddr: String) raises: ... @@ -119,7 +120,9 @@ struct TCPAddr(Addr): fn string(self) -> String: if self.zone != "": - return join_host_port(self.ip + "%" + self.zone, self.port.__str__()) + return join_host_port( + self.ip + "%" + self.zone, self.port.__str__() + ) return join_host_port(self.ip, self.port.__str__()) diff --git a/lightbug_http/python/__init__.mojo b/lightbug_http/python/__init__.mojo deleted file mode 100644 index 3035ab6c..00000000 --- a/lightbug_http/python/__init__.mojo +++ /dev/null @@ -1,29 +0,0 @@ -from python import Python, PythonObject - - -@value -struct Modules: - var builtins: PythonObject - var socket: PythonObject - - fn __init__(inout self) -> None: - self.builtins = self.__load_builtins() - self.socket = self.__load_socket() - - @staticmethod - fn __load_socket() -> PythonObject: - try: - var socket = Python.import_module("socket") - return socket - except e: - print("Failed to import socket module") - return None - - @staticmethod - fn __load_builtins() -> PythonObject: - try: - var builtins = Python.import_module("builtins") - return builtins - except e: - print("Failed to import builtins module") - return None diff --git a/lightbug_http/python/client.mojo b/lightbug_http/python/client.mojo deleted file mode 100644 index 81ae43db..00000000 --- a/lightbug_http/python/client.mojo +++ /dev/null @@ -1,60 +0,0 @@ -from lightbug_http.client import Client -from lightbug_http.http import HTTPRequest, HTTPResponse -from lightbug_http.python import Modules -from lightbug_http.io.bytes import Bytes, UnsafeString, bytes -from lightbug_http.strings import CharSet - - -struct PythonClient(Client): - var pymodules: Modules - var socket: PythonObject - var name: String - - var host: StringLiteral - var port: Int - - fn __init__(inout self) raises: - self.pymodules = Modules() - self.socket = self.pymodules.socket.socket() - self.host = "127.0.0.1" - self.port = 8888 - self.name = "lightbug_http_client" - - fn __init__(inout self, host: StringLiteral, port: Int) raises: - self.pymodules = Modules() - self.socket = self.pymodules.socket.socket() - self.host = host - self.port = port - self.name = "lightbug_http_client" - - fn do(self, req: HTTPRequest) raises -> HTTPResponse: - var uri = req.uri() - try: - _ = uri.parse() - except e: - print("error parsing uri: " + e.__str__()) - - var host = String(uri.host()) - - if host == "": - raise Error("URI is nil") - var is_tls = False - if uri.is_https(): - is_tls = True - - var host_port = host.split(":") - var host_str = host_port[0] - - var port = atol(host_port[1]) - - _ = self.socket.connect((UnsafeString(host_str.__str__()), port)) - - var data = self.pymodules.builtins.bytes( - String(req.body_raw), CharSet.utf8.value - ) - _ = self.socket.sendall(data) - - var res = self.socket.recv(1024).decode() - _ = self.socket.close() - - return HTTPResponse(bytes(res)) diff --git a/lightbug_http/python/net.mojo b/lightbug_http/python/net.mojo deleted file mode 100644 index 2174c511..00000000 --- a/lightbug_http/python/net.mojo +++ /dev/null @@ -1,145 +0,0 @@ -from lightbug_http.python import Modules -from lightbug_http.io.bytes import Bytes, UnsafeString, bytes -from lightbug_http.io.sync import Duration -from lightbug_http.net import ( - Net, - TCPAddr, - Listener, - ListenConfig, - resolve_internet_addr, - default_buffer_size, -) -from lightbug_http.net import Connection, default_tcp_keep_alive -from lightbug_http.strings import CharSet - - -@value -struct PythonTCPListener: - var __pymodules: PythonObject - var __addr: TCPAddr - var socket: PythonObject - - fn __init__(inout self) raises: - self.__pymodules = None - self.__addr = TCPAddr("localhost", 8080) - self.socket = None - - fn __init__(inout self, addr: TCPAddr) raises: - self.__pymodules = None - self.__addr = addr - self.socket = None - - fn __init__(inout self, pymodules: PythonObject, addr: TCPAddr) raises: - self.__pymodules = pymodules - self.__addr = addr - self.socket = None - - fn __init__( - inout self, pymodules: PythonObject, addr: TCPAddr, socket: PythonObject - ) raises: - self.__pymodules = pymodules - self.__addr = addr - self.socket = socket - - @always_inline - fn accept(self) raises -> PythonConnection: - var conn_addr = self.socket.accept() - return PythonConnection(self.__pymodules, conn_addr) - - fn close(self) raises: - if self.socket == None: - raise Error("socket is None, cannot close") - _ = self.socket.close() - - fn addr(self) -> TCPAddr: - return self.__addr - - -struct PythonListenConfig: - var __pymodules: Modules - var __keep_alive: Duration - - fn __init__(inout self): - self.__keep_alive = default_tcp_keep_alive - self.__pymodules = Modules() - - fn __init__(inout self, keep_alive: Duration): - self.__keep_alive = keep_alive - self.__pymodules = Modules() - - fn listen(inout self, network: String, address: String) raises -> PythonTCPListener: - var addr = resolve_internet_addr(network, address) - var listener = PythonTCPListener( - self.__pymodules.builtins, - addr, - self.__pymodules.socket.socket( - self.__pymodules.socket.AF_INET, - self.__pymodules.socket.SOCK_STREAM, - ), - ) - _ = listener.socket.bind((UnsafeString(addr.ip), addr.port)) - _ = listener.socket.listen() - print("Listening on " + String(addr.ip) + ":" + String(addr.port)) - return listener - - -@value -struct PythonConnection(Connection): - var pymodules: PythonObject - var conn: PythonObject - var raddr: PythonObject - var laddr: PythonObject - - fn __init__(inout self, laddr: String, raddr: String) raises: - self.conn = None - self.raddr = PythonObject(raddr) - self.laddr = PythonObject(laddr) - self.pymodules = Modules().builtins - - fn __init__(inout self, laddr: TCPAddr, raddr: TCPAddr) raises: - self.conn = None - self.raddr = PythonObject(raddr.ip + ":" + raddr.port.__str__()) - self.laddr = PythonObject(laddr.ip + ":" + laddr.port.__str__()) - self.pymodules = Modules().builtins - - fn __init__(inout self, pymodules: PythonObject, py_conn_addr: PythonObject) raises: - self.conn = py_conn_addr[0] - self.raddr = py_conn_addr[1] - self.laddr = "" - self.pymodules = pymodules - - fn read(self, inout buf: Bytes) raises -> Int: - var data = self.conn.recv(default_buffer_size) - buf = bytes( - self.pymodules.bytes.decode(data, CharSet.utf8.value).__str__() - ) - return len(buf) - - fn write(self, buf: Bytes) raises -> Int: - var data = self.pymodules.bytes(String(buf), CharSet.utf8.value) - _ = self.conn.sendall(data) - return len(buf) - - fn close(self) raises: - _ = self.conn.close() - - fn local_addr(inout self) raises -> TCPAddr: - if self.laddr.__str__() == "": - self.laddr = self.conn.getsockname() - return TCPAddr(self.laddr[0].__str__(), self.laddr[1].__int__()) - - fn remote_addr(self) raises -> TCPAddr: - return TCPAddr(self.raddr[0].__str__(), self.raddr[1].__int__()) - - -struct PythonNet: - var __lc: PythonListenConfig - - fn __init__(inout self): - self.__lc = PythonListenConfig(default_tcp_keep_alive) - - fn __init__(inout self, keep_alive: Duration) raises: - self.__lc = PythonListenConfig(keep_alive) - - fn listen(inout self, network: String, addr: String) raises -> PythonTCPListener: - return self.__lc.listen(network, addr) diff --git a/lightbug_http/python/server.mojo b/lightbug_http/python/server.mojo deleted file mode 100644 index eef0ba11..00000000 --- a/lightbug_http/python/server.mojo +++ /dev/null @@ -1,103 +0,0 @@ -from lightbug_http.server import DefaultConcurrency -from lightbug_http.net import Listener -from lightbug_http.http import HTTPRequest, encode, split_http_string -from lightbug_http.uri import URI -from lightbug_http.header import RequestHeader -from lightbug_http.python.net import ( - PythonTCPListener, - PythonNet, - PythonConnection, -) -from lightbug_http.python import Modules -from lightbug_http.service import HTTPService -from lightbug_http.io.sync import Duration -from lightbug_http.io.bytes import Bytes -from lightbug_http.error import ErrorHandler -from lightbug_http.strings import NetworkType - - -struct PythonServer: - var pymodules: Modules - var error_handler: ErrorHandler - - var name: String - var max_concurrent_connections: Int - - var tcp_keep_alive: Bool - - var ln: PythonTCPListener - - fn __init__(inout self) raises: - self.pymodules = Modules() - self.error_handler = ErrorHandler() - self.name = "lightbug_http" - self.max_concurrent_connections = 1000 - self.tcp_keep_alive = False - self.ln = PythonTCPListener() - - fn __init__(inout self, error_handler: ErrorHandler) raises: - self.pymodules = Modules() - self.error_handler = error_handler - - self.name = "lightbug_http" - self.max_concurrent_connections = 1000 - self.tcp_keep_alive = False - - self.ln = PythonTCPListener() - - fn get_concurrency(self) -> Int: - var concurrency = self.max_concurrent_connections - if concurrency <= 0: - concurrency = DefaultConcurrency - return concurrency - - fn listen_and_serve[ - T: HTTPService - ](inout self, address: String, handler: T) raises -> None: - var __net = PythonNet() - var listener = __net.listen(NetworkType.tcp4.value, address) - self.serve(listener, handler) - - fn serve[ - T: HTTPService - ](inout self, ln: PythonTCPListener, handler: T) raises -> None: - self.ln = ln - - while True: - var conn = self.ln.accept() - var buf = Bytes() - var read_len = conn.read(buf) - if read_len == 0: - conn.close() - break - - var request_first_line: String - var request_headers: String - var request_body: String - - request_first_line, request_headers, request_body = split_http_string(buf) - - var uri = URI(request_first_line) - try: - uri.parse() - except: - conn.close() - raise Error("Failed to parse request line") - - var header = RequestHeader(request_headers.as_bytes()) - try: - header.parse_raw(request_first_line) - except: - conn.close() - raise Error("Failed to parse request header") - - var res = handler.func( - HTTPRequest( - uri, - buf, - header, - ) - ) - var res_encoded = encode(res) - _ = conn.write(res_encoded.as_bytes_slice()) - conn.close() diff --git a/lightbug_http/server.mojo b/lightbug_http/server.mojo index 2b5659d8..8f50c097 100644 --- a/lightbug_http/server.mojo +++ b/lightbug_http/server.mojo @@ -7,14 +7,19 @@ alias DefaultConcurrency: Int = 256 * 1024 trait ServerTrait: fn __init__( - inout self, addr: String, service: HTTPService, error_handler: ErrorHandler + inout self, + addr: String, + service: HTTPService, + error_handler: ErrorHandler, ): ... fn get_concurrency(self) -> Int: ... - fn listen_and_serve(self, address: String, handler: HTTPService) raises -> None: + fn listen_and_serve( + self, address: String, handler: HTTPService + ) raises -> None: ... fn serve(self, ln: Listener, handler: HTTPService) raises -> None: diff --git a/lightbug_http/service.mojo b/lightbug_http/service.mojo index 375cd2d3..eff6ff47 100644 --- a/lightbug_http/service.mojo +++ b/lightbug_http/service.mojo @@ -1,16 +1,54 @@ -from lightbug_http.http import HTTPRequest, HTTPResponse, OK, NotFound +from lightbug_http.http import HTTPRequest, HTTPResponse from lightbug_http.io.bytes import Bytes, bytes +from lightbug_http.sys.net import SysConnection +from lightbug_http.strings import to_string +from lightbug_http.header import HeaderKey + trait HTTPService: fn func(self, req: HTTPRequest) raises -> HTTPResponse: ... +trait WebSocketService(Copyable): + fn on_message(inout self, conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + ... + +trait UpgradeLoop(CollectionElement): + fn process_data(inout self, owned conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + ... + + fn handle_frame(inout self, owned conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + ... + + fn can_upgrade(self) -> Bool: + ... + +@value +struct NoUpgrade(UpgradeLoop): + fn process_data(inout self, owned conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + ... + + fn handle_frame(inout self, owned conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + ... + + fn can_upgrade(self) -> Bool: + return False @value struct Printer(HTTPService): fn func(self, req: HTTPRequest) raises -> HTTPResponse: + var uri = req.uri + print("Request URI: ", to_string(uri.request_uri)) + + var header = req.headers + print("Request protocol: ", req.protocol) + print("Request method: ", req.method) + print( + "Request Content-Type: ", to_string(header[HeaderKey.CONTENT_TYPE]) + ) + var body = req.body_raw - print(String(body)) + print("Request Body: ", to_string(body)) return OK(body) @@ -18,37 +56,37 @@ struct Printer(HTTPService): @value struct Welcome(HTTPService): fn func(self, req: HTTPRequest) raises -> HTTPResponse: - var uri = req.uri() + var uri = req.uri - if uri.path() == "/": + if uri.path == "/": var html: Bytes with open("static/lightbug_welcome.html", "r") as f: html = f.read_bytes() return OK(html, "text/html; charset=utf-8") - - if uri.path() == "/logo.png": + + if uri.path == "/logo.png": var image: Bytes with open("static/logo.png", "r") as f: image = f.read_bytes() return OK(image, "image/png") - - return NotFound(uri.path()) + + return NotFound(uri.path) @value struct ExampleRouter(HTTPService): fn func(self, req: HTTPRequest) raises -> HTTPResponse: var body = req.body_raw - var uri = req.uri() + var uri = req.uri - if uri.path() == "/": + if uri.path == "/": print("I'm on the index path!") - if uri.path() == "/first": + if uri.path == "/first": print("I'm on /first!") - elif uri.path() == "/second": + elif uri.path == "/second": print("I'm on /second!") - elif uri.path() == "/echo": - print(String(body)) + elif uri.path == "/echo": + print(to_string(body)) return OK(body) @@ -56,12 +94,11 @@ struct ExampleRouter(HTTPService): @value struct TechEmpowerRouter(HTTPService): fn func(self, req: HTTPRequest) raises -> HTTPResponse: - # var body = req.body_raw - var uri = req.uri() + var uri = req.uri - if uri.path() == "/plaintext": + if uri.path == "/plaintext": return OK("Hello, World!", "text/plain") - elif uri.path() == "/json": + elif uri.path == "/json": return OK('{"message": "Hello, World!"}', "application/json") - return OK("Hello world!") # text/plain is the default + return OK("Hello world!") # text/plain is the default diff --git a/lightbug_http/strings.mojo b/lightbug_http/strings.mojo index 372537e1..d082cc7c 100644 --- a/lightbug_http/strings.mojo +++ b/lightbug_http/strings.mojo @@ -1,4 +1,6 @@ +from utils import Span from lightbug_http.io.bytes import Bytes +from lightbug_http.io.bytes import Bytes, bytes, byte alias strSlash = "/" alias strHttp = "http" @@ -12,11 +14,22 @@ alias strMethodGet = "GET" alias rChar = "\r" alias nChar = "\n" +alias lineBreak = rChar + nChar alias colonChar = ":" alias empty_string = "" alias whitespace = " " +alias whitespace_byte = ord(whitespace) alias tab = "\t" +alias tab_byte = ord(tab) + + +struct BytesConstant: + alias whitespace = byte(whitespace) + alias colon = byte(colonChar) + alias rChar = byte(rChar) + alias nChar = byte(nChar) + @value struct NetworkType: @@ -34,6 +47,7 @@ struct NetworkType: alias ip6 = NetworkType("ip6") alias unix = NetworkType("unix") + @value struct ConnType: var value: String @@ -42,6 +56,7 @@ struct ConnType: alias http = ConnType("http") alias websocket = ConnType("websocket") + @value struct RequestMethod: var value: String @@ -54,12 +69,14 @@ struct RequestMethod: alias patch = RequestMethod("PATCH") alias options = RequestMethod("OPTIONS") + @value struct CharSet: var value: String alias utf8 = CharSet("utf-8") + @value struct MediaType: var value: String @@ -68,9 +85,40 @@ struct MediaType: alias plain = MediaType("text/plain") alias json = MediaType("application/json") + @value struct Message: var type: String alias empty = Message("") alias http_start = Message("http.response.start") + + +fn to_string[T: Formattable](value: T) -> String: + var s = String() + var formatter = s._unsafe_to_formatter() + value.format_to(formatter) + return s + + +fn to_string(b: Span[UInt8]) -> String: + """Creates a String from a copy of the provided Span of bytes. + + Args: + b: The Span of bytes to convert to a String. + """ + var bytes = List[UInt8, True](b) + bytes.append(0) + return String(bytes^) + + +fn to_string(owned bytes: List[UInt8, True]) -> String: + """Creates a String from the provided List of bytes. + If you do not transfer ownership of the List, the List will be copied. + + Args: + bytes: The List of bytes to convert to a String. + """ + if bytes[-1] != 0: + bytes.append(0) + return String(bytes^) diff --git a/lightbug_http/sys/client.mojo b/lightbug_http/sys/client.mojo index 0e981e1f..9d30a931 100644 --- a/lightbug_http/sys/client.mojo +++ b/lightbug_http/sys/client.mojo @@ -1,6 +1,4 @@ -from external.gojo.bufio import Reader, Scanner, scan_words, scan_bytes -from external.gojo.bytes import buffer -from external.libc import ( +from ..libc import ( c_int, AF_INET, SOCK_STREAM, @@ -10,12 +8,14 @@ from external.libc import ( recv, close, ) +from lightbug_http.strings import to_string from lightbug_http.client import Client from lightbug_http.net import default_buffer_size -from lightbug_http.http import HTTPRequest, HTTPResponse, encode, split_http_string -from lightbug_http.header import ResponseHeader +from lightbug_http.http import HTTPRequest, HTTPResponse, encode +from lightbug_http.header import Headers from lightbug_http.sys.net import create_connection from lightbug_http.io.bytes import Bytes +from lightbug_http.utils import ByteReader struct MojoClient(Client): @@ -36,7 +36,7 @@ struct MojoClient(Client): self.port = port self.name = "lightbug_http_client" - fn do(self, req: HTTPRequest) raises -> HTTPResponse: + 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. @@ -64,8 +64,8 @@ struct MojoClient(Client): Error : If there is a failure in sending or receiving the message. """ - var uri = req.uri() - var host = String(uri.host()) + var uri = req.uri + var host = uri.host if host == "": raise Error("URI is nil") @@ -77,7 +77,7 @@ struct MojoClient(Client): var host_str: String var port: Int - if host.__contains__(":"): + if ":" in host: var host_port = host.split(":") host_str = host_port[0] port = atol(host_port[1]) @@ -87,53 +87,21 @@ struct MojoClient(Client): port = 443 else: port = 80 - var conn = create_connection(self.fd, host_str, port) - - var req_encoded = encode(req) - - var bytes_sent = conn.write(req_encoded) + 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() - var buf = buffer.new_buffer(new_buf^) - var reader = Reader(buf^) - - var error = Error() - - # # Ugly hack for now in case the default buffer is too large and we read additional responses from the server - # var newline_in_body = response_body.find("\r\n") - # if newline_in_body != -1: - # response_body = response_body[:newline_in_body] - - var header = ResponseHeader() - var first_line_and_headers_len = 0 try: - first_line_and_headers_len = header.parse_raw(reader) + return HTTPResponse.from_bytes(new_buf^) except e: conn.close() - error = Error("Failed to parse response headers: " + e.__str__()) - - var response = HTTPResponse(header, Bytes()) + raise e - try: - response.read_body(reader, first_line_and_headers_len,) - except e: - error = Error("Failed to read request body: " + e.__str__()) - # var total_recv = bytes_recv - # while header.content_length() > total_recv: - # if header.content_length() != 0 and header.content_length() != -2: - # var remaining_body = Bytes() - # var read_len = conn.read(remaining_body) - # response_body += remaining_body - # total_recv += read_len - - conn.close() - - return response + return HTTPResponse(Bytes()) diff --git a/lightbug_http/sys/net.mojo b/lightbug_http/sys/net.mojo index e7fd25ab..77478ff4 100644 --- a/lightbug_http/sys/net.mojo +++ b/lightbug_http/sys/net.mojo @@ -1,23 +1,26 @@ from utils import StaticTuple +from sys.info import sizeof +from sys.ffi import external_call from lightbug_http.net import ( Listener, ListenConfig, Connection, TCPAddr, Net, - resolve_internet_addr, default_buffer_size, default_tcp_keep_alive, + resolve_internet_addr, get_peer_name, ) -from lightbug_http.strings import NetworkType +from lightbug_http.strings import NetworkType, to_string from lightbug_http.io.bytes import Bytes, bytes from lightbug_http.io.sync import Duration -from external.libc import ( +from ..libc import ( c_void, c_int, c_uint, c_char, + c_ssize_t, in_addr, sockaddr, sockaddr_in, @@ -28,12 +31,15 @@ from external.libc import ( SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR, + SO_ERROR, SHUT_RDWR, htons, inet_pton, to_char_ptr, socket, connect, + getsockopt, + fcntl, setsockopt, listen, accept, @@ -46,6 +52,7 @@ from external.libc import ( from sys.info import os_is_macos from time import sleep + trait AnAddrInfo: fn get_ip_address(self, host: String) raises -> in_addr: """ @@ -102,13 +109,22 @@ struct SysListener: self.fd = fd fn accept(self) raises -> SysConnection: - var their_addr_ptr = UnsafePointer[sockaddr].alloc(1) + var their_addr = sockaddr(0, StaticTuple[c_char, 14]()) + var their_addr_ptr = Reference[sockaddr](their_addr) var sin_size = socklen_t(sizeof[socklen_t]()) - var new_sockfd = accept( - self.fd, their_addr_ptr, UnsafePointer[socklen_t].address_of(sin_size) + var sin_size_ptr = Reference[socklen_t](sin_size) + var new_sockfd = external_call["accept", c_int]( + self.fd, their_addr_ptr, sin_size_ptr ) + + # var new_sockfd = accept( + # self.fd, their_addr_ptr, UnsafePointer[socklen_t].address_of(sin_size) + # ) if new_sockfd == -1: - print("Failed to accept connection, system accept() returned an error.") + print( + "Failed to accept connection, system accept() returned an" + " error." + ) var peer = get_peer_name(new_sockfd) return SysConnection( @@ -134,28 +150,21 @@ struct SysListenConfig(ListenConfig): fn __init__(inout self, keep_alive: Duration) raises: self.__keep_alive = keep_alive - fn listen(inout self, network: String, address: String) raises -> SysListener: + fn listen( + inout self, network: String, address: String + ) raises -> SysListener: var addr = resolve_internet_addr(network, address) var address_family = AF_INET var ip_buf_size = 4 if address_family == AF_INET6: ip_buf_size = 16 - var ip_buf = UnsafePointer[c_void].alloc(ip_buf_size) - var conv_status = inet_pton(address_family, to_char_ptr(addr.ip), ip_buf) - var raw_ip = ip_buf.bitcast[c_uint]()[] - - var bin_port = htons(UInt16(addr.port)) - - var ai = sockaddr_in(address_family, bin_port, raw_ip, StaticTuple[c_char, 8]()) - var ai_ptr = UnsafePointer[sockaddr_in].address_of(ai).bitcast[sockaddr]() - var sockfd = socket(address_family, SOCK_STREAM, 0) if sockfd == -1: print("Socket creation error") var yes: Int = 1 - _ = setsockopt( + var setsockopt_result = setsockopt( sockfd, SOL_SOCKET, SO_REUSEADDR, @@ -165,13 +174,32 @@ struct SysListenConfig(ListenConfig): var bind_success = False var bind_fail_logged = False + + var ip_buf = UnsafePointer[c_void].alloc(ip_buf_size) + var conv_status = inet_pton( + address_family, to_char_ptr(addr.ip), ip_buf + ) + var raw_ip = ip_buf.bitcast[c_uint]()[] + var bin_port = htons(UInt16(addr.port)) + + var ai = sockaddr_in( + address_family, bin_port, raw_ip, StaticTuple[c_char, 8]() + ) + var ai_ptr = Reference[sockaddr_in](ai) + while not bind_success: - var bind = bind(sockfd, ai_ptr, sizeof[sockaddr_in]()) + # var bind = bind(sockfd, ai_ptr, sizeof[sockaddr_in]()) + var bind = external_call["bind", c_int]( + sockfd, ai_ptr, sizeof[sockaddr_in]() + ) if bind == 0: bind_success = True else: if not bind_fail_logged: - print("Bind attempt failed. The address might be in use or the socket might not be available.") + print( + "Bind attempt failed. The address might be in use or" + " the socket might not be available." + ) print("Retrying. Might take 10-15 seconds.") bind_fail_logged = True print(".", end="", flush=True) @@ -200,24 +228,39 @@ struct SysConnection(Connection): var fd: c_int var raddr: TCPAddr var laddr: TCPAddr + var __write_buffer: Bytes fn __init__(inout self, laddr: String, raddr: String) raises: self.raddr = resolve_internet_addr(NetworkType.tcp4.value, raddr) self.laddr = resolve_internet_addr(NetworkType.tcp4.value, laddr) self.fd = socket(AF_INET, SOCK_STREAM, 0) + self.__write_buffer = Bytes() fn __init__(inout self, laddr: TCPAddr, raddr: TCPAddr) raises: self.raddr = raddr self.laddr = laddr self.fd = socket(AF_INET, SOCK_STREAM, 0) + self.__write_buffer = Bytes() fn __init__(inout self, laddr: TCPAddr, raddr: TCPAddr, fd: c_int) raises: self.raddr = raddr self.laddr = laddr self.fd = fd + self.__write_buffer = Bytes() + + fn write_buffer(self) -> Bytes: + return self.__write_buffer + + fn set_write_buffer(inout self, buf: Bytes): + self.__write_buffer = buf fn read(self, inout buf: Bytes) raises -> Int: - var bytes_recv = recv(self.fd, buf.unsafe_ptr().offset(buf.size), buf.capacity - buf.size, 0) + var bytes_recv = recv( + self.fd, + buf.unsafe_ptr().offset(buf.size), + buf.capacity - buf.size, + 0, + ) if bytes_recv == -1: return 0 buf.size += bytes_recv @@ -227,21 +270,47 @@ struct SysConnection(Connection): return bytes_recv return bytes_recv - fn write(self, msg: String) raises -> Int: - if send(self.fd, to_char_ptr(msg).bitcast[c_void](), len(msg), 0) == -1: + fn write(self, owned msg: String) raises -> Int: + var bytes_sent = send(self.fd, msg.unsafe_ptr(), len(msg), 0) + if bytes_sent == -1: print("Failed to send response") - return len(msg) - + return bytes_sent + fn write(self, buf: Bytes) raises -> Int: - if send(self.fd, to_char_ptr(buf).bitcast[c_void](), len(buf), 0) == -1: + var content = to_string(buf) + var bytes_sent = send(self.fd, content.unsafe_ptr(), len(content), 0) + if bytes_sent == -1: print("Failed to send response") - return len(buf) + _ = content + return bytes_sent fn close(self) raises: _ = shutdown(self.fd, SHUT_RDWR) var close_status = close(self.fd) if close_status == -1: print("Failed to close new_sockfd") + + fn is_closed(self) -> Bool: + var error = 0 + var len = socklen_t(sizeof[Int]()) + var result = external_call[ + "getsockopt", + c_int, + ](self.fd, SOL_SOCKET, SO_ERROR, UnsafePointer.address_of(error), UnsafePointer.address_of(len)) + return result == -1 or error != 0 + + fn set_non_blocking(self, non_blocking: Bool) raises: + var flags = fcntl(self.fd, 3) + if flags == -1: + print("Failed to get flags") + return + if non_blocking: + flags |= 2048 + else: + flags &= ~2048 + var result = fcntl(self.fd, 4, flags) + if result == -1: + print("Failed to set flags") fn local_addr(inout self) raises -> TCPAddr: return self.laddr @@ -280,10 +349,15 @@ struct addrinfo_macos(AnAddrInfo): var ai_addr: UnsafePointer[sockaddr] var ai_next: UnsafePointer[c_void] - fn __init__() -> Self: - return Self( - 0, 0, 0, 0, 0, UnsafePointer[c_char](), UnsafePointer[sockaddr](), UnsafePointer[c_void]() - ) + fn __init__(inout self): + self.ai_flags = 0 + self.ai_family = 0 + self.ai_socktype = 0 + self.ai_protocol = 0 + self.ai_addrlen = 0 + self.ai_canonname = UnsafePointer[c_char]() + self.ai_addr = UnsafePointer[sockaddr]() + self.ai_next = UnsafePointer[c_void]() fn get_ip_address(self, host: String) raises -> in_addr: """ @@ -294,25 +368,24 @@ struct addrinfo_macos(AnAddrInfo): host: String - The host to get the IP from. Returns: - UInt32 - The IP address. + in_addr - The IP address. """ var host_ptr = to_char_ptr(host) - var servinfo = UnsafePointer[Self]().alloc(1) - initialize_pointee_move(servinfo, Self()) + var servinfo = Reference(Self()) + var servname = UnsafePointer[Int8]() var hints = Self() hints.ai_family = AF_INET hints.ai_socktype = SOCK_STREAM hints.ai_flags = AI_PASSIVE - var error = getaddrinfo[Self]( - host_ptr, - UnsafePointer[UInt8](), - UnsafePointer.address_of(hints), - UnsafePointer.address_of(servinfo), - ) + var error = external_call[ + "getaddrinfo", + Int32, + ](host_ptr, servname, Reference(hints), Reference(servinfo)) + if error != 0: - print("getaddrinfo failed") + print("getaddrinfo failed with error code: " + error.__str__()) raise Error("Failed to get IP address. getaddrinfo failed.") var addrinfo = servinfo[] @@ -321,8 +394,8 @@ struct addrinfo_macos(AnAddrInfo): if not ai_addr: print("ai_addr is null") raise Error( - "Failed to get IP address. getaddrinfo was called successfully, but" - " ai_addr is null." + "Failed to get IP address. getaddrinfo was called successfully," + " but ai_addr is null." ) var addr_in = ai_addr.bitcast[sockaddr_in]()[] @@ -346,10 +419,15 @@ struct addrinfo_unix(AnAddrInfo): var ai_canonname: UnsafePointer[c_char] var ai_next: UnsafePointer[c_void] - fn __init__() -> Self: - return Self( - 0, 0, 0, 0, 0, UnsafePointer[sockaddr](), UnsafePointer[c_char](), UnsafePointer[c_void]() - ) + fn __init__(inout self): + self.ai_flags = 0 + self.ai_family = 0 + self.ai_socktype = 0 + self.ai_protocol = 0 + self.ai_addrlen = 0 + self.ai_addr = UnsafePointer[sockaddr]() + self.ai_canonname = UnsafePointer[c_char]() + self.ai_next = UnsafePointer[c_void]() fn get_ip_address(self, host: String) raises -> in_addr: """ @@ -362,9 +440,9 @@ struct addrinfo_unix(AnAddrInfo): Returns: UInt32 - The IP address. """ - var host_ptr = to_char_ptr(String(host)) + var host_ptr = to_char_ptr(host) var servinfo = UnsafePointer[Self]().alloc(1) - initialize_pointee_move(servinfo, Self()) + servinfo.init_pointee_move(Self()) var hints = Self() hints.ai_family = AF_INET @@ -387,8 +465,8 @@ struct addrinfo_unix(AnAddrInfo): if not ai_addr: print("ai_addr is null") raise Error( - "Failed to get IP address. getaddrinfo was called successfully, but" - " ai_addr is null." + "Failed to get IP address. getaddrinfo was called successfully," + " but ai_addr is null." ) var addr_in = ai_addr.bitcast[sockaddr_in]()[] @@ -396,7 +474,9 @@ struct addrinfo_unix(AnAddrInfo): return addr_in.sin_addr -fn create_connection(sock: c_int, host: String, port: UInt16) raises -> SysConnection: +fn create_connection( + sock: c_int, host: String, port: UInt16 +) raises -> SysConnection: """ Connect to a server using a socket. @@ -418,9 +498,12 @@ fn create_connection(sock: c_int, host: String, port: UInt16) raises -> SysConne var addr: sockaddr_in = sockaddr_in( AF_INET, htons(port), ip, StaticTuple[c_char, 8](0, 0, 0, 0, 0, 0, 0, 0) ) - var addr_ptr = UnsafePointer[sockaddr_in].address_of(addr).bitcast[sockaddr]() + var addr_ptr = Reference[sockaddr_in](addr) - if connect(sock, addr_ptr, sizeof[sockaddr_in]()) == -1: + if ( + external_call["connect", c_int](sock, addr_ptr, sizeof[sockaddr_in]()) + == -1 + ): _ = shutdown(sock, SHUT_RDWR) raise Error("Failed to connect to server") diff --git a/lightbug_http/sys/server.mojo b/lightbug_http/sys/server.mojo index 8ba64f69..299ea153 100644 --- a/lightbug_http/sys/server.mojo +++ b/lightbug_http/sys/server.mojo @@ -1,26 +1,29 @@ -from external.gojo.bufio import Reader, Scanner, scan_words, scan_bytes -from external.gojo.bytes import buffer +from collections import Optional +from sys.intrinsics import _type_is_eq from lightbug_http.server import DefaultConcurrency from lightbug_http.net import Listener, default_buffer_size -from lightbug_http.http import HTTPRequest, encode, split_http_string +from lightbug_http.http import HTTPRequest, encode from lightbug_http.uri import URI -from lightbug_http.header import RequestHeader +from lightbug_http.header import Headers from lightbug_http.sys.net import SysListener, SysConnection, SysNet -from lightbug_http.service import HTTPService +from lightbug_http.service import HTTPService, UpgradeLoop, NoUpgrade from lightbug_http.io.sync import Duration from lightbug_http.io.bytes import Bytes, bytes from lightbug_http.error import ErrorHandler from lightbug_http.strings import NetworkType +from lightbug_http.utils import ByteReader +from lightbug_http.libc import fd_set, timeval, select alias default_max_request_body_size = 4 * 1024 * 1024 # 4MB + @value -struct SysServer: +struct SysServer[T: UpgradeLoop = NoUpgrade]: # TODO: conditional conformance on main struct , then a default for upgrade e.g. NoUpgrade """ A Mojo-based server that accept incoming requests and delivers HTTP services. """ - var error_handler: ErrorHandler + var upgrade_loop: Optional[T] var name: String var __address: String @@ -32,8 +35,13 @@ struct SysServer: var ln: SysListener + var connections: List[SysConnection] + var read_fds: fd_set + var write_fds: fd_set + fn __init__(inout self) raises: self.error_handler = ErrorHandler() + self.upgrade_loop = None self.name = "lightbug_http" self.__address = "127.0.0.1" self.max_concurrent_connections = 1000 @@ -41,9 +49,13 @@ struct SysServer: self.__max_request_body_size = default_max_request_body_size self.tcp_keep_alive = False self.ln = SysListener() - + self.connections = List[SysConnection]() + self.read_fds = fd_set() + self.write_fds = fd_set() + fn __init__(inout self, tcp_keep_alive: Bool) raises: self.error_handler = ErrorHandler() + self.upgrade_loop = None self.name = "lightbug_http" self.__address = "127.0.0.1" self.max_concurrent_connections = 1000 @@ -51,9 +63,13 @@ struct SysServer: self.__max_request_body_size = default_max_request_body_size self.tcp_keep_alive = tcp_keep_alive self.ln = SysListener() - + self.connections = List[SysConnection]() + self.read_fds = fd_set() + self.write_fds = fd_set() + fn __init__(inout self, own_address: String) raises: self.error_handler = ErrorHandler() + self.upgrade_loop = None self.name = "lightbug_http" self.__address = own_address self.max_concurrent_connections = 1000 @@ -61,9 +77,13 @@ struct SysServer: self.__max_request_body_size = default_max_request_body_size self.tcp_keep_alive = False self.ln = SysListener() + self.connections = List[SysConnection]() + self.read_fds = fd_set() + self.write_fds = fd_set() fn __init__(inout self, error_handler: ErrorHandler) raises: self.error_handler = error_handler + self.upgrade_loop = None self.name = "lightbug_http" self.__address = "127.0.0.1" self.max_concurrent_connections = 1000 @@ -71,9 +91,13 @@ struct SysServer: self.__max_request_body_size = default_max_request_body_size self.tcp_keep_alive = False self.ln = SysListener() - + self.connections = List[SysConnection]() + self.read_fds = fd_set() + self.write_fds = fd_set() + fn __init__(inout self, max_request_body_size: Int) raises: self.error_handler = ErrorHandler() + self.upgrade_loop = None self.name = "lightbug_http" self.__address = "127.0.0.1" self.max_concurrent_connections = 1000 @@ -81,9 +105,15 @@ struct SysServer: self.__max_request_body_size = max_request_body_size self.tcp_keep_alive = False self.ln = SysListener() - - fn __init__(inout self, max_request_body_size: Int, tcp_keep_alive: Bool) raises: + self.connections = List[SysConnection]() + self.read_fds = fd_set() + self.write_fds = fd_set() + + fn __init__( + inout self, max_request_body_size: Int, tcp_keep_alive: Bool + ) raises: self.error_handler = ErrorHandler() + self.upgrade_loop = None self.name = "lightbug_http" self.__address = "127.0.0.1" self.max_concurrent_connections = 1000 @@ -91,17 +121,34 @@ struct SysServer: self.__max_request_body_size = max_request_body_size self.tcp_keep_alive = tcp_keep_alive self.ln = SysListener() + self.connections = List[SysConnection]() + self.read_fds = fd_set() + self.write_fds = fd_set() + + fn __init__(inout self, upgrade: T) raises: + self.error_handler = ErrorHandler() + self.upgrade_loop = upgrade + self.name = "lightbug_http" + self.__address = "127.0.0.1" + self.max_concurrent_connections = 1000 + self.max_requests_per_connection = 0 + self.__max_request_body_size = default_max_request_body_size + self.tcp_keep_alive = False + self.ln = SysListener() + self.connections = List[SysConnection]() + self.read_fds = fd_set() + self.write_fds = fd_set() fn address(self) -> String: return self.__address - + fn set_address(inout self, own_address: String) -> Self: self.__address = own_address return self fn max_request_body_size(self) -> Int: return self.__max_request_body_size - + fn set_max_request_body_size(inout self, size: Int) -> Self: self.__max_request_body_size = size return self @@ -118,10 +165,16 @@ struct SysServer: if concurrency <= 0: concurrency = DefaultConcurrency return concurrency + + fn can_upgrade(self) -> Bool: + @parameter + if _type_is_eq[T, NoUpgrade](): + return False + return True fn listen_and_serve[ T: HTTPService - ](inout self, address: String, handler: T) raises -> None: + ](inout self, address: String, handler: T) raises -> None: # TODO: conditional conformance on main struct , then a default for handler e.g. WebsocketHandshake """ Listen for incoming connections and serve HTTP requests. @@ -134,7 +187,9 @@ struct SysServer: _ = self.set_address(address) self.serve(listener, handler) - fn serve[T: HTTPService](inout self, ln: SysListener, handler: T) raises -> None: + fn serve[ + T: HTTPService + ](inout self, ln: SysListener, handler: T) raises -> None: """ Serve HTTP requests. @@ -146,88 +201,98 @@ struct SysServer: If there is an error while serving requests. """ self.ln = ln + self.connections = List[SysConnection]() while True: - var conn = self.ln.accept() - self.serve_connection(conn, handler) - - fn serve_connection[T: HTTPService](inout self, conn: SysConnection, handler: T) raises -> None: - """ - Serve a single connection. + _ = self.read_fds.clear_all() + _ = self.write_fds.clear_all() - Args: - conn : SysConnection - A connection object that represents a client connection. - handler : HTTPService - An object that handles incoming HTTP requests. + self.read_fds.set(int(self.ln.fd)) - Raises: - If there is an error while serving the connection. - """ - var b = Bytes(capacity=default_buffer_size) - var bytes_recv = conn.read(b) - if bytes_recv == 0: - conn.close() - return + var max_fd = self.ln.fd + for i in range(len(self.connections)): + var conn = self.connections[i] + self.read_fds.set(int(conn.fd)) + self.write_fds.set(int(conn.fd)) + + if conn.fd > max_fd: + max_fd = conn.fd + + var timeout = timeval(0, 10000) - var buf = buffer.new_buffer(b^) - var reader = Reader(buf^) + var select_result = select( + max_fd + 1, + UnsafePointer.address_of(self.read_fds), + UnsafePointer.address_of(self.write_fds), + UnsafePointer[fd_set](), + UnsafePointer.address_of(timeout) + ) + if select_result == -1: + print("Select error") + return + + if self.read_fds.is_set(int(self.ln.fd)): + var conn = self.ln.accept() + try: + _ = conn.set_non_blocking(True) + except e: + print("Error setting connnection to non-blocking mode: ", e) + conn.close() + continue + self.connections.append(conn) + if conn.fd > max_fd: + max_fd = conn.fd + self.read_fds.set(int(conn.fd)) + + var i = 0 + while i < len(self.connections): + var conn = self.connections[i] + if self.read_fds.is_set(int(conn.fd)): + _ = self.handle_read(conn, handler) + if self.write_fds.is_set(int(conn.fd)): + _ = self.handle_write(conn) + + if conn.is_closed(): + _ = self.connections.pop(i) + else: + i += 1 - var error = Error() - + fn handle_read[T: HTTPService](inout self, inout conn: SysConnection, handler: T) raises -> None: var max_request_body_size = self.max_request_body_size() if max_request_body_size <= 0: max_request_body_size = default_max_request_body_size + + var b = Bytes(capacity=default_buffer_size) + var bytes_recv = conn.read(b) - var req_number = 0 + if bytes_recv == 0: + conn.close() + return + + var request = HTTPRequest.from_bytes(self.address(), max_request_body_size, b^) + var res = handler.func(request) + + var can_upgrade = self.can_upgrade() - while True: - req_number += 1 + if not self.tcp_keep_alive and not can_upgrade: + _ = res.set_connection_close() - if req_number > 1: - var b = Bytes(capacity=default_buffer_size) - var bytes_recv = conn.read(b) - if bytes_recv == 0: - conn.close() - break - buf = buffer.new_buffer(b^) - reader = Reader(buf^) - - var header = RequestHeader() - var first_line_and_headers_len = 0 - try: - first_line_and_headers_len = header.parse_raw(reader) - except e: - error = Error("Failed to parse request headers: " + e.__str__()) - - var uri = URI(self.address() + String(header.request_uri())) - try: - uri.parse() - except e: - error = Error("Failed to parse request line:" + e.__str__()) - - if header.content_length() > 0: - if max_request_body_size > 0 and header.content_length() > max_request_body_size: - error = Error("Request body too large") - - var request = HTTPRequest( - uri, - Bytes(), - header, - ) - - try: - request.read_body(reader, header.content_length(), first_line_and_headers_len, max_request_body_size) - except e: - error = Error("Failed to read request body: " + e.__str__()) - - var res = handler.func(request) - - if not self.tcp_keep_alive: - _ = res.set_connection_close() - - var res_encoded = encode(res) + conn.set_write_buffer(encode(res^)) - _ = conn.write(res_encoded) + # TODO: does this make sense? + self.write_fds.set(int(conn.fd)) + + if can_upgrade: + self.upgrade_loop.value().process_data(conn, False, Bytes()) # TODO: is_binary is now hardcoded to = False, need to get it from the frame - if not self.tcp_keep_alive: - conn.close() - return + # if not self.tcp_keep_alive: + # conn.close() + + fn handle_write(inout self, inout conn: SysConnection) raises -> None: + var write_buffer = conn.write_buffer() + if write_buffer: + var bytes_sent = conn.write(write_buffer) + if bytes_sent < len(write_buffer): + conn.set_write_buffer(write_buffer[bytes_sent:]) + else: + conn.set_write_buffer(Bytes()) \ No newline at end of file diff --git a/lightbug_http/uri.mojo b/lightbug_http/uri.mojo index ca31a0cb..51b09c22 100644 --- a/lightbug_http/uri.mojo +++ b/lightbug_http/uri.mojo @@ -1,4 +1,5 @@ -from lightbug_http.io.bytes import Bytes, BytesView, bytes_equal, bytes +from utils import Variant +from lightbug_http.io.bytes import Bytes, bytes_equal, bytes from lightbug_http.strings import ( strSlash, strHttp11, @@ -12,241 +13,58 @@ from lightbug_http.strings import ( @value struct URI: - var __path_original: Bytes - var __scheme: Bytes - var __path: Bytes - var __query_string: Bytes - var __hash: Bytes - var __host: Bytes - var __http_version: Bytes - - var disable_path_normalization: Bool - - var __full_uri: Bytes - var __request_uri: Bytes - - var __username: Bytes - var __password: Bytes - - fn __init__( - inout self, - full_uri: String, - ) -> None: - self.__path_original = Bytes() - self.__scheme = Bytes() - self.__path = Bytes() - self.__query_string = Bytes() - self.__hash = Bytes() - self.__host = Bytes() - self.__http_version = Bytes() - self.disable_path_normalization = False - self.__full_uri = bytes(full_uri, pop=False) - self.__request_uri = Bytes() - self.__username = Bytes() - self.__password = Bytes() + var __path_original: String + var scheme: String + var path: String + var query_string: String + var __hash: String + var host: String + + var full_uri: String + var request_uri: String + + var username: String + var password: String + + @staticmethod + fn parse(uri: String) -> Variant[URI, String]: + var u = URI(uri) + try: + u._parse() + except e: + return "Failed to parse URI: " + str(e) + + return u - fn __init__( - inout self, - full_uri: String, - host: String - ) -> None: - self.__path_original = Bytes() - self.__scheme = Bytes() - self.__path = Bytes() - self.__query_string = Bytes() - self.__hash = Bytes() - self.__host = bytes(host) - self.__http_version = Bytes() - self.disable_path_normalization = False - self.__full_uri = bytes(full_uri) - self.__request_uri = Bytes() - self.__username = Bytes() - self.__password = Bytes() + @staticmethod + fn parse_raises(uri: String) raises -> URI: + var u = URI(uri) + u._parse() + return u fn __init__( inout self, - scheme: String, - host: String, - path: String, + uri: String = "", ) -> None: - self.__path_original = bytes(path) - self.__scheme = scheme.as_bytes() - self.__path = normalise_path(bytes(path), self.__path_original) - self.__query_string = Bytes() - self.__hash = Bytes() - self.__host = bytes(host) - self.__http_version = Bytes() - self.disable_path_normalization = False - self.__full_uri = Bytes() - self.__request_uri = Bytes() - self.__username = Bytes() - self.__password = Bytes() - - fn __init__( - inout self, - path_original: Bytes, - path: Bytes, - scheme: Bytes, - query_string: Bytes, - hash: Bytes, - host: Bytes, - http_version: Bytes, - disable_path_normalization: Bool, - full_uri: Bytes, - request_uri: Bytes, - username: Bytes, - password: Bytes, - ): - self.__path_original = path_original - self.__scheme = scheme - self.__path = path - self.__query_string = query_string - self.__hash = hash - self.__host = host - self.__http_version = http_version - self.disable_path_normalization = disable_path_normalization - self.__full_uri = full_uri - self.__request_uri = request_uri - self.__username = username - self.__password = password - - fn path_original(self) -> BytesView: - return BytesView(unsafe_ptr=self.__path_original.unsafe_ptr(), len=self.__path_original.size) - - fn set_path(inout self, path: String) -> Self: - self.__path = normalise_path(bytes(path), self.__path_original) - return self - - fn set_path_bytes(inout self, path: Bytes) -> Self: - self.__path = normalise_path(path, self.__path_original) - return self - - fn path(self) -> String: - if len(self.__path) == 0: - return strSlash - return String(self.__path) - - fn path_bytes(self) -> BytesView: - if len(self.__path) == 0: - return BytesView(unsafe_ptr=strSlash.as_bytes_slice().unsafe_ptr(), len=2) - return BytesView(unsafe_ptr=self.__path.unsafe_ptr(), len=self.__path.size) - - fn set_scheme(inout self, scheme: String) -> Self: - self.__scheme = bytes(scheme) - return self - - fn set_scheme_bytes(inout self, scheme: Bytes) -> Self: - self.__scheme = scheme - return self - - fn scheme(self) -> BytesView: - if len(self.__scheme) == 0: - return BytesView(unsafe_ptr=strHttp.as_bytes_slice().unsafe_ptr(), len=5) - return BytesView(unsafe_ptr=self.__scheme.unsafe_ptr(), len=self.__scheme.size) - - fn http_version(self) -> BytesView: - if len(self.__http_version) == 0: - return BytesView(unsafe_ptr=strHttp11.as_bytes_slice().unsafe_ptr(), len=9) - return BytesView(unsafe_ptr=self.__http_version.unsafe_ptr(), len=self.__http_version.size) - - fn http_version_str(self) -> String: - return self.__http_version - - fn set_http_version(inout self, http_version: String) -> Self: - self.__http_version = bytes(http_version) - return self - - fn set_http_version_bytes(inout self, http_version: Bytes) -> Self: - self.__http_version = http_version - return self - - fn is_http_1_1(self) -> Bool: - return bytes_equal(self.http_version(), bytes(strHttp11, pop=False)) - - fn is_http_1_0(self) -> Bool: - return bytes_equal(self.http_version(), bytes(strHttp10, pop=False)) + self.__path_original = "/" + self.scheme = + self.path = "/" + self.query_string = "" + self.__hash = "" + self.host = "" + self.full_uri = uri + self.request_uri = "" + self.username = "" + self.password = "" fn is_https(self) -> Bool: - return bytes_equal(self.__scheme, bytes(https, pop=False)) + return self.scheme == https fn is_http(self) -> Bool: - return bytes_equal(self.__scheme, bytes(http, pop=False)) or len(self.__scheme) == 0 - - fn set_request_uri(inout self, request_uri: String) -> Self: - self.__request_uri = bytes(request_uri) - return self - - fn set_request_uri_bytes(inout self, request_uri: Bytes) -> Self: - self.__request_uri = request_uri - return self - - fn request_uri(self) -> BytesView: - return BytesView(unsafe_ptr=self.__request_uri.unsafe_ptr(), len=self.__request_uri.size) - - fn set_query_string(inout self, query_string: String) -> Self: - self.__query_string = bytes(query_string) - return self - - fn set_query_string_bytes(inout self, query_string: Bytes) -> Self: - self.__query_string = query_string - return self - - fn query_string(self) -> BytesView: - return BytesView(unsafe_ptr=self.__query_string.unsafe_ptr(), len=self.__query_string.size) - - fn set_hash(inout self, hash: String) -> Self: - self.__hash = bytes(hash) - return self - - fn set_hash_bytes(inout self, hash: Bytes) -> Self: - self.__hash = hash - return self - - fn hash(self) -> BytesView: - return BytesView(unsafe_ptr=self.__hash.unsafe_ptr(), len=self.__hash.size) - - fn set_host(inout self, host: String) -> Self: - self.__host = bytes(host) - return self - - fn set_host_bytes(inout self, host: Bytes) -> Self: - self.__host = host - return self - - fn host(self) -> BytesView: - return BytesView(unsafe_ptr=self.__host.unsafe_ptr(), len=self.__host.size) - - fn host_str(self) -> String: - return self.__host - - fn full_uri(self) -> BytesView: - return BytesView(unsafe_ptr=self.__full_uri.unsafe_ptr(), len=self.__full_uri.size) - - fn set_username(inout self, username: String) -> Self: - self.__username = bytes(username) - return self - - fn set_username_bytes(inout self, username: Bytes) -> Self: - self.__username = username - return self - - fn username(self) -> BytesView: - return BytesView(unsafe_ptr=self.__username.unsafe_ptr(), len=self.__username.size) - - fn set_password(inout self, password: String) -> Self: - self.__password = bytes(password) - return self - - fn set_password_bytes(inout self, password: Bytes) -> Self: - self.__password = password - return self - - fn password(self) -> BytesView: - return BytesView(unsafe_ptr=self.__password.unsafe_ptr(), len=self.__password.size) - - fn parse(inout self) raises -> None: - var raw_uri = String(self.__full_uri) + return self.scheme == http or len(self.scheme) == 0 + fn _parse(inout self) raises -> None: + var raw_uri = self.full_uri var proto_str = String(strHttp11) var is_https = False @@ -259,8 +77,8 @@ struct URI: remainder_uri = raw_uri[proto_end + 3:] else: remainder_uri = raw_uri - - _ = self.set_scheme_bytes(proto_str.as_bytes_slice()) + + self.scheme = proto_str^ var path_start = remainder_uri.find("/") var host_and_port: String @@ -268,29 +86,29 @@ struct URI: if path_start >= 0: host_and_port = remainder_uri[:path_start] request_uri = remainder_uri[path_start:] - _ = self.set_host_bytes(bytes(host_and_port[:path_start], pop=False)) + self.host = host_and_port[:path_start] else: host_and_port = remainder_uri request_uri = strSlash - _ = self.set_host_bytes(bytes(host_and_port, pop=False)) + self.host = host_and_port if is_https: - _ = self.set_scheme_bytes(bytes(https, pop=False)) + self.scheme = https else: - _ = self.set_scheme_bytes(bytes(http, pop=False)) + self.scheme = http var n = request_uri.find("?") if n >= 0: - self.__path_original = bytes(request_uri[:n], pop=False) - self.__query_string = bytes(request_uri[n + 1 :], pop=False) + self.__path_original = request_uri[:n] + self.query_string = request_uri[n + 1 :] else: - self.__path_original = bytes(request_uri, pop=False) - self.__query_string = Bytes() + self.__path_original = request_uri + self.query_string = Bytes() - _ = self.set_path_bytes(normalise_path(self.__path_original, self.__path_original)) - _ = self.set_request_uri_bytes(bytes(request_uri, pop=False)) + self.path = self.__path_original + self.request_uri = request_uri -fn normalise_path(path: Bytes, path_original: Bytes) -> Bytes: +fn normalise_path(path: String, path_original: String) -> String: # TODO: implement return path diff --git a/lightbug_http/utils.mojo b/lightbug_http/utils.mojo new file mode 100644 index 00000000..8ca06387 --- /dev/null +++ b/lightbug_http/utils.mojo @@ -0,0 +1,98 @@ +from lightbug_http.io.bytes import Bytes, Byte +from lightbug_http.strings import BytesConstant +from lightbug_http.net import default_buffer_size +from memory import memcpy + + +@always_inline +fn is_newline(b: Byte) -> Bool: + return b == BytesConstant.nChar or b == BytesConstant.rChar + + +@always_inline +fn is_space(b: Byte) -> Bool: + return b == BytesConstant.whitespace + + +struct ByteWriter: + var _inner: Bytes + + fn __init__(inout self): + self._inner = Bytes(capacity=default_buffer_size) + + @always_inline + fn write(inout self, owned b: Bytes): + self._inner.extend(b^) + + @always_inline + fn write(inout self, inout s: String): + # kind of cursed but seems to work? + _ = s._buffer.pop() + self._inner.extend(s._buffer^) + s._buffer = s._buffer_type() + + @always_inline + fn write(inout self, s: StringLiteral): + var str = String(s) + self.write(str) + + @always_inline + fn write(inout self, b: Byte): + self._inner.append(b) + + fn consume(inout self) -> Bytes: + var ret = self._inner^ + self._inner = Bytes() + return ret^ + + +struct ByteReader: + var _inner: Bytes + var read_pos: Int + + fn __init__(inout self, owned b: Bytes): + self._inner = b^ + self.read_pos = 0 + + fn peek(self) -> Byte: + if self.read_pos >= len(self._inner): + return 0 + return self._inner[self.read_pos] + + fn read_until(inout self, char: Byte) -> Bytes: + var start = self.read_pos + while self.peek() != char: + self.increment() + return self._inner[start : self.read_pos] + + @always_inline + fn read_word(inout self) -> Bytes: + return self.read_until(BytesConstant.whitespace) + + fn read_line(inout self) -> Bytes: + var start = self.read_pos + while not is_newline(self.peek()): + self.increment() + var ret = self._inner[start : self.read_pos] + if self.peek() == BytesConstant.rChar: + self.increment(2) + else: + self.increment() + return ret + + @always_inline + fn skip_whitespace(inout self): + while is_space(self.peek()): + self.increment() + + @always_inline + fn increment(inout self, v: Int = 1): + self.read_pos += v + + @always_inline + fn consume(inout self, inout buffer: Bytes): + var pos = self.read_pos + self.read_pos = -1 + var read_len = len(self._inner) - pos + buffer.resize(read_len, 0) + memcpy(buffer.data, self._inner.data + pos, read_len) diff --git a/work_in_progress/websocket.mojo b/lightbug_http/websocket.mojo similarity index 51% rename from work_in_progress/websocket.mojo rename to lightbug_http/websocket.mojo index 19d34492..04a7a7de 100644 --- a/work_in_progress/websocket.mojo +++ b/lightbug_http/websocket.mojo @@ -1,9 +1,17 @@ from collections import Dict, Optional from python import Python, PythonObject +from lightbug_http.io.bytes import Bytes, bytes from time import sleep - -# it is a "magic" constant, see: -# https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#server_handshake_response +from base64 import b64encode +from lightbug_http.io.bytes import bytes_equal, bytes +from lightbug_http.http import HTTPRequest, HTTPResponse, Headers +from lightbug_http.net import Connection, default_buffer_size +from lightbug_http.sys.net import SysConnection +from lightbug_http.service import WebSocketService, UpgradeLoop + +# This is a "magic" GUID (Globally Unique Identifier) string that is concatenated +# with the value of the Sec-WebSocket-Key header in order to securely conduct the websocket handshake +# https://datatracker.ietf.org/doc/html/rfc6455#section-1.3 alias MAGIC_CONSTANT = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" alias BYTE_0_TEXT: UInt8 = 1 @@ -15,134 +23,63 @@ alias BYTE_1_SIZE_ONE_BYTE:UInt8 = 125 alias BYTE_1_SIZE_TWO_BYTES:UInt8 = 126 alias BYTE_1_SIZE_EIGHT_BYTES:UInt8 = 127 -fn websocket[ - host: StringLiteral = "127.0.0.1", - port: Int = 8000 -]()->Optional[PythonObject]: + +@value +struct WebSocketPrinter(WebSocketService): + fn on_message(inout self, conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + print(String(data)) + + +@value +struct WebSocketLoop[T: WebSocketService](UpgradeLoop): + var handler: T + # array goes here + + fn process_data(inout self, owned conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + self.handle_frame(conn, is_binary, data) + return + + fn handle_frame(inout self, owned conn: SysConnection, is_binary: Bool, data: Bytes) -> None: + var message = receive_message(conn, data) + if message: + self.handler.on_message(conn, is_binary, message) + return + + fn can_upgrade(self) -> Bool: + return True + + +@value +struct WebSocketHandshake(HTTPService): """ - 1. Open server - 2. Upgrade first HTTP client to websocket - 3. Close server - 4. return the websocket. + Upgrades an HTTP connection to a WebSocket connection and returns the response. """ + fn func(self, req: HTTPRequest) raises -> HTTPResponse: + if not req.header.connection_upgrade(): + raise Error("Request headers do not contain an upgrade header") - var client = PythonObject(None) - try: - var py_socket = Python.import_module("socket") - var py_base64 = Python.import_module("base64") - var py_sha1 = Python.import_module("hashlib").sha1 - var server = py_socket.socket(py_socket.AF_INET, py_socket.SOCK_STREAM) - server.setsockopt(py_socket.SOL_SOCKET, py_socket.SO_REUSEADDR, 1) - server.bind((host, port)) - server.listen(1) - print("ws://"+str(host)+":"+str(port)) - - client = server.accept() - # Only localhost ! - if client[1][0] != '127.0.0.1': - print("Exit, request from: "+str(client[1][0])) - client.close() - server.close() - return None - - # Close server - server.close() - - # Get request - var request = client[0].recv(1024).decode() - var request_header = Dict[String,String]() - print(request.__repr__()) - - var end_header = int(request.find("\r\n\r\n")) - if end_header == -1: - raise "end_header == -1, no \\r\\n\\r\\n" - var request_split = str(request)[:end_header].split("\r\n") - if len(request_split) == 0: - raise "error: len(request_split) == 0" - if request_split[0] != "GET / HTTP/1.1": - raise "request_split[0] not GET / HTTP/1.1" - _ = request_split.pop(0) - - if len(request_split) == 0: - raise "error: no headers" - - for e in request_split: - var header_pos = e[].find(":") - if header_pos == -1: - raise "header_pos == -1" - if len(e[]) == header_pos+2: - raise "len(e[]) == header_pos+2" - var k = e[][:header_pos] - var v = e[][header_pos+2:] - request_header[k^]=v^ - - for h in request_header: - print(h[], request_header[h[]]) - - #Upgrade to websocket - if "Upgrade" not in request_header: - raise "Not upgrade to websocket" + if not bytes_equal(req.header.upgrade(), String("websocket").as_bytes()): + raise Error("Request upgrade do not contain an upgrade to websocket") - if request_header["Upgrade"] != "websocket": - raise "Not an upgrade to websocket" - - if "Sec-WebSocket-Key" not in request_header: - raise "No Sec-WebSocket-Key for upgrading to websocket" - - var accept = PythonObject(request_header["Sec-WebSocket-Key"]) - accept += PythonObject(MAGIC_CONSTANT) - accept = accept.encode() - accept = py_base64.b64encode(py_sha1(accept).digest()) - - var response = String("HTTP/1.1 101 Switching Protocols\r\n") - response += "Upgrade: websocket\r\n" - response += "Connection: Upgrade\r\n" - response += "Sec-WebSocket-Accept: " - response += str(accept.decode("utf-8")) - response += String("\r\n\r\n") - - print(response) - - client[0].send(PythonObject(response).encode()) - return client^ + if not req.headers["Sec-WebSocket-Key"]: + raise Error("No Sec-WebSocket-Key for upgrading to websocket") - except e: - print(e) - - return None + var accept = String(req.header["Sec-WebSocket-Key"]) + MAGIC_CONSTANT + var accept_sha1 = Python.import_module("hashlib").sha1(accept).digest() + var accept_encoded = b64encode(accept_sha1) -def main(): - var select = Python.import_module("select").select - var ws = websocket() - if ws: - for i in range(32): - var res = select([ws.value()[0]],[],[],0)[0] - while len(res) == 0: - send_message(ws.value(), "server waiting") - res = select([ws.value()[0]],[],[],0)[0] - print("\nwait\n") - sleep(1) - m = receive_message(ws.value()) - if m: - # print(m.value()) - send_message(ws.value(),m.value()) - - _ = ws^ - _ = select^ - -fn read_byte(inout ws: PythonObject)raises->UInt8: - return UInt8(int(ws[0].recv(1)[0])) + var header = Headers(Header("Upgrade", "websocket"), Header("Connection", "Upgrade"), Header("Sec-WebSocket-Accept", accept_encoded)) + + return HTTPResponse(Bytes(), header, 101, "Switching Protocols") fn receive_message[ maximum_default_capacity:Int = 1<<16 -](inout ws: PythonObject)->Optional[String]: +](inout ws: PythonObject, b: Bytes)->Optional[String]: #limit to 64kb by default! var res = String("") try: - _ = read_byte(ws) #not implemented yet - var b = read_byte(ws) - if (b&BYTE_1_FRAME_IS_MASKED) == 0: + if (len(b) != 0 and b[0] != BYTE_1_FRAME_IS_MASKED) == 0: # if client send non-masked frame, connection must be closed # https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#format ws[0].close() diff --git a/magic.lock b/magic.lock new file mode 100644 index 00000000..a276cbf1 --- /dev/null +++ b/magic.lock @@ -0,0 +1,1638 @@ +version: 5 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + - url: https://conda.modular.com/max/ + - url: https://repo.prefix.dev/mojo-community/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.7-unix_pyh707e725_0.conda + - conda: https://repo.prefix.dev/mojo-community/linux-64/gojo-0.1.9-hb0f4dca_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jupyter_core-5.7.2-py312h7900ff3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-23_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-23_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.3-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.1.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.1.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.1.0-hc5f4f2c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-23_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.27-pthreads_hac2b453_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.1.0-hc0a3c3a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + - conda: https://conda.modular.com/max/noarch/max-24.5.0-release.conda + - conda: https://conda.modular.com/max/linux-64/max-core-24.5.0-release.conda + - conda: https://conda.modular.com/max/linux-64/max-python-24.5.0-3.12release.conda + - conda: https://conda.modular.com/max/noarch/mblack-24.5.0-release.conda + - conda: https://conda.modular.com/max/noarch/mojo-jupyter-24.5.0-release.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.0.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.5-h2ad013b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-26.2.0-py312hbf22597_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 + - conda: https://repo.prefix.dev/mojo-community/linux-64/small_time-0.1.3-hb0f4dca_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.4.1-py312h66e93f0_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-ha4adb4c_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.8.30-hf0a4a13_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.7-unix_pyh707e725_0.conda + - conda: https://repo.prefix.dev/mojo-community/osx-arm64/gojo-0.1.9-h60d57d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/jupyter_core-5.7.2-py312h81bd7bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.9.0-23_osxarm64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.9.0-23_osxarm64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-18.1.8-h3ed4263_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20191231-hc8eb9b7_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.3-hf9b8971_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-5.0.0-13_2_0_hd922786_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-13.2.0-hf226fd6_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.9.0-23_osxarm64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.27-openmp_h517c56d_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.46.1-hc14010f_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-hfb2fe0b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-18.1.8-hde57baf_1.conda + - conda: https://conda.modular.com/max/noarch/max-24.5.0-release.conda + - conda: https://conda.modular.com/max/osx-arm64/max-core-24.5.0-release.conda + - conda: https://conda.modular.com/max/osx-arm64/max-python-24.5.0-3.12release.conda + - conda: https://conda.modular.com/max/noarch/mblack-24.5.0-release.conda + - conda: https://conda.modular.com/max/noarch/mojo-jupyter-24.5.0-release.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.0.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-1.26.4-py312h8442bc7_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.3.2-h8359307_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.6-h739c21a_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-26.2.0-py312hc6335d2_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 + - conda: https://repo.prefix.dev/mojo-community/osx-arm64/small_time-0.1.3-h60d57d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.4.1-py312h024a12e_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h64debc3_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda +packages: +- kind: conda + name: _libgcc_mutex + version: '0.1' + build: conda_forge + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + size: 2562 + timestamp: 1578324546067 +- kind: conda + name: _openmp_mutex + version: '4.5' + build: 2_gnu + build_number: 16 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 + md5: 73aaf86a425cc6e73fcf236a5a46396d + depends: + - _libgcc_mutex 0.1 conda_forge + - libgomp >=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + size: 23621 + timestamp: 1650670423406 +- kind: conda + name: bzip2 + version: 1.0.8 + build: h4bc722e_7 + build_number: 7 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d + md5: 62ee74e96c5ebb0af99386de58cf9553 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: bzip2-1.0.6 + license_family: BSD + size: 252783 + timestamp: 1720974456583 +- kind: conda + name: bzip2 + version: 1.0.8 + build: h99b78c6_7 + build_number: 7 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda + sha256: adfa71f158cbd872a36394c56c3568e6034aa55c623634b37a4836bd036e6b91 + md5: fc6948412dbbbe9a4c9ddbbcfe0a79ab + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + size: 122909 + timestamp: 1720974522888 +- kind: conda + name: ca-certificates + version: 2024.8.30 + build: hbcca054_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.8.30-hbcca054_0.conda + sha256: afee721baa6d988e27fef1832f68d6f32ac8cc99cdf6015732224c2841a09cea + md5: c27d1c142233b5bc9ca570c6e2e0c244 + license: ISC + size: 159003 + timestamp: 1725018903918 +- kind: conda + name: ca-certificates + version: 2024.8.30 + build: hf0a4a13_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/ca-certificates-2024.8.30-hf0a4a13_0.conda + sha256: 2db1733f4b644575dbbdd7994a8f338e6ef937f5ebdb74acd557e9dda0211709 + md5: 40dec13fd8348dbe303e57be74bd3d35 + license: ISC + size: 158482 + timestamp: 1725019034582 +- kind: conda + name: click + version: 8.1.7 + build: unix_pyh707e725_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.7-unix_pyh707e725_0.conda + sha256: f0016cbab6ac4138a429e28dbcb904a90305b34b3fe41a9b89d697c90401caec + md5: f3ad426304898027fc619827ff428eca + depends: + - __unix + - python >=3.8 + license: BSD-3-Clause + license_family: BSD + size: 84437 + timestamp: 1692311973840 +- kind: conda + name: gojo + version: 0.1.9 + build: h60d57d3_0 + subdir: osx-arm64 + url: https://repo.prefix.dev/mojo-community/osx-arm64/gojo-0.1.9-h60d57d3_0.conda + sha256: 4c268d0d8d5f1b78a547e78a3db9e8037143918cd1d696f5adb5db55942cef5e + depends: + - max >=24.5.0,<24.6.0 + arch: arm64 + platform: osx + license: MIT + size: 1009999 + timestamp: 1726268309700 +- kind: conda + name: gojo + version: 0.1.9 + build: hb0f4dca_0 + subdir: linux-64 + url: https://repo.prefix.dev/mojo-community/linux-64/gojo-0.1.9-hb0f4dca_0.conda + sha256: 9a49e21b4269368a6d906769bd041b8b91b99da3375d7944f7d8ddd73392c2f0 + depends: + - max >=24.5.0,<24.6.0 + arch: x86_64 + platform: linux + license: MIT + size: 1011206 + timestamp: 1726268249824 +- kind: conda + name: importlib-metadata + version: 8.5.0 + build: pyha770c72_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_0.conda + sha256: 7194700ce1a5ad2621fd68e894dd8c1ceaff9a38723e6e0e5298fdef13017b1c + md5: 54198435fce4d64d8a89af22573012a8 + depends: + - python >=3.8 + - zipp >=0.5 + license: Apache-2.0 + license_family: APACHE + size: 28646 + timestamp: 1726082927916 +- kind: conda + name: importlib_metadata + version: 8.5.0 + build: hd8ed1ab_0 + subdir: noarch + noarch: generic + url: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda + sha256: 313b8a05211bacd6b15ab2621cb73d7f41ea5c6cae98db53367d47833f03fef1 + md5: 2a92e152208121afadf85a5e1f3a5f4d + depends: + - importlib-metadata >=8.5.0,<8.5.1.0a0 + license: Apache-2.0 + license_family: APACHE + size: 9385 + timestamp: 1726082930346 +- kind: conda + name: jupyter_client + version: 8.6.2 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.6.2-pyhd8ed1ab_0.conda + sha256: 634f065cdd1d0aacd4bb6848ebf240dcebc8578135d65f4ad4aa42b2276c4e0c + md5: 3cdbb2fa84490e5fd44c9f9806c0d292 + depends: + - importlib_metadata >=4.8.3 + - jupyter_core >=4.12,!=5.0.* + - python >=3.8 + - python-dateutil >=2.8.2 + - pyzmq >=23.0 + - tornado >=6.2 + - traitlets >=5.3 + license: BSD-3-Clause + license_family: BSD + size: 106248 + timestamp: 1716472312833 +- kind: conda + name: jupyter_core + version: 5.7.2 + build: py312h7900ff3_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/jupyter_core-5.7.2-py312h7900ff3_0.conda + sha256: 22a6259c2b139191c76ed7633d1865757b3c15007989f6c74304a80f28e5a262 + md5: eee5a2e3465220ed87196bbb5665f420 + depends: + - platformdirs >=2.5 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - traitlets >=5.3 + license: BSD-3-Clause + license_family: BSD + size: 92843 + timestamp: 1710257533875 +- kind: conda + name: jupyter_core + version: 5.7.2 + build: py312h81bd7bf_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/jupyter_core-5.7.2-py312h81bd7bf_0.conda + sha256: 5ab0e75a30915d34ae27b4a76f1241c2f4cc4419b6b1c838cc1160b9ec8bfaf5 + md5: 209b9cb7159212afce5e16d7a3ee3b47 + depends: + - platformdirs >=2.5 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - traitlets >=5.3 + license: BSD-3-Clause + license_family: BSD + size: 93829 + timestamp: 1710257916303 +- kind: conda + name: keyutils + version: 1.6.1 + build: h166bdaf_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 + sha256: 150c05a6e538610ca7c43beb3a40d65c90537497a4f6a5f4d15ec0451b6f5ebb + md5: 30186d27e2c9fa62b45fb1476b7200e3 + depends: + - libgcc-ng >=10.3.0 + license: LGPL-2.1-or-later + size: 117831 + timestamp: 1646151697040 +- kind: conda + name: krb5 + version: 1.21.3 + build: h237132a_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda + sha256: 4442f957c3c77d69d9da3521268cad5d54c9033f1a73f99cde0a3658937b159b + md5: c6dc8a0fdec13a0565936655c33069a1 + depends: + - __osx >=11.0 + - libcxx >=16 + - libedit >=3.1.20191231,<3.2.0a0 + - libedit >=3.1.20191231,<4.0a0 + - openssl >=3.3.1,<4.0a0 + license: MIT + license_family: MIT + size: 1155530 + timestamp: 1719463474401 +- kind: conda + name: krb5 + version: 1.21.3 + build: h659f571_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda + sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 + md5: 3f43953b7d3fb3aaa1d0d0723d91e368 + depends: + - keyutils >=1.6.1,<2.0a0 + - libedit >=3.1.20191231,<3.2.0a0 + - libedit >=3.1.20191231,<4.0a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - openssl >=3.3.1,<4.0a0 + license: MIT + license_family: MIT + size: 1370023 + timestamp: 1719463201255 +- kind: conda + name: ld_impl_linux-64 + version: '2.40' + build: hf3520f5_7 + build_number: 7 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda + sha256: 764b6950aceaaad0c67ef925417594dd14cd2e22fff864aeef455ac259263d15 + md5: b80f2f396ca2c28b8c14c437a4ed1e74 + constrains: + - binutils_impl_linux-64 2.40 + license: GPL-3.0-only + license_family: GPL + size: 707602 + timestamp: 1718625640445 +- kind: conda + name: libblas + version: 3.9.0 + build: 23_linux64_openblas + build_number: 23 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-23_linux64_openblas.conda + sha256: edb1cee5da3ac4936940052dcab6969673ba3874564f90f5110f8c11eed789c2 + md5: 96c8450a40aa2b9733073a9460de972c + depends: + - libopenblas >=0.3.27,<0.3.28.0a0 + - libopenblas >=0.3.27,<1.0a0 + constrains: + - liblapacke 3.9.0 23_linux64_openblas + - libcblas 3.9.0 23_linux64_openblas + - liblapack 3.9.0 23_linux64_openblas + - blas * openblas + license: BSD-3-Clause + license_family: BSD + size: 14880 + timestamp: 1721688759937 +- kind: conda + name: libblas + version: 3.9.0 + build: 23_osxarm64_openblas + build_number: 23 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.9.0-23_osxarm64_openblas.conda + sha256: 1c30da861e306a25fac8cd30ce0c1b31c9238d04e7768c381cf4d431b4361e6c + md5: acae9191e8772f5aff48ab5232d4d2a3 + depends: + - libopenblas >=0.3.27,<0.3.28.0a0 + - libopenblas >=0.3.27,<1.0a0 + constrains: + - liblapack 3.9.0 23_osxarm64_openblas + - blas * openblas + - liblapacke 3.9.0 23_osxarm64_openblas + - libcblas 3.9.0 23_osxarm64_openblas + license: BSD-3-Clause + license_family: BSD + size: 15103 + timestamp: 1721688997980 +- kind: conda + name: libcblas + version: 3.9.0 + build: 23_linux64_openblas + build_number: 23 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-23_linux64_openblas.conda + sha256: 3e7a3236e7e03e308e1667d91d0aa70edd0cba96b4b5563ef4adde088e0881a5 + md5: eede29b40efa878cbe5bdcb767e97310 + depends: + - libblas 3.9.0 23_linux64_openblas + constrains: + - liblapacke 3.9.0 23_linux64_openblas + - liblapack 3.9.0 23_linux64_openblas + - blas * openblas + license: BSD-3-Clause + license_family: BSD + size: 14798 + timestamp: 1721688767584 +- kind: conda + name: libcblas + version: 3.9.0 + build: 23_osxarm64_openblas + build_number: 23 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.9.0-23_osxarm64_openblas.conda + sha256: c39d944909d0608bd0333398be5e0051045c9451bfd6cc6320732d33375569c8 + md5: bad6ee9b7d5584efc2bc5266137b5f0d + depends: + - libblas 3.9.0 23_osxarm64_openblas + constrains: + - liblapack 3.9.0 23_osxarm64_openblas + - liblapacke 3.9.0 23_osxarm64_openblas + - blas * openblas + license: BSD-3-Clause + license_family: BSD + size: 14991 + timestamp: 1721689017803 +- kind: conda + name: libcxx + version: 18.1.8 + build: h3ed4263_7 + build_number: 7 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-18.1.8-h3ed4263_7.conda + sha256: 15b4abaa249f0965ce42aeb4a1a2b1b5df9a1f402e7c5bd8156272fd6cad2878 + md5: e0e7d9a2ec0f9509ffdfd5f48da522fb + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + size: 436921 + timestamp: 1725403628507 +- kind: conda + name: libedit + version: 3.1.20191231 + build: hc8eb9b7_2 + build_number: 2 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20191231-hc8eb9b7_2.tar.bz2 + sha256: 3912636197933ecfe4692634119e8644904b41a58f30cad9d1fc02f6ba4d9fca + md5: 30e4362988a2623e9eb34337b83e01f9 + depends: + - ncurses >=6.2,<7.0.0a0 + license: BSD-2-Clause + license_family: BSD + size: 96607 + timestamp: 1597616630749 +- kind: conda + name: libedit + version: 3.1.20191231 + build: he28a2e2_2 + build_number: 2 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2 + sha256: a57d37c236d8f7c886e01656f4949d9dcca131d2a0728609c6f7fa338b65f1cf + md5: 4d331e44109e3f0e19b4cb8f9b82f3e1 + depends: + - libgcc-ng >=7.5.0 + - ncurses >=6.2,<7.0.0a0 + license: BSD-2-Clause + license_family: BSD + size: 123878 + timestamp: 1597616541093 +- kind: conda + name: libexpat + version: 2.6.3 + build: h5888daf_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.3-h5888daf_0.conda + sha256: 4bb47bb2cd09898737a5211e2992d63c555d63715a07ba56eae0aff31fb89c22 + md5: 59f4c43bb1b5ef1c71946ff2cbf59524 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - expat 2.6.3.* + license: MIT + license_family: MIT + size: 73616 + timestamp: 1725568742634 +- kind: conda + name: libexpat + version: 2.6.3 + build: hf9b8971_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.6.3-hf9b8971_0.conda + sha256: 5cbe5a199fba14ade55457a468ce663aac0b54832c39aa54470b3889b4c75c4a + md5: 5f22f07c2ab2dea8c66fe9585a062c96 + depends: + - __osx >=11.0 + constrains: + - expat 2.6.3.* + license: MIT + license_family: MIT + size: 63895 + timestamp: 1725568783033 +- kind: conda + name: libffi + version: 3.4.2 + build: h3422bc3_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 + sha256: 41b3d13efb775e340e4dba549ab5c029611ea6918703096b2eaa9c015c0750ca + md5: 086914b672be056eb70fd4285b6783b6 + license: MIT + license_family: MIT + size: 39020 + timestamp: 1636488587153 +- kind: conda + name: libffi + version: 3.4.2 + build: h7f98852_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 + sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e + md5: d645c6d2ac96843a2bfaccd2d62b3ac3 + depends: + - libgcc-ng >=9.4.0 + license: MIT + license_family: MIT + size: 58292 + timestamp: 1636488182923 +- kind: conda + name: libgcc + version: 14.1.0 + build: h77fa898_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda + sha256: 10fa74b69266a2be7b96db881e18fa62cfa03082b65231e8d652e897c4b335a3 + md5: 002ef4463dd1e2b44a94a4ace468f5d2 + depends: + - _libgcc_mutex 0.1 conda_forge + - _openmp_mutex >=4.5 + constrains: + - libgomp 14.1.0 h77fa898_1 + - libgcc-ng ==14.1.0=*_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 846380 + timestamp: 1724801836552 +- kind: conda + name: libgcc-ng + version: 14.1.0 + build: h69a702a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda + sha256: b91f7021e14c3d5c840fbf0dc75370d6e1f7c7ff4482220940eaafb9c64613b7 + md5: 1efc0ad219877a73ef977af7dbb51f17 + depends: + - libgcc 14.1.0 h77fa898_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 52170 + timestamp: 1724801842101 +- kind: conda + name: libgfortran + version: 5.0.0 + build: 13_2_0_hd922786_3 + build_number: 3 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-5.0.0-13_2_0_hd922786_3.conda + sha256: 44e541b4821c96b28b27fef5630883a60ce4fee91fd9c79f25a199f8f73f337b + md5: 4a55d9e169114b2b90d3ec4604cd7bbf + depends: + - libgfortran5 13.2.0 hf226fd6_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 110233 + timestamp: 1707330749033 +- kind: conda + name: libgfortran + version: 14.1.0 + build: h69a702a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.1.0-h69a702a_1.conda + sha256: ed77f04f873e43a26e24d443dd090631eedc7d0ace3141baaefd96a123e47535 + md5: 591e631bc1ae62c64f2ab4f66178c097 + depends: + - libgfortran5 14.1.0 hc5f4f2c_1 + constrains: + - libgfortran-ng ==14.1.0=*_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 52142 + timestamp: 1724801872472 +- kind: conda + name: libgfortran-ng + version: 14.1.0 + build: h69a702a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.1.0-h69a702a_1.conda + sha256: a2dc35cb7f87bb5beebf102d4085574c6a740e1df58e743185d4434cc5e4e0ae + md5: 16cec94c5992d7f42ae3f9fa8b25df8d + depends: + - libgfortran 14.1.0 h69a702a_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 52212 + timestamp: 1724802086021 +- kind: conda + name: libgfortran5 + version: 13.2.0 + build: hf226fd6_3 + build_number: 3 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-13.2.0-hf226fd6_3.conda + sha256: bafc679eedb468a86aa4636061c55966186399ee0a04b605920d208d97ac579a + md5: 66ac81d54e95c534ae488726c1f698ea + depends: + - llvm-openmp >=8.0.0 + constrains: + - libgfortran 5.0.0 13_2_0_*_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 997381 + timestamp: 1707330687590 +- kind: conda + name: libgfortran5 + version: 14.1.0 + build: hc5f4f2c_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.1.0-hc5f4f2c_1.conda + sha256: c40d7db760296bf9c776de12597d2f379f30e890b9ae70c1de962ff2aa1999f6 + md5: 10a0cef64b784d6ab6da50ebca4e984d + depends: + - libgcc >=14.1.0 + constrains: + - libgfortran 14.1.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 1459939 + timestamp: 1724801851300 +- kind: conda + name: libgomp + version: 14.1.0 + build: h77fa898_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda + sha256: c96724c8ae4ee61af7674c5d9e5a3fbcf6cd887a40ad5a52c99aa36f1d4f9680 + md5: 23c255b008c4f2ae008f81edcabaca89 + depends: + - _libgcc_mutex 0.1 conda_forge + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 460218 + timestamp: 1724801743478 +- kind: conda + name: liblapack + version: 3.9.0 + build: 23_linux64_openblas + build_number: 23 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-23_linux64_openblas.conda + sha256: 25c7aef86c8a1d9db0e8ee61aa7462ba3b46b482027a65d66eb83e3e6f949043 + md5: 2af0879961951987e464722fd00ec1e0 + depends: + - libblas 3.9.0 23_linux64_openblas + constrains: + - liblapacke 3.9.0 23_linux64_openblas + - libcblas 3.9.0 23_linux64_openblas + - blas * openblas + license: BSD-3-Clause + license_family: BSD + size: 14823 + timestamp: 1721688775172 +- kind: conda + name: liblapack + version: 3.9.0 + build: 23_osxarm64_openblas + build_number: 23 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.9.0-23_osxarm64_openblas.conda + sha256: 13799a137ffc80786725e7e2820d37d4c0d59dbb76013a14c21771415b0a4263 + md5: 754ef44f72ab80fd14eaa789ac393a27 + depends: + - libblas 3.9.0 23_osxarm64_openblas + constrains: + - blas * openblas + - liblapacke 3.9.0 23_osxarm64_openblas + - libcblas 3.9.0 23_osxarm64_openblas + license: BSD-3-Clause + license_family: BSD + size: 14999 + timestamp: 1721689026268 +- kind: conda + name: libnsl + version: 2.0.1 + build: hd590300_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda + sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 + md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 + depends: + - libgcc-ng >=12 + license: LGPL-2.1-only + license_family: GPL + size: 33408 + timestamp: 1697359010159 +- kind: conda + name: libopenblas + version: 0.3.27 + build: openmp_h517c56d_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.27-openmp_h517c56d_1.conda + sha256: 46cfcc592b5255262f567cd098be3c61da6bca6c24d640e878dc8342b0f6d069 + md5: 71b8a34d70aa567a990162f327e81505 + depends: + - __osx >=11.0 + - libgfortran 5.* + - libgfortran5 >=12.3.0 + - llvm-openmp >=16.0.6 + constrains: + - openblas >=0.3.27,<0.3.28.0a0 + license: BSD-3-Clause + license_family: BSD + size: 2925328 + timestamp: 1720425811743 +- kind: conda + name: libopenblas + version: 0.3.27 + build: pthreads_hac2b453_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.27-pthreads_hac2b453_1.conda + sha256: 714cb82d7c4620ea2635a92d3df263ab841676c9b183d0c01992767bb2451c39 + md5: ae05ece66d3924ac3d48b4aa3fa96cec + depends: + - libgcc-ng >=12 + - libgfortran-ng + - libgfortran5 >=12.3.0 + constrains: + - openblas >=0.3.27,<0.3.28.0a0 + license: BSD-3-Clause + license_family: BSD + size: 5563053 + timestamp: 1720426334043 +- kind: conda + name: libsodium + version: 1.0.20 + build: h4ab18f5_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda + sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 + md5: a587892d3c13b6621a6091be690dbca2 + depends: + - libgcc-ng >=12 + license: ISC + size: 205978 + timestamp: 1716828628198 +- kind: conda + name: libsodium + version: 1.0.20 + build: h99b78c6_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.20-h99b78c6_0.conda + sha256: fade8223e1e1004367d7101dd17261003b60aa576df6d7802191f8972f7470b1 + md5: a7ce36e284c5faaf93c220dfc39e3abd + depends: + - __osx >=11.0 + license: ISC + size: 164972 + timestamp: 1716828607917 +- kind: conda + name: libsqlite + version: 3.46.1 + build: hadc24fc_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.1-hadc24fc_0.conda + sha256: 9851c049abafed3ee329d6c7c2033407e2fc269d33a75c071110ab52300002b0 + md5: 36f79405ab16bf271edb55b213836dac + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libzlib >=1.3.1,<2.0a0 + license: Unlicense + size: 865214 + timestamp: 1725353659783 +- kind: conda + name: libsqlite + version: 3.46.1 + build: hc14010f_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.46.1-hc14010f_0.conda + sha256: 3725f962f490c5d44dae326d5f5b2e3c97f71a6322d914ccc85b5ddc2e50d120 + md5: 58050ec1724e58668d0126a1615553fa + depends: + - __osx >=11.0 + - libzlib >=1.3.1,<2.0a0 + license: Unlicense + size: 829500 + timestamp: 1725353720793 +- kind: conda + name: libstdcxx + version: 14.1.0 + build: hc0a3c3a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.1.0-hc0a3c3a_1.conda + sha256: 44decb3d23abacf1c6dd59f3c152a7101b7ca565b4ef8872804ceaedcc53a9cd + md5: 9dbb9699ea467983ba8a4ba89b08b066 + depends: + - libgcc 14.1.0 h77fa898_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 3892781 + timestamp: 1724801863728 +- kind: conda + name: libstdcxx-ng + version: 14.1.0 + build: h4852527_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-h4852527_1.conda + sha256: a2dc44f97290740cc187bfe94ce543e6eb3c2ea8964d99f189a1d8c97b419b8c + md5: bd2598399a70bb86d8218e95548d735e + depends: + - libstdcxx 14.1.0 hc0a3c3a_1 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 52219 + timestamp: 1724801897766 +- kind: conda + name: libuuid + version: 2.38.1 + build: h0b41bf4_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 + md5: 40b61aab5c7ba9ff276c41cfffe6b80b + depends: + - libgcc-ng >=12 + license: BSD-3-Clause + license_family: BSD + size: 33601 + timestamp: 1680112270483 +- kind: conda + name: libxcrypt + version: 4.4.36 + build: hd590300_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + size: 100393 + timestamp: 1702724383534 +- kind: conda + name: libzlib + version: 1.3.1 + build: h4ab18f5_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda + sha256: adf6096f98b537a11ae3729eaa642b0811478f0ea0402ca67b5108fe2cb0010d + md5: 57d7dc60e9325e3de37ff8dffd18e814 + depends: + - libgcc-ng >=12 + constrains: + - zlib 1.3.1 *_1 + license: Zlib + license_family: Other + size: 61574 + timestamp: 1716874187109 +- kind: conda + name: libzlib + version: 1.3.1 + build: hfb2fe0b_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-hfb2fe0b_1.conda + sha256: c34365dd37b0eab27b9693af32a1f7f284955517c2cc91f1b88a7ef4738ff03e + md5: 636077128927cf79fd933276dc3aed47 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.1 *_1 + license: Zlib + license_family: Other + size: 46921 + timestamp: 1716874262512 +- kind: conda + name: llvm-openmp + version: 18.1.8 + build: hde57baf_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-18.1.8-hde57baf_1.conda + sha256: 7a76e2932ac77e6314bfa1c4ff83f617c8260313bfed1b8401b508ed3e9d70ba + md5: fe89757e3cd14bb1c6ebd68dac591363 + depends: + - __osx >=11.0 + constrains: + - openmp 18.1.8|18.1.8.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + size: 276263 + timestamp: 1723605341828 +- kind: conda + name: max + version: 24.5.0 + build: release + subdir: noarch + noarch: python + url: https://conda.modular.com/max/noarch/max-24.5.0-release.conda + sha256: 3050d7885a304944afbf93ca9786e56e6df20f0685e1705f88fab045fb5aae70 + md5: 662a61803cd141e857d3b9f821c7bd66 + depends: + - max-core ==24.5.0 release + - max-python >=24.5.0,<25.0a0 + - mojo-jupyter ==24.5.0 release + - mblack ==24.5.0 release + size: 9642 + timestamp: 1726172475909 +- kind: conda + name: max-core + version: 24.5.0 + build: release + subdir: linux-64 + url: https://conda.modular.com/max/linux-64/max-core-24.5.0-release.conda + sha256: 4cd4ab217863a500e9df8112d5e4c335192baa4f527aaaacb925b7818dd2bbe1 + md5: a9b3f9d69310032f687789c475c029f5 + depends: + - mblack ==24.5.0 release + arch: x86_64 + platform: linux + size: 284994357 + timestamp: 1726172475907 +- kind: conda + name: max-core + version: 24.5.0 + build: release + subdir: osx-arm64 + url: https://conda.modular.com/max/osx-arm64/max-core-24.5.0-release.conda + sha256: 8848071dde1f98a4da8e39c90f9210098e7c3c4aaddd0e2255fd9fe1f01df0b7 + md5: fba502bf5142da57735a593ccf35a255 + depends: + - mblack ==24.5.0 release + arch: arm64 + platform: osx + size: 244231803 + timestamp: 1726175523753 +- kind: conda + name: max-python + version: 24.5.0 + build: 3.12release + subdir: linux-64 + url: https://conda.modular.com/max/linux-64/max-python-24.5.0-3.12release.conda + sha256: b5b0f36bb4c91bdff229fc680d7d2e4dd183e9dc90808869408e5883d95199ba + md5: e8dbea1cf138f97c022103a4b41c77bd + depends: + - max-core ==24.5.0 release + - python 3.12.* + - numpy >=1.18,<2.0 + - python_abi 3.12.* *_cp312 + arch: x86_64 + platform: linux + size: 138310039 + timestamp: 1726172475912 +- kind: conda + name: max-python + version: 24.5.0 + build: 3.12release + subdir: osx-arm64 + url: https://conda.modular.com/max/osx-arm64/max-python-24.5.0-3.12release.conda + sha256: e6cdd0477236d49d4f6586d4a66ffe1c5e5cb188535a8ec09ed742eda12cbf5f + md5: f33d8f4cc5c17d893fdb5d6e162c08c6 + depends: + - max-core ==24.5.0 release + - python 3.12.* + - numpy >=1.18,<2.0 + - python_abi 3.12.* *_cp312 + arch: arm64 + platform: osx + size: 125388933 + timestamp: 1726175523755 +- kind: conda + name: mblack + version: 24.5.0 + build: release + subdir: noarch + noarch: python + url: https://conda.modular.com/max/noarch/mblack-24.5.0-release.conda + sha256: 913881fc3aa19db447ed82e898f261a413be9129dc43b9ea600e06030f76dbd5 + md5: 2bc6ce9f257235686dc1b2509cc7198d + depends: + - python >=3.9,<3.13 + - click >=8.0.0 + - mypy_extensions >=0.4.3 + - packaging >=22.0 + - pathspec >=0.9.0 + - platformdirs >=2 + - python + license: MIT + size: 130435 + timestamp: 1726172475910 +- kind: conda + name: mojo-jupyter + version: 24.5.0 + build: release + subdir: noarch + noarch: python + url: https://conda.modular.com/max/noarch/mojo-jupyter-24.5.0-release.conda + sha256: dff2e857eae32ce92fde12a712756d647f0aa312aeb5d79b350b2acbc71a2f96 + md5: 3b7be5cbff5b8015b095e950506be4b3 + depends: + - max-core ==24.5.0 release + - python >=3.9,<3.13 + - jupyter_client >=8.6.2,<8.7 + - python + size: 21595 + timestamp: 1726172475911 +- kind: conda + name: mypy_extensions + version: 1.0.0 + build: pyha770c72_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/mypy_extensions-1.0.0-pyha770c72_0.conda + sha256: f240217476e148e825420c6bc3a0c0efb08c0718b7042fae960400c02af858a3 + md5: 4eccaeba205f0aed9ac3a9ea58568ca3 + depends: + - python >=3.5 + license: MIT + license_family: MIT + size: 10492 + timestamp: 1675543414256 +- kind: conda + name: ncurses + version: '6.5' + build: h7bae524_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda + sha256: 27d0b9ff78ad46e1f3a6c96c479ab44beda5f96def88e2fe626e0a49429d8afc + md5: cb2b0ea909b97b3d70cd3921d1445e1a + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + size: 802321 + timestamp: 1724658775723 +- kind: conda + name: ncurses + version: '6.5' + build: he02047a_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + sha256: 6a1d5d8634c1a07913f1c525db6455918cbc589d745fac46d9d6e30340c8731a + md5: 70caf8bb6cf39a0b6b7efc885f51c0fe + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: X11 AND BSD-3-Clause + size: 889086 + timestamp: 1724658547447 +- kind: conda + name: numpy + version: 1.26.4 + build: py312h8442bc7_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-1.26.4-py312h8442bc7_0.conda + sha256: c8841d6d6f61fd70ca80682efbab6bdb8606dc77c68d8acabfbd7c222054f518 + md5: d83fc83d589e2625a3451c9a7e21047c + depends: + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=16 + - liblapack >=3.9.0,<4.0a0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + size: 6073136 + timestamp: 1707226249608 +- kind: conda + name: numpy + version: 1.26.4 + build: py312heda63a1_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda + sha256: fe3459c75cf84dcef6ef14efcc4adb0ade66038ddd27cadb894f34f4797687d8 + md5: d8285bea2a350f63fab23bf460221f3f + depends: + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libgcc-ng >=12 + - liblapack >=3.9.0,<4.0a0 + - libstdcxx-ng >=12 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + size: 7484186 + timestamp: 1707225809722 +- kind: conda + name: openssl + version: 3.3.2 + build: h8359307_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.3.2-h8359307_0.conda + sha256: 940fa01c4dc6152158fe8943e05e55a1544cab639df0994e3b35937839e4f4d1 + md5: 1773ebccdc13ec603356e8ff1db9e958 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + size: 2882450 + timestamp: 1725410638874 +- kind: conda + name: openssl + version: 3.3.2 + build: hb9d3cd8_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.2-hb9d3cd8_0.conda + sha256: cee91036686419f6dd6086902acf7142b4916e1c4ba042e9ca23e151da012b6d + md5: 4d638782050ab6faa27275bed57e9b4e + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=13 + license: Apache-2.0 + license_family: Apache + size: 2891789 + timestamp: 1725410790053 +- kind: conda + name: packaging + version: '24.1' + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda + sha256: 36aca948219e2c9fdd6d80728bcc657519e02f06c2703d8db3446aec67f51d81 + md5: cbe1bb1f21567018ce595d9c2be0f0db + depends: + - python >=3.8 + license: Apache-2.0 + license_family: APACHE + size: 50290 + timestamp: 1718189540074 +- kind: conda + name: pathspec + version: 0.12.1 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/pathspec-0.12.1-pyhd8ed1ab_0.conda + sha256: 4e534e66bfe8b1e035d2169d0e5b185450546b17e36764272863e22e0370be4d + md5: 17064acba08d3686f1135b5ec1b32b12 + depends: + - python >=3.7 + license: MPL-2.0 + license_family: MOZILLA + size: 41173 + timestamp: 1702250135032 +- kind: conda + name: platformdirs + version: 4.3.2 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.2-pyhd8ed1ab_0.conda + sha256: 3aef5bb863a2db94e47272fd5ec5a5e4b240eafba79ebb9df7a162797cf035a3 + md5: e1a2dfcd5695f0744f1bcd3bbfe02523 + depends: + - python >=3.8 + license: MIT + license_family: MIT + size: 20623 + timestamp: 1725821846879 +- kind: conda + name: python + version: 3.12.5 + build: h2ad013b_0_cpython + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.5-h2ad013b_0_cpython.conda + sha256: e2aad83838988725d4ffba4e9717b9328054fd18a668cff3377e0c50f109e8bd + md5: 9c56c4df45f6571b13111d8df2448692 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.6.2,<3.0a0 + - libffi >=3.4,<4.0a0 + - libgcc-ng >=12 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.46.0,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.3.1,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 31663253 + timestamp: 1723143721353 +- kind: conda + name: python + version: 3.12.6 + build: h739c21a_0_cpython + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.12.6-h739c21a_0_cpython.conda + sha256: 7dc75f4a7f800426e39ba219a1202c00b002cd0c792e34e077d3d7c145ef0199 + md5: 1d0f564edfc8121b35a4dc2d25b62863 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.6.3,<3.0a0 + - libffi >=3.4,<4.0a0 + - libsqlite >=3.46.1,<4.0a0 + - libzlib >=1.3.1,<2.0a0 + - ncurses >=6.5,<7.0a0 + - openssl >=3.3.2,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 12877861 + timestamp: 1726030796871 +- kind: conda + name: python-dateutil + version: 2.9.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda + sha256: f3ceef02ac164a8d3a080d0d32f8e2ebe10dd29e3a685d240e38b3599e146320 + md5: 2cf4264fffb9e6eff6031c5b6884d61c + depends: + - python >=3.7 + - six >=1.5 + license: Apache-2.0 + license_family: APACHE + size: 222742 + timestamp: 1709299922152 +- kind: conda + name: python_abi + version: '3.12' + build: 5_cp312 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + sha256: d10e93d759931ffb6372b45d65ff34d95c6000c61a07e298d162a3bc2accebb0 + md5: 0424ae29b104430108f5218a66db7260 + constrains: + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD + size: 6238 + timestamp: 1723823388266 +- kind: conda + name: python_abi + version: '3.12' + build: 5_cp312 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/python_abi-3.12-5_cp312.conda + sha256: 49d624e4b809c799d2bf257b22c23cf3fc4460f5570d9a58e7ad86350aeaa1f4 + md5: b76f9b1c862128e56ac7aa8cd2333de9 + constrains: + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD + size: 6278 + timestamp: 1723823099686 +- kind: conda + name: pyzmq + version: 26.2.0 + build: py312hbf22597_2 + build_number: 2 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-26.2.0-py312hbf22597_2.conda + sha256: a2431644cdef4111f7120565090114f52897e687e83c991bd76a3baef8de77c4 + md5: 44f46ddfdd01d242d2fff2d69a0d7cba + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libsodium >=1.0.20,<1.0.21.0a0 + - libstdcxx >=13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - zeromq >=4.3.5,<4.4.0a0 + license: BSD-3-Clause + license_family: BSD + size: 378667 + timestamp: 1725449078945 +- kind: conda + name: pyzmq + version: 26.2.0 + build: py312hc6335d2_2 + build_number: 2 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-26.2.0-py312hc6335d2_2.conda + sha256: 8d46c0f1af50989f308b9da68e6123bc3560f3a3a741b4e7cb8867c603b5a9f1 + md5: ca61d76f24d66c2938af62e882c9a02d + depends: + - __osx >=11.0 + - libcxx >=17 + - libsodium >=1.0.20,<1.0.21.0a0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + - zeromq >=4.3.5,<4.4.0a0 + license: BSD-3-Clause + license_family: BSD + size: 359594 + timestamp: 1725449428595 +- kind: conda + name: readline + version: '8.2' + build: h8228510_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 + md5: 47d31b792659ce70f470b5c82fdfb7a4 + depends: + - libgcc-ng >=12 + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 281456 + timestamp: 1679532220005 +- kind: conda + name: readline + version: '8.2' + build: h92ec313_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.2-h92ec313_1.conda + sha256: a1dfa679ac3f6007362386576a704ad2d0d7a02e98f5d0b115f207a2da63e884 + md5: 8cbb776a2f641b943d413b3e19df71f4 + depends: + - ncurses >=6.3,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 250351 + timestamp: 1679532511311 +- kind: conda + name: six + version: 1.16.0 + build: pyh6c4a22f_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 + sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 + md5: e5f25f8dbc060e9a8d912e432202afc2 + depends: + - python + license: MIT + license_family: MIT + size: 14259 + timestamp: 1620240338595 +- kind: conda + name: small_time + version: 0.1.3 + build: h60d57d3_0 + subdir: osx-arm64 + url: https://repo.prefix.dev/mojo-community/osx-arm64/small_time-0.1.3-h60d57d3_0.conda + sha256: 8f08a189fa15d96e6dc8b0cc49aecaefe9caf5a8209ca3ab5d2da91b7e13a3ba + depends: + - max >=24.5.0,<25 + arch: arm64 + platform: osx + license: MIT + size: 404869 + timestamp: 1726265944269 +- kind: conda + name: small_time + version: 0.1.3 + build: hb0f4dca_0 + subdir: linux-64 + url: https://repo.prefix.dev/mojo-community/linux-64/small_time-0.1.3-hb0f4dca_0.conda + sha256: 6af5090414abb697ab570f48362ed2910b7188ee6df75ba4d7682d448428675f + depends: + - max >=24.5.0,<25 + arch: x86_64 + platform: linux + license: MIT + size: 404857 + timestamp: 1726266228925 +- kind: conda + name: tk + version: 8.6.13 + build: h5083fa2_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda + sha256: 72457ad031b4c048e5891f3f6cb27a53cb479db68a52d965f796910e71a403a8 + md5: b50a57ba89c32b62428b71a875291c9b + depends: + - libzlib >=1.2.13,<2.0.0a0 + license: TCL + license_family: BSD + size: 3145523 + timestamp: 1699202432999 +- kind: conda + name: tk + version: 8.6.13 + build: noxft_h4845f30_101 + build_number: 101 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e + md5: d453b98d9c83e71da0741bb0ff4d76bc + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: TCL + license_family: BSD + size: 3318875 + timestamp: 1699202167581 +- kind: conda + name: tornado + version: 6.4.1 + build: py312h024a12e_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.4.1-py312h024a12e_1.conda + sha256: 5eefede1d8a2f55892bc582dbcb574b1806f19bc1e3939ce56b79721b9406db7 + md5: 967bc97bb9e258993289546479af971f + depends: + - __osx >=11.0 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: Apache-2.0 + license_family: Apache + size: 841722 + timestamp: 1724956439106 +- kind: conda + name: tornado + version: 6.4.1 + build: py312h66e93f0_1 + build_number: 1 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.4.1-py312h66e93f0_1.conda + sha256: c0c9cc7834e8f43702956afaa5af7b0639c4835c285108a43e6b91687ce53ab8 + md5: af648b62462794649066366af4ecd5b0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: Apache-2.0 + license_family: Apache + size: 837665 + timestamp: 1724956252424 +- kind: conda + name: traitlets + version: 5.14.3 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.14.3-pyhd8ed1ab_0.conda + sha256: 8a64fa0f19022828513667c2c7176cfd125001f3f4b9bc00d33732e627dd2592 + md5: 3df84416a021220d8b5700c613af2dc5 + depends: + - python >=3.8 + license: BSD-3-Clause + license_family: BSD + size: 110187 + timestamp: 1713535244513 +- kind: conda + name: tzdata + version: 2024a + build: h8827d51_1 + build_number: 1 + subdir: noarch + noarch: generic + url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h8827d51_1.conda + sha256: 7d21c95f61319dba9209ca17d1935e6128af4235a67ee4e57a00908a1450081e + md5: 8bfdead4e0fff0383ae4c9c50d0531bd + license: LicenseRef-Public-Domain + size: 124164 + timestamp: 1724736371498 +- kind: conda + name: xz + version: 5.2.6 + build: h166bdaf_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 + sha256: 03a6d28ded42af8a347345f82f3eebdd6807a08526d47899a42d62d319609162 + md5: 2161070d867d1b1204ea749c8eec4ef0 + depends: + - libgcc-ng >=12 + license: LGPL-2.1 and GPL-2.0 + size: 418368 + timestamp: 1660346797927 +- kind: conda + name: xz + version: 5.2.6 + build: h57fd34a_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/xz-5.2.6-h57fd34a_0.tar.bz2 + sha256: 59d78af0c3e071021cfe82dc40134c19dab8cdf804324b62940f5c8cd71803ec + md5: 39c6b54e94014701dd157f4f576ed211 + license: LGPL-2.1 and GPL-2.0 + size: 235693 + timestamp: 1660346961024 +- kind: conda + name: zeromq + version: 4.3.5 + build: h64debc3_5 + build_number: 5 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h64debc3_5.conda + sha256: b4ba544a04129472651a5df3b8906ed68e7f43bf23e724fd0e368218083c920c + md5: c29dbe9343a0b55b027fa645644c59d9 + depends: + - __osx >=11.0 + - krb5 >=1.21.3,<1.22.0a0 + - libcxx >=17 + - libsodium >=1.0.20,<1.0.21.0a0 + license: MPL-2.0 + license_family: MOZILLA + size: 296355 + timestamp: 1725430145243 +- kind: conda + name: zeromq + version: 4.3.5 + build: ha4adb4c_5 + build_number: 5 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-ha4adb4c_5.conda + sha256: dd48adc07fcd029c86fbf82e68d0e4818c7744b768e08139379920b56b582814 + md5: e8372041ebb377237db9d0d24c7b5962 + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.21.3,<1.22.0a0 + - libgcc >=13 + - libsodium >=1.0.20,<1.0.21.0a0 + - libstdcxx >=13 + license: MPL-2.0 + license_family: MOZILLA + size: 353159 + timestamp: 1725429777124 +- kind: conda + name: zipp + version: 3.20.2 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.20.2-pyhd8ed1ab_0.conda + sha256: 1e84fcfa41e0afdd87ff41e6fbb719c96a0e098c1f79be342293ab0bd8dea322 + md5: 4daaed111c05672ae669f7036ee5bba3 + depends: + - python >=3.8 + license: MIT + license_family: MIT + size: 21409 + timestamp: 1726248679175 diff --git a/mojoproject.toml b/mojoproject.toml new file mode 100644 index 00000000..6f9a6abe --- /dev/null +++ b/mojoproject.toml @@ -0,0 +1,19 @@ +[project] +authors = ["saviorand"] +channels = ["conda-forge", "https://conda.modular.com/max", "https://repo.prefix.dev/mojo-community"] +description = "Simple and fast HTTP framework for Mojo!" +name = "lightbug_http" +platforms = ["osx-arm64", "linux-64"] +version = "0.1.4" + +[tasks] +build = { cmd = "rattler-build build --recipe recipes -c https://conda.modular.com/max -c conda-forge --skip-existing=all", env = {MODULAR_MOJO_IMPORT_PATH = "$CONDA_PREFIX/lib/mojo"} } +publish = { cmd = "bash scripts/publish.sh", env = { PREFIX_API_KEY = "$PREFIX_API_KEY" } } +test = { cmd = "magic run mojo run_tests.mojo" } +bench = { cmd = "magic run mojo bench.mojo" } +bench_server = { cmd = "magic run mojo build bench_server.mojo && ./bench_server ; rm bench_server" } + +[dependencies] +max = ">=24.5.0,<25" +gojo = "0.1.9" +small_time = "0.1.3" \ No newline at end of file diff --git a/recipes/recipe.yaml b/recipes/recipe.yaml new file mode 100644 index 00000000..7cf9e8c6 --- /dev/null +++ b/recipes/recipe.yaml @@ -0,0 +1,30 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/prefix-dev/recipe-format/main/schema.json + +context: + version: "13.4.2" + +package: + name: "lightbug_http" + version: 0.1.4 + +source: + - path: ../lightbug_http + - path: ../LICENSE + +build: + script: + - mkdir -p ${PREFIX}/lib/mojo + - magic run mojo package . -o ${PREFIX}/lib/mojo/lightbug_http.mojopkg + +requirements: + run: + - max >=24.5.0 + - gojo == 0.1.9 + - small_time == 0.1.3 + +about: + homepage: https://github.com/saviorand/lightbug_http + license: MIT + license_file: LICENSE + summary: Lightbug is a simple and sweet HTTP framework for Mojo + repository: https://github.com/saviorand/lightbug_http diff --git a/run_tests.mojo b/run_tests.mojo index 5d3411c3..ccfd1244 100644 --- a/run_tests.mojo +++ b/run_tests.mojo @@ -2,12 +2,12 @@ from tests.test_io import test_io from tests.test_http import test_http from tests.test_header import test_header from tests.test_uri import test_uri -# from lightbug_http.test.test_client import test_client +from tests.test_client import test_client + fn main() raises: test_io() test_http() test_header() test_uri() - # test_client() - + test_client() diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 00000000..e081260c --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# ignore errors because we want to ignore duplicate packages +for file in $CONDA_BLD_PATH/**/*.conda; do + magic run rattler-build upload prefix -c "mojo-community" "$file" || true +done + +rm $CONDA_BLD_PATH/**/*.conda \ No newline at end of file diff --git a/tests/test_client.mojo b/tests/test_client.mojo index 47c8059b..5ca9717f 100644 --- a/tests/test_client.mojo +++ b/tests/test_client.mojo @@ -1,67 +1,31 @@ -from external.gojo.tests.wrapper import MojoTest -from external.morrow import Morrow +import testing from tests.utils import ( default_server_conn_string, - getRequest, ) -from lightbug_http.python.client import PythonClient from lightbug_http.sys.client import MojoClient from lightbug_http.http import HTTPRequest, encode from lightbug_http.uri import URI -from lightbug_http.header import RequestHeader +from lightbug_http.header import Header, Headers from lightbug_http.io.bytes import bytes def test_client(): var mojo_client = MojoClient() - var py_client = PythonClient() test_mojo_client_lightbug_external_req(mojo_client) - test_python_client_lightbug(py_client) - - -fn test_mojo_client_lightbug(client: MojoClient) raises: - var test = MojoTest("test_mojo_client_lightbug") - var res = client.do( - HTTPRequest( - URI(default_server_conn_string), - bytes("Hello world!"), - RequestHeader(getRequest), - ) - ) - test.assert_equal( - String(res.body_raw[0:112]), - String( - "HTTP/1.1 200 OK\r\nServer: lightbug_http\r\nContent-Type:" - " text/plain\r\nContent-Length: 12\r\nConnection: close\r\nDate: " - ), - ) fn test_mojo_client_lightbug_external_req(client: MojoClient) raises: - var test = MojoTest("test_mojo_client_lightbug_external_req") var req = HTTPRequest( - URI("http://grandinnerastoundingspell.neverssl.com/online/"), + uri=URI.parse("http://httpbin.org/status/200")[URI], + headers=Headers( + Header("Connection", "keep-alive"), + Header("Host", "httpbin.org")), + method="GET", ) + try: var res = client.do(req) - test.assert_equal(res.header.status_code(), 200) + testing.assert_equal(res.status_code, 200) + except e: print(e) - - -fn test_python_client_lightbug(client: PythonClient) raises: - var test = MojoTest("test_python_client_lightbug") - var res = client.do( - HTTPRequest( - URI(default_server_conn_string), - bytes("Hello world!"), - RequestHeader(getRequest), - ) - ) - test.assert_equal( - String(res.body_raw[0:112]), - String( - "HTTP/1.1 200 OK\r\nServer: lightbug_http\r\nContent-Type:" - " text/plain\r\nContent-Length: 12\r\nConnection: close\r\nDate: " - ), - ) diff --git a/tests/test_header.mojo b/tests/test_header.mojo index 57c265f7..eed820f8 100644 --- a/tests/test_header.mojo +++ b/tests/test_header.mojo @@ -1,47 +1,63 @@ -from external.gojo.tests.wrapper import MojoTest -from external.gojo.bytes import buffer -from external.gojo.bufio import Reader -from lightbug_http.header import RequestHeader, ResponseHeader +from testing import assert_equal, assert_true +from lightbug_http.utils import ByteReader +from lightbug_http.header import Headers, Header from lightbug_http.io.bytes import Bytes, bytes -from lightbug_http.strings import empty_string +from lightbug_http.strings import empty_string from lightbug_http.net import default_buffer_size + def test_header(): test_parse_request_header() test_parse_response_header() + test_header_case_insensitive() + + +def test_header_case_insensitive(): + var headers = Headers(Header("Host", "SomeHost")) + assert_true("host" in headers) + assert_true("HOST" in headers) + assert_true("hOST" in headers) + assert_equal(headers["Host"], "SomeHost") + assert_equal(headers["host"], "SomeHost") + def test_parse_request_header(): - var test = MojoTest("test_parse_request_header") - var headers_str = bytes('''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''') - var header = RequestHeader() + var headers_str = bytes( + """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""" + ) + var header = Headers() var b = Bytes(headers_str) - var buf = buffer.new_buffer(b^) - var reader = Reader(buf^) - _ = header.parse_raw(reader) - test.assert_equal(String(header.request_uri()), "/index.html") - test.assert_equal(String(header.protocol()), "HTTP/1.1") - test.assert_equal(header.no_http_1_1, False) - test.assert_equal(String(header.host()), String("example.com")) - test.assert_equal(String(header.user_agent()), "Mozilla/5.0") - test.assert_equal(String(header.content_type()), "text/html") - test.assert_equal(header.content_length(), 1234) - test.assert_equal(header.connection_close(), True) + var reader = ByteReader(b^) + var method: String + var protocol: String + var uri: String + method, uri, protocol = header.parse_raw(reader) + assert_equal(uri, "/index.html") + assert_equal(protocol, "HTTP/1.1") + assert_equal(method, "GET") + assert_equal(header["Host"], "example.com") + assert_equal(header["User-Agent"], "Mozilla/5.0") + assert_equal(header["Content-Type"], "text/html") + assert_equal(header["Content-Length"], "1234") + assert_equal(header["Connection"], "close") + def test_parse_response_header(): - var test = MojoTest("test_parse_response_header") - var headers_str = bytes('''HTTP/1.1 200 OK\r\nServer: example.com\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/html\r\nContent-Encoding: gzip\r\nContent-Length: 1234\r\nConnection: close\r\nTrailer: end-of-message\r\n\r\n''') - var header = ResponseHeader() - var b = Bytes(headers_str) - var buf = buffer.new_buffer(b^) - var reader = Reader(buf^) - _ = header.parse_raw(reader) - test.assert_equal(String(header.protocol()), "HTTP/1.1") - test.assert_equal(header.no_http_1_1, False) - test.assert_equal(header.status_code(), 200) - test.assert_equal(String(header.status_message()), "OK") - test.assert_equal(String(header.server()), "example.com") - test.assert_equal(String(header.content_type()), "text/html") - test.assert_equal(String(header.content_encoding()), "gzip") - test.assert_equal(header.content_length(), 1234) - test.assert_equal(header.connection_close(), True) - test.assert_equal(header.trailer_str(), "end-of-message") + var headers_str = bytes( + """HTTP/1.1 200 OK\r\nServer: example.com\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/html\r\nContent-Encoding: gzip\r\nContent-Length: 1234\r\nConnection: close\r\nTrailer: end-of-message\r\n\r\n""" + ) + var header = Headers() + var protocol: String + var status_code: String + var status_text: String + var reader = ByteReader(headers_str^) + protocol, status_code, status_text = header.parse_raw(reader) + assert_equal(protocol, "HTTP/1.1") + assert_equal(status_code, "200") + assert_equal(status_text, "OK") + assert_equal(header["Server"], "example.com") + assert_equal(header["Content-Type"], "text/html") + assert_equal(header["Content-Encoding"], "gzip") + assert_equal(header["Content-Length"], "1234") + assert_equal(header["Connection"], "close") + assert_equal(header["Trailer"], "end-of-message") diff --git a/tests/test_http.mojo b/tests/test_http.mojo index f5f85400..c7f40d5d 100644 --- a/tests/test_http.mojo +++ b/tests/test_http.mojo @@ -1,65 +1,44 @@ -from external.gojo.tests.wrapper import MojoTest +import testing +from collections import Dict, List from lightbug_http.io.bytes import Bytes, bytes -from lightbug_http.http import HTTPRequest, HTTPResponse, split_http_string, encode -from lightbug_http.header import RequestHeader +from lightbug_http.http import HTTPRequest, HTTPResponse, encode +from lightbug_http.header import Header, Headers, HeaderKey from lightbug_http.uri import URI -from tests.utils import ( - default_server_conn_string, - getRequest, -) +from lightbug_http.strings import to_string +from tests.utils import default_server_conn_string + def test_http(): - test_split_http_string() test_encode_http_request() test_encode_http_response() -def test_split_http_string(): - var test = MojoTest("test_split_http_string") - var cases = Dict[StringLiteral, List[StringLiteral]]() - - cases["GET /index.html HTTP/1.1\r\nHost: www.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\nHello, World!\0"] = - List("GET /index.html HTTP/1.1", - "Host: www.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", - "Hello, World!") - - for c in cases.items(): - var buf = bytes((c[].key)) - request_first_line, request_headers, request_body = split_http_string(buf) - test.assert_equal(request_first_line, c[].value[0]) - test.assert_equal(request_headers, String(c[].value[1])) - test.assert_equal(request_body, c[].value[2]) def test_encode_http_request(): - var test = MojoTest("test_encode_http_request") - var uri = URI(default_server_conn_string) + var uri = URI(default_server_conn_string + "/foobar?baz") var req = HTTPRequest( - uri, - String("Hello world!").as_bytes(), - RequestHeader(getRequest), - ) + uri, + body=String("Hello world!").as_bytes(), + headers=Headers(Header("Connection", "keep-alive")), + ) + + var as_str = str(req) + var req_encoded = to_string(encode(req^)) + testing.assert_equal( + req_encoded, + ( + "GET / HTTP/1.1\r\nconnection: keep-alive\r\ncontent-length:" + " 12\r\n\r\nHello world!" + ), + ) + testing.assert_equal(req_encoded, as_str) - var req_encoded = encode(req) - test.assert_equal(String(req_encoded), "GET / HTTP/1.1\r\nContent-Length: 12\r\nConnection: keep-alive\r\n\r\nHello world!") def test_encode_http_response(): - var test = MojoTest("test_encode_http_response") - var res = HTTPResponse( - bytes("Hello, World!"), - ) + var res = HTTPResponse(bytes("Hello, World!")) + res.headers[HeaderKey.DATE] = "2024-06-02T13:41:50.766880+00:00" + var as_str = str(res) + var res_encoded = to_string(encode(res^)) + var expected_full = "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\nHello, World!" - var res_encoded = encode(res) - var res_str = String(res_encoded) - - # Since we cannot compare the exact date, we will only compare the headers until the date and the body - var expected_full = "HTTP/1.1 200 OK\r\nServer: lightbug_http\r\nContent-Type: application/octet-stream\r\nContent-Length: 13\r\nConnection: keep-alive\r\nDate: 2024-06-02T13:41:50.766880+00:00\r\n\r\nHello, World!" - - var expected_headers_len = 124 - var hello_world_len = len(String("Hello, World!")) - 1 # -1 for the null terminator - var date_header_len = len(String("Date: 2024-06-02T13:41:50.766880+00:00")) - - var expected_split = String(expected_full).split("\r\n\r\n") - var expected_headers = expected_split[0] - var expected_body = expected_split[1] - - test.assert_equal(res_str[:expected_headers_len], expected_headers[:len(expected_headers) - date_header_len]) - test.assert_equal(res_str[(len(res_str) - hello_world_len):len(res_str) + 1], expected_body) \ No newline at end of file + testing.assert_equal(res_encoded, expected_full) + testing.assert_equal(res_encoded, as_str) diff --git a/tests/test_io.mojo b/tests/test_io.mojo index 93363c34..9520659a 100644 --- a/tests/test_io.mojo +++ b/tests/test_io.mojo @@ -1,31 +1,41 @@ -from external.gojo.tests.wrapper import MojoTest +import testing +from collections import Dict, List from lightbug_http.io.bytes import Bytes, bytes_equal, bytes + def test_io(): test_string_literal_to_bytes() + fn test_string_literal_to_bytes() raises: - var test = MojoTest("test_string_to_bytes") var cases = Dict[StringLiteral, Bytes]() cases[""] = Bytes() - cases["Hello world!"] = List[UInt8](72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33) + cases["Hello world!"] = List[UInt8]( + 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 + ) cases["\0"] = List[UInt8](0) cases["\0\0\0\0"] = List[UInt8](0, 0, 0, 0) cases["OK"] = List[UInt8](79, 75) - cases["HTTP/1.1 200 OK"] = List[UInt8](72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75) - + cases["HTTP/1.1 200 OK"] = List[UInt8]( + 72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75 + ) + for c in cases.items(): - test.assert_true(bytes_equal(bytes(c[].key), c[].value)) - + testing.assert_true(bytes_equal(bytes(c[].key), c[].value)) + + fn test_string_to_bytes() raises: - var test = MojoTest("test_string_to_bytes") var cases = Dict[String, Bytes]() cases[String("")] = Bytes() - cases[String("Hello world!")] = List[UInt8](72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33) + cases[String("Hello world!")] = List[UInt8]( + 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 + ) cases[String("\0")] = List[UInt8](0) cases[String("\0\0\0\0")] = List[UInt8](0, 0, 0, 0) cases[String("OK")] = List[UInt8](79, 75) - cases[String("HTTP/1.1 200 OK")] = List[UInt8](72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75) + cases[String("HTTP/1.1 200 OK")] = List[UInt8]( + 72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75 + ) for c in cases.items(): - test.assert_true(bytes_equal(bytes(c[].key), c[].value)) \ No newline at end of file + testing.assert_true(bytes_equal(bytes(c[].key), c[].value)) diff --git a/tests/test_net.mojo b/tests/test_net.mojo index 23b91c3f..2a4d241b 100644 --- a/tests/test_net.mojo +++ b/tests/test_net.mojo @@ -1,6 +1,7 @@ - def test_net(): test_split_host_port() + def test_split_host_port(): - ... \ No newline at end of file + # TODO: Implement this test + ... diff --git a/tests/test_uri.mojo b/tests/test_uri.mojo index 3af3df30..1c4a8a14 100644 --- a/tests/test_uri.mojo +++ b/tests/test_uri.mojo @@ -1,8 +1,10 @@ -from external.gojo.tests.wrapper import MojoTest +from utils import StringSlice +import testing from lightbug_http.uri import URI -from lightbug_http.strings import empty_string +from lightbug_http.strings import empty_string, to_string from lightbug_http.io.bytes import Bytes + def test_uri(): test_uri_no_parse_defaults() test_uri_parse_http_with_port() @@ -13,101 +15,87 @@ def test_uri(): test_uri_parse_http_basic_www() test_uri_parse_http_with_query_string() test_uri_parse_http_with_hash() - test_uri_parse_http_with_query_string_and_hash() + def test_uri_no_parse_defaults(): - var test = MojoTest("test_uri_no_parse_defaults") - var uri = URI("http://example.com") - test.assert_equal(String(uri.full_uri()), "http://example.com") - test.assert_equal(String(uri.scheme()), "http") - test.assert_equal(String(uri.path()), "/") + var uri = URI.parse("http://example.com")[URI] + testing.assert_equal(uri.full_uri, "http://example.com") + + testing.assert_equal(uri.scheme, "http") + testing.assert_equal(uri.path, "/") + def test_uri_parse_http_with_port(): - var test = MojoTest("test_uri_parse_http_with_port") - var uri = URI("http://example.com:8080/index.html") - _ = uri.parse() - test.assert_equal(String(uri.scheme()), "http") - test.assert_equal(String(uri.host()), "example.com:8080") - test.assert_equal(String(uri.path()), "/index.html") - test.assert_equal(String(uri.path_original()), "/index.html") - test.assert_equal(String(uri.request_uri()), "/index.html") - test.assert_equal(String(uri.http_version()), "HTTP/1.1") - test.assert_equal(uri.is_http_1_0(), False) - test.assert_equal(uri.is_http_1_1(), True) - test.assert_equal(uri.is_https(), False) - test.assert_equal(uri.is_http(), True) - test.assert_equal(String(uri.query_string()), String(empty_string.as_bytes_slice())) + var uri = URI.parse("http://example.com:8080/index.html")[URI] + testing.assert_equal(uri.scheme, "http") + testing.assert_equal(uri.host, "example.com:8080") + testing.assert_equal(uri.path, "/index.html") + testing.assert_equal(uri.__path_original, "/index.html") + testing.assert_equal(uri.request_uri, "/index.html") + testing.assert_equal(uri.is_https(), False) + testing.assert_equal(uri.is_http(), True) + testing.assert_equal(uri.query_string, empty_string) + def test_uri_parse_https_with_port(): - var test = MojoTest("test_uri_parse_https_with_port") - var uri = URI("https://example.com:8080/index.html") - _ = uri.parse() - test.assert_equal(String(uri.scheme()), "https") - test.assert_equal(String(uri.host()), "example.com:8080") - test.assert_equal(String(uri.path()), "/index.html") - test.assert_equal(String(uri.path_original()), "/index.html") - test.assert_equal(String(uri.request_uri()), "/index.html") - test.assert_equal(uri.is_https(), True) - test.assert_equal(uri.is_http(), False) - test.assert_equal(String(uri.query_string()), String(empty_string.as_bytes_slice())) + var uri = URI.parse("https://example.com:8080/index.html")[URI] + testing.assert_equal(uri.scheme, "https") + testing.assert_equal(uri.host, "example.com:8080") + testing.assert_equal(uri.path, "/index.html") + testing.assert_equal(uri.__path_original, "/index.html") + testing.assert_equal(uri.request_uri, "/index.html") + testing.assert_equal(uri.is_https(), True) + testing.assert_equal(uri.is_http(), False) + testing.assert_equal(uri.query_string, empty_string) + def test_uri_parse_http_with_path(): - var test = MojoTest("test_uri_parse_http_with_path") - uri = URI("http://example.com/index.html") - _ = uri.parse() - test.assert_equal(String(uri.scheme()), "http") - test.assert_equal(String(uri.host()), "example.com") - test.assert_equal(String(uri.path()), "/index.html") - test.assert_equal(String(uri.path_original()), "/index.html") - test.assert_equal(String(uri.request_uri()), "/index.html") - test.assert_equal(uri.is_https(), False) - test.assert_equal(uri.is_http(), True) - test.assert_equal(String(uri.query_string()), String(empty_string.as_bytes_slice())) + var uri = URI.parse("http://example.com/index.html")[URI] + testing.assert_equal(uri.scheme, "http") + testing.assert_equal(uri.host, "example.com") + testing.assert_equal(uri.path, "/index.html") + testing.assert_equal(uri.__path_original, "/index.html") + testing.assert_equal(uri.request_uri, "/index.html") + testing.assert_equal(uri.is_https(), False) + testing.assert_equal(uri.is_http(), True) + testing.assert_equal(uri.query_string, empty_string) + def test_uri_parse_https_with_path(): - var test = MojoTest("test_uri_parse_https_with_path") - uri = URI("https://example.com/index.html") - _ = uri.parse() - test.assert_equal(String(uri.scheme()), "https") - test.assert_equal(String(uri.host()), "example.com") - test.assert_equal(String(uri.path()), "/index.html") - test.assert_equal(String(uri.path_original()), "/index.html") - test.assert_equal(String(uri.request_uri()), "/index.html") - test.assert_equal(uri.is_https(), True) - test.assert_equal(uri.is_http(), False) - test.assert_equal(String(uri.query_string()), String(empty_string.as_bytes_slice())) + var uri = URI.parse("https://example.com/index.html")[URI] + testing.assert_equal(uri.scheme, "https") + testing.assert_equal(uri.host, "example.com") + testing.assert_equal(uri.path, "/index.html") + testing.assert_equal(uri.__path_original, "/index.html") + testing.assert_equal(uri.request_uri, "/index.html") + testing.assert_equal(uri.is_https(), True) + testing.assert_equal(uri.is_http(), False) + testing.assert_equal(uri.query_string, empty_string) + def test_uri_parse_http_basic(): - var test = MojoTest("test_uri_parse_http_basic") - uri = URI("http://example.com") - _ = uri.parse() - test.assert_equal(String(uri.scheme()), "http") - test.assert_equal(String(uri.host()), "example.com") - test.assert_equal(String(uri.path()), "/") - test.assert_equal(String(uri.path_original()), "/") - test.assert_equal(String(uri.http_version()), "HTTP/1.1") - test.assert_equal(String(uri.request_uri()), "/") - test.assert_equal(String(uri.query_string()), String(empty_string.as_bytes_slice())) + var uri = URI.parse("http://example.com")[URI] + testing.assert_equal(uri.scheme, "http") + testing.assert_equal(uri.host, "example.com") + testing.assert_equal(uri.path, "/") + testing.assert_equal(uri.__path_original, "/") + testing.assert_equal(uri.request_uri, "/") + testing.assert_equal(uri.query_string, empty_string) + def test_uri_parse_http_basic_www(): - var test = MojoTest("test_uri_parse_http_basic_www") - uri = URI("http://www.example.com") - _ = uri.parse() - test.assert_equal(String(uri.scheme()), "http") - test.assert_equal(String(uri.host()), "www.example.com") - test.assert_equal(String(uri.path()), "/") - test.assert_equal(String(uri.path_original()), "/") - test.assert_equal(String(uri.request_uri()), "/") - test.assert_equal(String(uri.http_version()), "HTTP/1.1") - test.assert_equal(String(uri.query_string()), String(empty_string.as_bytes_slice())) + var uri = URI.parse("http://www.example.com")[URI] + testing.assert_equal(uri.scheme, "http") + testing.assert_equal(uri.host, "www.example.com") + testing.assert_equal(uri.path, "/") + testing.assert_equal(uri.__path_original, "/") + testing.assert_equal(uri.request_uri, "/") + testing.assert_equal(uri.query_string, empty_string) + def test_uri_parse_http_with_query_string(): ... -def test_uri_parse_http_with_hash(): - ... -def test_uri_parse_http_with_query_string_and_hash(): +def test_uri_parse_http_with_hash(): ... - - diff --git a/tests/utils.mojo b/tests/utils.mojo index b255f315..8cc4bd2e 100644 --- a/tests/utils.mojo +++ b/tests/utils.mojo @@ -2,28 +2,23 @@ from python import Python, PythonObject from lightbug_http.io.bytes import Bytes from lightbug_http.error import ErrorHandler from lightbug_http.uri import URI -from lightbug_http.http import HTTPRequest, HTTPResponse, ResponseHeader +from lightbug_http.http import HTTPRequest, HTTPResponse from lightbug_http.net import Listener, Addr, Connection, TCPAddr from lightbug_http.service import HTTPService, OK from lightbug_http.server import ServerTrait from lightbug_http.client import Client from lightbug_http.io.bytes import bytes +from lightbug_http.header import Headers, Header alias default_server_conn_string = "http://localhost:8080" -alias getRequest = bytes( - "GET /foobar?baz HTTP/1.1\r\nHost: google.com\r\nUser-Agent: aaa/bbb/ccc/ddd/eee" - " Firefox Chrome MSIE Opera\r\n" - + "Referer: http://example.com/aaa?bbb=ccc\r\nCookie: foo=bar; baz=baraz;" - " aa=aakslsdweriwereowriewroire\r\n\r\n" -) - alias defaultExpectedGetResponse = bytes( "HTTP/1.1 200 OK\r\nServer: lightbug_http\r\nContent-Type:" - " text/plain\r\nContent-Length: 12\r\nConnection: close\r\nDate: \r\n\r\nHello" - " world!" + " text/plain\r\nContent-Length: 12\r\nConnection: close\r\nDate:" + " \r\n\r\nHello world!" ) + @parameter fn new_httpx_client() -> PythonObject: try: @@ -33,9 +28,11 @@ fn new_httpx_client() -> PythonObject: print("Could not set up httpx client: " + e.__str__()) return None + fn new_fake_listener(request_count: Int, request: Bytes) -> FakeListener: return FakeListener(request_count, request) + struct ReqInfo: var full_uri: URI var host: String @@ -46,6 +43,7 @@ struct ReqInfo: self.host = host self.is_tls = is_tls + struct FakeClient(Client): """FakeClient doesn't actually send any requests, but it extracts useful information from the input. """ @@ -97,6 +95,7 @@ struct FakeClient(Client): return ReqInfo(full_uri, host, is_tls) + struct FakeServer(ServerTrait): var __listener: FakeListener var __handler: FakeResponder @@ -106,7 +105,10 @@ struct FakeServer(ServerTrait): self.__handler = handler fn __init__( - inout self, addr: String, service: HTTPService, error_handler: ErrorHandler + inout self, + addr: String, + service: HTTPService, + error_handler: ErrorHandler, ): self.__listener = FakeListener() self.__handler = FakeResponder() @@ -114,7 +116,9 @@ struct FakeServer(ServerTrait): fn get_concurrency(self) -> Int: return 1 - fn listen_and_serve(self, address: String, handler: HTTPService) raises -> None: + fn listen_and_serve( + self, address: String, handler: HTTPService + ) raises -> None: ... fn serve(inout self) -> None: @@ -127,14 +131,16 @@ struct FakeServer(ServerTrait): fn serve(self, ln: Listener, handler: HTTPService) raises -> None: ... + @value struct FakeResponder(HTTPService): fn func(self, req: HTTPRequest) raises -> HTTPResponse: - var method = String(req.header.method()) + var method = req.method if method != "GET": raise Error("Did not expect a non-GET request! Got: " + method) return OK(bytes("Hello, world!")) + @value struct FakeConnection(Connection): fn __init__(inout self, laddr: String, raddr: String) raises: @@ -158,6 +164,7 @@ struct FakeConnection(Connection): fn remote_addr(self) raises -> TCPAddr: return TCPAddr() + @value struct FakeListener: var request_count: Int @@ -189,6 +196,7 @@ struct FakeListener: fn addr(self) -> TCPAddr: return TCPAddr() + @value struct TestStruct: var a: String @@ -211,6 +219,7 @@ struct TestStruct: fn set_a_copy(self, a: String) -> Self: return Self(a, self.b) + @value struct TestStructNested: var a: String diff --git a/websocket_test.mojo b/websocket_test.mojo new file mode 100644 index 00000000..2a3df87e --- /dev/null +++ b/websocket_test.mojo @@ -0,0 +1,10 @@ +from lightbug_http.sys.server import SysServer +from lightbug_http.websocket import WebSocketLoop, WebSocketHandshake, WebSocketPrinter, send_message, receive_message + + +def main(): + var handler = WebSocketPrinter() + var ws = WebSocketLoop(handler) + var server = SysServer[WebSocketLoop[WebSocketPrinter]](ws) + var handshake = WebSocketHandshake() + server.listen_and_serve("0.0.0.0:8080", handshake)