HTTP API: moved everything back to functions

This commit is contained in:
Igor Katson 2022-12-08 23:56:14 +00:00
parent 1875599706
commit b52824df6f
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
2 changed files with 91 additions and 76 deletions

View file

@ -1,5 +1,7 @@
use anyhow::Context; use anyhow::Context;
use axum::extract::{Path, Query, State}; use axum::extract::{Path, Query, State};
use axum::response::IntoResponse;
use axum::routing::get;
use buffers::ByteString; use buffers::ByteString;
use dht::{Dht, DhtStats}; use dht::{Dht, DhtStats};
use http::StatusCode; use http::StatusCode;
@ -12,9 +14,9 @@ use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use axum::{routing, Router}; use axum::Router;
use crate::http_api_error::{ApiError, WithErrorStatus}; use crate::http_api_error::{ApiError, ApiErrorExt};
use crate::session::{AddTorrentOptions, AddTorrentResponse, ListOnlyResponse, Session}; use crate::session::{AddTorrentOptions, AddTorrentResponse, ListOnlyResponse, Session};
use crate::torrent_manager::TorrentManagerHandle; use crate::torrent_manager::TorrentManagerHandle;
use crate::torrent_state::StatsSnapshot; use crate::torrent_state::StatsSnapshot;
@ -37,7 +39,9 @@ impl HttpApi {
pub async fn make_http_api_and_run(self, addr: SocketAddr) -> anyhow::Result<()> { pub async fn make_http_api_and_run(self, addr: SocketAddr) -> anyhow::Result<()> {
let state = self.inner; let state = self.inner;
let api_description_body = serde_json::json!({
async fn api_root() -> impl IntoResponse {
axum::Json(serde_json::json!({
"apis": { "apis": {
"GET /": "list all available APIs", "GET /": "list all available APIs",
"GET /dht/stats": "DHT stats", "GET /dht/stats": "DHT stats",
@ -51,66 +55,59 @@ impl HttpApi {
"POST /torrents": "Add a torrent here. magnet: or http:// or a local file." "POST /torrents": "Add a torrent here. magnet: or http:// or a local file."
}, },
"server": "rqbit", "server": "rqbit",
}); }))
}
let app = Router::new() async fn dht_stats(State(state): State<ApiState>) -> Result<impl IntoResponse> {
.route(
"/",
routing::get(move || async move { axum::Json(api_description_body) }),
)
.route(
"/dht/stats",
routing::get(|State(state): State<ApiState>| async move {
state.api_dht_stats().map(axum::Json) state.api_dht_stats().map(axum::Json)
}), }
)
.route( async fn dht_table(State(state): State<ApiState>) -> Result<impl IntoResponse> {
"/dht/table",
routing::get(|State(state): State<ApiState>| async move {
state.api_dht_table().map(axum::Json) state.api_dht_table().map(axum::Json)
}), }
)
.route( async fn torrents_list(State(state): State<ApiState>) -> impl IntoResponse {
"/torrents",
routing::get(|State(state): State<ApiState>| async move {
axum::Json(state.api_torrent_list()) axum::Json(state.api_torrent_list())
}), }
)
.route( async fn torrents_post(
"/torrents", State(state): State<ApiState>,
routing::post(
|State(state): State<ApiState>,
Query(params): Query<TorrentAddQueryParams>, Query(params): Query<TorrentAddQueryParams>,
url: String| async move { url: String,
) -> Result<impl IntoResponse> {
let opts = params.into_add_torrent_options(); let opts = params.into_add_torrent_options();
state.api_add_torrent(url, Some(opts)).await.map(axum::Json) state.api_add_torrent(url, Some(opts)).await.map(axum::Json)
}, }
),
) async fn torrent_details(
.route( State(state): State<ApiState>,
"/torrents/:id", Path(idx): Path<usize>,
routing::get( ) -> Result<impl IntoResponse> {
|State(state): State<ApiState>, Path(idx): Path<usize>| async move {
state.api_torrent_details(idx).map(axum::Json) state.api_torrent_details(idx).map(axum::Json)
}, }
),
) async fn torrent_haves(
.route( State(state): State<ApiState>,
"/torrents/:id/haves", Path(idx): Path<usize>,
routing::get( ) -> Result<impl IntoResponse> {
|State(state): State<ApiState>, Path(idx): Path<usize>| async move {
state.api_dump_haves(idx) state.api_dump_haves(idx)
}, }
),
) async fn torrent_stats(
.route( State(state): State<ApiState>,
"/torrents/:id/stats", Path(idx): Path<usize>,
routing::get( ) -> Result<impl IntoResponse> {
|State(state): State<ApiState>, Path(idx): Path<usize>| async move {
state.api_stats(idx).map(axum::Json) state.api_stats(idx).map(axum::Json)
}, }
),
) let app = Router::new()
.route("/", get(api_root))
.route("/dht/stats", get(dht_stats))
.route("/dht/table", get(dht_table))
.route("/torrents", get(torrents_list).post(torrents_post))
.route("/torrents/:id", get(torrent_details))
.route("/torrents/:id/haves", get(torrent_haves))
.route("/torrents/:id/stats", get(torrent_stats))
.with_state(state); .with_state(state);
log::info!("starting HTTP server on {}", addr); log::info!("starting HTTP server on {}", addr);

View file

@ -7,6 +7,7 @@ use serde::{Serialize, Serializer};
pub struct ApiError { pub struct ApiError {
status: Option<StatusCode>, status: Option<StatusCode>,
kind: ApiErrorKind, kind: ApiErrorKind,
plaintext: bool,
} }
impl ApiError { impl ApiError {
@ -14,6 +15,7 @@ impl ApiError {
Self { Self {
status: Some(StatusCode::NOT_FOUND), status: Some(StatusCode::NOT_FOUND),
kind: ApiErrorKind::TorrentNotFound(torrent_id), kind: ApiErrorKind::TorrentNotFound(torrent_id),
plaintext: false,
} }
} }
@ -21,6 +23,7 @@ impl ApiError {
Self { Self {
status: Some(StatusCode::NOT_FOUND), status: Some(StatusCode::NOT_FOUND),
kind: ApiErrorKind::DhtDisabled, kind: ApiErrorKind::DhtDisabled,
plaintext: false,
} }
} }
@ -32,6 +35,15 @@ impl ApiError {
Self { Self {
status: Some(status), status: Some(status),
kind: self.kind, kind: self.kind,
plaintext: self.plaintext,
}
}
pub fn with_plaintext_error(self, value: bool) -> Self {
Self {
status: self.status,
kind: self.kind,
plaintext: value,
} }
} }
} }
@ -79,6 +91,7 @@ impl From<anyhow::Error> for ApiError {
Self { Self {
status, status,
kind: ApiErrorKind::Other(value), kind: ApiErrorKind::Other(value),
plaintext: false,
} }
} }
} }
@ -110,15 +123,20 @@ impl IntoResponse for ApiError {
} }
} }
pub trait WithErrorStatus<T> { pub trait ApiErrorExt<T> {
fn with_error_status_code(self, s: StatusCode) -> Result<T, ApiError>; fn with_error_status_code(self, s: StatusCode) -> Result<T, ApiError>;
fn with_plaintext_error(self, value: bool) -> Result<T, ApiError>;
} }
impl<T, E> WithErrorStatus<T> for std::result::Result<T, E> impl<T, E> ApiErrorExt<T> for std::result::Result<T, E>
where where
E: Into<ApiError>, E: Into<ApiError>,
{ {
fn with_error_status_code(self, s: StatusCode) -> Result<T, ApiError> { fn with_error_status_code(self, s: StatusCode) -> Result<T, ApiError> {
self.map_err(|e| e.into().with_status(s)) self.map_err(|e| e.into().with_status(s))
} }
fn with_plaintext_error(self, value: bool) -> Result<T, ApiError> {
self.map_err(|e| e.into().with_plaintext_error(value))
}
} }