reqwest 0.12 and other breaking changes (#135)

* update reqwest and http crates

remove task_local_extensions

* remove older opentelemetry packages

* remove more legacy and add new otel

* attempt to make features additive

* features are additive

* delete commented out code

* build split

* docs

* more uniform with reqwest::Client

* remove arcs

* slight optimisation

* update readmes

* update changelog
This commit is contained in:
Conrad Ludgate 2024-04-03 18:13:10 +01:00 committed by GitHub
parent 69269e183a
commit 60212ae451
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 516 additions and 282 deletions

View file

@ -12,13 +12,6 @@ jobs:
strategy: strategy:
matrix: matrix:
otel_version: otel_version:
- opentelemetry_0_13
- opentelemetry_0_14
- opentelemetry_0_15
- opentelemetry_0_16
- opentelemetry_0_17
- opentelemetry_0_18
- opentelemetry_0_19
- opentelemetry_0_20 - opentelemetry_0_20
- opentelemetry_0_21 - opentelemetry_0_21
- opentelemetry_0_22 - opentelemetry_0_22
@ -61,13 +54,6 @@ jobs:
strategy: strategy:
matrix: matrix:
otel_version: otel_version:
- opentelemetry_0_13
- opentelemetry_0_14
- opentelemetry_0_15
- opentelemetry_0_16
- opentelemetry_0_17
- opentelemetry_0_18
- opentelemetry_0_19
- opentelemetry_0_20 - opentelemetry_0_20
- opentelemetry_0_21 - opentelemetry_0_21
- opentelemetry_0_22 - opentelemetry_0_22
@ -102,13 +88,6 @@ jobs:
strategy: strategy:
matrix: matrix:
otel_version: otel_version:
- opentelemetry_0_13
- opentelemetry_0_14
- opentelemetry_0_15
- opentelemetry_0_16
- opentelemetry_0_17
- opentelemetry_0_18
- opentelemetry_0_19
- opentelemetry_0_20 - opentelemetry_0_20
- opentelemetry_0_21 - opentelemetry_0_21
- opentelemetry_0_22 - opentelemetry_0_22

View file

@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Breaking changes
- Updated `reqwest` to `0.12.0`.
- Removed `task_local_extensions` in favour of `http::Extensions`.
- All extensions must be `Clone` now.
- Replaced `with_arc` and `with_arc_init` methods with `with_box` and `with_box_init`.
### Changed
- `RequestBuilder::try_clone` now clones the extensions.
### Added
- implemented `Service` for `ClientWithMiddleware` to have more feature parity with `reqwest`.
- Added more methods like `build_split` to have more feature parity with `reqwest.`
- Added a lot more documentation
### [0.2.5] - 2024-03-15 ### [0.2.5] - 2024-03-15
### Changed ### Changed

View file

@ -29,11 +29,11 @@ The `reqwest-middleware` client exposes the same interface as a plain `reqwest`
# Cargo.toml # Cargo.toml
# ... # ...
[dependencies] [dependencies]
reqwest = "0.11" reqwest = { version = "0.12", features = ["rustls-tls"] }
reqwest-middleware = "0.1.6" reqwest-middleware = "0.3"
reqwest-retry = "0.1.5" reqwest-retry = "0.5"
reqwest-tracing = "0.2.3" reqwest-tracing = "0.5"
tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
``` ```
```rust ```rust

View file

@ -1,6 +1,6 @@
[package] [package]
name = "reqwest-middleware" name = "reqwest-middleware"
version = "0.2.5" version = "0.3.0"
authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"] authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"]
edition = "2018" edition = "2018"
description = "Wrapper around reqwest to allow for client middleware chains." description = "Wrapper around reqwest to allow for client middleware chains."
@ -10,17 +10,21 @@ keywords = ["reqwest", "http", "middleware"]
categories = ["web-programming::http-client"] categories = ["web-programming::http-client"]
readme = "../README.md" readme = "../README.md"
[features]
multipart = ["reqwest/multipart"]
json = ["reqwest/json"]
[dependencies] [dependencies]
anyhow = "1.0.0" anyhow = "1.0.0"
async-trait = "0.1.51" async-trait = "0.1.51"
http = "0.2.0" http = "1.0.0"
reqwest = { version = "0.11.10", default-features = false, features = ["json", "multipart"] } reqwest = { version = "0.12.0", default-features = false }
serde = "1.0.106" serde = "1.0.106"
task-local-extensions = "0.1.4"
thiserror = "1.0.21" thiserror = "1.0.21"
tower-service = "0.3.0"
[dev-dependencies] [dev-dependencies]
reqwest-retry = { path = "../reqwest-retry" } reqwest-retry = { path = "../reqwest-retry" }
reqwest-tracing = { path = "../reqwest-tracing" } reqwest-tracing = { path = "../reqwest-tracing" }
tokio = { version = "1.0.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.0.0", features = ["macros", "rt-multi-thread"] }
wiremock = "0.5.0" wiremock = "0.6.0"

View file

