Skip to content

Commit

Permalink
Outbound HTTP
Browse files Browse the repository at this point in the history
Signed-off-by: itowlson <ivan.towlson@fermyon.com>
  • Loading branch information
itowlson committed Oct 27, 2023
1 parent 77199d6 commit b34d5f7
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 62 deletions.
81 changes: 44 additions & 37 deletions content/spin/v2/http-outbound.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,64 @@ Spin provides an interface for you to make outgoing HTTP requests.

## Using HTTP From Applications

The Spin SDK surfaces the Spin HTTP interface to your language. The interface contains only one operation:
The outbound HTTP interface depends on your language.

| Operation | Parameters | Returns | Behavior |
|------------|------------|---------|----------|
| `request` | request record | response record | Sends the given HTTP request, and returns the response. |
> Under the surface, Spin uses the `wasi-http` interface. If your tools support the Wasm Component Model, you can work with that directly; but for most languages the Spin SDK is more idiomatic.
The request record specifies:
{{ tabs "sdk-type" }}

| Field | Type | Meaning |
|------------|----------|------------------|
| `method` | enum | The HTTP method for the request, e.g. GET, POST, DELETE, etc. |
| `uri` | string | The URI to which to make the request |
| `headers` | list of key-value string pairs | The request headers |
| `body` | bytes | Optional request body |
{{ startTab "Rust"}}

