|
|
@@ -0,0 +1,153 @@ |
|
|
|
use futures::{Future, IntoFuture, Stream}; |
|
|
|
|
|
|
|
#[derive(serde_derive::Deserialize)] |
|
|
|
struct CrateInfoResponse { |
|
|
|
#[serde(rename = "crate")] |
|
|
|
crate_: CrateInfo, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(serde_derive::Deserialize)] |
|
|
|
struct CrateInfo { |
|
|
|
repository: Option<String>, |
|
|
|
} |
|
|
|
|
|
|
|
fn handle_query( |
|
|
|
client: hyper::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>>, |
|
|
|
req_path: &str, |
|
|
|
) -> impl Future<Item = hyper::Response<hyper::Body>, Error = hyper::Error> { |
|
|
|
let path = format!( |
|
|
|
"/api/v1/crates/{}", |
|
|
|
percent_encoding::utf8_percent_encode(req_path, percent_encoding::PATH_SEGMENT_ENCODE_SET) |
|
|
|
); |
|
|
|
let path_ref: &str = &path; |
|
|
|
let req_path = req_path.to_owned(); |
|
|
|
hyper::Uri::builder() |
|
|
|
.scheme("https") |
|
|
|
.authority("crates.io") |
|
|
|
.path_and_query(path_ref) |
|
|
|
.build() |
|
|
|
.map_err(|err| format!("Failed to construct API URI: {:?}", err)) |
|
|
|
.into_future() |
|
|
|
.and_then(move |uri| { |
|
|
|
let mut req = hyper::Request::new("".into()); |
|
|
|
*req.uri_mut() = uri; |
|
|
|
req.headers_mut().insert( |
|
|
|
hyper::header::USER_AGENT, |
|
|
|
hyper::header::HeaderValue::from_static("crepo.top"), |
|
|
|
); |
|
|
|
|
|
|
|
client |
|
|
|
.request(req) |
|
|
|
.map_err(|err| format!("Failed to make API request: {:?}", err)) |
|
|
|
.and_then(|res| { |
|
|
|
if res.status() == hyper::StatusCode::OK { |
|
|
|
Ok(Some(res.into_body())) |
|
|
|
} else if res.status() == hyper::StatusCode::NOT_FOUND { |
|
|
|
Ok(None) |
|
|
|
} else { |
|
|
|
Err(format!( |
|
|
|
"Unexpected response code from API: {}", |
|
|
|
res.status() |
|
|
|
)) |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
.and_then(|body| match body { |
|
|
|
Some(body) => futures::future::Either::A( |
|
|
|
body.concat2() |
|
|
|
.map_err(|err| format!("Failed to read API response: {:?}", err)) |
|
|
|
.and_then(|body| { |
|
|
|
serde_json::from_slice(&body) |
|
|
|
.map_err(|err| format!("Failed to parse API response: {:?}", err)) |
|
|
|
}) |
|
|
|
.map(Some), |
|
|
|
), |
|
|
|
None => futures::future::Either::B(futures::future::ok(None)), |
|
|
|
}) |
|
|
|
.and_then(move |body: Option<CrateInfoResponse>| match body { |
|
|
|
Some(body) => { |
|
|
|
let (text, url) = match body.crate_.repository { |
|
|
|
Some(url) => (format!("Redirecting to {}", url), url), |
|
|
|
None => { |
|
|
|
let url = format!("https://crates.io/crates/{}", req_path); |
|
|
|
(format!("No repository found, redirecting to {}", url), url) |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
url.parse() |
|
|
|
.map(|location| { |
|
|
|
let mut resp = hyper::Response::new(text.into()); |
|
|
|
*resp.status_mut() = hyper::StatusCode::FOUND; |
|
|
|
resp.headers_mut().insert(hyper::header::LOCATION, location); |
|
|
|
|
|
|
|
resp |
|
|
|
}) |
|
|
|
.map_err(|err| format!("Failed to set Location header: {:?}", err)) |
|
|
|
} |
|
|
|
None => { |
|
|
|
let mut resp = hyper::Response::new("Crate not found.".into()); |
|
|
|
*resp.status_mut() = hyper::StatusCode::NOT_FOUND; |
|
|
|
resp.headers_mut().insert( |
|
|
|
hyper::header::CONTENT_TYPE, |
|
|
|
hyper::header::HeaderValue::from_static("text/plain"), |
|
|
|
); |
|
|
|
|
|
|
|
Ok(resp) |
|
|
|
} |
|
|
|
}) |
|
|
|
.or_else(|err| { |
|
|
|
eprintln!("Error: {:?}", err); |
|
|
|
|
|
|
|
let mut resp = hyper::Response::new("Internal Server Error".into()); |
|
|
|
*resp.status_mut() = hyper::StatusCode::INTERNAL_SERVER_ERROR; |
|
|
|
|
|
|
|
Ok(resp) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
fn main() { |
|
|
|
let port = 2727; |
|
|
|
|
|
|
|
let http_client = hyper::Client::builder() |
|
|
|
.build(hyper_tls::HttpsConnector::new(4).expect("HttpsConnector initialization failed")); |
|
|
|
|
|
|
|
tokio::run( |
|
|
|
hyper::Server::bind(&std::net::SocketAddr::new( |
|
|
|
std::net::Ipv6Addr::UNSPECIFIED.into(), |
|
|
|
port, |
|
|
|
)) |
|
|
|
.serve(move || { |
|
|
|
hyper::service::service_fn({ |
|
|
|
let http_client = http_client.clone(); |
|
|
|
move |req| { |
|
|
|
let mut path = req.uri().path(); |
|
|
|
println!("{}", path); |
|
|
|
if path.len() > 0 && path.starts_with("/") { |
|
|
|
path = &path[1..]; |
|
|
|
} |
|
|
|
let path = path; |
|
|
|
|
|
|
|
if path.len() == 0 { |
|
|
|
futures::future::Either::A(futures::future::ok(hyper::Response::new( |
|
|
|
hyper::Body::from("Usage: https://crepo.top/{crate}"), |
|
|
|
))) |
|
|
|
} else { |
|
|
|
if path.contains("/") || path.contains(".") { |
|
|
|
// quick check for invalid crate names |
|
|
|
let mut resp = hyper::Response::new("Invalid crate name.".into()); |
|
|
|
*resp.status_mut() = hyper::StatusCode::NOT_FOUND; |
|
|
|
resp.headers_mut().insert( |
|
|
|
hyper::header::CONTENT_TYPE, |
|
|
|
hyper::header::HeaderValue::from_static("text/plain"), |
|
|
|
); |
|
|
|
futures::future::Either::A(futures::future::ok(resp)) |
|
|
|
} else { |
|
|
|
futures::future::Either::B(handle_query(http_client.clone(), path)) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
.map_err(|err| panic!("Server execution failed: {:?}", err)), |
|
|
|
); |
|
|
|
} |