Skip to content
This repository has been archived by the owner on Aug 3, 2023. It is now read-only.

Commit

Permalink
Merge pull request #883 from cloudflare/avery/a-time-sync
Browse files Browse the repository at this point in the history
[rc] [dev] Dev Server Feature Branch
  • Loading branch information
EverlastingBugstopper authored Feb 6, 2020
2 parents 1a93246 + b5c8ad1 commit ad3a473
Show file tree
Hide file tree
Showing 14 changed files with 1,642 additions and 1,026 deletions.
2,089 changes: 1,087 additions & 1,002 deletions Cargo.lock

Large diffs are not rendered by default.

54 changes: 31 additions & 23 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,51 @@ build = "build.rs"

[dependencies]
atty = "0.2.11"
base64 = "0.10.1"
binary-install = "0.0.3-alpha"
chrome-devtools-rs = "0.0.0-alpha.0"
chrono = "0.4.9"
clap = "2.32.0"
cloudflare = "0.6.2"
config = "0.9.2"
console = "0.7.5"
console = "0.9.1"
data-encoding = "2.1.2"
dirs = "1.0.5"
cloudflare = "0.6.2"
env_logger = "0.6.1"
exitfailure = "0.5.1"
failure = "0.1.5"
flate2 = "1.0.7"
fs2 = "0.4.3"
futures = "0.3"
futures-util = "0.3"
http = "0.2.0"
hyper = "0.13.1"
hyper-tls = "0.4.0"
ignore = "0.4.10"
indicatif = "0.13.0"
lazy_static = "1.3.0"
log = "0.4.6"
openssl = { version = '0.10.11', optional = true }
notify = "4.0.12"
number_prefix = "0.3.0"
openssl = { version = '0.10.26', optional = true }
percent-encoding = "1.0.1"
prettytable-rs = "0.8.0"
rand = "0.6.5"
regex = "1"
reqwest = { version = "0.10.1", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.39"
serde_with = "1.3.1"
sha2 = "0.8.0"
tempfile = "3.1.0"
text_io = "0.1.7"
tokio = { version = "0.2", default-features = false, features = ["io-std", "time"] }
tokio-tungstenite = { version = "0.10.1", features = ["tls"] }
toml = "0.5.5"
url = "2.1.0"
uuid = { version = "0.8", features = ["v4"] }
which = "2.0.1"
rand = "0.6.5"
fs2 = "0.4.3"
number_prefix = "0.3.0"
flate2 = "1.0.7"
base64 = "0.10.1"
lazy_static = "1.3.0"
text_io = "0.1.7"
exitfailure = "0.5.1"
prettytable-rs = "0.8.0"
notify = "4.0.12"
ws = "0.9.0"
url = "2.1.0"
percent-encoding = "1.0.1"
http = "0.2.0"
regex = "1"
sha2 = "0.8.0"
data-encoding = "2.1.2"
ignore = "0.4.10"
tempfile = "3.1.0"
indicatif = "0.13.0"
serde_with = "1.3.1"

[dev-dependencies]
assert_cmd = "0.11.1"
Expand Down
Empty file added logs/connection.log
Empty file.
58 changes: 58 additions & 0 deletions src/commands/dev/headers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const HEADER_PREFIX: &str = "cf-ew-raw-";

use std::str::FromStr;

use hyper::header::{HeaderMap, HeaderName};
use hyper::http::request::Parts as RequestParts;
use hyper::http::response::Parts as ResponseParts;
use hyper::http::status::StatusCode;

pub fn structure_request(parts: &mut RequestParts) {
prepend_request_headers_prefix(parts)
}

pub fn destructure_response(parts: &mut ResponseParts) -> Result<(), failure::Error> {
set_response_status(parts)?;
strip_response_headers_prefix(parts)
}

fn prepend_request_headers_prefix(parts: &mut RequestParts) {
let mut headers: HeaderMap = HeaderMap::new();

for header in &parts.headers {
let (name, value) = header;
let forward_header = format!("{}{}", HEADER_PREFIX, name);
let header_name = HeaderName::from_bytes(forward_header.as_bytes())
.unwrap_or_else(|_| panic!("Could not create header name for {}", name));
headers.insert(header_name, value.clone());
}
parts.headers = headers;
}

fn strip_response_headers_prefix(parts: &mut ResponseParts) -> Result<(), failure::Error> {
let mut headers = HeaderMap::new();

for header in &parts.headers {
let (name, value) = header;
let name = name.as_str();
if name.starts_with(HEADER_PREFIX) {
let header_name = &name[HEADER_PREFIX.len()..];
let header_name = HeaderName::from_bytes(header_name.as_bytes())?;
headers.insert(header_name, value.clone());
}
}
parts.headers = headers;
Ok(())
}

fn set_response_status(parts: &mut ResponseParts) -> Result<(), failure::Error> {
let status = parts
.headers
.get("cf-ew-status")
.expect("Could not determine status code of response");
// status will be "404 not found" or "200 ok"
// we need to split that string to create hyper's status code
let status_vec: Vec<&str> = status.to_str()?.split(' ').collect();
parts.status = StatusCode::from_str(status_vec[0])?;
Ok(())
}
198 changes: 198 additions & 0 deletions src/commands/dev/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
mod server_config;
mod socket;
use server_config::ServerConfig;
mod headers;
use headers::{destructure_response, structure_request};
mod watch;
use watch::watch_for_changes;

use std::sync::{Arc, Mutex};
use std::thread;

use chrono::prelude::*;

use hyper::client::{HttpConnector, ResponseFuture};
use hyper::header::{HeaderName, HeaderValue};

use hyper::http::uri::InvalidUri;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Client as HyperClient, Request, Response, Server, Uri};

use hyper_tls::HttpsConnector;