@ -1,11 +1,13 @@
use http::Extensions;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::multipart::Form;
use reqwest::{Body, Client, IntoUrl, Method, Request, Response}; use reqwest::{Body, Client, IntoUrl, Method, Request, Response};
use serde::Serialize; use serde::Serialize;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::sync::Arc; use std::sync::Arc;
use task_local_extensions::Extensions;
#[cfg(feature = "multipart")]
use reqwest::multipart;
use crate::error::Result; use crate::error::Result;
use crate::middleware::{Middleware, Next}; use crate::middleware::{Middleware, Next};
@ -16,8 +18,8 @@ use crate::RequestInitialiser;
/// [`ClientWithMiddleware`]: crate::ClientWithMiddleware /// [`ClientWithMiddleware`]: crate::ClientWithMiddleware
pub struct ClientBuilder { pub struct ClientBuilder {
client: Client, client: Client,
middleware_stack: Vec<Arc<dyn Middleware>>, middleware_stack: Vec<Box<dyn Middleware>>,
initialiser_stack: Vec<Arc<dyn RequestInitialiser>>, initialiser_stack: Vec<Box<dyn RequestInitialiser>>,
} }
impl ClientBuilder { impl ClientBuilder {
@ -31,40 +33,40 @@ impl ClientBuilder {
/// Convenience method to attach middleware. /// Convenience method to attach middleware.
/// ///
/// If you need to keep a reference to the middleware after attaching, use [`with_arc`]. /// If you need to keep a reference to the middleware after attaching, use [`with_box`].
/// ///
/// [`with_arc`]: Self::with_arc /// [`with_box`]: Self::with_box
pub fn with<M>(self, middleware: M) -> Self pub fn with<M>(self, middleware: M) -> Self
where where
M: Middleware, M: Middleware,
{ {
self.with_arc(Arc::new(middleware)) self.with_box(Box::new(middleware))
} }
/// Add middleware to the chain. [`with`] is more ergonomic if you don't need the `Arc`. /// Add middleware to the chain. [`with`] is more ergonomic if you don't need the `Box`.
/// ///
/// [`with`]: Self::with /// [`with`]: Self::with
pub fn with_arc(mut self, middleware: Arc<dyn Middleware>) -> Self { pub fn with_box(mut self, middleware: Box<dyn Middleware>) -> Self {
self.middleware_stack.push(middleware); self.middleware_stack.push(middleware);
self self
} }
/// Convenience method to attach a request initialiser. /// Convenience method to attach a request initialiser.
/// ///
/// If you need to keep a reference to the initialiser after attaching, use [`with_arc_init`]. /// If you need to keep a reference to the initialiser after attaching, use [`with_box_init`].
/// ///
/// [`with_arc_init`]: Self::with_arc_init /// [`with_box_init`]: Self::with_box_init
pub fn with_init<I>(self, initialiser: I) -> Self pub fn with_init<I>(self, initialiser: I) -> Self
where where
I: RequestInitialiser, I: RequestInitialiser,
{ {
self.with_arc_init(Arc::new(initialiser)) self.with_box_init(Box::new(initialiser))
} }
/// Add a request initialiser to the chain. [`with_init`] is more ergonomic if you don't need the `Arc`. /// Add a request initialiser to the chain. [`with_init`] is more ergonomic if you don't need the `Box`.
/// ///
/// [`with_init`]: Self::with_init /// [`with_init`]: Self::with_init
pub fn with_arc_init(mut self, initialiser: Arc<dyn RequestInitialiser>) -> Self { pub fn with_box_init(mut self, initialiser: Box<dyn RequestInitialiser>) -> Self {
self.initialiser_stack.push(initialiser); self.initialiser_stack.push(initialiser);
self self
} }
@ -73,8 +75,8 @@ impl ClientBuilder {
pub fn build(self) -> ClientWithMiddleware { pub fn build(self) -> ClientWithMiddleware {
ClientWithMiddleware { ClientWithMiddleware {
inner: self.client, inner: self.client,
middleware_stack: self.middleware_stack.into_boxed_slice(), middleware_stack: self.middleware_stack.into(),
initialiser_stack: self.initialiser_stack.into_boxed_slice(), initialiser_stack: self.initialiser_stack.into(),
} }
} }
} }
@ -84,73 +86,127 @@ impl ClientBuilder {
#[derive(Clone)] #[derive(Clone)]
pub struct ClientWithMiddleware { pub struct ClientWithMiddleware {
inner: reqwest::Client, inner: reqwest::Client,
middleware_stack: Box<[Arc<dyn Middleware>]>, middleware_stack: Arc<[Box<dyn Middleware>]>,
initialiser_stack: Box<[Arc<dyn RequestInitialiser>]>, initialiser_stack: Arc<[Box<dyn RequestInitialiser>]>,
} }
impl ClientWithMiddleware { impl ClientWithMiddleware {
/// See [`ClientBuilder`] for a more ergonomic way to build `ClientWithMiddleware` instances. /// See [`ClientBuilder`] for a more ergonomic way to build `ClientWithMiddleware` instances.
pub fn new<T>(client: Client, middleware_stack: T) -> Self pub fn new<T>(client: Client, middleware_stack: T) -> Self
where where
T: Into<Box<[Arc<dyn Middleware>]>>, T: Into<Arc<[Box<dyn Middleware>]>>,
{ {
ClientWithMiddleware { ClientWithMiddleware {
inner: client, inner: client,
middleware_stack: middleware_stack.into(), middleware_stack: middleware_stack.into(),
// TODO(conradludgate) - allow downstream code to control this manually if desired // TODO(conradludgate) - allow downstream code to control this manually if desired
initialiser_stack: Box::new([]), initialiser_stack: Arc::from(vec![]),
} }
} }
/// See [`Client::get`] /// Convenience method to make a `GET` request to a URL.
///
/// # Errors
///
/// This method fails whenever the supplied `Url` cannot be parsed.
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::GET, url) self.request(Method::GET, url)
} }
/// See [`Client::post`] /// Convenience method to make a `POST` request to a URL.
///
/// # Errors
///
/// This method fails whenever the supplied `Url` cannot be parsed.
pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::POST, url) self.request(Method::POST, url)
} }
/// See [`Client::put`] /// Convenience method to make a `PUT` request to a URL.
///
/// # Errors
///
/// This method fails whenever the supplied `Url` cannot be parsed.
pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PUT, url) self.request(Method::PUT, url)
} }
/// See [`Client::patch`] /// Convenience method to make a `PATCH` request to a URL.
///
/// # Errors
///
/// This method fails whenever the supplied `Url` cannot be parsed.
pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PATCH, url) self.request(Method::PATCH, url)
} }
/// See [`Client::delete`] /// Convenience method to make a `DELETE` request to a URL.
///
/// # Errors
///
/// This method fails whenever the supplied `Url` cannot be parsed.
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::DELETE, url) self.request(Method::DELETE, url)
} }
/// See [`Client::head`] /// Convenience method to make a `HEAD` request to a URL.
///
/// # Errors
///
/// This method fails whenever the supplied `Url` cannot be parsed.
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder { pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::HEAD, url) self.request(Method::HEAD, url)
} }
/// See [`Client::request`] /// Start building a `Request` with the `Method` and `Url`.
///
/// Returns a `RequestBuilder`, which will allow setting headers and
/// the request body before sending.
///
/// # Errors
///
/// This method fails whenever the supplied `Url` cannot be parsed.
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder { pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
let req = RequestBuilder { let req = RequestBuilder {
inner: self.inner.request(method, url), inner: self.inner.request(method, url),
client: self.clone(),
extensions: Extensions::new(), extensions: Extensions::new(),
middleware_stack: self.middleware_stack.clone(),
initialiser_stack: self.initialiser_stack.clone(),
}; };
self.initialiser_stack self.initialiser_stack
.iter() .iter()
.fold(req, |req, i| i.init(req)) .fold(req, |req, i| i.init(req))
} }
/// See [`Client::execute`] /// Executes a `Request`.
///
/// A `Request` can be built manually with `Request::new()` or obtained
/// from a RequestBuilder with `RequestBuilder::build()`.
///
/// You should prefer to use the `RequestBuilder` and
/// `RequestBuilder::send()`.
///
/// # Errors
///
/// This method fails if there was an error while sending request,
/// redirect loop was detected or redirect limit was exhausted.
pub async fn execute(&self, req: Request) -> Result<Response> { pub async fn execute(&self, req: Request) -> Result<Response> {
let mut ext = Extensions::new(); let mut ext = Extensions::new();
self.execute_with_extensions(req, &mut ext).await self.execute_with_extensions(req, &mut ext).await
} }
/// Executes a request with initial [`Extensions`]. /// Executes a `Request` with initial [`Extensions`].
///
/// A `Request` can be built manually with `Request::new()` or obtained
/// from a RequestBuilder with `RequestBuilder::build()`.
///
/// You should prefer to use the `RequestBuilder` and
/// `RequestBuilder::send()`.
///
/// # Errors
///
/// This method fails if there was an error while sending request,
/// redirect loop was detected or redirect limit was exhausted.
pub async fn execute_with_extensions( pub async fn execute_with_extensions(
&self, &self,
req: Request, req: Request,
@ -166,8 +222,8 @@ impl From<Client> for ClientWithMiddleware {
fn from(client: Client) -> Self { fn from(client: Client) -> Self {
ClientWithMiddleware { ClientWithMiddleware {
inner: client, inner: client,
middleware_stack: Box::new([]), middleware_stack: Arc::from(vec![]),
initialiser_stack: Box::new([]), initialiser_stack: Arc::from(vec![]),
} }
} }
} }
@ -181,15 +237,101 @@ impl fmt::Debug for ClientWithMiddleware {
} }
} }
mod service {
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use crate::Result;
use http::Extensions;
use reqwest::{Request, Response};
use crate::{middleware::BoxFuture, ClientWithMiddleware, Next};
// this is meant to be semi-private, same as reqwest's pending
pub struct Pending {
inner: BoxFuture<'static, Result<Response>>,
}
impl Unpin for Pending {}
impl Future for Pending {
type Output = Result<Response>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.as_mut().poll(cx)
}
}
impl tower_service::Service<Request> for ClientWithMiddleware {
type Response = Response;
type Error = crate::Error;
type Future = Pending;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
self.inner.poll_ready(cx).map_err(crate::Error::Reqwest)
}
fn call(&mut self, req: Request) -> Self::Future {
let inner = self.inner.clone();
let middlewares = self.middleware_stack.clone();
let mut extensions = Extensions::new();
Pending {
inner: Box::pin(async move {
let next = Next::new(&inner, &middlewares);
next.run(req, &mut extensions).await
}),
}
}
}
impl tower_service::Service<Request> for &'_ ClientWithMiddleware {
type Response = Response;
type Error = crate::Error;
type Future = Pending;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
(&self.inner).poll_ready(cx).map_err(crate::Error::Reqwest)
}
fn call(&mut self, req: Request) -> Self::Future {
let inner = self.inner.clone();
let middlewares = self.middleware_stack.clone();
let mut extensions = Extensions::new();
Pending {
inner: Box::pin(async move {
let next = Next::new(&inner, &middlewares);
next.run(req, &mut extensions).await
}),
}
}
}
}
/// This is a wrapper around [`reqwest::RequestBuilder`] exposing the same API. /// This is a wrapper around [`reqwest::RequestBuilder`] exposing the same API.
#[must_use = "RequestBuilder does nothing until you 'send' it"] #[must_use = "RequestBuilder does nothing until you 'send' it"]
pub struct RequestBuilder { pub struct RequestBuilder {
inner: reqwest::RequestBuilder, inner: reqwest::RequestBuilder,
client: ClientWithMiddleware, middleware_stack: Arc<[Box<dyn Middleware>]>,
initialiser_stack: Arc<[Box<dyn RequestInitialiser>]>,
extensions: Extensions, extensions: Extensions,
} }
impl RequestBuilder { impl RequestBuilder {
/// Assemble a builder starting from an existing `Client` and a `Request`.
pub fn from_parts(client: ClientWithMiddleware, request: Request) -> RequestBuilder {
let inner = reqwest::RequestBuilder::from_parts(client.inner, request);
RequestBuilder {
inner,
middleware_stack: client.middleware_stack,
initialiser_stack: client.initialiser_stack,
extensions: Extensions::new(),
}
}
/// Add a `Header` to this Request.
pub fn header<K, V>(self, key: K, value: V) -> Self pub fn header<K, V>(self, key: K, value: V) -> Self
where where
HeaderName: TryFrom<K>, HeaderName: TryFrom<K>,
@ -203,6 +345,9 @@ impl RequestBuilder {
} }
} }
/// Add a set of Headers to the existing ones on this Request.
///
/// The headers will be merged in to any already set.
pub fn headers(self, headers: HeaderMap) -> Self { pub fn headers(self, headers: HeaderMap) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.headers(headers), inner: self.inner.headers(headers),
@ -218,6 +363,20 @@ impl RequestBuilder {
} }
} }
/// Enable HTTP basic authentication.
///
/// ```rust
/// # use anyhow::Error;
///
/// # async fn run() -> Result<(), Error> {
/// let client = reqwest_middleware::ClientWithMiddleware::from(reqwest::Client::new());
/// let resp = client.delete("http://httpbin.org/delete")
/// .basic_auth("admin", Some("good password"))
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> Self
where where
U: Display, U: Display,
@ -229,6 +388,7 @@ impl RequestBuilder {
} }
} }
/// Enable HTTP bearer authentication.
pub fn bearer_auth<T>(self, token: T) -> Self pub fn bearer_auth<T>(self, token: T) -> Self
where where
T: Display, T: Display,
@ -239,6 +399,7 @@ impl RequestBuilder {
} }
} }
/// Set the request body.
pub fn body<T: Into<Body>>(self, body: T) -> Self { pub fn body<T: Into<Body>>(self, body: T) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.body(body), inner: self.inner.body(body),
@ -246,7 +407,11 @@ impl RequestBuilder {
} }
} }
#[cfg(not(target_arch = "wasm32"))] /// Enables a request timeout.
///
/// The timeout is applied from when the request starts connecting until the
/// response body has finished. It affects only this request and overrides
/// the timeout configured using `ClientBuilder::timeout()`.
pub fn timeout(self, timeout: std::time::Duration) -> Self { pub fn timeout(self, timeout: std::time::Duration) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.timeout(timeout), inner: self.inner.timeout(timeout),
@ -254,13 +419,33 @@ impl RequestBuilder {
} }
} }
pub fn multipart(self, multipart: Form) -> Self { #[cfg(feature = "multipart")]
#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
pub fn multipart(self, multipart: multipart::Form) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.multipart(multipart), inner: self.inner.multipart(multipart),
..self ..self
} }
} }
/// Modify the query string of the URL.
///
/// Modifies the URL of this request, adding the parameters provided.
/// This method appends and does not overwrite. This means that it can
/// be called multiple times and that existing query parameters are not
/// overwritten if the same key is used. The key will simply show up
/// twice in the query string.
/// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
///
/// # Note
/// This method does not support serializing a single key-value
/// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
/// as `.query(&[("key", "val")])`. It's also possible to serialize structs
/// and maps into a key-value pair.
///
/// # Errors
/// This method will fail if the object you provide cannot be serialized
/// into a query string.
pub fn query<T: Serialize + ?Sized>(self, query: &T) -> Self { pub fn query<T: Serialize + ?Sized>(self, query: &T) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.query(query), inner: self.inner.query(query),
@ -268,6 +453,33 @@ impl RequestBuilder {
} }
} }
/// Send a form body.
///
/// Sets the body to the url encoded serialization of the passed value,
/// and also sets the `Content-Type: application/x-www-form-urlencoded`
/// header.
///
/// ```rust
/// # use anyhow::Error;
/// # use std::collections::HashMap;
/// #
/// # async fn run() -> Result<(), Error> {
/// let mut params = HashMap::new();
/// params.insert("lang", "rust");
///
/// let client = reqwest_middleware::ClientWithMiddleware::from(reqwest::Client::new());
/// let res = client.post("http://httpbin.org")
/// .form(&params)
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// This method fails if the passed value cannot be serialized into
/// url encoded format
pub fn form<T: Serialize + ?Sized>(self, form: &T) -> Self { pub fn form<T: Serialize + ?Sized>(self, form: &T) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.form(form), inner: self.inner.form(form),
@ -275,6 +487,18 @@ impl RequestBuilder {
} }
} }
/// Send a JSON body.
///
/// # Optional
///
/// This requires the optional `json` feature enabled.
///
/// # Errors
///
/// Serialization can fail if `T`'s implementation of `Serialize` decides to
/// fail, or if `T` contains a map with non-string keys.
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json<T: Serialize + ?Sized>(self, json: &T) -> Self { pub fn json<T: Serialize + ?Sized>(self, json: &T) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.json(json), inner: self.inner.json(json),
@ -282,6 +506,15 @@ impl RequestBuilder {
} }
} }
/// Disable CORS on fetching the request.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request mode][mdn] will be set to 'no-cors'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
pub fn fetch_mode_no_cors(self) -> Self { pub fn fetch_mode_no_cors(self) -> Self {
RequestBuilder { RequestBuilder {
inner: self.inner.fetch_mode_no_cors(), inner: self.inner.fetch_mode_no_cors(),
@ -289,12 +522,35 @@ impl RequestBuilder {
} }
} }
/// Build a `Request`, which can be inspected, modified and executed with
/// `ClientWithMiddleware::execute()`.
pub fn build(self) -> reqwest::Result<Request> { pub fn build(self) -> reqwest::Result<Request> {
self.inner.build() self.inner.build()
} }
/// Build a `Request`, which can be inspected, modified and executed with
/// `ClientWithMiddleware::execute()`.
///
/// This is similar to [`RequestBuilder::build()`], but also returns the
/// embedded `Client`.
pub fn build_split(self) -> (ClientWithMiddleware, reqwest::Result<Request>) {
let Self {
inner,
middleware_stack,
initialiser_stack,
..
} = self;
let (inner, req) = inner.build_split();
let client = ClientWithMiddleware {
inner,
middleware_stack,
initialiser_stack,
};
(client, req)
}
/// Inserts the extension into this request builder /// Inserts the extension into this request builder
pub fn with_extension<T: Send + Sync + 'static>(mut self, extension: T) -> Self { pub fn with_extension<T: Send + Sync + Clone + 'static>(mut self, extension: T) -> Self {
self.extensions.insert(extension); self.extensions.insert(extension);
self self
} }
@ -304,14 +560,31 @@ impl RequestBuilder {
&mut self.extensions &mut self.extensions
} }
pub async fn send(self) -> Result<Response> { /// Constructs the Request and sends it to the target URL, returning a
let Self { /// future Response.
inner, ///
client, /// # Errors
mut extensions, ///
} = self; /// This method fails if there was an error while sending request,
let req = inner.build()?; /// redirect loop was detected or redirect limit was exhausted.
client.execute_with_extensions(req, &mut extensions).await ///
/// # Example
///
/// ```no_run
/// # use anyhow::Error;
/// #
/// # async fn run() -> Result<(), Error> {
/// let response = reqwest_middleware::ClientWithMiddleware::from(reqwest::Client::new())
/// .get("https://hyper.rs")
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
pub async fn send(mut self) -> Result<Response> {
let mut extensions = std::mem::take(self.extensions());
let (client, req) = self.build_split();
client.execute_with_extensions(req?, &mut extensions).await
} }
/// Attempt to clone the RequestBuilder. /// Attempt to clone the RequestBuilder.
@ -319,13 +592,26 @@ impl RequestBuilder {
/// `None` is returned if the RequestBuilder can not be cloned, /// `None` is returned if the RequestBuilder can not be cloned,
/// i.e. if the request body is a stream. /// i.e. if the request body is a stream.
/// ///
/// # Extensions /// # Examples
/// Note that extensions are not preserved through cloning. ///
/// ```
/// # use reqwest::Error;
/// #
/// # fn run() -> Result<(), Error> {
/// let client = reqwest_middleware::ClientWithMiddleware::from(reqwest::Client::new());
/// let builder = client.post("http://httpbin.org/post")
/// .body("from a &str!");
/// let clone = builder.try_clone();
/// assert!(clone.is_some());
/// # Ok(())
/// # }
/// ```
pub fn try_clone(&self) -> Option<Self> { pub fn try_clone(&self) -> Option<Self> {
self.inner.try_clone().map(|inner| RequestBuilder { self.inner.try_clone().map(|inner| RequestBuilder {
inner, inner,
client: self.client.clone(), middleware_stack: self.middleware_stack.clone(),
extensions: Extensions::new(), initialiser_stack: self.initialiser_stack.clone(),
extensions: self.extensions.clone(),
}) })
} }
} }

