Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend serving #46

Merged
merged 2 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
707 changes: 707 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions dfx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ tar = "0.4.26"
flate2 = "1.0.11"

[dependencies]
actix = "0.8.3"
actix-web = "1.0.8"
actix-files = "0.1.4"
clap = "2.33.0"
console = "0.7.7"
flate2 = "1.0.11"
Expand All @@ -28,6 +31,7 @@ serde_repr = "0.1.5"
serde_json = "1.0.40"
tar = "0.4.26"
tokio = "0.1"
url = "2.1.0"
wabt = "0.9.2"

[dev-dependencies]
Expand Down
5 changes: 3 additions & 2 deletions dfx/assets/new_project_files/dfinity.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
"output": "build/"
},
"start": {
"port": 8080,
"address": "127.0.0.1"
"port": 8000,
"address": "127.0.0.1",
"serve_root": "app/"
}
}
}
75 changes: 62 additions & 13 deletions dfx/src/commands/start.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,103 @@
use crate::lib::api_client::{ping, Client, ClientConfig};
use crate::lib::env::BinaryResolverEnv;
use crate::lib::env::{BinaryResolverEnv, ProjectConfigEnv};
use crate::lib::error::{DfxError, DfxResult};
use crate::lib::webserver::webserver;
use clap::{App, Arg, ArgMatches, SubCommand};
use indicatif::{ProgressBar, ProgressDrawTarget};
use std::time::{Duration, Instant};
use tokio::prelude::FutureExt;
use tokio::runtime::Runtime;

const TIMEOUT_IN_SECS: u64 = 5;
const TIMEOUT_IN_SECS: u64 = 10;
const IC_CLIENT_BIND_ADDR: &str = "http://localhost:8080/api";

pub fn construct() -> App<'static, 'static> {
SubCommand::with_name("start")
.about("Start a local network in the background.")
.arg(
Arg::with_name("host")
.help("The host (with port) to send the query to.")
.help("The host (with port) to bind the frontend to.")
.long("host")
.takes_value(true),
)
}

pub fn exec<T>(env: &T, args: &ArgMatches<'_>) -> DfxResult
where
T: BinaryResolverEnv,
T: ProjectConfigEnv + BinaryResolverEnv,
{
let b = ProgressBar::new_spinner();
b.set_draw_target(ProgressDrawTarget::stderr());

b.set_message("Starting up the client...");
b.enable_steady_tick(80);

let _child = {
let client_pathbuf = env.get_binary_command_path("client")?;
let client_pathbuf = env.get_binary_command_path("client").unwrap();
let nodemanager_pathbuf = env.get_binary_command_path("nodemanager").unwrap();

let config = env.get_config().unwrap();
let address_and_port = args
.value_of("host")
.and_then(|host| Option::from(host.parse()))
.unwrap_or_else(|| {
Ok(config
.get_config()
.get_defaults()
.get_start()
.get_binding_socket_addr("localhost:8000")
.unwrap())
})?;
let frontend_url = format!(
"http://{}:{}",
address_and_port.ip(),
address_and_port.port()
);
let project_root = config.get_path().parent().unwrap();

let client_watchdog = std::thread::spawn(move || {
let client = client_pathbuf.as_path();
let nodemanager = nodemanager_pathbuf.as_path();
loop {
let mut cmd = std::process::Command::new(nodemanager);
cmd.args(&[client]);
cmd.stdout(std::process::Stdio::inherit());
cmd.stderr(std::process::Stdio::inherit());

let mut cmd = env.get_binary_command("nodemanager")?;
cmd.args(&[client]);
// If the nodemanager itself fails, we are probably deeper into troubles than
// we can solve at this point and the user is better rerunning the server.
let mut child = cmd.spawn().unwrap();
if child.wait().is_err() {
break;
}
}
});
let frontend_watchdog = webserver(
address_and_port,
url::Url::parse(IC_CLIENT_BIND_ADDR).unwrap(),
project_root
.join(
config
.get_config()
.get_defaults()
.get_start()
.get_serve_root(".")
.as_path(),
)
.as_path(),
);

cmd.spawn()?
};
b.set_message("Pinging the DFINITY client...");

std::thread::sleep(Duration::from_millis(500));

let url = String::from(args.value_of("host").unwrap_or("http://localhost:8080"));

let mut runtime = Runtime::new().expect("Unable to create a runtime");

// Try to ping for 1 second, then timeout after 5 seconds if ping hasn't succeeded.
let start = Instant::now();
while {
let client = Client::new(ClientConfig { url: url.clone() });
let client = Client::new(ClientConfig {
url: frontend_url.clone(),
});

runtime
.block_on(ping(client).timeout(Duration::from_millis(TIMEOUT_IN_SECS * 1000 / 4)))
Expand All @@ -66,5 +113,7 @@ where

b.finish_with_message("DFINITY client started...");

frontend_watchdog.join().unwrap();
client_watchdog.join().unwrap();
Ok(())
}
110 changes: 107 additions & 3 deletions dfx/src/config/dfinity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::lib::error::DfxResult;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf};

pub const CONFIG_FILE_NAME: &str = "dfinity.json";
Expand All @@ -15,6 +16,7 @@ const EMPTY_CONFIG_DEFAULTS_START: ConfigDefaultsStart = ConfigDefaultsStart {
address: None,
port: None,
nodes: None,
serve_root: None,
};
const EMPTY_CONFIG_DEFAULTS_BUILD: ConfigDefaultsBuild = ConfigDefaultsBuild { output: None };

Expand All @@ -28,6 +30,7 @@ pub struct ConfigDefaultsStart {
pub address: Option<String>,
pub nodes: Option<u64>,
pub port: Option<u16>,
pub serve_root: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -55,12 +58,34 @@ impl ConfigCanistersCanister {
}
}

fn to_socket_addr(s: &str) -> Option<SocketAddr> {
match s.to_socket_addrs() {
Ok(mut a) => a.next(),
Err(_) => None,
}
}

impl ConfigDefaultsStart {
pub fn get_address(&self, default: &str) -> String {
self.address
.to_owned()
.unwrap_or_else(|| default.to_string())
}
pub fn get_binding_socket_addr(&self, default: &str) -> Option<SocketAddr> {
to_socket_addr(default).and_then(|default_addr| {
let addr = self.get_address(default_addr.ip().to_string().as_str());
let port = self.get_port(default_addr.port());

to_socket_addr(format!("{}:{}", addr, port).as_str())
})
}
pub fn get_serve_root(&self, default: &str) -> PathBuf {
PathBuf::from(
self.serve_root
.to_owned()
.unwrap_or_else(|| default.to_string()),
)
}
pub fn get_nodes(&self, default: u64) -> u64 {
self.nodes.unwrap_or(default)
}
Expand Down Expand Up @@ -136,16 +161,25 @@ impl Config {
))
}

