feat(server): use msgpack instead of json
This commit is contained in:
parent
28746242d3
commit
71ecba455f
|
@ -1043,8 +1043,11 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_path_to_error",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tower",
|
||||
|
@ -1114,6 +1117,28 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.2"
|
||||
|
|
|
@ -8,8 +8,11 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1"
|
||||
axum = "0.6"
|
||||
bytes = "1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
rmp-serde = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_path_to_error = "0.1"
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "chrono"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
tower = "0.4"
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::{BoxError, Json, Router, Server};
|
||||
use axum::{async_trait, BoxError, Router, Server};
|
||||
use axum::body::HttpBody;
|
||||
use axum::error_handling::HandleErrorLayer;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::http::{Method, StatusCode};
|
||||
use axum::extract::{FromRequest, Path, State};
|
||||
use axum::extract::rejection::BytesRejection;
|
||||
use axum::http::{HeaderValue, Request, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::{get, post};
|
||||
use bytes::Bytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use sqlx::SqlitePool;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
@ -21,14 +26,17 @@ async fn main() -> Result<()> {
|
|||
let pool = SqlitePool::connect("./database.sqlite").await?;
|
||||
MIGRATOR.run(&pool).await?;
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
#[cfg(not(debug_assertions))]
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin([
|
||||
"https://map.anna.lgbt".parse()?,
|
||||
])
|
||||
.allow_methods([Method::GET]);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let cors = CorsLayer::permissive();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/:territory", get(territory))
|
||||
.route("/:territory/:world", get(territory_world))
|
||||
.route("/upload", post(upload))
|
||||
|
@ -49,14 +57,10 @@ async fn main() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn index() -> &'static str {
|
||||
"hi"
|
||||
}
|
||||
|
||||
async fn territory(
|
||||
State(pool): State<Arc<SqlitePool>>,
|
||||
Path(territory): Path<u32>,
|
||||
) -> Result<Json<Vec<AnonymousPlayerInfo>>, AppError>
|
||||
) -> Result<MsgPack<Vec<AnonymousPlayerInfo>>, AppError>
|
||||
{
|
||||
let info = sqlx::query_as!(
|
||||
AnonymousPlayerInfo,
|
||||
|
@ -75,20 +79,20 @@ async fn territory(
|
|||
max_hp
|
||||
from players
|
||||
where territory = ?
|
||||
and unixepoch('now') - unixepoch(timestamp) < 30
|
||||
--and unixepoch('now') - unixepoch(timestamp) < 30
|
||||
"#,
|
||||
territory,
|
||||
)
|
||||
.fetch_all(&*pool)
|
||||
.await?;
|
||||
|
||||
Ok(Json(info))
|
||||
Ok(MsgPack(info))
|
||||
}
|
||||
|
||||
async fn territory_world(
|
||||
State(pool): State<Arc<SqlitePool>>,
|
||||
Path((territory, world)): Path<(u32, u32)>,
|
||||
) -> Result<Json<Vec<AnonymousPlayerInfo>>, AppError>
|
||||
) -> Result<MsgPack<Vec<AnonymousPlayerInfo>>, AppError>
|
||||
{
|
||||
let info = sqlx::query_as!(
|
||||
AnonymousPlayerInfo,
|
||||
|
@ -108,7 +112,7 @@ async fn territory_world(
|
|||
from players
|
||||
where territory = ?
|
||||
and current_world = ?
|
||||
and unixepoch('now') - unixepoch(timestamp) < 30
|
||||
--and unixepoch('now') - unixepoch(timestamp) < 30
|
||||
"#,
|
||||
territory,
|
||||
world,
|
||||
|
@ -116,12 +120,12 @@ async fn territory_world(
|
|||
.fetch_all(&*pool)
|
||||
.await?;
|
||||
|
||||
Ok(Json(info))
|
||||
Ok(MsgPack(info))
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
pool: State<Arc<SqlitePool>>,
|
||||
data: Json<Update>,
|
||||
data: MsgPack<Update>,
|
||||
) -> Result<(), AppError> {
|
||||
let mut t = pool.begin().await?;
|
||||
|
||||
|
@ -272,3 +276,99 @@ impl<E> From<E> for AppError
|
|||
Self(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
struct MsgPack<T>(pub T);
|
||||
|
||||
impl<T> Deref for MsgPack<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for MsgPack<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<S, B, T> FromRequest<S, B> for MsgPack<T>
|
||||
where S: Send + Sync,
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
type Rejection = MsgPackRejection;
|
||||
|
||||
async fn from_request(req: Request<B>, state: &S) -> std::result::Result<Self, Self::Rejection> {
|
||||
if req.headers().get("content-type").and_then(|val| val.to_str().ok()) != Some("application/msgpack") {
|
||||
return Err(MsgPackRejection::ContentType);
|
||||
}
|
||||
|
||||
let bytes = Bytes::from_request(req, state).await?;
|
||||
let value = rmp_serde::from_slice(&bytes)?;
|
||||
// let des = &mut rmp_serde::Deserializer::from_read_ref(&bytes);
|
||||
// let value = match serde_path_to_error::deserialize(des) {
|
||||
// Ok(v) => v,
|
||||
// Err(err) => {
|
||||
// let rejection = match err.inner() {
|
||||
// rmp_serde::decode::Error::DepthLimitExceeded => {},
|
||||
// };
|
||||
//
|
||||
// return Err(rejection);
|
||||
// }
|
||||
// };
|
||||
|
||||
Ok(MsgPack(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for MsgPack<T>
|
||||
where T: Serialize,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
[(
|
||||
axum::http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/msgpack"),
|
||||
)],
|
||||
rmp_serde::to_vec(&self.0)
|
||||
.map_err(|e| AppError(e.into()))
|
||||
.into_response()
|
||||
).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
enum MsgPackRejection {
|
||||
ContentType,
|
||||
Bytes(BytesRejection),
|
||||
MsgPack(rmp_serde::decode::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for MsgPackRejection {
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
StatusCode::BAD_REQUEST,
|
||||
match self {
|
||||
Self::ContentType => "expected application/msgpack content-type header".into_response(),
|
||||
Self::Bytes(e) => e.into_response(),
|
||||
Self::MsgPack(e) => format!("could not deserialize msgpack: {:#}", e).into_response(),
|
||||
}
|
||||
).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesRejection> for MsgPackRejection {
|
||||
fn from(value: BytesRejection) -> Self {
|
||||
Self::Bytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmp_serde::decode::Error> for MsgPackRejection {
|
||||
fn from(value: rmp_serde::decode::Error) -> Self {
|
||||
Self::MsgPack(value)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue