Merge pull request #137 from radu-matei/docs
This commit is contained in:
commit
70b3893127
|
@ -2,5 +2,6 @@
|
|||
"recommendations": [
|
||||
"matklad.rust-analyzer",
|
||||
"serayuzgur.crates",
|
||||
"fermyon.autobindle",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2888,10 +2888,7 @@ name = "spin-config"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"itertools",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2899,31 +2896,15 @@ name = "spin-engine"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bindle",
|
||||
"bytes 1.1.0",
|
||||
"dirs 4.0.0",
|
||||
"fs_extra",
|
||||
"futures",
|
||||
"git2",
|
||||
"glob",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"serde",
|
||||
"sha2 0.10.1",
|
||||
"spin-config",
|
||||
"structopt",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber 0.3.8",
|
||||
"wasi-common",
|
||||
"wasi-experimental-http-wasmtime 0.9.0 (git+https://github.com/deislabs/wasi-experimental-http?rev=4ed321d6943f75546e38bba80e14a59797aa29de)",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-wasmtime",
|
||||
]
|
||||
|
||||
|
@ -2957,7 +2938,6 @@ dependencies = [
|
|||
"wasi-common",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-wasmtime",
|
||||
]
|
||||
|
||||
|
|
|
@ -6,7 +6,4 @@ authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.52"
|
||||
itertools = "0.10.3"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
tokio = "1.16.1"
|
||||
|
|
|
@ -185,7 +185,7 @@ pub struct WagiConfig {
|
|||
///
|
||||
/// This should be a space-separate list of strings. The value
|
||||
/// ${SCRIPT_NAME} will be replaced with the Wagi SCRIPT_NAME,
|
||||
/// and the value ${ARGS} will be replaced with the query paramater
|
||||
/// and the value ${ARGS} will be replaced with the query parameter
|
||||
/// name/value pairs presented as args. For example,
|
||||
/// `param1=val1¶m2=val2` will become `param1=val1 param2=val2`,
|
||||
/// which will then be presented to the program as two arguments
|
||||
|
|
|
@ -6,31 +6,15 @@ authors = [ "Radu Matei <radu.matei@fermyon.com>" ]
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.44"
|
||||
async-trait = "0.1.51"
|
||||
bindle = "0.8.0"
|
||||
bytes = "1.1.0"
|
||||
dirs = "4.0"
|
||||
fs_extra = "1.2.0"
|
||||
futures = "0.3.17"
|
||||
git2 = "0.13"
|
||||
glob = "0.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.5.4"
|
||||
serde = { version = "1.0.130", features = [ "derive" ] }
|
||||
sha2 = "0.10.1"
|
||||
spin-config = { path = "../config" }
|
||||
structopt = "0.3.23"
|
||||
tempfile = "3.3.0"
|
||||
tokio = { version = "1.10.0", features = [ "fs" ] }
|
||||
toml = "0.5.8"
|
||||
tracing = { version = "0.1", features = [ "log" ] }
|
||||
tracing-futures = "0.2"
|
||||
tracing-subscriber = { version = "0.3.7", features = [ "env-filter" ] }
|
||||
wasi-common = "0.34"
|
||||
wasi-experimental-http-wasmtime = { git = "https://github.com/deislabs/wasi-experimental-http", rev = "4ed321d6943f75546e38bba80e14a59797aa29de" }
|
||||
wasmtime = "0.34"
|
||||
wasmtime-wasi = "0.34"
|
||||
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
|
||||
|
||||
[dev-dependencies]
|
||||
wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "spin-http-engine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = [ "Radu Matei <radu.matei@fermyon.com>" ]
|
||||
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
@ -33,7 +33,6 @@ wagi = { git = "https://github.com/deislabs/wagi", rev = "984c3922626b770ba43443
|
|||
wasi-common = "0.34"
|
||||
wasmtime = "0.34"
|
||||
wasmtime-wasi = "0.34"
|
||||
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
|
||||
wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "e9c7c0a3405845cecd3fe06f3c20ab413302fc73" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
name = "spin-loader"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
|
|
|
@ -74,7 +74,7 @@ pub struct RawWasmConfig {
|
|||
/// Files to be mapped inside the Wasm module at runtime.
|
||||
///
|
||||
/// In the local configuration file, this is a vector, each element of which
|
||||
/// is either a file paths or glob relative to the spin.toml file, or a
|
||||
/// is either a file path or glob relative to the spin.toml file, or a
|
||||
/// mapping of a source path to an absolute mount path in the guest.
|
||||
pub files: Option<Vec<RawFileMount>>,
|
||||
/// Optional list of HTTP hosts the component is allowed to connect.
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
name = "spin-publish"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
name = "spin-templates"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = [ "Radu Matei <radu.matei@fermyon.com>" ]
|
||||
|
||||
authors = [ "Fermyon Engineering <engineering@fermyon.com>" ]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# Spin architecture and internals
|
||||
|
||||
This document aims to offer an overview to the implementation of Spin, as well
|
||||
as explain how the code is structured and how all parts fit together. This
|
||||
document is continuously evolving, and if you want even more detailed
|
||||
information, make sure to review the code for a given part of Spin.
|
||||
|
||||
## How Spin runs an application
|
||||
|
||||
A Spin application is defined as a `spin.toml` file. It can either be run
|
||||
directly by `spin up`, passing the manifest file (`--file spin.toml`), or it can
|
||||
be pushed to the registry then referenced using its remote ID
|
||||
(`spin bindle push` followed by `spin up --bindle <id>`).
|
||||
|
||||
Regardless of the application origin (local file or remote reference from the
|
||||
registry), a Spin application is defined by
|
||||
`spin_config::Configuration<CoreComponent>` (contained in the
|
||||
[`spin-config`](../crates/config) crate), which is the canonical representation
|
||||
of a Spin application.
|
||||
|
||||
The crate responsible for transforming a custom configuration into a canonical
|
||||
Spin application is [`spin-loader`](../crates/loader), which implements loading
|
||||
applications from local `spin.toml` files and from remote Bindle references (and
|
||||
ensures files referenced in the application configuration are copied and mounted
|
||||
at the location expected in the WebAssmebly module). Once the canonical
|
||||
representation is loaded from an application source, it is passed to a trigger —
|
||||
currently, the only trigger implemented is the HTTP trigger, and we will use it
|
||||
as an example throughout this document.
|
||||
|
||||
The HTTP trigger (defined in the [`spin-http`](../crates/http) crate) takes an
|
||||
application configuration ([#40](https://github.com/fermyon/spin/issues/40)
|
||||
explores a trigger handling multiple applications), starts an HTTP listener, and
|
||||
for each new request, it routes it to the component configured in the
|
||||
application configuration. Then, it instantiates the WebAssembly module (using a
|
||||
`spin_engine::ExecutionContext`) and uses the appropriate executor (either the
|
||||
[`SpinHttpExecutor`](../crates/http/src/spin.rs) or the
|
||||
[`WagiHttpExecutor`](../crates/http/src/wagi.rs), based on the component
|
||||
configuration) to handle the request and return the response.
|
||||
|
||||
## The Spin execution context
|
||||
|
||||
The Spin execution context (or "Spin engine") is the part of Spin that executes
|
||||
WebAssembly components using the
|
||||
[Wasmtime](https://github.com/bytecodealliance/wasmtime) WebAssembly runtime. It
|
||||
is implemented in the [`spin-engine`](../crates/engine/) crate, and serves as
|
||||
the part of Spin that takes a fully formed application configuration and creates
|
||||
Wasm instances based on the component configurations.
|
||||
|
||||
There are two important concepts in this crate:
|
||||
|
||||
- `spin_engine::Builder` — the builder for creating an execution context. It is
|
||||
created using an `ExecutionContextConfiguration` object (which contains a Spin
|
||||
application and Wasmtime configuration), and implements the logic for
|
||||
configuring WASI and the other host implementations provided by Spin. The
|
||||
builder exposes the Wasmtime
|
||||
[`Linker`](https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html),
|
||||
[`Engine`](https://docs.rs/wasmtime/latest/wasmtime/struct.Engine.html), and
|
||||
[`Store<RuntimeContext<T>>`](https://docs.rs/wasmtime/latest/wasmtime/struct.Store.html)
|
||||
(where `RuntimeContext<T>` is the internal Spin context, which is detailed
|
||||
later in the document), and it uses them to [pre-instantiate]()
|
||||
|
||||
- `spin_engine::ExecutionContext` — the main execution engine in Spin.
|
|
@ -1,144 +1,119 @@
|
|||
# Configuration for Spin applications
|
||||
|
||||
A _Spin application_ is a collection of at least one _component_, each
|
||||
instantiated as a result of an _event_ generated by a _trigger_.
|
||||
|
||||
In the example below we can see a simple application with a single component,
|
||||
executed when the `/hello` endpoint is accessed:
|
||||
Spin applications are comprised of general information, and a collection of at
|
||||
least one _component_. In the example below we can see a simple HTTP application
|
||||
with a single component executed when the `/hello` endpoint is accessed:
|
||||
|
||||
```toml
|
||||
apiVersion = "0.1.0"
|
||||
name = "spin-hello-world"
|
||||
description = "A simple application that returns hello world."
|
||||
trigger = { type = "http", base = "/" }
|
||||
version = "1.0.0"
|
||||
description = "A simple application that returns hello and goodbye."
|
||||
authors = [ "Radu Matei <radu@fermyon.com>" ]
|
||||
trigger = {type = "http", base = "/" }
|
||||
|
||||
[[component]]
|
||||
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
|
||||
id = "hello"
|
||||
id = "hello"
|
||||
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
|
||||
[component.trigger]
|
||||
route = "/hello"
|
||||
route = "/hello"
|
||||
```
|
||||
|
||||
All components of an application must be executed by the same trigger type.
|
||||
## Application configuration
|
||||
|
||||
## The `spin.toml` configuration file
|
||||
|
||||
Currently, the only configuration file for a Spin application is the `spin.toml`
|
||||
file. This might change in the future (it could potentially be generated from
|
||||
multiple configuration sources), as this file currently mixes build- and
|
||||
run-time concerns.
|
||||
|
||||
These are the fields currently supported for the configuration:
|
||||
The following are the fields supported by the `spin.toml` configuration file:
|
||||
|
||||
- `apiVersion` (REQUIRED): Spin API version. Currently, this value MUST be
|
||||
`"0.1.0"`.
|
||||
- `name` (REQUIRED): Name of the application.
|
||||
- `version` (REQUIRED): Version of the application.
|
||||
- `description` (OPTIONAL): Description of the application.
|
||||
- `authors` (OPTIONAL): Authors of the application.
|
||||
- `namespace` (OPTIONAL): Logical grouping of the application at runtime.
|
||||
- `component` (REQUIRED): List with the components of the application.
|
||||
- `trigger` (REQUIRED): Application trigger and configuration. (Currently, the
|
||||
only implemented trigger is `http`, and the application base path can be
|
||||
configured (`base`), and will be prepended to the routes individual components
|
||||
are handling. For example, if `base = "/foo"`, and a component
|
||||
`route = "/bar/..."`, that component will handle incoming requests for
|
||||
`/foo/bar/...`.)
|
||||
- `authors` (OPTIONAL): List with the authors of the application.
|
||||
- `trigger` (REQUIRED): Trigger for the application. Currently, all components
|
||||
of the application must be invoked as a result of the same trigger type.
|
||||
Currently, the only implemented application trigger is `http`, with the
|
||||
following configuration fields:
|
||||
- `type` (REQUIRED): The application trigger type with the value `"http"`.
|
||||
- `base` (REQUIRED): The base path for the HTTP application which will be
|
||||
prepended to the routes of all components. (For example, if `base = "/foo"`
|
||||
and a component has `route = "/bar"`, the component will be invoked for
|
||||
requests on `/foo/bar`.)
|
||||
- a list of `component` objects (REQUIRED) defining the application components.
|
||||
|
||||
## Component configuration
|
||||
|
||||
Each `component` object has the following fields:
|
||||
|
||||
- `id` (REQUIRED): ID of the component, used at runtime to select between
|
||||
multiple components of the same application.
|
||||
- `source` (REQUIRED): Source for the component. Can either be a path to a local
|
||||
file, or a pair of `reference` (REQUIRED) and `parcel` (REQUIRED) fields
|
||||
pointing to a remote bindle.
|
||||
- `environment` (OPTIONAL): Environment variables to be mapped inside the Wasm
|
||||
module at runtime.
|
||||
- `files` (OPTIONAL): Paths (relative to the configuration file) of files to be
|
||||
mapped inside the Wasm module at runtime.
|
||||
- `id` (REQUIRED): unique (per application) ID of the component, used at runtime
|
||||
to select between multiple components of the same application.
|
||||
- `source` (REQUIRED): Source for the WebAssembly module of the component. This
|
||||
field can be _one_ the following:
|
||||
- a string with the path to a local file containing the WebAssembly module for
|
||||
the component OR
|
||||
- a pair of `reference` (REQUIRED) and `parcel` (REQUIRED) fields pointing to
|
||||
a remote bindle package (Note that this is currently not implemented, see
|
||||
[#135](https://github.com/fermyon/spin/issues/135)).
|
||||
- `environment` (OPTIONAL): Environment variables to be made available inside
|
||||
the WebAssembly module at runtime.
|
||||
- `files` (OPTIONAL): Files to be made available inside the WebAssembly module
|
||||
at runtime. This is a list, each element of which is either:
|
||||
- a file path or glob relative to the `spin.toml` file (for example
|
||||
`file.txt`, or `content/static/**/*`) OR
|
||||
- a mapping of a `source` (REQUIRED), a directory relative to `spin.toml` and
|
||||
`destination` (REQUIRED), the absolute mount path to be mapped inside the
|
||||
WebAssembly module. For example
|
||||
`{ source = "/content/", destination = "/"}`.
|
||||
- `allowed_http_hosts` (OPTIONAL): List of HTTP hosts the component is allowed
|
||||
to connect to.
|
||||
- `trigger` (REQUIRED): Trigger configuration for the component.
|
||||
- `dependencies` (OPTIONAL): List of dependencies to be resolved and satisfied
|
||||
at runtime by the host.
|
||||
- `build` (OPTIONAL): Currently unused build information or configuration that
|
||||
could be used by a plugin to build the component.
|
||||
|
||||
### Component sources
|
||||
|
||||
When writing a `spin.toml` file, components can either come from a local file,
|
||||
or be they can reference a remote bindle.
|
||||
|
||||
As such, the `source` field in the component configuration can either directly
|
||||
point to the path:
|
||||
|
||||
```toml
|
||||
source = "path/to/wasm/file.wasm"
|
||||
```
|
||||
|
||||
Or it can be an object containing the bindle reference and parcel:
|
||||
|
||||
```toml
|
||||
[component.source]
|
||||
reference = "bindle reference"
|
||||
parcel = "parcel"
|
||||
```
|
||||
|
||||
### Triggers
|
||||
|
||||
Triggers in Spin are components that generate events that cause the execution of
|
||||
components. Currently, the only trigger implemented for Spin is the HTTP
|
||||
trigger, which contains the following fields:
|
||||
|
||||
- `route` (REQUIRED): The HTTP route the component will be invoked for.
|
||||
- `executor` (REQUIRED): The object that sets an executor. There are currently two executor `type`s:
|
||||
- `spin` uses the Spin HTTP executor
|
||||
- `wagi` uses the Wagi CGI executor
|
||||
to make HTTP requests to (using the
|
||||
[WASI experimental HTTP library](https://github.com/deislabs/wasi-experimental-http))
|
||||
- `trigger` (REQUIRED): Trigger configuration for the component. Triggers are
|
||||
the components that generate events that cause the execution of components.
|
||||
Since the only implemented Spin trigger is HTTP, the component trigger
|
||||
configuration currently contains HTTP component trigger configuration, which
|
||||
has the following fields:
|
||||
- `route` (REQUIRED): The HTTP route the component will be invoked for. It can
|
||||
either be an exact route (for example `/foo/test`), or it can contain a
|
||||
wildcard (`/foo/test/...`) as the last path segment, which means the
|
||||
component will be invoked for every request starting with the `/foo/test`
|
||||
prefix (for example `/foo/test/abc/def`).
|
||||
- `executor` (REQUIRED): The executor for the HTTP component. There are
|
||||
currently two executor `type`s:
|
||||
- `spin` (DEFAULT): the Spin HTTP executor, which uses
|
||||
[the WebAssembly component model](https://github.com/WebAssembly/component-model)
|
||||
OR
|
||||
- `wagi`: the Wagi CGI executor, which can be used to write components in
|
||||
any language that compiles to WASI. The Wagi executor has the following
|
||||
optional fields:
|
||||
- `argv` (OPTIONAL): The string representation of the `argv` list that
|
||||
should be passed into the handler. `${SCRIPT_NAME}` will be replaced
|
||||
with the script name, and `${ARGS}` will be replaced with the query
|
||||
parameters of the request, formatted as arguments. The default is to
|
||||
follow the CGI specification, and pass `${SCRIPT_NAME} ${ARGS}`
|
||||
- `entrypoint` (OPTIONAL): The name of the function that should be called
|
||||
as the entrypoint to this handler. By default, it is `_start` (which in
|
||||
most languages translates to calling `main` in the guest module).
|
||||
|
||||
## Examples
|
||||
|
||||
- Spin HTTP component that contains the files in `static/` mapped to `/`:
|
||||
|
||||
```toml
|
||||
[[component]]
|
||||
source = "modules/spin_static_fs.wasm"
|
||||
id = "fileserver"
|
||||
files = [ { source = "static/", destination = "/" } ]
|
||||
[component.trigger]
|
||||
route = "/hello"
|
||||
executor = { type = "spin" }
|
||||
# executor = { type="wagi" }
|
||||
route = "/static/..."
|
||||
```
|
||||
|
||||
### Wagi Executor
|
||||
|
||||
Some executors have additional configuration. Wagi supports the following extra configurations:
|
||||
|
||||
- `argv` (OPTIONAL): The string representation of the `argv` list that should be passed into the handler. `${SCRIPT_NAME}` will be replaced with the script name, and `${ARGS}` will be replaced with the query parameters of the request, formatted as arguments. The default is to follow the CGI specification, and pass `${SCRIPT_NAME} ${ARGS}`
|
||||
- `entrypoint` (OPTIONAL, EXPERT): The name of the function that should be called as the entrypoint to this handler. By default, it is `_start` (which in most languages translates to calling `main` in the guest module).
|
||||
- a Wagi component that contains file mounts and sets the module `argv` and
|
||||
invokes a custom export function as the entrypoint:
|
||||
|
||||
```toml
|
||||
[[component]]
|
||||
source = "modules/env_wagi.wasm"
|
||||
id = "env"
|
||||
files = [ "content/**/*" , "templates/*", "scripts/*", "config/*"]
|
||||
[component.trigger]
|
||||
route = "/..."
|
||||
executor = {type="wagi", argv="test ${SCRIPT_NAME} ${ARGS} done", entrypoint="_start"}
|
||||
executor = { type="wagi", argv="test ${SCRIPT_NAME} ${ARGS} done", entrypoint = "some-other-export-function" }
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
Each entry in the `dependencies` table should correspond to exactly one import
|
||||
module from the Wasm module. Currently, this map should either contain an
|
||||
interface that should be satisfied by the host runtime (through a host
|
||||
implementation), or an exact reference (_not_ a version range or constraint) to
|
||||
a component from the registry. The fields in a dependency section are:
|
||||
|
||||
- `type` (REQUIRED): The type of the dependency. Possible values: `host` or
|
||||
`component`.
|
||||
- `reference` (OPTIONAL): Reference to a component from the registry. Required
|
||||
if `type` is `component`.
|
||||
- `parcel` (OPTIONAL): Parcel to use from the bindle reference. Required if
|
||||
`type` is `component`.
|
||||
|
||||
## Glossary
|
||||
|
||||
- WebAssembly module (or "module") — compilation artifact targeting
|
||||
`wasm32-wasi` that adheres to
|
||||
[the Wasm specification](https://webassembly.org/specs/).
|
||||
|
||||
- WebAssembly component (or "component") — compilation artifact targeting
|
||||
`wasm32-wasi` that adheres to
|
||||
[the WebAssembly component model](https://github.com/WebAssembly/component-model/blob/main/design/high-level/Goals.md),
|
||||
and whose imports and exports use
|
||||
[interface types](https://github.com/WebAssembly/interface-types)
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# Contributing to Spin
|
||||
|
||||
We are delighted that you are interested in making Spin better! Thank you! This
|
||||
document will guide you in making your first contribution to the project.
|
||||
|
||||
First, any contribution and interaction on any Fermyon project MUST follow our
|
||||
[code of conduct](https://www.fermyon.com/code-of-conduct). Thank you for being
|
||||
part of an inclusive and open community!
|
||||
|
||||
We welcome and appreciate contributions of all types — opening issues, fixing
|
||||
typos, adding examples, one-liner code fixes, tests, or complete features.
|
||||
|
||||
If you plan on contributing anything complex, please go through the issue and PR
|
||||
queues first to make sure someone else has not started working on it. If it
|
||||
doesn't exist already, please open an issue so you have a change to get feedback
|
||||
from the community and the maintainers before you start working on your feature.
|
||||
|
||||
## Making code contributions to Spin
|
||||
|
||||
The following guide is intended to make sure your contribution can get merged as
|
||||
soon as possible. First, make sure you have the following prerequisites
|
||||
configured:
|
||||
|
||||
- [Rust](https://www.rust-lang.org/) at
|
||||
[1.56+](https://www.rust-lang.org/tools/install) with the `wasm32-wasi` and
|
||||
`wasm32-unknown-unknown` targets configured
|
||||
(`rustup target add wasm32-wasi && rustup target add wasm32-unknown-unknown`)
|
||||
- [`rustfmt`](https://github.com/rust-lang/rustfmt) and
|
||||
[`clippy`](https://github.com/rust-lang/rust-clippy) configured for your Rust
|
||||
installation
|
||||
- `make`
|
||||
- [Bindle server v0.8.0](https://github.com/deislabs/bindle/releases/tag/v0.8.0)
|
||||
in your system path.
|
||||
- if you are a VS Code user, we recommend the
|
||||
[`rust-analyzer`](https://rust-analyzer.github.io/) and
|
||||
[`autobindle`](https://github.com/fermyon/autobindle) extensions.
|
||||
- please ensure you
|
||||
[configure adding a GPG signature to your commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
|
||||
as well as appending a sign-off message (`git commit -S -s`)
|
||||
|
||||
Once you have set up the prerequisites and identified the contribution you want
|
||||
to make to Spin, make sure you can correctly build the project:
|
||||
|
||||
```shell
|
||||
# clone the repository
|
||||
$ git clone https://github.com/fermyon/spin && cd spin
|
||||
# add a new remote pointing to your fork of the project
|
||||
$ git remote add fork https://github.com/<your-username>/spin
|
||||
# create a new branch for your work
|
||||
$ git checkout -b <your-branch>
|
||||
|
||||
# if you are making a documentation contribution,
|
||||
# you can skip compiling and running the tests.
|
||||
|
||||
# build a release version of the Spin CLI
|
||||
$ cargo build --release
|
||||
# make sure compilation is successful
|
||||
$ ./target/release/spin --help
|
||||
|
||||
# run the tests and make sure they pass
|
||||
$ make test
|
||||
```
|
||||
|
||||
Now you should be ready to start making your contribution. To familiarize
|
||||
yourself with the Spin project, please read the
|
||||
[architecture document](./architecture.md). Since most of Spin is implemented in
|
||||
Rust, we try to follow the common Rust coding conventions (keep an eye on the
|
||||
recommendations from Clippy!) If applicable, add units or integration tests to
|
||||
ensure your contribution is correct.
|
||||
|
||||
Build the project and run the tests (`make build test`), and if everything is
|
||||
successful, you should be ready to commit your changes. We try to follow the
|
||||
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
guidelines for writing commit messages:
|
||||
|
||||
```shell
|
||||
$ git commit -S -s -m "<your commit message that follows https://www.conventionalcommits.org/en/v1.0.0/>"
|
||||
```
|
||||
|
||||
We try to only keep useful changes as separate commits — if you prefer to commit
|
||||
often, please
|
||||
[cleanup the commit history](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)
|
||||
before opening a pull request. Once you are happy with your changes you can push
|
||||
the branch to your fork:
|
||||
|
||||
```shell
|
||||
# "fork" is the name of the git remote pointing to your fork
|
||||
$ git push fork
|
||||
```
|
||||
|
||||
Now you are ready to create a pull request. Thank you for your contribution!
|
|
@ -0,0 +1,8 @@
|
|||
# Index
|
||||
|
||||
1. [Introduction](./intro.md)
|
||||
1. [Quickstart](./quickstart.md)
|
||||
1. [Configuration for Spin applications](./configuration.md)
|
||||
1. [Writing HTTP applications with Spin](./writing-http-apps.md)
|
||||
1. [Architecture](./architecture.md)
|
||||
1. [Contributing to Spin](./contributing.md)
|
|
@ -0,0 +1,24 @@
|
|||
# Introducing Spin
|
||||
|
||||
Spin is an open source framework for building and running fast, secure, and
|
||||
composable cloud microservices with WebAssembly. It aims to be the easiest way
|
||||
to get started with WebAssembly microservices, and takes advantage of the latest
|
||||
developments in the
|
||||
[WebAssembly component model](https://github.com/WebAssembly/component-model)
|
||||
and [Wasmtime](https://wasmtime.dev/) runtime.
|
||||
|
||||
Spin offers a simple CLI that helps you create, distribute, and execute
|
||||
applications, and in the next sections we will learn more about Spin
|
||||
applications and how to get started.
|
||||
|
||||
## Spin applications
|
||||
|
||||
Spin applications are comprised of one or more _components_, and follow the
|
||||
event-driven model — they are executed as the result of events being generated
|
||||
by _triggers_ (for example an HTTP server receiving requests, or a queue
|
||||
subscription receiving messages). On each new event, _the entrypoint_ of a
|
||||
component is executed by Spin. The entrypoints to components are _functions_.
|
||||
This, together with the fact that they are invoked in response to events, brings
|
||||
the Spin application model closer to the Function-as-a-Service model.
|
||||
|
||||
In the next section, we will [take Spin for a spin](./quickstart.md).
|
|
@ -0,0 +1,163 @@
|
|||
# Taking Spin for a spin
|
||||
|
||||
## Getting the `spin` binary
|
||||
|
||||
You can download the [latest release](https://github.com/fermyon/spin/releases).
|
||||
For example, for an M1 macOS machine:
|
||||
|
||||
```
|
||||
$ wget https://github.com/fermyon/spin/releases/download/canary/spin-canary-macos-aarch64.tar.gz
|
||||
$ tar xfv spin-canary-macos-aarch64.tar.gz
|
||||
$ ./spin --help
|
||||
```
|
||||
|
||||
> On an M1 macOS machine you might need to install / configure OpenSSL@1.1 by
|
||||
> running
|
||||
> `brew install openssl@1.1 && sudo ln -s /opt/homebrew/Cellar/openssl@1.1/1.1.1m /usr/local/openssl-aarch64`
|
||||
|
||||
Alternatively, if you want to build Spin from source,
|
||||
[follow the contribution guide](./contributing.md) for a detailed guide on
|
||||
getting started:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/fermyon/spin
|
||||
$ cd spin && cargo build --release
|
||||
$ ./target/release/spin --help
|
||||
```
|
||||
|
||||
At this point, move the `spin` binary somewhere in your path, so it can be
|
||||
accessed from any directory.
|
||||
|
||||
## Creating a new Spin HTTP application in Rust
|
||||
|
||||
First, we need to add the official Spin templates from the repository:
|
||||
|
||||
```
|
||||
$ spin templates add --git https://github.com/fermyon/spin --name fermyon
|
||||
$ spin templates list
|
||||
+-----------------------------------------------------------------------------------+
|
||||
| Name Repository URL Branch |
|
||||
+===================================================================================+
|
||||
| spin-http fermyon https://github.com/fermyon/bartholomew refs/heads/main |
|
||||
+-----------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
Now we can create a new application from the template:
|
||||
|
||||
```
|
||||
$ spin new --repo fermyon --template spin-http --path spin-hello-world
|
||||
$ cd spin-hello-world
|
||||
```
|
||||
|
||||
This generated all we need to build and run our very first Spin application.
|
||||
Let's have a look at `spin.toml`:
|
||||
|
||||
```toml
|
||||
apiVersion = "0.1.0"
|
||||
name = "spin-hello-world"
|
||||
description = "A simple application that returns hello world."
|
||||
trigger = { type = "http", base = "/" }
|
||||
version = "1.0.0"
|
||||
|
||||
[[component]]
|
||||
id = "hello"
|
||||
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
|
||||
[component.trigger]
|
||||
route = "/hello"
|
||||
```
|
||||
|
||||
Since this is an HTTP application, the application trigger is of `type = http`,
|
||||
and there is one component that responds to requests on route `/hello` using the
|
||||
`spinhelloworld.wasm` WebAssembly module. (See the
|
||||
[configuration document](./configuration.md) for a detailed guide on the Spin
|
||||
application configuration.)
|
||||
|
||||
Now let's have a look at the `hello` component — below is the complete source
|
||||
code for a Spin HTTP component written in Rust. It is a regular Rust function
|
||||
that takes an HTTP request and returns an HTTP response, annotated with the
|
||||
`http_component` macro:
|
||||
|
||||
```rust
|
||||
use anyhow::Result;
|
||||
use spin_sdk::{
|
||||
http::{Request, Response},
|
||||
http_component,
|
||||
};
|
||||
|
||||
/// A simple Spin HTTP component.
|
||||
#[http_component]
|
||||
fn hello_world(req: Request) -> Result<Response> {
|
||||
println!("{:?}", req.headers());
|
||||
Ok(http::Response::builder()
|
||||
.status(200)
|
||||
.header("foo", "bar")
|
||||
.body(Some("Hello, Fermyon!".into()))?)
|
||||
}
|
||||
```
|
||||
|
||||
> See
|
||||
> [the section on building HTTP applications with Spin for a detailed guide](./writing-http-apps.md).
|
||||
|
||||
We can build this component using the regular Rust toolchain, targeting
|
||||
`wasm32-wasi`, which will produce the WebAssembly module referenced in
|
||||
`spin.toml`:
|
||||
|
||||
```
|
||||
$ cargo build --target wasm32-wasi --release
|
||||
```
|
||||
|
||||
## Running the application with `spin up`
|
||||
|
||||
Now that we configured the application and built our component, we can _spin up_
|
||||
the application (pun intended):
|
||||
|
||||
```shell
|
||||
# optionally, use RUST_LOG=spin=trace to see detailed logs
|
||||
$ spin up --file spin.toml
|
||||
INFO spin_http_engine: Serving HTTP on address 127.0.0.1:3000
|
||||
```
|
||||
|
||||
Spin will instantiate all components from the application configuration, and
|
||||
will crate the router configuration for the HTTP trigger accordingly. The
|
||||
component can now be invoked by making requests to `http://localhost:3000/hello`
|
||||
(see route field in the configuration):
|
||||
|
||||
```
|
||||
$ curl -i localhost:3000/hello
|
||||
HTTP/1.1 200 OK
|
||||
foo: bar
|
||||
content-length: 15
|
||||
|
||||
Hello, Fermyon!
|
||||
```
|
||||
|
||||
You can add as many components as needed in `spin.toml`, mount files and
|
||||
directories, allow granular outbound HTTP connections, or environment variables.
|
||||
(see the [configuration document](./configuration.md) for a detailed guide on
|
||||
the Spin application configuration) and iterate locally with
|
||||
`spin up --file spin.toml` until you are ready to distribute the application.
|
||||
|
||||
## Distributing the application
|
||||
|
||||
First, we need to start the registry. You can
|
||||
[install the latest Bindle release](https://github.com/deislabs/bindle/tree/main/docs#from-the-binary-releases),
|
||||
or use the
|
||||
[`autobindle`](https://marketplace.visualstudio.com/items?itemName=fermyon.autobindle)
|
||||
VS Code extension, which automatically downloads and starts Bindle on
|
||||
`http://localhost:8080/v1`. Now we can package the entire application, the
|
||||
components, and all the referenced files and publishes them to the registry:
|
||||
|
||||
```
|
||||
$ export BINDLE_URL=http://localhost:8080/v1
|
||||
$ spin bindle push --file spin.toml
|
||||
pushed: spin-hello-world/1.0.0
|
||||
```
|
||||
|
||||
Now we can run the application using `spin up` directly from the registry:
|
||||
|
||||
```
|
||||
$ spin up --bindle spin-hello-world/1.0.0
|
||||
```
|
||||
|
||||
Congratulations! You just completed writing, building, publishing, and running
|
||||
your first Spin application.
|
|
@ -1,17 +1,26 @@
|
|||
# Cutting a Spin Release
|
||||
# Creating a new Spin release
|
||||
|
||||
To cut a release of Spin, you will need to do the following:
|
||||
|
||||
1. Create a pull request that changes the version number for your new version (e.g. 0.1.0 becomes 0.1.1)
|
||||
1. Create a pull request that changes the version number for your new version
|
||||
(e.g. 0.1.0 becomes 0.1.1)
|
||||
- `Cargo.toml` is the most important place to make this change
|
||||
- Check the docs for hard-coded version strings
|
||||
1. Merge the PR created in #1 (Such PRs are still required to get approvals, so make sure you get signoff on the PR)
|
||||
1. Before proceeding, verify that the merge commit on `main` intended to be tagged is green, i.e. CI is successful
|
||||
1. Merge the PR created in #1 (Such PRs are still required to get approvals, so
|
||||
make sure you get signoff on the PR)
|
||||
1. Before proceeding, verify that the merge commit on `main` intended to be
|
||||
tagged is green, i.e. CI is successful
|
||||
1. Create a new tag with a `v` and then the version number (`v0.1.1`)
|
||||
1. Push the tag up to `main` on GitHub
|
||||
- This will trigger a release build
|
||||
1. Wait for the `release` [action](https://github.com/fermyon/spin/actions/workflows/release.yaml) to complete, and download the binary artifacts that are generated by that action.
|
||||
1. Generate SHAs of the Windows, Mac (`amd64` and `aarch64`), and Linux (`amd64` and `aarch64`) binaries with `shasum -a 256` or a similar command
|
||||
1. Go to the GitHub [tags page](https://github.com/fermyon/spin/releases) and create a release, adding release notes, and uploading the binaries you downloaded above. The SHAs should go in the release notes.
|
||||
1. Wait for the `release`
|
||||
[action](https://github.com/fermyon/spin/actions/workflows/release.yaml) to
|
||||
complete, and download the binary artifacts that are generated by that
|
||||
action.
|
||||
1. Generate SHAs of the Windows, Mac (`amd64` and `aarch64`), and Linux (`amd64`
|
||||
and `aarch64`) binaries with `shasum -a 256` or a similar command
|
||||
1. Go to the GitHub [tags page](https://github.com/fermyon/spin/releases) and
|
||||
create a release, adding release notes, and uploading the binaries you
|
||||
downloaded above. The SHAs should go in the release notes.
|
||||
|
||||
At this point, you can just verify that all things are good.
|
||||
At this point, you can verify in the GitHub UI that the release was successful.
|
||||
|
|
|
@ -1,154 +1,184 @@
|
|||
# Writing HTTP applications using Spin
|
||||
# Building HTTP applications using Spin
|
||||
|
||||
// TODO
|
||||
Currently, the only applications that can be built with Spin are web based, or
|
||||
applications that are invoked as the result of an HTTP request, and which return
|
||||
an HTTP response. This is because HTTP workloads appear to be the most important
|
||||
for event-driven Functions-as-a-Service workloads, and we think initially serve
|
||||
the most popular use cases.
|
||||
|
||||
Let's take the following Spin application. It sets a base path, `/test`, and
|
||||
there are two components, each serving requests for `/test/hello/...` and
|
||||
`/test/wagi/...` respectively:
|
||||
> The extensible nature of Spin allows anyone to extend it by building more
|
||||
> triggers (see the [architecture](./architecture.md) and
|
||||
> [contributing](./contributing.md) documents), and we are experimenting with a
|
||||
> new trigger that invokes components for new payloads on a Redis message queue
|
||||
> (see [#59](https://github.com/fermyon/spin/issues/59)).
|
||||
|
||||
```toml
|
||||
name = "spin-hello-world"
|
||||
trigger = { type = "http", base = "/test" }
|
||||
Spin is built on top of the
|
||||
[WebAssembly component model](https://github.com/WebAssembly/component-model).
|
||||
We _strongly_ believe it represents the future of WebAssembly, and that it will
|
||||
enable scenarios that are simply not possible today (for example dynamic linking
|
||||
and transitive dependencies). As a result, the Spin HTTP trigger (and executor)
|
||||
is defined using [WebAssembly interfaces](../wit/ephemeral), and the
|
||||
[SDK for building Rust components](../sdk/rust) is built on top of the Rust
|
||||
implementation and bindings generator for WebAssembly components.
|
||||
|
||||
[[component]]
|
||||
source = "spin-module-that-prints-requests.wasm"
|
||||
id = "hello"
|
||||
[component.trigger]
|
||||
route = "/hello/..."
|
||||
But the WebAssembly component model is currently in its early stages. This means
|
||||
only a few languages fully implement it. While language communities implement
|
||||
the component model, we want to allow developers to use
|
||||
[any language that compiles to WASI](https://www.fermyon.com/wasm-languages/webassembly-language-support)
|
||||
to build Spin HTTP applications. This is why we currently implement a Wagi
|
||||
executor which supports [Wagi](https://github.com/deislabs/wagi)-based
|
||||
components that expect the HTTP request using the module's standard input, and
|
||||
return the HTTP response using the module's standard output, following
|
||||
[the CGI specification](https://tools.ietf.org/html/rfc3875). As a programming
|
||||
language adds support for the component model, we plan to enable better support
|
||||
for it in Spin, and eventually only support Spin applications that implement the
|
||||
WebAssembly component model.
|
||||
|
||||
[[component]]
|
||||
source = "env_wagi.wasm"
|
||||
id = "wagi"
|
||||
[component.trigger]
|
||||
route = "/wagi/..."
|
||||
executor = "wagi"
|
||||
```
|
||||
## Building HTTP components in Rust
|
||||
|
||||
Let's see how the application configuration above gets turned into the headers
|
||||
by starting the application on `localhost:3000`.
|
||||
We believe the Rust SDK offers the best experience for building Spin HTTP
|
||||
components, and this is the recommended way of writing Spin components in Rust.
|
||||
|
||||
First, let's send a request to the `hello` component.
|
||||
Building such a component in Rust requires writing a function that takes an HTTP
|
||||
`Request` and returns an HTTP `Response`, annotated with a special Spin
|
||||
procedural macro. Below is a complete component implementation:
|
||||
|
||||
```js
|
||||
➜ curl 'localhost:3000/test/hello/abc/def?foo=bar' -d "abc"
|
||||
Request {
|
||||
method: Method::Post,
|
||||
uri: "/test/hello/abc/def",
|
||||
headers: [
|
||||
(
|
||||
"host",
|
||||
"localhost:3000",
|
||||
),
|
||||
(
|
||||
"user-agent",
|
||||
"curl/7.77.0",
|
||||
),
|
||||
(
|
||||
"accept",
|
||||
"*/*",
|
||||
),
|
||||
(
|
||||
"content-length",
|
||||
"3",
|
||||
),
|
||||
(
|
||||
"content-type",
|
||||
"application/x-www-form-urlencoded",
|
||||
),
|
||||
(
|
||||
"PATH_INFO",
|
||||
"/abc/def",
|
||||
),
|
||||
(
|
||||
"X_FULL_URL",
|
||||
"http://localhost:3000/test/hello/abc/def?foo=bar",
|
||||
),
|
||||
(
|
||||
"X_MATCHED_ROUTE",
|
||||
"/test/hello/...",
|
||||
),
|
||||
(
|
||||
"X_BASE_PATH",
|
||||
"/test",
|
||||
),
|
||||
(
|
||||
"X_RAW_COMPONENT_ROUTE",
|
||||
"/hello/...",
|
||||
),
|
||||
(
|
||||
"X_COMPONENT_ROUTE",
|
||||
"/hello",
|
||||
),
|
||||
],
|
||||
params: [
|
||||
(
|
||||
"foo",
|
||||
"bar",
|
||||
),
|
||||
],
|
||||
body: Some(
|
||||
[
|
||||
97,
|
||||
98,
|
||||
99,
|
||||
],
|
||||
),
|
||||
```rust
|
||||
use anyhow::Result;
|
||||
use spin_sdk::{
|
||||
http::{Request, Response},
|
||||
http_component,
|
||||
};
|
||||
|
||||
/// A simple Spin HTTP component.
|
||||
#[http_component]
|
||||
fn hello_world(req: Request) -> Result<Response> {
|
||||
println!("{:?}", req.headers());
|
||||
Ok(http::Response::builder()
|
||||
.status(200)
|
||||
.header("foo", "bar")
|
||||
.body(Some("Hello, Fermyon!".into()))?)
|
||||
}
|
||||
```
|
||||
|
||||
Available in the request object are the following fields:
|
||||
The important things to note in the function above:
|
||||
|
||||
- `method` — the HTTP method of the request — in this case, GET
|
||||
- `uri` — the absolute path of the URI, _without_ the query parameters
|
||||
- `params` — list of `(key, value)` pairs with the query parameters
|
||||
- `headers` — list of `(key, value)` pairs with the headers (see the default
|
||||
headers for a list of default headers and their meaning)
|
||||
- body — optional byte array containing the request body
|
||||
- the `spin_sdk::http_component` macro — this marks the function as the
|
||||
entrypoint for the Spin component
|
||||
- the function signature — `fn hello_world(req: Request) -> Result<Response>` —
|
||||
the Spin HTTP component uses the HTTP objects from the popular Rust crate
|
||||
[`http`](https://crates.io/crates/http), and the request and response bodies
|
||||
are optionally using [`bytes::Bytes`](https://crates.io/crates/bytes)
|
||||
|
||||
Now let's send a request to the Wagi component and inspect the environment
|
||||
variables:
|
||||
### Making outbound HTTP requests
|
||||
|
||||
```
|
||||
➜ curl 'localhost:3000/test/wagi/abc/def?foo=bar' -d "abc"
|
||||
### Arguments ###
|
||||
This SDK includes the ability to send outbound HTTP requests using the
|
||||
[DeisLabs WASI experimental HTTP library](https://github.com/deislabs/wasi-experimental-http).
|
||||
Let's see an example where the component makes an outbound HTTP request to a
|
||||
server, modifies the result, then returns it:
|
||||
|
||||
### Env Vars ###
|
||||
QUERY_STRING = foo=bar
|
||||
REMOTE_HOST = 127.0.0.1
|
||||
AUTH_TYPE =
|
||||
X_FULL_URL = http://localhost:3000/test/wagi/abc/def?foo=bar
|
||||
PATH_TRANSLATED = /abc/def
|
||||
SERVER_PORT = 3000
|
||||
X_MATCHED_ROUTE = /test/wagi/...
|
||||
SERVER_PROTOCOL = HTTP/1.1
|
||||
CONTENT_TYPE =
|
||||
SERVER_SOFTWARE = WAGI/1
|
||||
HTTP_HOST = localhost:3000
|
||||
HTTP_ACCEPT = */*
|
||||
REMOTE_ADDR = 127.0.0.1
|
||||
X_RAW_COMPONENT_ROUTE = /wagi/...
|
||||
CONTENT_LENGTH = 3
|
||||
SERVER_NAME = localhost
|
||||
GATEWAY_INTERFACE = CGI/1.1
|
||||
HTTP_CONTENT_LENGTH = 3
|
||||
HTTP_CONTENT_TYPE = application/x-www-form-urlencoded
|
||||
X_BASE_PATH = /test
|
||||
HTTP_USER_AGENT = curl/7.77.0
|
||||
X_COMPONENT_ROUTE = /wagi
|
||||
REMOTE_USER =
|
||||
PATH_INFO = /abc/def
|
||||
REQUEST_METHOD = POST
|
||||
X_RAW_PATH_INFO = /abc/def
|
||||
SCRIPT_NAME = /test/wagi
|
||||
```rust
|
||||
#[http_component]
|
||||
fn hello_world(_req: Request) -> Result<Response> {
|
||||
let mut res = spin_sdk::http::send(
|
||||
http::Request::builder()
|
||||
.method("GET")
|
||||
.uri("https://fermyon.com")
|
||||
.body(None)?,
|
||||
)?;
|
||||
|
||||
### STDIN ###
|
||||
abc%
|
||||
res.headers_mut()
|
||||
.insert(http::header::SERVER, "spin/0.1.0".try_into()?);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
```
|
||||
|
||||
### The default headers
|
||||
In order for the component above to be allowed to make the outbound HTTP
|
||||
request, the destination host must be declared in the Spin application
|
||||
configuration:
|
||||
|
||||
```toml
|
||||
[[component]]
|
||||
id = "hello"
|
||||
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
|
||||
allowedHttpHosts = [ "https://fermyon.com" ]
|
||||
[component.trigger]
|
||||
route = "/hello"
|
||||
```
|
||||
|
||||
Making a request to this component, we can see the appended header, and that the
|
||||
response contains the expected body:
|
||||
|
||||
```shell
|
||||
$ curl -I localhost:3000/hello
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 29350
|
||||
content-type: text/html; charset=utf-8
|
||||
server: spin/0.1.0 # the header added by our component
|
||||
```
|
||||
|
||||
Any Rust crate that compiles to `wasm32-wasi` can be used as dependency in Rust
|
||||
components.
|
||||
|
||||
As the Spin framework evolves, the Spin SDK will continue adding functionality
|
||||
that improves the experience for building Spin components (such as implementing
|
||||
interfaces for popular functionality such as
|
||||
[object storage](https://github.com/fermyon/spin/issues/48),
|
||||
[key/value stores](https://github.com/fermyon/spin/issues/47), or
|
||||
[neural networks](https://github.com/fermyon/spin/issues/50)).
|
||||
|
||||
As more languages support the WebAssembly component model, our goal is to
|
||||
develop language SDKs for such popular languages.
|
||||
|
||||
## Building HTTP components using the Wagi executor
|
||||
|
||||
You can use any language that compiles to WASI to build an HTTP component using
|
||||
the [Wagi](https://github.com/deislabs/wagi) executor.
|
||||
|
||||
Wagi is a project that lets you write HTTP handlers using nothing but a
|
||||
language's standard library, following
|
||||
[the CGI specification](https://tools.ietf.org/html/rfc3875).
|
||||
|
||||
For example, here is a complete Wagi component written in Swift:
|
||||
|
||||
```swift
|
||||
print("content-type: text/html; charset=UTF-8\n\n");
|
||||
print("hello world\n");
|
||||
```
|
||||
|
||||
Here is another example, this time written in [Grain](https://grain-lang.org/),
|
||||
a new programming language that natively targets WebAssembly:
|
||||
|
||||
```js
|
||||
import Process from "sys/process";
|
||||
import Array from "array";
|
||||
|
||||
print("content-type: text/plain\n");
|
||||
|
||||
// This will print all the Wagi env variable
|
||||
print("==== Environment: ====");
|
||||
Array.forEach(print, Process.env());
|
||||
|
||||
// This will print the route path followed by each query
|
||||
// param. So /foo?bar=baz will be ["/foo", "bar=baz"].
|
||||
print("==== Args: ====");
|
||||
Array.forEach(print, Process.argv());
|
||||
```
|
||||
|
||||
> You can find examples on how to build Wagi applications in
|
||||
> [the DeisLabs GitHub organization](https://github.com/deislabs?q=wagi&type=public&language=&sort=).
|
||||
|
||||
In short, read HTTP headers from environment variables and the HTTP body from
|
||||
standard input, and return the response to standard output. You can follow the
|
||||
[Wagi guide](https://github.com/deislabs/wagi/blob/main/docs/writing_modules.md)
|
||||
on writing modules (note that a module declaring its subroutes will not be
|
||||
implemented in Spin).
|
||||
|
||||
## The default headers set in Spin HTTP components
|
||||
|
||||
Spin sets a few default headers on the request based on the base path, component
|
||||
route, and request URI, which should always be available when writing a module:
|
||||
route, and request URI, which will always be available when writing a module:
|
||||
|
||||
- `X_FULL_URL` - the full URL of the request —
|
||||
`http://localhost:3000/test/wagi/abc/def?foo=bar`
|
||||
|
@ -167,5 +197,3 @@ route, and request URI, which should always be available when writing a module:
|
|||
Besides the headers above, components that use the Wagi executor also have
|
||||
available
|
||||
[all headers set by Wagi, following the CGI spec](https://github.com/deislabs/wagi/blob/main/docs/environment_variables.md).
|
||||
|
||||
### The HTTP headers
|
||||
|
|
180
readme.md
180
readme.md
|
@ -1,165 +1,45 @@
|
|||
<div align="center">
|
||||
<h1>Project Spin</h1>
|
||||
<h1>Spin</h1>
|
||||
<img src="./docs/images/spin.png" width="300"/>
|
||||
<p>Spin is a tool that allows developers to build, publish, and deploy WebAssembly workloads. It is the next version of the Fermyon runtime.</p>
|
||||
<p>Spin is a framework for building, deploying, and running fast, secure, and composable cloud microservices with WebAssembly.</p>
|
||||
</div>
|
||||
|
||||
## Take Spin for a spin
|
||||
## What is Spin?
|
||||
|
||||
* [Take Spin for a spin](#take-spin-for-a-spin)
|
||||
* [Build Spin CLI](#build-spin-cli)
|
||||
* [Build and Run an HTTP Application with Spin](#build-and-run-an-http-application-with-spin)
|
||||
* [Generate an HTTP Application Using a Spin Template](#generate-an-http-application-using-a-spin-template)
|
||||
* [Build the Application](#build-the-application)
|
||||
* [Run the Application Locally](#run-the-application-locally)
|
||||
* [Publishing Interfaces](#publishing-interfaces)
|
||||
* [Publish the Spin HTTP Interface](#publish-the-spin-http-interface)
|
||||
* [Use Interface in HTTP Application](#use-interface-in-http-application)
|
||||
Spin is an open source framework for building and running fast, secure, and
|
||||
composable cloud microservices with WebAssembly. It aims to be the easiest way
|
||||
to get started with WebAssembly microservices, and takes advantage of the latest
|
||||
developments in the
|
||||
[WebAssembly component model](https://github.com/WebAssembly/component-model)
|
||||
and [Wasmtime](https://wasmtime.dev/) runtime.
|
||||
|
||||
## Build Spin CLI
|
||||
Spin offers a simple CLI that helps you create, distribute, and execute
|
||||
applications, and in the next sections we will learn more about Spin
|
||||
applications and how to get started.
|
||||
|
||||
Clone this repository and build the Spin CLI:
|
||||
## Getting started
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/fermyon/spin
|
||||
$ cd spin && cargo build --release
|
||||
See the [quickstart document](./docs/quickstart.md) for a detailed guide on
|
||||
configuring Spin and writing your first Spin application, but in short:
|
||||
|
||||
```
|
||||
$ wget https://github.com/fermyon/spin/releases/download/canary/spin-canary-<os-arch>.tar.gz
|
||||
$ tar xfv spin-canary-<os-arch>.tar.gz
|
||||
$ ./spin --help
|
||||
```
|
||||
|
||||
## Build and Run an HTTP Application with Spin
|
||||
After you follow the [quickstart document](./docs/quickstart.md), you can follow
|
||||
the [guide on writing HTTP applications with Spin](./docs/writing-http-apps.md)
|
||||
and the [guide on configuring Spin applications](./docs/configuration.md).
|
||||
|
||||
### Generate an HTTP Application Using a Spin Template
|
||||
After you built your application, run it using Spin, pointing to the Spin
|
||||
application configuration file:
|
||||
|
||||
Add a new Spin template based on the `templates/spin-http` directory from this
|
||||
repo:
|
||||
|
||||
```shell
|
||||
$ spin templates add --local templates/spin-http --name spin-http
|
||||
$ spin templates list
|
||||
+---------------------------------------+
|
||||
| Name Repository URL Branch |
|
||||
+=======================================+
|
||||
| spin-http local |
|
||||
+---------------------------------------+
|
||||
```
|
||||
$ spin up --file spin.toml
|
||||
```
|
||||
|
||||
Create the application:
|
||||
## Contributing
|
||||
|
||||
```shell
|
||||
$ mkdir helloworld
|
||||
# TODO: the name and path where the app is generated is wrong.
|
||||
$ spin new --repo local --template spin-http --path .
|
||||
```
|
||||
|
||||
### Build the Application
|
||||
|
||||
In the application directory:
|
||||
|
||||
```shell
|
||||
$ cargo build --release
|
||||
```
|
||||
|
||||
### Run the Application Locally
|
||||
|
||||
The configuration file `spin.toml` contains the information required for Spin to
|
||||
run the application locally:
|
||||
|
||||
```shell
|
||||
$ export RUST_LOG=spin_engine=info,spin_http,wact=info
|
||||
$ spin up --app spin.toml
|
||||
|
||||
2022-02-06T02:44:08.810806Z INFO spin_http_engine: Processing request for application spin-hello-world on path /hello
|
||||
2022-02-06T02:44:08.810897Z INFO execute{component="hello"}: spin_http_engine: Executing request for component hello
|
||||
2022-02-06T02:44:08.810918Z INFO execute{component="hello"}: prepare_component{component="hello"}: spin_engine: Preparing component hello
|
||||
2022-02-06T02:44:08.810936Z INFO execute{component="hello"}: prepare_component{component="hello"}: store: spin_engine: Creating store.
|
||||
2022-02-06T02:44:08.811318Z INFO execute{component="hello"}: spin_http_engine: Request URI: "/hello"
|
||||
2022-02-06T02:44:08.811553Z INFO execute{component="hello"}: spin_http_engine: Response status code: 200
|
||||
2022-02-06T02:44:08.811715Z INFO execute{component="hello"}: spin_http_engine: Request finished, sending response.
|
||||
```
|
||||
|
||||
The application is now ready, after starting, send a request using
|
||||
`curl -i localhost:3000/hello`:
|
||||
|
||||
```console
|
||||
$ curl -i localhost:3000/hello
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 12
|
||||
date: Sun, 06 Feb 2022 02:44:08 GMT
|
||||
|
||||
I'm a teapot
|
||||
```
|
||||
|
||||
## Publishing Interfaces
|
||||
|
||||
In the example above, the interface (`.wit` file) was copied over to the local
|
||||
HTTP application directory. You can also publish interfaces to a bindle registry
|
||||
for others to consume as well as pull interfaces from a bindle registry to use.
|
||||
The example below creates and publishes the spin http interface and then walks
|
||||
through how to consume it in the HTTP application from the previous example.
|
||||
|
||||
### Publish the Spin HTTP Interface
|
||||
|
||||
Push the Spin HTTP interface to the registry (from the root of this repository).
|
||||
This step, together with starting the registry, will not be required once we set
|
||||
up a canonical registry instance:
|
||||
|
||||
```shell
|
||||
$ wact interface publish --name fermyon/http --version 0.1.0 wit/ephemeral/spin-http.wit
|
||||
```
|
||||
|
||||
### Use Interface in HTTP Application
|
||||
|
||||
1. Update `Cargo.toml` to include the following dependency, component and
|
||||
interface information:
|
||||
|
||||
```toml
|
||||
[...]
|
||||
[dependencies]
|
||||
# The Wact dependency generates bindings that simplify working with interfaces.
|
||||
wact = { git = "https://github.com/fermyon/wact", rev = "93a9eaeba9205918dc214a6310c0bb6e33c0e3c8" }
|
||||
|
||||
[workspace]
|
||||
|
||||
# Metadata about this component.
|
||||
[package.metadata.component]
|
||||
name = "spinhelloworld"
|
||||
|
||||
# This component implements the fermyon/http interface.
|
||||
[package.metadata.component.exports]
|
||||
fermyon-http = { name = "fermyon/http", version = "0.1.0" }
|
||||
```
|
||||
|
||||
2. Update the application to use wact to generate and use rust bindings. In
|
||||
`src/lib.rs`:
|
||||
|
||||
```rust
|
||||
// Import the HTTP objects from the generated bindings.
|
||||
use fermyon_http::{Request, Response};
|
||||
|
||||
// Generate Rust bindings for all interfaces in Cargo.toml.
|
||||
wact::component!();
|
||||
|
||||
struct FermyonHttp {}
|
||||
impl fermyon_http::FermyonHttp for FermyonHttp {
|
||||
// Implement the `handler` entrypoint for Spin HTTP components.
|
||||
fn handler(req: Request) -> Response {
|
||||
println!("Request: {:?}", req);
|
||||
Response {
|
||||
status: 418,
|
||||
headers: None,
|
||||
body: Some("I'm a teapot".as_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Remove `*.wit` files from local HTTP application directory
|
||||
|
||||
4. In the application directory, build the component:
|
||||
|
||||
```shell
|
||||
$ cargo build --target wasm32-wasi --release
|
||||
# OR
|
||||
$ cargo component build --release
|
||||
```
|
||||
|
||||
[Run the application locally](#run-the-application-locally) to test.
|
||||
We are delighted that you are interested in making Spin better! Thank you!
|
||||
Please follow the [contributing guide](./docs/contributing.md).
|
||||
|
|
|
@ -22,7 +22,7 @@ fn hello_world(req: Request) -> Result<Response> {
|
|||
Ok(http::Response::builder()
|
||||
.status(200)
|
||||
.header("foo", "bar")
|
||||
.body(Some("Hello, Fermyon".into()))?)
|
||||
.body(Some("Hello, Fermyon!".into()))?)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -46,7 +46,7 @@ server, modifies the result, then returns it:
|
|||
```rust
|
||||
#[http_component]
|
||||
fn hello_world(_req: Request) -> Result<Response> {
|
||||
let mut res = spin_sdk::outbound_http::send_request(
|
||||
let mut res = spin_sdk::http::send(
|
||||
http::Request::builder()
|
||||
.method("GET")
|
||||
.uri("https://fermyon.com")
|
||||
|
@ -54,10 +54,11 @@ fn hello_world(_req: Request) -> Result<Response> {
|
|||
)?;
|
||||
|
||||
res.headers_mut()
|
||||
.insert(header::SERVER, "spin/0.1.0".try_into()?);
|
||||
.insert(http::header::SERVER, "spin/0.1.0".try_into()?);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In order for the component above to be allowed to make the outbound HTTP
|
||||
|
@ -81,6 +82,5 @@ $ curl -I localhost:3000/hello
|
|||
HTTP/1.1 200 OK
|
||||
content-length: 29350
|
||||
content-type: text/html; charset=utf-8
|
||||
date: Fri, 04 Mar 2022 23:06:43 GMT
|
||||
server: spin/0.1.0
|
||||
```
|
||||
|
|
|
@ -20,6 +20,9 @@ pub mod http {
|
|||
/// The Spin HTTP response.
|
||||
pub type Response = http::Response<Option<bytes::Bytes>>;
|
||||
|
||||
/// Directly expose the ability to send an HTTP request.
|
||||
pub use crate::outbound_http::send_request as send;
|
||||
|
||||
/// Helper function to return a 404 Not Found response.
|
||||
pub fn not_found() -> Result<Response> {
|
||||
Ok(http::Response::builder()
|
||||
|
|
|
@ -2,12 +2,11 @@ apiVersion = "0.1.0"
|
|||
authors = ["Fermyon Engineering <engineering@fermyon.com>"]
|
||||
description = "A simple application that returns hello."
|
||||
name = "spin-hello-world"
|
||||
trigger = { type = "http", base = "/" }
|
||||
trigger = { type = "http" }
|
||||
version = "1.0.0"
|
||||
|
||||
[[component]]
|
||||
id = "hello"
|
||||
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
|
||||
# allowedHttpHosts = [ "https://fermyon.com" ]
|
||||
[component.trigger]
|
||||
route = "/hello"
|
||||
|
|
Loading…
Reference in New Issue