use bytes::Bytes;
use headers::{HeaderMapExt, UserAgent};
use http_body_util::{BodyExt as _, combinators::BoxBody};
use serde::Deserialize;
use tower::{BoxError, Service, ServiceBuilder, util::BoxCloneSyncService};
use tower_http::ServiceBuilderExt;
use tower_http_client::{ResponseExt as _, ServiceExt};
use tower_reqwest::HttpClientLayer;
type CloneableBody = http_body_util::Full<Bytes>;
type HttpClient = BoxCloneSyncService<
http::Request<CloneableBody>,
http::Response<BoxBody<Bytes, BoxError>>,
BoxError,
>;
fn into_tower_http_client(
client: reqwest::Client,
) -> impl Send
+ Clone
+ Service<
http::Request<CloneableBody>,
Response = http::Response<BoxBody<Bytes, BoxError>>,
Error = BoxError,
Future = impl Send,
> {
ServiceBuilder::new()
.map_err(BoxError::from)
.map_response_body(|body: reqwest::Body| body.map_err(BoxError::from).boxed())
.map_request_body(reqwest::Body::wrap)
.layer(HttpClientLayer)
.service(client)
}
#[derive(Debug, Deserialize)]
struct IpInfo {
ip: String,
country: String,
}
#[tokio::main]
async fn main() -> Result<(), BoxError> {
eprintln!("-> Creating an HTTP client with Tower layers...");
let opaque_client = into_tower_http_client(reqwest::Client::new());
let mut client: HttpClient = ServiceBuilder::new()
.layer_fn(BoxCloneSyncService::new)
.map_request(|mut request: http::Request<_>| {
request
.headers_mut()
.typed_insert(UserAgent::from_static("tower-http-client"));
request
})
.service(opaque_client);
eprintln!("-> Getting IP information...");
let response = client.get("http://api.myip.com").send().await?;
let info = response.body_reader().json::<IpInfo>().await?;
eprintln!("-> Got information:");
eprintln!(" IP address: {}", info.ip);
eprintln!(" Country: {}", info.country);
Ok(())
}