Skip to content
This repository was archived by the owner on Jun 10, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,061 changes: 612 additions & 449 deletions httpserver-rs/Cargo.lock

Large diffs are not rendered by default.

18 changes: 11 additions & 7 deletions httpserver-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
[package]
name = "wasmcloud-provider-httpserver"
version = "0.17.0"
version = "0.17.1"
edition = "2021"

[dependencies]
async-trait = "0.1.52"
atty = "0.2"
base64 = "0.13"
base64 = "0.21"
bytes = "1.2"
dashmap = "5.4.0"
flume = "0.10.14"
futures = "0.3"
http = "0.2"
opentelemetry = { version = "0.18.0", features = ["rt-tokio"] }
serde_bytes = "0.11"
serde_json = "1.0"
serde = {version = "1.0", features = ["derive"] }
smithy-bindgen = "0.1"
thiserror = "1.0"
tokio = { version = "1", features = ["full"] }
toml = "0.5"
toml = "0.7"
tracing = "0.1.34"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
warp = { version="0.3", features=["tls"] }
wasmcloud-interface-httpserver = "0.8.1"
wasmbus-rpc = { version = "0.11.2", features = ["otel"] }
wasmbus-rpc = { version = "0.12.0", features = ["otel"]}


[dev-dependencies]
assert_matches = "1.5"
blake2 = "0.10.4"
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"]}
wasmcloud-test-util = "0.6.4"
reqwest = { version = "0.11", features = ["json"]}
wasmcloud-test-util = { git = "https://github.com/wasmcloud/wasmcloud-test", branch = "fix/bindgen" }


[lib]
name = "wasmcloud_provider_httpserver"
Expand Down
9 changes: 8 additions & 1 deletion httpserver-rs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,12 @@ oci_insecure = --insecure

include ../build/makefiles/provider.mk

test:
ifeq ($(shell nc -czt -w1 127.0.0.1 4222 || echo fail),fail)
test::
@echo To run these tests, you need a wamcloud host and nats server running
exit 2
else
test::
cargo test -- --nocapture
endif

78 changes: 62 additions & 16 deletions httpserver-rs/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
//! Http Server implementation for wasmcloud:httpserver
//!
//!
use std::{collections::HashMap, convert::Infallible, sync::Arc};
use std::{convert::Infallible, sync::Arc};

use async_trait::async_trait;
use tokio::sync::RwLock;
use wasmbus_rpc::{core::LinkDefinition, error::RpcError, provider::prelude::*};
use wasmcloud_provider_httpserver::{load_settings, HttpServerCore};
use tracing::{error, instrument, trace, warn};
use wasmbus_rpc::{
core::LinkDefinition, error::RpcError, provider::prelude::*, provider::ProviderTransport,
};
use wasmcloud_provider_httpserver::{
load_settings,
wasmcloud_interface_httpserver::{HttpRequest, HttpResponse, HttpServer, HttpServerSender},
HttpServerCore,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
// handle lattice control messages and forward rpc to the provider dispatch
Expand All @@ -24,7 +30,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Clone, Default, Provider)]
struct HttpServerProvider {
// map to store http server (and its link parameters) for each linked actor
actors: Arc<RwLock<HashMap<String, HttpServerCore>>>,
actors: Arc<dashmap::DashMap<String, HttpServerCore>>,
}

