Perform an HTTP request. Exposed to modules via the `ProcedureContext`. It's very important that the error returned from this function not contain any potentially sensitive data from `request`, such as the query parameters or header values. This way, it's safe to log the errors (either for us to do so, or for module code to do so), and less dangerous to send them to the calling client of a proced
(
&mut self,
request: st_http::Request,
body: bytes::Bytes,
)
| 853 | /// This way, it's safe to log the errors (either for us to do so, or for module code to do so), |
| 854 | /// and less dangerous to send them to the calling client of a procedure. |
| 855 | pub fn http_request( |
| 856 | &mut self, |
| 857 | request: st_http::Request, |
| 858 | body: bytes::Bytes, |
| 859 | ) -> Result<impl Future<Output = Result<(st_http::Response, bytes::Bytes), NodesError>> + use<>, NodesError> { |
| 860 | if self.in_tx() { |
| 861 | // If we're holding a transaction open, refuse to perform this blocking operation. |
| 862 | return Err(NodesError::WouldBlockTransaction(super::AbiCall::ProcedureHttpRequest)); |
| 863 | } |
| 864 | |
| 865 | // Record in metrics that we're starting an HTTP request. |
| 866 | DB_METRICS |
| 867 | .procedure_num_http_requests |
| 868 | .with_label_values(self.database_identity()) |
| 869 | .inc(); |
| 870 | DB_METRICS |
| 871 | .procedure_http_request_size_bytes |
| 872 | .with_label_values(self.database_identity()) |
| 873 | .inc_by((request.size_in_bytes() + body.len()) as _); |
| 874 | // Make a guard for the `in_progress` metric that will be decremented on exit. |
| 875 | let _in_progress_metric = DB_METRICS |
| 876 | .procedure_num_in_progress_http_requests |
| 877 | .with_label_values(self.database_identity()) |
| 878 | .inc_scope(); |
| 879 | |
| 880 | /// Strip the query part out of the URL in `err`, as query parameters may be sensitive |
| 881 | /// and we'd like it to be safe to directly log errors from this method. |
| 882 | fn strip_query_params_from_reqwest_error(mut err: reqwest::Error) -> reqwest::Error { |
| 883 | if let Some(url) = err.url_mut() { |
| 884 | // `set_query` of `None` clears the query part. |
| 885 | url.set_query(None); |
| 886 | } |
| 887 | err |
| 888 | } |
| 889 | |
| 890 | fn http_error<E: std::error::Error>(err: E) -> NodesError { |
| 891 | // Include the full error chain, not just the top-level message. |
| 892 | // `reqwest::Error` wraps underlying causes (DNS failure, connection refused, |
| 893 | // timeout, TLS errors, etc.) which are essential for debugging. |
| 894 | use std::fmt::Write; |
| 895 | let mut message = err.to_string(); |
| 896 | let mut source = err.source(); |
| 897 | while let Some(cause) = source { |
| 898 | write!(message, ": {cause}").unwrap(); |
| 899 | source = cause.source(); |
| 900 | } |
| 901 | NodesError::HttpError(message) |
| 902 | } |
| 903 | |
| 904 | // Then convert the request into an `http::Request`, a semi-standard "lingua franca" type in the Rust ecosystem, |
| 905 | // and map its body into a type `reqwest` will like. |
| 906 | // |
| 907 | // See comments on and in `convert_http_request` for justification that there's no sensitive info in this error. |
| 908 | let (request, timeout) = convert_http_request(request).map_err(http_error)?; |
| 909 | |
| 910 | let request = http::Request::from_parts(request, body); |
| 911 | |
| 912 | let mut reqwest: reqwest::Request = request |
no test coverage detected