rama_tower/
lib.rs

1//! Tower support for Rama.
2//!
3//! See <https://github.com/plabayo/rama/blob/main/examples/http_rama_tower.rs>
4//! for an example on how to use this crate with opt-in tower support for rama.
5//!
6//! ## Overview
7//!
8//! Rama has the concept of a [`Context`], containing dynamic and static state,
9//! as well as runtime utilities. Tower does not have this. While it is possible
10//! that rama will move also away from the [`Context`] concept all together
11//! (see <https://github.com/plabayo/rama/issues/462>), it is there for now,
12//! which is an issue for [`tower::Layer`]-[`tower::Service`]s in specific.
13//!
14//! ```plain
15//! +-----------+         +-----------+         +-----------+
16//! | serviceA  |         | serviceB  |         | serviceC  |
17//! |           |         |           |         |           |
18//! | serve(ctx, req)     | call(req) |         | serve(ctx, req)
19//! |   |                 |    |      |         |     |
20//! |   |                 |    |      |         |     |
21//! |   +--> [ctx]        |    |      |         |     |
22//! |         |           |    |      |         |     |
23//! |         +---------->+---+------>+-------->+     |
24//! |        passes req   |  calls C, but only  |     |
25//! |        & ctx held   |  has req, so must   |     |
26//! |        somewhere    |  forward ctx        |     |
27//! +-----------+         +-----------+         +-----------+
28//! ```
29//!
30//! That is where the [`ContextSmuggler`] trait comes into play.
31//!
32//! Note that this is only a problem for [`tower::Layer`]-[`tower::Service`]s,
33//! and not for (leaf/endpoint) [`tower::Service`]s.
34//!
35//! ### [`tower::Service`] adapters
36//!
37//! Adapters to use a [`tower::Service`] as a [`rama::Service`].
38//!
39//! - either [`ServiceAdapter`]: [`Clone`] / call, most commonly used and also what we do for the layer version;
40//! - or [`SharedServiceAdapter`]: shared service across all calls, locked using an async [`Mutex`], less commonly
41//!   done, but there if you really have to.
42//!
43//! ### [`tower::Layer`] adapters
44//!
45//! Adapters to use a [`tower::Layer`] as a [`rama::Layer`]. Adapting layers
46//! is a bit more complicated than _service_ adapters because of the entire
47//! [`Context`] smuggling troubles.
48//!
49//! Next to that there is the fact that a layer produces
50//! a service which also has to be wrapped, or in our case we have to wrap 2 times.
51//! Once we have to wrap it to turn the "inner" [`rama::Service`] ("service C" in the above diagram)
52//! into a [`tower::Service`] and produce the resulting [`tower::Service`] produced by the wrapped
53//! [`tower::Layer`] also into a [`rama::Service`] ("service A" in the above diagram).
54//!
55//! To make this all happen and possible we have the following components:
56//!
57//! - [`LayerAdapter`]: use a [`tower::Layer`] as a [`rama::Layer`], which ensures that:
58//!   - the inner [`rama::Service`] passed to the adapted [`tower::Layer`] is first
59//!     wrapped by a [`TowerAdapterService`] to ensure it is a [`tower::Service`];
60//!   - the produced [`tower::Service`] by the [`tower::Layer`] is turned into a [`rama::Service`]
61//!     by wrapping it with a [`LayerAdapterService`].
62//!
63//! It is the [`LayerAdapterService`] which makes use of [`ContextSmuggler::inject_ctx`] to smuggle the [`Context`],
64//! such that the nested [`TowerAdapterService`] can pass it on using [`ContextSmuggler::try_extract_ctx`]
65//! to the "most" inner [`rama::Service`] from the POV of this (sub)stack.
66//!
67//! [`Context`]: rama_core::Context
68//! [`ContextSmuggler::inject_ctx`]: layer::ContextSmuggler::inject_ctx
69//! [`ContextSmuggler::try_extract_ctx`]: layer::ContextSmuggler::try_extract_ctx
70//! [`ContextSmuggler`]: layer::ContextSmuggler
71//! [`tower::Service`]: tower_service::Service
72//! [`tower::Layer`]: tower_layer::Layer
73//! [`rama::Service`]: rama_core::Service
74//! [`rama::Layer`]: rama_core::Layer
75//!
76//! ## Halting
77//!
78//! The adapters in this carate assumes that a [`tower::Service`] will always become ready eventually,
79//! as it will call [`poll_ready`] until ready prior to [`calling`] the [`tower::Service`].
80//! Please ensure that your [`tower::Service`] does not require a side-step to prevent such halting.
81//!
82//! [`poll_ready`]: tower_service::Service::poll_ready
83//! [`calling`]: tower_service::Service::call
84//!
85//! ## Rama
86//!
87//! Crate used by the end-user `rama` crate and `rama` crate authors alike.
88//!
89//! Learn more about `rama`:
90//!
91//! - Github: <https://github.com/plabayo/rama>
92//! - Book: <https://ramaproxy.org/book/>
93//!
94//! ## Rama Tower Origins
95//!
96//! Initially Rama was designed fully around the idea of Tower, directly. The initial design of Rama took many
97//! iterations and was R&D'd over a timespan of about a year, in between other work and parenting.
98//! We switched between [`tower`](https://crates.io/crates/tower),
99//! [`tower-async`](https://crates.io/crates/tower-async)
100//! (our own public fork of tower) and back to [`tower`](https://crates.io/crates/tower) again...
101//!
102//! It became clear however that the version of [`tower`](https://crates.io/crates/tower)
103//! at the time was incompatible (and still is) with the ideas which we wanted it to have:
104//!
105//! - We are not interested in the `poll_ready` code of tower,
106//!   and in fact it would be harmful if something is used which makes use of it
107//!   (Axum warns for it, but strictly it is possible...);
108//!   - This idea is also further elaborated in the FAQ of our tower-async fork:
109//!     <https://github.com/plabayo/tower-async?tab=readme-ov-file#faq>
110//! - We want to start to prepare for an `async`-ready future as soon as we can...
111//!
112//! All in all, it was clear after several iterations that usage of tower did more
113//! harm then it did good. What was supposed to be a stack to help us implement our vision,
114//! became a hurdle instead.
115//!
116//! This is not the fault of tower, but more a sign that it did not age well,
117//! or perhaps... it is actually a very different beast altogether.
118//!
119//! As both tower and rama are still in their pre "1.0" days, and
120//! we are still evolving together and with the rest of the wider ecosystems,
121//! it is possible that we grow closer once again.
122
123#![doc(
124    html_favicon_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/old_logo.png"
125)]
126#![doc(html_logo_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/old_logo.png")]
127#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
128#![cfg_attr(test, allow(clippy::float_cmp))]
129#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))]
130
131mod service;
132mod service_ready;
133
134pub mod layer;
135
136#[doc(inline)]
137pub use service::{ServiceAdapter, SharedServiceAdapter};
138
139#[doc(inline)]
140pub use layer::{LayerAdapter, LayerAdapterService, TowerAdapterService};
141
142pub mod core {
143    //! re-exported tower-rs crates
144
145    pub use ::tower_layer as layer;
146    #[doc(inline)]
147    pub use layer::Layer;
148
149    pub use ::tower_service as service;
150    #[doc(inline)]
151    pub use service::Service;
152}