1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::models::api_config::ApiConfig;
use actix_web::{web, HttpRequest};
use awc::{Client, ClientRequest};
use myc_core::domain::dtos::route::Route;
use myc_http_tools::{
responses::GatewayError,
settings::{FORWARD_FOR_KEY, MYCELIUM_SERVICE_NAME},
};
use mycelium_base::dtos::Parent;
use std::time::Duration;
use url::Url;
/// Initialize the downstream request
///
/// This function initializes the downstream request by checking the protocol
/// permission and the source reliability.
///
#[tracing::instrument(name = "initialize_downstream_request", skip_all)]
pub(super) async fn initialize_downstream_request(
req: HttpRequest,
route: &Route,
client: web::Data<Client>,
config: web::Data<ApiConfig>,
) -> Result<ClientRequest, GatewayError> {
// ? -----------------------------------------------------------------------
// ? Extract service name from the route matching uri
//
// Extract the service name from the route matching uri. This is used to
// adjust the downstream path to include the service name. Otherwise, the
// downstream request will not be able to find the service. Also remote the
// gateway base path from the request path.
//
// ? -----------------------------------------------------------------------
let service = match route.service {
Parent::Record(ref service) => service,
Parent::Id(_) => {
tracing::error!("Service not found");
return Err(GatewayError::InternalServerError(String::from(
"Service not found",
)));
}
};
// ? -----------------------------------------------------------------------
// ? Build URI from matching route
//
// Build the registered uri from the route. This uri is the uri that the
// gateway will use to forward the request to the service. The URI can
// include wildcards and variables.
//
// Example:
//
// ```
// http://my-service:8083/public*
// ```
//
// ? -----------------------------------------------------------------------
let route_matching_uri = route.build_uri().await.map_err(|err| {
tracing::warn!("{:?}", err);
GatewayError::InternalServerError(format!("{err}"))
})?;
// ? -----------------------------------------------------------------------
// ? Parse the registered uri as a url
//
// Parse the registered uri as a url. This url is the url that the gateway
// will use to forward the request to the service.
//
// ? -----------------------------------------------------------------------
let mut target_url = Url::parse(route_matching_uri.to_string().as_str())
.map_err(|err| {
tracing::warn!("{:?}", err);
GatewayError::InternalServerError(format!("{err}"))
})?;
target_url.set_path(
req.uri()
.path()
.replace(
format!("/{name}", name = service.name.to_owned()).as_str(),
"",
)
.as_str(),
);
target_url.set_query(req.uri().query());
//
// If the proxy address exists, the downstream url should be adjusted to
// concatenate the proxy address with the service url.
//
// Example:
//
// ```
// # Original url
// http://my-service:8083/public?test=value
//
// # With proxy address
// http://localhost:8888/http://my-service:8083/public?test=value
// ```
//
let routing_url =
if let Some(proxy_address) = service.proxy_address.to_owned() {
let proxy_url = format!(
"{}/{}",
proxy_address.as_str(),
target_url.to_owned().to_string().as_str()
);
Url::parse(proxy_url.as_str()).map_err(|err| {
tracing::warn!("{:?}", err);
GatewayError::InternalServerError(format!("{err}"))
})?
} else {
target_url.to_owned()
};
// ? -----------------------------------------------------------------------
// ? Build the downstream request
//
// Build the downstream request by setting the forward for key. Request
// configs like timeout and decompression are also set. It should prevent
// the request to be consumed by the gateway before the downstream request
// is sent.
//
// ? -----------------------------------------------------------------------
let forward_for_key = if let Some(addr) = req.head().peer_addr {
Some(format!("{}", addr.ip()))
} else {
None
};
let mut downstream_request = client
.request_from(routing_url.as_str(), req.head())
.no_decompress()
.timeout(Duration::from_secs(config.gateway_timeout))
.insert_header((FORWARD_FOR_KEY, forward_for_key.unwrap_or_default()));
//
// Inform to the downstream service about the target host, protocol and
// port. Also, inform that the request is coming from the mycelium gateway.
//
downstream_request = downstream_request
.insert_header((MYCELIUM_SERVICE_NAME, format!("{}", service.name)));
Ok(downstream_request)
}