Skip to content

Commit

Permalink
[WIP] carlo: parse responses using api types
Browse files Browse the repository at this point in the history
TODO
- [ ] carol_core should probably contain all the shared types
- [ ] needs tests to ensure there's no regression with non-default
      accept encoding. as a temporary measure this should be randomized
      before the carlo codebase stabilizes its interfaces?
- [ ] impl Display for {Binary,Machine}Id using hex::encode?
  • Loading branch information
nothingmuch committed May 26, 2023
1 parent 60964f4 commit 740fa93
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 15 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/carlo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ edition = "2021"

[dependencies]
anyhow.workspace = true
bincode = { workspace = true }
cargo_metadata = "0.15.4"
carol_core.workspace = true
carol_host.workspace = true
cargo_metadata = "0.15.4"
carol.workspace = true
clap = { workspace = true }
serde = { workspace = true }
reqwest = { workspace = true }
wit-component.workspace = true
111 changes: 97 additions & 14 deletions crates/carlo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use anyhow::{anyhow, Context};
use cargo_metadata::camino::Utf8PathBuf;
use cargo_metadata::Message;
use carol::http::api::{BinaryCreated, MachineCreated}; // TODO move to carol_core?
use carol::http::server::Encoding;
use carol_core::{hex, BinaryId};
use carol_host::Executor;
use clap::{Parser, Subcommand};
use reqwest::blocking::Client;
use std::fmt::Display;
use std::process::{Command, Stdio};
use wit_component::ComponentEncoder;
Expand All @@ -31,6 +32,87 @@ enum Commands {
},
}

struct Client {
base: String,
http_client: reqwest::blocking::Client,
}

impl Client {
fn new(base: String) -> Self {
Self {
base,
http_client: reqwest::blocking::Client::new(), // blocking::get() does builder().build()?
}
}

fn post(&self, path: &str) -> reqwest::blocking::RequestBuilder {
self.http_client
.post(format!("{}/{}", self.base, path))
.header(
reqwest::header::ACCEPT,
"application/bincode, application/json", // application/json;q=0.9? // TODO randomize whether or not sent for initial development?
)
}

fn decode_response<B>(&self, response: reqwest::blocking::Response) -> anyhow::Result<B>
where
B: for<'de> serde::Deserialize<'de> + bincode::Decode,
{
let response = response.error_for_status()?;

// FIXME error if no content type specified?
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.map(|h| h.to_str().unwrap_or(""))
.unwrap_or("");

let encoding = Encoding::from_content_type(content_type);

let body = response.bytes().context("Reading server response")?;

encoding
.decode_body::<B>(&body)
.context("Decoding response body")
}

fn upload_binary<B: Into<reqwest::blocking::Body>>(
&self,
binary_id: &BinaryId,
binary: B,
) -> anyhow::Result<BinaryCreated> {
let http_response = self
.post("binaries") // TODO idempotent HTTP PUT?
.body(binary)
.send()
.context("Uploading compiled WASM file to {carol_url}")?;

let api_response: BinaryCreated = self
.decode_response(http_response)
.context("Parse response")?;

if api_response.id != *binary_id {
return Err(anyhow!(
"Locally computed binary ID {} doesn't match server reported value {}",
hex::encode(binary_id.as_ref()),
hex::encode(api_response.id.as_ref()),
));
}

Ok(api_response)
}

fn activate(&self, binary_id: &BinaryId) -> anyhow::Result<MachineCreated> {
let http_response = self
.post(&format!("binaries/{}", hex::encode(binary_id.as_ref())))
.send()
.context("Activating {binary_id} on {carol_url}")?;

self.decode_response::<MachineCreated>(http_response)
.context("Parse response")
}
}

fn main() -> anyhow::Result<()> {
let cli = Cli::parse();

Expand Down Expand Up @@ -113,29 +195,30 @@ fn main() -> anyhow::Result<()> {
Box::new(component_target)
}
Commands::Upload { binary, carol_url } => {
let client = Client::new(carol_url.clone());

// Validate and derive BinaryId
let binary_id = Executor::new()
.load_binary_from_wasm_file(binary)
.context("Loading compiled binary")?
.binary_id();

let file = std::fs::File::open(binary)
.context(format!("Reading compiled WASM file {}", binary))?;

let client = Client::builder().build()?; // blocking::get() does this, not new() for some reason?
let response = client
.post(format!("{carol_url}/binaries"))
.body(file)
.send()
.context("Uploading compiled WASM file to {carol_url}")?;
let response_binary_id = client.upload_binary(&binary_id, file)?.id;

Box::new(response.text().context("Reading server response")?)
Box::new(hex::encode(response_binary_id.as_ref()))
}
Commands::Activate {
binary_id,
carol_url,
} => {
let client = Client::builder().build()?; // blocking::get() does this, not new() for some reason?
let response = client
.post(format!("{carol_url}/binaries/{binary_id}"))
.send()
.context("Activating {binary_id} on {carol_url}")?;
let client = Client::new(carol_url.clone());

let machine_id = client.activate(binary_id)?.id;

Box::new(response.text().context("Reading server response")?)
Box::new(hex::encode(machine_id.as_ref()))
}
};

Expand Down
12 changes: 12 additions & 0 deletions crates/carol/src/http/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,18 @@ impl Encoding {
}
}

pub fn decode_body<B>(&self, request_body: &[u8]) -> anyhow::Result<B>
where
B: for<'de> serde::Deserialize<'de> + bincode::Decode,
{
match self {
Encoding::Json => Ok(serde_json::from_slice(request_body)?),
Encoding::Bincode => {
Ok(bincode::decode_from_slice(request_body, bincode::config::standard())?.0)
}
}
}

pub fn from_accepts_header(val: &str) -> Self {
if val.contains("application/bincode") {
Encoding::Bincode
Expand Down

0 comments on commit 740fa93

Please sign in to comment.