> The Wasm request record declaration also contains a `parameters` field. This is unused and is retained only for binary compatibility.
To send requests, use the [`spin_sdk::http::send`](https://fermyon.github.io/rust-docs/spin/main/spin_sdk/http/fn.send.html) function. This takes a request argument and returns a response (or error). It is `async`, so within an async inbound handler you can have multiple outbound `send`s running concurrently.

The response record contains:
`send` is quite flexible in its request and response types. The request may be:

| Field | Type | Meaning |
|------------|----------|------------------|
| `status` | integer | The HTTP status code of the response, e.g. 200, 404, etc. |
| `headers` | list of key-value string pairs | The response headers, if any |
| `body` | bytes | The response body, if any |
* [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html) - typically constructed via `http::Request::builder()`
* [`spin_sdk::http::Request`](https://fermyon.github.io/rust-docs/spin/main/spin_sdk/http/struct.Request.html) - typically constructed via `spin_sdk::http::Request::builder()`
* [`spin_sdk::http::OutgoingRequest`](https://fermyon.github.io/rust-docs/spin/main/spin_sdk/http/struct.OutgoingRequest.html) - constructed via `OutgoingRequest::new()`
* Any type for which you have implemented the `TryInto<spin_sdk::http::OutgoingRequest>` trait

The exact detail of calling the `request` operation from your application depends on your language:
Generally, you should use `OutgoingRequest` when you need to stream the outbound request body; otherwise, the `Request` types are usually simpler.

{{ tabs "sdk-type" }}

{{ startTab "Rust"}}
The response may be:

HTTP functions are available in the `spin_sdk::outbound_http` module. The function is named `send_request`. It takes a `spin_sdk::http::Request` and returns a `spin_sdk::http::Response`. Both of these types are specializations of the `Request` and `Response` types from the `http` crate, and have all their behaviour and methods; the Spin SDK maps them to the underlying Wasm interface. For example:
* [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html)
* [`spin_sdk::http::Response`](https://fermyon.github.io/rust-docs/spin/main/spin_sdk/http/struct.Response.html)
* [`spin_sdk::http::IncomingResponse`](https://fermyon.github.io/rust-docs/spin/main/spin_sdk/http/struct.IncomingResponse.html)
* Any type for which you have implemented the [spin_sdk::http::conversions::TryFromIncomingResponse](https://fermyon.github.io/rust-docs/spin/main/spin_sdk/http/conversions/trait.TryFromIncomingResponse.html) trait

```rust
use spin_sdk::http::{Request, Response};
Generally, you should use `IncomingResponse` when you need to stream the response body; otherwise, the `Response` types are usually simpler.

let request = http::Request::builder()
.method("POST")
.uri("https://example.com/users")
.body(Some(json_text.into()))?;
Here is an example of doing outbound HTTP in a simple request-response style:

let response = spin_sdk::outbound_http::send_request(request)?;
println!("Status: {}", response.status().as_str());
```rust
use spin_sdk::http::{IntoResponse, Request, send};
use spin_sdk::http_component;

#[http_component]
// The trigger handler (in this case an HTTP handler) has to be async
// so we can `await` the outbound send.
async fn handle_request(_req: Request) -> anyhow::Result<impl IntoResponse> {

// For this example, use the http::Request type for the outbound request
let outbound_req = http::Request::builder()
.uri("https://www.fermyon.com/")
.body(())?;

// Send the outbound request, capturing the response as raw bytes
let response: http::Response<Vec<u8>> = send(outbound_req).await?;

// Use the outbound response body
let response_len = response.body().len();

Ok(http::Response::builder()
.status(200)
.header("content-type", "text/plain")
.body(format!("The test page was {response_len} bytes"))?)
}
```

**Notes**

* The Rust SDK surfaces the idiomatic `http` types rather than the raw Wasm interface types. For example, the `method` in Rust is a string, not an enum.
* Request and response bodies are of type `Option<bytes::Bytes>`.

You can find a complete example for using outbound HTTP in the [Spin repository on GitHub](https://github.com/fermyon/spin/tree/main/examples/http-rust-outbound-http).
For an example of receiving the response in a streaming style, [see this example in the Spin repository](https://github.com/fermyon/spin/blob/main/examples/wasi-http-rust-streaming-outgoing-body/src/lib.rs).

{{ blockEnd }}

Expand Down
59 changes: 34 additions & 25 deletions content/spin/v2/rust-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,62 +210,69 @@ inserts a custom header into the response before returning:
```rust
use anyhow::Result;
use spin_sdk::{
http::{Request, Response},
http::{IntoResponse, Request},
http_component,
};

/// Send an HTTP request and return the response.
#[http_component]
fn send_outbound(_req: Request) -> Result<Response> {
let mut res = spin_sdk::outbound_http::send_request(
http::Request::builder()
.method("GET")
.uri("https://random-data-api.fermyon.app/animals/json")
.body(None)?,
)?;
async fn send_outbound(_req: Request) -> Result<impl IntoResponse> {
// Create the outbound request object
let req = http::Request::builder()
.method("GET")
.uri("https://random-data-api.fermyon.app/animals/json")
.body(())?;

// Send the request and await the response
let mut res: http::Response<()> = spin_sdk::http::send(req).await?;

// Demonstrate modifying the response before passing it
// back to the client
res.headers_mut()
.insert("spin-component", "rust-outbound-http".try_into()?);
println!("{:?}", res);
.insert("spin-component", "get-animal-fact".try_into()?);

println!("{:?}", res); // log the response
Ok(res)
}
```

> The `http::Request::builder()` method is provided by the Rust `http` crate. The `http` crate is already added to projects using the Spin `http-rust` template. If you create a project without using this template, you'll need to add the `http` crate yourself via `cargo add http`.
Before we can execute this component, we need to add the `random-data-api.fermyon.app`
domain to the application manifest `allowed_http_hosts` list containing the list of
domain to the component's `allowed_http_hosts` list in the application manifest. This contains the list of
domains the component is allowed to make HTTP requests to:

<!-- @nocpy -->

```toml
# spin.toml
spin_manifest_version = "1"
name = "spin-hello-world"
trigger = { type = "http", base = "/" }
spin_manifest_version = 2

[application]
name = "animal-facts"
version = "1.0.0"

[[component]]
id = "hello"
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
allowed_http_hosts = ["random-data-api.fermyon.app"]
[component.trigger]
route = "/outbound"
[[trigger.http]]
route = "/..."
component = "get-animal-fact"

[component.get-animal-fact]
source = "get-animal-fact/target/wasm32-wasi/release/get_animal_fact.wasm"
allowed_http_hosts = ["https://random-data-api.fermyon.app"]
```

Running the application using `spin up --file spin.toml` will start the HTTP
Running the application using `spin up` will start the HTTP
listener locally (by default on `localhost:3000`), and our component can
now receive requests in route `/outbound`:

<!-- @selectiveCpy -->

```bash
$ curl -i localhost:3000/outbound
$ curl -i localhost:3000
HTTP/1.1 200 OK
date: Fri, 18 Mar 2022 03:54:36 GMT
date: Fri, 27 Oct 2023 03:54:36 GMT
content-type: application/json; charset=utf-8
content-length: 185
server: spin/0.1.0
spin-component: get-animal-fact

{"timestamp":1684299253331,"fact":"Reindeer grow new antlers every year"}
```
Expand All @@ -284,6 +291,8 @@ This can be the basis for building components that communicate with external
databases or storage accounts, or even more specialized components like HTTP
proxies or URL shorteners.

> The Spin SDK for Rust provides more flexibility than we show here, including allowing streaming uploads or downloads. See the [Outbound HTTP API Guide](./http-outbound.md) for more information.
## Storing Data in Redis From Rust Components

Using the Spin's Rust SDK, you can use the Redis key/value store and to publish
Expand Down

0 comments on commit b34d5f7

Please sign in to comment.