diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2020-07-04 13:05:01 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-04 13:05:01 -0400 |
commit | 5f9e600c5bb78ae35a9a12d250f1d7cad79cf7a4 (patch) | |
tree | df0085e1287912f014bf804d2070f189a367b772 /test_util/src | |
parent | fca492907cb0e6b12f202681ccf36278b5bfa81a (diff) |
chore: port http_server.py to rust (#6364)
Diffstat (limited to 'test_util/src')
-rw-r--r-- | test_util/src/lib.rs | 375 | ||||
-rw-r--r-- | test_util/src/test_server.rs | 3 |
2 files changed, 363 insertions, 15 deletions
diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index f63ed71a7..67ced971a 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -3,10 +3,12 @@ #[macro_use] extern crate lazy_static; +use futures::future::{self, FutureExt}; use os_pipe::pipe; use regex::Regex; use std::io::Read; use std::io::Write; +use std::mem::replace; use std::path::PathBuf; use std::process::Child; use std::process::Command; @@ -15,6 +17,20 @@ use std::process::Stdio; use std::sync::Mutex; use std::sync::MutexGuard; use tempfile::TempDir; +use warp::http::Uri; +use warp::http::{HeaderValue, Response, StatusCode}; +use warp::hyper::Body; +use warp::reply::with_header; +use warp::reply::Reply; +use warp::Filter; + +const PORT: u16 = 4545; +const REDIRECT_PORT: u16 = 4546; +const ANOTHER_REDIRECT_PORT: u16 = 4547; +const DOUBLE_REDIRECTS_PORT: u16 = 4548; +const INF_REDIRECTS_PORT: u16 = 4549; +const REDIRECT_ABSOLUTE_PORT: u16 = 4550; +const HTTPS_PORT: u16 = 5545; pub const PERMISSION_VARIANTS: [&str; 5] = ["read", "write", "env", "net", "run"]; @@ -54,27 +70,358 @@ pub fn deno_exe_path() -> PathBuf { p } +pub fn test_server_path() -> PathBuf { + let mut p = target_dir().join("test_server"); + if cfg!(windows) { + p.set_extension("exe"); + } + p +} + +#[tokio::main] +pub async fn run_all_servers() { + let routes = warp::path::full().map(|path: warp::path::FullPath| { + let p = path.as_str(); + assert_eq!(&p[0..1], "/"); + let url = format!("http://localhost:{}{}", PORT, p); + let u = url.parse::<Uri>().unwrap(); + warp::redirect(u) + }); + let redirect_server_fut = + warp::serve(routes).bind(([127, 0, 0, 1], REDIRECT_PORT)); + + let routes = warp::path::full().map(|path: warp::path::FullPath| { + let p = path.as_str(); + assert_eq!(&p[0..1], "/"); + let url = format!("http://localhost:{}/cli/tests/subdir{}", PORT, p); + let u = url.parse::<Uri>().unwrap(); + warp::redirect(u) + }); + let another_redirect_server_fut = + warp::serve(routes).bind(([127, 0, 0, 1], ANOTHER_REDIRECT_PORT)); + + let routes = warp::path::full().map(|path: warp::path::FullPath| { + let p = path.as_str(); + assert_eq!(&p[0..1], "/"); + let url = format!("http://localhost:{}{}", REDIRECT_PORT, p); + let u = url.parse::<Uri>().unwrap(); + warp::redirect(u) + }); + let double_redirect_server_fut = + warp::serve(routes).bind(([127, 0, 0, 1], DOUBLE_REDIRECTS_PORT)); + + let routes = warp::path::full().map(|path: warp::path::FullPath| { + let p = path.as_str(); + assert_eq!(&p[0..1], "/"); + let url = format!("http://localhost:{}{}", INF_REDIRECTS_PORT, p); + let u = url.parse::<Uri>().unwrap(); + warp::redirect(u) + }); + let inf_redirect_server_fut = + warp::serve(routes).bind(([127, 0, 0, 1], INF_REDIRECTS_PORT)); + + // redirect server that redirect to absolute paths under same host + // redirects /REDIRECT/file_name to /file_name + let routes = warp::path("REDIRECT") + .and(warp::path::peek()) + .map(|path: warp::path::Peek| { + let p = path.as_str(); + let url = format!("/{}", p); + let u = url.parse::<Uri>().unwrap(); + warp::redirect(u) + }) + .or( + warp::any() + .and(warp::path::peek()) + .and(warp::fs::dir(root_path())) + .map(custom_headers), + ); + let absolute_redirect_server_fut = + warp::serve(routes).bind(([127, 0, 0, 1], REDIRECT_ABSOLUTE_PORT)); + + let echo_server = warp::path("echo_server") + .and(warp::post()) + .and(warp::body::bytes()) + .and(warp::header::optional::<String>("x-status")) + .and(warp::header::optional::<String>("content-type")) + .and(warp::header::optional::<String>("user-agent")) + .map( + |bytes: bytes::Bytes, + status: Option<String>, + content_type: Option<String>, + user_agent: Option<String>| + -> Box<dyn Reply> { + let mut res = Response::new(Body::from(bytes)); + if let Some(v) = status { + *res.status_mut() = StatusCode::from_bytes(v.as_bytes()).unwrap(); + } + let h = res.headers_mut(); + if let Some(v) = content_type { + h.insert("content-type", HeaderValue::from_str(&v).unwrap()); + } + if let Some(v) = user_agent { + h.insert("user-agent", HeaderValue::from_str(&v).unwrap()); + } + Box::new(res) + }, + ); + let echo_multipart_file = warp::path("echo_multipart_file") + .and(warp::post()) + .and(warp::body::bytes()) + .map(|bytes: bytes::Bytes| -> Box<dyn Reply> { + let start = b"--boundary\t \r\n\ + Content-Disposition: form-data; name=\"field_1\"\r\n\ + \r\n\ + value_1 \r\n\ + \r\n--boundary\r\n\ + Content-Disposition: form-data; name=\"file\"; \ + filename=\"file.bin\"\r\n\ + Content-Type: application/octet-stream\r\n\ + \r\n"; + let end = b"\r\n--boundary--\r\n"; + let b = [start as &[u8], &bytes, end].concat(); + + let mut res = Response::new(Body::from(b)); + let h = res.headers_mut(); + h.insert( + "content-type", + HeaderValue::from_static("multipart/form-data;boundary=boundary"), + ); + Box::new(res) + }); + let multipart_form_data = + warp::path("multipart_form_data.txt").map(|| -> Box<dyn Reply> { + let b = "Preamble\r\n\ + --boundary\t \r\n\ + Content-Disposition: form-data; name=\"field_1\"\r\n\ + \r\n\ + value_1 \r\n\ + \r\n--boundary\r\n\ + Content-Disposition: form-data; name=\"field_2\";\ + filename=\"file.js\"\r\n\ + Content-Type: text/javascript\r\n\ + \r\n\ + console.log(\"Hi\")\ + \r\n--boundary--\r\n\ + Epilogue"; + let mut res = Response::new(Body::from(b)); + res.headers_mut().insert( + "content-type", + HeaderValue::from_static("multipart/form-data;boundary=boundary"), + ); + Box::new(res) + }); + + let etag_script = warp::path!("etag_script.ts") + .and(warp::header::optional::<String>("if-none-match")) + .map(|if_none_match| -> Box<dyn Reply> { + if if_none_match == Some("33a64df551425fcc55e".to_string()) { + let r = + warp::reply::with_status(warp::reply(), StatusCode::NOT_MODIFIED); + let r = with_header(r, "Content-type", "application/typescript"); + let r = with_header(r, "ETag", "33a64df551425fcc55e"); + Box::new(r) + } else { + let mut res = Response::new(Body::from("console.log('etag')")); + let h = res.headers_mut(); + h.insert( + "Content-type", + HeaderValue::from_static("application/typescript"), + ); + h.insert("ETag", HeaderValue::from_static("33a64df551425fcc55e")); + Box::new(res) + } + }); + let xtypescripttypes = warp::path!("xTypeScriptTypes.js") + .map(|| { + let mut res = Response::new(Body::from("export const foo = 'foo';")); + let h = res.headers_mut(); + h.insert( + "Content-type", + HeaderValue::from_static("application/javascript"), + ); + h.insert( + "X-TypeScript-Types", + HeaderValue::from_static("./xTypeScriptTypes.d.ts"), + ); + res + }) + .or(warp::path!("xTypeScriptTypes.d.ts").map(|| { + let mut res = Response::new(Body::from("export const foo: 'foo';")); + res.headers_mut().insert( + "Content-type", + HeaderValue::from_static("application/typescript"), + ); + res + })) + .or(warp::path!("type_directives_redirect.js").map(|| { + let mut res = Response::new(Body::from("export const foo = 'foo';")); + let h = res.headers_mut(); + h.insert( + "Content-type", + HeaderValue::from_static("application/javascript"), + ); + h.insert( + "X-TypeScript-Types", + HeaderValue::from_static( + "http://localhost:4547/xTypeScriptTypesRedirect.d.ts", + ), + ); + res + })) + .or(warp::path!("cli"/"tests"/"subdir"/"xTypeScriptTypesRedirect.d.ts").map(|| { + let mut res = Response::new(Body::from( + "import './xTypeScriptTypesRedirected.d.ts';", + )); + let h = res.headers_mut(); + h.insert( + "Content-type", + HeaderValue::from_static("application/typescript"), + ); + res + })) + .or(warp::path!("cli"/"tests"/"subdir"/"xTypeScriptTypesRedirected.d.ts").map(|| { + let mut res = Response::new(Body::from("export const foo: 'foo';")); + let h = res.headers_mut(); + h.insert( + "Content-type", + HeaderValue::from_static("application/typescript"), + ); + res + })) + .or(warp::path!("referenceTypes.js").map(|| { + let mut res = Response::new(Body::from("/// <reference types=\"./xTypeScriptTypes.d.ts\" />\r\nexport const foo = \"foo\";\r\n")); + let h = res.headers_mut(); + h.insert( + "Content-type", + HeaderValue::from_static("application/javascript"), + ); + res + })); + + let content_type_handler = warp::any() + .and(warp::path::peek()) + .and(warp::fs::dir(root_path())) + .map(custom_headers) + .or(etag_script) + .or(xtypescripttypes) + .or(echo_server) + .or(echo_multipart_file) + .or(multipart_form_data); + + let http_fut = + warp::serve(content_type_handler.clone()).bind(([127, 0, 0, 1], PORT)); + + let https_fut = warp::serve(content_type_handler.clone()) + .tls() + .cert_path("std/http/testdata/tls/localhost.crt") + .key_path("std/http/testdata/tls/localhost.key") + .bind(([127, 0, 0, 1], HTTPS_PORT)); + + let mut server_fut = async { + futures::join!( + http_fut, + https_fut, + redirect_server_fut, + another_redirect_server_fut, + inf_redirect_server_fut, + double_redirect_server_fut, + absolute_redirect_server_fut, + ) + } + .boxed(); + + let mut did_print_ready = false; + future::poll_fn(move |cx| { + let poll_result = server_fut.poll_unpin(cx); + if !replace(&mut did_print_ready, true) { + println!("ready"); + } + poll_result + }) + .await; +} + +fn custom_headers(path: warp::path::Peek, f: warp::fs::File) -> Box<dyn Reply> { + let p = path.as_str(); + + if p.ends_with("cli/tests/x_deno_warning.js") { + let f = with_header(f, "Content-Type", "application/javascript"); + let f = with_header(f, "X-Deno-Warning", "foobar"); + return Box::new(f); + } + if p.ends_with("cli/tests/053_import_compression/brotli") { + let f = with_header(f, "Content-Encoding", "br"); + let f = with_header(f, "Content-Type", "application/javascript"); + let f = with_header(f, "Content-Length", "26"); + return Box::new(f); + } + if p.ends_with("cli/tests/053_import_compression/gziped") { + let f = with_header(f, "Content-Encoding", "gzip"); + let f = with_header(f, "Content-Type", "application/javascript"); + let f = with_header(f, "Content-Length", "39"); + return Box::new(f); + } + + let content_type = if p.contains(".t1.") { + Some("text/typescript") + } else if p.contains(".t2.") { + Some("video/vnd.dlna.mpeg-tts") + } else if p.contains(".t3.") { + Some("video/mp2t") + } else if p.contains(".t4.") { + Some("application/x-typescript") + } else if p.contains(".j1.") { + Some("text/javascript") + } else if p.contains(".j2.") { + Some("application/ecmascript") + } else if p.contains(".j3.") { + Some("text/ecmascript") + } else if p.contains(".j4.") { + Some("application/x-javascript") + } else if p.contains("form_urlencoded") { + Some("application/x-www-form-urlencoded") + } else if p.contains("unknown_ext") || p.contains("no_ext") { + Some("text/typescript") + } else if p.contains("mismatch_ext") { + Some("text/javascript") + } else if p.ends_with(".ts") || p.ends_with(".tsx") { + Some("application/typescript") + } else if p.ends_with(".js") || p.ends_with(".jsx") { + Some("application/javascript") + } else if p.ends_with(".json") { + Some("application/json") + } else { + None + }; + + if let Some(t) = content_type { + Box::new(with_header(f, "Content-Type", t)) + } else { + Box::new(f) + } +} + pub struct HttpServerGuard<'a> { #[allow(dead_code)] g: MutexGuard<'a, ()>, - child: Child, + test_server: Child, } impl<'a> Drop for HttpServerGuard<'a> { fn drop(&mut self) { - match self.child.try_wait() { + match self.test_server.try_wait() { Ok(None) => { - self.child.kill().expect("failed to kill http_server.py"); - } - Ok(Some(status)) => { - panic!("http_server.py exited unexpectedly {}", status) + self.test_server.kill().expect("failed to kill test_server"); + let _ = self.test_server.wait(); } - Err(e) => panic!("http_server.py err {}", e), + Ok(Some(status)) => panic!("test_server exited unexpectedly {}", status), + Err(e) => panic!("test_server error: {}", e), } } } -/// Starts tools/http_server.py when the returned guard is dropped, the server +/// Starts target/debug/test_server when the returned guard is dropped, the server /// will be killed. pub fn http_server<'a>() -> HttpServerGuard<'a> { // TODO(bartlomieju) Allow tests to use the http server in parallel. @@ -86,18 +433,16 @@ pub fn http_server<'a>() -> HttpServerGuard<'a> { r.unwrap() }; - println!("tools/http_server.py starting..."); - let mut child = Command::new("python") + println!("test_server starting..."); + let mut test_server = Command::new(test_server_path()) .current_dir(root_path()) - .args(&["-u", "tools/http_server.py"]) .stdout(Stdio::piped()) .spawn() - .expect("failed to execute child"); + .expect("failed to execute test_server"); - let stdout = child.stdout.as_mut().unwrap(); + let stdout = test_server.stdout.as_mut().unwrap(); use std::io::{BufRead, BufReader}; let lines = BufReader::new(stdout).lines(); - // Wait for "ready" on stdout. See tools/http_server.py for maybe_line in lines { if let Ok(line) = maybe_line { if line.starts_with("ready") { @@ -108,7 +453,7 @@ pub fn http_server<'a>() -> HttpServerGuard<'a> { } } - HttpServerGuard { child, g } + HttpServerGuard { test_server, g } } /// Helper function to strip ansi codes. diff --git a/test_util/src/test_server.rs b/test_util/src/test_server.rs new file mode 100644 index 000000000..ed958a94a --- /dev/null +++ b/test_util/src/test_server.rs @@ -0,0 +1,3 @@ +fn main() { + test_util::run_all_servers(); +} |