diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..e89194f --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "wasm32-wasip2" + +[target.wasm32-wasip2] +runner = "wasmtime -Shttp" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a899524..726cd1a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,8 +1,8 @@ { - "image": "ghcr.io/asw101/rust-wasi-hello-devcontainer:latest", + "image": "ghcr.io/bytecodealliance/sample-wasi-http-rust/devcontainer:latest", "features": { //"ghcr.io/devcontainers/features/rust:1": {}, - "ghcr.io/devcontainers/features/azure-cli:1": {} + //"ghcr.io/devcontainers/features/azure-cli:1": {} }, "customizations": { "vscode": { diff --git a/.devcontainer/docker/Dockerfile b/.devcontainer/docker/Dockerfile index 20136f2..38326bc 100644 --- a/.devcontainer/docker/Dockerfile +++ b/.devcontainer/docker/Dockerfile @@ -3,5 +3,8 @@ FROM mcr.microsoft.com/devcontainers/rust:${TAG} USER vscode -COPY ./scripts /tmp/scripts/ -RUN bash /tmp/scripts/install.sh +# Install wasmtime +RUN curl https://wasmtime.dev/install.sh -sSf | bash + +# Install cargo-component and wkg +RUN cargo install wkg diff --git a/.devcontainer/scripts/devcontainer.json b/.devcontainer/scripts/devcontainer.json deleted file mode 100644 index 17c97f1..0000000 --- a/.devcontainer/scripts/devcontainer.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "image": "mcr.microsoft.com/devcontainers/universal:2", - "features": { - "ghcr.io/devcontainers/features/rust:1": {}, - "ghcr.io/devcontainers/features/azure-cli:1": {} - }, - "postCreateCommand": "bash scripts/install.sh", - "customizations": { - "vscode": { - "extensions": [ - "rust-lang.rust-analyzer" - ] - } - } -} \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..932b49e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# These owners are the default owners for everything in the repo. +* @yoshuawuyts + +# These owners own specific parts of the repo +.devcontainer @asw101 +.github/workflows @asw101 @duffney +.github/scripts @asw101 @duffney diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..67e41cd --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,143 @@ +# Bytecode Alliance Organizational Code of Conduct (OCoC) + +*Note*: this Code of Conduct pertains to organizations' behavior. Please also see the [Individual Code of Conduct](CODE_OF_CONDUCT.md). + +## Preamble + +The Bytecode Alliance (BA) welcomes involvement from organizations, +including commercial organizations. This document is an +*organizational* code of conduct, intended particularly to provide +guidance to commercial organizations. It is distinct from the +[Individual Code of Conduct (ICoC)](CODE_OF_CONDUCT.md), and does not +replace the ICoC. This OCoC applies to any group of people acting in +concert as a BA member or as a participant in BA activities, whether +or not that group is formally incorporated in some jurisdiction. + +The code of conduct described below is not a set of rigid rules, and +we did not write it to encompass every conceivable scenario that might +arise. For example, it is theoretically possible there would be times +when asserting patents is in the best interest of the BA community as +a whole. In such instances, consult with the BA, strive for +consensus, and interpret these rules with an intent that is generous +to the community the BA serves. + +While we may revise these guidelines from time to time based on +real-world experience, overall they are based on a simple principle: + +*Bytecode Alliance members should observe the distinction between + public community functions and private functions — especially + commercial ones — and should ensure that the latter support, or at + least do not harm, the former.* + +## Guidelines + + * **Do not cause confusion about Wasm standards or interoperability.** + + Having an interoperable WebAssembly core is a high priority for + the BA, and members should strive to preserve that core. It is fine + to develop additional non-standard features or APIs, but they + should always be clearly distinguished from the core interoperable + Wasm. + + Treat the WebAssembly name and any BA-associated names with + respect, and follow BA trademark and branding guidelines. If you + distribute a customized version of software originally produced by + the BA, or if you build a product or service using BA-derived + software, use names that clearly distinguish your work from the + original. (You should still provide proper attribution to the + original, of course, wherever such attribution would normally be + given.) + + Further, do not use the WebAssembly name or BA-associated names in + other public namespaces in ways that could cause confusion, e.g., + in company names, names of commercial service offerings, domain + names, publicly-visible social media accounts or online service + accounts, etc. It may sometimes be reasonable, however, to + register such a name in a new namespace and then immediately donate + control of that account to the BA, because that would help the project + maintain its identity. + + For further guidance, see the BA Trademark and Branding Policy + [TODO: create policy, then insert link]. + + * **Do not restrict contributors.** If your company requires + employees or contractors to sign non-compete agreements, those + agreements must not prevent people from participating in the BA or + contributing to related projects. + + This does not mean that all non-compete agreements are incompatible + with this code of conduct. For example, a company may restrict an + employee's ability to solicit the company's customers. However, an + agreement must not block any form of technical or social + participation in BA activities, including but not limited to the + implementation of particular features. + + The accumulation of experience and expertise in individual persons, + who are ultimately free to direct their energy and attention as + they decide, is one of the most important drivers of progress in + open source projects. A company that limits this freedom may hinder + the success of the BA's efforts. + + * **Do not use patents as offensive weapons.** If any BA participant + prevents the adoption or development of BA technologies by + asserting its patents, that undermines the purpose of the + coalition. The collaboration fostered by the BA cannot include + members who act to undermine its work. + + * **Practice responsible disclosure** for security vulnerabilities. + Use designated, non-public reporting channels to disclose technical + vulnerabilities, and give the project a reasonable period to + respond, remediate, and patch. [TODO: optionally include the + security vulnerability reporting URL here.] + + Vulnerability reporters may patch their company's own offerings, as + long as that patching does not significantly delay the reporting of + the vulnerability. Vulnerability information should never be used + for unilateral commercial advantage. Vendors may legitimately + compete on the speed and reliability with which they deploy + security fixes, but withholding vulnerability information damages + everyone in the long run by risking harm to the BA project's + reputation and to the security of all users. + + * **Respect the letter and spirit of open source practice.** While + there is not space to list here all possible aspects of standard + open source practice, some examples will help show what we mean: + + * Abide by all applicable open source license terms. Do not engage + in copyright violation or misattribution of any kind. + + * Do not claim others' ideas or designs as your own. + + * When others engage in publicly visible work (e.g., an upcoming + demo that is coordinated in a public issue tracker), do not + unilaterally announce early releases or early demonstrations of + that work ahead of their schedule in order to secure private + advantage (such as marketplace advantage) for yourself. + + The BA reserves the right to determine what constitutes good open + source practices and to take action as it deems appropriate to + encourage, and if necessary enforce, such practices. + +## Enforcement + +Instances of organizational behavior in violation of the OCoC may +be reported by contacting the Bytecode Alliance CoC team at +[report@bytecodealliance.org](mailto:report@bytecodealliance.org). The +CoC team will review and investigate all complaints, and will respond +in a way that it deems appropriate to the circumstances. The CoC team +is obligated to maintain confidentiality with regard to the reporter of +an incident. Further details of specific enforcement policies may be +posted separately. + +When the BA deems an organization in violation of this OCoC, the BA +will, at its sole discretion, determine what action to take. The BA +will decide what type, degree, and duration of corrective action is +needed, if any, before a violating organization can be considered for +membership (if it was not already a member) or can have its membership +reinstated (if it was a member and the BA canceled its membership due +to the violation). + +In practice, the BA's first approach will be to start a conversation, +with punitive enforcement used only as a last resort. Violations +often turn out to be unintentional and swiftly correctable with all +parties acting in good faith. \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..e4e380a --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing +Contributions include code, documentation, answering user questions, running the +project's infrastructure, and advocating for all types of users. + +The project welcomes all contributions from anyone willing to work in good faith +with other contributors and the community. No contribution is too small and all +contributions are valued. + +This guide explains the process for contributing to the project's GitHub +Repository. + +- [Code of Conduct](#code-of-conduct) +- [Bad Actors](#bad-actors) + +## Code of Conduct +The project has a [Code of Conduct](./CODE_OF_CONDUCT.md) that *all* +contributors are expected to follow. This code describes the *minimum* behavior +expectations for all contributors. + +As a contributor, how you choose to act and interact towards your +fellow contributors, as well as to the community, will reflect back not only +on yourself but on the project as a whole. The Code of Conduct is designed and +intended, above all else, to help establish a culture within the project that +allows anyone and everyone who wants to contribute to feel safe doing so. + +Should any individual act in any way that is considered in violation of the +[Code of Conduct](./CODE_OF_CONDUCT.md), corrective actions will be taken. It is +possible, however, for any individual to *act* in such a manner that is not in +violation of the strict letter of the Code of Conduct guidelines while still +going completely against the spirit of what that Code is intended to accomplish. + +Open, diverse, and inclusive communities live and die on the basis of trust. +Contributors can disagree with one another so long as they trust that those +disagreements are in good faith and everyone is working towards a common +goal. + +## Bad Actors +All contributors to tacitly agree to abide by both the letter and +spirit of the [Code of Conduct](./CODE_OF_CONDUCT.md). Failure, or +unwillingness, to do so will result in contributions being respectfully +declined. + +A *bad actor* is someone who repeatedly violates the *spirit* of the Code of +Conduct through consistent failure to self-regulate the way in which they +interact with other contributors in the project. In doing so, bad actors +alienate other contributors, discourage collaboration, and generally reflect +poorly on the project as a whole. + +Being a bad actor may be intentional or unintentional. Typically, unintentional +bad behavior can be easily corrected by being quick to apologize and correct +course *even if you are not entirely convinced you need to*. Giving other +contributors the benefit of the doubt and having a sincere willingness to admit +that you *might* be wrong is critical for any successful open collaboration. + +Don't be a bad actor. \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4af2269 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "cargo" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/publish-devcontainer.yml b/.github/workflows/publish-devcontainer.yml index bba388b..580c814 100644 --- a/.github/workflows/publish-devcontainer.yml +++ b/.github/workflows/publish-devcontainer.yml @@ -10,7 +10,7 @@ on: # Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }}-devcontainer + IMAGE_NAME: ${{ github.repository }}/devcontainer # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. jobs: @@ -39,6 +39,9 @@ jobs: uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=raw,value=latest,enable={{is_default_branch}} # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c686f18..04d518a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,49 +1,125 @@ -name: Publish a Wasm Component to GitHub Artifacts +name: Build and publish a Wasm Component to GitHub Artifacts on: push: - branches: - - main + tags: + - v* workflow_dispatch: env: IMAGE_NAME: ${{ github.repository }} + COMPONENT_NAME: sample-wasi-http-rust + COMPONENT_DESCRIPTION: "A sample WASI HTTP component written in Rust" + COMPONENT_SOURCE: "https://github.com/bytecodealliance/sample-wasi-http-rust" + COMPONENT_HOMEPAGE: "https://github.com/bytecodealliance/sample-wasi-http-rust" + COMPONENT_LICENSES: "Apache-2.0 WITH LLVM-exception" jobs: publish: runs-on: ubuntu-latest + permissions: + id-token: write + packages: write + contents: read steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Cache cargo bin - id: cache-cargo - uses: actions/cache@v3 - env: - cache-name: cache-cargo-bin - with: - path: ~/.cargo/bin - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('scripts/install.sh') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - - if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} - name: Run install script - continue-on-error: false - run: bash scripts/install.sh - - - name: Run build script - run: bash scripts/build.sh - - - name: Run publish script - run: bash scripts/publish.sh + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@v1.10.15 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.actor }}/{{ env.COMPONENT_NAME }} + tags: | + type=semver,pattern={{version}} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install cosign + if: github.event_name != 'workflow_dispatch' + uses: sigstore/cosign-installer@v3.7.0 + + - name: Cache cargo bin + id: cache-cargo + uses: actions/cache@v3 + env: + cache-name: cache-cargo-bin + with: + path: ~/.cargo/bin + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('scripts/install.sh') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - if: ${{ steps.cache-cargo.outputs.cache-hit != 'true' }} + name: Install build dependencies + continue-on-error: false + run: | + cargo binstall wkg --force + cargo binstall cargo-auditable cargo-audit + cargo binstall auditable2cdx + + - name: Add wasm32-wasip2 target + run: rustup target add wasm32-wasip2 + + - name: Build the component + run: cargo auditable build --release --target wasm32-wasip2 + + - name: Normalize COMPONENT_NAME and Append .wasm + run: echo "COMPONENT_NAME_UNDERSCORED=${COMPONENT_NAME//-/_}.wasm" >> $GITHUB_ENV + + - name: Extract the SBOM from the component + if: github.event_name != 'workflow_dispatch' + run: auditable2cdx target/wasm32-wasip2/release/${{ env.COMPONENT_NAME_UNDERSCORED }} >> ${{ env.COMPONENT_NAME }}.spdx.json + + - name: Publish `:` to GitHub Container Registry + if: github.event_name != 'workflow_dispatch' + id: publish_versioned + uses: bytecodealliance/wkg-github-action@v5 + with: + file: target/wasm32-wasip2/release/${{ env.COMPONENT_NAME_UNDERSCORED }} + oci-reference-without-tag: ghcr.io/${{ env.IMAGE_NAME }}/${{ env.COMPONENT_NAME }} + version: ${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} + description: ${{ env.COMPONENT_DESCRIPTION }} + source: ${{ env.COMPONENT_SOURCE }} + homepage: ${{ env.COMPONENT_HOMEPAGE }} + licenses: ${{ env.COMPONENT_LICENSES }} + + - name: Sign the versioned wasm component + if: github.event_name != 'workflow_dispatch' + run: cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}/${{ env.COMPONENT_NAME }}@${{ steps.publish_versioned.outputs.digest }} + + - name: Sign the SBOM for the versioned Wasm component + if: github.event_name != 'workflow_dispatch' + run: cosign attest --yes --type spdxjson --predicate ${{ env.COMPONENT_NAME }}.spdx.json ghcr.io/${{ env.IMAGE_NAME }}/${{ env.COMPONENT_NAME }}@${{ steps.publish_versioned.outputs.digest }} + + - name: Publish `:latest` release to GitHub Container Registry + if: github.event_name != 'workflow_dispatch' + id: publish_latest + uses: bytecodealliance/wkg-github-action@v5 + with: + file: target/wasm32-wasip2/release/${{ env.COMPONENT_NAME_UNDERSCORED }} + oci-reference-without-tag: ghcr.io/${{ env.IMAGE_NAME }}/${{ env.COMPONENT_NAME }} + version: latest + description: ${{ env.COMPONENT_DESCRIPTION }} + source: ${{ env.COMPONENT_SOURCE }} + homepage: ${{ env.COMPONENT_HOMEPAGE }} + licenses: ${{ env.COMPONENT_LICENSES }} + + - name: Sign the latest wasm component + if: github.event_name != 'workflow_dispatch' + run: cosign sign --yes ghcr.io/${{ env.IMAGE_NAME }}/${{ env.COMPONENT_NAME }}@${{ steps.publish_latest.outputs.digest }} + + - name: Sign the SBOM for the latest Wasm component + if: github.event_name != 'workflow_dispatch' + run: cosign attest --yes --type spdxjson --predicate ${{ env.COMPONENT_NAME }}.spdx.json ghcr.io/${{ env.IMAGE_NAME }}/${{ env.COMPONENT_NAME }}@${{ steps.publish_latest.outputs.digest }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d3ccf5f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: CI +on: + pull_request: +env: + RUSTFLAGS: -Dwarnings + +jobs: + check_and_test: + name: It builds and tests pass + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Add wasm32-wasip2 target + run: rustup target add wasm32-wasip2 + - name: Run cargo check + shell: bash + run: cargo check --target wasm32-wasip2 + + check_style: + name: It follows all style conventions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install wasm32-wasip2 target + run: rustup target add wasm32-wasip2 + - name: Run cargo clippy + run: cargo clippy --target wasm32-wasip2 -- -Dwarnings + - name: Run cargo fmt + run: cargo fmt --all -- --check diff --git a/.gitignore b/.gitignore index c41cc9e..9211caa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target \ No newline at end of file +/target +/wit/deps \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b38422f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,46 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "LLDB Debug", + "preLaunchTask": "Build WebAssembly", + "program": "wasmtime", + "args": [ + "serve", + "-D", + "debug-info=y", + "--dir", + ".", + "${workspaceFolder}/target/wasm32-wasip1/debug/sample_wasi_http_rust.wasm" + ], + "cwd": "${workspaceFolder}" + }, + { + "name": "GDB debug", + "type":"cppdbg", + "request": "launch", + "preLaunchTask": "Build WebAssembly", + "program": "/home//.wasmtime/bin/wasmtime", + "args": [ + "serve", + "-D", + "debug-info=y", + "--dir", + ".", + "${workspaceFolder}/target/wasm32-wasip1/debug/sample_wasi_http_rust.wasm" + ], + "cwd": "${workspaceFolder}", + "MIMode": "gdb", + "setupCommands": [ + { + "text": "-enable-pretty-printing", + } + ] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..4b6bd4f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build WebAssembly", + "type": "shell", + "command": "cargo component build", + "group": "build", + "presentation": { + "reveal": "always", + "panel": "new" + } + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8a7c74a..aa6b1da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,24 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" @@ -9,36 +27,268 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] -name = "rust-wasi-hello" +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "sample-wasi-http-rust" version = "0.0.0" dependencies = [ - "wasi", - "wit-bindgen-rt 0.30.0", + "http-body-util", + "wstd", ] [[package]] -name = "wasi" -version = "0.13.2+wasi-0.2.1" +name = "serde" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9860ef2271bc7a6e1f985ac4438862a165137fe585ac10bdfad0a8aeb37ba25" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ - "wit-bindgen-rt 0.29.0", + "serde_derive", ] [[package]] -name = "wit-bindgen-rt" -version = "0.29.0" +name = "serde_derive" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18e27825442721d23ed98591a80f55220cfe0bf883d92b1ead6c9be237bb34" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ - "bitflags", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", ] [[package]] -name = "wit-bindgen-rt" -version = "0.30.0" +name = "syn" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2de7a3b06b9725d129b5cbd1beca968feed919c433305a23da46843185ecdd6" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ "bitflags", ] + +[[package]] +name = "wstd" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0903606f1acdecad11576768ecc61ce215d6848652ac16c0e4592bb265e4200e" +dependencies = [ + "anyhow", + "async-task", + "bytes", + "futures-lite", + "http", + "http-body", + "http-body-util", + "itoa", + "pin-project-lite", + "serde", + "serde_json", + "slab", + "wasip2", + "wstd-macro", +] + +[[package]] +name = "wstd-macro" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a9df01a7fb39fbe7e9b5ef76f586f06425dd6f2be350de4781936f72f9899d" +dependencies = [ + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 479c501..aa33dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rust-wasi-hello" +name = "sample-wasi-http-rust" version = "0.0.0" edition = "2021" publish = false @@ -7,12 +7,6 @@ publish = false [lib] crate-type = ["cdylib"] -[package.metadata.component] -package = "component:rust-wasi-hello" -# This tells cargo-component to use a special adapter, which doesn't depend on -# `get-environment` or other things not present in the proxy world. -proxy = true - [dependencies] -wasi = "0.13.2" -wit-bindgen-rt = { version = "0.30.0", features = ["bitflags"] } +http-body-util = "0.1.3" +wstd = "0.6" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be1d7c4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,219 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. diff --git a/README.md b/README.md index 6c40c97..4ae418d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,28 @@ -# Rust WASI Hello +# Sample: `wasi:http` in Rust -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/yoshuawuyts/rust-wasi-hello) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/bytecodealliance/sample-wasi-http-rust) -An example project showing how to build an HTTP server for WASI 0.2 built in -Rust. +An example project showing how to build a spec-compliant +[`wasi:http/proxy`][wasi-http] server for WASI 0.2 written in Rust. This sample +includes several [routes](#routes) that showcase different behavior. This sample +can be run by any spec-compliant `wasi:http/proxy` server. + +Each release of this sample is packaged up as a [Wasm OCI image][wasm-oci-image] +and published to the [GitHub Packages Registry][gh-pkg]. See the ["Deploying +published artifacts"][using-arifacts] section for more on how to fetch and run +the published artifacts. + +## Routes + +The following HTTP routes are available from the component: + +```text +/ # Hello world +/wait # Sleep for one second +/echo # Echo the HTTP body +/echo-headers # Echo the HTTP headers +/echo-trailers # Echo the HTTP trailers +``` ## Installation @@ -13,30 +32,44 @@ would prefer to run this locally, you can run the following commands: ```bash $ curl https://wasmtime.dev/install.sh -sSf | bash # install wasm runtime -$ cargo install cargo-component # install build tooling $ cargo install wkg # install wasm OCI tooling ``` +## Fetching WIT Dependencies + +This project uses [wkg][wkg] to manage WIT dependencies. To fetch the required +WIT packages, run: + +```bash +$ wkg wit fetch +``` + +This will download the WIT dependencies specified in the project and populate +the `wit/deps` directory. The `wkg.lock` file tracks the resolved versions. + ## Local Development -The HTTP server uses the `wasi:http/proxy` world. You can run it locally in a -`wasmtime` instance by using the following [cargo-component] command: +The HTTP server uses the `wasi:http/proxy` world. You can build and run it +locally using cargo and wasmtime: -```rust -$ cargo component run +```bash +$ cargo build --release --target wasm32-wasip2 # build the component +$ wasmtime serve -Scli -Shttp target/wasm32-wasip2/release/sample_wasi_http_rust.wasm ``` +### Note on Debugging +There are launch and task configuration files if you want to use VSCode for debugging in an IDE; however, if you prefer using GDB or LLDB directly the configuration files should be enough to get you up and running. Note that the [GDB configuration requires an absolute path](https://github.com/bytecodealliance/sample-wasi-http-rust/blob/fe47fc9f6c87d09575f6683a26f9a67e3e71aa26/.vscode/launch.json#L28), so that configuration in VSCode you will need to modify for your computer. -## Working with deployment artifacts +## Deploying published artifacts This project automatically published compiled Wasm Components as OCI to GitHub Artifacts. You can pull the artifact with any OCI-compliant tooling and run it in any Wasm runtime that supports the `wasi:http/proxy` world. To fetch the -latest published version from GitHub releases and run it in a local `wasmtime` -instance you can run the following command: +latest published version from GitHub releases using [wkg][wkg] and run it in a +local [`wasmtime` instance][wasmtime] you can run the following commands: ```bash -$ wkg pull ghcr.io/yoshuawuyts/rust-wasi-hello:latest -$ wasmtime serve rust-wasi-hello.wasm +$ wkg oci pull ghcr.io/bytecodealliance/sample-wasi-http-rust/sample-wasi-http-rust:latest +$ wasmtime serve -Scli -Shttp sample-wasi-http-rust.wasm ``` For production workloads however, you may want to use other runtimes or @@ -44,22 +77,13 @@ integrations which provide their own OCI integrations. Deployment will vary depending on you providers, though at their core they will tend to be variations on the pull + serve pattern we've shown here. -## Roadmap - -- [x] Get a base version compiling -- [x] Create a GitHub Actions Workflow -- [x] Add routes to the HTTP handler -- [ ] Create a wasm OCI publish action -- [ ] Setup a GitHub Codespace with all tools pre-installed -- [ ] Demo unit tests in CI -- [ ] Automatically publish a package on GitHub Release -- [ ] Add a load-testing example -- [ ] Upstream and integrate `scripts/` into their respective packages -- [ ] Add deploy remote artifact -- [ ] Show wkg pull and run locally - ## License -MIT OR Apache-2.0 +Apache-2.0 with LLVM Exception -[cargo-component]: https://github.com/bytecodealliance/cargo-component +[wasm-oci-image]: https://tag-runtime.cncf.io/wgs/wasm/deliverables/wasm-oci-artifact/ +[gh-pkg]: https://github.com/bytecodealliance/sample-wasi-http-rust/pkgs/container/sample-wasi-http-rust%2Fsample-wasi-http-rust +[using-arifacts]: #working-with-deployment-artifacts +[wasi-http]: https://github.com/WebAssembly/wasi-http +[wkg]: https://github.com/bytecodealliance/wasm-pkg-tools/tree/main/crates/wkg +[wasmtime]: https://wasmtime.dev diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index 66beb42..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -# build our project -cargo component build --release diff --git a/scripts/install.sh b/scripts/install.sh deleted file mode 100755 index ab858e9..0000000 --- a/scripts/install.sh +++ /dev/null @@ -1,7 +0,0 @@ -curl https://wasmtime.dev/install.sh -sSf | bash - -# install cargo-component -cargo install cargo-component --force --version 0.16.0 - -# install wkg -cargo install wkg --force diff --git a/scripts/publish.sh b/scripts/publish.sh deleted file mode 100755 index ef227d9..0000000 --- a/scripts/publish.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# not running in github actions? use gh cli to login, etc, locally -if [ "$GITHUB_ACTIONS" != "true" ]; then - # login to the registry - # if running locally, we may need to: - # gh auth refresh -h github.com -s write:packages,read:packages - gh auth token | docker login ghcr.io --username YOURUSERNAME --password-stdin - GH_USER=$(gh api user --jq '.login') - IMAGE_NAME="${GH_USER}/rust-wasi-hello" -fi - -PROJECT_NAME="rust_wasi_hello" -REGISTRY_REFERENCE="ghcr.io/${IMAGE_NAME}:latest" - -wkg oci push $REGISTRY_REFERENCE target/wasm32-wasip1/release/$PROJECT_NAME.wasm diff --git a/src/lib.rs b/src/lib.rs index 77441a2..1474e36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,60 +1,70 @@ -pub use wasi::http::types::{ - Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam, -}; - -struct Component; -wasi::http::proxy::export!(Component); - -impl wasi::exports::http::incoming_handler::Guest for Component { - fn handle(req: IncomingRequest, outparam: ResponseOutparam) { - match req.path_with_query().unwrap().as_str() { - "/wait" => http_wait(req, outparam), - // "/echo" => {} // TODO - // "/host" => {} // TODO - "/" | _ => http_home(req, outparam), - } +use wstd::http::{body::Bytes, Body, Request, Response, Result, StatusCode}; +use wstd::time::{Duration, Instant}; + +#[wstd::http_server] +async fn main(req: Request) -> Result> { + match req.uri().path_and_query().unwrap().as_str() { + "/wait" => wait(req).await, + "/echo" => echo(req).await, + "/echo-headers" => echo_headers(req).await, + "/echo-trailers" => echo_trailers(req).await, + "/" => home(req).await, + _ => not_found(req).await, } } -fn http_home(_req: IncomingRequest, outparam: ResponseOutparam) { - let headers = Fields::new(); - let res = OutgoingResponse::new(headers); - let body = res.body().expect("outgoing response"); - - ResponseOutparam::set(outparam, Ok(res)); - - let out = body.write().expect("outgoing stream"); - out.blocking_write_and_flush(b"Hello, wasi:http/proxy world!\n") - .expect("writing response"); - - drop(out); - OutgoingBody::finish(body, None).unwrap(); +async fn home(_req: Request) -> Result> { + // To send a single string as the response body: + Ok(Response::new("Hello, wasi:http/proxy world!\n".into())) } -fn http_wait(_req: IncomingRequest, outparam: ResponseOutparam) { +async fn wait(_req: Request) -> Result> { // Get the time now - let now = wasi::clocks::monotonic_clock::now(); + let now = Instant::now(); - // Sleep for 1 second - let nanos = 1_000_000_000; - let pollable = wasi::clocks::monotonic_clock::subscribe_duration(nanos); - pollable.block(); + // Sleep for one second. + wstd::task::sleep(Duration::from_secs(1)).await; // Compute how long we slept for. - let elapsed = wasi::clocks::monotonic_clock::now() - now; - let elapsed = elapsed / 1_000_000; // change to millis + let elapsed = Instant::now().duration_since(now).as_millis(); + + Ok(Response::new( + format!("slept for {elapsed} millis\n").into(), + )) +} - let headers = Fields::new(); - let res = OutgoingResponse::new(headers); - let body = res.body().expect("outgoing response"); +async fn echo(req: Request) -> Result> { + // Stream data from the req body to the response body. + let req_body = req.into_body(); + Ok(Response::new(req_body)) +} - ResponseOutparam::set(outparam, Ok(res)); +async fn echo_headers(req: Request) -> Result> { + let mut res = Response::builder(); + *res.headers_mut().unwrap() = req.into_parts().0.headers; + Ok(res.body(().into()).expect("builder success")) +} - let out = body.write().expect("outgoing stream"); - let msg = format!("slept for {elapsed} millis\n"); - out.blocking_write_and_flush(msg.as_bytes()) - .expect("writing response"); +async fn echo_trailers(req: Request) -> Result> { + use http_body_util::{BodyExt, Full}; + let collected = req.into_body().into_boxed_body().collect().await?; + let (trailers, report) = if let Some(trailers) = collected.trailers() { + ( + Some(Ok(trailers.clone())), + format!("recieved trailers: {trailers:?}"), + ) + } else { + (None, "request had no trailers".to_owned()) + }; + + Ok(Response::new(Body::from_http_body( + Full::new(Bytes::from(report)).with_trailers(async { trailers }), + ))) +} - drop(out); - OutgoingBody::finish(body, None).unwrap(); +async fn not_found(_req: Request) -> Result> { + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(().into()) + .expect("builder succeeds")) } diff --git a/wit/world.wit b/wit/world.wit new file mode 100644 index 0000000..2bd7d01 --- /dev/null +++ b/wit/world.wit @@ -0,0 +1,5 @@ +package component:sample-wasi-http-rust; + +world sample-wasi-http-rust { + include wasi:http/proxy@0.2.2; +} diff --git a/wkg.lock b/wkg.lock new file mode 100644 index 0000000..3210499 --- /dev/null +++ b/wkg.lock @@ -0,0 +1,12 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 + +[[packages]] +name = "wasi:http" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.2" +version = "0.2.2" +digest = "sha256:a1f129cdf1fde55ec2d4ae8d998c39a7e5cf7544a8bd84a831054ac0d2ac64dd"