2021-03-09 15:49:06 +13:00
|
|
|
// Global specifiers
|
|
|
|
#![forbid(unsafe_code)]
|
2021-12-27 18:18:20 +13:00
|
|
|
#![allow(clippy::cmp_owned)]
|
2021-03-09 15:49:06 +13:00
|
|
|
|
2020-10-26 09:25:59 +13:00
|
|
|
// Reference local files
|
2022-11-10 05:16:51 +13:00
|
|
|
mod duplicates;
|
2020-10-26 09:25:59 +13:00
|
|
|
mod post;
|
2021-01-01 12:54:13 +13:00
|
|
|
mod search;
|
2021-01-06 15:04:49 +13:00
|
|
|
mod settings;
|
2020-10-26 09:25:59 +13:00
|
|
|
mod subreddit;
|
2020-10-26 16:57:19 +13:00
|
|
|
mod user;
|
2020-11-26 10:53:30 +13:00
|
|
|
mod utils;
|
2020-10-26 09:25:59 +13:00
|
|
|
|
2021-02-14 12:02:38 +13:00
|
|
|
// Import Crates
|
2022-05-21 14:20:44 +12:00
|
|
|
use clap::{Arg, Command};
|
2021-02-14 12:02:38 +13:00
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
use futures_lite::FutureExt;
|
2021-03-18 12:32:28 +13:00
|
|
|
use hyper::{header::HeaderValue, Body, Request, Response};
|
2021-02-10 06:38:52 +13:00
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
mod client;
|
2022-11-05 21:29:04 +13:00
|
|
|
use client::{canonical_path, proxy};
|
2021-03-18 11:30:33 +13:00
|
|
|
use server::RequestExt;
|
2022-05-21 13:41:31 +12:00
|
|
|
use utils::{error, redirect, ThemeAssets};
|
2021-03-18 11:30:33 +13:00
|
|
|
|
|
|
|
mod server;
|
2021-02-10 06:38:52 +13:00
|
|
|
|
2020-10-26 09:25:59 +13:00
|
|
|
// Create Services
|
2021-02-01 23:10:53 +13:00
|
|
|
|
|
|
|
// Required for the manifest to be valid
|
2021-03-18 11:30:33 +13:00
|
|
|
async fn pwa_logo() -> Result<Response<Body>, String> {
|
|
|
|
Ok(
|
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header("content-type", "image/png")
|
|
|
|
.body(include_bytes!("../static/logo.png").as_ref().into())
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
2021-02-01 23:10:53 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
// Required for iOS App Icons
|
2021-03-18 11:30:33 +13:00
|
|
|
async fn iphone_logo() -> Result<Response<Body>, String> {
|
2021-02-10 06:38:52 +13:00
|
|
|
Ok(
|
2021-03-18 11:30:33 +13:00
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header("content-type", "image/png")
|
|
|
|
.body(include_bytes!("../static/apple-touch-icon.png").as_ref().into())
|
|
|
|
.unwrap_or_default(),
|
2021-02-10 06:38:52 +13:00
|
|
|
)
|
2021-02-01 23:10:53 +13:00
|
|
|
}
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
async fn favicon() -> Result<Response<Body>, String> {
|
2021-02-10 06:38:52 +13:00
|
|
|
Ok(
|
2021-03-18 11:30:33 +13:00
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header("content-type", "image/vnd.microsoft.icon")
|
2021-02-10 06:38:52 +13:00
|
|
|
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
|
2021-03-18 11:30:33 +13:00
|
|
|
.body(include_bytes!("../static/favicon.ico").as_ref().into())
|
|
|
|
.unwrap_or_default(),
|
2021-02-10 06:38:52 +13:00
|
|
|
)
|
2020-10-26 09:25:59 +13:00
|
|
|
}
|
|
|
|
|
2021-05-20 11:09:08 +12:00
|
|
|
async fn font() -> Result<Response<Body>, String> {
|
|
|
|
Ok(
|
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header("content-type", "font/woff2")
|
2021-11-29 11:47:50 +13:00
|
|
|
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
|
2021-05-20 11:09:08 +12:00
|
|
|
.body(include_bytes!("../static/Inter.var.woff2").as_ref().into())
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
async fn resource(body: &str, content_type: &str, cache: bool) -> Result<Response<Body>, String> {
|
|
|
|
let mut res = Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header("content-type", content_type)
|
|
|
|
.body(body.to_string().into())
|
|
|
|
.unwrap_or_default();
|
2021-02-25 06:26:01 +13:00
|
|
|
|
|
|
|
if cache {
|
2021-03-21 18:10:31 +13:00
|
|
|
if let Ok(val) = HeaderValue::from_str("public, max-age=1209600, s-maxage=86400") {
|
|
|
|
res.headers_mut().insert("Cache-Control", val);
|
2021-03-18 12:32:28 +13:00
|
|
|
}
|
2021-02-25 06:26:01 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
|
2022-05-21 13:41:31 +12:00
|
|
|
async fn style() -> Result<Response<Body>, String> {
|
|
|
|
let mut res = include_str!("../static/style.css").to_string();
|
|
|
|
for file in ThemeAssets::iter() {
|
2022-05-21 14:20:44 +12:00
|
|
|
res.push('\n');
|
2022-05-21 13:41:31 +12:00
|
|
|
let theme = ThemeAssets::get(file.as_ref()).unwrap();
|
|
|
|
res.push_str(std::str::from_utf8(theme.data.as_ref()).unwrap());
|
|
|
|
}
|
|
|
|
Ok(
|
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.header("content-type", "text/css")
|
|
|
|
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
|
|
|
|
.body(res.to_string().into())
|
|
|
|
.unwrap_or_default(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
#[tokio::main]
|
|
|
|
async fn main() {
|
2022-03-14 08:06:27 +13:00
|
|
|
let matches = Command::new("Libreddit")
|
2021-02-19 08:40:10 +13:00
|
|
|
.version(env!("CARGO_PKG_VERSION"))
|
|
|
|
.about("Private front-end for Reddit written in Rust ")
|
2021-05-27 15:30:08 +12:00
|
|
|
.arg(
|
2022-01-06 13:39:56 +13:00
|
|
|
Arg::new("redirect-https")
|
|
|
|
.short('r')
|
2021-05-27 15:30:08 +12:00
|
|
|
.long("redirect-https")
|
|
|
|
.help("Redirect all HTTP requests to HTTPS (no longer functional)")
|
2022-11-01 16:23:59 +13:00
|
|
|
.num_args(0),
|
2021-05-27 15:30:08 +12:00
|
|
|
)
|
2021-02-19 08:40:10 +13:00
|
|
|
.arg(
|
2022-01-06 13:39:56 +13:00
|
|
|
Arg::new("address")
|
|
|
|
.short('a')
|
2021-02-19 08:40:10 +13:00
|
|
|
.long("address")
|
|
|
|
.value_name("ADDRESS")
|
|
|
|
.help("Sets address to listen on")
|
2021-02-19 08:49:50 +13:00
|
|
|
.default_value("0.0.0.0")
|
2022-11-01 16:23:59 +13:00
|
|
|
.num_args(1),
|
2021-02-19 08:49:50 +13:00
|
|
|
)
|
|
|
|
.arg(
|
2022-01-06 13:39:56 +13:00
|
|
|
Arg::new("port")
|
|
|
|
.short('p')
|
2021-02-19 08:49:50 +13:00
|
|
|
.long("port")
|
|
|
|
.value_name("PORT")
|
|
|
|
.help("Port to listen on")
|
|
|
|
.default_value("8080")
|
2022-11-01 16:23:59 +13:00
|
|
|
.num_args(1),
|
2021-02-19 08:40:10 +13:00
|
|
|
)
|
2021-03-21 18:10:31 +13:00
|
|
|
.arg(
|
2022-01-06 13:39:56 +13:00
|
|
|
Arg::new("hsts")
|
|
|
|
.short('H')
|
2021-03-21 18:10:31 +13:00
|
|
|
.long("hsts")
|
|
|
|
.value_name("EXPIRE_TIME")
|
|
|
|
.help("HSTS header to tell browsers that this site should only be accessed over HTTPS")
|
|
|
|
.default_value("604800")
|
2022-11-01 16:23:59 +13:00
|
|
|
.num_args(1),
|
2021-03-21 18:10:31 +13:00
|
|
|
)
|
2021-02-19 08:40:10 +13:00
|
|
|
.get_matches();
|
|
|
|
|
2022-11-01 16:23:59 +13:00
|
|
|
let address = matches.get_one("address").map(|m: &String| m.as_str()).unwrap_or("0.0.0.0");
|
|
|
|
let port = std::env::var("PORT").unwrap_or_else(|_| matches.get_one("port").map(|m: &String| m.as_str()).unwrap_or("8080").to_string());
|
|
|
|
let hsts = matches.get_one("hsts").map(|m: &String| m.as_str());
|
2021-02-19 08:40:10 +13:00
|
|
|
|
2021-09-20 07:03:01 +12:00
|
|
|
let listener = [address, ":", &port].concat();
|
2020-11-23 16:21:07 +13:00
|
|
|
|
2021-02-25 08:00:04 +13:00
|
|
|
println!("Starting Libreddit...");
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
// Begin constructing a server
|
|
|
|
let mut app = server::Server::new();
|
2021-02-10 06:38:52 +13:00
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
// Define default headers (added to all responses)
|
|
|
|
app.default_headers = headers! {
|
|
|
|
"Referrer-Policy" => "no-referrer",
|
|
|
|
"X-Content-Type-Options" => "nosniff",
|
|
|
|
"X-Frame-Options" => "DENY",
|
2021-05-20 11:09:08 +12:00
|
|
|
"Content-Security-Policy" => "default-src 'none'; font-src 'self'; script-src 'self' blob:; manifest-src 'self'; media-src 'self' data: blob: about:; style-src 'self' 'unsafe-inline'; base-uri 'none'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none'; connect-src 'self'; worker-src blob:;"
|
2021-03-18 11:30:33 +13:00
|
|
|
};
|
2021-02-10 06:38:52 +13:00
|
|
|
|
2021-03-21 18:10:31 +13:00
|
|
|
if let Some(expire_time) = hsts {
|
|
|
|
if let Ok(val) = HeaderValue::from_str(&format!("max-age={}", expire_time)) {
|
|
|
|
app.default_headers.insert("Strict-Transport-Security", val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-10 06:38:52 +13:00
|
|
|
// Read static files
|
2022-05-21 13:41:31 +12:00
|
|
|
app.at("/style.css").get(|_| style().boxed());
|
2021-02-26 06:07:45 +13:00
|
|
|
app
|
2021-03-18 11:30:33 +13:00
|
|
|
.at("/manifest.json")
|
|
|
|
.get(|_| resource(include_str!("../static/manifest.json"), "application/json", false).boxed());
|
2021-09-10 12:28:55 +12:00
|
|
|
app
|
|
|
|
.at("/robots.txt")
|
|
|
|
.get(|_| resource("User-agent: *\nDisallow: /u/\nDisallow: /user/", "text/plain", true).boxed());
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/favicon.ico").get(|_| favicon().boxed());
|
|
|
|
app.at("/logo.png").get(|_| pwa_logo().boxed());
|
2021-05-20 11:09:08 +12:00
|
|
|
app.at("/Inter.var.woff2").get(|_| font().boxed());
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/touch-icon-iphone.png").get(|_| iphone_logo().boxed());
|
|
|
|
app.at("/apple-touch-icon.png").get(|_| iphone_logo().boxed());
|
2021-05-10 13:25:52 +12:00
|
|
|
app
|
|
|
|
.at("/playHLSVideo.js")
|
|
|
|
.get(|_| resource(include_str!("../static/playHLSVideo.js"), "text/javascript", false).boxed());
|
|
|
|
app
|
|
|
|
.at("/hls.min.js")
|
|
|
|
.get(|_| resource(include_str!("../static/hls.min.js"), "text/javascript", false).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
|
|
|
// Proxy media through Libreddit
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/vid/:id/:size").get(|r| proxy(r, "https://v.redd.it/{id}/DASH_{size}").boxed());
|
2021-05-10 13:25:52 +12:00
|
|
|
app.at("/hls/:id/*path").get(|r| proxy(r, "https://v.redd.it/{id}/{path}").boxed());
|
2021-11-25 15:08:27 +13:00
|
|
|
app.at("/img/*path").get(|r| proxy(r, "https://i.redd.it/{path}").boxed());
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/thumb/:point/:id").get(|r| proxy(r, "https://{point}.thumbs.redditmedia.com/{id}").boxed());
|
|
|
|
app.at("/emoji/:id/:name").get(|r| proxy(r, "https://emoji.redditmedia.com/{id}/{name}").boxed());
|
2021-11-29 11:47:50 +13:00
|
|
|
app
|
|
|
|
.at("/preview/:loc/award_images/:fullname/:id")
|
|
|
|
.get(|r| proxy(r, "https://{loc}view.redd.it/award_images/{fullname}/{id}").boxed());
|
2021-04-09 17:26:03 +12:00
|
|
|
app.at("/preview/:loc/:id").get(|r| proxy(r, "https://{loc}view.redd.it/{id}").boxed());
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/style/*path").get(|r| proxy(r, "https://styles.redditmedia.com/{path}").boxed());
|
|
|
|
app.at("/static/*path").get(|r| proxy(r, "https://www.redditstatic.com/{path}").boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
|
|
|
// Browse user profile
|
2021-03-18 11:30:33 +13:00
|
|
|
app
|
|
|
|
.at("/u/:name")
|
|
|
|
.get(|r| async move { Ok(redirect(format!("/user/{}", r.param("name").unwrap_or_default()))) }.boxed());
|
|
|
|
app.at("/u/:name/comments/:id/:title").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/u/:name/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
2021-03-18 17:40:55 +13:00
|
|
|
app.at("/user/[deleted]").get(|req| error(req, "User has deleted their account".to_string()).boxed());
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/user/:name").get(|r| user::profile(r).boxed());
|
2022-03-14 08:06:27 +13:00
|
|
|
app.at("/user/:name/:listing").get(|r| user::profile(r).boxed());
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/user/:name/comments/:id").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/user/:name/comments/:id/:title").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/user/:name/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
|
|
|
// Configure settings
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/settings").get(|r| settings::get(r).boxed()).post(|r| settings::set(r).boxed());
|
|
|
|
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
|
2021-05-10 13:25:52 +12:00
|
|
|
app.at("/settings/update").get(|r| settings::update(r).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
|
|
|
// Subreddit services
|
2021-05-17 03:53:39 +12:00
|
|
|
app
|
|
|
|
.at("/r/:sub")
|
|
|
|
.get(|r| subreddit::community(r).boxed())
|
|
|
|
.post(|r| subreddit::add_quarantine_exception(r).boxed());
|
2021-02-25 06:26:01 +13:00
|
|
|
|
2021-03-20 18:04:44 +13:00
|
|
|
app
|
|
|
|
.at("/r/u_:name")
|
|
|
|
.get(|r| async move { Ok(redirect(format!("/user/{}", r.param("name").unwrap_or_default()))) }.boxed());
|
|
|
|
|
2021-11-26 17:02:04 +13:00
|
|
|
app.at("/r/:sub/subscribe").post(|r| subreddit::subscriptions_filters(r).boxed());
|
|
|
|
app.at("/r/:sub/unsubscribe").post(|r| subreddit::subscriptions_filters(r).boxed());
|
|
|
|
app.at("/r/:sub/filter").post(|r| subreddit::subscriptions_filters(r).boxed());
|
|
|
|
app.at("/r/:sub/unfilter").post(|r| subreddit::subscriptions_filters(r).boxed());
|
2021-02-25 06:26:01 +13:00
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/r/:sub/comments/:id").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/r/:sub/comments/:id/:title").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/r/:sub/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
2022-11-02 16:53:42 +13:00
|
|
|
app.at("/comments/:id").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/comments/:id/comments").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/comments/:id/comments/:comment_id").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/comments/:id/:title").get(|r| post::item(r).boxed());
|
|
|
|
app.at("/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
2021-02-25 06:26:01 +13:00
|
|
|
|
2022-11-10 05:16:51 +13:00
|
|
|
app.at("/r/:sub/duplicates/:id").get(|r| duplicates::item(r).boxed());
|
|
|
|
app.at("/r/:sub/duplicates/:id/:title").get(|r| duplicates::item(r).boxed());
|
|
|
|
app.at("/duplicates/:id").get(|r| duplicates::item(r).boxed());
|
|
|
|
app.at("/duplicates/:id/:title").get(|r| duplicates::item(r).boxed());
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/r/:sub/search").get(|r| search::find(r).boxed());
|
2021-02-25 06:26:01 +13:00
|
|
|
|
2021-03-18 17:40:55 +13:00
|
|
|
app
|
|
|
|
.at("/r/:sub/w")
|
|
|
|
.get(|r| async move { Ok(redirect(format!("/r/{}/wiki", r.param("sub").unwrap_or_default()))) }.boxed());
|
|
|
|
app
|
2021-04-15 16:53:17 +12:00
|
|
|
.at("/r/:sub/w/*page")
|
2021-03-18 17:40:55 +13:00
|
|
|
.get(|r| async move { Ok(redirect(format!("/r/{}/wiki/{}", r.param("sub").unwrap_or_default(), r.param("wiki").unwrap_or_default()))) }.boxed());
|
|
|
|
app.at("/r/:sub/wiki").get(|r| subreddit::wiki(r).boxed());
|
2021-04-15 16:53:17 +12:00
|
|
|
app.at("/r/:sub/wiki/*page").get(|r| subreddit::wiki(r).boxed());
|
2021-02-25 06:26:01 +13:00
|
|
|
|
2021-03-22 15:28:05 +13:00
|
|
|
app.at("/r/:sub/about/sidebar").get(|r| subreddit::sidebar(r).boxed());
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/r/:sub/:sort").get(|r| subreddit::community(r).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
|
|
|
// Front page
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/").get(|r| subreddit::community(r).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
|
|
|
// View Reddit wiki
|
2021-03-21 11:42:47 +13:00
|
|
|
app.at("/w").get(|_| async { Ok(redirect("/wiki".to_string())) }.boxed());
|
2021-03-18 17:40:55 +13:00
|
|
|
app
|
2021-04-15 16:53:17 +12:00
|
|
|
.at("/w/*page")
|
2021-03-18 17:40:55 +13:00
|
|
|
.get(|r| async move { Ok(redirect(format!("/wiki/{}", r.param("page").unwrap_or_default()))) }.boxed());
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/wiki").get(|r| subreddit::wiki(r).boxed());
|
2021-04-15 16:53:17 +12:00
|
|
|
app.at("/wiki/*page").get(|r| subreddit::wiki(r).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
|
|
|
// Search all of Reddit
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/search").get(|r| search::find(r).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
2021-02-22 07:13:20 +13:00
|
|
|
// Handle about pages
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/about").get(|req| error(req, "About pages aren't added yet".to_string()).boxed());
|
|
|
|
|
2022-11-05 21:29:04 +13:00
|
|
|
app.at("/:id").get(|req: Request<Body>| {
|
|
|
|
Box::pin(async move {
|
|
|
|
match req.param("id").as_deref() {
|
|
|
|
// Sort front page
|
|
|
|
Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await,
|
|
|
|
|
|
|
|
// Short link for post
|
2023-01-02 12:06:58 +13:00
|
|
|
Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{}", id)).await {
|
2022-11-05 21:29:04 +13:00
|
|
|
Ok(path_opt) => match path_opt {
|
|
|
|
Some(path) => Ok(redirect(path)),
|
|
|
|
None => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await,
|
|
|
|
},
|
|
|
|
Err(e) => error(req, e).await,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Error message for unknown pages
|
|
|
|
_ => error(req, "Nothing here".to_string()).await,
|
|
|
|
}
|
|
|
|
})
|
2021-02-10 06:38:52 +13:00
|
|
|
});
|
|
|
|
|
|
|
|
// Default service in case no routes match
|
2021-03-18 11:30:33 +13:00
|
|
|
app.at("/*").get(|req| error(req, "Nothing here".to_string()).boxed());
|
2021-02-10 06:38:52 +13:00
|
|
|
|
2021-02-25 08:00:04 +13:00
|
|
|
println!("Running Libreddit v{} on {}!", env!("CARGO_PKG_VERSION"), listener);
|
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
let server = app.listen(listener);
|
2021-02-25 06:26:01 +13:00
|
|
|
|
2021-03-18 11:30:33 +13:00
|
|
|
// Run this server for... forever!
|
|
|
|
if let Err(e) = server.await {
|
|
|
|
eprintln!("Server error: {}", e);
|
|
|
|
}
|
2020-10-26 16:57:19 +13:00
|
|
|
}
|