View file

@ -8,7 +8,7 @@
//! ``` //! ```
//! use reqwest::{Client, Request, Response}; //! use reqwest::{Client, Request, Response};
//! use reqwest_middleware::{ClientBuilder, Middleware, Next, Result}; //! use reqwest_middleware::{ClientBuilder, Middleware, Next, Result};
//! use task_local_extensions::Extensions; //! use http::Extensions;
//! //!
//! struct LoggingMiddleware; //! struct LoggingMiddleware;
//! //!

View file

@ -1,6 +1,5 @@
use http::Extensions;
use reqwest::{Client, Request, Response}; use reqwest::{Client, Request, Response};
use std::sync::Arc;
use task_local_extensions::Extensions;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@ -12,7 +11,7 @@ use crate::error::{Error, Result};
/// ``` /// ```
/// use reqwest::{Client, Request, Response}; /// use reqwest::{Client, Request, Response};
/// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result}; /// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result};
/// use task_local_extensions::Extensions; /// use http::Extensions;
/// ///
/// struct TransparentMiddleware; /// struct TransparentMiddleware;
/// ///
@ -74,7 +73,7 @@ where
#[derive(Clone)] #[derive(Clone)]
pub struct Next<'a> { pub struct Next<'a> {
client: &'a Client, client: &'a Client,
middlewares: &'a [Arc<dyn Middleware>], middlewares: &'a [Box<dyn Middleware>],
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -83,7 +82,7 @@ pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T
pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + 'a>>; pub type BoxFuture<'a, T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + 'a>>;
impl<'a> Next<'a> { impl<'a> Next<'a> {
pub(crate) fn new(client: &'a Client, middlewares: &'a [Arc<dyn Middleware>]) -> Self { pub(crate) fn new(client: &'a Client, middlewares: &'a [Box<dyn Middleware>]) -> Self {
Next { Next {
client, client,
middlewares, middlewares,
@ -97,7 +96,7 @@ impl<'a> Next<'a> {
) -> BoxFuture<'a, Result<Response>> { ) -> BoxFuture<'a, Result<Response>> {
if let Some((current, rest)) = self.middlewares.split_first() { if let Some((current, rest)) = self.middlewares.split_first() {
self.middlewares = rest; self.middlewares = rest;
Box::pin(current.handle(req, extensions, self)) current.handle(req, extensions, self)
} else { } else {
Box::pin(async move { self.client.execute(req).await.map_err(Error::from) }) Box::pin(async move { self.client.execute(req).await.map_err(Error::from) })
} }

View file

@ -32,14 +32,14 @@ where
} }
} }
/// A middleware that inserts the value into the [`Extensions`](task_local_extensions::Extensions) during the call. /// A middleware that inserts the value into the [`Extensions`](http::Extensions) during the call.
/// ///
/// This is a good way to inject extensions to middleware deeper in the stack /// This is a good way to inject extensions to middleware deeper in the stack
/// ///
/// ``` /// ```
/// use reqwest::{Client, Request, Response}; /// use reqwest::{Client, Request, Response};
/// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result, Extension}; /// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result, Extension};
/// use task_local_extensions::Extensions; /// use http::Extensions;
/// ///
/// #[derive(Clone)] /// #[derive(Clone)]
/// struct LogName(&'static str); /// struct LogName(&'static str);

View file

@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Breaking changes
- Updated `reqwest-middleware` to `0.3.0`.
## [0.3.0] - 2023-09-07 ## [0.3.0] - 2023-09-07
### Changed ### Changed
- `retry-policies` upgraded to 0.2.0 - `retry-policies` upgraded to 0.2.0

View file

@ -1,6 +1,6 @@
[package] [package]
name = "reqwest-retry" name = "reqwest-retry"
version = "0.4.0" version = "0.5.0"
authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"] authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"]
edition = "2018" edition = "2018"
description = "Retry middleware for reqwest." description = "Retry middleware for reqwest."
@ -10,20 +10,19 @@ keywords = ["reqwest", "http", "middleware", "retry"]
categories = ["web-programming::http-client"] categories = ["web-programming::http-client"]
[dependencies] [dependencies]
reqwest-middleware = { version = "0.2.0", path = "../reqwest-middleware" } reqwest-middleware = { version = "0.3.0", path = "../reqwest-middleware" }
anyhow = "1.0.0" anyhow = "1.0.0"
async-trait = "0.1.51" async-trait = "0.1.51"
chrono = { version = "0.4.19", features = ["clock"], default-features = false } chrono = { version = "0.4.19", features = ["clock"], default-features = false }
futures = "0.3.0" futures = "0.3.0"
http = "0.2.0" http = "1.0"
reqwest = { version = "0.11.0", default-features = false } reqwest = { version = "0.12.0", default-features = false }
retry-policies = "0.3.0" retry-policies = "0.3.0"
task-local-extensions = "0.1.4"
tracing = "0.1.26" tracing = "0.1.26"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
hyper = "0.14.0" hyper = "1.0"
tokio = { version = "1.6.0", features = ["time"] } tokio = { version = "1.6.0", features = ["time"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
@ -34,5 +33,5 @@ getrandom = { version = "0.2.0", features = ["js"] }
[dev-dependencies] [dev-dependencies]
paste = "1.0.0" paste = "1.0.0"
tokio = { version = "1.0.0", features = ["full"] } tokio = { version = "1.0.0", features = ["full"] }
wiremock = "0.5.0" wiremock = "0.6.0"
futures = "0.3.0" futures = "0.3.0"

View file

@ -3,10 +3,10 @@ use crate::retryable_strategy::RetryableStrategy;
use crate::{retryable::Retryable, retryable_strategy::DefaultRetryableStrategy}; use crate::{retryable::Retryable, retryable_strategy::DefaultRetryableStrategy};
use anyhow::anyhow; use anyhow::anyhow;
use chrono::Utc; use chrono::Utc;
use http::Extensions;
use reqwest::{Request, Response}; use reqwest::{Request, Response};
use reqwest_middleware::{Error, Middleware, Next, Result}; use reqwest_middleware::{Error, Middleware, Next, Result};
use retry_policies::RetryPolicy; use retry_policies::RetryPolicy;
use task_local_extensions::Extensions;
/// `RetryTransientMiddleware` offers retry logic for requests that fail in a transient manner /// `RetryTransientMiddleware` offers retry logic for requests that fail in a transient manner
/// and can be safely executed again. /// and can be safely executed again.

View file

@ -18,7 +18,7 @@ use reqwest_middleware::Error;
/// use reqwest_retry::{default_on_request_failure, policies::ExponentialBackoff, Retryable, RetryableStrategy, RetryTransientMiddleware}; /// use reqwest_retry::{default_on_request_failure, policies::ExponentialBackoff, Retryable, RetryableStrategy, RetryTransientMiddleware};
/// use reqwest::{Request, Response}; /// use reqwest::{Request, Response};
/// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result}; /// use reqwest_middleware::{ClientBuilder, Middleware, Next, Result};
/// use task_local_extensions::Extensions; /// use http::Extensions;
/// ///
/// // Log each request to show that the requests will be retried /// // Log each request to show that the requests will be retried
/// struct LoggingMiddleware; /// struct LoggingMiddleware;

View file

@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Breaking changes
- Updated `reqwest-middleware` to `0.3.0`.
- Removed support for `opentelemetry` 0.13 to 0.19
### Changed
- opentelemetry features are now additive.
## [0.4.8] - 2024-03-11 ## [0.4.8] - 2024-03-11
### Added ### Added

View file

@ -1,6 +1,6 @@
[package] [package]
name = "reqwest-tracing" name = "reqwest-tracing"
version = "0.4.8" version = "0.5.0"
authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"] authors = ["Rodrigo Gryzinski <rodrigo.gryzinski@truelayer.com>"]
edition = "2018" edition = "2018"
description = "Opentracing middleware for reqwest." description = "Opentracing middleware for reqwest."
@ -10,46 +10,25 @@ keywords = ["reqwest", "http", "middleware", "opentelemetry", "tracing"]
categories = ["web-programming::http-client"] categories = ["web-programming::http-client"]
[features] [features]
opentelemetry_0_13 = ["opentelemetry_0_13_pkg", "tracing-opentelemetry_0_12_pkg"] opentelemetry_0_20 = ["opentelemetry_0_20_pkg", "tracing-opentelemetry_0_21_pkg"]
opentelemetry_0_14 = ["opentelemetry_0_14_pkg", "tracing-opentelemetry_0_13_pkg"]
opentelemetry_0_15 = ["opentelemetry_0_15_pkg", "tracing-opentelemetry_0_14_pkg"]
opentelemetry_0_16 = ["opentelemetry_0_16_pkg", "tracing-opentelemetry_0_16_pkg"]
opentelemetry_0_17 = ["opentelemetry_0_17_pkg", "tracing-opentelemetry_0_17_pkg"]
opentelemetry_0_18 = ["opentelemetry_0_18_pkg", "tracing-opentelemetry_0_18_pkg"]
opentelemetry_0_19 = ["opentelemetry_0_19_pkg", "tracing-opentelemetry_0_19_pkg"]
opentelemetry_0_20 = ["opentelemetry_0_20_pkg", "tracing-opentelemetry_0_20_pkg"]
opentelemetry_0_21 = ["opentelemetry_0_21_pkg", "tracing-opentelemetry_0_22_pkg"] opentelemetry_0_21 = ["opentelemetry_0_21_pkg", "tracing-opentelemetry_0_22_pkg"]
opentelemetry_0_22 = ["opentelemetry_0_22_pkg", "tracing-opentelemetry_0_23_pkg"] opentelemetry_0_22 = ["opentelemetry_0_22_pkg", "tracing-opentelemetry_0_23_pkg"]
[dependencies] [dependencies]
reqwest-middleware = { version = "0.2.0", path = "../reqwest-middleware" } reqwest-middleware = { version = "0.3.0", path = "../reqwest-middleware" }
anyhow = "1.0.70" anyhow = "1.0.70"
async-trait = "0.1.51" async-trait = "0.1.51"
matchit = "0.7.0" matchit = "0.7.3"
reqwest = { version = "0.11.0", default-features = false } http = "1"
task-local-extensions = "0.1.4" reqwest = { version = "0.12.0", default-features = false }
tracing = "0.1.26" tracing = "0.1.26"
opentelemetry_0_13_pkg = { package = "opentelemetry", version = "0.13.0", optional = true }
opentelemetry_0_14_pkg = { package = "opentelemetry", version = "0.14.0", optional = true }
opentelemetry_0_15_pkg = { package = "opentelemetry", version = "0.15.0", optional = true }
opentelemetry_0_16_pkg = { package = "opentelemetry", version = "0.16.0", optional = true }
opentelemetry_0_17_pkg = { package = "opentelemetry", version = "0.17.0", optional = true }
opentelemetry_0_18_pkg = { package = "opentelemetry", version = "0.18.0", optional = true }
opentelemetry_0_19_pkg = { package = "opentelemetry", version = "0.19.0", optional = true }
opentelemetry_0_20_pkg = { package = "opentelemetry", version = "0.20.0", optional = true } opentelemetry_0_20_pkg = { package = "opentelemetry", version = "0.20.0", optional = true }
opentelemetry_0_21_pkg = { package = "opentelemetry", version = "0.21.0", optional = true } opentelemetry_0_21_pkg = { package = "opentelemetry", version = "0.21.0", optional = true }
opentelemetry_0_22_pkg = { package = "opentelemetry", version = "0.22.0", optional = true } opentelemetry_0_22_pkg = { package = "opentelemetry", version = "0.22.0", optional = true }
tracing-opentelemetry_0_12_pkg = { package = "tracing-opentelemetry", version = "0.12.0", optional = true } tracing-opentelemetry_0_21_pkg = { package = "tracing-opentelemetry", version = "0.21.0", optional = true }
tracing-opentelemetry_0_13_pkg = { package = "tracing-opentelemetry", version = "0.13.0", optional = true }
tracing-opentelemetry_0_14_pkg = { package = "tracing-opentelemetry", version = "0.14.0", optional = true }
tracing-opentelemetry_0_16_pkg = { package = "tracing-opentelemetry", version = "0.16.0", optional = true }
tracing-opentelemetry_0_17_pkg = { package = "tracing-opentelemetry", version = "0.17.0", optional = true }
tracing-opentelemetry_0_18_pkg = { package = "tracing-opentelemetry", version = "0.18.0", optional = true }
tracing-opentelemetry_0_19_pkg = { package = "tracing-opentelemetry", version = "0.19.0", optional = true }
tracing-opentelemetry_0_20_pkg = { package = "tracing-opentelemetry", version = "0.20.0", optional = true }
tracing-opentelemetry_0_22_pkg = { package = "tracing-opentelemetry", version = "0.22.0", optional = true } tracing-opentelemetry_0_22_pkg = { package = "tracing-opentelemetry", version = "0.22.0", optional = true }
tracing-opentelemetry_0_23_pkg = { package = "tracing-opentelemetry", version = "0.23.0", optional = true } tracing-opentelemetry_0_23_pkg = { package = "tracing-opentelemetry", version = "0.23.0", optional = true }
@ -58,9 +37,9 @@ getrandom = { version = "0.2.0", features = ["js"] }
[dev-dependencies] [dev-dependencies]
tokio = { version = "1.0.0", features = ["macros"] } tokio = { version = "1.0.0", features = ["macros"] }
tracing_subscriber_0_2 = { package = "tracing-subscriber", version = "0.2.0" } tracing_subscriber = { package = "tracing-subscriber", version = "0.3.0" }
tracing_subscriber_0_3 = { package = "tracing-subscriber", version = "0.3.0" } wiremock = "0.6.0"
wiremock = "0.5.0" reqwest = { version = "0.12.0", features = ["rustls-tls"]}
opentelemetry_sdk_0_21 = { package = "opentelemetry_sdk", version = "0.21.0", features = ["trace"] } opentelemetry_sdk_0_21 = { package = "opentelemetry_sdk", version = "0.21.0", features = ["trace"] }
opentelemetry_sdk_0_22 = { package = "opentelemetry_sdk", version = "0.22.0", features = ["trace"] } opentelemetry_sdk_0_22 = { package = "opentelemetry_sdk", version = "0.22.0", features = ["trace"] }

View file

@ -16,16 +16,15 @@ Attach `TracingMiddleware` to your client to automatically trace HTTP requests:
# Cargo.toml # Cargo.toml
# ... # ...
[dependencies] [dependencies]
opentelemetry = "0.18" opentelemetry = "0.22"
reqwest = "0.11" reqwest = { version = "0.12", features = ["rustls-tls"] }
reqwest-middleware = "0.1.1" reqwest-middleware = "0.3"
reqwest-retry = "0.1.1" reqwest-retry = "0.5"
reqwest-tracing = { version = "0.3.1", features = ["opentelemetry_0_18"] } reqwest-tracing = { version = "0.5", features = ["opentelemetry_0_22"] }
tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.12.0", features = ["macros", "rt-multi-thread"] }
tracing = "0.1" tracing = "0.1"
tracing-opentelemetry = "0.18" tracing-opentelemetry = "0.23"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
task-local-extensions = "0.1.4"
``` ```
```rust,skip ```rust,skip
@ -34,7 +33,7 @@ use opentelemetry::sdk::export::trace::stdout;
use reqwest::{Request, Response}; use reqwest::{Request, Response};
use reqwest_middleware::{ClientBuilder, Result}; use reqwest_middleware::{ClientBuilder, Result};
use std::time::Instant; use std::time::Instant;
use task_local_extensions::Extensions; use http::Extensions;
use tracing::Span; use tracing::Span;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Registry; use tracing_subscriber::Registry;
@ -89,12 +88,10 @@ an opentelemetry version feature:
```toml ```toml
[dependencies] [dependencies]
# ... # ...
reqwest-tracing = { version = "0.3.1", features = ["opentelemetry_0_18"] } reqwest-tracing = { version = "0.5.0", features = ["opentelemetry_0_22"] }
``` ```
Available opentelemetry features are `opentelemetry_0_22`, `opentelemetry_0_21`, `opentelemetry_0_20`, Available opentelemetry features are `opentelemetry_0_22`, `opentelemetry_0_21`, and `opentelemetry_0_20`,
`opentelemetry_0_19`, `opentelemetry_0_18`, `opentelemetry_0_17`, `opentelemetry_0_16`,
`opentelemetry_0_15`, `opentelemetry_0_14` and `opentelemetry_0_13`.
#### License #### License

View file

@ -53,7 +53,7 @@
//! Note that Opentelemetry tracks start and stop already, there is no need to have a custom builder like this. //! Note that Opentelemetry tracks start and stop already, there is no need to have a custom builder like this.
//! ```rust //! ```rust
//! use reqwest_middleware::Result; //! use reqwest_middleware::Result;
//! use task_local_extensions::Extensions; //! use http::Extensions;
//! use reqwest::{Request, Response}; //! use reqwest::{Request, Response};
//! use reqwest_middleware::ClientBuilder; //! use reqwest_middleware::ClientBuilder;
//! use reqwest_tracing::{ //! use reqwest_tracing::{
@ -84,13 +84,6 @@
mod middleware; mod middleware;
#[cfg(any( #[cfg(any(
feature = "opentelemetry_0_13",
feature = "opentelemetry_0_14",
feature = "opentelemetry_0_15",
feature = "opentelemetry_0_16",
feature = "opentelemetry_0_17",
feature = "opentelemetry_0_18",
feature = "opentelemetry_0_19",
feature = "opentelemetry_0_20", feature = "opentelemetry_0_20",
feature = "opentelemetry_0_21", feature = "opentelemetry_0_21",
feature = "opentelemetry_0_22", feature = "opentelemetry_0_22",

View file

@ -1,6 +1,6 @@
use http::Extensions;
use reqwest::{Request, Response}; use reqwest::{Request, Response};
use reqwest_middleware::{Middleware, Next, Result}; use reqwest_middleware::{Middleware, Next, Result};
use task_local_extensions::Extensions;
use tracing::Instrument; use tracing::Instrument;
use crate::{DefaultSpanBackend, ReqwestOtelSpanBackend}; use crate::{DefaultSpanBackend, ReqwestOtelSpanBackend};
@ -46,18 +46,11 @@ where
let outcome_future = async { let outcome_future = async {
#[cfg(any( #[cfg(any(
feature = "opentelemetry_0_13",
feature = "opentelemetry_0_14",
feature = "opentelemetry_0_15",
feature = "opentelemetry_0_16",
feature = "opentelemetry_0_17",
feature = "opentelemetry_0_18",
feature = "opentelemetry_0_19",
feature = "opentelemetry_0_20", feature = "opentelemetry_0_20",
feature = "opentelemetry_0_21", feature = "opentelemetry_0_21",
feature = "opentelemetry_0_22", feature = "opentelemetry_0_22",
))] ))]
let req = if !extensions.contains::<crate::DisableOtelPropagation>() { let req = if extensions.get::<crate::DisableOtelPropagation>().is_none() {
// Adds tracing headers to the given request to propagate the OpenTelemetry context to downstream revivers of the request. // Adds tracing headers to the given request to propagate the OpenTelemetry context to downstream revivers of the request.
// Spans added by downstream consumers will be part of the same trace. // Spans added by downstream consumers will be part of the same trace.
crate::otel::inject_opentelemetry_context_into_request(req) crate::otel::inject_opentelemetry_context_into_request(req)

View file

@ -3,75 +3,26 @@ use reqwest::Request;
use std::str::FromStr; use std::str::FromStr;
use tracing::Span; use tracing::Span;
#[cfg(feature = "opentelemetry_0_13")]
use opentelemetry_0_13_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_14")]
use opentelemetry_0_14_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_15")]
use opentelemetry_0_15_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_16")]
use opentelemetry_0_16_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_17")]
use opentelemetry_0_17_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_18")]
use opentelemetry_0_18_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_19")]
use opentelemetry_0_19_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_20")]
use opentelemetry_0_20_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_21")]
use opentelemetry_0_21_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_22")]
use opentelemetry_0_22_pkg as opentelemetry;
#[cfg(feature = "opentelemetry_0_13")]
pub use tracing_opentelemetry_0_12_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_14")]
pub use tracing_opentelemetry_0_13_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_15")]
pub use tracing_opentelemetry_0_14_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_16")]
pub use tracing_opentelemetry_0_16_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_17")]
pub use tracing_opentelemetry_0_17_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_18")]
pub use tracing_opentelemetry_0_18_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_19")]
pub use tracing_opentelemetry_0_19_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_20")]
pub use tracing_opentelemetry_0_20_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_21")]
pub use tracing_opentelemetry_0_22_pkg as tracing_opentelemetry;
#[cfg(feature = "opentelemetry_0_22")]
pub use tracing_opentelemetry_0_23_pkg as tracing_opentelemetry;
use opentelemetry::global;
use opentelemetry::propagation::Injector;
use tracing_opentelemetry::OpenTelemetrySpanExt;
/// Injects the given OpenTelemetry Context into a reqwest::Request headers to allow propagation downstream. /// Injects the given OpenTelemetry Context into a reqwest::Request headers to allow propagation downstream.
pub fn inject_opentelemetry_context_into_request(mut request: Request) -> Request { pub fn inject_opentelemetry_context_into_request(mut request: Request) -> Request {
let context = Span::current().context(); #[cfg(feature = "opentelemetry_0_20")]
opentelemetry_0_20_pkg::global::get_text_map_propagator(|injector| {
use tracing_opentelemetry_0_21_pkg::OpenTelemetrySpanExt;
let context = Span::current().context();
injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
});
global::get_text_map_propagator(|injector| { #[cfg(feature = "opentelemetry_0_21")]
opentelemetry_0_21_pkg::global::get_text_map_propagator(|injector| {
use tracing_opentelemetry_0_22_pkg::OpenTelemetrySpanExt;
let context = Span::current().context();
injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
});
#[cfg(feature = "opentelemetry_0_22")]
opentelemetry_0_22_pkg::global::get_text_map_propagator(|injector| {
use tracing_opentelemetry_0_23_pkg::OpenTelemetrySpanExt;
let context = Span::current().context();
injector.inject_context(&context, &mut RequestCarrier::new(&mut request)) injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
}); });
@ -94,97 +45,125 @@ impl<'a> RequestCarrier<'a> {
} }
} }
impl<'a> Injector for RequestCarrier<'a> { impl<'a> RequestCarrier<'a> {
fn set(&mut self, key: &str, value: String) { fn set_inner(&mut self, key: &str, value: String) {
let header_name = HeaderName::from_str(key).expect("Must be header name"); let header_name = HeaderName::from_str(key).expect("Must be header name");
let header_value = HeaderValue::from_str(&value).expect("Must be a header value"); let header_value = HeaderValue::from_str(&value).expect("Must be a header value");
self.request.headers_mut().insert(header_name, header_value); self.request.headers_mut().insert(header_name, header_value);
} }
} }
#[cfg(feature = "opentelemetry_0_20")]
impl<'a> opentelemetry_0_20_pkg::propagation::Injector for RequestCarrier<'a> {
fn set(&mut self, key: &str, value: String) {
self.set_inner(key, value)
}
}
#[cfg(feature = "opentelemetry_0_21")]
impl<'a> opentelemetry_0_21_pkg::propagation::Injector for RequestCarrier<'a> {
fn set(&mut self, key: &str, value: String) {
self.set_inner(key, value)
}
}
#[cfg(feature = "opentelemetry_0_22")]
impl<'a> opentelemetry_0_22_pkg::propagation::Injector for RequestCarrier<'a> {
fn set(&mut self, key: &str, value: String) {
self.set_inner(key, value)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::sync::OnceLock; use std::sync::OnceLock;
use super::*;
use crate::{DisableOtelPropagation, TracingMiddleware}; use crate::{DisableOtelPropagation, TracingMiddleware};
#[cfg(not(any(feature = "opentelemetry_0_22", feature = "opentelemetry_0_21")))]
use opentelemetry::sdk::propagation::TraceContextPropagator;
#[cfg(feature = "opentelemetry_0_21")]
use opentelemetry_sdk_0_21::propagation::TraceContextPropagator;
#[cfg(feature = "opentelemetry_0_22")]
use opentelemetry_sdk_0_22::propagation::TraceContextPropagator;
use reqwest::Response; use reqwest::Response;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, Extension}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, Extension};
use tracing::{info_span, Instrument, Level}; use tracing::{info_span, Instrument, Level};
#[cfg(any(
feature = "opentelemetry_0_13", use tracing_subscriber::{filter, layer::SubscriberExt, Registry};
feature = "opentelemetry_0_14",
feature = "opentelemetry_0_15"
))]
use tracing_subscriber_0_2::{filter, layer::SubscriberExt, Registry};
#[cfg(not(any(
feature = "opentelemetry_0_13",
feature = "opentelemetry_0_14",
feature = "opentelemetry_0_15"
)))]
use tracing_subscriber_0_3::{filter, layer::SubscriberExt, Registry};
use wiremock::{matchers::any, Mock, MockServer, ResponseTemplate}; use wiremock::{matchers::any, Mock, MockServer, ResponseTemplate};
async fn make_echo_request_in_otel_context(client: ClientWithMiddleware) -> Response { async fn make_echo_request_in_otel_context(client: ClientWithMiddleware) -> Response {
static TELEMETRY: OnceLock<()> = OnceLock::new(); static TELEMETRY: OnceLock<()> = OnceLock::new();
TELEMETRY.get_or_init(|| { TELEMETRY.get_or_init(|| {
#[cfg(all( let subscriber = Registry::default().with(
not(feature = "opentelemetry_0_20"), filter::Targets::new().with_target("reqwest_tracing::otel::test", Level::DEBUG),
not(feature = "opentelemetry_0_21"), );
not(feature = "opentelemetry_0_22")
))] #[cfg(feature = "opentelemetry_0_20")]
let tracer = opentelemetry::sdk::export::trace::stdout::new_pipeline() let subscriber = {
.with_writer(std::io::sink()) use opentelemetry_0_20_pkg::trace::TracerProvider;
.install_simple();
#[cfg(any(
feature = "opentelemetry_0_20",
feature = "opentelemetry_0_21",
feature = "opentelemetry_0_22"
))]
let tracer = {
use opentelemetry::trace::TracerProvider;
#[cfg(feature = "opentelemetry_0_20")]
use opentelemetry_stdout_0_1::SpanExporterBuilder; use opentelemetry_stdout_0_1::SpanExporterBuilder;
#[cfg(feature = "opentelemetry_0_21")]
let exporter = SpanExporterBuilder::default()
.with_writer(std::io::sink())
.build();
let provider = opentelemetry_0_20_pkg::sdk::trace::TracerProvider::builder()
.with_simple_exporter(exporter)
.build();
let tracer = provider.versioned_tracer("reqwest", None::<&str>, None::<&str>, None);
let _ = opentelemetry_0_20_pkg::global::set_tracer_provider(provider);
opentelemetry_0_20_pkg::global::set_text_map_propagator(
opentelemetry_0_20_pkg::sdk::propagation::TraceContextPropagator::new(),
);
let telemetry = tracing_opentelemetry_0_21_pkg::layer().with_tracer(tracer);
subscriber.with(telemetry)
};
#[cfg(feature = "opentelemetry_0_21")]
let subscriber = {
use opentelemetry_0_21_pkg::trace::TracerProvider;
use opentelemetry_stdout_0_2::SpanExporterBuilder; use opentelemetry_stdout_0_2::SpanExporterBuilder;
#[cfg(feature = "opentelemetry_0_22")]
let exporter = SpanExporterBuilder::default()
.with_writer(std::io::sink())
.build();
let provider = opentelemetry_sdk_0_21::trace::TracerProvider::builder()
.with_simple_exporter(exporter)
.build();
let tracer = provider.versioned_tracer("reqwest", None::<&str>, None::<&str>, None);
let _ = opentelemetry_0_21_pkg::global::set_tracer_provider(provider);
opentelemetry_0_21_pkg::global::set_text_map_propagator(
opentelemetry_sdk_0_21::propagation::TraceContextPropagator::new(),
);
let telemetry = tracing_opentelemetry_0_22_pkg::layer().with_tracer(tracer);
subscriber.with(telemetry)
};
#[cfg(feature = "opentelemetry_0_22")]
let subscriber = {
use opentelemetry_0_22_pkg::trace::TracerProvider;
use opentelemetry_stdout_0_3::SpanExporterBuilder; use opentelemetry_stdout_0_3::SpanExporterBuilder;
let exporter = SpanExporterBuilder::default() let exporter = SpanExporterBuilder::default()
.with_writer(std::io::sink()) .with_writer(std::io::sink())
.build(); .build();
#[cfg(feature = "opentelemetry_0_20")]
let provider = opentelemetry::sdk::trace::TracerProvider::builder()
.with_simple_exporter(exporter)
.build();
#[cfg(feature = "opentelemetry_0_21")]
let provider = opentelemetry_sdk_0_21::trace::TracerProvider::builder()
.with_simple_exporter(exporter)
.build();
#[cfg(feature = "opentelemetry_0_22")]
let provider = opentelemetry_sdk_0_22::trace::TracerProvider::builder() let provider = opentelemetry_sdk_0_22::trace::TracerProvider::builder()
.with_simple_exporter(exporter) .with_simple_exporter(exporter)
.build(); .build();
let tracer = provider.versioned_tracer("reqwest", None::<&str>, None::<&str>, None); let tracer = provider.versioned_tracer("reqwest", None::<&str>, None::<&str>, None);
let _ = global::set_tracer_provider(provider); let _ = opentelemetry_0_22_pkg::global::set_tracer_provider(provider);
tracer opentelemetry_0_22_pkg::global::set_text_map_propagator(
opentelemetry_sdk_0_22::propagation::TraceContextPropagator::new(),
);
let telemetry = tracing_opentelemetry_0_23_pkg::layer().with_tracer(tracer);
subscriber.with(telemetry)
}; };
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
let subscriber = Registry::default()
.with(
filter::Targets::new().with_target("reqwest_tracing::otel::test", Level::DEBUG),
)
.with(telemetry);
tracing::subscriber::set_global_default(subscriber).unwrap(); tracing::subscriber::set_global_default(subscriber).unwrap();
global::set_text_map_propagator(TraceContextPropagator::new());
}); });
// Mock server - sends all request headers back in the response // Mock server - sends all request headers back in the response

View file

@ -4,7 +4,7 @@ use matchit::Router;
use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{Request, Response, StatusCode as RequestStatusCode, Url}; use reqwest::{Request, Response, StatusCode as RequestStatusCode, Url};
use reqwest_middleware::{Error, Result}; use reqwest_middleware::{Error, Result};
use task_local_extensions::Extensions; use http::Extensions;
use tracing::{warn, Span}; use tracing::{warn, Span};
use crate::reqwest_otel_span; use crate::reqwest_otel_span;

View file

@ -31,7 +31,7 @@
/// ///
/// ```rust /// ```rust
/// use reqwest_middleware::Result; /// use reqwest_middleware::Result;
/// use task_local_extensions::Extensions; /// use http::Extensions;
/// use reqwest::{Request, Response}; /// use reqwest::{Request, Response};
/// use reqwest_tracing::{ /// use reqwest_tracing::{
/// default_on_request_end, reqwest_otel_span, ReqwestOtelSpanBackend /// default_on_request_end, reqwest_otel_span, ReqwestOtelSpanBackend