feat(server): add tls/ssl support
Added support for https server.
This commit is contained in:
parent
f440f30ea8
commit
0794bea31d
|
@ -10,6 +10,10 @@
|
|||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# Tls
|
||||
cert.key
|
||||
cert.pem
|
||||
|
||||
# IDE configs
|
||||
.idea
|
||||
.vscode
|
||||
|
|
|
@ -67,6 +67,12 @@ version = "1.0.65"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.3.14"
|
||||
|
@ -155,6 +161,26 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-server"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87ba6170b61f7b086609dabcae68d2e07352539c6ef04a7c82980bdfa01a159d"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.66"
|
||||
|
@ -627,6 +653,7 @@ version = "0.0.0-dev"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum-server",
|
||||
"backtrace",
|
||||
"byte-unit",
|
||||
"crypto-utils",
|
||||
|
|
|
@ -37,6 +37,7 @@ toml = "0.5"
|
|||
|
||||
# HTTP server
|
||||
axum = { version = "0.6.0-rc.2", features = ["http2", "multipart"] }
|
||||
axum-server = { version = "0.4", features = ["tls-rustls"] }
|
||||
tower-http = { version = "0.3", features = ["full"] }
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
byte-unit = "4.0.14"
|
||||
|
|
19
README.md
19
README.md
|
@ -13,3 +13,22 @@
|
|||
|
||||
<a href="https://documenter.getpostman.com/view/23280189/VVk9dwRk"><img src="https://img.shields.io/badge/API_Docs-887BB0?style=flat-square&labelColor=555555&logo=postman"></a>
|
||||
</p>
|
||||
|
||||
## Documentation
|
||||
|
||||
### 👨💻 Compile server
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Now you can run server using command `./target/release/homedisk`.
|
||||
|
||||
### 🔒 Generate development TLS certificate
|
||||
|
||||
```bash
|
||||
# Generate private key
|
||||
openssl genrsa -out cert.key 204
|
||||
# Generate certificate
|
||||
openssl req -new -x509 -key cert.key -out cert.pem -days 365
|
||||
```
|
||||
|
|
22
config.toml
22
config.toml
|
@ -1,20 +1,14 @@
|
|||
[http]
|
||||
# HTTP host
|
||||
host = "0.0.0.0"
|
||||
# HTTP port
|
||||
port = 8080
|
||||
# Cors domains
|
||||
cors = [
|
||||
"127.0.0.1:8000",
|
||||
"localhost:8000",
|
||||
]
|
||||
httpPort = 8080 # http server port (recommended 80)
|
||||
httpsPort = 8443 # https server port (recommended 443)
|
||||
cors = [ "localhost:8000" ] # CORS domains
|
||||
tlsCert = "./cert.pem" # TLS certificate file
|
||||
tlsKey = "./cert.key" # TLS key file
|
||||
|
||||
[jwt]
|
||||
# JWT Secret string (used to sign tokens)
|
||||
secret = "secret key used to sign tokens"
|
||||
# Token expiration time in hours
|
||||
expires = 24 # one day
|
||||
secret = "secret key used to sign tokens" # jsonwebtoken secret string used to sign tokens
|
||||
expires = 24 # token expiration time in hours (default one day)
|
||||
|
||||
[storage]
|
||||
# Directory where user files will be stored
|
||||
path = "/home/homedisk"
|
||||
path = "/home/homedisk" # path to directory where user files will be stored
|
||||
|
|
|
@ -2,18 +2,32 @@ mod api;
|
|||
pub mod error;
|
||||
pub mod utils;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::{http::HeaderValue, routing::get, Extension, Router, Server};
|
||||
use tower_http::cors::{AllowOrigin, CorsLayer};
|
||||
use tracing::info;
|
||||
use axum::{
|
||||
extract::Host,
|
||||
handler::HandlerWithoutStateExt,
|
||||
http::{HeaderValue, StatusCode, Uri},
|
||||
response::Redirect,
|
||||
routing::get,
|
||||
Extension, Router,
|
||||
};
|
||||
use axum_server::tls_rustls::RustlsConfig;
|
||||
use tower_http::{
|
||||
cors::{AllowOrigin, CorsLayer},
|
||||
BoxError,
|
||||
};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::{config::Config, database::Database};
|
||||
|
||||
pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
|
||||
info!(
|
||||
"🚀 Server has launched on http://{}:{}",
|
||||
config.http.host, config.http.port
|
||||
);
|
||||
let host = format!("{}:{}", config.http.host, config.http.https_port);
|
||||
|
||||
tokio::spawn(redirect_http_to_https(config.clone()));
|
||||
|
||||
info!("🚀 Server has launched on https://{host}");
|
||||
|
||||
// change the type from Vec<String> to Vec<HeaderValue> so that the http server can correctly detect CORS hosts
|
||||
let origins = config
|
||||
|
@ -23,7 +37,12 @@ pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
|
|||
.map(|e| e.parse().expect("Failed to parse CORS hosts"))
|
||||
.collect::<Vec<HeaderValue>>();
|
||||
|
||||
let host = format!("{}:{}", config.http.host, config.http.port);
|
||||
let tls_config = RustlsConfig::from_pem_file(
|
||||
PathBuf::from("").join("").join(&config.http.tls_cert),
|
||||
PathBuf::from("").join("").join(&config.http.tls_key),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/api", api::app())
|
||||
|
@ -32,9 +51,48 @@ pub async fn start_server(config: Config, db: Database) -> anyhow::Result<()> {
|
|||
.layer(Extension(config))
|
||||
.layer(Extension(db));
|
||||
|
||||
Server::bind(&host.parse()?)
|
||||
axum_server::bind_rustls(host.parse()?, tls_config)
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
|
||||
Err(anyhow!("Server unexpected stopped!"))
|
||||
}
|
||||
|
||||
async fn redirect_http_to_https(config: Config) {
|
||||
fn make_https(host: String, uri: Uri, config: Config) -> Result<Uri, BoxError> {
|
||||
let mut parts = uri.into_parts();
|
||||
|
||||
parts.scheme = Some(axum::http::uri::Scheme::HTTPS);
|
||||
|
||||
if parts.path_and_query.is_none() {
|
||||
parts.path_and_query = Some("/".parse().unwrap());
|
||||
}
|
||||
|
||||
let https_host = host.replace(
|
||||
&config.http.http_port.to_string(),
|
||||
&config.http.https_port.to_string(),
|
||||
);
|
||||
parts.authority = Some(https_host.parse()?);
|
||||
|
||||
Ok(Uri::from_parts(parts)?)
|
||||
}
|
||||
|
||||
let host = format!("{}:{}", config.http.host, config.http.http_port);
|
||||
|
||||
let redirect = move |Host(host): Host, uri: Uri| async move {
|
||||
match make_https(host, uri, config) {
|
||||
Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
|
||||
Err(error) => {
|
||||
tracing::warn!(%error, "Failed to convert URI to HTTPS");
|
||||
Err(StatusCode::BAD_REQUEST)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
debug!("🚀 Http redirect listening on http://{host}");
|
||||
|
||||
axum::Server::bind(&host.parse().unwrap())
|
||||
.serve(redirect.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -4,41 +4,32 @@ use std::fs;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Settings configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
/// HTTP Settings.
|
||||
pub http: ConfigHTTP,
|
||||
/// Json Web Token Settings.
|
||||
pub jwt: ConfigJWT,
|
||||
/// Storage Settings.
|
||||
pub storage: ConfigStorage,
|
||||
}
|
||||
|
||||
/// HTTP Settings.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConfigHTTP {
|
||||
/// HTTP Host.
|
||||
pub host: String,
|
||||
/// Port HTTP Port.
|
||||
pub port: u16,
|
||||
/// [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) Domains (e.g ["site1.example.com", "site2.example.com"]).
|
||||
pub http_port: u16,
|
||||
pub https_port: u16,
|
||||
pub cors: Vec<String>,
|
||||
pub tls_cert: String,
|
||||
pub tls_key: String,
|
||||
}
|
||||
|
||||
/// Json Web Token Settings.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigJWT {
|
||||
/// JWT Secret string (used to sign tokens).
|
||||
pub secret: String,
|
||||
/// Token expiration time in hours.
|
||||
pub expires: i64,
|
||||
}
|
||||
|
||||
/// Storage Settings.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigStorage {
|
||||
/// Directory where user files will be stored.
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue