Skip to content

Instantly share code, notes, and snippets.

@williballenthin
Created October 22, 2019 19:21
Show Gist options
  • Save williballenthin/fff30c837946a5d5ad6513ebc5a8a08a to your computer and use it in GitHub Desktop.
Save williballenthin/fff30c837946a5d5ad6513ebc5a8a08a to your computer and use it in GitHub Desktop.
use log::{info};
use actix;
use actix_web::{
http, middleware, server, App, HttpRequest, HttpResponse,
};
/// These are the possible states that the server can be requested to run.
#[derive(Debug)]
enum ServerState {
/// Stop the server.
STOP,
/// Run ServerA.
A,
/// Run ServerB.
B,
}
struct AppState {
/// Used by the Server runtime to signal the requested next state.
tx: crossbeam_channel::Sender<ServerState>
}
#[derive(Clone)]
struct Config {
address: String,
}
trait Server {
/// Run some server, block, and when requested, return the next server state.
fn run(&self) -> ServerState;
}
struct ServerA {
config: Config
}
impl ServerA {
pub fn new(config: Config) -> ServerA {
ServerA {
config
}
}
fn root(_req: &HttpRequest<AppState>) -> HttpResponse {
HttpResponse::Ok().body(
"
<html><body>
hello from A
<br />
<ul>
<li><a href=\"/goto/b/\"><button>goto b</button></a></li>
<li><a href=\"/stop/\"><button>stop</button></a></li>
</ul>
</body></html>
"
)
}
fn stop(req: &HttpRequest<AppState>) -> HttpResponse {
req.state().tx.send(ServerState::STOP).unwrap();
let resp = HttpResponse::Ok().body("stopping...");
// race: hope that the response is sent before server shutdown.
actix::System::current().stop();
resp
}
fn b(req: &HttpRequest<AppState>) -> HttpResponse {
req.state().tx.send(ServerState::B).unwrap();
let resp = HttpResponse::Ok().body("transitioning to b...");
// race: hope that the response is sent before server shutdown.
actix::System::current().stop();
resp
}
}
impl Server for ServerA {
fn run(&self) -> ServerState {
let (tx, rx) = crossbeam_channel::unbounded();
let sys = actix::System::new("sys");
server::new(move || {
App::with_state(AppState {
tx: tx.clone()
})
.middleware(middleware::Logger::default())
.resource("/", |r|
r.method(http::Method::GET).f(ServerA::root)
)
.resource("/stop/", |r|
r.method(http::Method::GET).f(ServerA::stop)
)
.resource("/goto/b/", |r|
r.method(http::Method::GET).f(ServerA::b)
)
})
.bind(&self.config.address)
.unwrap()
.shutdown_timeout(1)
.start();
sys.run();
rx.recv().unwrap()
}
}
struct ServerB {
config: Config
}
impl ServerB {
pub fn new(config: Config) -> ServerB {
ServerB {
config
}
}
fn root(_req: &HttpRequest<AppState>) -> HttpResponse {
HttpResponse::Ok().body(
"
<html><body>
hello from B
<br />
<ul>
<li><a href=\"/goto/a/\"><button>goto a</button></a></li>
<li><a href=\"/stop/\"><button>stop</button></a></li>
</ul>
</body></html>
"
)
}
fn stop(req: &HttpRequest<AppState>) -> HttpResponse {
req.state().tx.send(ServerState::STOP).unwrap();
let resp = HttpResponse::Ok().body("stopping...");
// race: hope that the response is sent before server shutdown.
actix::System::current().stop();
resp
}
fn a(req: &HttpRequest<AppState>) -> HttpResponse {
req.state().tx.send(ServerState::A).unwrap();
let resp = HttpResponse::Ok().body("transitioning to a...");
// race: hope that the response is sent before server shutdown.
actix::System::current().stop();
resp
}
}
impl Server for ServerB {
fn run(&self) -> ServerState {
let (tx, rx) = crossbeam_channel::unbounded();
let sys = actix::System::new("sys");
server::new(move || {
App::with_state(AppState {
tx: tx.clone()
})
.middleware(middleware::Logger::default())
.resource("/", |r|
r.method(http::Method::GET).f(ServerB::root)
)
.resource("/stop/", |r|
r.method(http::Method::GET).f(ServerB::stop)
)
.resource("/goto/a/", |r|
r.method(http::Method::GET).f(ServerB::a)
)
})
.bind(&self.config.address)
.unwrap()
.shutdown_timeout(1)
.start();
sys.run();
rx.recv().unwrap()
}
}
fn main() {
simplelog::TermLogger::init(simplelog::LevelFilter::Debug, simplelog::Config::default())
.or_else(|_| {
// when run non-interactively, e.g. in docker, termlogger can't open the tty
// so we fall back to simple formatting (i.e. no colors)
simplelog::SimpleLogger::init(simplelog::LevelFilter::Debug, simplelog::Config::default())
})
.expect("failed to setup logging");
let config = Config { address: String::from("127.0.0.1:8080") };
let mut server_state = ServerState::A; // initial state: A
loop {
if let ServerState::STOP = server_state {
info!("stopping the server...");
break;
}
let srv: Box<dyn Server> = match server_state {
ServerState::A => {
info!("running server: {:?}", server_state);
Box::new(ServerA::new(config.clone()))
},
ServerState::B => {
info!("running server: {:?}", server_state);
Box::new(ServerB::new(config.clone()))
},
// keep this variant explicit
// so that we force a compiler error if we add a new variant,
// and forget to update here.
ServerState::STOP => { unreachable!() },
};
server_state = srv.run();
}
info!("done.")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment