diff --git a/cli/args/flags.rs b/cli/args/flags.rs index f9f7364903135c..95809107887305 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -311,6 +311,13 @@ pub struct PublishFlags { pub no_provenance: bool, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ServeFlags { + pub entrypoint: String, + pub host: String, + pub port: String, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub enum DenoSubcommand { Add(AddFlags), @@ -339,6 +346,8 @@ pub enum DenoSubcommand { Upgrade(UpgradeFlags), Vendor(VendorFlags), Publish(PublishFlags), + + Serve(ServeFlags), } impl DenoSubcommand { @@ -769,7 +778,7 @@ impl Flags { } Add(_) | Bundle(_) | Completions(_) | Doc(_) | Fmt(_) | Init(_) | Install(_) | Uninstall(_) | Jupyter(_) | Lsp | Lint(_) | Types - | Upgrade(_) | Vendor(_) => None, + | Upgrade(_) | Vendor(_) | Serve(_) => None, } } @@ -956,6 +965,7 @@ pub fn flags_from_vec(args: Vec) -> clap::error::Result { "upgrade" => upgrade_parse(&mut flags, &mut m), "vendor" => vendor_parse(&mut flags, &mut m), "publish" => publish_parse(&mut flags, &mut m), + "serve" => serve_parse(&mut flags, &mut m), _ => unreachable!(), } } else { @@ -1105,6 +1115,7 @@ fn clap_root() -> Command { .subcommand(lsp_subcommand()) .subcommand(lint_subcommand()) .subcommand(publish_subcommand()) + .subcommand(serve_subcommand()) .subcommand(repl_subcommand()) .subcommand(task_subcommand()) .subcommand(test_subcommand()) @@ -2293,6 +2304,24 @@ Directory arguments are expanded to all contained files matching the glob ) } +fn serve_subcommand() -> Command { + Command::new("serve") + .about("Run a server") + .long_about("") + .defer(|cmd| { + runtime_args(cmd, true, true) + .arg(check_arg(true)) + .arg(Arg::new("host").long("host").require_equals(true)) + .arg(Arg::new("port").long("port").require_equals(true)) + .arg( + Arg::new("entrypoint") + .help("List of file names to run") + .value_hint(ValueHint::AnyPath), + ) + .arg(env_file_arg()) + }) +} + fn types_subcommand() -> Command { Command::new("types") .about("Print runtime TypeScript declarations") @@ -3908,6 +3937,31 @@ fn publish_parse(flags: &mut Flags, matches: &mut ArgMatches) { }); } +fn serve_parse(flags: &mut Flags, matches: &mut ArgMatches) { + runtime_args_parse(flags, matches, true, true); + flags.allow_all = true; + flags.allow_read = Some(vec![]); + flags.allow_env = Some(vec![]); + flags.allow_net = Some(vec![]); + flags.allow_run = Some(vec![]); + flags.allow_write = Some(vec![]); + flags.allow_sys = Some(vec![]); + flags.allow_ffi = Some(vec![]); + flags.allow_hrtime = true; + let entrypoint = matches.remove_one("entrypoint").unwrap(); + let host = matches + .remove_one("host") + .unwrap_or_else(|| "127.0.0.1".to_string()); + let port = matches + .remove_one("port") + .unwrap_or_else(|| "8000".to_string()); + flags.subcommand = DenoSubcommand::Serve(ServeFlags { + entrypoint, + host, + port, + }); +} + fn compile_args_parse(flags: &mut Flags, matches: &mut ArgMatches) { compile_args_without_check_parse(flags, matches); no_check_arg_parse(flags, matches); diff --git a/cli/args/mod.rs b/cli/args/mod.rs index d72b419476742d..bcd21dc53b8a55 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -999,6 +999,10 @@ impl CliOptions { resolve_url_or_path("./$deno$repl.ts", self.initial_cwd()) .map_err(AnyError::from) } + DenoSubcommand::Serve(_) => { + resolve_url_or_path("./$deno$serve.ts", self.initial_cwd()) + .map_err(AnyError::from) + } DenoSubcommand::Run(run_flags) => { if run_flags.is_stdin() { std::env::current_dir() diff --git a/cli/main.rs b/cli/main.rs index 3d72a3334324d0..5d6e2d4e7a79cb 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -221,6 +221,24 @@ async fn run_subcommand(flags: Flags) -> Result { DenoSubcommand::Publish(publish_flags) => spawn_subcommand(async { tools::registry::publish(flags, publish_flags).await }), + DenoSubcommand::Serve(serve_flags) => spawn_subcommand(async move { + let mut thread_handles = Vec::with_capacity(8); + + for _i in 0..8 { + let flags_ = flags.clone(); + let serve_flags_ = serve_flags.clone(); + let handle = std::thread::spawn(move || { + let future = tools::run::serve(flags_, serve_flags_).boxed_local(); + create_and_run_current_thread_with_maybe_metrics(future) + }); + thread_handles.push(handle); + } + + let handle = thread_handles.pop().unwrap(); + let _ = handle.join().unwrap().unwrap(); + + Ok(0) + }), }; handle.await? diff --git a/cli/tools/run/mod.rs b/cli/tools/run/mod.rs index 0de852fc222cfa..8ffbf50ad81dc1 100644 --- a/cli/tools/run/mod.rs +++ b/cli/tools/run/mod.rs @@ -3,12 +3,14 @@ use std::io::Read; use deno_core::error::AnyError; +use deno_core::resolve_url_or_path; use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsContainer; use crate::args::EvalFlags; use crate::args::Flags; use crate::args::RunFlags; +use crate::args::ServeFlags; use crate::args::WatchFlagsWithPaths; use crate::factory::CliFactory; use crate::factory::CliFactoryBuilder; @@ -18,6 +20,62 @@ use crate::util::file_watcher::WatcherRestartMode; pub mod hmr; +pub async fn serve( + flags: Flags, + serve_flags: ServeFlags, +) -> Result { + // TODO(bartlomieju): actually I think it will also fail if there's an import + // map specified and bare specifier is used on the command line + let factory = CliFactory::from_flags(flags).await?; + let _deno_dir = factory.deno_dir()?; + let _http_client = factory.http_client(); + let cli_options = factory.cli_options(); + + let main_module = cli_options.resolve_main_module()?; + + let serve_handler = + resolve_url_or_path(&serve_flags.entrypoint, cli_options.initial_cwd()) + .map_err(AnyError::from)?; + + maybe_npm_install(&factory).await?; + + let permissions = PermissionsContainer::new(Permissions::from_options( + &cli_options.permissions_options(), + )?); + let worker_factory = factory.create_cli_main_worker_factory().await?; + + let serve_code = format!( + r#"import * as handler from "{}"; + + if (typeof handler.default.fetch !== "function") {{ + throw new Error("Handler must export a fetch function"); + }} + const serveHandler = handler.default.fetch; + + Deno.serve({{ + hostname: "{}", + port: {}, + reusePort: true, + }}, (req) => {{ + return serveHandler(req); + }}); + "#, + serve_handler, serve_flags.host, serve_flags.port + ); + + factory.file_fetcher().unwrap().insert_memory_files(File { + specifier: main_module.clone(), + maybe_headers: None, + source: serve_code.as_bytes().into(), + }); + + let mut worker = worker_factory + .create_main_worker(main_module, permissions) + .await?; + let exit_code = worker.run().await?; + Ok(exit_code) +} + pub async fn run_script( flags: Flags, run_flags: RunFlags, diff --git a/ext/net/ops.rs b/ext/net/ops.rs index 4b24529355175c..6c0218f4e7fba7 100644 --- a/ext/net/ops.rs +++ b/ext/net/ops.rs @@ -378,7 +378,11 @@ where #[cfg(not(windows))] socket.set_reuse_address(true)?; if reuse_port { - #[cfg(any(target_os = "android", target_os = "linux"))] + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "macos" + ))] socket.set_reuse_port(true)?; } let socket_addr = socket2::SockAddr::from(addr); diff --git a/handler.js b/handler.js new file mode 100644 index 00000000000000..397e655571ec5c --- /dev/null +++ b/handler.js @@ -0,0 +1,6 @@ +export default { + async fetch(request) { + // console.log("Got request", request.url); + return new Response("Hello world!"); + }, +}; diff --git a/handler2.js b/handler2.js new file mode 100644 index 00000000000000..6154fbbfc88faf --- /dev/null +++ b/handler2.js @@ -0,0 +1,3 @@ +Deno.serve((request) => { + return new Response("Hello world!"); +});