impl ProviderDispatch for HttpServerProvider {}
Expand All @@ -42,36 +48,76 @@ impl ProviderHandler for HttpServerProvider {
let settings =
load_settings(&ld.values).map_err(|e| RpcError::ProviderInit(e.to_string()))?;

let http_server = HttpServerCore::new(settings.clone(), get_host_bridge());
http_server.start(ld.clone()).await.map_err(|e| {
let http_server = HttpServerCore::new(
settings.clone(),
get_host_bridge().lattice_prefix().to_string(),
call_actor,
);

http_server.start(ld).await.map_err(|e| {
RpcError::ProviderInit(format!(
"starting httpserver for {} {:?}: {}",
&ld.actor_id, &settings.address, e
))
})?;

let mut update_map = self.actors.write().await;
update_map.insert(ld.actor_id.to_string(), http_server);
self.actors.insert(ld.actor_id.to_string(), http_server);

Ok(true)
}

/// Handle notification that a link is dropped - stop the http listener
async fn delete_link(&self, actor_id: &str) {
let mut aw = self.actors.write().await;
if let Some(server) = aw.remove(actor_id) {
if let Some(entry) = self.actors.remove(actor_id) {
tracing::info!(%actor_id, "httpserver stopping listener for actor");
server.begin_shutdown().await;
entry.1.begin_shutdown();
}
}

/// Handle shutdown request by shutting down all the http server threads
async fn shutdown(&self) -> Result<(), Infallible> {
let mut aw = self.actors.write().await;
// empty the actor link data and stop all servers
for (_, server) in aw.drain() {
server.begin_shutdown().await;
}
self.actors.clear();
Ok(())
}
}

/// forward HttpRequest to actor.
#[instrument(level = "debug", skip(_lattice_id, ld, req, timeout), fields(actor_id = %ld.actor_id))]
async fn call_actor(
_lattice_id: String,
ld: Arc<LinkDefinition>,
req: HttpRequest,
timeout: Option<std::time::Duration>,
) -> Result<HttpResponse, RpcError> {
let tx = ProviderTransport::new_with_timeout(ld.as_ref(), Some(get_host_bridge()), timeout);
let ctx = Context::default();
let actor = HttpServerSender::via(tx);

let rc = actor.handle_request(&ctx, &req).await;
match rc {
Err(RpcError::Timeout(_)) => {
error!("actor request timed out: returning 503",);
Ok(HttpResponse {
status_code: 503,
body: Default::default(),
header: Default::default(),
})
}

Ok(resp) => {
trace!(
status_code = %resp.status_code,
"http response received from actor"
);
Ok(resp)
}
Err(e) => {
warn!(
error = %e,
"actor responded with error"
);
Err(e)
}
}
}
17 changes: 13 additions & 4 deletions httpserver-rs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Address is a string in the form "IP:PORT". The default bind address is "127.0.0.

### TLS

An empty tls section, or no tls section, disables tls. To enable tls, both `cert_file` and `priv_key_file` must contain absolute paths to existing files.
An empty tls section, or no tls section, disables tls. To enable TLS, both `cert_file` and `priv_key_file` must contain absolute paths to existing files.

### CORS

Expand All @@ -34,10 +34,18 @@ An empty tls section, or no tls section, disables tls. To enable tls, both `cert

- `max_age_secs` - sets the `Access-Control-Max-Age` header. Default is 300 seconds.

### Content length limit

The http server is configured with a maximum content size that will be accepted for an incoming request. This is an important safety measure to prevent a caller from submitting unreasonably large requests to cause the server to run out of memory. By default, the content limit is 100MB (104857600 bytes).
The value can be overridden with the setting `max_content_len`. The value of this setting is a json string containing a number, or a number followed immediately by a 'K', 'M', or 'G' (or lowercase 'k','m', or 'g'), representing *1024, *1048576, or *1073741824, respectively.
For example, the following setting limits uploads to 20 MB.
```json
{ "max_content_len": "20M" }
```

## Examples of settings files

Bind to all IP interfaces and port 3000, no TLS
Bind to all IP interfaces and port 3000, with TLS disabled

```json
{ "address": "0.0.0.0:3000" }
Expand All @@ -60,13 +68,14 @@ Example with all settings
"allowed_methods": [ "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS" ],
"exposed_headers": [],
"max_age_secs": 300
}
},
"max_content_len": "100M"
}
```

## Using the http server settings

The link definition connecting an actor to a capability provider includes a key-value map called "values". (The location of this 'values' map is still tbd, possibly in a manifest file). "values" is currently defined as a map with string keys and string values, and there are a few options for specifying the httpserver settings within the values map.
The link definition connecting an actor to a capability provider includes a key-value map called "values". "values" is currently defined as a map with string keys and string values, and there are a few options for specifying the httpserver settings within the values map.

- use key `config_file`, with a value that is the absolute path to a json or toml file (file type detected by the `.json` or `.toml` extension).

Expand Down
Loading