-
-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5616 from roc-lang/server-example
Add basic webserver platform
- Loading branch information
Showing
11 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
platform/glue | ||
app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
interface Http exposes [request] imports [HttpInternal] | ||
|
||
request : Str -> Str | ||
request = \req -> HttpInternal.request req |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
interface HttpInternal exposes [request] imports [] | ||
|
||
request : Str -> Str | ||
request = \req -> req |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
roc glue ../../crates/glue/src/RustGlue.roc platform/glue platform/main.roc | ||
roc build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
app "app" | ||
packages { pf: "platform/main.roc" } | ||
imports [] | ||
provides [main] to pf | ||
|
||
main = \str -> "hi, \(str)!!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# ⚠️ READ THIS BEFORE MODIFYING THIS FILE! ⚠️ | ||
# | ||
# This file is a fixture template. If the file you're looking at is | ||
# in the fixture-templates/ directory, then you're all set - go ahead | ||
# and modify it, and it will modify all the fixture tests. | ||
# | ||
# If this file is in the fixtures/ directory, on the other hand, then | ||
# it is gitignored and will be overwritten the next time tests run. | ||
# So you probably don't want to modify it by hand! Instead, modify the | ||
# file with the same name in the fixture-templates/ directory. | ||
|
||
[package] | ||
name = "host" | ||
version = "0.0.1" | ||
authors = ["The Roc Contributors"] | ||
license = "UPL-1.0" | ||
edition = "2018" | ||
links = "app" | ||
|
||
[lib] | ||
name = "host" | ||
path = "src/lib.rs" | ||
crate-type = ["staticlib", "rlib"] | ||
|
||
[[bin]] | ||
name = "host" | ||
path = "src/main.rs" | ||
|
||
[dependencies] | ||
roc_std = { path = "glue/roc_std", features = ["std"] } | ||
roc_app = { path = "glue/roc_app" } | ||
libc = "0.2" | ||
hyper = { version = "0.14", features = ["http1", "http2", "client", "server", "runtime", "backports", "deprecated"] } | ||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } | ||
futures = "0.3" | ||
bytes = "1.0" | ||
|
||
[workspace] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
fn main() { | ||
#[cfg(not(windows))] | ||
println!("cargo:rustc-link-lib=dylib=app"); | ||
|
||
#[cfg(windows)] | ||
println!("cargo:rustc-link-lib=dylib=libapp"); | ||
|
||
println!("cargo:rustc-link-search=."); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// ⚠️ READ THIS BEFORE MODIFYING THIS FILE! ⚠️ | ||
// | ||
// This file is a fixture template. If the file you're looking at is | ||
// in the fixture-templates/ directory, then you're all set - go ahead | ||
// and modify it, and it will modify all the fixture tests. | ||
// | ||
// If this file is in the fixtures/ directory, on the other hand, then | ||
// it is gitignored and will be overwritten the next time tests run. | ||
// So you probably don't want to modify it by hand! Instead, modify the | ||
// file with the same name in the fixture-templates/ directory. | ||
|
||
extern int rust_main(); | ||
|
||
int main() { return rust_main(); } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
platform "webserver-platform" | ||
requires {} { main : _ } | ||
exposes [] | ||
packages {} | ||
imports [] | ||
provides [mainForHost] | ||
|
||
mainForHost : Str -> Str | ||
mainForHost = \str -> main str |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
use futures::{Future, FutureExt}; | ||
use hyper::{Body, Request, Response, Server, StatusCode}; | ||
use roc_app; | ||
use roc_std::RocStr; | ||
use std::cell::RefCell; | ||
use std::convert::Infallible; | ||
use std::net::SocketAddr; | ||
use std::os::raw::{c_int, c_long, c_void}; | ||
use std::panic::AssertUnwindSafe; | ||
use tokio::task::spawn_blocking; | ||
use libc::{sigaction, siginfo_t, sigemptyset, SIGBUS, SIGFPE, SIGILL, SIGSEGV, sighandler_t, sigset_t, SA_SIGINFO, SIG_DFL}; | ||
|
||
const DEFAULT_PORT: u16 = 8000; | ||
|
||
// If we have a roc_panic or a segfault, these will be used to record where to jump back to | ||
// (a point at which we can return a different response). | ||
thread_local! { | ||
// 64 is the biggest jmp_buf in setjmp.h | ||
static SETJMP_ENV: RefCell<[c_long; 64]> = RefCell::new([0 as c_long; 64]); | ||
static ROC_CRASH_MSG: RefCell<RocStr> = RefCell::new(RocStr::empty()); | ||
static SIGNAL_CAUGHT: RefCell<c_int> = RefCell::new(0); | ||
} | ||
|
||
extern "C" { | ||
#[link_name = "setjmp"] | ||
pub fn setjmp(env: *mut c_void) -> c_int; | ||
|
||
#[link_name = "longjmp"] | ||
pub fn longjmp(env: *mut c_void, val: c_int); | ||
} | ||
|
||
unsafe extern "C" fn signal_handler(sig: c_int, _: *mut siginfo_t, _: *mut libc::c_void) { | ||
SIGNAL_CAUGHT.with(|val| { | ||
*val.borrow_mut() = sig; | ||
}); | ||
|
||
SETJMP_ENV.with(|env| { | ||
longjmp(env.borrow_mut().as_mut_ptr().cast(), 1); | ||
}); | ||
} | ||
|
||
fn setup_signal(sig: c_int) { | ||
let sa = libc::sigaction { | ||
sa_sigaction: signal_handler as sighandler_t, | ||
sa_mask: sigset_t::default(), | ||
sa_flags: SA_SIGINFO, | ||
}; | ||
|
||
let mut old_sa = libc::sigaction { | ||
sa_sigaction: SIG_DFL, | ||
sa_mask: sigset_t::default(), | ||
sa_flags: 0, | ||
}; | ||
|
||
unsafe { | ||
sigemptyset(&mut old_sa.sa_mask as *mut sigset_t); | ||
sigaction(sig, &sa, &mut old_sa); | ||
} | ||
} | ||
|
||
fn call_roc(req_bytes: &[u8]) -> Response<Body> { | ||
let mut setjmp_result = 0; | ||
|
||
SETJMP_ENV.with(|env| { | ||
setjmp_result = unsafe { setjmp(env.borrow_mut().as_mut_ptr().cast()) }; | ||
}); | ||
|
||
if setjmp_result == 0 { | ||
setup_signal(SIGSEGV); | ||
setup_signal(SIGILL); | ||
setup_signal(SIGFPE); | ||
setup_signal(SIGBUS); | ||
|
||
let req_str: &str = std::str::from_utf8(req_bytes).unwrap(); // TODO don't unwrap | ||
let resp: String = roc_app::mainForHost(req_str.into()).as_str().into(); | ||
|
||
Response::builder() | ||
.status(StatusCode::OK) // TODO get status code from Roc too | ||
.body(Body::from(resp)) | ||
.unwrap() // TODO don't unwrap() here | ||
} else { | ||
let mut crash_msg: String = String::new(); | ||
let mut sig: c_int = 0; | ||
|
||
SIGNAL_CAUGHT.with(|val| { | ||
sig = *val.borrow(); | ||
}); | ||
|
||
if sig == 0 { | ||
ROC_CRASH_MSG.with(|env| { | ||
crash_msg = env.borrow().as_str().into(); | ||
}); | ||
} else { | ||
crash_msg = "Roc crashed with signal {sig}".into(); // TODO print the name of the signal | ||
} | ||
|
||
Response::builder() | ||
.status(StatusCode::INTERNAL_SERVER_ERROR) | ||
.body(Body::from(crash_msg)) | ||
.unwrap() // TODO don't unwrap() here | ||
} | ||
} | ||
|
||
async fn handle_req(req: Request<Body>) -> Response<Body> { | ||
match hyper::body::to_bytes(req.into_body()).await { | ||
Ok(req_body) => { | ||
spawn_blocking(move || call_roc(&req_body)) | ||
.then(|resp| async { | ||
resp.unwrap() // TODO don't unwrap here | ||
}) | ||
.await | ||
} | ||
Err(_) => todo!(), // TODO | ||
} | ||
} | ||
|
||
/// Translate Rust panics in the given Future into 500 errors | ||
async fn handle_panics( | ||
fut: impl Future<Output = Response<Body>>, | ||
) -> Result<Response<Body>, Infallible> { | ||
match AssertUnwindSafe(fut).catch_unwind().await { | ||
Ok(response) => Ok(response), | ||
Err(_panic) => { | ||
let error = Response::builder() | ||
.status(StatusCode::INTERNAL_SERVER_ERROR) | ||
.body("Panic detected!".into()) | ||
.unwrap(); // TODO don't unwrap here | ||
|
||
Ok(error) | ||
} | ||
} | ||
} | ||
|
||
const LOCALHOST: [u8; 4] = [127, 0, 0, 1]; | ||
|
||
async fn run_server(port: u16) -> i32 { | ||
let addr = SocketAddr::from((LOCALHOST, port)); | ||
let server = Server::bind(&addr).serve(hyper::service::make_service_fn(|_conn| async { | ||
Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle_req(req)))) | ||
})); | ||
|
||
println!("Listening on <http://localhost:{port}>"); | ||
|
||
match server.await { | ||
Ok(_) => 0, | ||
Err(err) => { | ||
eprintln!("Error initializing Rust `hyper` server: {}", err); // TODO improve this | ||
|
||
1 | ||
} | ||
} | ||
} | ||
|
||
#[no_mangle] | ||
pub extern "C" fn rust_main() -> i32 { | ||
match tokio::runtime::Builder::new_multi_thread() | ||
.enable_all() | ||
.build() | ||
{ | ||
Ok(runtime) => runtime.block_on(async { run_server(DEFAULT_PORT).await }), | ||
Err(err) => { | ||
eprintln!("Error initializing tokio multithreaded runtime: {}", err); // TODO improve this | ||
|
||
1 | ||
} | ||
} | ||
} | ||
|
||
// Externs required by roc_std and by the Roc app | ||
|
||
#[no_mangle] | ||
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { | ||
return libc::malloc(size); | ||
} | ||
|
||
#[no_mangle] | ||
pub unsafe extern "C" fn roc_realloc( | ||
c_ptr: *mut c_void, | ||
new_size: usize, | ||
_old_size: usize, | ||
_alignment: u32, | ||
) -> *mut c_void { | ||
return libc::realloc(c_ptr, new_size); | ||
} | ||
|
||
#[no_mangle] | ||
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { | ||
return libc::free(c_ptr); | ||
} | ||
|
||
#[no_mangle] | ||
pub unsafe extern "C" fn roc_panic(msg: RocStr) { | ||
// Set the last caught signal to 0, so we don't mistake this for a signal. | ||
SIGNAL_CAUGHT.with(|val| { | ||
*val.borrow_mut() = 0; | ||
}); | ||
|
||
ROC_CRASH_MSG.with(|val| { | ||
*val.borrow_mut() = msg; | ||
}); | ||
|
||
SETJMP_ENV.with(|env| { | ||
longjmp(env.borrow_mut().as_mut_ptr().cast(), 1); | ||
}); | ||
} | ||
|
||
#[no_mangle] | ||
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { | ||
libc::memset(dst, c, n) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fn main() { | ||
std::process::exit(host::rust_main() as _); | ||
} |