rs_starter/core/
builtin_handles.rs

1use std::{io, time::Duration};
2use actix_web::{http, get, web, error, web::ServiceConfig, Error, web::{Data}, Result, dev::ServiceRequest, HttpRequest, HttpResponse, Responder};
3use actix_cors::Cors;
4use actix_files::NamedFile;
5use actix_extensible_rate_limit::{backend::memory::InMemoryBackend, backend::SimpleInputFunctionBuilder, backend::SimpleInput, backend::SimpleOutput, RateLimiter};
6
7use futures::{future::ok, stream::once};
8use derive_more::{Display, Error};
9use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslAcceptorBuilder};
10
11use tera::{Tera, Context};
12
13use lazy_static::lazy_static;
14use regex::Regex;
15
16
17#[derive(Debug, Display, Error)]
18#[display(fmt = "my error: {}", name)]
19pub struct MyError {
20    name: &'static str,
21}
22
23// Use default implementation for `error_response()` method
24impl error::ResponseError for MyError {
25    
26}
27
28/// Prettify HTML input
29pub fn prettify(input: &str) -> String {
30
31    lazy_static! {
32        static ref OPEN_TAG: Regex = Regex::new("(?P<tag><[A-z])").unwrap();
33        static ref EMPTY_LINE: Regex = Regex::new("(\\s*\n){1,}").unwrap();
34        static ref CLOSE_TAG: Regex = Regex::new("([^>\n]\\s*</)").unwrap();
35    }
36
37    // First get all tags on their own lines
38    let mut stage1 = input.to_string();
39    stage1 = stage1.replace("<!--", "\n<!--");
40    stage1 = stage1.replace("-->", "-->\n");
41    stage1 = stage1.replace("</", "\n</");
42    stage1 = OPEN_TAG.replace_all(&stage1, "\n$tag").to_string();
43    stage1 = stage1.trim().to_string();
44
45    // Now fix indentation
46    let mut stage2: Vec<String> = vec![];
47    let mut indent = 0;
48    for line in stage1.split('\n') {
49        let mut post_add = 0;
50        if line.starts_with("</") {
51            indent -= 1;
52        } else if line.ends_with("/>") || line.starts_with("<!DOCTYPE") || line.starts_with("<meta ") {
53            // Self-closing, nothing
54            // or DOCTYPE, also nothing
55        } else if line.starts_with('<') {
56            post_add += 1;
57        }
58
59        stage2.push(format!("{}{}", "  ".repeat(indent), line));
60        indent += post_add;
61    }
62
63    let pretty_html1 = stage2.join("\n");
64    // let pretty_html2 = EMPTY_LINE.replace_all(&pretty_html1, "\n").to_string();
65    // let pretty_html3 = CLOSE_TAG.replace_all(&pretty_html2, "</").to_string();
66
67    pretty_html1
68
69}
70
71
72#[get("/favicon.ico")]
73pub async fn favicon(_req: HttpRequest) -> io::Result<NamedFile> {
74    Ok(NamedFile::open("static/favicon.ico")?)
75}
76
77#[get("/favicon.svg")]
78pub async fn favicon_svg(_req: HttpRequest) -> io::Result<NamedFile> {
79    Ok(NamedFile::open("static/favicon.svg")?)
80}
81
82pub async fn index(tmpl: Data<Tera>) -> impl Responder {
83
84    let mut ctx = Context::new();
85    ctx.insert("name", "啦啦发啦");
86
87    let render_result = tmpl.render("index.html", &ctx);
88
89    match render_result {
90        Ok(rendered) => {
91            HttpResponse::Ok().body(rendered)
92        },
93        Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
94    }
95
96}
97
98pub async fn info() -> impl Responder {
99    HttpResponse::Ok().json("Hello, server is alive and kicking.")
100}
101
102pub async fn readme(_req: HttpRequest) -> io::Result<NamedFile> {
103    Ok(NamedFile::open("README.md")?)
104}
105
106pub async fn about() -> Result<HttpResponse> {
107    Ok(HttpResponse::build(http::StatusCode::OK)
108        .content_type("text/html;charset=utf-8")
109        .body("<h1>About</h1>"))
110}
111
112pub async fn developer(_req: HttpRequest) -> Result<HttpResponse> {
113    Ok(HttpResponse::build(http::StatusCode::OK)
114        .content_type("text/html;charset=utf-8")
115        .body("<h1>Developer</h1>"))
116}
117
118// Response body can be generated asynchronously. 
119// In this case, body must implement the stream trait Stream<Item=Bytes, Error=Error>, i.e.:
120pub async fn stream() -> HttpResponse {
121    let body = once(ok::<_, Error>(web::Bytes::from_static(b"test")));
122    HttpResponse::Ok()
123        .content_type("text/plain;charset=utf-8")
124        .streaming(body)
125}
126
127#[get("/errors")]
128pub async fn errors() -> Result<&'static str, MyError> {
129    Err(MyError { name: "MyError,粗欧文" })
130}
131
132pub async fn throw_error(id: web::Path<u32>) -> Result<HttpResponse, MyError> {
133    let user_id: u32 = id.into_inner();
134    log::info!("userId: {}", user_id);
135    Err(MyError { name: "MyError,粗欧文" })
136}
137
138pub async fn not_found(_request: HttpRequest) -> Result<HttpResponse> {
139    Ok(HttpResponse::build(http::StatusCode::OK)
140        .content_type("text/html;charset=utf-8")
141        .body("<h1>404 - Page not found</h1>"))
142}
143
144pub fn static_handler(config: &mut ServiceConfig) {
145    // let static_path =
146    //     std::env::var("STATIC_ROOT").expect("Running in debug without STATIC_ROOT set!");
147    let static_path = "static";
148    let fs = actix_files::Files::new("/static", &static_path);
149    config.service(fs);
150}
151
152pub fn tls_builder() -> SslAcceptorBuilder {
153    let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
154    builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
155    builder.set_certificate_chain_file("cert.pem").unwrap();
156    return builder
157}
158
159pub fn cors() -> Cors{
160    return Cors::default()
161    .allowed_methods(vec!["GET", "POST", "DELETE", "PUT", "PATCH"])
162    .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
163    .allowed_header(http::header::CONTENT_TYPE)
164    .max_age(3600);
165}
166
167pub fn access_limiter() -> RateLimiter<InMemoryBackend, SimpleOutput, impl Fn(&ServiceRequest) -> std::future::Ready<Result<SimpleInput, Error>>>{
168    return RateLimiter::builder(
169        InMemoryBackend::builder().build(), 
170        SimpleInputFunctionBuilder::new(Duration::from_secs(1), 5).real_ip_key().build()
171    ).add_headers().build();
172}