iptoasn-webservice/src/webservice.rs

287 lines
9.6 KiB
Rust
Raw Normal View History

2019-04-03 01:58:51 +02:00
use crate::asns::*;
use horrorshow::prelude::*;
2019-05-26 17:32:30 +02:00
use iron::headers::{Accept, CacheControl, CacheDirective, Expires, HttpDate, Vary};
2016-10-26 13:11:19 +02:00
use iron::mime::*;
use iron::modifiers::Header;
use iron::prelude::*;
use iron::status;
2019-02-18 14:41:52 +01:00
use iron::{typemap, BeforeMiddleware};
2016-10-26 13:11:19 +02:00
use router::Router;
use serde_json;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::{Arc, RwLock};
2019-05-26 17:32:30 +02:00
use time::{self, Duration};
use unicase::UniCase;
2016-10-26 13:11:19 +02:00
2018-02-18 21:44:19 +01:00
const TTL: u32 = 86_400;
2016-10-26 13:11:19 +02:00
struct ASNsMiddleware {
2016-10-27 16:37:07 +02:00
asns_arc: Arc<RwLock<Arc<ASNs>>>,
2016-10-26 13:11:19 +02:00
}
impl typemap::Key for ASNsMiddleware {
type Value = Arc<ASNs>;
}
impl ASNsMiddleware {
2016-10-27 16:37:07 +02:00
fn new(asns_arc: Arc<RwLock<Arc<ASNs>>>) -> ASNsMiddleware {
2019-02-18 14:41:52 +01:00
ASNsMiddleware { asns_arc }
2016-10-26 13:11:19 +02:00
}
}
impl BeforeMiddleware for ASNsMiddleware {
2019-04-03 01:58:51 +02:00
fn before(&self, req: &mut Request<'_, '_>) -> IronResult<()> {
req.extensions
.insert::<ASNsMiddleware>(self.asns_arc.read().unwrap().clone());
2016-10-26 13:11:19 +02:00
Ok(())
}
}
enum OutputType {
Json,
Html,
}
2016-10-26 13:11:19 +02:00
pub struct WebService;
impl WebService {
2019-04-03 01:58:51 +02:00
fn index(_: &mut Request<'_, '_>) -> IronResult<Response> {
2017-08-12 14:32:05 +02:00
Ok(Response::with((
status::Ok,
Mime(
TopLevel::Text,
SubLevel::Plain,
vec![(Attr::Charset, Value::Utf8)],
),
2018-02-18 21:28:09 +01:00
Header(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(TTL),
])),
2019-05-26 17:32:30 +02:00
Header(Expires(HttpDate(
time::now() + Duration::seconds(TTL.into()),
))),
2017-08-12 14:32:05 +02:00
"See https://iptoasn.com",
)))
2016-10-26 13:11:19 +02:00
}
2019-04-03 01:58:51 +02:00
fn accept_type(req: &Request<'_, '_>) -> OutputType {
let mut output_type = OutputType::Json;
if let Some(header_accept) = req.headers.get::<Accept>() {
for header in header_accept.iter() {
match header.item {
Mime(TopLevel::Text, SubLevel::Html, _) => {
output_type = OutputType::Html;
break;
}
Mime(_, SubLevel::Json, _) => {
output_type = OutputType::Json;
break;
}
_ => {}
}
}
}
output_type
}
2017-08-12 14:32:05 +02:00
fn output_json(
2018-02-18 21:44:19 +01:00
map: &serde_json::Map<String, serde_json::value::Value>,
2019-05-26 17:32:30 +02:00
cache_headers: (Header<CacheControl>, Header<Expires>),
vary_header: Header<Vary>,
2017-08-12 14:32:05 +02:00
) -> IronResult<Response> {
let json = serde_json::to_string(&map).unwrap();
2017-08-12 14:32:05 +02:00
let mime_json = Mime(
TopLevel::Application,
SubLevel::Json,
vec![(Attr::Charset, Value::Utf8)],
);
2018-02-18 21:28:09 +01:00
Ok(Response::with((
status::Ok,
mime_json,
2019-05-26 17:32:30 +02:00
cache_headers.0,
cache_headers.1,
2018-02-18 21:28:09 +01:00
vary_header,
json,
)))
}
2017-08-12 14:32:05 +02:00
fn output_html(
2018-02-18 21:44:19 +01:00
map: &serde_json::Map<String, serde_json::value::Value>,
2019-05-26 17:32:30 +02:00
cache_headers: (Header<CacheControl>, Header<Expires>),
vary_header: Header<Vary>,
2017-08-12 14:32:05 +02:00
) -> IronResult<Response> {
let mime_html = Mime(
TopLevel::Text,
SubLevel::Html,
vec![(Attr::Charset, Value::Utf8)],
);
let html = html!{
head {
title { : "iptoasn lookup" }
2016-11-14 12:28:15 +01:00
meta(name="viewport", content="width=device-widthinitial-scale=1");
link(rel="stylesheet", href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css", integrity="sha384-AysaV+vQoT3kOAXZkl02PThvDr8HYKPZhNT5h/CXfBThSRXQ6jW5DO2ekP5ViFdi", crossorigin="anonymous");
style {
: "body { margin: 1em 4em }"
}
}
body(class="container-fluid") {
header {
h1 { : format_args!("Information for IP address: {}", map.get("ip").unwrap().as_str().unwrap()) }
}
table {
tr {
th { : "Announced" }
2016-11-14 12:28:15 +01:00
td { : format_args!("{}", if map.get("announced")
.unwrap().as_bool().unwrap() { "Yes" } else { "No" }) }
}
2018-02-18 21:44:19 +01:00
@ if map.get("announced").unwrap().as_bool().unwrap() {
tr {
th { : "First IP" }
td { : format_args!("{}", map.get("first_ip")
2016-11-12 23:44:47 +01:00
.unwrap().as_str().unwrap()) }
}
tr {
th { : "Last IP" }
td { : format_args!("{}", map.get("last_ip")
2016-11-12 23:44:47 +01:00
.unwrap().as_str().unwrap()) }
}
tr {
th { : "AS Number" }
td { : format_args!("{}", map.get("as_number")
2016-11-12 23:44:47 +01:00
.unwrap().as_u64().unwrap()) }
}
tr {
th { : "AS Country code" }
td { : format_args!("{}", map.get("as_country_code")
2016-11-12 23:44:47 +01:00
.unwrap().as_str().unwrap()) }
}
tr {
th { : "AS Description" }
td { : format_args!("{}", map.get("as_description")
2016-11-12 23:44:47 +01:00
.unwrap().as_str().unwrap()) }
}
}
}
}
2018-02-18 21:28:09 +01:00
}.into_string()
.unwrap();
2016-11-14 12:28:15 +01:00
let html = format!("<!DOCTYPE html>\n<html>{}</html>", html);
2018-02-18 21:28:09 +01:00
Ok(Response::with((
status::Ok,
mime_html,
2019-05-26 17:32:30 +02:00
cache_headers.0,
cache_headers.1,
2018-02-18 21:28:09 +01:00
vary_header,
html,
)))
}
2017-08-12 14:32:05 +02:00
fn output(
2018-02-18 21:44:19 +01:00
output_type: &OutputType,
map: &serde_json::Map<String, serde_json::value::Value>,
2019-05-26 17:32:30 +02:00
cache_headers: (Header<CacheControl>, Header<Expires>),
vary_header: Header<Vary>,
2017-08-12 14:32:05 +02:00
) -> IronResult<Response> {
2018-02-18 21:44:19 +01:00
match *output_type {
2019-05-26 17:32:30 +02:00
OutputType::Json => Self::output_json(map, cache_headers, vary_header),
_ => Self::output_html(map, cache_headers, vary_header),
}
}
2019-04-03 01:58:51 +02:00
fn ip_lookup(req: &mut Request<'_, '_>) -> IronResult<Response> {
2017-08-12 14:32:05 +02:00
let mime_text = Mime(
TopLevel::Text,
SubLevel::Plain,
vec![(Attr::Charset, Value::Utf8)],
);
2019-05-26 17:32:30 +02:00
let cache_headers = (
Header(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(TTL),
])),
Header(Expires(HttpDate(
time::now() + Duration::seconds(TTL.into()),
))),
);
let vary_header = Header(Vary::Items(vec![
UniCase::from_str("accept-encoding").unwrap(),
UniCase::from_str("accept").unwrap(),
]));
2016-10-26 13:11:19 +02:00
let ip_str = match req.extensions.get::<Router>().unwrap().find("ip") {
None => {
2017-08-12 14:32:05 +02:00
let response = Response::with((
status::BadRequest,
mime_text,
2019-05-26 17:32:30 +02:00
cache_headers,
2017-08-12 14:32:05 +02:00
"Missing IP address",
));
2016-10-26 13:11:19 +02:00
return Ok(response);
}
Some(ip_str) => ip_str,
};
let ip = match IpAddr::from_str(ip_str) {
Err(_) => {
2017-08-12 14:32:05 +02:00
return Ok(Response::with((
status::BadRequest,
mime_text,
2019-05-26 17:32:30 +02:00
cache_headers,
2017-08-12 14:32:05 +02:00
"Invalid IP address",
)));
2016-10-26 13:11:19 +02:00
}
Ok(ip) => ip,
};
let asns = req.extensions.get::<ASNsMiddleware>().unwrap();
let mut map = serde_json::Map::new();
2017-08-12 14:32:05 +02:00
map.insert(
"ip".to_string(),
serde_json::value::Value::String(ip_str.to_string()),
);
2016-10-26 13:11:19 +02:00
let found = match asns.lookup_by_ip(ip) {
None => {
2017-08-12 14:32:05 +02:00
map.insert(
"announced".to_string(),
serde_json::value::Value::Bool(false),
);
2019-05-26 17:32:30 +02:00
return Self::output(&Self::accept_type(req), &map, cache_headers, vary_header);
2016-10-26 13:11:19 +02:00
}
Some(found) => found,
};
2017-08-12 14:32:05 +02:00
map.insert(
"announced".to_string(),
serde_json::value::Value::Bool(true),
);
map.insert(
"first_ip".to_string(),
serde_json::value::Value::String(found.first_ip.to_string()),
);
map.insert(
"last_ip".to_string(),
serde_json::value::Value::String(found.last_ip.to_string()),
);
map.insert(
"as_number".to_string(),
serde_json::value::Value::Number(serde_json::Number::from(found.number)),
);
map.insert(
"as_country_code".to_string(),
serde_json::value::Value::String(found.country.clone()),
);
map.insert(
"as_description".to_string(),
serde_json::value::Value::String(found.description.clone()),
);
2019-05-26 17:32:30 +02:00
Self::output(&Self::accept_type(req), &map, cache_headers, vary_header)
2016-10-26 13:11:19 +02:00
}
2016-10-27 16:37:07 +02:00
pub fn start(asns_arc: Arc<RwLock<Arc<ASNs>>>, listen_addr: &str) {
2016-10-26 13:11:19 +02:00
let router = router!(index: get "/" => Self::index,
ip_lookup: get "/v1/as/ip/:ip" => Self::ip_lookup);
let mut chain = Chain::new(router);
let asns_middleware = ASNsMiddleware::new(asns_arc);
2016-10-26 13:11:19 +02:00
chain.link_before(asns_middleware);
warn!("webservice ready");
Iron::new(chain).http(listen_addr).unwrap();
}
}