Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[POC] Declarative way to write servers #22938

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion cli/args/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -339,6 +346,8 @@ pub enum DenoSubcommand {
Upgrade(UpgradeFlags),
Vendor(VendorFlags),
Publish(PublishFlags),

Serve(ServeFlags),
}

impl DenoSubcommand {
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -956,6 +965,7 @@ pub fn flags_from_vec(args: Vec<String>) -> clap::error::Result<Flags> {
"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 {
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions cli/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
18 changes: 18 additions & 0 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,24 @@ async fn run_subcommand(flags: Flags) -> Result<i32, AnyError> {
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?
Expand Down
58 changes: 58 additions & 0 deletions cli/tools/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,6 +20,62 @@ use crate::util::file_watcher::WatcherRestartMode;

pub mod hmr;

pub async fn serve(
flags: Flags,
serve_flags: ServeFlags,
) -> Result<i32, AnyError> {
// 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,
Expand Down
6 changes: 5 additions & 1 deletion ext/net/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
async fetch(request) {
// console.log("Got request", request.url);
return new Response("Hello world!");
},
};
3 changes: 3 additions & 0 deletions handler2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Deno.serve((request) => {
return new Response("Hello world!");
});
Loading