Skip to content

Commit 2615b9c

Browse files
committed
fix: Correctly proxy HEAD requests
1 parent e761bb1 commit 2615b9c

3 files changed

Lines changed: 97 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ All notable changes to this project will be documented in this file.
2626

2727
- Set connection and response timeout for Redis connections ([#85]).
2828
- Only remove queries from the persistence in case they don't send a `nextUri` and are in state `FINISHED` ([#98]).
29+
- Correctly proxy HEAD requests to `/v1/statement/executing/{queryId}/{slug}/{token}`.
30+
Previously, we would GET (instead of HEAD) the URL at the Trino cluster, which resulted in trino-lb dropping the HTTP body, causing problems ([#100]).
2931

3032
[#68]: https://github.com/stackabletech/trino-lb/pull/68
3133
[#85]: https://github.com/stackabletech/trino-lb/pull/85

trino-lb/src/cluster_group_manager.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub enum Error {
3535
#[snafu(display("Failed to contact Trino API to post query"))]
3636
ContactTrinoPostQuery { source: reqwest::Error },
3737

38+
#[snafu(display("Failed to call Trino HEAD URL {url:?}"))]
39+
CallTrinoHeadUrl { source: reqwest::Error, url: Url },
40+
3841
#[snafu(display("Failed to decode Trino API response"))]
3942
DecodeTrinoResponse { source: reqwest::Error },
4043

@@ -232,7 +235,7 @@ impl ClusterGroupManager {
232235
.get(next_uri)
233236
.headers(headers)
234237
.send()
235-
.instrument(info_span!("Send HTTP get to Trino"))
238+
.instrument(info_span!("Send HTTP GET to Trino"))
236239
.await
237240
.context(ContactTrinoPostQuerySnafu)?;
238241
let headers = response.headers();
@@ -249,6 +252,33 @@ impl ClusterGroupManager {
249252
Ok((trino_query_api_response, headers))
250253
}
251254

255+
/// Sometimes the trino-client HEADs a /executing/xxx endpoint instead of GETing it.
256+
/// We need to proxy this as a HEAD request as well.
257+
#[instrument(
258+
skip(self),
259+
fields(head_uri = %head_uri, headers = ?headers.sanitize())
260+
)]
261+
pub async fn send_head_to_trino(
262+
&self,
263+
head_uri: Url,
264+
mut headers: HeaderMap,
265+
) -> Result<HeaderMap, Error> {
266+
add_current_context_to_client_request(tracing::Span::current().context(), &mut headers);
267+
268+
let response = self
269+
.http_client
270+
.head(head_uri.clone())
271+
.headers(headers)
272+
.send()
273+
.instrument(info_span!("Send HTTP HEAD to Trino"))
274+
.await
275+
.with_context(|_| CallTrinoHeadUrlSnafu { url: head_uri })?;
276+
let headers = response.headers();
277+
let headers = filter_to_trino_headers(headers);
278+
279+
Ok(headers)
280+
}
281+
252282
#[instrument(
253283
skip(self),
254284
fields(request_headers = ?request_headers.sanitize())

trino-lb/src/http_server/v1/statement.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{
88

99
use axum::{
1010
Json,
11+
body::Body,
1112
extract::{Path, State},
1213
response::{IntoResponse, Response},
1314
};
@@ -93,6 +94,11 @@ pub enum Error {
9394
source: cluster_group_manager::Error,
9495
},
9596

97+
#[snafu(display("Failed to send HEAD request to trino"))]
98+
SendHeadToTrino {
99+
source: cluster_group_manager::Error,
100+
},
101+
96102
#[snafu(display(
97103
"Failed to decrement the query counter query trino cluster {trino_cluster:?}"
98104
))]
@@ -216,22 +222,45 @@ pub async fn get_trino_queued_statement(
216222
/// Trino cluster and currently running.
217223
///
218224
/// In case the nextUri is null, the query will be stopped and removed from trino-lb.
225+
///
226+
/// Please note that sometimes the Trino clients also HEAD this endpoint, in which case we need some
227+
/// special handling.
219228
#[instrument(
220-
name = "GET /v1/statement/executing/{queryId}/{slug}/{token}",
229+
name = "GET (or HEAD) /v1/statement/executing/{queryId}/{slug}/{token}",
221230
skip(state, headers)
222231
)]
223232
pub async fn get_trino_executing_statement(
233+
method: http::Method,
224234
headers: HeaderMap,
225235
State(state): State<Arc<AppState>>,
226236
Path((query_id, _, _)): Path<(TrinoQueryId, String, u64)>,
227237
uri: Uri,
228-
) -> Result<(HeaderMap, Json<TrinoQueryApiResponse>), Error> {
238+
) -> Result<Response, Error> {
239+
if method == http::Method::HEAD {
240+
state.metrics.http_counter.add(
241+
1,
242+
&[KeyValue::new("resource", "head_trino_executing_statement")],
243+
);
244+
245+
let headers = handle_head_request_to_trino(&state, headers, query_id, uri.path()).await?;
246+
247+
// For a HEAD request we don't need (nor can) return a body.
248+
let mut response = Response::new(Body::empty());
249+
*response.status_mut() = StatusCode::OK;
250+
*response.headers_mut() = headers;
251+
return Ok(response);
252+
}
229253
state.metrics.http_counter.add(
230254
1,
231255
&[KeyValue::new("resource", "get_trino_executing_statement")],
232256
);
233257

234-
handle_query_running_on_trino(&state, headers, query_id, uri.path()).await
258+
let (headers, body) =
259+
handle_query_running_on_trino(&state, headers, query_id, uri.path()).await?;
260+
261+
let mut response = body.into_response();
262+
*response.headers_mut() = headers;
263+
Ok(response)
235264
}
236265

237266
#[instrument(skip(
@@ -495,6 +524,38 @@ async fn handle_query_running_on_trino(
495524
Ok((trino_headers, Json(trino_query_api_response)))
496525
}
497526

527+
async fn handle_head_request_to_trino(
528+
state: &Arc<AppState>,
529+
headers: HeaderMap,
530+
query_id: TrinoQueryId,
531+
requested_path: &str,
532+
) -> Result<HeaderMap, Error> {
533+
let query =
534+
state
535+
.persistence
536+
.load_query(&query_id)
537+
.await
538+
.context(LoadQueryFromPersistenceSnafu {
539+
query_id: query_id.clone(),
540+
})?;
541+
542+
let headers = state
543+
.cluster_group_manager
544+
.send_head_to_trino(
545+
query.trino_endpoint.join(requested_path).context(
546+
JoinRequestPathToTrinoEndpointSnafu {
547+
requested_path,
548+
trino_endpoint: query.trino_endpoint.clone(),
549+
},
550+
)?,
551+
headers,
552+
)
553+
.await
554+
.context(SendHeadToTrinoSnafu)?;
555+
556+
Ok(headers)
557+
}
558+
498559
/// This function get's asked to delete the queued query.
499560
/// IMPORTANT: It does not check that the user is authorized to delete the queued query. Instead we assume that the
500561
/// random part of the queryId trino-lb generates provides sufficient protection, as other clients can not extract

0 commit comments

Comments
 (0)