rewrite using actix
actix supports websocket, which iron does not. Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
parent
0f9110b36c
commit
881599d031
1037
Cargo.lock
generated
1037
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@ -4,22 +4,10 @@ version = "0.1.0"
|
|||||||
authors = ["Sean Cross <sean@xobs.io>"]
|
authors = ["Sean Cross <sean@xobs.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
#[dependencies]
|
|
||||||
#clap = "2.25"
|
|
||||||
#error-type = "0.1"
|
|
||||||
#futures = "0.1"
|
|
||||||
#futures-cpupool = "0.1"
|
|
||||||
#git2 = "0.8"
|
|
||||||
#http = "0.1"
|
|
||||||
#hyper = "0.12"
|
|
||||||
##hyper-staticfile = "0.3"
|
|
||||||
#serde_json = "1.0"
|
|
||||||
#websocket = "0.22"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
actix = "0.7"
|
||||||
|
actix-web = "0.7"
|
||||||
|
env_logger = "0.5"
|
||||||
git2 = "0.8"
|
git2 = "0.8"
|
||||||
iron = "0.6"
|
http = "0.1"
|
||||||
mount = "0.4"
|
serde_json = "1.0"
|
||||||
serde_json = "1.0"
|
|
||||||
staticfile = "0.5"
|
|
||||||
websocket = "0.22"
|
|
118
src/main.rs
118
src/main.rs
@ -1,8 +1,11 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
extern crate git2;
|
extern crate git2;
|
||||||
extern crate iron;
|
extern crate http;
|
||||||
extern crate mount;
|
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate staticfile;
|
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
|
||||||
// This example serves the docs from target/doc/staticfile at /doc/
|
// This example serves the docs from target/doc/staticfile at /doc/
|
||||||
//
|
//
|
||||||
@ -11,12 +14,12 @@ extern crate staticfile;
|
|||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::env;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use iron::prelude::{Chain, Request, Response};
|
use actix_web::{
|
||||||
use iron::{status, Iron, IronResult};
|
fs, middleware, server, App, HttpMessage, HttpRequest, HttpResponse
|
||||||
use mount::Mount;
|
};
|
||||||
use staticfile::Static;
|
|
||||||
|
|
||||||
use git2::Repository;
|
use git2::Repository;
|
||||||
|
|
||||||
struct WebHookConfig {
|
struct WebHookConfig {
|
||||||
@ -42,44 +45,61 @@ fn redeploy_repo(url: &str, path: &Path, name: &str) -> Result<(), git2::Error>
|
|||||||
};
|
};
|
||||||
repo.set_head("FETCH_HEAD")?;
|
repo.set_head("FETCH_HEAD")?;
|
||||||
|
|
||||||
// repo.checkout_tree(&branch.into_reference(), Some(git2::build::CheckoutBuilder::new().force().use_theirs(true)))?;
|
|
||||||
repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force().use_theirs(true)))?;
|
repo.checkout_head(Some(git2::build::CheckoutBuilder::new().force().use_theirs(true)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn webhook(req: &mut Request, config: &WebHookConfig) -> IronResult<Response> {
|
fn webhook_handler((contents, req): (String, HttpRequest<Arc<Mutex<WebHookConfig>>>)) -> HttpResponse {
|
||||||
eprintln!("Webhook Request: {:?}", req);
|
let config = req.state().lock().unwrap();
|
||||||
let wh: serde_json::Value = serde_json::from_reader(&mut req.body).unwrap();
|
|
||||||
|
// Ensure there's a "Push" event
|
||||||
|
if let Some(v) = req.headers().get("X-GitHub-Event") {
|
||||||
|
match v.to_str() {
|
||||||
|
Err(_) => return HttpResponse::BadRequest().body("invalid X-Github-Event header"),
|
||||||
|
Ok(o) => {
|
||||||
|
if o != "push" {
|
||||||
|
return HttpResponse::Ok().body("Ok");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return HttpResponse::Ok().body("Ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
let wh: serde_json::Value = match serde_json::from_str(&contents) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => return HttpResponse::BadRequest().body(format!("invalid json data: {}", e.description())),
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure we have a "Secret" parameter, and that it matches the config.
|
// Ensure we have a "Secret" parameter, and that it matches the config.
|
||||||
let found_secret = wh.get("secret");
|
let found_secret = wh.get("secret");
|
||||||
if found_secret.is_none() {
|
if found_secret.is_none() {
|
||||||
return Ok(Response::with(status::Unauthorized));
|
return HttpResponse::Unauthorized().body("invalid secret");
|
||||||
}
|
}
|
||||||
let found_secret = found_secret.unwrap();
|
let found_secret = found_secret.unwrap();
|
||||||
if found_secret != &config.secret {
|
if found_secret != &config.secret {
|
||||||
return Ok(Response::with(status::Unauthorized));
|
return HttpResponse::Unauthorized().body("invalid secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the repo is valid.
|
// Ensure the repo is valid.
|
||||||
let repository = wh.get("repository");
|
let repository = wh.get("repository");
|
||||||
if repository.is_none() {
|
if repository.is_none() {
|
||||||
eprintln!("No 'repository' key found");
|
eprintln!("No 'repository' key found");
|
||||||
return Ok(Response::with(status::PreconditionRequired));
|
return HttpResponse::PreconditionFailed().body("no 'repository' key found");
|
||||||
}
|
}
|
||||||
let repository = repository.unwrap();
|
let repository = repository.unwrap();
|
||||||
|
|
||||||
// Ensure there's an HTML URL that's a valid string
|
// Ensure there's an HTML URL that's a valid string
|
||||||
let html_url = match repository.get("html_url") {
|
let html_url = match repository.get("html_url") {
|
||||||
None => {
|
None => {
|
||||||
eprintln!("No HTML URL found");
|
eprintln!("No HTML URL found");
|
||||||
return Ok(Response::with(status::PreconditionRequired));
|
return HttpResponse::PreconditionFailed().body("no repository html_url found");
|
||||||
}
|
}
|
||||||
Some(s) => match s.as_str() {
|
Some(s) => match s.as_str() {
|
||||||
None => {
|
None => {
|
||||||
eprintln!("HTML URL isn't a string");
|
eprintln!("HTML URL isn't a string");
|
||||||
return Ok(Response::with(status::PreconditionRequired));
|
return HttpResponse::PreconditionFailed().body("html_url is not a string");
|
||||||
}
|
}
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
},
|
},
|
||||||
@ -95,32 +115,32 @@ fn webhook(req: &mut Request, config: &WebHookConfig) -> IronResult<Response> {
|
|||||||
}
|
}
|
||||||
if !found_match {
|
if !found_match {
|
||||||
eprintln!("URL doesn't start with match");
|
eprintln!("URL doesn't start with match");
|
||||||
return Ok(Response::with(status::PreconditionRequired));
|
return HttpResponse::PreconditionFailed().body("url doesn't start with a recognized prefix");
|
||||||
}
|
}
|
||||||
|
|
||||||
// And ensure the URL is a string
|
// And ensure the URL is a string
|
||||||
let website_url = match repository.get("website") {
|
let website_url: http::uri::Uri = match repository.get("website") {
|
||||||
None => {
|
None => {
|
||||||
eprintln!("The website isn't configured for this repo");
|
eprintln!("The website isn't configured for this repo");
|
||||||
return Ok(Response::with(status::PreconditionRequired));
|
return HttpResponse::PreconditionFailed().body("this repo has no website configured");
|
||||||
}
|
}
|
||||||
Some(s) => match s.as_str() {
|
Some(s) => match s.as_str() {
|
||||||
None => {
|
None => {
|
||||||
eprintln!("Website URL isn't a string");
|
eprintln!("Website URL isn't a string");
|
||||||
return Ok(Response::with(status::PreconditionRequired));
|
return HttpResponse::PreconditionFailed().body("repo's website url is not a string");
|
||||||
}
|
}
|
||||||
Some(s) => match iron::Url::parse(s) {
|
Some(s) => match s.parse() {
|
||||||
Ok(u) => u,
|
Ok(u) => u,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Unable to parse URL: {:?}", e);
|
eprintln!("Unable to parse URL: {:?}", e);
|
||||||
return Ok(Response::with(status::PreconditionFailed));
|
return HttpResponse::PreconditionFailed().body("repo's url is unparseable");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let target_path = match website_url.path().last() {
|
let target_path = match website_url.path().split("/").last() {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
if s.is_empty() {
|
if s.is_empty() || s == "." || s == ".." {
|
||||||
eprintln!("No website URL path was found -- defaulting to \"current\"");
|
eprintln!("No website URL path was found -- defaulting to \"current\"");
|
||||||
"current".to_owned()
|
"current".to_owned()
|
||||||
} else {
|
} else {
|
||||||
@ -129,21 +149,24 @@ fn webhook(req: &mut Request, config: &WebHookConfig) -> IronResult<Response> {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
eprintln!("No website URL path was found");
|
eprintln!("No website URL path was found");
|
||||||
return Ok(Response::with(status::PreconditionFailed));
|
return HttpResponse::PreconditionFailed().body("no website url path could be found");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
eprintln!("Final path: {:?} / {:?}", config.repo_root, target_path);
|
eprintln!("Final path: {:?} / {:?}", config.repo_root, target_path);
|
||||||
|
|
||||||
if let Err(e) = redeploy_repo(html_url, &config.repo_root, &target_path) {
|
if let Err(e) = redeploy_repo(html_url, &config.repo_root, &target_path) {
|
||||||
eprintln!("unable to clone/update repo: {}", e.description());
|
eprintln!("unable to clone/update repo: {}", e.description());
|
||||||
return Ok(Response::with(status::NotModified));
|
return HttpResponse::NotModified().body("unable to clone/update repo");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Response::with(status::Ok))
|
HttpResponse::Ok().body("Ok") // <- send response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut mount = Mount::new();
|
env::set_var("RUST_LOG", "actix_web=debug");
|
||||||
|
env::set_var("RUST_BACKTRACE", "1");
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
let hostaddr = "0.0.0.0:9119".to_owned();
|
let hostaddr = "0.0.0.0:9119".to_owned();
|
||||||
let config = WebHookConfig {
|
let config = WebHookConfig {
|
||||||
secret: "1234".to_owned(),
|
secret: "1234".to_owned(),
|
||||||
@ -151,16 +174,31 @@ fn main() {
|
|||||||
repo_prefixes: vec!["https://git.xobs.io/xobs".to_owned()],
|
repo_prefixes: vec!["https://git.xobs.io/xobs".to_owned()],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Serve the shared JS/CSS at /
|
|
||||||
mount.mount("/", Static::new(config.repo_root.clone()));
|
|
||||||
|
|
||||||
// Listen for calls to "/webhook" and process accordingly
|
|
||||||
mount.mount(
|
|
||||||
"/webhook",
|
|
||||||
Chain::new(move |req: &mut Request| webhook(req, &config)),
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("Doc server running on http://{}", hostaddr);
|
println!("Doc server running on http://{}", hostaddr);
|
||||||
|
|
||||||
Iron::new(mount).http(hostaddr).unwrap();
|
let repo_root_copy = Arc::new(Mutex::new(config.repo_root.clone()));
|
||||||
|
let config = Arc::new(Mutex::new(config));
|
||||||
|
let sys = actix::System::new("basic-example");
|
||||||
|
|
||||||
|
server::new(move || {
|
||||||
|
App::with_state(config.clone())
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
|
||||||
|
// Our webhook
|
||||||
|
.resource("/webhook", |r|
|
||||||
|
r.method(actix_web::http::Method::POST)
|
||||||
|
.with_config(webhook_handler, |((cfg, _),)| {
|
||||||
|
cfg.limit(16384);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// static files
|
||||||
|
.handler("/", fs::StaticFiles::new(&*repo_root_copy.lock().unwrap().clone()).unwrap().index_file("index.html"))
|
||||||
|
|
||||||
|
})
|
||||||
|
.bind(hostaddr.clone()).expect(&format!("Can not bind to {}", hostaddr))
|
||||||
|
.shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s)
|
||||||
|
.start();
|
||||||
|
let _ = sys.run();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user