mirror of
https://github.com/jedisct1/iptoasn-webservice
synced 2025-04-11 22:55:50 +02:00
Remove iron
This commit is contained in:
parent
50a5134360
commit
02453cb235
16
Cargo.toml
16
Cargo.toml
@ -13,17 +13,17 @@ edition = "2021"
|
||||
clap = { version = "4.5", features = ["derive", "cargo", "wrap_help"] }
|
||||
flate2 = "1.1"
|
||||
horrorshow = "0.8"
|
||||
reqwest = { version = "0.12", features = ["blocking"] }
|
||||
iron = "0.6"
|
||||
reqwest = { version = "0.12", features = ["rustls-tls"] }
|
||||
hyper = { version = "1.6", features = ["server", "http1"] }
|
||||
hyper-util = { version = "0.1", features = ["tokio"] }
|
||||
http-body-util = "0.1"
|
||||
tokio = { version = "1.44", features = ["full"] }
|
||||
log = "0.4"
|
||||
router = "0.6"
|
||||
env_logger = "0.11"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
# Use the same unicase version that iron uses
|
||||
unicase = "1.4"
|
||||
# Use time 0.3 with macros feature for datetime literals
|
||||
time = { version = "0.3", features = ["macros", "formatting"] }
|
||||
# Add the old time crate for compatibility with iron
|
||||
time01 = { version = "0.1", package = "time" }
|
||||
http = "1.3"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
54
src/asns.rs
54
src/asns.rs
@ -1,5 +1,6 @@
|
||||
use flate2::read::GzDecoder;
|
||||
use reqwest::blocking::Client;
|
||||
use hyper::body::Bytes;
|
||||
use reqwest::Client;
|
||||
use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::prelude::*;
|
||||
@ -53,20 +54,43 @@ pub struct Asns {
|
||||
}
|
||||
|
||||
impl Asns {
|
||||
pub fn new(url: &str) -> Result<Self, &'static str> {
|
||||
info!("Loading the database");
|
||||
let client = Client::new();
|
||||
let Ok(res) = client.get(url).send() else {
|
||||
error!("Unable to load the database");
|
||||
return Err("Unable to load the database");
|
||||
};
|
||||
if !res.status().is_success() {
|
||||
error!("Unable to load the database");
|
||||
return Err("Unable to load the database");
|
||||
}
|
||||
let Ok(bytes) = res.bytes() else {
|
||||
error!("Unable to read response body");
|
||||
return Err("Unable to read response body");
|
||||
pub async fn new(url: &str) -> Result<Self, &'static str> {
|
||||
info!("Loading the database from {}", url);
|
||||
|
||||
let bytes = if url.starts_with("file://") {
|
||||
// Handle local file URLs
|
||||
let path = url.strip_prefix("file://").unwrap_or(url);
|
||||
match tokio::fs::read(path).await {
|
||||
Ok(content) => Bytes::from(content),
|
||||
Err(e) => {
|
||||
error!("Unable to read local file: {}", e);
|
||||
return Err("Unable to read local file");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle HTTP/HTTPS URLs
|
||||
let client = Client::builder()
|
||||
.user_agent("iptoasn-webservice/0.2.5")
|
||||
.build()
|
||||
.map_err(|_| {
|
||||
error!("Failed to create HTTP client");
|
||||
"Failed to create HTTP client"
|
||||
})?;
|
||||
|
||||
let res = client.get(url).send().await.map_err(|e| {
|
||||
error!("Unable to load the database: {}", e);
|
||||
"Unable to load the database"
|
||||
})?;
|
||||
|
||||
if !res.status().is_success() {
|
||||
error!("Unable to load the database, status: {}", res.status());
|
||||
return Err("Unable to load the database");
|
||||
}
|
||||
|
||||
res.bytes().await.map_err(|e| {
|
||||
error!("Unable to read response body: {}", e);
|
||||
"Unable to read response body"
|
||||
})?
|
||||
};
|
||||
let mut data = String::new();
|
||||
if GzDecoder::new(bytes.as_ref())
|
||||
|
114
src/main.rs
114
src/main.rs
@ -2,8 +2,6 @@
|
||||
extern crate horrorshow;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate router;
|
||||
|
||||
mod asns;
|
||||
mod webservice;
|
||||
@ -12,58 +10,84 @@ use crate::asns::Asns;
|
||||
use crate::webservice::WebService;
|
||||
use clap::{Arg, Command};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn get_asns(db_url: &str) -> Result<Asns, &'static str> {
|
||||
info!("Retrieving ASNs");
|
||||
let asns = Asns::new(db_url);
|
||||
info!("ASNs loaded");
|
||||
asns
|
||||
}
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
fn update_asns(asns_arc: &Arc<RwLock<Arc<Asns>>>, db_url: &str) {
|
||||
let asns = match get_asns(db_url) {
|
||||
let matches = Command::new("iptoasn-webservice")
|
||||
.version("0.2.5")
|
||||
.author("Frank Denis <github@pureftpd.org>")
|
||||
.about("IP to ASN webservice")
|
||||
.arg(
|
||||
Arg::new("listen_addr")
|
||||
.short('l')
|
||||
.long("listen")
|
||||
.value_name("listen_addr")
|
||||
.help("Address:port to listen to")
|
||||
.default_value("127.0.0.1:53661"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("db_url")
|
||||
.short('u')
|
||||
.long("url")
|
||||
.value_name("db_url")
|
||||
.help("URL of the database")
|
||||
.default_value("file:///Users/j/src/iptoasn-webservice/test_data.tsv.gz"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("refresh_delay")
|
||||
.short('r')
|
||||
.long("refresh")
|
||||
.value_name("refresh_delay")
|
||||
.help("Database refresh delay (minutes)")
|
||||
.default_value("60"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let db_url = matches.get_one::<String>("db_url").unwrap();
|
||||
let listen_addr = matches.get_one::<String>("listen_addr").unwrap();
|
||||
let refresh_delay = matches.get_one::<String>("refresh_delay").unwrap();
|
||||
let refresh_delay = refresh_delay.parse::<u64>().unwrap();
|
||||
|
||||
let asns = match get_asns(db_url).await {
|
||||
Ok(asns) => asns,
|
||||
Err(e) => {
|
||||
warn!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
*asns_arc.write().unwrap() = Arc::new(asns);
|
||||
let asns_arc = Arc::new(RwLock::new(Arc::new(asns)));
|
||||
|
||||
let asns_arc_t = asns_arc.clone();
|
||||
let db_url_t = db_url.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(refresh_delay * 60)).await;
|
||||
update_asns(&asns_arc_t, &db_url_t).await;
|
||||
}
|
||||
});
|
||||
|
||||
WebService::start(asns_arc, listen_addr).await;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = Command::new(env!("CARGO_PKG_NAME"))
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author(env!("CARGO_PKG_AUTHORS"))
|
||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||
.arg(
|
||||
Arg::new("listen_addr")
|
||||
.short('l')
|
||||
.long("listen")
|
||||
.value_name("ip:port")
|
||||
.help("Webservice IP and port")
|
||||
.default_value("0.0.0.0:53661"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("db_url")
|
||||
.short('u')
|
||||
.long("dburl")
|
||||
.value_name("url")
|
||||
.help("URL of the gzipped database")
|
||||
.default_value("https://iptoasn.com/data/ip2asn-combined.tsv.gz"),
|
||||
)
|
||||
.get_matches();
|
||||
let db_url = matches.get_one::<String>("db_url").unwrap().to_owned();
|
||||
let listen_addr = matches.get_one::<String>("listen_addr").unwrap().as_str();
|
||||
let asns = get_asns(&db_url).expect("Unable to load the initial database");
|
||||
let asns_arc = Arc::new(RwLock::new(Arc::new(asns)));
|
||||
let asns_arc_copy = asns_arc.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_secs(3600));
|
||||
update_asns(&asns_arc_copy, &db_url);
|
||||
});
|
||||
info!("Starting the webservice");
|
||||
WebService::start(asns_arc, listen_addr);
|
||||
async fn get_asns(db_url: &str) -> Result<Asns, &'static str> {
|
||||
info!("Retrieving ASNs");
|
||||
let asns = Asns::new(db_url).await?;
|
||||
info!("ASNs loaded");
|
||||
Ok(asns)
|
||||
}
|
||||
|
||||
async fn update_asns(asns_arc: &Arc<RwLock<Arc<Asns>>>, db_url: &str) {
|
||||
let asns = match get_asns(db_url).await {
|
||||
Ok(asns) => asns,
|
||||
Err(e) => {
|
||||
warn!("{e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let asns_arc_new = Arc::new(asns);
|
||||
let mut asns_arc_w = asns_arc.write().unwrap();
|
||||
*asns_arc_w = asns_arc_new;
|
||||
}
|
||||
|
@ -1,123 +1,121 @@
|
||||
use crate::asns::Asns;
|
||||
use horrorshow::prelude::*;
|
||||
use iron::headers::{Accept, CacheControl, CacheDirective, Expires, HttpDate, Vary};
|
||||
use iron::mime::*;
|
||||
use iron::modifiers::Header;
|
||||
use iron::prelude::*;
|
||||
use iron::status;
|
||||
use iron::{typemap, BeforeMiddleware};
|
||||
use router::Router;
|
||||
|
||||
use std::net::IpAddr;
|
||||
use http::header::{ACCEPT, CACHE_CONTROL, CONTENT_TYPE, EXPIRES, VARY};
|
||||
use http::{HeaderMap, HeaderValue, Method, Request, Response, StatusCode};
|
||||
use http_body_util::Full;
|
||||
use hyper::body::Bytes;
|
||||
use hyper::server::conn::http1;
|
||||
use hyper::service::service_fn;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
// Use the old time crate for iron compatibility
|
||||
extern crate time01 as time_old;
|
||||
// Import unicase for Vary headers
|
||||
use unicase::UniCase;
|
||||
use time::macros::format_description;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const TTL: u32 = 86_400;
|
||||
|
||||
struct AsnsMiddleware {
|
||||
asns_arc: Arc<RwLock<Arc<Asns>>>,
|
||||
}
|
||||
|
||||
impl typemap::Key for AsnsMiddleware {
|
||||
type Value = Arc<Asns>;
|
||||
}
|
||||
|
||||
impl AsnsMiddleware {
|
||||
fn new(asns_arc: Arc<RwLock<Arc<Asns>>>) -> Self {
|
||||
Self { asns_arc }
|
||||
}
|
||||
}
|
||||
|
||||
impl BeforeMiddleware for AsnsMiddleware {
|
||||
fn before(&self, req: &mut Request<'_, '_>) -> IronResult<()> {
|
||||
req.extensions
|
||||
.insert::<AsnsMiddleware>(self.asns_arc.read().unwrap().clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum OutputType {
|
||||
Json,
|
||||
Html,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct IpLookupResponse {
|
||||
ip: String,
|
||||
announced: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
first_ip: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
last_ip: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
as_number: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
as_country_code: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
as_description: Option<String>,
|
||||
}
|
||||
|
||||
pub struct WebService;
|
||||
|
||||
impl WebService {
|
||||
fn index(_: &mut Request<'_, '_>) -> IronResult<Response> {
|
||||
Ok(Response::with((
|
||||
status::Ok,
|
||||
Mime(
|
||||
TopLevel::Text,
|
||||
SubLevel::Plain,
|
||||
vec![(Attr::Charset, Value::Utf8)],
|
||||
),
|
||||
Header(CacheControl(vec![
|
||||
CacheDirective::Public,
|
||||
CacheDirective::MaxAge(TTL),
|
||||
])),
|
||||
Header(Expires(HttpDate(
|
||||
time_old::now() + time_old::Duration::seconds(TTL.into()),
|
||||
))),
|
||||
"See https://iptoasn.com",
|
||||
)))
|
||||
async fn handle_request(
|
||||
req: Request<hyper::body::Incoming>,
|
||||
asns_arc: Arc<RwLock<Arc<Asns>>>,
|
||||
) -> Result<Response<Full<Bytes>>, Infallible> {
|
||||
let method = req.method();
|
||||
let uri = req.uri().path();
|
||||
|
||||
match (method, uri) {
|
||||
(&Method::GET, "/") => Ok(Self::index()),
|
||||
(&Method::GET, path) if path.starts_with("/v1/as/ip/") => {
|
||||
let ip_s = path.strip_prefix("/v1/as/ip/").unwrap_or("");
|
||||
Self::ip_lookup(ip_s, req.headers(), asns_arc)
|
||||
}
|
||||
_ => {
|
||||
let mut response = Response::new(Full::new(Bytes::from("Not Found")));
|
||||
*response.status_mut() = StatusCode::NOT_FOUND;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
fn index() -> Response<Full<Bytes>> {
|
||||
let mut response = Response::new(Full::new(Bytes::from("iptoasn-webservice\n")));
|
||||
response.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("text/plain; charset=utf-8"),
|
||||
);
|
||||
*response.status_mut() = StatusCode::OK;
|
||||
response
|
||||
}
|
||||
|
||||
fn accept_type(headers: &HeaderMap) -> OutputType {
|
||||
if let Some(accept) = headers.get(ACCEPT) {
|
||||
if let Ok(accept_str) = accept.to_str() {
|
||||
if accept_str.contains("application/json") {
|
||||
return OutputType::Json;
|
||||
}
|
||||
}
|
||||
}
|
||||
output_type
|
||||
OutputType::Html
|
||||
}
|
||||
|
||||
fn output_json(
|
||||
map: &serde_json::Map<String, serde_json::value::Value>,
|
||||
cache_headers: (Header<CacheControl>, Header<Expires>),
|
||||
vary_header: Header<Vary>,
|
||||
) -> Response {
|
||||
let json = serde_json::to_string(&map).unwrap();
|
||||
let mime_json = Mime(
|
||||
TopLevel::Application,
|
||||
SubLevel::Json,
|
||||
vec![(Attr::Charset, Value::Utf8)],
|
||||
fn cache_headers(headers: &mut HeaderMap) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let expires = now + time::Duration::seconds(TTL as i64);
|
||||
|
||||
let format = format_description!(
|
||||
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
|
||||
);
|
||||
Response::with((
|
||||
status::Ok,
|
||||
mime_json,
|
||||
cache_headers.0,
|
||||
cache_headers.1,
|
||||
vary_header,
|
||||
json,
|
||||
))
|
||||
let expires_str = expires.format(&format).unwrap();
|
||||
|
||||
headers.insert(
|
||||
CACHE_CONTROL,
|
||||
HeaderValue::from_str(&format!("max-age={}", TTL)).unwrap(),
|
||||
);
|
||||
headers.insert(EXPIRES, HeaderValue::from_str(&expires_str).unwrap());
|
||||
headers.insert(VARY, HeaderValue::from_static("Accept"));
|
||||
}
|
||||
|
||||
fn output_html(
|
||||
map: &serde_json::Map<String, serde_json::value::Value>,
|
||||
cache_headers: (Header<CacheControl>, Header<Expires>),
|
||||
vary_header: Header<Vary>,
|
||||
) -> Response {
|
||||
let mime_html = Mime(
|
||||
TopLevel::Text,
|
||||
SubLevel::Html,
|
||||
vec![(Attr::Charset, Value::Utf8)],
|
||||
fn output_json(response: &IpLookupResponse) -> Response<Full<Bytes>> {
|
||||
let json = serde_json::to_string(&response).unwrap();
|
||||
let mut response = Response::new(Full::new(Bytes::from(json)));
|
||||
|
||||
response.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/json; charset=utf-8"),
|
||||
);
|
||||
Self::cache_headers(response.headers_mut());
|
||||
*response.status_mut() = StatusCode::OK;
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn output_html(response: &IpLookupResponse) -> Response<Full<Bytes>> {
|
||||
let html = html! {
|
||||
head {
|
||||
title : "iptoasn lookup";
|
||||
@ -127,35 +125,35 @@ impl WebService {
|
||||
}
|
||||
body(class="container-fluid") {
|
||||
header {
|
||||
h1 : format_args!("Information for IP address: {}", map.get("ip").unwrap().as_str().unwrap());
|
||||
h1 : format_args!("Information for IP address: {}", response.ip);
|
||||
}
|
||||
table {
|
||||
tr {
|
||||
th : "Announced";
|
||||
td {
|
||||
@ if map.get("announced").unwrap().as_bool().unwrap() {
|
||||
@ if response.announced {
|
||||
: "Yes";
|
||||
} else {
|
||||
: "No";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ if map.get("announced").unwrap().as_bool().unwrap() {
|
||||
@ if response.announced {
|
||||
tr {
|
||||
th : "AS Number";
|
||||
td : format_args!("AS{}", map.get("as_number").unwrap().as_u64().unwrap());
|
||||
td : format_args!("AS{}", response.as_number.unwrap());
|
||||
}
|
||||
tr {
|
||||
th : "AS Range";
|
||||
td : format_args!("{} - {}", map.get("first_ip").unwrap().as_str().unwrap(), map.get("last_ip").unwrap().as_str().unwrap());
|
||||
td : format_args!("{} - {}", response.first_ip.as_ref().unwrap(), response.last_ip.as_ref().unwrap());
|
||||
}
|
||||
tr {
|
||||
th : "AS Country Code";
|
||||
td : map.get("as_country_code").unwrap().as_str().unwrap();
|
||||
td : response.as_country_code.as_ref().unwrap();
|
||||
}
|
||||
tr {
|
||||
th : "AS Description";
|
||||
td : map.get("as_description").unwrap().as_str().unwrap();
|
||||
td : response.as_description.as_ref().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,130 +167,98 @@ impl WebService {
|
||||
}.into_string()
|
||||
.unwrap();
|
||||
let html = format!("<!DOCTYPE html>\n<html>{html}</html>");
|
||||
Response::with((
|
||||
status::Ok,
|
||||
mime_html,
|
||||
cache_headers.0,
|
||||
cache_headers.1,
|
||||
vary_header,
|
||||
html,
|
||||
))
|
||||
|
||||
let mut response = Response::new(Full::new(Bytes::from(html)));
|
||||
response.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("text/html; charset=utf-8"),
|
||||
);
|
||||
Self::cache_headers(response.headers_mut());
|
||||
*response.status_mut() = StatusCode::OK;
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn output(
|
||||
output_type: &OutputType,
|
||||
map: &serde_json::Map<String, serde_json::value::Value>,
|
||||
cache_headers: (Header<CacheControl>, Header<Expires>),
|
||||
vary_header: Header<Vary>,
|
||||
) -> Response {
|
||||
fn output(output_type: &OutputType, response: &IpLookupResponse) -> Response<Full<Bytes>> {
|
||||
match *output_type {
|
||||
OutputType::Json => Self::output_json(map, cache_headers, vary_header),
|
||||
OutputType::Html => Self::output_html(map, cache_headers, vary_header),
|
||||
OutputType::Json => Self::output_json(response),
|
||||
OutputType::Html => Self::output_html(response),
|
||||
}
|
||||
}
|
||||
|
||||
fn ip_lookup(req: &mut Request<'_, '_>) -> IronResult<Response> {
|
||||
let mime_text = Mime(
|
||||
TopLevel::Text,
|
||||
SubLevel::Plain,
|
||||
vec![(Attr::Charset, Value::Utf8)],
|
||||
);
|
||||
let cache_headers = (
|
||||
Header(CacheControl(vec![
|
||||
CacheDirective::Public,
|
||||
CacheDirective::MaxAge(TTL),
|
||||
])),
|
||||
Header(Expires(HttpDate(
|
||||
time_old::now() + time_old::Duration::seconds(TTL.into()),
|
||||
))),
|
||||
);
|
||||
let vary_header = Header(Vary::Items(vec![
|
||||
UniCase::from_str("accept-encoding").unwrap(),
|
||||
UniCase::from_str("accept").unwrap(),
|
||||
]));
|
||||
let ip_str = match req.extensions.get::<Router>().unwrap().find("ip") {
|
||||
None => {
|
||||
let response = Response::with((
|
||||
status::BadRequest,
|
||||
mime_text,
|
||||
cache_headers,
|
||||
"Missing IP address",
|
||||
));
|
||||
return Ok(response);
|
||||
}
|
||||
Some(ip_str) => ip_str,
|
||||
};
|
||||
let ip = match IpAddr::from_str(ip_str) {
|
||||
fn ip_lookup(
|
||||
ip_s: &str,
|
||||
headers: &HeaderMap,
|
||||
asns_arc: Arc<RwLock<Arc<Asns>>>,
|
||||
) -> Result<Response<Full<Bytes>>, Infallible> {
|
||||
let ip = match std::net::IpAddr::from_str(ip_s) {
|
||||
Err(_) => {
|
||||
return Ok(Response::with((
|
||||
status::BadRequest,
|
||||
mime_text,
|
||||
cache_headers,
|
||||
"Invalid IP address",
|
||||
)));
|
||||
let response = IpLookupResponse {
|
||||
ip: ip_s.to_owned(),
|
||||
announced: false,
|
||||
first_ip: None,
|
||||
last_ip: None,
|
||||
as_number: None,
|
||||
as_country_code: None,
|
||||
as_description: None,
|
||||
};
|
||||
return Ok(Self::output(&Self::accept_type(headers), &response));
|
||||
}
|
||||
Ok(ip) => ip,
|
||||
};
|
||||
let asns = req.extensions.get::<AsnsMiddleware>().unwrap();
|
||||
let mut map = serde_json::Map::new();
|
||||
map.insert(
|
||||
"ip".to_string(),
|
||||
serde_json::value::Value::String(ip_str.to_string()),
|
||||
);
|
||||
|
||||
let asns = asns_arc.read().unwrap().clone();
|
||||
|
||||
let found = match asns.lookup_by_ip(ip) {
|
||||
None => {
|
||||
map.insert(
|
||||
"announced".to_string(),
|
||||
serde_json::value::Value::Bool(false),
|
||||
);
|
||||
return Ok(Self::output(
|
||||
&Self::accept_type(req),
|
||||
&map,
|
||||
cache_headers,
|
||||
vary_header,
|
||||
));
|
||||
let response = IpLookupResponse {
|
||||
ip: ip.to_string(),
|
||||
announced: false,
|
||||
first_ip: None,
|
||||
last_ip: None,
|
||||
as_number: None,
|
||||
as_country_code: None,
|
||||
as_description: None,
|
||||
};
|
||||
return Ok(Self::output(&Self::accept_type(headers), &response));
|
||||
}
|
||||
Some(found) => found,
|
||||
};
|
||||
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()),
|
||||
);
|
||||
Ok(Self::output(
|
||||
&Self::accept_type(req),
|
||||
&map,
|
||||
cache_headers,
|
||||
vary_header,
|
||||
))
|
||||
|
||||
let response = IpLookupResponse {
|
||||
ip: ip.to_string(),
|
||||
announced: true,
|
||||
first_ip: Some(found.first_ip.to_string()),
|
||||
last_ip: Some(found.last_ip.to_string()),
|
||||
as_number: Some(found.number),
|
||||
as_country_code: Some(found.country.clone()),
|
||||
as_description: Some(found.description.clone()),
|
||||
};
|
||||
|
||||
Ok(Self::output(&Self::accept_type(headers), &response))
|
||||
}
|
||||
|
||||
pub fn start(asns_arc: Arc<RwLock<Arc<Asns>>>, listen_addr: &str) {
|
||||
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);
|
||||
chain.link_before(asns_middleware);
|
||||
warn!("webservice ready");
|
||||
Iron::new(chain).http(listen_addr).unwrap();
|
||||
pub async fn start(asns_arc: Arc<RwLock<Arc<Asns>>>, listen_addr: &str) {
|
||||
let addr: SocketAddr = listen_addr.parse().expect("Could not parse socket address");
|
||||
let listener = TcpListener::bind(addr).await.unwrap();
|
||||
|
||||
log::warn!("webservice ready");
|
||||
|
||||
loop {
|
||||
let (tcp, _) = listener.accept().await.unwrap();
|
||||
let io = TokioIo::new(tcp);
|
||||
let asns_arc = asns_arc.clone();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let service = service_fn(move |req| {
|
||||
let asns_arc = asns_arc.clone();
|
||||
async move { Self::handle_request(req, asns_arc).await }
|
||||
});
|
||||
|
||||
if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
|
||||
log::error!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user