Initial commit

This commit is contained in:
Valentin Brandl 2019-07-24 17:54:00 +02:00
commit 6be9b598b2
No known key found for this signature in database
GPG Key ID: 30D341DD34118D7D
8 changed files with 2247 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

1994
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "gitache"
version = "0.1.0"
authors = ["Valentin Brandl <vbrandl@riseup.net>"]
edition = "2018"
[dependencies]
actix-web = "1.0.5"
awc = { version = "0.2.2", features = ["default", "ssl"] }
env_logger = "0.6.2"
futures = "0.1.28"
lazy_static = "1.3.0"
mime_guess = "1.8.7"
serde = "1.0.97"
serde_derive = "1.0.97"
serde_json = "1.0.40"

8
src/data.rs Normal file
View File

@ -0,0 +1,8 @@
#[derive(Deserialize, Debug)]
pub(crate) struct FilePath {
pub(crate) user: String,
pub(crate) repo: String,
pub(crate) commit: String,
pub(crate) file: String,
}

67
src/error.rs Normal file
View File

@ -0,0 +1,67 @@
use actix_web::{HttpResponse, ResponseError};
use std::fmt;
pub(crate) type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub(crate) enum Error {
// HttpClient(reqwest::Error),
HttpClient(awc::error::SendRequestError),
HttpPayload(awc::error::PayloadError),
HttpServer(actix_web::Error),
Io(std::io::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::error::Error::*;
match self {
HttpClient(err) => write!(f, "HttpClient({})", err),
HttpPayload(err) => write!(f, "HttpPayload({})", err),
HttpServer(err) => write!(f, "HttpServer({})", err),
Io(err) => write!(f, "Io({})", err),
}
}
}
impl std::error::Error for Error {}
// impl From<reqwest::Error> for Error {
// fn from(err: reqwest::Error) -> Self {
// Error::HttpClient(err)
// }
// }
impl From<actix_web::Error> for Error {
fn from(err: actix_web::Error) -> Self {
Error::HttpServer(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
}
}
impl From<awc::error::SendRequestError> for Error {
fn from(err: awc::error::SendRequestError) -> Self {
Error::HttpClient(err)
}
}
impl From<awc::error::PayloadError> for Error {
fn from(err: awc::error::PayloadError) -> Self {
Error::HttpPayload(err)
}
}
impl ResponseError for Error {
fn error_response(&self) -> HttpResponse {
HttpResponse::InternalServerError().finish()
}
fn render_response(&self) -> HttpResponse {
self.error_response()
}
}

117
src/main.rs Normal file
View File

@ -0,0 +1,117 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
mod data;
mod error;
mod service;
mod statics;
use crate::{
data::FilePath,
error::Result,
service::{GitHubApiResponse, Github, Service},
};
use actix_web::{
http::header::{self, CacheControl, CacheDirective, Expires, LOCATION},
middleware, web, App, Error as AError, HttpResponse, HttpServer,
};
use awc::{http::StatusCode, Client};
use futures::Future;
use std::time::{Duration, SystemTime};
fn proxy_file<T: Service>(
client: web::Data<Client>,
data: web::Path<FilePath>,
) -> Box<dyn Future<Item = HttpResponse, Error = AError>> {
Box::new(
client
.get(&T::raw_url(
&data.user,
&data.repo,
&data.commit,
&data.file,
))
.header(header::USER_AGENT, statics::USER_AGENT.as_str())
.send()
.from_err()
.and_then(move |response| match response.status() {
StatusCode::OK => {
let mime = mime_guess::guess_mime_type(&data.file);
let expiration = SystemTime::now() + Duration::from_secs(2_592_000_000);
Ok(HttpResponse::Ok()
.content_type(mime.to_string().as_str())
.set(Expires(expiration.into()))
.set(CacheControl(vec![
CacheDirective::MaxAge(2_592_000_000),
CacheDirective::Public,
]))
.streaming(response))
}
code => Ok(HttpResponse::build(code).finish()),
}),
)
}
fn redirect<T: Service>(
client: web::Data<Client>,
data: web::Path<FilePath>,
) -> Box<dyn Future<Item = HttpResponse, Error = AError>> {
Box::new(
client
.get(&T::api_url(&data))
.header(header::USER_AGENT, statics::USER_AGENT.as_str())
.send()
.from_err()
.and_then(move |mut response| match response.status() {
StatusCode::OK => Box::new(
response
.json::<GitHubApiResponse>()
.map(move |resp| {
HttpResponse::SeeOther()
.header(
LOCATION,
T::redirect_url(&data.user, &data.repo, &resp.sha, &data.file)
.as_str(),
)
.finish()
})
.from_err(),
)
as Box<dyn Future<Item = HttpResponse, Error = AError>>,
code => Box::new(futures::future::ok(HttpResponse::build(code).finish()))
as Box<dyn Future<Item = HttpResponse, Error = AError>>,
}),
)
}
fn handle_request<T: Service>(
client: web::Data<Client>,
data: web::Path<FilePath>,
) -> Box<dyn Future<Item = HttpResponse, Error = AError>> {
if data.commit.len() == 40 {
proxy_file::<T>(client, data)
} else {
redirect::<T>(client, data)
}
}
fn main() -> Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=trace");
env_logger::init();
Ok(HttpServer::new(move || {
App::new()
.data(Client::new())
.wrap(middleware::Logger::default())
.route(
"/github/{user}/{repo}/{commit}/{file}",
web::get().to_async(handle_request::<Github>),
)
// .default_service(web::resource("").route(web::get().to_async(p404)))
})
// .workers(OPT.workers)
.bind("127.0.0.1:8080")?
.run()?)
}

38
src/service.rs Normal file
View File

@ -0,0 +1,38 @@
use crate::data::FilePath;
// use actix_web::Error;
// use awc::Client;
// use futures::Future;
// use std::borrow::Cow;
#[derive(Deserialize)]
pub(crate) struct GitHubApiResponse {
pub(crate) sha: String,
}
pub(crate) trait Service {
fn raw_url(user: &str, repo: &str, commit: &str, file: &str) -> String;
fn api_url(path: &FilePath) -> String;
fn redirect_url(user: &str, repo: &str, commit: &str, file: &str) -> String;
}
pub(crate) struct Github;
impl Service for Github {
fn raw_url(user: &str, repo: &str, commit: &str, file: &str) -> String {
format!(
"https://raw.githubusercontent.com/{}/{}/{}/{}",
user, repo, commit, file
)
}
fn api_url(path: &FilePath) -> String {
format!(
"https://api.github.com/repos/{}/{}/commits/{}",
path.user, path.repo, path.commit
)
}
fn redirect_url(user: &str, repo: &str, commit: &str, file: &str) -> String {
format!("/github/{}/{}/{}/{}", user, repo, commit, file)
}
}

4
src/statics.rs Normal file
View File

@ -0,0 +1,4 @@
const VERSION: &str = env!("CARGO_PKG_VERSION");
lazy_static! {
pub(crate) static ref USER_AGENT: String = format!("gitache/{}", VERSION);
}