@@ -24,6 +24,7 @@ super::function_definition_macro::sqlpage_functions! {
2424 exec( ( & RequestInfo ) , program_name: Cow <str >, args: Vec <Cow <str >>) ;
2525
2626 fetch( ( & RequestInfo ) , http_request: SqlPageFunctionParam <super :: http_fetch_request:: HttpFetchRequest <' _>>) ;
27+ fetch_with_meta( ( & RequestInfo ) , http_request: SqlPageFunctionParam <super :: http_fetch_request:: HttpFetchRequest <' _>>) ;
2728
2829 hash_password( password: Option <String >) ;
2930 header( ( & RequestInfo ) , name: Cow <str >) ;
@@ -135,53 +136,70 @@ async fn exec<'a>(
135136 Ok ( String :: from_utf8_lossy ( & res. stdout ) . into_owned ( ) )
136137}
137138
138- async fn fetch (
139- request : & RequestInfo ,
140- http_request : super :: http_fetch_request:: HttpFetchRequest < ' _ > ,
141- ) -> anyhow:: Result < String > {
139+ fn build_request < ' a > (
140+ client : & ' a awc :: Client ,
141+ http_request : & ' a super :: http_fetch_request:: HttpFetchRequest < ' _ > ,
142+ ) -> anyhow:: Result < awc :: ClientRequest > {
142143 use awc:: http:: Method ;
143- let client = make_http_client ( & request. app_state . config )
144- . with_context ( || "Unable to create an HTTP client" ) ?;
145-
146- let method = if let Some ( method) = http_request. method {
147- Method :: from_str ( & method) . with_context ( || format ! ( "Invalid HTTP method: {method}" ) ) ?
144+ let method = if let Some ( method) = & http_request. method {
145+ Method :: from_str ( method) . with_context ( || format ! ( "Invalid HTTP method: {method}" ) ) ?
148146 } else {
149147 Method :: GET
150148 } ;
151149 let mut req = client. request ( method, http_request. url . as_ref ( ) ) ;
152150 if let Some ( timeout) = http_request. timeout_ms {
153151 req = req. timeout ( core:: time:: Duration :: from_millis ( timeout) ) ;
154152 }
155- for ( k, v) in http_request. headers {
153+ for ( k, v) in & http_request. headers {
156154 req = req. insert_header ( ( k. as_ref ( ) , v. as_ref ( ) ) ) ;
157155 }
158- if let Some ( username) = http_request. username {
159- let password = http_request. password . unwrap_or_default ( ) ;
156+ if let Some ( username) = & http_request. username {
157+ let password = http_request. password . as_deref ( ) . unwrap_or_default ( ) ;
160158 req = req. basic_auth ( username, password) ;
161159 }
160+ Ok ( req)
161+ }
162+
163+ fn prepare_request_body (
164+ body : & serde_json:: value:: RawValue ,
165+ mut req : awc:: ClientRequest ,
166+ ) -> anyhow:: Result < ( String , awc:: ClientRequest ) > {
167+ let val = body. get ( ) ;
168+ let body_str = if val. starts_with ( '"' ) {
169+ serde_json:: from_str :: < ' _ , String > ( val) . with_context ( || {
170+ format ! ( "Invalid JSON string in the body of the HTTP request: {val}" )
171+ } ) ?
172+ } else {
173+ req = req. content_type ( "application/json" ) ;
174+ val. to_owned ( )
175+ } ;
176+ Ok ( ( body_str, req) )
177+ }
178+
179+ async fn fetch (
180+ request : & RequestInfo ,
181+ http_request : super :: http_fetch_request:: HttpFetchRequest < ' _ > ,
182+ ) -> anyhow:: Result < String > {
183+ let client = make_http_client ( & request. app_state . config )
184+ . with_context ( || "Unable to create an HTTP client" ) ?;
185+ let req = build_request ( & client, & http_request) ?;
186+
162187 log:: info!( "Fetching {}" , http_request. url) ;
163- let mut response = if let Some ( body) = http_request. body {
164- let val = body. get ( ) ;
165- // The body can be either json, or a string representing a raw body
166- let body = if val. starts_with ( '"' ) {
167- serde_json:: from_str :: < ' _ , String > ( val) . with_context ( || {
168- format ! ( "Invalid JSON string in the body of the HTTP request: {val}" )
169- } ) ?
170- } else {
171- req = req. content_type ( "application/json" ) ;
172- val. to_owned ( )
173- } ;
188+ let mut response = if let Some ( body) = & http_request. body {
189+ let ( body, req) = prepare_request_body ( body, req) ?;
174190 req. send_body ( body)
175191 } else {
176192 req. send ( )
177193 }
178194 . await
179195 . map_err ( |e| anyhow ! ( "Unable to fetch {}: {e}" , http_request. url) ) ?;
196+
180197 log:: debug!(
181198 "Finished fetching {}. Status: {}" ,
182199 http_request. url,
183200 response. status( )
184201 ) ;
202+
185203 let body = response
186204 . body ( )
187205 . await
@@ -199,6 +217,76 @@ async fn fetch(
199217 Ok ( response_str)
200218}
201219
220+ async fn fetch_with_meta (
221+ request : & RequestInfo ,
222+ http_request : super :: http_fetch_request:: HttpFetchRequest < ' _ > ,
223+ ) -> anyhow:: Result < String > {
224+ let client = make_http_client ( & request. app_state . config )
225+ . with_context ( || "Unable to create an HTTP client" ) ?;
226+ let req = build_request ( & client, & http_request) ?;
227+
228+ log:: info!( "Fetching {} with metadata" , http_request. url) ;
229+ let response_result = if let Some ( body) = & http_request. body {
230+ let ( body, req) = prepare_request_body ( body, req) ?;
231+ req. send_body ( body) . await
232+ } else {
233+ req. send ( ) . await
234+ } ;
235+
236+ let mut response_info = serde_json:: Map :: new ( ) ;
237+ match response_result {
238+ Ok ( mut response) => {
239+ response_info. insert ( "status" . to_string ( ) , response. status ( ) . as_u16 ( ) . into ( ) ) ;
240+
241+ let mut headers = serde_json:: Map :: new ( ) ;
242+ for ( name, value) in response. headers ( ) . iter ( ) {
243+ if let Ok ( value_str) = value. to_str ( ) {
244+ headers. insert ( name. to_string ( ) , value_str. into ( ) ) ;
245+ }
246+ }
247+ response_info. insert ( "headers" . to_string ( ) , headers. clone ( ) . into ( ) ) ;
248+
249+ match response. body ( ) . await {
250+ Ok ( body) => {
251+ let body_bytes = body. to_vec ( ) ;
252+ let content_type = headers. get ( "content-type" )
253+ . and_then ( |v| v. as_str ( ) )
254+ . unwrap_or_default ( ) ;
255+
256+ let body_value = if content_type. contains ( "application/json" ) {
257+ match serde_json:: from_slice ( & body_bytes) {
258+ Ok ( json_value) => json_value,
259+ Err ( _) => serde_json:: Value :: String ( String :: from_utf8_lossy ( & body_bytes) . into_owned ( ) ) ,
260+ }
261+ } else {
262+ match String :: from_utf8 ( body_bytes. clone ( ) ) {
263+ Ok ( text) => serde_json:: Value :: String ( text) ,
264+ Err ( _) => {
265+ let mut base64_string = String :: new ( ) ;
266+ base64:: Engine :: encode_string (
267+ & base64:: engine:: general_purpose:: STANDARD ,
268+ & body_bytes,
269+ & mut base64_string,
270+ ) ;
271+ serde_json:: Value :: String ( base64_string)
272+ }
273+ }
274+ } ;
275+ response_info. insert ( "body" . to_string ( ) , body_value) ;
276+ }
277+ Err ( e) => {
278+ response_info. insert ( "error" . to_string ( ) , format ! ( "Failed to read response body: {}" , e) . into ( ) ) ;
279+ }
280+ }
281+ }
282+ Err ( e) => {
283+ response_info. insert ( "error" . to_string ( ) , format ! ( "Request failed: {}" , e) . into ( ) ) ;
284+ }
285+ }
286+
287+ Ok ( serde_json:: to_string ( & response_info) ?)
288+ }
289+
202290static NATIVE_CERTS : OnceLock < anyhow:: Result < rustls:: RootCertStore > > = OnceLock :: new ( ) ;
203291
204292fn make_http_client ( config : & crate :: app_config:: AppConfig ) -> anyhow:: Result < awc:: Client > {
0 commit comments