Merge branch 'master' of https://github.com/jkelleyrtp/dioxus-studio
This commit is contained in:
commit
fdda6b9ffe
8 files changed
+580
-371
No files matched your search
Generated
+312
-331
File diff suppressed because it is too large.
Load diff
+8
-15
@@ -36,6 +36,7 @@ regex = "1.5.4"
|
|||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
anyhow = "1.0.53"
|
anyhow = "1.0.53"
|
||||||
hyper = "0.14.17"
|
hyper = "0.14.17"
|
||||||
|
hyper-rustls = "0.23.2"
|
||||||
indicatif = "0.17.0-rc.11"
|
indicatif = "0.17.0-rc.11"
|
||||||
subprocess = "0.2.9"
|
subprocess = "0.2.9"
|
||||||
|
|
||||||
@@ -76,23 +77,15 @@ ctrlc = "3.2.3"
|
|||||||
# dioxus-rsx = "0.0.1"
|
# dioxus-rsx = "0.0.1"
|
||||||
gitignore = "1.0.7"
|
gitignore = "1.0.7"
|
||||||
|
|
||||||
dioxus-rsx = { path = "../../dioxus/packages/rsx" }
|
dioxus-rsx = { git = "https://github.com/DioxusLabs/dioxus" }
|
||||||
dioxus-html = { path = "../../dioxus/packages/html", features = [
|
dioxus-html = { git = "https://github.com/DioxusLabs/dioxus", features = [
|
||||||
"hot-reload-context",
|
"hot-reload-context",
|
||||||
] }
|
] }
|
||||||
dioxus-core = { path = "../../dioxus/packages/core", features = ["serialize"] }
|
dioxus-core = { git = "https://github.com/DioxusLabs/dioxus", features = [
|
||||||
dioxus-autofmt = { path = "../../dioxus/packages/autofmt" }
|
"serialize",
|
||||||
rsx-rosetta = { path = "../../dioxus/packages/rsx-rosetta" }
|
] }
|
||||||
|
dioxus-autofmt = { git = "https://github.com/dioxuslabs/dioxus" }
|
||||||
# dioxus-rsx = { git = "https://github.com/DioxusLabs/dioxus" }
|
rsx-rosetta = { git = "https://github.com/dioxuslabs/dioxus" }
|
||||||
# dioxus-html = { git = "https://github.com/DioxusLabs/dioxus", features = [
|
|
||||||
# "hot-reload-context",
|
|
||||||
# ] }
|
|
||||||
# dioxus-core = { git = "https://github.com/DioxusLabs/dioxus", features = [
|
|
||||||
# "serialize",
|
|
||||||
# ] }
|
|
||||||
# dioxus-autofmt = { git = "https://github.com/dioxuslabs/dioxus" }
|
|
||||||
# rsx-rosetta = { git = "https://github.com/dioxuslabs/dioxus" }
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ Only include resources at `Dev` mode.
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Web.Proxy
|
||||||
|
|
||||||
|
Proxy requests matching a path to a backend server.
|
||||||
|
|
||||||
|
1. ***backend*** - the URL to the backend server.
|
||||||
|
```
|
||||||
|
backend = "http://localhost:8000/api/"
|
||||||
|
```
|
||||||
|
This will cause any requests made to the dev server with prefix /api/ to be redirected to the backend server at http://localhost:8000. The path and query parameters will be passed on as-is (path rewriting is not currently supported).
|
||||||
|
|
||||||
## Config example
|
## Config example
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
@@ -168,4 +178,7 @@ style = []
|
|||||||
|
|
||||||
# Javascript code file
|
# Javascript code file
|
||||||
script = []
|
script = []
|
||||||
|
|
||||||
|
[[web.proxy]]
|
||||||
|
backend = "http://localhost:8000/api/"
|
||||||
```
|
```
|
||||||
+1
-1
@@ -34,7 +34,7 @@ pub fn crate_root() -> Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
.ok_or_else(|| Error::CargoError("Failed to find the cargo directory".to_string()))
|
.ok_or_else(|| Error::CargoError("Failed to find directory containing Cargo.toml".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the directory contains `Cargo.toml`
|
/// Checks if the directory contains `Cargo.toml`
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ impl Default for DioxusConfig {
|
|||||||
title: Some("dioxus | ⛺".into()),
|
title: Some("dioxus | ⛺".into()),
|
||||||
base_path: None,
|
base_path: None,
|
||||||
},
|
},
|
||||||
|
proxy: Some(vec![]),
|
||||||
watcher: WebWatcherConfig {
|
watcher: WebWatcherConfig {
|
||||||
watch_path: Some(vec![PathBuf::from("src")]),
|
watch_path: Some(vec![PathBuf::from("src")]),
|
||||||
reload_html: Some(false),
|
reload_html: Some(false),
|
||||||
@@ -97,6 +98,7 @@ pub struct ApplicationConfig {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct WebConfig {
|
pub struct WebConfig {
|
||||||
pub app: WebAppConfig,
|
pub app: WebAppConfig,
|
||||||
|
pub proxy: Option<Vec<WebProxyConfig>>,
|
||||||
pub watcher: WebWatcherConfig,
|
pub watcher: WebWatcherConfig,
|
||||||
pub resource: WebResourceConfig,
|
pub resource: WebResourceConfig,
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,11 @@ pub struct WebAppConfig {
|
|||||||
pub base_path: Option<String>,
|
pub base_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WebProxyConfig {
|
||||||
|
pub backend: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct WebWatcherConfig {
|
pub struct WebWatcherConfig {
|
||||||
pub watch_path: Option<Vec<PathBuf>>,
|
pub watch_path: Option<Vec<PathBuf>>,
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ pub enum Error {
|
|||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
CustomError(String),
|
CustomError(String),
|
||||||
|
|
||||||
|
#[error("Invalid proxy URL: {0}")]
|
||||||
|
InvalidProxy(#[from] hyper::http::uri::InvalidUri),
|
||||||
|
|
||||||
|
#[error("Error proxying request: {0}")]
|
||||||
|
ProxyRequestError(hyper::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Other(#[from] anyhow::Error),
|
Other(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-24
@@ -23,6 +23,8 @@ use tokio::sync::broadcast;
|
|||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody};
|
use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody};
|
||||||
|
|
||||||
|
mod proxy;
|
||||||
|
|
||||||
pub struct BuildManager {
|
pub struct BuildManager {
|
||||||
config: CrateConfig,
|
config: CrateConfig,
|
||||||
reload_tx: broadcast::Sender<()>,
|
reload_tx: broadcast::Sender<()>,
|
||||||
@@ -136,9 +138,11 @@ pub async fn startup_hot_reload(ip: String, port: u16, config: CrateConfig) -> R
|
|||||||
|
|
||||||
let dist_path = config.out_dir.clone();
|
let dist_path = config.out_dir.clone();
|
||||||
let (reload_tx, _) = broadcast::channel(100);
|
let (reload_tx, _) = broadcast::channel(100);
|
||||||
let file_map = Arc::new(Mutex::new(FileMap::<HtmlCtx>::new(
|
let FileMapBuildResult { map, errors } = FileMap::<HtmlCtx>::create(config.crate_dir.clone())?;
|
||||||
config.crate_dir.clone(),
|
for err in errors {
|
||||||
)));
|
log::error!("{}", err);
|
||||||
|
}
|
||||||
|
let file_map = Arc::new(Mutex::new(map));
|
||||||
let build_manager = Arc::new(BuildManager {
|
let build_manager = Arc::new(BuildManager {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
reload_tx: reload_tx.clone(),
|
reload_tx: reload_tx.clone(),
|
||||||
@@ -178,17 +182,35 @@ pub async fn startup_hot_reload(ip: String, port: u16, config: CrateConfig) -> R
|
|||||||
if let Ok(evt) = evt {
|
if let Ok(evt) = evt {
|
||||||
let mut messages: Vec<Template<'static>> = Vec::new();
|
let mut messages: Vec<Template<'static>> = Vec::new();
|
||||||
for path in evt.paths.clone() {
|
for path in evt.paths.clone() {
|
||||||
|
// if this is not a rust file, rebuild the whole project
|
||||||
if path.extension().and_then(|p| p.to_str()) != Some("rs") {
|
if path.extension().and_then(|p| p.to_str()) != Some("rs") {
|
||||||
continue;
|
match build_manager.rebuild() {
|
||||||
|
Ok(res) => {
|
||||||
|
print_console_info(
|
||||||
|
&watcher_ip,
|
||||||
|
port,
|
||||||
|
&config,
|
||||||
|
PrettierOptions {
|
||||||
|
changed: evt.paths,
|
||||||
|
warnings: res.warnings,
|
||||||
|
elapsed_time: res.elapsed_time,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// find changes to the rsx in the file
|
// find changes to the rsx in the file
|
||||||
let mut map = file_map.lock().unwrap();
|
let mut map = file_map.lock().unwrap();
|
||||||
|
|
||||||
match map.update_rsx(&path, &crate_dir) {
|
match map.update_rsx(&path, &crate_dir) {
|
||||||
UpdateResult::UpdatedRsx(msgs) => {
|
Ok(UpdateResult::UpdatedRsx(msgs)) => {
|
||||||
messages.extend(msgs);
|
messages.extend(msgs);
|
||||||
}
|
}
|
||||||
UpdateResult::NeedsRebuild => {
|
Ok(UpdateResult::NeedsRebuild) => {
|
||||||
match build_manager.rebuild() {
|
match build_manager.rebuild() {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
print_console_info(
|
print_console_info(
|
||||||
@@ -208,6 +230,9 @@ pub async fn startup_hot_reload(ip: String, port: u16, config: CrateConfig) -> R
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("{}", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for msg in messages {
|
for msg in messages {
|
||||||
@@ -222,12 +247,12 @@ pub async fn startup_hot_reload(ip: String, port: u16, config: CrateConfig) -> R
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for sub_path in allow_watch_path {
|
for sub_path in allow_watch_path {
|
||||||
watcher
|
if let Err(err) = watcher.watch(
|
||||||
.watch(
|
&config.crate_dir.join(&sub_path),
|
||||||
&config.crate_dir.join(sub_path),
|
notify::RecursiveMode::Recursive,
|
||||||
notify::RecursiveMode::Recursive,
|
) {
|
||||||
)
|
log::error!("error watching {sub_path:?}: \n{}", err);
|
||||||
.unwrap();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start serve dev-server at 0.0.0.0:8080
|
// start serve dev-server at 0.0.0.0:8080
|
||||||
@@ -279,16 +304,18 @@ pub async fn startup_hot_reload(ip: String, port: u16, config: CrateConfig) -> R
|
|||||||
)
|
)
|
||||||
.service(ServeDir::new(config.crate_dir.join(&dist_path)));
|
.service(ServeDir::new(config.crate_dir.join(&dist_path)));
|
||||||
|
|
||||||
let router = Router::new()
|
let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));
|
||||||
.route("/_dioxus/ws", get(ws_handler))
|
for proxy_config in config.dioxus_config.web.proxy.unwrap_or_default() {
|
||||||
.fallback(
|
router = proxy::add_proxy(router, &proxy_config )?;
|
||||||
get_service(file_service).handle_error(|error: std::io::Error| async move {
|
}
|
||||||
(
|
router = router.fallback(get_service(file_service).handle_error(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|error: std::io::Error| async move {
|
||||||
format!("Unhandled internal error: {}", error),
|
(
|
||||||
)
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
}),
|
format!("Unhandled internal error: {}", error),
|
||||||
);
|
)
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
let router = router
|
let router = router
|
||||||
.route("/_dioxus/hot_reload", get(hot_reload_handler))
|
.route("/_dioxus/hot_reload", get(hot_reload_handler))
|
||||||
@@ -422,8 +449,9 @@ pub async fn startup_default(ip: String, port: u16, config: CrateConfig) -> Resu
|
|||||||
)
|
)
|
||||||
.service(ServeDir::new(config.crate_dir.join(&dist_path)));
|
.service(ServeDir::new(config.crate_dir.join(&dist_path)));
|
||||||
|
|
||||||
let router = Router::new()
|
let mut router = Router::new().route("/_dioxus/ws", get(ws_handler));
|
||||||
.route("/_dioxus/ws", get(ws_handler))
|
|
||||||
|
router = router
|
||||||
.fallback(
|
.fallback(
|
||||||
get_service(file_service).handle_error(|error: std::io::Error| async move {
|
get_service(file_service).handle_error(|error: std::io::Error| async move {
|
||||||
(
|
(
|
||||||
@@ -493,6 +521,8 @@ fn print_console_info(ip: &String, port: u16, config: &CrateConfig, options: Pre
|
|||||||
"False"
|
"False"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let proxies = config.dioxus_config.web.proxy.as_ref();
|
||||||
|
|
||||||
if options.changed.is_empty() {
|
if options.changed.is_empty() {
|
||||||
println!(
|
println!(
|
||||||
"{} @ v{} [{}] \n",
|
"{} @ v{} [{}] \n",
|
||||||
@@ -523,6 +553,14 @@ fn print_console_info(ip: &String, port: u16, config: &CrateConfig, options: Pre
|
|||||||
println!("");
|
println!("");
|
||||||
println!("\t> Profile : {}", profile.green());
|
println!("\t> Profile : {}", profile.green());
|
||||||
println!("\t> Hot Reload : {}", hot_reload.cyan());
|
println!("\t> Hot Reload : {}", hot_reload.cyan());
|
||||||
|
if let Some(proxies) = proxies {
|
||||||
|
if !proxies.is_empty() {
|
||||||
|
println!("\t> Proxies :");
|
||||||
|
for proxy in proxies {
|
||||||
|
println!("\t\t- {}", proxy.backend.blue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
println!("\t> Index Template : {}", custom_html_file.green());
|
println!("\t> Index Template : {}", custom_html_file.green());
|
||||||
println!("\t> URL Rewrite [index_on_404] : {}", url_rewrite.purple());
|
println!("\t> URL Rewrite [index_on_404] : {}", url_rewrite.purple());
|
||||||
println!("");
|
println!("");
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
use crate::{Result, WebProxyConfig};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use axum::{http::StatusCode, routing::any, Router};
|
||||||
|
use hyper::{Request, Response, Uri};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ProxyClient {
|
||||||
|
inner: hyper::Client<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>,
|
||||||
|
url: Uri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyClient {
|
||||||
|
fn new(url: Uri) -> Self {
|
||||||
|
let https = hyper_rustls::HttpsConnectorBuilder::new()
|
||||||
|
.with_native_roots()
|
||||||
|
.https_or_http()
|
||||||
|
.enable_http1()
|
||||||
|
.build();
|
||||||
|
Self {
|
||||||
|
inner: hyper::Client::builder().build(https),
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send(
|
||||||
|
&self,
|
||||||
|
mut req: Request<hyper::body::Body>,
|
||||||
|
) -> Result<Response<hyper::body::Body>> {
|
||||||
|
let mut uri_parts = req.uri().clone().into_parts();
|
||||||
|
uri_parts.authority = self.url.authority().cloned();
|
||||||
|
uri_parts.scheme = self.url.scheme().cloned();
|
||||||
|
*req.uri_mut() = Uri::from_parts(uri_parts).context("Invalid URI parts")?;
|
||||||
|
self.inner
|
||||||
|
.request(req)
|
||||||
|
.await
|
||||||
|
.map_err(crate::error::Error::ProxyRequestError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add routes to the router handling the specified proxy config.
|
||||||
|
///
|
||||||
|
/// We will proxy requests directed at either:
|
||||||
|
///
|
||||||
|
/// - the exact path of the proxy config's backend URL, e.g. /api
|
||||||
|
/// - the exact path with a trailing slash, e.g. /api/
|
||||||
|
/// - any subpath of the backend URL, e.g. /api/foo/bar
|
||||||
|
pub fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Router> {
|
||||||
|
let url: Uri = proxy.backend.parse()?;
|
||||||
|
let path = url.path().to_string();
|
||||||
|
let client = ProxyClient::new(url);
|
||||||
|
|
||||||
|
// We also match everything after the path using a wildcard matcher.
|
||||||
|
let wildcard_client = client.clone();
|
||||||
|
|
||||||
|
router = router.route(
|
||||||
|
// Always remove trailing /'s so that the exact route
|
||||||
|
// matches.
|
||||||
|
path.trim_end_matches('/'),
|
||||||
|
any(move |req| async move {
|
||||||
|
client
|
||||||
|
.send(req)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wildcard match anything else _after_ the backend URL's path.
|
||||||
|
// Note that we know `path` ends with a trailing `/` in this branch,
|
||||||
|
// so `wildcard` will look like `http://localhost/api/*proxywildcard`.
|
||||||
|
let wildcard = format!("{}/*proxywildcard", path.trim_end_matches('/'));
|
||||||
|
router = router.route(
|
||||||
|
&wildcard,
|
||||||
|
any(move |req| async move {
|
||||||
|
wildcard_client
|
||||||
|
.send(req)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
Ok(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use axum::{extract::Path, Router};
|
||||||
|
|
||||||
|
fn setup_servers(
|
||||||
|
mut config: WebProxyConfig,
|
||||||
|
) -> (
|
||||||
|
tokio::task::JoinHandle<()>,
|
||||||
|
tokio::task::JoinHandle<()>,
|
||||||
|
String,
|
||||||
|
) {
|
||||||
|
let backend_router = Router::new().route(
|
||||||
|
"/*path",
|
||||||
|
any(|path: Path<String>| async move { format!("backend: {}", path.0) }),
|
||||||
|
);
|
||||||
|
let backend_server = axum::Server::bind(&"127.0.0.1:0".parse().unwrap())
|
||||||
|
.serve(backend_router.into_make_service());
|
||||||
|
let backend_addr = backend_server.local_addr();
|
||||||
|
let backend_handle = tokio::spawn(async move { backend_server.await.unwrap() });
|
||||||
|
config.backend = format!("http://{}{}", backend_addr, config.backend);
|
||||||
|
let router = super::add_proxy(Router::new(), &config);
|
||||||
|
let server = axum::Server::bind(&"127.0.0.1:0".parse().unwrap())
|
||||||
|
.serve(router.unwrap().into_make_service());
|
||||||
|
let server_addr = server.local_addr();
|
||||||
|
let server_handle = tokio::spawn(async move { server.await.unwrap() });
|
||||||
|
(backend_handle, server_handle, server_addr.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn test_proxy_requests(path: String) {
|
||||||
|
let config = WebProxyConfig {
|
||||||
|
// Normally this would be an absolute URL including scheme/host/port,
|
||||||
|
// but in these tests we need to let the OS choose the port so tests
|
||||||
|
// don't conflict, so we'll concatenate the final address and this
|
||||||
|
// path together.
|
||||||
|
// So in day to day usage, use `http://localhost:8000/api` instead!
|
||||||
|
backend: path,
|
||||||
|
};
|
||||||
|
let (backend_handle, server_handle, server_addr) = setup_servers(config);
|
||||||
|
let resp = hyper::Client::new()
|
||||||
|
.get(format!("http://{}/api", server_addr).parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
hyper::body::to_bytes(resp.into_body()).await.unwrap(),
|
||||||
|
"backend: /api"
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp = hyper::Client::new()
|
||||||
|
.get(format!("http://{}/api/", server_addr).parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
hyper::body::to_bytes(resp.into_body()).await.unwrap(),
|
||||||
|
"backend: /api/"
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp = hyper::Client::new()
|
||||||
|
.get(
|
||||||
|
format!("http://{}/api/subpath", server_addr)
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(
|
||||||
|
hyper::body::to_bytes(resp.into_body()).await.unwrap(),
|
||||||
|
"backend: /api/subpath"
|
||||||
|
);
|
||||||
|
backend_handle.abort();
|
||||||
|
server_handle.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn add_proxy() {
|
||||||
|
test_proxy_requests("/api".to_string()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn add_proxy_trailing_slash() {
|
||||||
|
test_proxy_requests("/api/".to_string()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user