pub fn load_from(working_dir: &PathBuf) -> std::io::Result<Config> {
pub fn from_file(working_dir: &PathBuf) -> std::io::Result<Config> {
let path = Config::resolve_config_path(working_dir)?;
let content = std::fs::read(&path)?;
Config::from_slice(path, &content)
}

pub fn from_current_dir() -> std::io::Result<Config> {
Config::from_file(&std::env::current_dir()?)
}

fn from_slice(path: PathBuf, content: &[u8]) -> std::io::Result<Config> {
let config = serde_json::from_slice(&content)?;
let json = serde_json::from_slice(&content)?;
Ok(Config { path, json, config })
}

pub fn from_current_dir() -> std::io::Result<Config> {
Config::load_from(&std::env::current_dir()?)
/// Create a configuration from a string.
pub fn from_str(content: &str) -> std::io::Result<Config> {
Config::from_slice(PathBuf::from("-"), content.as_bytes())
}

pub fn get_path(&self) -> &PathBuf {
Expand Down Expand Up @@ -218,4 +252,74 @@ mod tests {
Config::resolve_config_path(subdir_path.as_path()).unwrap(),
);
}

#[test]
fn config_defaults_start_addr() {
let config = Config::from_str(
r#"{
"defaults": {
"start": {
"address": "localhost",
"port": 8000
}
}
}"#,
)
.unwrap();

assert_eq!(
config
.get_config()
.get_defaults()
.get_start()
.get_binding_socket_addr("1.2.3.4:123"),
to_socket_addr("localhost:8000")
);
}

#[test]
fn config_defaults_start_addr_no_address() {
let config = Config::from_str(
r#"{
"defaults": {
"start": {
"port": 8000
}
}
}"#,
)
.unwrap();

assert_eq!(
config
.get_config()
.get_defaults()
.get_start()
.get_binding_socket_addr("1.2.3.4:123"),
to_socket_addr("1.2.3.4:8000")
);
}

#[test]
fn config_defaults_start_addr_no_port() {
let config = Config::from_str(
r#"{
"defaults": {
"start": {
"address": "localhost"
}
}
}"#,
)
.unwrap();

assert_eq!(
config
.get_config()
.get_defaults()
.get_start()
.get_binding_socket_addr("1.2.3.4:123"),
to_socket_addr("localhost:123")
);
}
}
7 changes: 7 additions & 0 deletions dfx/src/lib/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum DfxError {
SerdeJson(serde_json::error::Error),
Url(reqwest::UrlError),
WabtError(wabt::Error),
AddrParseError(std::net::AddrParseError),

/// An unknown command was used. The argument is the command itself.
UnknownCommand(String),
Expand Down Expand Up @@ -78,3 +79,9 @@ impl From<wabt::Error> for DfxError {
DfxError::WabtError(err)
}
}

impl From<std::net::AddrParseError> for DfxError {
fn from(err: std::net::AddrParseError) -> DfxError {
DfxError::AddrParseError(err)
}
}
1 change: 1 addition & 0 deletions dfx/src/lib/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod api_client;
pub mod env;
pub mod error;
pub mod webserver;

pub type CanisterId = u64;
Loading