rustlavel_discovery/
server.rs1use std::sync::Arc;
12
13use rustlavel_core::Json;
14use rustlavel_http::{BoxFuture, Plugin, Request, Response, Setup, Status};
15
16use crate::eureka;
17use crate::instance::Status as InstanceStatus;
18use crate::registry::{Mode, Registry};
19use crate::replicate::{Change, Peers, REPLICATION_HEADER, is_replication};
20
21pub struct RegistryServer {
30 registry: Registry,
31 peers: Peers,
32 dashboard: Option<String>,
34}
35
36impl Default for RegistryServer {
37 fn default() -> Self {
38 RegistryServer::new()
39 }
40}
41
42impl RegistryServer {
43 pub fn new() -> RegistryServer {
44 RegistryServer {
45 registry: Registry::new(),
46 peers: Peers::new(Vec::new()),
47 dashboard: Some("/discovery".to_string()),
48 }
49 }
50
51 pub fn using(mut self, registry: Registry) -> RegistryServer {
54 self.registry = registry;
55 self
56 }
57
58 pub fn registry(&self) -> &Registry {
59 &self.registry
60 }
61
62 pub fn peers(mut self, peers: Peers) -> RegistryServer {
65 self.peers = peers;
66 self
67 }
68
69 pub fn dashboard(mut self, at: Option<&str>) -> RegistryServer {
78 self.dashboard = at.map(str::to_string);
79 self
80 }
81}
82
83fn arrived_from_a_client(request: &Request) -> bool {
85 !is_replication(request.query("isReplication"), request.header(REPLICATION_HEADER))
86}
87
88impl Plugin for RegistryServer {
89 fn name(&self) -> &'static str {
90 "discovery"
91 }
92
93 fn register(self: Box<Self>, setup: &mut Setup<'_>) {
94 let this = Arc::new(*self);
95
96 setup.state(this.registry.clone());
100
101 let owner = Arc::clone(&this);
102 setup.router.post("/eureka/apps/{app}", move |mut request: Request| {
103 let owner = Arc::clone(&owner);
104 Box::pin(async move {
105 let Some(body) = request.json().cloned() else {
106 return Response::new(Status::BAD_REQUEST)
107 .with_json(problem("a registration must be a JSON body"));
108 };
109 let Some(instance) = eureka::read_instance(&body) else {
110 return Response::new(Status::BAD_REQUEST)
111 .with_json(problem("a registration needs an app and a host"));
112 };
113
114 owner.registry.register(instance.clone());
115 if arrived_from_a_client(&request) {
116 owner.peers.spread(Change::Registered(instance));
117 }
118
119 Response::no_content()
122 }) as BoxFuture<Response>
123 });
124
125 let owner = Arc::clone(&this);
126 setup.router.put("/eureka/apps/{app}/{id}", move |request: Request| {
127 let owner = Arc::clone(&owner);
128 Box::pin(async move {
129 let (app, id) = named(&request);
130
131 if !owner.registry.renew(&app, &id) {
132 return Response::not_found();
136 }
137
138 if arrived_from_a_client(&request) {
139 owner.peers.spread(Change::Renewed { service: app, id });
140 }
141 Response::ok()
142 }) as BoxFuture<Response>
143 });
144
145 let owner = Arc::clone(&this);
146 setup.router.put("/eureka/apps/{app}/{id}/status", move |request: Request| {
147 let owner = Arc::clone(&owner);
148 Box::pin(async move {
149 let (app, id) = named(&request);
150 let status = InstanceStatus::parse(request.query("value").unwrap_or(""));
153
154 if !owner.registry.set_status(&app, &id, status) {
155 return Response::not_found();
156 }
157
158 if arrived_from_a_client(&request) {
159 owner.peers.spread(Change::StatusChanged { service: app, id, status });
160 }
161 Response::ok()
162 }) as BoxFuture<Response>
163 });
164
165 let owner = Arc::clone(&this);
166 setup.router.delete("/eureka/apps/{app}/{id}", move |request: Request| {
167 let owner = Arc::clone(&owner);
168 Box::pin(async move {
169 let (app, id) = named(&request);
170 let removed = owner.registry.cancel(&app, &id);
171
172 if arrived_from_a_client(&request) {
177 owner.peers.spread(Change::Cancelled { service: app, id });
178 }
179
180 match removed {
181 true => Response::ok(),
182 false => Response::not_found(),
183 }
184 }) as BoxFuture<Response>
185 });
186
187 let owner = Arc::clone(&this);
188 setup.router.get("/eureka/apps", move |_request: Request| {
189 let owner = Arc::clone(&owner);
190 Box::pin(async move {
191 Response::json(eureka::applications_json(&owner.registry.all()))
195 }) as BoxFuture<Response>
196 });
197
198 let owner = Arc::clone(&this);
199 setup.router.get("/eureka/apps/{app}", move |request: Request| {
200 let owner = Arc::clone(&owner);
201 Box::pin(async move {
202 let app = request.param("app").unwrap_or("").to_ascii_uppercase();
203 let (instances, mode) = owner.registry.alive(&app);
204
205 if instances.is_empty() {
206 return Response::not_found();
207 }
208
209 let mut response = Response::json(eureka::application_json(&app, &instances));
210 if mode == Mode::SelfPreservation {
211 response = response.with_header("x-discovery-self-preservation", "true");
215 }
216 response
217 }) as BoxFuture<Response>
218 });
219
220 let owner = Arc::clone(&this);
223 setup.router.get("/discovery/registry.json", move |_request: Request| {
224 let owner = Arc::clone(&owner);
225 Box::pin(async move { Response::json(crate::ui::registry_json(&owner.registry)) })
226 as BoxFuture<Response>
227 });
228
229 if let Some(at) = this.dashboard.clone() {
230 let owner = Arc::clone(&this);
231 setup.router.get(&at, move |_request: Request| {
232 let owner = Arc::clone(&owner);
233 Box::pin(async move { Response::html(crate::ui::page(&owner.registry)) })
234 as BoxFuture<Response>
235 });
236 }
237 }
238}
239
240fn named(request: &Request) -> (String, String) {
241 (
242 request.param("app").unwrap_or("").to_ascii_uppercase(),
243 request.param("id").unwrap_or("").to_string(),
244 )
245}
246
247fn problem(message: &str) -> Json {
248 Json::object([("message", Json::from(message))])
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::instance::Instance;
255
256 #[test]
257 fn the_dashboard_can_be_taken_away() {
258 assert_eq!(RegistryServer::new().dashboard.as_deref(), Some("/discovery"));
259 assert!(RegistryServer::new().dashboard(None).dashboard.is_none());
260 assert_eq!(
261 RegistryServer::new().dashboard(Some("/ops/discovery")).dashboard.as_deref(),
262 Some("/ops/discovery")
263 );
264 }
265
266 #[test]
269 fn a_registry_can_be_shared_with_the_rest_of_the_application() {
270 let registry = Registry::new();
271 let server = RegistryServer::new().using(registry.clone());
272
273 registry.register(Instance::new("orders", "10.0.0.1", 8080));
274 assert_eq!(server.registry().all().len(), 1);
275 }
276}