Binary patching rust hot-reloading, sub-second rebuilds, independent server/client hot-reload (#3797)
Enable runtime rust hot patching. * roll back wry to 0.45 * cleanup rustc * wip * wip: fix client/server * get the subsecond cli thing working again * back to list of builds but with accessors * migrate some methods to workspace, clean up impl a bit * pass mode through build context * use a build context, similar to cargo/rustc * merge BuildUpdate and HandleUpdate into BuilderUpdate * Move more resolution of args into runner * migrate some methods to runner * hoist out fullstack * fix build request resolver * yay request is cleaned up * fixup resolution a bit * spawn off the build! * re-wire android autodetection * re-wire android autodetection * wire back up a few more things * re-wire tooling verification * I think it's mostly in the right condition for regular app * add depinfo parser * yay okay works with regular apps again * full rebuilds are back * rewire file wtcher * wire patch! * yayyyyyy patching works and is really fast * yay android works * clean up stdout piping * yayyy memap on android so we don't need root * create a static wasm jump table * wip: global offset table for wasm * wip... thinking about making the patches relocatable * wip: customized relocation * YESSSSSS RELOCATABLE WASM * clean up impl a bit * lil bit more cleanup * lil bit more * lil bit more cleanup * lil bit more cleanup, support 32 bit platforms * sick, wasm is completely pic * hmmmm not quite working yet * woooo, patches loading from rust+wasm * integrated into the cli 😎 * condense a bit * cleaned up request a bit * bust fingerprints * Make the file diffing logic a bit more sensible * still working through a few issues, server launches but is acting weird * fix merge conflict * more merge issues * remove stuff we don't want anymore * revert name change * wip: server not working anymore :( * split apart "dioxus fullstack" into "dioxus server" and "dioxus web" * fixup a few more compile errors * grrr opening server * simultaneous frontend and backend patching * use rustc wrapper * wip: passing assets to jumptable * migrate project-like examples * patchy patchy server fns! * rollback some random changes * unwind the js files * rollback hash * no need for patch * more cleanups * lil bit more cleanup, remove some old cruft * allow patching when wasm webpage is closed * tiny bit more robust * lil bit of clean up * clean up ws code * bit more clean ups * condense * undo file moves * move back other project * move other project * migrate out harness and janky CLI * fix compile * anonymize some files, heavily document others * lots more documentation, for posterity! * ton more docs * clean up the cli a bit more * more ws cleanup * more cleanup * migrate build id to cli-config * add command file handling * random small cleanups * wip.... * wip.... * fix: use workspace for krate discovery * fix panic logging on serve by placing it *after* the logs * reorder logging to be sensible on success * bring back swc * ws cruft * small patches to relocs in loader * bump krates * hoist main funcs into the ifunc table, fixing wasm * fix tui output for long lines * add more linker logging * wow, incredible, we build fat binaries, holy heckkkkk * fix args for frontend/backend * small cleanups * properly send along build IDs to clients * fix workspace compiles * clean up jump table work a bit * clean up logging a bit * open existing * fixup some commands * open existing browser integration * wire up the new dx serve client/server syntax * fixup `dx run` command * bring back some old functionality * fix serverfn feature sets * remove toast animation and simplify its code a bit * less intrusive toast * dont change build status when patching on web * add proper cache busting to vcomponent * clean up the patch file a bit * more lints/checks cleaned up * go back to TargetArgs but a BuildTargets resolved struct * clean up more nits * use an atomicptr for jumptable * fix interaction with initial web load + suspense * don't run asset system on wasm/bindgen js in dev * reduce blast radius of PR * use profile to determine if we're in release * cleanup prod html template * cleanup profiles * fix feature resolution of fullstack * if fullstack is explicitly false, don't use it * light cleanups * drop carg config2 * pass along linker args properly * make workspace examples compile with dx again * fewer unwraps and better error loggign * bit more error handlign * small cleanup * drive-by cleanups * use queries instead of initialize for aslr stuff * fix hotpatch with higher opt level * fix aslr bug from multiple clients * fix merge conflict * fix typos * clippy * fix miscompile * fix doctest * properly rollback wry to 0.45 * fix markdown path issues, other test issues * fix test in router * fix release mode test * fix some more tests, clean up a few more items * use fnptr instead of typeid * clean up memozation via subsecond in core * use "main" as sentinel * fix imports and re-exports * get off __rust_alloc for aslr offset detection * wip * wip... fixing unoptimized web * hmmmm mmmm * close,ish, still missing the wasmbindgen dynaimc imports * aha! full wasm works now - disable externref when compilng patch modules - manually link wasm instrinsics - displace the __wbindgen_placeholder__ table with the synthesized wbg imports what an awful pain. There might be a few items missing, so will test against the docsite shortly * remove failing test * hmmmm things aren't perfect * from what I can tell, relocatable wasm just doesn't work with wasm-bindgen sad https://github.com/rustwasm/wasm-bindgen/issues/1420 * AI IS LITERALLY THE WORST * preserve wasm snippets across compiles * IT HOTPATCHES THE DOCSITE, WE ARE LIVING IN THE FUTURE * remove the macos cfg-out * fix: need to allocate 1 more page * fix url issue * properly span props * properly fill in extra symbols * light cleanups and good docs * delete cruft, better docs * More cleanups and simplifications * clippy, typos * remove harnesses * wasm-bindgen error logging * clean up deps * remove cruft * proper priv/pub * use ifuncs for all env imports * cruft * use par_iter, better output * implement a cache for the base module * delete custom sections * cache the main native module too * add par iter in a few more places * clippy, map ident * better logging and status bars * wip: some small windows support * wip more windowss * fix weird regression * implement windows jump table * small fixes for windows * better windows support * windows support pt2 * rust lld is not msvc * whole archive the rlibs * ignore windows and sys crates * logging * ohhhhhh, pop out dlls * dedupe * thANK GOD FAT LINKING WORKS ON WINDOWS * hoist caching * implement patch based on cache * fix location for pdb * pass data symbols, correct object * add all symbol for windows * add log * treat none rva as undefined * whoops * handle windows calling convention * dont use relocated arm64 stub windows * not all are text? * WINDOWS FUNCTION PATCHING WORKS but issue with statics? * use our own entry * handle windows imp symbols * add log * jump to the real symbol not the layout * whoops * hotpatching windows works completely holy heck yay * drop linker args crate * disable ssg playwright for now * light cleanups * some cleanups / preps: - bump to serverfn 0.8.2 - fix playwright test - fix clippy * use fix from #4042 * bring in other fix for liveview * hoist some configuration * use __aslr_reference instead of aslr_reference * add better errors to patcherror * fix android * panic if patching fails * clean up tomls * wip: fixing wasm compile for server * fullstack wasm again * fix cfg * fixup feature detection * fix issue with features * fix playwright! * add barebones template test harness * fix compatibility between the two hotreload engines * fix preldue of server * fix check for wasm-opt * small fixes
This commit is contained in:
1 parent
71d85e6dd5
commit
d976ca184a
142 files changed
+17982
-7156
No files matched your search
+119
-123
@@ -1,14 +1,13 @@
|
||||
use crate::{
|
||||
config::WebHttpsConfig,
|
||||
serve::{ServeArgs, ServeUpdate},
|
||||
BuildStage, BuildUpdate, DioxusCrate, Platform, Result, TraceSrc,
|
||||
config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, Platform,
|
||||
Result, TraceSrc,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{
|
||||
ws::{Message, WebSocket},
|
||||
Request, State, WebSocketUpgrade,
|
||||
Query, Request, State, WebSocketUpgrade,
|
||||
},
|
||||
http::{
|
||||
header::{HeaderName, HeaderValue, CACHE_CONTROL, EXPIRES, PRAGMA},
|
||||
@@ -34,13 +33,18 @@ use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener},
|
||||
path::Path,
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
use subsecond_types::JumpTable;
|
||||
use tokio::process::Command;
|
||||
use tower_http::{
|
||||
cors::Any,
|
||||
services::fs::{ServeDir, ServeFileSystemResponseBody},
|
||||
ServiceBuilderExt,
|
||||
};
|
||||
|
||||
use super::AppServer;
|
||||
|
||||
/// The webserver that serves statics assets (if fullstack isn't already doing that) and the websocket
|
||||
/// communication layer that we use to send status updates and hotreloads to the client.
|
||||
///
|
||||
@@ -52,46 +56,40 @@ pub(crate) struct WebServer {
|
||||
devserver_bind_ip: IpAddr,
|
||||
devserver_port: u16,
|
||||
proxied_port: Option<u16>,
|
||||
hot_reload_sockets: Vec<WebSocket>,
|
||||
build_status_sockets: Vec<WebSocket>,
|
||||
new_hot_reload_sockets: UnboundedReceiver<WebSocket>,
|
||||
new_build_status_sockets: UnboundedReceiver<WebSocket>,
|
||||
hot_reload_sockets: Vec<ConnectedWsClient>,
|
||||
build_status_sockets: Vec<ConnectedWsClient>,
|
||||
new_hot_reload_sockets: UnboundedReceiver<ConnectedWsClient>,
|
||||
new_build_status_sockets: UnboundedReceiver<ConnectedWsClient>,
|
||||
build_status: SharedStatus,
|
||||
application_name: String,
|
||||
platform: Platform,
|
||||
}
|
||||
|
||||
pub(crate) struct ConnectedWsClient {
|
||||
socket: WebSocket,
|
||||
build_id: Option<BuildId>,
|
||||
aslr_reference: Option<u64>,
|
||||
}
|
||||
|
||||
impl WebServer {
|
||||
pub const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||
|
||||
/// Start the development server.
|
||||
/// This will set up the default http server if there's no server specified (usually via fullstack).
|
||||
///
|
||||
/// This will also start the websocket server that powers the devtools. If you want to communicate
|
||||
/// with connected devtools clients, this is the place to do it.
|
||||
pub(crate) fn start(krate: &DioxusCrate, args: &ServeArgs) -> Result<Self> {
|
||||
pub(crate) fn start(runner: &AppServer) -> Result<Self> {
|
||||
let (hot_reload_sockets_tx, hot_reload_sockets_rx) = futures_channel::mpsc::unbounded();
|
||||
let (build_status_sockets_tx, build_status_sockets_rx) = futures_channel::mpsc::unbounded();
|
||||
|
||||
const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
|
||||
// Use 0.0.0.0 as the default address if none is specified - this will let us expose the
|
||||
// devserver to the network (for other devices like phones/embedded)
|
||||
let devserver_bind_ip = args.address.addr.unwrap_or(SELF_IP);
|
||||
|
||||
// If the user specified a port, use that, otherwise use any available port, preferring 8080
|
||||
let devserver_port = args
|
||||
.address
|
||||
.port
|
||||
.unwrap_or_else(|| get_available_port(devserver_bind_ip, Some(8080)).unwrap_or(8080));
|
||||
|
||||
// All servers will end up behind us (the devserver) but on a different port
|
||||
// This is so we can serve a loading screen as well as devtools without anything particularly fancy
|
||||
let proxied_port = args
|
||||
.should_proxy_build()
|
||||
.then(|| get_available_port(devserver_bind_ip, None))
|
||||
.flatten();
|
||||
|
||||
// Create the listener that we'll pass into the devserver, but save its IP here so
|
||||
// we can display it to the user in the tui
|
||||
let devserver_bind_ip = runner.devserver_bind_ip;
|
||||
let devserver_port = runner.devserver_port;
|
||||
let proxied_port = runner.proxied_port;
|
||||
let devserver_exposed_ip = devserver_bind_ip;
|
||||
|
||||
let devserver_bind_address = SocketAddr::new(devserver_bind_ip, devserver_port);
|
||||
let listener = std::net::TcpListener::bind(devserver_bind_address).with_context(|| {
|
||||
anyhow::anyhow!(
|
||||
@@ -99,21 +97,12 @@ impl WebServer {
|
||||
)
|
||||
})?;
|
||||
|
||||
// If the IP is 0.0.0.0, we need to get the actual IP of the machine
|
||||
// This will let ios/android/network clients connect to the devserver
|
||||
let devserver_exposed_ip = if devserver_bind_ip == SELF_IP {
|
||||
local_ip_address::local_ip().unwrap_or(devserver_bind_ip)
|
||||
} else {
|
||||
devserver_bind_ip
|
||||
};
|
||||
|
||||
let proxied_address = proxied_port.map(|port| SocketAddr::new(devserver_exposed_ip, port));
|
||||
|
||||
// Set up the router with some shared state that we'll update later to reflect the current state of the build
|
||||
let build_status = SharedStatus::new_with_starting_build();
|
||||
let router = build_devserver_router(
|
||||
args,
|
||||
krate,
|
||||
runner,
|
||||
hot_reload_sockets_tx,
|
||||
build_status_sockets_tx,
|
||||
proxied_address,
|
||||
@@ -122,7 +111,7 @@ impl WebServer {
|
||||
|
||||
// And finally, start the server mainloop
|
||||
tokio::spawn(devserver_mainloop(
|
||||
krate.config.web.https.clone(),
|
||||
runner.client().build.config.web.https.clone(),
|
||||
listener,
|
||||
router,
|
||||
));
|
||||
@@ -137,8 +126,8 @@ impl WebServer {
|
||||
build_status_sockets: Default::default(),
|
||||
new_hot_reload_sockets: hot_reload_sockets_rx,
|
||||
new_build_status_sockets: build_status_sockets_rx,
|
||||
application_name: krate.executable_name().to_string(),
|
||||
platform: args.build_arguments.platform(),
|
||||
application_name: runner.app_name().to_string(),
|
||||
platform: runner.client.build.platform,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,15 +139,19 @@ impl WebServer {
|
||||
.hot_reload_sockets
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.map(|(idx, socket)| async move { (idx, socket.next().await) })
|
||||
.map(|(idx, socket)| async move { (idx, socket.socket.next().await) })
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
tokio::select! {
|
||||
new_hot_reload_socket = &mut new_hot_reload_socket => {
|
||||
if let Some(new_socket) = new_hot_reload_socket {
|
||||
let aslr_reference = new_socket.aslr_reference;
|
||||
let id = new_socket.build_id.unwrap_or(BuildId::CLIENT);
|
||||
|
||||
drop(new_message);
|
||||
self.hot_reload_sockets.push(new_socket);
|
||||
return ServeUpdate::NewConnection;
|
||||
|
||||
return ServeUpdate::NewConnection { aslr_reference, id };
|
||||
} else {
|
||||
panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use");
|
||||
}
|
||||
@@ -169,8 +162,8 @@ impl WebServer {
|
||||
|
||||
// Update the socket with project info and current build status
|
||||
let project_info = SharedStatus::new(Status::ClientInit { application_name: self.application_name.clone(), platform: self.platform });
|
||||
if project_info.send_to(&mut new_socket).await.is_ok() {
|
||||
_ = self.build_status.send_to(&mut new_socket).await;
|
||||
if project_info.send_to(&mut new_socket.socket).await.is_ok() {
|
||||
_ = self.build_status.send_to(&mut new_socket.socket).await;
|
||||
self.build_status_sockets.push(new_socket);
|
||||
}
|
||||
return future::pending::<ServeUpdate>().await;
|
||||
@@ -180,7 +173,7 @@ impl WebServer {
|
||||
}
|
||||
Some((idx, message)) = new_message.next() => {
|
||||
match message {
|
||||
Some(Ok(message)) => return ServeUpdate::WsMessage(message),
|
||||
Some(Ok(msg)) => return ServeUpdate::WsMessage { msg, platform: Platform::Web },
|
||||
_ => {
|
||||
drop(new_message);
|
||||
_ = self.hot_reload_sockets.remove(idx);
|
||||
@@ -193,26 +186,9 @@ impl WebServer {
|
||||
}
|
||||
|
||||
pub(crate) async fn shutdown(&mut self) {
|
||||
if matches!(self.platform, Platform::Android) {
|
||||
use std::process::{Command, Stdio};
|
||||
if let Err(err) = Command::new("adb")
|
||||
.arg("reverse")
|
||||
.arg("--remove")
|
||||
.arg(format!("tcp:{}", self.devserver_port))
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
{
|
||||
tracing::error!(
|
||||
"failed to remove forwarded port {}: {err}",
|
||||
self.devserver_port
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.send_shutdown().await;
|
||||
for mut socket in self.hot_reload_sockets.drain(..) {
|
||||
_ = socket.send(Message::Close(None)).await;
|
||||
_ = socket.socket.send(Message::Close(None)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +197,7 @@ impl WebServer {
|
||||
let mut i = 0;
|
||||
while i < self.build_status_sockets.len() {
|
||||
let socket = &mut self.build_status_sockets[i];
|
||||
if self.build_status.send_to(socket).await.is_err() {
|
||||
if self.build_status.send_to(&mut socket.socket).await.is_err() {
|
||||
self.build_status_sockets.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
@@ -239,13 +215,9 @@ impl WebServer {
|
||||
}
|
||||
|
||||
/// Sends an updated build status to all clients.
|
||||
pub(crate) async fn new_build_update(
|
||||
&mut self,
|
||||
update: &BuildUpdate,
|
||||
builder: &super::Builder,
|
||||
) {
|
||||
pub(crate) async fn new_build_update(&mut self, update: &BuilderUpdate) {
|
||||
match update {
|
||||
BuildUpdate::Progress { stage } => {
|
||||
BuilderUpdate::Progress { stage } => {
|
||||
// Todo(miles): wire up more messages into the splash screen UI
|
||||
match stage {
|
||||
BuildStage::Success => {}
|
||||
@@ -259,7 +231,10 @@ impl WebServer {
|
||||
krate,
|
||||
..
|
||||
} => {
|
||||
if !builder.is_finished() {
|
||||
if !matches!(
|
||||
self.build_status.get(),
|
||||
Status::Ready | Status::BuildError { .. }
|
||||
) {
|
||||
self.build_status.set(Status::Building {
|
||||
progress: (*current as f64 / *total as f64).clamp(0.0, 1.0),
|
||||
build_message: format!("{krate} compiling"),
|
||||
@@ -273,9 +248,9 @@ impl WebServer {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
BuildUpdate::CompilerMessage { .. } => {}
|
||||
BuildUpdate::BuildReady { .. } => {}
|
||||
BuildUpdate::BuildFailed { err } => {
|
||||
BuilderUpdate::CompilerMessage { .. } => {}
|
||||
BuilderUpdate::BuildReady { .. } => {}
|
||||
BuilderUpdate::BuildFailed { err } => {
|
||||
let error = err.to_string();
|
||||
self.build_status.set(Status::BuildError {
|
||||
error: ansi_to_html::convert(&error).unwrap_or(error),
|
||||
@@ -283,6 +258,9 @@ impl WebServer {
|
||||
self.send_reload_failed().await;
|
||||
self.send_build_status().await;
|
||||
}
|
||||
BuilderUpdate::StdoutReceived { .. } => {}
|
||||
BuilderUpdate::StderrReceived { .. } => {}
|
||||
BuilderUpdate::ProcessExited { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +270,6 @@ impl WebServer {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::trace!("Sending hotreload to clients {:?}", reload);
|
||||
|
||||
let msg = DevserverMsg::HotReload(reload);
|
||||
@@ -303,6 +280,7 @@ impl WebServer {
|
||||
while i < self.hot_reload_sockets.len() {
|
||||
let socket = &mut self.hot_reload_sockets[i];
|
||||
if socket
|
||||
.socket
|
||||
.send(Message::Text(msg.clone().into()))
|
||||
.await
|
||||
.is_err()
|
||||
@@ -314,15 +292,37 @@ impl WebServer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_patch(
|
||||
&mut self,
|
||||
jump_table: JumpTable,
|
||||
time_taken: Duration,
|
||||
build: BuildId,
|
||||
) {
|
||||
let msg = DevserverMsg::HotReload(HotReloadMsg {
|
||||
jump_table: Some(jump_table),
|
||||
ms_elapsed: time_taken.as_millis() as u64,
|
||||
templates: vec![],
|
||||
assets: vec![],
|
||||
for_build_id: Some(build.0 as _),
|
||||
});
|
||||
self.send_devserver_message_to_all(msg).await;
|
||||
}
|
||||
|
||||
/// Tells all clients that a hot patch has started.
|
||||
pub(crate) async fn send_patch_start(&mut self) {
|
||||
self.send_devserver_message_to_all(DevserverMsg::HotPatchStart)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Tells all clients that a full rebuild has started.
|
||||
pub(crate) async fn send_reload_start(&mut self) {
|
||||
self.send_devserver_message(DevserverMsg::FullReloadStart)
|
||||
self.send_devserver_message_to_all(DevserverMsg::FullReloadStart)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Tells all clients that a full rebuild has failed.
|
||||
pub(crate) async fn send_reload_failed(&mut self) {
|
||||
self.send_devserver_message(DevserverMsg::FullReloadFailed)
|
||||
self.send_devserver_message_to_all(DevserverMsg::FullReloadFailed)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -334,19 +334,21 @@ impl WebServer {
|
||||
|
||||
self.build_status.set(Status::Ready);
|
||||
self.send_build_status().await;
|
||||
self.send_devserver_message(DevserverMsg::FullReloadCommand)
|
||||
self.send_devserver_message_to_all(DevserverMsg::FullReloadCommand)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Send a shutdown message to all connected clients.
|
||||
pub(crate) async fn send_shutdown(&mut self) {
|
||||
self.send_devserver_message(DevserverMsg::Shutdown).await;
|
||||
self.send_devserver_message_to_all(DevserverMsg::Shutdown)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Sends a devserver message to all connected clients.
|
||||
async fn send_devserver_message(&mut self, msg: DevserverMsg) {
|
||||
async fn send_devserver_message_to_all(&mut self, msg: DevserverMsg) {
|
||||
for socket in self.hot_reload_sockets.iter_mut() {
|
||||
_ = socket
|
||||
.socket
|
||||
.send(Message::Text(serde_json::to_string(&msg).unwrap().into()))
|
||||
.await;
|
||||
}
|
||||
@@ -424,23 +426,24 @@ async fn devserver_mainloop(
|
||||
/// - Setting up the file serve service
|
||||
/// - Setting up the websocket endpoint for devtools
|
||||
fn build_devserver_router(
|
||||
args: &ServeArgs,
|
||||
krate: &DioxusCrate,
|
||||
hot_reload_sockets: UnboundedSender<WebSocket>,
|
||||
build_status_sockets: UnboundedSender<WebSocket>,
|
||||
runner: &AppServer,
|
||||
hot_reload_sockets: UnboundedSender<ConnectedWsClient>,
|
||||
build_status_sockets: UnboundedSender<ConnectedWsClient>,
|
||||
fullstack_address: Option<SocketAddr>,
|
||||
build_status: SharedStatus,
|
||||
) -> Result<Router> {
|
||||
let mut router = Router::new();
|
||||
let build = runner.client();
|
||||
|
||||
// Setup proxy for the endpoint specified in the config
|
||||
for proxy_config in krate.config.web.proxy.iter() {
|
||||
for proxy_config in build.build.config.web.proxy.iter() {
|
||||
router = super::proxy::add_proxy(router, proxy_config)?;
|
||||
}
|
||||
|
||||
if args.should_proxy_build() {
|
||||
// For fullstack, liveview, and server, forward all requests to the inner server
|
||||
let address = fullstack_address.unwrap();
|
||||
// For fullstack, liveview, and server, forward all requests to the inner server
|
||||
if runner.proxied_port.is_some() {
|
||||
tracing::debug!("Proxying requests to fullstack server at {fullstack_address:?}");
|
||||
let address = fullstack_address.context("No fullstack address specified")?;
|
||||
tracing::debug!("Proxying requests to fullstack server at {address}");
|
||||
router = router.fallback_service(super::proxy::proxy_to(
|
||||
format!("http://{address}").parse().unwrap(),
|
||||
@@ -460,7 +463,9 @@ fn build_devserver_router(
|
||||
// Route file service to output the .wasm and assets if this is a web build
|
||||
let base_path = format!(
|
||||
"/{}",
|
||||
krate
|
||||
runner
|
||||
.client()
|
||||
.build
|
||||
.config
|
||||
.web
|
||||
.app
|
||||
@@ -470,9 +475,9 @@ fn build_devserver_router(
|
||||
.trim_matches('/')
|
||||
);
|
||||
if base_path == "/" {
|
||||
router = router.fallback_service(build_serve_dir(args, krate));
|
||||
router = router.fallback_service(build_serve_dir(runner));
|
||||
} else {
|
||||
router = router.nest_service(&base_path, build_serve_dir(args, krate));
|
||||
router = router.nest_service(&base_path, build_serve_dir(runner));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,6 +487,12 @@ fn build_devserver_router(
|
||||
build_status_middleware,
|
||||
));
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ConnectionQuery {
|
||||
aslr_reference: Option<u64>,
|
||||
build_id: Option<BuildId>,
|
||||
}
|
||||
|
||||
// Setup websocket endpoint - and pass in the extension layer immediately after
|
||||
router = router.nest(
|
||||
"/_dioxus",
|
||||
@@ -489,9 +500,9 @@ fn build_devserver_router(
|
||||
.route(
|
||||
"/",
|
||||
get(
|
||||
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<WebSocket>>| async move {
|
||||
tracing::debug!("New devtool websocket connection");
|
||||
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(socket) })
|
||||
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>, query: Query<ConnectionQuery>| async move {
|
||||
tracing::debug!("New devtool websocket connection: {:?}", query);
|
||||
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id }) })
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -499,8 +510,8 @@ fn build_devserver_router(
|
||||
.route(
|
||||
"/build_status",
|
||||
get(
|
||||
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<WebSocket>>| async move {
|
||||
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(socket) })
|
||||
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>| async move {
|
||||
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None }) })
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -520,7 +531,7 @@ fn build_devserver_router(
|
||||
Ok(router)
|
||||
}
|
||||
|
||||
fn build_serve_dir(args: &ServeArgs, cfg: &DioxusCrate) -> axum::routing::MethodRouter {
|
||||
fn build_serve_dir(runner: &AppServer) -> axum::routing::MethodRouter {
|
||||
use tower::ServiceBuilder;
|
||||
|
||||
static CORS_UNSAFE: (HeaderValue, HeaderValue) = (
|
||||
@@ -533,15 +544,19 @@ fn build_serve_dir(args: &ServeArgs, cfg: &DioxusCrate) -> axum::routing::Method
|
||||
HeaderValue::from_static("same-origin"),
|
||||
);
|
||||
|
||||
let (coep, coop) = match args.cross_origin_policy {
|
||||
let (coep, coop) = match runner.cross_origin_policy {
|
||||
true => CORS_REQUIRE.clone(),
|
||||
false => CORS_UNSAFE.clone(),
|
||||
};
|
||||
|
||||
let out_dir = cfg
|
||||
.build_dir(Platform::Web, args.build_arguments.release)
|
||||
let app = &runner.client;
|
||||
let cfg = &runner.client.build.config;
|
||||
|
||||
let out_dir = app
|
||||
.build
|
||||
.build_dir(Platform::Web, app.build.release)
|
||||
.join("public");
|
||||
let index_on_404 = cfg.config.web.watcher.index_on_404;
|
||||
let index_on_404: bool = cfg.web.watcher.index_on_404;
|
||||
|
||||
get_service(
|
||||
ServiceBuilder::new()
|
||||
@@ -641,7 +656,7 @@ async fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {
|
||||
_ = fs::create_dir("ssl");
|
||||
}
|
||||
|
||||
let cmd = tokio::process::Command::new("mkcert")
|
||||
let cmd = Command::new("mkcert")
|
||||
.args([
|
||||
"-install",
|
||||
"-key-file",
|
||||
@@ -673,25 +688,6 @@ async fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {
|
||||
|
||||
Ok((cert_path, key_path))
|
||||
}
|
||||
/// Bind a listener to any point and return it
|
||||
/// When the listener is dropped, the socket will be closed, but we'll still have a port that we
|
||||
/// can bind our proxy to.
|
||||
///
|
||||
/// Todo: we might want to do this on every new build in case the OS tries to bind things to this port
|
||||
/// and we don't already have something bound to it. There's no great way of "reserving" a port.
|
||||
fn get_available_port(address: IpAddr, prefer: Option<u16>) -> Option<u16> {
|
||||
// First, try to bind to the preferred port
|
||||
if let Some(port) = prefer {
|
||||
if let Ok(_listener) = TcpListener::bind((address, port)) {
|
||||
return Some(port);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, try to bind to any port and return the first one we can
|
||||
TcpListener::bind((address, 0))
|
||||
.map(|listener| listener.local_addr().unwrap().port())
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Middleware that intercepts html requests if the status is "Building" and returns a loading page instead
|
||||
async fn build_status_middleware(
|
||||
@@ -708,7 +704,7 @@ async fn build_status_middleware(
|
||||
if let Some(true) = accepts_html {
|
||||
let status = state.get();
|
||||
if status != Status::Ready {
|
||||
let html = include_str!("../../assets/web/loading.html");
|
||||
let html = include_str!("../../assets/web/dev.loading.html");
|
||||
return axum::response::Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
// Load the html loader then keep loading forever
|
||||
|
||||
Reference in new issue
Block a user