2022-01-07 22:09:52 -05:00
|
|
|
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
2020-07-06 13:00:08 -04:00
|
|
|
// Usage: provide a port as argument to run hyper_hello benchmark server
|
|
|
|
// otherwise this starts multiple servers on many ports for test endpoints.
|
2021-12-06 18:48:11 -05:00
|
|
|
use anyhow::anyhow;
|
2020-12-24 08:11:32 -05:00
|
|
|
use futures::FutureExt;
|
|
|
|
use futures::Stream;
|
|
|
|
use futures::StreamExt;
|
|
|
|
use hyper::header::HeaderValue;
|
2021-01-10 07:20:47 -05:00
|
|
|
use hyper::server::Server;
|
2020-12-24 08:11:32 -05:00
|
|
|
use hyper::service::make_service_fn;
|
|
|
|
use hyper::service::service_fn;
|
|
|
|
use hyper::Body;
|
|
|
|
use hyper::Request;
|
|
|
|
use hyper::Response;
|
|
|
|
use hyper::StatusCode;
|
2021-03-26 12:34:25 -04:00
|
|
|
use lazy_static::lazy_static;
|
2020-06-18 11:54:55 -04:00
|
|
|
use os_pipe::pipe;
|
2022-03-15 18:15:56 -04:00
|
|
|
use pretty_assertions::assert_eq;
|
2020-06-18 11:54:55 -04:00
|
|
|
use regex::Regex;
|
2021-12-06 18:48:11 -05:00
|
|
|
use rustls::Certificate;
|
|
|
|
use rustls::PrivateKey;
|
2021-01-17 11:40:29 -05:00
|
|
|
use serde::Serialize;
|
2020-08-28 09:03:50 -04:00
|
|
|
use std::collections::HashMap;
|
2021-01-10 07:20:47 -05:00
|
|
|
use std::convert::Infallible;
|
2020-07-06 13:00:08 -04:00
|
|
|
use std::env;
|
2020-12-24 08:11:32 -05:00
|
|
|
use std::io;
|
2020-06-18 11:54:55 -04:00
|
|
|
use std::io::Read;
|
|
|
|
use std::io::Write;
|
2021-01-19 09:39:04 -05:00
|
|
|
use std::mem::replace;
|
2020-12-24 08:11:32 -05:00
|
|
|
use std::net::SocketAddr;
|
2022-04-01 11:15:37 -04:00
|
|
|
use std::ops::Deref;
|
|
|
|
use std::ops::DerefMut;
|
2020-06-18 11:54:55 -04:00
|
|
|
use std::path::PathBuf;
|
2020-12-24 08:11:32 -05:00
|
|
|
use std::pin::Pin;
|
2020-06-18 11:54:55 -04:00
|
|
|
use std::process::Child;
|
|
|
|
use std::process::Command;
|
|
|
|
use std::process::Output;
|
|
|
|
use std::process::Stdio;
|
2020-12-24 08:11:32 -05:00
|
|
|
use std::result::Result;
|
|
|
|
use std::sync::Arc;
|
2020-06-18 11:54:55 -04:00
|
|
|
use std::sync::Mutex;
|
2020-06-25 08:53:13 -04:00
|
|
|
use std::sync::MutexGuard;
|
2020-12-24 08:11:32 -05:00
|
|
|
use std::task::Context;
|
|
|
|
use std::task::Poll;
|
2021-08-09 09:55:00 -04:00
|
|
|
use tokio::io::AsyncWriteExt;
|
2020-12-24 08:11:32 -05:00
|
|
|
use tokio::net::TcpListener;
|
|
|
|
use tokio::net::TcpStream;
|
2021-12-06 18:48:11 -05:00
|
|
|
use tokio_rustls::rustls;
|
2020-12-24 08:11:32 -05:00
|
|
|
use tokio_rustls::TlsAcceptor;
|
|
|
|
use tokio_tungstenite::accept_async;
|
2020-07-04 13:05:01 -04:00
|
|
|
|
2022-06-21 15:25:07 -04:00
|
|
|
pub mod assertions;
|
2021-05-17 16:45:13 -04:00
|
|
|
pub mod lsp;
|
2021-09-20 22:15:44 -04:00
|
|
|
pub mod pty;
|
2022-04-01 11:15:37 -04:00
|
|
|
mod temp_dir;
|
|
|
|
|
|
|
|
pub use temp_dir::TempDir;
|
2021-05-17 16:45:13 -04:00
|
|
|
|
2020-07-04 13:05:01 -04:00
|
|
|
const PORT: u16 = 4545;
|
2021-02-15 21:50:27 -05:00
|
|
|
const TEST_AUTH_TOKEN: &str = "abcdef123456789";
|
2021-09-08 00:18:11 -04:00
|
|
|
const TEST_BASIC_AUTH_USERNAME: &str = "testuser123";
|
|
|
|
const TEST_BASIC_AUTH_PASSWORD: &str = "testpassabc";
|
2020-07-04 13:05:01 -04:00
|
|
|
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;
|
2021-02-15 21:50:27 -05:00
|
|
|
const AUTH_REDIRECT_PORT: u16 = 4551;
|
2021-08-09 09:55:00 -04:00
|
|
|
const TLS_CLIENT_AUTH_PORT: u16 = 4552;
|
2021-09-08 00:18:11 -04:00
|
|
|
const BASIC_AUTH_REDIRECT_PORT: u16 = 4554;
|
2021-09-30 03:26:15 -04:00
|
|
|
const TLS_PORT: u16 = 4557;
|
2020-07-04 13:05:01 -04:00
|
|
|
const HTTPS_PORT: u16 = 5545;
|
2021-10-25 12:41:06 -04:00
|
|
|
const H1_ONLY_PORT: u16 = 5546;
|
|
|
|
const H2_ONLY_PORT: u16 = 5547;
|
2021-08-25 08:25:12 -04:00
|
|
|
const HTTPS_CLIENT_AUTH_PORT: u16 = 5552;
|
2020-09-05 10:39:25 -04:00
|
|
|
const WS_PORT: u16 = 4242;
|
|
|
|
const WSS_PORT: u16 = 4243;
|
2021-02-21 11:51:46 -05:00
|
|
|
const WS_CLOSE_PORT: u16 = 4244;
|
2020-06-18 11:54:55 -04:00
|
|
|
|
|
|
|
pub const PERMISSION_VARIANTS: [&str; 5] =
|
|
|
|
["read", "write", "env", "net", "run"];
|
|
|
|
pub const PERMISSION_DENIED_PATTERN: &str = "PermissionDenied";
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
// STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate.
|
|
|
|
// Copyright 2017 Armin Ronacher <armin.ronacher@active-4.com>. MIT License.
|
|
|
|
static ref STRIP_ANSI_RE: Regex = Regex::new(
|
|
|
|
r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]"
|
|
|
|
).unwrap();
|
|
|
|
|
2020-07-06 13:07:15 -04:00
|
|
|
static ref GUARD: Mutex<HttpServerCount> = Mutex::new(HttpServerCount::default());
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn root_path() -> PathBuf {
|
2021-06-10 12:35:38 -04:00
|
|
|
PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR")))
|
|
|
|
.parent()
|
|
|
|
.unwrap()
|
|
|
|
.to_path_buf()
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
|
2020-08-28 09:03:50 -04:00
|
|
|
pub fn prebuilt_path() -> PathBuf {
|
|
|
|
third_party_path().join("prebuilt")
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:54:55 -04:00
|
|
|
pub fn tests_path() -> PathBuf {
|
|
|
|
root_path().join("cli").join("tests")
|
|
|
|
}
|
|
|
|
|
2021-08-11 10:20:47 -04:00
|
|
|
pub fn testdata_path() -> PathBuf {
|
|
|
|
tests_path().join("testdata")
|
|
|
|
}
|
|
|
|
|
2020-08-28 09:03:50 -04:00
|
|
|
pub fn third_party_path() -> PathBuf {
|
|
|
|
root_path().join("third_party")
|
|
|
|
}
|
|
|
|
|
2021-11-24 06:24:13 -05:00
|
|
|
pub fn std_path() -> PathBuf {
|
|
|
|
root_path().join("test_util").join("std")
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:54:55 -04:00
|
|
|
pub fn target_dir() -> PathBuf {
|
|
|
|
let current_exe = std::env::current_exe().unwrap();
|
|
|
|
let target_dir = current_exe.parent().unwrap().parent().unwrap();
|
|
|
|
target_dir.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deno_exe_path() -> PathBuf {
|
|
|
|
// Something like /Users/rld/src/deno/target/debug/deps/deno
|
|
|
|
let mut p = target_dir().join("deno");
|
|
|
|
if cfg!(windows) {
|
|
|
|
p.set_extension("exe");
|
|
|
|
}
|
|
|
|
p
|
|
|
|
}
|
|
|
|
|
2020-08-28 09:03:50 -04:00
|
|
|
pub fn prebuilt_tool_path(tool: &str) -> PathBuf {
|
|
|
|
let mut exe = tool.to_string();
|
|
|
|
exe.push_str(if cfg!(windows) { ".exe" } else { "" });
|
|
|
|
prebuilt_path().join(platform_dir_name()).join(exe)
|
|
|
|
}
|
|
|
|
|
2022-06-29 07:27:19 -04:00
|
|
|
pub fn platform_dir_name() -> &'static str {
|
2020-08-28 09:03:50 -04:00
|
|
|
if cfg!(target_os = "linux") {
|
|
|
|
"linux64"
|
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
"mac"
|
|
|
|
} else if cfg!(target_os = "windows") {
|
|
|
|
"win"
|
|
|
|
} else {
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-04 13:05:01 -04:00
|
|
|
pub fn test_server_path() -> PathBuf {
|
|
|
|
let mut p = target_dir().join("test_server");
|
|
|
|
if cfg!(windows) {
|
|
|
|
p.set_extension("exe");
|
|
|
|
}
|
|
|
|
p
|
|
|
|
}
|
|
|
|
|
2021-06-08 17:56:54 -04:00
|
|
|
fn ensure_test_server_built() {
|
|
|
|
// if the test server doesn't exist then remind the developer to build first
|
|
|
|
if !test_server_path().exists() {
|
|
|
|
panic!(
|
|
|
|
"Test server not found. Please cargo build before running the tests."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-06 13:00:08 -04:00
|
|
|
/// Benchmark server that just serves "hello world" responses.
|
|
|
|
async fn hyper_hello(port: u16) {
|
|
|
|
println!("hyper hello");
|
2020-12-24 08:11:32 -05:00
|
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
|
|
|
let hello_svc = make_service_fn(|_| async move {
|
2021-01-10 07:20:47 -05:00
|
|
|
Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
|
|
|
|
Ok::<_, Infallible>(Response::new(Body::from("Hello World!")))
|
|
|
|
}))
|
2020-12-24 08:11:32 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
let server = Server::bind(&addr).serve(hello_svc);
|
|
|
|
if let Err(e) = server.await {
|
|
|
|
eprintln!("server error: {}", e);
|
|
|
|
}
|
2020-07-06 13:00:08 -04:00
|
|
|
}
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
fn redirect_resp(url: String) -> Response<Body> {
|
|
|
|
let mut redirect_resp = Response::new(Body::empty());
|
|
|
|
*redirect_resp.status_mut() = StatusCode::MOVED_PERMANENTLY;
|
|
|
|
redirect_resp.headers_mut().insert(
|
|
|
|
hyper::header::LOCATION,
|
|
|
|
HeaderValue::from_str(&url[..]).unwrap(),
|
|
|
|
);
|
|
|
|
|
|
|
|
redirect_resp
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn redirect(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
|
|
|
let p = req.uri().path();
|
|
|
|
assert_eq!(&p[0..1], "/");
|
|
|
|
let url = format!("http://localhost:{}{}", PORT, p);
|
|
|
|
|
|
|
|
Ok(redirect_resp(url))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn double_redirects(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
|
|
|
let p = req.uri().path();
|
|
|
|
assert_eq!(&p[0..1], "/");
|
|
|
|
let url = format!("http://localhost:{}{}", REDIRECT_PORT, p);
|
|
|
|
|
|
|
|
Ok(redirect_resp(url))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn inf_redirects(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
|
|
|
let p = req.uri().path();
|
|
|
|
assert_eq!(&p[0..1], "/");
|
|
|
|
let url = format!("http://localhost:{}{}", INF_REDIRECTS_PORT, p);
|
|
|
|
|
|
|
|
Ok(redirect_resp(url))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn another_redirect(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
|
|
|
let p = req.uri().path();
|
|
|
|
assert_eq!(&p[0..1], "/");
|
2021-08-11 10:20:47 -04:00
|
|
|
let url = format!("http://localhost:{}/subdir{}", PORT, p);
|
2020-12-24 08:11:32 -05:00
|
|
|
|
|
|
|
Ok(redirect_resp(url))
|
|
|
|
}
|
|
|
|
|
2021-02-15 21:50:27 -05:00
|
|
|
async fn auth_redirect(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
|
|
|
if let Some(auth) = req
|
|
|
|
.headers()
|
|
|
|
.get("authorization")
|
|
|
|
.map(|v| v.to_str().unwrap())
|
|
|
|
{
|
|
|
|
if auth.to_lowercase() == format!("bearer {}", TEST_AUTH_TOKEN) {
|
|
|
|
let p = req.uri().path();
|
|
|
|
assert_eq!(&p[0..1], "/");
|
|
|
|
let url = format!("http://localhost:{}{}", PORT, p);
|
|
|
|
return Ok(redirect_resp(url));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut resp = Response::new(Body::empty());
|
|
|
|
*resp.status_mut() = StatusCode::NOT_FOUND;
|
|
|
|
Ok(resp)
|
|
|
|
}
|
|
|
|
|
2021-09-08 00:18:11 -04:00
|
|
|
async fn basic_auth_redirect(
|
|
|
|
req: Request<Body>,
|
|
|
|
) -> hyper::Result<Response<Body>> {
|
|
|
|
if let Some(auth) = req
|
|
|
|
.headers()
|
|
|
|
.get("authorization")
|
|
|
|
.map(|v| v.to_str().unwrap())
|
|
|
|
{
|
|
|
|
let credentials =
|
|
|
|
format!("{}:{}", TEST_BASIC_AUTH_USERNAME, TEST_BASIC_AUTH_PASSWORD);
|
|
|
|
if auth == format!("Basic {}", base64::encode(credentials)) {
|
|
|
|
let p = req.uri().path();
|
|
|
|
assert_eq!(&p[0..1], "/");
|
|
|
|
let url = format!("http://localhost:{}{}", PORT, p);
|
|
|
|
return Ok(redirect_resp(url));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut resp = Response::new(Body::empty());
|
|
|
|
*resp.status_mut() = StatusCode::NOT_FOUND;
|
|
|
|
Ok(resp)
|
|
|
|
}
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
async fn run_ws_server(addr: &SocketAddr) {
|
2021-01-10 07:20:47 -05:00
|
|
|
let listener = TcpListener::bind(addr).await.unwrap();
|
2021-08-09 09:55:00 -04:00
|
|
|
println!("ready: ws"); // Eye catcher for HttpServerCount
|
2020-12-24 08:11:32 -05:00
|
|
|
while let Ok((stream, _addr)) = listener.accept().await {
|
|
|
|
tokio::spawn(async move {
|
|
|
|
let ws_stream_fut = accept_async(stream);
|
|
|
|
|
|
|
|
let ws_stream = ws_stream_fut.await;
|
|
|
|
if let Ok(ws_stream) = ws_stream {
|
|
|
|
let (tx, rx) = ws_stream.split();
|
|
|
|
rx.forward(tx)
|
|
|
|
.map(|result| {
|
|
|
|
if let Err(e) = result {
|
|
|
|
println!("websocket server error: {:?}", e);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
});
|
2020-07-06 13:00:08 -04:00
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
}
|
2020-07-06 13:00:08 -04:00
|
|
|
|
2021-02-21 11:51:46 -05:00
|
|
|
async fn run_ws_close_server(addr: &SocketAddr) {
|
|
|
|
let listener = TcpListener::bind(addr).await.unwrap();
|
|
|
|
while let Ok((stream, _addr)) = listener.accept().await {
|
|
|
|
tokio::spawn(async move {
|
|
|
|
let ws_stream_fut = accept_async(stream);
|
|
|
|
|
|
|
|
let ws_stream = ws_stream_fut.await;
|
|
|
|
if let Ok(mut ws_stream) = ws_stream {
|
|
|
|
ws_stream.close(None).await.unwrap();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-25 12:41:06 -04:00
|
|
|
enum SupportedHttpVersions {
|
|
|
|
All,
|
|
|
|
Http1Only,
|
|
|
|
Http2Only,
|
|
|
|
}
|
|
|
|
impl Default for SupportedHttpVersions {
|
|
|
|
fn default() -> SupportedHttpVersions {
|
|
|
|
SupportedHttpVersions::All
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
async fn get_tls_config(
|
|
|
|
cert: &str,
|
|
|
|
key: &str,
|
2021-08-09 09:55:00 -04:00
|
|
|
ca: &str,
|
2021-10-25 12:41:06 -04:00
|
|
|
http_versions: SupportedHttpVersions,
|
2020-12-24 08:11:32 -05:00
|
|
|
) -> io::Result<Arc<rustls::ServerConfig>> {
|
2021-08-11 10:20:47 -04:00
|
|
|
let cert_path = testdata_path().join(cert);
|
|
|
|
let key_path = testdata_path().join(key);
|
|
|
|
let ca_path = testdata_path().join(ca);
|
2020-12-24 08:11:32 -05:00
|
|
|
|
|
|
|
let cert_file = std::fs::File::open(cert_path)?;
|
|
|
|
let key_file = std::fs::File::open(key_path)?;
|
2021-08-09 09:55:00 -04:00
|
|
|
let ca_file = std::fs::File::open(ca_path)?;
|
2020-12-24 08:11:32 -05:00
|
|
|
|
2021-12-06 18:48:11 -05:00
|
|
|
let certs: Vec<Certificate> = {
|
|
|
|
let mut cert_reader = io::BufReader::new(cert_file);
|
|
|
|
rustls_pemfile::certs(&mut cert_reader)
|
|
|
|
.unwrap()
|
|
|
|
.into_iter()
|
|
|
|
.map(Certificate)
|
|
|
|
.collect()
|
|
|
|
};
|
2021-08-09 09:55:00 -04:00
|
|
|
|
|
|
|
let mut ca_cert_reader = io::BufReader::new(ca_file);
|
2021-12-06 18:48:11 -05:00
|
|
|
let ca_cert = rustls_pemfile::certs(&mut ca_cert_reader)
|
2021-08-09 09:55:00 -04:00
|
|
|
.expect("Cannot load CA certificate")
|
|
|
|
.remove(0);
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
let mut key_reader = io::BufReader::new(key_file);
|
|
|
|
let key = {
|
2021-12-06 18:48:11 -05:00
|
|
|
let pkcs8_key = rustls_pemfile::pkcs8_private_keys(&mut key_reader)
|
|
|
|
.expect("Cannot load key file");
|
|
|
|
let rsa_key = rustls_pemfile::rsa_private_keys(&mut key_reader)
|
2020-12-24 08:11:32 -05:00
|
|
|
.expect("Cannot load key file");
|
|
|
|
if !pkcs8_key.is_empty() {
|
|
|
|
Some(pkcs8_key[0].clone())
|
|
|
|
} else if !rsa_key.is_empty() {
|
|
|
|
Some(rsa_key[0].clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
match key {
|
|
|
|
Some(key) => {
|
2021-08-09 09:55:00 -04:00
|
|
|
let mut root_cert_store = rustls::RootCertStore::empty();
|
2021-12-06 18:48:11 -05:00
|
|
|
root_cert_store.add(&rustls::Certificate(ca_cert)).unwrap();
|
|
|
|
|
2021-08-09 09:55:00 -04:00
|
|
|
// Allow (but do not require) client authentication.
|
2021-12-06 18:48:11 -05:00
|
|
|
|
|
|
|
let mut config = rustls::ServerConfig::builder()
|
|
|
|
.with_safe_defaults()
|
|
|
|
.with_client_cert_verifier(
|
|
|
|
rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(
|
|
|
|
root_cert_store,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.with_single_cert(certs, PrivateKey(key))
|
2022-03-29 13:33:00 -04:00
|
|
|
.map_err(|e| anyhow!("Error setting cert: {:?}", e))
|
2021-12-06 18:48:11 -05:00
|
|
|
.unwrap();
|
|
|
|
|
2021-10-25 12:41:06 -04:00
|
|
|
match http_versions {
|
|
|
|
SupportedHttpVersions::All => {
|
2021-12-06 18:48:11 -05:00
|
|
|
config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
|
2021-10-25 12:41:06 -04:00
|
|
|
}
|
|
|
|
SupportedHttpVersions::Http1Only => {}
|
|
|
|
SupportedHttpVersions::Http2Only => {
|
2021-12-06 18:48:11 -05:00
|
|
|
config.alpn_protocols = vec!["h2".into()];
|
2021-10-25 12:41:06 -04:00
|
|
|
}
|
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
|
2021-06-17 15:56:30 -04:00
|
|
|
Ok(Arc::new(config))
|
2020-12-24 08:11:32 -05:00
|
|
|
}
|
2021-06-17 15:56:30 -04:00
|
|
|
None => Err(io::Error::new(io::ErrorKind::Other, "Cannot find key")),
|
2020-12-24 08:11:32 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn run_wss_server(addr: &SocketAddr) {
|
2021-08-11 10:20:47 -04:00
|
|
|
let cert_file = "tls/localhost.crt";
|
|
|
|
let key_file = "tls/localhost.key";
|
|
|
|
let ca_cert_file = "tls/RootCA.pem";
|
2020-12-24 08:11:32 -05:00
|
|
|
|
2021-10-25 12:41:06 -04:00
|
|
|
let tls_config =
|
|
|
|
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2020-12-24 08:11:32 -05:00
|
|
|
let tls_acceptor = TlsAcceptor::from(tls_config);
|
2021-01-10 07:20:47 -05:00
|
|
|
let listener = TcpListener::bind(addr).await.unwrap();
|
2021-08-09 09:55:00 -04:00
|
|
|
println!("ready: wss"); // Eye catcher for HttpServerCount
|
2020-12-24 08:11:32 -05:00
|
|
|
|
|
|
|
while let Ok((stream, _addr)) = listener.accept().await {
|
|
|
|
let acceptor = tls_acceptor.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
match acceptor.accept(stream).await {
|
|
|
|
Ok(tls_stream) => {
|
|
|
|
let ws_stream_fut = accept_async(tls_stream);
|
|
|
|
let ws_stream = ws_stream_fut.await;
|
|
|
|
if let Ok(ws_stream) = ws_stream {
|
|
|
|
let (tx, rx) = ws_stream.split();
|
|
|
|
rx.forward(tx)
|
|
|
|
.map(|result| {
|
|
|
|
if let Err(e) = result {
|
|
|
|
println!("Websocket server error: {:?}", e);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
Err(e) => {
|
|
|
|
eprintln!("TLS accept error: {:?}", e);
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-09 09:55:00 -04:00
|
|
|
/// This server responds with 'PASS' if client authentication was successful. Try it by running
|
|
|
|
/// test_server and
|
2021-08-11 10:20:47 -04:00
|
|
|
/// curl --key cli/tests/testdata/tls/localhost.key \
|
|
|
|
/// --cert cli/tests/testsdata/tls/localhost.crt \
|
|
|
|
/// --cacert cli/tests/testdata/tls/RootCA.crt https://localhost:4552/
|
2021-08-09 09:55:00 -04:00
|
|
|
async fn run_tls_client_auth_server() {
|
2021-08-11 10:20:47 -04:00
|
|
|
let cert_file = "tls/localhost.crt";
|
|
|
|
let key_file = "tls/localhost.key";
|
|
|
|
let ca_cert_file = "tls/RootCA.pem";
|
2021-10-25 12:41:06 -04:00
|
|
|
let tls_config =
|
|
|
|
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2021-08-09 09:55:00 -04:00
|
|
|
let tls_acceptor = TlsAcceptor::from(tls_config);
|
|
|
|
|
|
|
|
// Listen on ALL addresses that localhost can resolves to.
|
|
|
|
let accept = |listener: tokio::net::TcpListener| {
|
|
|
|
async {
|
|
|
|
let result = listener.accept().await;
|
|
|
|
Some((result, listener))
|
|
|
|
}
|
|
|
|
.boxed()
|
|
|
|
};
|
|
|
|
|
|
|
|
let host_and_port = &format!("localhost:{}", TLS_CLIENT_AUTH_PORT);
|
|
|
|
|
|
|
|
let listeners = tokio::net::lookup_host(host_and_port)
|
|
|
|
.await
|
|
|
|
.expect(host_and_port)
|
|
|
|
.inspect(|address| println!("{} -> {}", host_and_port, address))
|
|
|
|
.map(tokio::net::TcpListener::bind)
|
|
|
|
.collect::<futures::stream::FuturesUnordered<_>>()
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.await
|
|
|
|
.into_iter()
|
|
|
|
.map(|s| s.unwrap())
|
|
|
|
.map(|listener| futures::stream::unfold(listener, accept))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
println!("ready: tls client auth"); // Eye catcher for HttpServerCount
|
|
|
|
|
|
|
|
let mut listeners = futures::stream::select_all(listeners);
|
|
|
|
|
|
|
|
while let Some(Ok((stream, _addr))) = listeners.next().await {
|
|
|
|
let acceptor = tls_acceptor.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
match acceptor.accept(stream).await {
|
|
|
|
Ok(mut tls_stream) => {
|
|
|
|
let (_, tls_session) = tls_stream.get_mut();
|
|
|
|
// We only need to check for the presence of client certificates
|
|
|
|
// here. Rusttls ensures that they are valid and signed by the CA.
|
2021-12-06 18:48:11 -05:00
|
|
|
let response = match tls_session.peer_certificates() {
|
2021-08-09 09:55:00 -04:00
|
|
|
Some(_certs) => b"PASS",
|
|
|
|
None => b"FAIL",
|
|
|
|
};
|
|
|
|
tls_stream.write_all(response).await.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("TLS accept error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-30 03:26:15 -04:00
|
|
|
/// This server responds with 'PASS' if client authentication was successful. Try it by running
|
|
|
|
/// test_server and
|
|
|
|
/// curl --cacert cli/tests/testdata/tls/RootCA.crt https://localhost:4553/
|
|
|
|
async fn run_tls_server() {
|
|
|
|
let cert_file = "tls/localhost.crt";
|
|
|
|
let key_file = "tls/localhost.key";
|
|
|
|
let ca_cert_file = "tls/RootCA.pem";
|
2021-10-25 12:41:06 -04:00
|
|
|
let tls_config =
|
|
|
|
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2021-09-30 03:26:15 -04:00
|
|
|
let tls_acceptor = TlsAcceptor::from(tls_config);
|
|
|
|
|
|
|
|
// Listen on ALL addresses that localhost can resolves to.
|
|
|
|
let accept = |listener: tokio::net::TcpListener| {
|
|
|
|
async {
|
|
|
|
let result = listener.accept().await;
|
|
|
|
Some((result, listener))
|
|
|
|
}
|
|
|
|
.boxed()
|
|
|
|
};
|
|
|
|
|
|
|
|
let host_and_port = &format!("localhost:{}", TLS_PORT);
|
|
|
|
|
|
|
|
let listeners = tokio::net::lookup_host(host_and_port)
|
|
|
|
.await
|
|
|
|
.expect(host_and_port)
|
|
|
|
.inspect(|address| println!("{} -> {}", host_and_port, address))
|
|
|
|
.map(tokio::net::TcpListener::bind)
|
|
|
|
.collect::<futures::stream::FuturesUnordered<_>>()
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.await
|
|
|
|
.into_iter()
|
|
|
|
.map(|s| s.unwrap())
|
|
|
|
.map(|listener| futures::stream::unfold(listener, accept))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
println!("ready: tls"); // Eye catcher for HttpServerCount
|
|
|
|
|
|
|
|
let mut listeners = futures::stream::select_all(listeners);
|
|
|
|
|
|
|
|
while let Some(Ok((stream, _addr))) = listeners.next().await {
|
|
|
|
let acceptor = tls_acceptor.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
match acceptor.accept(stream).await {
|
|
|
|
Ok(mut tls_stream) => {
|
|
|
|
tls_stream.write_all(b"PASS").await.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("TLS accept error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
async fn absolute_redirect(
|
|
|
|
req: Request<Body>,
|
|
|
|
) -> hyper::Result<Response<Body>> {
|
|
|
|
let path = req.uri().path();
|
|
|
|
|
|
|
|
if path.starts_with("/REDIRECT") {
|
|
|
|
let url = &req.uri().path()[9..];
|
|
|
|
println!("URL: {:?}", url);
|
|
|
|
let redirect = redirect_resp(url.to_string());
|
|
|
|
return Ok(redirect);
|
|
|
|
}
|
|
|
|
|
|
|
|
if path.starts_with("/a/b/c") {
|
|
|
|
if let Some(x_loc) = req.headers().get("x-location") {
|
|
|
|
let loc = x_loc.to_str().unwrap();
|
|
|
|
return Ok(redirect_resp(loc.to_string()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-11 10:20:47 -04:00
|
|
|
let mut file_path = testdata_path();
|
2020-12-24 08:11:32 -05:00
|
|
|
file_path.push(&req.uri().path()[1..]);
|
|
|
|
if file_path.is_dir() || !file_path.exists() {
|
|
|
|
let mut not_found_resp = Response::new(Body::empty());
|
|
|
|
*not_found_resp.status_mut() = StatusCode::NOT_FOUND;
|
|
|
|
return Ok(not_found_resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
let file = tokio::fs::read(file_path).await.unwrap();
|
|
|
|
let file_resp = custom_headers(req.uri().path(), file);
|
2021-06-17 15:56:30 -04:00
|
|
|
Ok(file_resp)
|
2020-12-24 08:11:32 -05:00
|
|
|
}
|
|
|
|
|
2021-11-08 20:26:39 -05:00
|
|
|
async fn main_server(
|
|
|
|
req: Request<Body>,
|
|
|
|
) -> Result<Response<Body>, hyper::http::Error> {
|
2020-12-24 08:11:32 -05:00
|
|
|
return match (req.method(), req.uri().path()) {
|
|
|
|
(&hyper::Method::POST, "/echo_server") => {
|
|
|
|
let (parts, body) = req.into_parts();
|
|
|
|
let mut response = Response::new(body);
|
|
|
|
|
|
|
|
if let Some(status) = parts.headers.get("x-status") {
|
|
|
|
*response.status_mut() =
|
|
|
|
StatusCode::from_bytes(status.as_bytes()).unwrap();
|
|
|
|
}
|
|
|
|
if let Some(content_type) = parts.headers.get("content-type") {
|
|
|
|
response
|
|
|
|
.headers_mut()
|
|
|
|
.insert("content-type", content_type.clone());
|
|
|
|
}
|
|
|
|
if let Some(user_agent) = parts.headers.get("user-agent") {
|
|
|
|
response
|
|
|
|
.headers_mut()
|
|
|
|
.insert("user-agent", user_agent.clone());
|
|
|
|
}
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
(&hyper::Method::POST, "/echo_multipart_file") => {
|
|
|
|
let body = req.into_body();
|
|
|
|
let bytes = &hyper::body::to_bytes(body).await.unwrap()[0..];
|
2020-07-04 13:05:01 -04:00
|
|
|
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";
|
2020-12-24 08:11:32 -05:00
|
|
|
let b = [start as &[u8], bytes, end].concat();
|
2020-07-04 13:05:01 -04:00
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
let mut response = Response::new(Body::from(b));
|
|
|
|
response.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"content-type",
|
|
|
|
HeaderValue::from_static("multipart/form-data;boundary=boundary"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
(_, "/multipart_form_data.txt") => {
|
2020-07-04 13:05:01 -04:00
|
|
|
let b = "Preamble\r\n\
|
2020-12-24 08:11:32 -05:00
|
|
|
--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";
|
2020-07-04 13:05:01 -04:00
|
|
|
let mut res = Response::new(Body::from(b));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"content-type",
|
|
|
|
HeaderValue::from_static("multipart/form-data;boundary=boundary"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
2020-12-30 17:46:08 -05:00
|
|
|
(_, "/multipart_form_bad_content_type") => {
|
|
|
|
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-datatststs;boundary=boundary"),
|
|
|
|
);
|
|
|
|
Ok(res)
|
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
(_, "/bad_redirect") => {
|
|
|
|
let mut res = Response::new(Body::empty());
|
|
|
|
*res.status_mut() = StatusCode::FOUND;
|
|
|
|
Ok(res)
|
|
|
|
}
|
2022-02-01 21:04:26 -05:00
|
|
|
(_, "/x_deno_warning.js") => {
|
|
|
|
let mut res = Response::new(Body::empty());
|
|
|
|
*res.status_mut() = StatusCode::MOVED_PERMANENTLY;
|
|
|
|
res
|
|
|
|
.headers_mut()
|
|
|
|
.insert("X-Deno-Warning", HeaderValue::from_static("foobar"));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"location",
|
|
|
|
HeaderValue::from_bytes(b"/x_deno_warning_redirect.js").unwrap(),
|
|
|
|
);
|
|
|
|
Ok(res)
|
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
(_, "/non_ascii_redirect") => {
|
2020-12-09 10:48:06 -05:00
|
|
|
let mut res = Response::new(Body::empty());
|
|
|
|
*res.status_mut() = StatusCode::MOVED_PERMANENTLY;
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"location",
|
|
|
|
HeaderValue::from_bytes(b"/redirect\xae").unwrap(),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/etag_script.ts") => {
|
|
|
|
let if_none_match = req.headers().get("if-none-match");
|
|
|
|
if if_none_match == Some(&HeaderValue::from_static("33a64df551425fcc55e"))
|
|
|
|
{
|
|
|
|
let mut resp = Response::new(Body::empty());
|
|
|
|
*resp.status_mut() = StatusCode::NOT_MODIFIED;
|
|
|
|
resp.headers_mut().insert(
|
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
|
|
|
resp
|
|
|
|
.headers_mut()
|
|
|
|
.insert("ETag", HeaderValue::from_static("33a64df551425fcc55e"));
|
2020-07-04 13:05:01 -04:00
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(resp)
|
2020-07-04 13:05:01 -04:00
|
|
|
} else {
|
2020-12-24 08:11:32 -05:00
|
|
|
let mut resp = Response::new(Body::from("console.log('etag')"));
|
|
|
|
resp.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
resp
|
|
|
|
.headers_mut()
|
|
|
|
.insert("ETag", HeaderValue::from_static("33a64df551425fcc55e"));
|
|
|
|
Ok(resp)
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
}
|
|
|
|
(_, "/xTypeScriptTypes.js") => {
|
2020-07-04 13:05:01 -04:00
|
|
|
let mut res = Response::new(Body::from("export const foo = 'foo';"));
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"X-TypeScript-Types",
|
|
|
|
HeaderValue::from_static("./xTypeScriptTypes.d.ts"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
2021-05-12 19:07:22 -04:00
|
|
|
(_, "/xTypeScriptTypes.jsx") => {
|
|
|
|
let mut res = Response::new(Body::from("export const foo = 'foo';"));
|
|
|
|
res
|
|
|
|
.headers_mut()
|
|
|
|
.insert("Content-type", HeaderValue::from_static("text/jsx"));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"X-TypeScript-Types",
|
|
|
|
HeaderValue::from_static("./xTypeScriptTypes.d.ts"),
|
|
|
|
);
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/xTypeScriptTypes.ts") => {
|
|
|
|
let mut res =
|
|
|
|
Response::new(Body::from("export const foo: string = 'foo';"));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"X-TypeScript-Types",
|
|
|
|
HeaderValue::from_static("./xTypeScriptTypes.d.ts"),
|
|
|
|
);
|
|
|
|
Ok(res)
|
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
(_, "/xTypeScriptTypes.d.ts") => {
|
2020-07-04 13:05:01 -04:00
|
|
|
let mut res = Response::new(Body::from("export const foo: 'foo';"));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/type_directives_redirect.js") => {
|
2020-07-04 13:05:01 -04:00
|
|
|
let mut res = Response::new(Body::from("export const foo = 'foo';"));
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"X-TypeScript-Types",
|
|
|
|
HeaderValue::from_static(
|
|
|
|
"http://localhost:4547/xTypeScriptTypesRedirect.d.ts",
|
|
|
|
),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/type_headers_deno_types.foo.js") => {
|
|
|
|
let mut res = Response::new(Body::from(
|
|
|
|
"export function foo(text) { console.log(text); }",
|
|
|
|
));
|
|
|
|
res.headers_mut().insert(
|
2020-07-24 08:21:36 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-24 08:21:36 -04:00
|
|
|
"X-TypeScript-Types",
|
|
|
|
HeaderValue::from_static(
|
|
|
|
"http://localhost:4545/type_headers_deno_types.d.ts",
|
|
|
|
),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/type_headers_deno_types.d.ts") => {
|
|
|
|
let mut res =
|
|
|
|
Response::new(Body::from("export function foo(text: number): void;"));
|
|
|
|
res.headers_mut().insert(
|
2020-07-24 08:21:36 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/type_headers_deno_types.foo.d.ts") => {
|
|
|
|
let mut res =
|
|
|
|
Response::new(Body::from("export function foo(text: string): void;"));
|
|
|
|
res.headers_mut().insert(
|
2020-07-24 08:21:36 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
2021-08-11 10:20:47 -04:00
|
|
|
(_, "/subdir/xTypeScriptTypesRedirect.d.ts") => {
|
2020-07-04 13:05:01 -04:00
|
|
|
let mut res = Response::new(Body::from(
|
|
|
|
"import './xTypeScriptTypesRedirected.d.ts';",
|
|
|
|
));
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
2021-08-11 10:20:47 -04:00
|
|
|
(_, "/subdir/xTypeScriptTypesRedirected.d.ts") => {
|
2020-07-04 13:05:01 -04:00
|
|
|
let mut res = Response::new(Body::from("export const foo: 'foo';"));
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/referenceTypes.js") => {
|
2020-07-04 13:05:01 -04:00
|
|
|
let mut res = Response::new(Body::from("/// <reference types=\"./xTypeScriptTypes.d.ts\" />\r\nexport const foo = \"foo\";\r\n"));
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-07-04 13:05:01 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
2021-08-11 10:20:47 -04:00
|
|
|
(_, "/subdir/file_with_:_in_name.ts") => {
|
2020-08-03 08:55:03 -04:00
|
|
|
let mut res = Response::new(Body::from(
|
|
|
|
"console.log('Hello from file_with_:_in_name.ts');",
|
|
|
|
));
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-08-03 08:55:03 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
2022-01-31 04:32:49 -05:00
|
|
|
(_, "/v1/extensionless") => {
|
|
|
|
let mut res =
|
|
|
|
Response::new(Body::from(r#"export * from "/subdir/mod1.ts";"#));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"content-type",
|
|
|
|
HeaderValue::from_static("application/typescript"),
|
|
|
|
);
|
|
|
|
Ok(res)
|
|
|
|
}
|
2021-08-11 10:20:47 -04:00
|
|
|
(_, "/subdir/no_js_ext@1.0.0") => {
|
2020-10-25 16:17:58 -04:00
|
|
|
let mut res = Response::new(Body::from(
|
|
|
|
r#"import { printHello } from "./mod2.ts";
|
|
|
|
printHello();
|
|
|
|
"#,
|
|
|
|
));
|
2020-12-24 08:11:32 -05:00
|
|
|
res.headers_mut().insert(
|
2020-10-25 16:17:58 -04:00
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
Ok(res)
|
|
|
|
}
|
2021-04-08 21:27:27 -04:00
|
|
|
(_, "/.well-known/deno-import-intellisense.json") => {
|
2021-08-11 10:20:47 -04:00
|
|
|
let file_path =
|
|
|
|
testdata_path().join("lsp/registries/deno-import-intellisense.json");
|
2021-04-08 21:27:27 -04:00
|
|
|
if let Ok(body) = tokio::fs::read(file_path).await {
|
|
|
|
Ok(custom_headers(
|
|
|
|
"/.well-known/deno-import-intellisense.json",
|
|
|
|
body,
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
Ok(Response::new(Body::empty()))
|
|
|
|
}
|
|
|
|
}
|
2021-10-25 12:41:06 -04:00
|
|
|
(_, "/http_version") => {
|
|
|
|
let version = format!("{:?}", req.version());
|
|
|
|
Ok(Response::new(version.into()))
|
|
|
|
}
|
2021-11-09 06:10:40 -05:00
|
|
|
(_, "/content_length") => {
|
|
|
|
let content_length = format!("{:?}", req.headers().get("content-length"));
|
|
|
|
Ok(Response::new(content_length.into()))
|
|
|
|
}
|
2021-11-08 20:26:39 -05:00
|
|
|
(_, "/jsx/jsx-runtime") | (_, "/jsx/jsx-dev-runtime") => {
|
|
|
|
let mut res = Response::new(Body::from(
|
|
|
|
r#"export function jsx(
|
|
|
|
_type,
|
|
|
|
_props,
|
|
|
|
_key,
|
|
|
|
_source,
|
|
|
|
_self,
|
|
|
|
) {}
|
|
|
|
export const jsxs = jsx;
|
|
|
|
export const jsxDEV = jsx;
|
|
|
|
export const Fragment = Symbol("Fragment");
|
|
|
|
console.log("imported", import.meta.url);
|
|
|
|
"#,
|
|
|
|
));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"Content-type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
|
|
|
Ok(res)
|
|
|
|
}
|
2021-12-09 06:16:17 -05:00
|
|
|
(_, "/dynamic") => {
|
|
|
|
let mut res = Response::new(Body::from(
|
|
|
|
serde_json::to_string_pretty(&std::time::SystemTime::now()).unwrap(),
|
|
|
|
));
|
|
|
|
res
|
|
|
|
.headers_mut()
|
|
|
|
.insert("cache-control", HeaderValue::from_static("no-cache"));
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
(_, "/dynamic_cache") => {
|
|
|
|
let mut res = Response::new(Body::from(
|
|
|
|
serde_json::to_string_pretty(&std::time::SystemTime::now()).unwrap(),
|
|
|
|
));
|
|
|
|
res.headers_mut().insert(
|
|
|
|
"cache-control",
|
|
|
|
HeaderValue::from_static("public, max-age=604800, immutable"),
|
|
|
|
);
|
|
|
|
Ok(res)
|
|
|
|
}
|
2021-12-20 21:40:22 -05:00
|
|
|
(_, "/echo_accept") => {
|
|
|
|
let accept = req.headers().get("accept").map(|v| v.to_str().unwrap());
|
|
|
|
let res = Response::new(Body::from(
|
|
|
|
serde_json::json!({ "accept": accept }).to_string(),
|
|
|
|
));
|
|
|
|
Ok(res)
|
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
_ => {
|
2021-08-11 10:20:47 -04:00
|
|
|
let mut file_path = testdata_path();
|
2020-12-24 08:11:32 -05:00
|
|
|
file_path.push(&req.uri().path()[1..]);
|
2022-08-20 11:31:33 -04:00
|
|
|
if let Ok(file) = tokio::fs::read(&file_path).await {
|
2021-08-11 10:20:47 -04:00
|
|
|
let file_resp = custom_headers(req.uri().path(), file);
|
2020-12-24 08:11:32 -05:00
|
|
|
return Ok(file_resp);
|
|
|
|
}
|
|
|
|
|
2022-08-20 11:31:33 -04:00
|
|
|
// serve npm registry files
|
|
|
|
if req.uri().path().starts_with("/npm/registry/") {
|
|
|
|
let is_tarball = req.uri().path().ends_with(".tgz");
|
|
|
|
if !is_tarball {
|
|
|
|
file_path.push("registry.json");
|
|
|
|
}
|
|
|
|
if let Ok(file) = tokio::fs::read(&file_path).await {
|
|
|
|
let file_resp = custom_headers(req.uri().path(), file);
|
|
|
|
return Ok(file_resp);
|
|
|
|
} else if should_download_npm_packages() {
|
|
|
|
if let Err(err) =
|
|
|
|
download_npm_registry_file(&file_path, is_tarball).await
|
|
|
|
{
|
|
|
|
return Response::builder()
|
|
|
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
|
|
.body(format!("{:#}", err).into());
|
|
|
|
};
|
|
|
|
|
|
|
|
// serve the file
|
|
|
|
if let Ok(file) = tokio::fs::read(&file_path).await {
|
|
|
|
let file_resp = custom_headers(req.uri().path(), file);
|
|
|
|
return Ok(file_resp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-08 20:26:39 -05:00
|
|
|
Response::builder()
|
|
|
|
.status(StatusCode::NOT_FOUND)
|
|
|
|
.body(Body::empty())
|
2020-12-24 08:11:32 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-08-20 11:31:33 -04:00
|
|
|
fn should_download_npm_packages() -> bool {
|
|
|
|
// when this env var is set, it will download and save npm packages
|
|
|
|
// to the testdata/npm/registry directory
|
|
|
|
std::env::var("DENO_TEST_UTIL_UPDATE_NPM") == Ok("1".to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn download_npm_registry_file(
|
|
|
|
file_path: &PathBuf,
|
|
|
|
is_tarball: bool,
|
|
|
|
) -> Result<(), anyhow::Error> {
|
|
|
|
let package_name = file_path
|
|
|
|
.parent()
|
|
|
|
.unwrap()
|
|
|
|
.file_name()
|
|
|
|
.unwrap()
|
|
|
|
.to_string_lossy();
|
|
|
|
let url = if is_tarball {
|
|
|
|
let file_name = file_path.file_name().unwrap().to_string_lossy();
|
|
|
|
format!(
|
|
|
|
"https://registry.npmjs.org/{}/-/{}",
|
|
|
|
package_name, file_name
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!("https://registry.npmjs.org/{}", package_name)
|
|
|
|
};
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
let response = client.get(url).send().await?;
|
|
|
|
let bytes = response.bytes().await?;
|
|
|
|
let bytes = if is_tarball {
|
|
|
|
bytes.to_vec()
|
|
|
|
} else {
|
|
|
|
String::from_utf8(bytes.to_vec())
|
|
|
|
.unwrap()
|
|
|
|
.replace(
|
|
|
|
&format!("https://registry.npmjs.org/{}/-/", package_name),
|
|
|
|
&format!("http://localhost:4545/npm/registry/{}/", package_name),
|
|
|
|
)
|
|
|
|
.into_bytes()
|
|
|
|
};
|
|
|
|
std::fs::create_dir_all(file_path.parent().unwrap())?;
|
|
|
|
std::fs::write(&file_path, bytes)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
/// Taken from example in https://github.com/ctz/hyper-rustls/blob/a02ef72a227dcdf102f86e905baa7415c992e8b3/examples/server.rs
|
|
|
|
struct HyperAcceptor<'a> {
|
|
|
|
acceptor: Pin<
|
|
|
|
Box<
|
|
|
|
dyn Stream<Item = io::Result<tokio_rustls::server::TlsStream<TcpStream>>>
|
|
|
|
+ 'a,
|
|
|
|
>,
|
|
|
|
>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl hyper::server::accept::Accept for HyperAcceptor<'_> {
|
|
|
|
type Conn = tokio_rustls::server::TlsStream<TcpStream>;
|
|
|
|
type Error = io::Error;
|
|
|
|
|
|
|
|
fn poll_accept(
|
|
|
|
mut self: Pin<&mut Self>,
|
|
|
|
cx: &mut Context,
|
|
|
|
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
|
|
|
Pin::new(&mut self.acceptor).poll_next(cx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-15 01:10:12 -05:00
|
|
|
#[allow(clippy::non_send_fields_in_send_ty)]
|
2022-08-21 13:31:14 -04:00
|
|
|
// SAFETY: unsafe trait must have unsafe implementation
|
2020-12-24 08:11:32 -05:00
|
|
|
unsafe impl std::marker::Send for HyperAcceptor<'_> {}
|
|
|
|
|
|
|
|
async fn wrap_redirect_server() {
|
|
|
|
let redirect_svc =
|
2021-01-10 07:20:47 -05:00
|
|
|
make_service_fn(|_| async { Ok::<_, Infallible>(service_fn(redirect)) });
|
2020-12-24 08:11:32 -05:00
|
|
|
let redirect_addr = SocketAddr::from(([127, 0, 0, 1], REDIRECT_PORT));
|
|
|
|
let redirect_server = Server::bind(&redirect_addr).serve(redirect_svc);
|
|
|
|
if let Err(e) = redirect_server.await {
|
|
|
|
eprintln!("Redirect error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wrap_double_redirect_server() {
|
|
|
|
let double_redirects_svc = make_service_fn(|_| async {
|
2021-01-10 07:20:47 -05:00
|
|
|
Ok::<_, Infallible>(service_fn(double_redirects))
|
2020-12-24 08:11:32 -05:00
|
|
|
});
|
|
|
|
let double_redirects_addr =
|
|
|
|
SocketAddr::from(([127, 0, 0, 1], DOUBLE_REDIRECTS_PORT));
|
|
|
|
let double_redirects_server =
|
|
|
|
Server::bind(&double_redirects_addr).serve(double_redirects_svc);
|
|
|
|
if let Err(e) = double_redirects_server.await {
|
|
|
|
eprintln!("Double redirect error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wrap_inf_redirect_server() {
|
|
|
|
let inf_redirects_svc = make_service_fn(|_| async {
|
2021-01-10 07:20:47 -05:00
|
|
|
Ok::<_, Infallible>(service_fn(inf_redirects))
|
2020-12-24 08:11:32 -05:00
|
|
|
});
|
|
|
|
let inf_redirects_addr =
|
|
|
|
SocketAddr::from(([127, 0, 0, 1], INF_REDIRECTS_PORT));
|
|
|
|
let inf_redirects_server =
|
|
|
|
Server::bind(&inf_redirects_addr).serve(inf_redirects_svc);
|
|
|
|
if let Err(e) = inf_redirects_server.await {
|
|
|
|
eprintln!("Inf redirect error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wrap_another_redirect_server() {
|
|
|
|
let another_redirect_svc = make_service_fn(|_| async {
|
2021-01-10 07:20:47 -05:00
|
|
|
Ok::<_, Infallible>(service_fn(another_redirect))
|
2020-12-24 08:11:32 -05:00
|
|
|
});
|
|
|
|
let another_redirect_addr =
|
|
|
|
SocketAddr::from(([127, 0, 0, 1], ANOTHER_REDIRECT_PORT));
|
|
|
|
let another_redirect_server =
|
|
|
|
Server::bind(&another_redirect_addr).serve(another_redirect_svc);
|
|
|
|
if let Err(e) = another_redirect_server.await {
|
|
|
|
eprintln!("Another redirect error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-15 21:50:27 -05:00
|
|
|
async fn wrap_auth_redirect_server() {
|
|
|
|
let auth_redirect_svc = make_service_fn(|_| async {
|
|
|
|
Ok::<_, Infallible>(service_fn(auth_redirect))
|
|
|
|
});
|
|
|
|
let auth_redirect_addr =
|
|
|
|
SocketAddr::from(([127, 0, 0, 1], AUTH_REDIRECT_PORT));
|
|
|
|
let auth_redirect_server =
|
|
|
|
Server::bind(&auth_redirect_addr).serve(auth_redirect_svc);
|
|
|
|
if let Err(e) = auth_redirect_server.await {
|
|
|
|
eprintln!("Auth redirect error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-08 00:18:11 -04:00
|
|
|
async fn wrap_basic_auth_redirect_server() {
|
|
|
|
let basic_auth_redirect_svc = make_service_fn(|_| async {
|
|
|
|
Ok::<_, Infallible>(service_fn(basic_auth_redirect))
|
|
|
|
});
|
|
|
|
let basic_auth_redirect_addr =
|
|
|
|
SocketAddr::from(([127, 0, 0, 1], BASIC_AUTH_REDIRECT_PORT));
|
|
|
|
let basic_auth_redirect_server =
|
|
|
|
Server::bind(&basic_auth_redirect_addr).serve(basic_auth_redirect_svc);
|
|
|
|
if let Err(e) = basic_auth_redirect_server.await {
|
|
|
|
eprintln!("Basic auth redirect error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
async fn wrap_abs_redirect_server() {
|
|
|
|
let abs_redirect_svc = make_service_fn(|_| async {
|
2021-01-10 07:20:47 -05:00
|
|
|
Ok::<_, Infallible>(service_fn(absolute_redirect))
|
2020-12-24 08:11:32 -05:00
|
|
|
});
|
|
|
|
let abs_redirect_addr =
|
|
|
|
SocketAddr::from(([127, 0, 0, 1], REDIRECT_ABSOLUTE_PORT));
|
|
|
|
let abs_redirect_server =
|
|
|
|
Server::bind(&abs_redirect_addr).serve(abs_redirect_svc);
|
|
|
|
if let Err(e) = abs_redirect_server.await {
|
|
|
|
eprintln!("Absolute redirect error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wrap_main_server() {
|
2021-01-10 07:20:47 -05:00
|
|
|
let main_server_svc =
|
|
|
|
make_service_fn(|_| async { Ok::<_, Infallible>(service_fn(main_server)) });
|
2020-12-24 08:11:32 -05:00
|
|
|
let main_server_addr = SocketAddr::from(([127, 0, 0, 1], PORT));
|
|
|
|
let main_server = Server::bind(&main_server_addr).serve(main_server_svc);
|
|
|
|
if let Err(e) = main_server.await {
|
|
|
|
eprintln!("HTTP server error: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wrap_main_https_server() {
|
|
|
|
let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], HTTPS_PORT));
|
2021-08-11 10:20:47 -04:00
|
|
|
let cert_file = "tls/localhost.crt";
|
|
|
|
let key_file = "tls/localhost.key";
|
|
|
|
let ca_cert_file = "tls/RootCA.pem";
|
2021-10-25 12:41:06 -04:00
|
|
|
let tls_config =
|
|
|
|
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
loop {
|
|
|
|
let tcp = TcpListener::bind(&main_server_https_addr)
|
|
|
|
.await
|
|
|
|
.expect("Cannot bind TCP");
|
|
|
|
println!("ready: https"); // Eye catcher for HttpServerCount
|
|
|
|
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
|
|
|
|
// Prepare a long-running future stream to accept and serve cients.
|
|
|
|
let incoming_tls_stream = async_stream::stream! {
|
|
|
|
loop {
|
|
|
|
let (socket, _) = tcp.accept().await?;
|
|
|
|
let stream = tls_acceptor.accept(socket);
|
|
|
|
yield stream.await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed();
|
|
|
|
|
|
|
|
let main_server_https_svc = make_service_fn(|_| async {
|
|
|
|
Ok::<_, Infallible>(service_fn(main_server))
|
|
|
|
});
|
|
|
|
let main_server_https = Server::builder(HyperAcceptor {
|
|
|
|
acceptor: incoming_tls_stream,
|
|
|
|
})
|
|
|
|
.serve(main_server_https_svc);
|
|
|
|
|
|
|
|
//continue to prevent TLS error stopping the server
|
|
|
|
if main_server_https.await.is_err() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wrap_https_h1_only_server() {
|
|
|
|
let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], H1_ONLY_PORT));
|
|
|
|
let cert_file = "tls/localhost.crt";
|
|
|
|
let key_file = "tls/localhost.key";
|
|
|
|
let ca_cert_file = "tls/RootCA.pem";
|
|
|
|
let tls_config = get_tls_config(
|
|
|
|
cert_file,
|
|
|
|
key_file,
|
|
|
|
ca_cert_file,
|
|
|
|
SupportedHttpVersions::Http1Only,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2020-12-24 08:11:32 -05:00
|
|
|
loop {
|
2021-01-10 07:20:47 -05:00
|
|
|
let tcp = TcpListener::bind(&main_server_https_addr)
|
|
|
|
.await
|
|
|
|
.expect("Cannot bind TCP");
|
2021-08-09 09:55:00 -04:00
|
|
|
println!("ready: https"); // Eye catcher for HttpServerCount
|
2020-12-24 08:11:32 -05:00
|
|
|
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
|
|
|
|
// Prepare a long-running future stream to accept and serve cients.
|
2021-01-10 07:20:47 -05:00
|
|
|
let incoming_tls_stream = async_stream::stream! {
|
|
|
|
loop {
|
|
|
|
let (socket, _) = tcp.accept().await?;
|
|
|
|
let stream = tls_acceptor.accept(socket);
|
|
|
|
yield stream.await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed();
|
2020-12-24 08:11:32 -05:00
|
|
|
|
|
|
|
let main_server_https_svc = make_service_fn(|_| async {
|
2021-01-10 07:20:47 -05:00
|
|
|
Ok::<_, Infallible>(service_fn(main_server))
|
2020-12-24 08:11:32 -05:00
|
|
|
});
|
|
|
|
let main_server_https = Server::builder(HyperAcceptor {
|
|
|
|
acceptor: incoming_tls_stream,
|
|
|
|
})
|
2021-10-25 12:41:06 -04:00
|
|
|
.http1_only(true)
|
|
|
|
.serve(main_server_https_svc);
|
|
|
|
|
|
|
|
//continue to prevent TLS error stopping the server
|
|
|
|
if main_server_https.await.is_err() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn wrap_https_h2_only_server() {
|
|
|
|
let main_server_https_addr = SocketAddr::from(([127, 0, 0, 1], H2_ONLY_PORT));
|
|
|
|
let cert_file = "tls/localhost.crt";
|
|
|
|
let key_file = "tls/localhost.key";
|
|
|
|
let ca_cert_file = "tls/RootCA.pem";
|
|
|
|
let tls_config = get_tls_config(
|
|
|
|
cert_file,
|
|
|
|
key_file,
|
|
|
|
ca_cert_file,
|
|
|
|
SupportedHttpVersions::Http2Only,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
loop {
|
|
|
|
let tcp = TcpListener::bind(&main_server_https_addr)
|
|
|
|
.await
|
|
|
|
.expect("Cannot bind TCP");
|
|
|
|
println!("ready: https"); // Eye catcher for HttpServerCount
|
|
|
|
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
|
|
|
|
// Prepare a long-running future stream to accept and serve cients.
|
|
|
|
let incoming_tls_stream = async_stream::stream! {
|
|
|
|
loop {
|
|
|
|
let (socket, _) = tcp.accept().await?;
|
|
|
|
let stream = tls_acceptor.accept(socket);
|
|
|
|
yield stream.await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed();
|
|
|
|
|
|
|
|
let main_server_https_svc = make_service_fn(|_| async {
|
|
|
|
Ok::<_, Infallible>(service_fn(main_server))
|
|
|
|
});
|
|
|
|
let main_server_https = Server::builder(HyperAcceptor {
|
|
|
|
acceptor: incoming_tls_stream,
|
|
|
|
})
|
|
|
|
.http2_only(true)
|
2020-12-24 08:11:32 -05:00
|
|
|
.serve(main_server_https_svc);
|
|
|
|
|
|
|
|
//continue to prevent TLS error stopping the server
|
|
|
|
if main_server_https.await.is_err() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-25 08:25:12 -04:00
|
|
|
async fn wrap_client_auth_https_server() {
|
|
|
|
let main_server_https_addr =
|
|
|
|
SocketAddr::from(([127, 0, 0, 1], HTTPS_CLIENT_AUTH_PORT));
|
|
|
|
let cert_file = "tls/localhost.crt";
|
|
|
|
let key_file = "tls/localhost.key";
|
|
|
|
let ca_cert_file = "tls/RootCA.pem";
|
2021-10-25 12:41:06 -04:00
|
|
|
let tls_config =
|
|
|
|
get_tls_config(cert_file, key_file, ca_cert_file, Default::default())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2021-08-25 08:25:12 -04:00
|
|
|
loop {
|
|
|
|
let tcp = TcpListener::bind(&main_server_https_addr)
|
|
|
|
.await
|
|
|
|
.expect("Cannot bind TCP");
|
|
|
|
println!("ready: https_client_auth on :{:?}", HTTPS_CLIENT_AUTH_PORT); // Eye catcher for HttpServerCount
|
|
|
|
let tls_acceptor = TlsAcceptor::from(tls_config.clone());
|
|
|
|
// Prepare a long-running future stream to accept and serve cients.
|
|
|
|
let incoming_tls_stream = async_stream::stream! {
|
|
|
|
loop {
|
|
|
|
let (socket, _) = tcp.accept().await?;
|
|
|
|
|
|
|
|
match tls_acceptor.accept(socket).await {
|
|
|
|
Ok(mut tls_stream) => {
|
|
|
|
let (_, tls_session) = tls_stream.get_mut();
|
|
|
|
// We only need to check for the presence of client certificates
|
|
|
|
// here. Rusttls ensures that they are valid and signed by the CA.
|
2021-12-06 18:48:11 -05:00
|
|
|
match tls_session.peer_certificates() {
|
2021-08-25 08:25:12 -04:00
|
|
|
Some(_certs) => { yield Ok(tls_stream); },
|
|
|
|
None => { eprintln!("https_client_auth: no valid client certificate"); },
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("https-client-auth accept error: {:?}", e);
|
|
|
|
yield Err(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.boxed();
|
|
|
|
|
|
|
|
let main_server_https_svc = make_service_fn(|_| async {
|
|
|
|
Ok::<_, Infallible>(service_fn(main_server))
|
|
|
|
});
|
|
|
|
let main_server_https = Server::builder(HyperAcceptor {
|
|
|
|
acceptor: incoming_tls_stream,
|
|
|
|
})
|
|
|
|
.serve(main_server_https_svc);
|
|
|
|
|
|
|
|
//continue to prevent TLS error stopping the server
|
|
|
|
if main_server_https.await.is_err() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-02 05:27:16 -05:00
|
|
|
// Use the single-threaded scheduler. The hyper server is used as a point of
|
|
|
|
// comparison for the (single-threaded!) benchmarks in cli/bench. We're not
|
|
|
|
// comparing apples to apples if we use the default multi-threaded scheduler.
|
2021-01-10 07:20:47 -05:00
|
|
|
#[tokio::main(flavor = "current_thread")]
|
2020-12-24 08:11:32 -05:00
|
|
|
pub async fn run_all_servers() {
|
|
|
|
if let Some(port) = env::args().nth(1) {
|
|
|
|
return hyper_hello(port.parse::<u16>().unwrap()).await;
|
|
|
|
}
|
|
|
|
|
|
|
|
let redirect_server_fut = wrap_redirect_server();
|
|
|
|
let double_redirects_server_fut = wrap_double_redirect_server();
|
|
|
|
let inf_redirects_server_fut = wrap_inf_redirect_server();
|
|
|
|
let another_redirect_server_fut = wrap_another_redirect_server();
|
2021-02-15 21:50:27 -05:00
|
|
|
let auth_redirect_server_fut = wrap_auth_redirect_server();
|
2021-09-08 00:18:11 -04:00
|
|
|
let basic_auth_redirect_server_fut = wrap_basic_auth_redirect_server();
|
2020-12-24 08:11:32 -05:00
|
|
|
let abs_redirect_server_fut = wrap_abs_redirect_server();
|
|
|
|
|
|
|
|
let ws_addr = SocketAddr::from(([127, 0, 0, 1], WS_PORT));
|
|
|
|
let ws_server_fut = run_ws_server(&ws_addr);
|
|
|
|
let wss_addr = SocketAddr::from(([127, 0, 0, 1], WSS_PORT));
|
|
|
|
let wss_server_fut = run_wss_server(&wss_addr);
|
2021-02-21 11:51:46 -05:00
|
|
|
let ws_close_addr = SocketAddr::from(([127, 0, 0, 1], WS_CLOSE_PORT));
|
|
|
|
let ws_close_server_fut = run_ws_close_server(&ws_close_addr);
|
2020-12-24 08:11:32 -05:00
|
|
|
|
2021-09-30 03:26:15 -04:00
|
|
|
let tls_server_fut = run_tls_server();
|
2021-08-09 09:55:00 -04:00
|
|
|
let tls_client_auth_server_fut = run_tls_client_auth_server();
|
2021-08-25 08:25:12 -04:00
|
|
|
let client_auth_server_https_fut = wrap_client_auth_https_server();
|
2020-12-24 08:11:32 -05:00
|
|
|
let main_server_fut = wrap_main_server();
|
|
|
|
let main_server_https_fut = wrap_main_https_server();
|
2021-10-25 12:41:06 -04:00
|
|
|
let h1_only_server_fut = wrap_https_h1_only_server();
|
|
|
|
let h2_only_server_fut = wrap_https_h2_only_server();
|
2020-07-04 13:05:01 -04:00
|
|
|
|
|
|
|
let mut server_fut = async {
|
|
|
|
futures::join!(
|
|
|
|
redirect_server_fut,
|
2020-09-05 10:39:25 -04:00
|
|
|
ws_server_fut,
|
|
|
|
wss_server_fut,
|
2021-09-30 03:26:15 -04:00
|
|
|
tls_server_fut,
|
2021-08-09 09:55:00 -04:00
|
|
|
tls_client_auth_server_fut,
|
2021-02-21 11:51:46 -05:00
|
|
|
ws_close_server_fut,
|
2020-07-04 13:05:01 -04:00
|
|
|
another_redirect_server_fut,
|
2021-02-15 21:50:27 -05:00
|
|
|
auth_redirect_server_fut,
|
2021-09-08 00:18:11 -04:00
|
|
|
basic_auth_redirect_server_fut,
|
2020-12-24 08:11:32 -05:00
|
|
|
inf_redirects_server_fut,
|
|
|
|
double_redirects_server_fut,
|
|
|
|
abs_redirect_server_fut,
|
|
|
|
main_server_fut,
|
|
|
|
main_server_https_fut,
|
2021-08-25 08:25:12 -04:00
|
|
|
client_auth_server_https_fut,
|
2021-10-25 12:41:06 -04:00
|
|
|
h1_only_server_fut,
|
|
|
|
h2_only_server_fut
|
2020-07-04 13:05:01 -04:00
|
|
|
)
|
|
|
|
}
|
|
|
|
.boxed();
|
|
|
|
|
|
|
|
let mut did_print_ready = false;
|
2020-12-24 08:11:32 -05:00
|
|
|
futures::future::poll_fn(move |cx| {
|
2020-07-04 13:05:01 -04:00
|
|
|
let poll_result = server_fut.poll_unpin(cx);
|
|
|
|
if !replace(&mut did_print_ready, true) {
|
2021-08-09 09:55:00 -04:00
|
|
|
println!("ready: server_fut"); // Eye catcher for HttpServerCount
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
|
|
|
poll_result
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2020-12-24 08:11:32 -05:00
|
|
|
fn custom_headers(p: &str, body: Vec<u8>) -> Response<Body> {
|
|
|
|
let mut response = Response::new(Body::from(body));
|
2020-07-04 13:05:01 -04:00
|
|
|
|
2021-08-11 10:20:47 -04:00
|
|
|
if p.ends_with("/053_import_compression/brotli") {
|
2020-12-24 08:11:32 -05:00
|
|
|
response
|
|
|
|
.headers_mut()
|
|
|
|
.insert("Content-Encoding", HeaderValue::from_static("br"));
|
|
|
|
response.headers_mut().insert(
|
|
|
|
"Content-Type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
|
|
|
response
|
|
|
|
.headers_mut()
|
|
|
|
.insert("Content-Length", HeaderValue::from_static("26"));
|
|
|
|
return response;
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
2021-08-11 10:20:47 -04:00
|
|
|
if p.ends_with("/053_import_compression/gziped") {
|
2020-12-24 08:11:32 -05:00
|
|
|
response
|
|
|
|
.headers_mut()
|
|
|
|
.insert("Content-Encoding", HeaderValue::from_static("gzip"));
|
|
|
|
response.headers_mut().insert(
|
|
|
|
"Content-Type",
|
|
|
|
HeaderValue::from_static("application/javascript"),
|
|
|
|
);
|
|
|
|
response
|
|
|
|
.headers_mut()
|
|
|
|
.insert("Content-Length", HeaderValue::from_static("39"));
|
|
|
|
return response;
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
|
2021-08-11 10:20:47 -04:00
|
|
|
if p.contains("/encoding/") {
|
2020-08-03 17:39:48 -04:00
|
|
|
let charset = p
|
|
|
|
.split_terminator('/')
|
|
|
|
.last()
|
|
|
|
.unwrap()
|
|
|
|
.trim_end_matches(".ts");
|
2020-12-24 08:11:32 -05:00
|
|
|
|
|
|
|
response.headers_mut().insert(
|
2020-08-03 17:39:48 -04:00
|
|
|
"Content-Type",
|
2020-12-24 08:11:32 -05:00
|
|
|
HeaderValue::from_str(
|
|
|
|
&format!("application/typescript;charset={}", charset)[..],
|
|
|
|
)
|
|
|
|
.unwrap(),
|
2020-08-03 17:39:48 -04:00
|
|
|
);
|
2020-12-24 08:11:32 -05:00
|
|
|
return response;
|
2020-08-03 17:39:48 -04:00
|
|
|
}
|
2020-07-04 13:05:01 -04:00
|
|
|
|
|
|
|
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")
|
2020-10-25 16:17:58 -04:00
|
|
|
} else if p.contains("mismatch_ext") || p.contains("no_js_ext") {
|
2020-07-04 13:05:01 -04:00
|
|
|
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")
|
2021-07-03 17:33:36 -04:00
|
|
|
} else if p.ends_with(".wasm") {
|
|
|
|
Some("application/wasm")
|
2020-07-04 13:05:01 -04:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(t) = content_type {
|
2020-12-24 08:11:32 -05:00
|
|
|
response
|
|
|
|
.headers_mut()
|
|
|
|
.insert("Content-Type", HeaderValue::from_str(t).unwrap());
|
|
|
|
return response;
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
2020-12-24 08:11:32 -05:00
|
|
|
|
|
|
|
response
|
2020-07-04 13:05:01 -04:00
|
|
|
}
|
|
|
|
|
2020-07-06 13:07:15 -04:00
|
|
|
#[derive(Default)]
|
|
|
|
struct HttpServerCount {
|
|
|
|
count: usize,
|
|
|
|
test_server: Option<Child>,
|
2020-06-25 08:53:13 -04:00
|
|
|
}
|
2020-06-18 11:54:55 -04:00
|
|
|
|
2020-07-06 13:07:15 -04:00
|
|
|
impl HttpServerCount {
|
|
|
|
fn inc(&mut self) {
|
|
|
|
self.count += 1;
|
|
|
|
if self.test_server.is_none() {
|
|
|
|
assert_eq!(self.count, 1);
|
|
|
|
|
|
|
|
println!("test_server starting...");
|
|
|
|
let mut test_server = Command::new(test_server_path())
|
2021-08-11 10:20:47 -04:00
|
|
|
.current_dir(testdata_path())
|
2020-07-06 13:07:15 -04:00
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.spawn()
|
|
|
|
.expect("failed to execute test_server");
|
|
|
|
let stdout = test_server.stdout.as_mut().unwrap();
|
|
|
|
use std::io::{BufRead, BufReader};
|
|
|
|
let lines = BufReader::new(stdout).lines();
|
2021-08-09 09:55:00 -04:00
|
|
|
|
|
|
|
// Wait for all the servers to report being ready.
|
|
|
|
let mut ready_count = 0;
|
2020-07-06 13:07:15 -04:00
|
|
|
for maybe_line in lines {
|
|
|
|
if let Ok(line) = maybe_line {
|
2021-08-09 09:55:00 -04:00
|
|
|
if line.starts_with("ready:") {
|
|
|
|
ready_count += 1;
|
2021-01-13 10:48:33 -05:00
|
|
|
}
|
2021-09-30 03:26:15 -04:00
|
|
|
if ready_count == 6 {
|
2020-07-06 13:07:15 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
2021-03-25 14:17:37 -04:00
|
|
|
panic!("{}", maybe_line.unwrap_err());
|
2020-07-06 13:07:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
self.test_server = Some(test_server);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn dec(&mut self) {
|
|
|
|
assert!(self.count > 0);
|
|
|
|
self.count -= 1;
|
|
|
|
if self.count == 0 {
|
|
|
|
let mut test_server = self.test_server.take().unwrap();
|
|
|
|
match test_server.try_wait() {
|
|
|
|
Ok(None) => {
|
|
|
|
test_server.kill().expect("failed to kill test_server");
|
|
|
|
let _ = test_server.wait();
|
|
|
|
}
|
|
|
|
Ok(Some(status)) => {
|
|
|
|
panic!("test_server exited unexpectedly {}", status)
|
|
|
|
}
|
|
|
|
Err(e) => panic!("test_server error: {}", e),
|
2020-06-25 08:53:13 -04:00
|
|
|
}
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-06 13:07:15 -04:00
|
|
|
impl Drop for HttpServerCount {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
assert_eq!(self.count, 0);
|
|
|
|
assert!(self.test_server.is_none());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lock_http_server<'a>() -> MutexGuard<'a, HttpServerCount> {
|
2020-06-29 17:04:19 -04:00
|
|
|
let r = GUARD.lock();
|
2020-07-06 13:07:15 -04:00
|
|
|
if let Err(poison_err) = r {
|
2020-06-29 17:04:19 -04:00
|
|
|
// If panics happened, ignore it. This is for tests.
|
|
|
|
poison_err.into_inner()
|
|
|
|
} else {
|
|
|
|
r.unwrap()
|
2020-07-06 13:07:15 -04:00
|
|
|
}
|
|
|
|
}
|
2020-06-18 11:54:55 -04:00
|
|
|
|
2020-07-06 13:07:15 -04:00
|
|
|
pub struct HttpServerGuard {}
|
|
|
|
|
|
|
|
impl Drop for HttpServerGuard {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
let mut g = lock_http_server();
|
|
|
|
g.dec();
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
2020-07-06 13:07:15 -04:00
|
|
|
}
|
2020-06-18 11:54:55 -04:00
|
|
|
|
2020-07-06 13:07:15 -04:00
|
|
|
/// Adds a reference to a shared target/debug/test_server subprocess. When the
|
|
|
|
/// last instance of the HttpServerGuard is dropped, the subprocess will be
|
|
|
|
/// killed.
|
|
|
|
pub fn http_server() -> HttpServerGuard {
|
2021-06-08 17:56:54 -04:00
|
|
|
ensure_test_server_built();
|
2020-07-06 13:07:15 -04:00
|
|
|
let mut g = lock_http_server();
|
|
|
|
g.inc();
|
|
|
|
HttpServerGuard {}
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Helper function to strip ansi codes.
|
|
|
|
pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow<str> {
|
|
|
|
STRIP_ANSI_RE.replace_all(s, "")
|
|
|
|
}
|
|
|
|
|
2020-08-28 09:03:50 -04:00
|
|
|
pub fn run(
|
|
|
|
cmd: &[&str],
|
|
|
|
input: Option<&[&str]>,
|
|
|
|
envs: Option<Vec<(String, String)>>,
|
|
|
|
current_dir: Option<&str>,
|
|
|
|
expect_success: bool,
|
|
|
|
) {
|
|
|
|
let mut process_builder = Command::new(cmd[0]);
|
|
|
|
process_builder.args(&cmd[1..]).stdin(Stdio::piped());
|
|
|
|
|
|
|
|
if let Some(dir) = current_dir {
|
|
|
|
process_builder.current_dir(dir);
|
|
|
|
}
|
|
|
|
if let Some(envs) = envs {
|
|
|
|
process_builder.envs(envs);
|
|
|
|
}
|
|
|
|
let mut prog = process_builder.spawn().expect("failed to spawn script");
|
|
|
|
if let Some(lines) = input {
|
|
|
|
let stdin = prog.stdin.as_mut().expect("failed to get stdin");
|
|
|
|
stdin
|
|
|
|
.write_all(lines.join("\n").as_bytes())
|
|
|
|
.expect("failed to write to stdin");
|
|
|
|
}
|
|
|
|
let status = prog.wait().expect("failed to wait on child");
|
|
|
|
if expect_success != status.success() {
|
|
|
|
panic!("Unexpected exit code: {:?}", status.code());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_collect(
|
|
|
|
cmd: &[&str],
|
|
|
|
input: Option<&[&str]>,
|
|
|
|
envs: Option<Vec<(String, String)>>,
|
|
|
|
current_dir: Option<&str>,
|
|
|
|
expect_success: bool,
|
|
|
|
) -> (String, String) {
|
|
|
|
let mut process_builder = Command::new(cmd[0]);
|
|
|
|
process_builder
|
|
|
|
.args(&cmd[1..])
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.stderr(Stdio::piped());
|
|
|
|
if let Some(dir) = current_dir {
|
|
|
|
process_builder.current_dir(dir);
|
|
|
|
}
|
|
|
|
if let Some(envs) = envs {
|
|
|
|
process_builder.envs(envs);
|
|
|
|
}
|
|
|
|
let mut prog = process_builder.spawn().expect("failed to spawn script");
|
|
|
|
if let Some(lines) = input {
|
|
|
|
let stdin = prog.stdin.as_mut().expect("failed to get stdin");
|
|
|
|
stdin
|
|
|
|
.write_all(lines.join("\n").as_bytes())
|
|
|
|
.expect("failed to write to stdin");
|
|
|
|
}
|
|
|
|
let Output {
|
|
|
|
stdout,
|
|
|
|
stderr,
|
|
|
|
status,
|
|
|
|
} = prog.wait_with_output().expect("failed to wait on child");
|
|
|
|
let stdout = String::from_utf8(stdout).unwrap();
|
|
|
|
let stderr = String::from_utf8(stderr).unwrap();
|
|
|
|
if expect_success != status.success() {
|
|
|
|
eprintln!("stdout: <<<{}>>>", stdout);
|
|
|
|
eprintln!("stderr: <<<{}>>>", stderr);
|
|
|
|
panic!("Unexpected exit code: {:?}", status.code());
|
|
|
|
}
|
|
|
|
(stdout, stderr)
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:54:55 -04:00
|
|
|
pub fn run_and_collect_output(
|
|
|
|
expect_success: bool,
|
|
|
|
args: &str,
|
|
|
|
input: Option<Vec<&str>>,
|
|
|
|
envs: Option<Vec<(String, String)>>,
|
|
|
|
need_http_server: bool,
|
2021-08-06 17:30:28 -04:00
|
|
|
) -> (String, String) {
|
|
|
|
run_and_collect_output_with_args(
|
|
|
|
expect_success,
|
|
|
|
args.split_whitespace().collect(),
|
|
|
|
input,
|
|
|
|
envs,
|
|
|
|
need_http_server,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_and_collect_output_with_args(
|
|
|
|
expect_success: bool,
|
|
|
|
args: Vec<&str>,
|
|
|
|
input: Option<Vec<&str>>,
|
|
|
|
envs: Option<Vec<(String, String)>>,
|
|
|
|
need_http_server: bool,
|
2020-06-18 11:54:55 -04:00
|
|
|
) -> (String, String) {
|
|
|
|
let mut deno_process_builder = deno_cmd();
|
|
|
|
deno_process_builder
|
2021-08-06 17:30:28 -04:00
|
|
|
.args(args)
|
2021-08-11 10:20:47 -04:00
|
|
|
.current_dir(&testdata_path())
|
2020-06-18 11:54:55 -04:00
|
|
|
.stdin(Stdio::piped())
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.stderr(Stdio::piped());
|
|
|
|
if let Some(envs) = envs {
|
|
|
|
deno_process_builder.envs(envs);
|
|
|
|
}
|
2020-08-10 17:31:05 -04:00
|
|
|
let _http_guard = if need_http_server {
|
2020-06-18 11:54:55 -04:00
|
|
|
Some(http_server())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
let mut deno = deno_process_builder
|
|
|
|
.spawn()
|
|
|
|
.expect("failed to spawn script");
|
|
|
|
if let Some(lines) = input {
|
|
|
|
let stdin = deno.stdin.as_mut().expect("failed to get stdin");
|
|
|
|
stdin
|
|
|
|
.write_all(lines.join("\n").as_bytes())
|
|
|
|
.expect("failed to write to stdin");
|
|
|
|
}
|
|
|
|
let Output {
|
|
|
|
stdout,
|
|
|
|
stderr,
|
|
|
|
status,
|
|
|
|
} = deno.wait_with_output().expect("failed to wait on child");
|
|
|
|
let stdout = String::from_utf8(stdout).unwrap();
|
|
|
|
let stderr = String::from_utf8(stderr).unwrap();
|
|
|
|
if expect_success != status.success() {
|
|
|
|
eprintln!("stdout: <<<{}>>>", stdout);
|
|
|
|
eprintln!("stderr: <<<{}>>>", stderr);
|
|
|
|
panic!("Unexpected exit code: {:?}", status.code());
|
|
|
|
}
|
|
|
|
(stdout, stderr)
|
|
|
|
}
|
|
|
|
|
2020-06-26 16:04:01 -04:00
|
|
|
pub fn new_deno_dir() -> TempDir {
|
2022-04-01 11:15:37 -04:00
|
|
|
TempDir::new()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct DenoCmd {
|
|
|
|
// keep the deno dir directory alive for the duration of the command
|
|
|
|
_deno_dir: TempDir,
|
|
|
|
cmd: Command,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Deref for DenoCmd {
|
|
|
|
type Target = Command;
|
|
|
|
fn deref(&self) -> &Command {
|
|
|
|
&self.cmd
|
|
|
|
}
|
2020-06-26 16:04:01 -04:00
|
|
|
}
|
|
|
|
|
2022-04-01 11:15:37 -04:00
|
|
|
impl DerefMut for DenoCmd {
|
|
|
|
fn deref_mut(&mut self) -> &mut Command {
|
|
|
|
&mut self.cmd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deno_cmd() -> DenoCmd {
|
2020-06-26 16:04:01 -04:00
|
|
|
let deno_dir = new_deno_dir();
|
2022-04-01 11:15:37 -04:00
|
|
|
deno_cmd_with_deno_dir(&deno_dir)
|
2021-02-24 09:18:35 -05:00
|
|
|
}
|
|
|
|
|
2022-04-01 11:15:37 -04:00
|
|
|
pub fn deno_cmd_with_deno_dir(deno_dir: &TempDir) -> DenoCmd {
|
|
|
|
let exe_path = deno_exe_path();
|
|
|
|
assert!(exe_path.exists());
|
|
|
|
let mut cmd = Command::new(exe_path);
|
|
|
|
cmd.env("DENO_DIR", deno_dir.path());
|
|
|
|
DenoCmd {
|
|
|
|
_deno_dir: deno_dir.clone(),
|
|
|
|
cmd,
|
|
|
|
}
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
|
2020-07-09 15:06:51 -04:00
|
|
|
pub fn run_powershell_script_file(
|
|
|
|
script_file_path: &str,
|
|
|
|
args: Vec<&str>,
|
2020-12-24 08:11:32 -05:00
|
|
|
) -> std::result::Result<(), i64> {
|
2020-07-09 15:06:51 -04:00
|
|
|
let deno_dir = new_deno_dir();
|
|
|
|
let mut command = Command::new("powershell.exe");
|
|
|
|
|
|
|
|
command
|
|
|
|
.env("DENO_DIR", deno_dir.path())
|
2021-08-11 10:20:47 -04:00
|
|
|
.current_dir(testdata_path())
|
2020-07-09 15:06:51 -04:00
|
|
|
.arg("-file")
|
|
|
|
.arg(script_file_path);
|
|
|
|
|
|
|
|
for arg in args {
|
|
|
|
command.arg(arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
let output = command.output().expect("failed to spawn script");
|
|
|
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
|
|
|
let stderr = String::from_utf8(output.stderr).unwrap();
|
|
|
|
println!("{}", stdout);
|
|
|
|
if !output.status.success() {
|
|
|
|
panic!(
|
|
|
|
"{} executed with failing error code\n{}{}",
|
|
|
|
script_file_path, stdout, stderr
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:54:55 -04:00
|
|
|
#[derive(Debug, Default)]
|
2022-05-02 15:43:03 -04:00
|
|
|
pub struct CheckOutputIntegrationTest<'a> {
|
|
|
|
pub args: &'a str,
|
|
|
|
pub args_vec: Vec<&'a str>,
|
|
|
|
pub output: &'a str,
|
|
|
|
pub input: Option<&'a str>,
|
|
|
|
pub output_str: Option<&'a str>,
|
2020-06-18 11:54:55 -04:00
|
|
|
pub exit_code: i32,
|
|
|
|
pub http_server: bool,
|
2021-11-24 06:24:13 -05:00
|
|
|
pub envs: Vec<(String, String)>,
|
2022-05-23 12:04:28 -04:00
|
|
|
pub env_clear: bool,
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
|
2022-05-02 15:43:03 -04:00
|
|
|
impl<'a> CheckOutputIntegrationTest<'a> {
|
2020-06-18 11:54:55 -04:00
|
|
|
pub fn run(&self) {
|
2022-03-10 20:56:14 -05:00
|
|
|
let args = if self.args_vec.is_empty() {
|
|
|
|
std::borrow::Cow::Owned(self.args.split_whitespace().collect::<Vec<_>>())
|
|
|
|
} else {
|
|
|
|
assert!(
|
|
|
|
self.args.is_empty(),
|
|
|
|
"Do not provide args when providing args_vec."
|
|
|
|
);
|
|
|
|
std::borrow::Cow::Borrowed(&self.args_vec)
|
|
|
|
};
|
2020-06-18 11:54:55 -04:00
|
|
|
let deno_exe = deno_exe_path();
|
|
|
|
println!("deno_exe path {}", deno_exe.display());
|
|
|
|
|
2020-08-10 17:31:05 -04:00
|
|
|
let _http_server_guard = if self.http_server {
|
2020-06-18 11:54:55 -04:00
|
|
|
Some(http_server())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let (mut reader, writer) = pipe().unwrap();
|
2021-08-11 10:20:47 -04:00
|
|
|
let testdata_dir = testdata_path();
|
2022-04-01 11:15:37 -04:00
|
|
|
let deno_dir = new_deno_dir(); // keep this alive for the test
|
|
|
|
let mut command = deno_cmd_with_deno_dir(&deno_dir);
|
2020-06-18 11:54:55 -04:00
|
|
|
println!("deno_exe args {}", self.args);
|
2021-08-11 10:20:47 -04:00
|
|
|
println!("deno_exe testdata path {:?}", &testdata_dir);
|
2022-03-10 20:56:14 -05:00
|
|
|
command.args(args.iter());
|
2022-05-23 12:04:28 -04:00
|
|
|
if self.env_clear {
|
|
|
|
command.env_clear();
|
|
|
|
}
|
2021-11-24 06:24:13 -05:00
|
|
|
command.envs(self.envs.clone());
|
2021-08-11 10:20:47 -04:00
|
|
|
command.current_dir(&testdata_dir);
|
2020-06-18 11:54:55 -04:00
|
|
|
command.stdin(Stdio::piped());
|
|
|
|
let writer_clone = writer.try_clone().unwrap();
|
|
|
|
command.stderr(writer_clone);
|
|
|
|
command.stdout(writer);
|
|
|
|
|
|
|
|
let mut process = command.spawn().expect("failed to execute process");
|
|
|
|
|
|
|
|
if let Some(input) = self.input {
|
|
|
|
let mut p_stdin = process.stdin.take().unwrap();
|
|
|
|
write!(p_stdin, "{}", input).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Very important when using pipes: This parent process is still
|
|
|
|
// holding its copies of the write ends, and we have to close them
|
|
|
|
// before we read, otherwise the read end will never report EOF. The
|
|
|
|
// Command object owns the writers now, and dropping it closes them.
|
|
|
|
drop(command);
|
|
|
|
|
|
|
|
let mut actual = String::new();
|
|
|
|
reader.read_to_string(&mut actual).unwrap();
|
|
|
|
|
|
|
|
let status = process.wait().expect("failed to finish process");
|
|
|
|
|
2020-11-15 21:19:31 -05:00
|
|
|
if let Some(exit_code) = status.code() {
|
|
|
|
if self.exit_code != exit_code {
|
|
|
|
println!("OUTPUT\n{}\nOUTPUT", actual);
|
|
|
|
panic!(
|
|
|
|
"bad exit code, expected: {:?}, actual: {:?}",
|
|
|
|
self.exit_code, exit_code
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
#[cfg(unix)]
|
|
|
|
{
|
|
|
|
use std::os::unix::process::ExitStatusExt;
|
|
|
|
let signal = status.signal().unwrap();
|
|
|
|
println!("OUTPUT\n{}\nOUTPUT", actual);
|
|
|
|
panic!(
|
|
|
|
"process terminated by signal, expected exit code: {:?}, actual signal: {:?}",
|
|
|
|
self.exit_code, signal
|
|
|
|
);
|
|
|
|
}
|
|
|
|
#[cfg(not(unix))]
|
|
|
|
{
|
|
|
|
println!("OUTPUT\n{}\nOUTPUT", actual);
|
|
|
|
panic!("process terminated without status code on non unix platform, expected exit code: {:?}", self.exit_code);
|
|
|
|
}
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
|
2020-11-15 21:19:31 -05:00
|
|
|
actual = strip_ansi_codes(&actual).to_string();
|
|
|
|
|
2022-05-10 16:24:37 -04:00
|
|
|
// deno test's output capturing flushes with a zero-width space in order to
|
|
|
|
// synchronize the output pipes. Occassionally this zero width space
|
|
|
|
// might end up in the output so strip it from the output comparison here.
|
2022-08-21 13:31:14 -04:00
|
|
|
if args.first() == Some(&"test") {
|
2022-05-10 16:24:37 -04:00
|
|
|
actual = actual.replace('\u{200B}', "");
|
|
|
|
}
|
|
|
|
|
2020-06-18 11:54:55 -04:00
|
|
|
let expected = if let Some(s) = self.output_str {
|
|
|
|
s.to_owned()
|
|
|
|
} else {
|
2021-08-11 10:20:47 -04:00
|
|
|
let output_path = testdata_dir.join(self.output);
|
2020-06-18 11:54:55 -04:00
|
|
|
println!("output path {}", output_path.display());
|
|
|
|
std::fs::read_to_string(output_path).expect("cannot read output")
|
|
|
|
};
|
|
|
|
|
2022-03-15 18:15:56 -04:00
|
|
|
if !expected.contains("[WILDCARD]") {
|
|
|
|
assert_eq!(actual, expected)
|
|
|
|
} else if !wildcard_match(&expected, &actual) {
|
2020-06-18 11:54:55 -04:00
|
|
|
println!("OUTPUT\n{}\nOUTPUT", actual);
|
|
|
|
println!("EXPECTED\n{}\nEXPECTED", expected);
|
|
|
|
panic!("pattern match failed");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-18 16:29:32 -04:00
|
|
|
pub fn wildcard_match(pattern: &str, s: &str) -> bool {
|
2020-06-18 11:54:55 -04:00
|
|
|
pattern_match(pattern, s, "[WILDCARD]")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pattern_match(pattern: &str, s: &str, wildcard: &str) -> bool {
|
|
|
|
// Normalize line endings
|
|
|
|
let mut s = s.replace("\r\n", "\n");
|
|
|
|
let pattern = pattern.replace("\r\n", "\n");
|
|
|
|
|
|
|
|
if pattern == wildcard {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let parts = pattern.split(wildcard).collect::<Vec<&str>>();
|
|
|
|
if parts.len() == 1 {
|
|
|
|
return pattern == s;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !s.starts_with(parts[0]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the first line of the pattern is just a wildcard the newline character
|
|
|
|
// needs to be pre-pended so it can safely match anything or nothing and
|
|
|
|
// continue matching.
|
|
|
|
if pattern.lines().next() == Some(wildcard) {
|
2020-11-27 14:47:35 -05:00
|
|
|
s.insert(0, '\n');
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut t = s.split_at(parts[0].len());
|
|
|
|
|
|
|
|
for (i, part) in parts.iter().enumerate() {
|
|
|
|
if i == 0 {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
dbg!(part, i);
|
2020-11-27 14:47:35 -05:00
|
|
|
if i == parts.len() - 1 && (part.is_empty() || *part == "\n") {
|
2020-06-18 11:54:55 -04:00
|
|
|
dbg!("exit 1 true", i);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if let Some(found) = t.1.find(*part) {
|
|
|
|
dbg!("found ", found);
|
|
|
|
t = t.1.split_at(found + part.len());
|
|
|
|
} else {
|
|
|
|
dbg!("exit false ", i);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dbg!("end ", t.1.len());
|
|
|
|
t.1.is_empty()
|
|
|
|
}
|
|
|
|
|
2021-09-09 08:38:47 -04:00
|
|
|
pub enum PtyData {
|
|
|
|
Input(&'static str),
|
|
|
|
Output(&'static str),
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn test_pty2(args: &str, data: Vec<PtyData>) {
|
|
|
|
use std::io::BufRead;
|
2020-08-18 16:29:32 -04:00
|
|
|
|
2021-09-20 22:15:44 -04:00
|
|
|
with_pty(&args.split_whitespace().collect::<Vec<_>>(), |console| {
|
|
|
|
let mut buf_reader = std::io::BufReader::new(console);
|
|
|
|
for d in data.iter() {
|
2021-09-09 08:38:47 -04:00
|
|
|
match d {
|
|
|
|
PtyData::Input(s) => {
|
|
|
|
println!("INPUT {}", s.escape_debug());
|
2021-09-20 22:15:44 -04:00
|
|
|
buf_reader.get_mut().write_text(s);
|
2021-09-09 08:38:47 -04:00
|
|
|
|
|
|
|
// Because of tty echo, we should be able to read the same string back.
|
|
|
|
assert!(s.ends_with('\n'));
|
|
|
|
let mut echo = String::new();
|
|
|
|
buf_reader.read_line(&mut echo).unwrap();
|
|
|
|
println!("ECHO: {}", echo.escape_debug());
|
2021-09-20 22:15:44 -04:00
|
|
|
|
|
|
|
// Windows may also echo the previous line, so only check the end
|
2022-06-21 15:25:07 -04:00
|
|
|
assert_ends_with!(normalize_text(&echo), normalize_text(s));
|
2021-09-09 08:38:47 -04:00
|
|
|
}
|
|
|
|
PtyData::Output(s) => {
|
|
|
|
let mut line = String::new();
|
|
|
|
if s.ends_with('\n') {
|
|
|
|
buf_reader.read_line(&mut line).unwrap();
|
|
|
|
} else {
|
2021-09-20 22:15:44 -04:00
|
|
|
// assumes the buffer won't have overlapping virtual terminal sequences
|
|
|
|
while normalize_text(&line).len() < normalize_text(s).len() {
|
2021-09-09 08:38:47 -04:00
|
|
|
let mut buf = [0; 64 * 1024];
|
2021-09-20 22:15:44 -04:00
|
|
|
let bytes_read = buf_reader.read(&mut buf).unwrap();
|
|
|
|
assert!(bytes_read > 0);
|
2021-09-09 08:38:47 -04:00
|
|
|
let buf_str = std::str::from_utf8(&buf)
|
|
|
|
.unwrap()
|
|
|
|
.trim_end_matches(char::from(0));
|
|
|
|
line += buf_str;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
println!("OUTPUT {}", line.escape_debug());
|
2021-09-20 22:15:44 -04:00
|
|
|
assert_eq!(normalize_text(&line), normalize_text(s));
|
2021-09-09 08:38:47 -04:00
|
|
|
}
|
|
|
|
}
|
2020-08-18 16:29:32 -04:00
|
|
|
}
|
2021-09-20 22:15:44 -04:00
|
|
|
});
|
2021-09-09 08:38:47 -04:00
|
|
|
|
2021-09-20 22:15:44 -04:00
|
|
|
// This normalization function is not comprehensive
|
|
|
|
// and may need to updated as new scenarios emerge.
|
|
|
|
fn normalize_text(text: &str) -> String {
|
|
|
|
lazy_static! {
|
|
|
|
static ref MOVE_CURSOR_RIGHT_ONE_RE: Regex =
|
|
|
|
Regex::new(r"\x1b\[1C").unwrap();
|
|
|
|
static ref FOUND_SEQUENCES_RE: Regex =
|
|
|
|
Regex::new(r"(\x1b\]0;[^\x07]*\x07)*(\x08)*(\x1b\[\d+X)*").unwrap();
|
|
|
|
static ref CARRIAGE_RETURN_RE: Regex =
|
|
|
|
Regex::new(r"[^\n]*\r([^\n])").unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
// any "move cursor right" sequences should just be a space
|
|
|
|
let text = MOVE_CURSOR_RIGHT_ONE_RE.replace_all(text, " ");
|
|
|
|
// replace additional virtual terminal sequences that strip ansi codes doesn't catch
|
|
|
|
let text = FOUND_SEQUENCES_RE.replace_all(&text, "");
|
|
|
|
// strip any ansi codes, which also strips more terminal sequences
|
|
|
|
let text = strip_ansi_codes(&text);
|
|
|
|
// get rid of any text that is overwritten with only a carriage return
|
|
|
|
let text = CARRIAGE_RETURN_RE.replace_all(&text, "$1");
|
|
|
|
// finally, trim surrounding whitespace
|
|
|
|
text.trim().to_string()
|
2020-08-18 16:29:32 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-20 22:15:44 -04:00
|
|
|
pub fn with_pty(deno_args: &[&str], mut action: impl FnMut(Box<dyn pty::Pty>)) {
|
|
|
|
if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
|
|
|
|
eprintln!("Ignoring non-tty environment.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let deno_dir = new_deno_dir();
|
|
|
|
let mut env_vars = std::collections::HashMap::new();
|
|
|
|
env_vars.insert("NO_COLOR".to_string(), "1".to_string());
|
|
|
|
env_vars.insert(
|
|
|
|
"DENO_DIR".to_string(),
|
|
|
|
deno_dir.path().to_string_lossy().to_string(),
|
|
|
|
);
|
|
|
|
let pty = pty::create_pty(
|
|
|
|
&deno_exe_path().to_string_lossy().to_string(),
|
|
|
|
deno_args,
|
|
|
|
testdata_path(),
|
|
|
|
Some(env_vars),
|
|
|
|
);
|
|
|
|
|
|
|
|
action(pty);
|
|
|
|
}
|
|
|
|
|
2020-08-28 09:03:50 -04:00
|
|
|
pub struct WrkOutput {
|
|
|
|
pub latency: f64,
|
|
|
|
pub requests: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_wrk_output(output: &str) -> WrkOutput {
|
|
|
|
lazy_static! {
|
|
|
|
static ref REQUESTS_RX: Regex =
|
|
|
|
Regex::new(r"Requests/sec:\s+(\d+)").unwrap();
|
|
|
|
static ref LATENCY_RX: Regex =
|
|
|
|
Regex::new(r"\s+99%(?:\s+(\d+.\d+)([a-z]+))").unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut requests = None;
|
|
|
|
let mut latency = None;
|
|
|
|
|
|
|
|
for line in output.lines() {
|
|
|
|
if requests == None {
|
|
|
|
if let Some(cap) = REQUESTS_RX.captures(line) {
|
|
|
|
requests =
|
|
|
|
Some(str::parse::<u64>(cap.get(1).unwrap().as_str()).unwrap());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if latency == None {
|
|
|
|
if let Some(cap) = LATENCY_RX.captures(line) {
|
|
|
|
let time = cap.get(1).unwrap();
|
|
|
|
let unit = cap.get(2).unwrap();
|
|
|
|
|
|
|
|
latency = Some(
|
|
|
|
str::parse::<f64>(time.as_str()).unwrap()
|
|
|
|
* match unit.as_str() {
|
|
|
|
"ms" => 1.0,
|
|
|
|
"us" => 0.001,
|
|
|
|
"s" => 1000.0,
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
WrkOutput {
|
|
|
|
requests: requests.unwrap(),
|
|
|
|
latency: latency.unwrap(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-17 11:40:29 -05:00
|
|
|
#[derive(Debug, Clone, Serialize)]
|
2020-08-28 09:03:50 -04:00
|
|
|
pub struct StraceOutput {
|
|
|
|
pub percent_time: f64,
|
|
|
|
pub seconds: f64,
|
|
|
|
pub usecs_per_call: Option<u64>,
|
|
|
|
pub calls: u64,
|
|
|
|
pub errors: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_strace_output(output: &str) -> HashMap<String, StraceOutput> {
|
|
|
|
let mut summary = HashMap::new();
|
|
|
|
|
|
|
|
// Filter out non-relevant lines. See the error log at
|
|
|
|
// https://github.com/denoland/deno/pull/3715/checks?check_run_id=397365887
|
|
|
|
// This is checked in testdata/strace_summary2.out
|
|
|
|
let mut lines = output
|
|
|
|
.lines()
|
|
|
|
.filter(|line| !line.is_empty() && !line.contains("detached ..."));
|
|
|
|
let count = lines.clone().count();
|
|
|
|
|
|
|
|
if count < 4 {
|
|
|
|
return summary;
|
|
|
|
}
|
|
|
|
|
|
|
|
let total_line = lines.next_back().unwrap();
|
|
|
|
lines.next_back(); // Drop separator
|
|
|
|
let data_lines = lines.skip(2);
|
|
|
|
|
|
|
|
for line in data_lines {
|
|
|
|
let syscall_fields = line.split_whitespace().collect::<Vec<_>>();
|
|
|
|
let len = syscall_fields.len();
|
|
|
|
let syscall_name = syscall_fields.last().unwrap();
|
|
|
|
|
2020-11-27 14:47:35 -05:00
|
|
|
if (5..=6).contains(&len) {
|
2020-08-28 09:03:50 -04:00
|
|
|
summary.insert(
|
|
|
|
syscall_name.to_string(),
|
|
|
|
StraceOutput {
|
|
|
|
percent_time: str::parse::<f64>(syscall_fields[0]).unwrap(),
|
|
|
|
seconds: str::parse::<f64>(syscall_fields[1]).unwrap(),
|
|
|
|
usecs_per_call: Some(str::parse::<u64>(syscall_fields[2]).unwrap()),
|
|
|
|
calls: str::parse::<u64>(syscall_fields[3]).unwrap(),
|
|
|
|
errors: if syscall_fields.len() < 6 {
|
|
|
|
0
|
|
|
|
} else {
|
|
|
|
str::parse::<u64>(syscall_fields[4]).unwrap()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let total_fields = total_line.split_whitespace().collect::<Vec<_>>();
|
|
|
|
summary.insert(
|
|
|
|
"total".to_string(),
|
|
|
|
StraceOutput {
|
|
|
|
percent_time: str::parse::<f64>(total_fields[0]).unwrap(),
|
|
|
|
seconds: str::parse::<f64>(total_fields[1]).unwrap(),
|
|
|
|
usecs_per_call: None,
|
|
|
|
calls: str::parse::<u64>(total_fields[2]).unwrap(),
|
|
|
|
errors: str::parse::<u64>(total_fields[3]).unwrap(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
summary
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_max_mem(output: &str) -> Option<u64> {
|
|
|
|
// Takes the output from "time -v" as input and extracts the 'maximum
|
|
|
|
// resident set size' and returns it in bytes.
|
|
|
|
for line in output.lines() {
|
|
|
|
if line
|
|
|
|
.to_lowercase()
|
|
|
|
.contains("maximum resident set size (kbytes)")
|
|
|
|
{
|
|
|
|
let value = line.split(": ").nth(1).unwrap();
|
|
|
|
return Some(str::parse::<u64>(value).unwrap() * 1024);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2022-03-15 18:15:56 -04:00
|
|
|
use pretty_assertions::assert_eq;
|
2020-08-28 09:03:50 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_wrk_output_1() {
|
|
|
|
const TEXT: &str = include_str!("./testdata/wrk1.txt");
|
|
|
|
let wrk = parse_wrk_output(TEXT);
|
|
|
|
assert_eq!(wrk.requests, 1837);
|
|
|
|
assert!((wrk.latency - 6.25).abs() < f64::EPSILON);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_wrk_output_2() {
|
|
|
|
const TEXT: &str = include_str!("./testdata/wrk2.txt");
|
|
|
|
let wrk = parse_wrk_output(TEXT);
|
|
|
|
assert_eq!(wrk.requests, 53435);
|
|
|
|
assert!((wrk.latency - 6.22).abs() < f64::EPSILON);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_wrk_output_3() {
|
|
|
|
const TEXT: &str = include_str!("./testdata/wrk3.txt");
|
|
|
|
let wrk = parse_wrk_output(TEXT);
|
|
|
|
assert_eq!(wrk.requests, 96037);
|
|
|
|
assert!((wrk.latency - 6.36).abs() < f64::EPSILON);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn strace_parse_1() {
|
|
|
|
const TEXT: &str = include_str!("./testdata/strace_summary.out");
|
|
|
|
let strace = parse_strace_output(TEXT);
|
|
|
|
|
|
|
|
// first syscall line
|
|
|
|
let munmap = strace.get("munmap").unwrap();
|
|
|
|
assert_eq!(munmap.calls, 60);
|
|
|
|
assert_eq!(munmap.errors, 0);
|
|
|
|
|
|
|
|
// line with errors
|
|
|
|
assert_eq!(strace.get("mkdir").unwrap().errors, 2);
|
|
|
|
|
|
|
|
// last syscall line
|
|
|
|
let prlimit = strace.get("prlimit64").unwrap();
|
|
|
|
assert_eq!(prlimit.calls, 2);
|
|
|
|
assert!((prlimit.percent_time - 0.0).abs() < f64::EPSILON);
|
|
|
|
|
|
|
|
// summary line
|
|
|
|
assert_eq!(strace.get("total").unwrap().calls, 704);
|
|
|
|
assert_eq!(strace.get("total").unwrap().errors, 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn strace_parse_2() {
|
|
|
|
const TEXT: &str = include_str!("./testdata/strace_summary2.out");
|
|
|
|
let strace = parse_strace_output(TEXT);
|
|
|
|
|
|
|
|
// first syscall line
|
|
|
|
let futex = strace.get("futex").unwrap();
|
|
|
|
assert_eq!(futex.calls, 449);
|
|
|
|
assert_eq!(futex.errors, 94);
|
|
|
|
|
|
|
|
// summary line
|
|
|
|
assert_eq!(strace.get("total").unwrap().calls, 821);
|
|
|
|
assert_eq!(strace.get("total").unwrap().errors, 107);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_wildcard_match() {
|
|
|
|
let fixtures = vec![
|
|
|
|
("foobarbaz", "foobarbaz", true),
|
|
|
|
("[WILDCARD]", "foobarbaz", true),
|
|
|
|
("foobar", "foobarbaz", false),
|
|
|
|
("foo[WILDCARD]baz", "foobarbaz", true),
|
|
|
|
("foo[WILDCARD]baz", "foobazbar", false),
|
|
|
|
("foo[WILDCARD]baz[WILDCARD]qux", "foobarbazqatqux", true),
|
|
|
|
("foo[WILDCARD]", "foobar", true),
|
|
|
|
("foo[WILDCARD]baz[WILDCARD]", "foobarbazqat", true),
|
|
|
|
// check with different line endings
|
|
|
|
("foo[WILDCARD]\nbaz[WILDCARD]\n", "foobar\nbazqat\n", true),
|
|
|
|
(
|
|
|
|
"foo[WILDCARD]\nbaz[WILDCARD]\n",
|
|
|
|
"foobar\r\nbazqat\r\n",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"foo[WILDCARD]\r\nbaz[WILDCARD]\n",
|
|
|
|
"foobar\nbazqat\r\n",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"foo[WILDCARD]\r\nbaz[WILDCARD]\r\n",
|
|
|
|
"foobar\nbazqat\n",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"foo[WILDCARD]\r\nbaz[WILDCARD]\r\n",
|
|
|
|
"foobar\r\nbazqat\r\n",
|
|
|
|
true,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
// Iterate through the fixture lists, testing each one
|
|
|
|
for (pattern, string, expected) in fixtures {
|
|
|
|
let actual = wildcard_match(pattern, string);
|
|
|
|
dbg!(pattern, string, expected);
|
|
|
|
assert_eq!(actual, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-01 13:09:25 -05:00
|
|
|
#[test]
|
|
|
|
fn test_pattern_match() {
|
|
|
|
// foo, bar, baz, qux, quux, quuz, corge, grault, garply, waldo, fred, plugh, xyzzy
|
|
|
|
|
|
|
|
let wildcard = "[BAR]";
|
|
|
|
assert!(pattern_match("foo[BAR]baz", "foobarbaz", wildcard));
|
|
|
|
assert!(!pattern_match("foo[BAR]baz", "foobazbar", wildcard));
|
|
|
|
|
|
|
|
let multiline_pattern = "[BAR]
|
|
|
|
foo:
|
|
|
|
[BAR]baz[BAR]";
|
|
|
|
|
|
|
|
fn multi_line_builder(input: &str, leading_text: Option<&str>) -> String {
|
|
|
|
// If there is leading text add a newline so it's on it's own line
|
|
|
|
let head = match leading_text {
|
|
|
|
Some(v) => format!("{}\n", v),
|
|
|
|
None => "".to_string(),
|
|
|
|
};
|
|
|
|
format!(
|
|
|
|
"{}foo:
|
|
|
|
quuz {} corge
|
|
|
|
grault",
|
|
|
|
head, input
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate multi-line string builder
|
|
|
|
assert_eq!(
|
|
|
|
"QUUX=qux
|
|
|
|
foo:
|
|
|
|
quuz BAZ corge
|
|
|
|
grault",
|
|
|
|
multi_line_builder("BAZ", Some("QUUX=qux"))
|
|
|
|
);
|
|
|
|
|
|
|
|
// Correct input & leading line
|
|
|
|
assert!(pattern_match(
|
|
|
|
multiline_pattern,
|
|
|
|
&multi_line_builder("baz", Some("QUX=quux")),
|
|
|
|
wildcard
|
|
|
|
));
|
|
|
|
|
|
|
|
// Correct input & no leading line
|
|
|
|
assert!(pattern_match(
|
|
|
|
multiline_pattern,
|
|
|
|
&multi_line_builder("baz", None),
|
|
|
|
wildcard
|
|
|
|
));
|
|
|
|
|
|
|
|
// Incorrect input & leading line
|
|
|
|
assert!(!pattern_match(
|
|
|
|
multiline_pattern,
|
|
|
|
&multi_line_builder("garply", Some("QUX=quux")),
|
|
|
|
wildcard
|
|
|
|
));
|
|
|
|
|
|
|
|
// Incorrect input & no leading line
|
|
|
|
assert!(!pattern_match(
|
|
|
|
multiline_pattern,
|
|
|
|
&multi_line_builder("garply", None),
|
|
|
|
wildcard
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2020-08-28 09:03:50 -04:00
|
|
|
#[test]
|
|
|
|
fn max_mem_parse() {
|
|
|
|
const TEXT: &str = include_str!("./testdata/time.out");
|
|
|
|
let size = parse_max_mem(TEXT);
|
|
|
|
|
|
|
|
assert_eq!(size, Some(120380 * 1024));
|
2020-06-18 11:54:55 -04:00
|
|
|
}
|
|
|
|
}
|