use tokio::runtime::Runtime as TokioRuntime;

use uuid::Uuid;

use crate::commands;
use crate::commands::preview::upload;

use crate::settings::global_user::GlobalUser;
use crate::settings::toml::Target;

use crate::terminal::emoji;

const PREVIEW_HOST: &str = "rawhttp.cloudflareworkers.com";

pub fn dev(
target: Target,
user: Option<GlobalUser>,
host: Option<&str>,
port: Option<&str>,
ip: Option<&str>,
verbose: bool,
) -> Result<(), failure::Error> {
commands::build(&target)?;
let server_config = ServerConfig::new(host, ip, port)?;
let session_id = get_session_id()?;
let preview_id = get_preview_id(
target.clone(),
user.clone(),
&server_config,
&session_id.clone(),
verbose,
)?;
let preview_id = Arc::new(Mutex::new(preview_id));

{
let session_id = session_id.clone();
let preview_id = preview_id.clone();
let server_config = server_config.clone();
thread::spawn(move || {
watch_for_changes(
target,
user,
&server_config,
Arc::clone(&preview_id),
&session_id,
verbose,
)
});
}

let mut runtime = TokioRuntime::new()?;

let devtools_listener = socket::listen(&session_id);
let server = serve(server_config, Arc::clone(&preview_id));

let runners = futures::future::join(devtools_listener, server);

runtime.block_on(async {
let (devtools_listener, server) = runners.await;
devtools_listener?;
server
})
}

async fn serve(
server_config: ServerConfig,
preview_id: Arc<Mutex<String>>,
) -> Result<(), failure::Error> {
// set up https client to connect to the preview service
let https = HttpsConnector::new();
let client = HyperClient::builder().build::<_, Body>(https);

let listening_address = server_config.listening_address.clone();
// create a closure that hyper will use later to handle HTTP requests
let make_service = make_service_fn(move |_| {
let client = client.to_owned();
let preview_id = preview_id.lock().unwrap().to_owned();
let server_config = server_config.to_owned();
async move {
Ok::<_, failure::Error>(service_fn(move |req| {
let client = client.to_owned();
let preview_id = preview_id.to_owned();
let server_config = server_config.to_owned();
async move {
let resp =
preview_request(req, client, preview_id.to_owned(), server_config).await?;
let (mut parts, body) = resp.into_parts();

destructure_response(&mut parts)?;
let resp = Response::from_parts(parts, body);
Ok::<_, failure::Error>(resp)
}
}))
}
});

let server = Server::bind(&listening_address.address).serve(make_service);
println!("{} Listening on http://{}", emoji::EAR, listening_address);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
Ok(())
}

fn get_preview_url(path_string: &str) -> Result<Uri, InvalidUri> {
format!("https://{}{}", PREVIEW_HOST, path_string).parse()
}

fn get_path_as_str(uri: &Uri) -> String {
uri.path_and_query()
.map(|x| x.as_str())
.unwrap_or("")
.to_string()
}

fn preview_request(
req: Request<Body>,
client: HyperClient<HttpsConnector<HttpConnector>>,
preview_id: String,
server_config: ServerConfig,
) -> ResponseFuture {
let (mut parts, body) = req.into_parts();

let path = get_path_as_str(&parts.uri);
let method = parts.method.to_string();
let now: DateTime<Local> = Local::now();
let preview_id = &preview_id;

structure_request(&mut parts);

parts.headers.insert(
HeaderName::from_static("host"),
HeaderValue::from_static(PREVIEW_HOST),
);

parts.headers.insert(
HeaderName::from_static("cf-ew-preview"),
HeaderValue::from_str(preview_id).expect("Could not create header for preview id"),
);

parts.uri = get_preview_url(&path).expect("Could not get preview url");

let req = Request::from_parts(parts, body);

println!(
"[{}] \"{} {}{} {:?}\"",
now.format("%Y-%m-%d %H:%M:%S"),
method,
server_config.host,
path,
req.version()
);
client.request(req)
}

fn get_session_id() -> Result<String, failure::Error> {
Ok(Uuid::new_v4().to_simple().to_string())
}

pub fn get_preview_id(
mut target: Target,
user: Option<GlobalUser>,
server_config: &ServerConfig,
session_id: &str,
verbose: bool,
) -> Result<String, failure::Error> {
let sites_preview = false;
let script_id = upload(&mut target, user.as_ref(), sites_preview, verbose)?;
Ok(format!(
"{}{}{}{}",
&script_id,
session_id,
server_config.host.is_https() as u8,
server_config.host
))
}
51 changes: 51 additions & 0 deletions src/commands/dev/server_config/host.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::fmt;

use failure::format_err;
use url::Url;

#[derive(Clone)]
pub struct Host {
url: Url,
}

impl Host {
pub fn new(host: &str) -> Result<Self, failure::Error> {
// try to create a url from host
let url = match Url::parse(&host) {
Ok(host) => Ok(host),
// if it doesn't work, it might be because there was no scheme
// default to https
Err(_) => Url::parse(&format!("https://{}", host)),
}?;

// validate scheme
let scheme = url.scheme();
if scheme != "http" && scheme != "https" {
failure::bail!("Your host scheme must be either http or https")
}

// validate host
let host = url.host_str().ok_or_else(|| format_err!("Invalid host, accepted formats are example.com, http://example.com, or https://example.com"))?;

// recreate url without any trailing path
let url = Url::parse(&format!("{}://{}", scheme, host))?;
Ok(Host { url })
}

pub fn is_https(&self) -> bool {
self.url.scheme() == "https"
}
}

impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.url
.host_str()
.expect("could not parse host")
.to_string()
)
}
}
Loading

0 comments on commit ad3a473

Please sign in to comment.