From b52824df6f86358516192b089b16817b8b4327cc Mon Sep 17 00:00:00 2001 From: Igor Katson Date: Thu, 8 Dec 2022 23:56:14 +0000 Subject: [PATCH] HTTP API: moved everything back to functions --- crates/librqbit/src/http_api.rs | 145 +++++++++++++------------- crates/librqbit/src/http_api_error.rs | 22 +++- 2 files changed, 91 insertions(+), 76 deletions(-) diff --git a/crates/librqbit/src/http_api.rs b/crates/librqbit/src/http_api.rs index df1e22b..5683bdc 100644 --- a/crates/librqbit/src/http_api.rs +++ b/crates/librqbit/src/http_api.rs @@ -1,5 +1,7 @@ use anyhow::Context; use axum::extract::{Path, Query, State}; +use axum::response::IntoResponse; +use axum::routing::get; use buffers::ByteString; use dht::{Dht, DhtStats}; use http::StatusCode; @@ -12,9 +14,9 @@ use std::net::SocketAddr; use std::sync::Arc; 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::torrent_manager::TorrentManagerHandle; use crate::torrent_state::StatsSnapshot; @@ -37,80 +39,75 @@ impl HttpApi { pub async fn make_http_api_and_run(self, addr: SocketAddr) -> anyhow::Result<()> { let state = self.inner; - let api_description_body = serde_json::json!({ - "apis": { - "GET /": "list all available APIs", - "GET /dht/stats": "DHT stats", - "GET /dht/table": "DHT routing table", - "GET /torrents": "List torrents (default torrent is 0)", - "GET /torrents/{index}": "Torrent details", - "GET /torrents/{index}/haves": "The bitfield of have pieces", - "GET /torrents/{index}/stats": "Torrent stats", - // This is kind of not secure as it just reads any local file that it has access to, - // or any URL, but whatever, ok for our purposes / threat model. - "POST /torrents": "Add a torrent here. magnet: or http:// or a local file." - }, - "server": "rqbit", - }); + + async fn api_root() -> impl IntoResponse { + axum::Json(serde_json::json!({ + "apis": { + "GET /": "list all available APIs", + "GET /dht/stats": "DHT stats", + "GET /dht/table": "DHT routing table", + "GET /torrents": "List torrents (default torrent is 0)", + "GET /torrents/{index}": "Torrent details", + "GET /torrents/{index}/haves": "The bitfield of have pieces", + "GET /torrents/{index}/stats": "Torrent stats", + // This is kind of not secure as it just reads any local file that it has access to, + // or any URL, but whatever, ok for our purposes / threat model. + "POST /torrents": "Add a torrent here. magnet: or http:// or a local file." + }, + "server": "rqbit", + })) + } + + async fn dht_stats(State(state): State) -> Result { + state.api_dht_stats().map(axum::Json) + } + + async fn dht_table(State(state): State) -> Result { + state.api_dht_table().map(axum::Json) + } + + async fn torrents_list(State(state): State) -> impl IntoResponse { + axum::Json(state.api_torrent_list()) + } + + async fn torrents_post( + State(state): State, + Query(params): Query, + url: String, + ) -> Result { + let opts = params.into_add_torrent_options(); + state.api_add_torrent(url, Some(opts)).await.map(axum::Json) + } + + async fn torrent_details( + State(state): State, + Path(idx): Path, + ) -> Result { + state.api_torrent_details(idx).map(axum::Json) + } + + async fn torrent_haves( + State(state): State, + Path(idx): Path, + ) -> Result { + state.api_dump_haves(idx) + } + + async fn torrent_stats( + State(state): State, + Path(idx): Path, + ) -> Result { + state.api_stats(idx).map(axum::Json) + } let app = Router::new() - .route( - "/", - routing::get(move || async move { axum::Json(api_description_body) }), - ) - .route( - "/dht/stats", - routing::get(|State(state): State| async move { - state.api_dht_stats().map(axum::Json) - }), - ) - .route( - "/dht/table", - routing::get(|State(state): State| async move { - state.api_dht_table().map(axum::Json) - }), - ) - .route( - "/torrents", - routing::get(|State(state): State| async move { - axum::Json(state.api_torrent_list()) - }), - ) - .route( - "/torrents", - routing::post( - |State(state): State, - Query(params): Query, - url: String| async move { - let opts = params.into_add_torrent_options(); - state.api_add_torrent(url, Some(opts)).await.map(axum::Json) - }, - ), - ) - .route( - "/torrents/:id", - routing::get( - |State(state): State, Path(idx): Path| async move { - state.api_torrent_details(idx).map(axum::Json) - }, - ), - ) - .route( - "/torrents/:id/haves", - routing::get( - |State(state): State, Path(idx): Path| async move { - state.api_dump_haves(idx) - }, - ), - ) - .route( - "/torrents/:id/stats", - routing::get( - |State(state): State, Path(idx): Path| async move { - state.api_stats(idx).map(axum::Json) - }, - ), - ) + .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); log::info!("starting HTTP server on {}", addr); diff --git a/crates/librqbit/src/http_api_error.rs b/crates/librqbit/src/http_api_error.rs index 7abce25..dfa68db 100644 --- a/crates/librqbit/src/http_api_error.rs +++ b/crates/librqbit/src/http_api_error.rs @@ -7,6 +7,7 @@ use serde::{Serialize, Serializer}; pub struct ApiError { status: Option, kind: ApiErrorKind, + plaintext: bool, } impl ApiError { @@ -14,6 +15,7 @@ impl ApiError { Self { status: Some(StatusCode::NOT_FOUND), kind: ApiErrorKind::TorrentNotFound(torrent_id), + plaintext: false, } } @@ -21,6 +23,7 @@ impl ApiError { Self { status: Some(StatusCode::NOT_FOUND), kind: ApiErrorKind::DhtDisabled, + plaintext: false, } } @@ -32,6 +35,15 @@ impl ApiError { Self { status: Some(status), 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 for ApiError { Self { status, kind: ApiErrorKind::Other(value), + plaintext: false, } } } @@ -110,15 +123,20 @@ impl IntoResponse for ApiError { } } -pub trait WithErrorStatus { +pub trait ApiErrorExt { fn with_error_status_code(self, s: StatusCode) -> Result; + fn with_plaintext_error(self, value: bool) -> Result; } -impl WithErrorStatus for std::result::Result +impl ApiErrorExt for std::result::Result where E: Into, { fn with_error_status_code(self, s: StatusCode) -> Result { self.map_err(|e| e.into().with_status(s)) } + + fn with_plaintext_error(self, value: bool) -> Result { + self.map_err(|e| e.into().with_plaintext_error(value)) + } }