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}