Skip to content

Commit

Permalink
Merge pull request #16 from dfinity/rjb/content-type-headers
Browse files Browse the repository at this point in the history
fix: add headers to proxied API endpoints
  • Loading branch information
r-birkner authored Aug 9, 2024
2 parents 4ae9d7c + 738ed3a commit 45e7d3e
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 49 deletions.
32 changes: 32 additions & 0 deletions src/http/headers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Clippy complains that these are interior-mutable.
// We don't mutate them, so silence it.
// https://rust-lang.github.io/rust-clippy/master/index.html#/declare_interior_mutable_const
#![allow(clippy::declare_interior_mutable_const)]
#![allow(clippy::borrow_interior_mutable_const)]

use http::header::{HeaderName, HeaderValue};

pub const HEADER_IC_CACHE_STATUS: HeaderName = HeaderName::from_static("x-ic-cache-status");
pub const HEADER_IC_CACHE_BYPASS_REASON: HeaderName =
HeaderName::from_static("x-ic-cache-bypass-reason");
pub const HEADER_IC_SUBNET_ID: HeaderName = HeaderName::from_static("x-ic-subnet-id");
pub const HEADER_IC_NODE_ID: HeaderName = HeaderName::from_static("x-ic-node-id");
pub const HEADER_IC_SUBNET_TYPE: HeaderName = HeaderName::from_static("x-ic-subnet-type");
pub const HEADER_IC_CANISTER_ID_CBOR: HeaderName = HeaderName::from_static("x-ic-canister-id-cbor");
pub const HEADER_IC_METHOD_NAME: HeaderName = HeaderName::from_static("x-ic-method-name");
pub const HEADER_IC_SENDER: HeaderName = HeaderName::from_static("x-ic-sender");
pub const HEADER_IC_RETRIES: HeaderName = HeaderName::from_static("x-ic-retries");
pub const HEADER_IC_ERROR_CAUSE: HeaderName = HeaderName::from_static("x-ic-error-cause");
pub const HEADER_IC_REQUEST_TYPE: HeaderName = HeaderName::from_static("x-ic-request-type");
pub const HEADER_IC_CANISTER_ID: HeaderName = HeaderName::from_static("x-ic-canister-id");
pub const HEADER_IC_COUNTRY_CODE: HeaderName = HeaderName::from_static("x-ic-country-code");
pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
pub const X_REQUESTED_WITH: HeaderName = HeaderName::from_static("x-requested-with");

pub const CONTENT_TYPE_CBOR: HeaderValue = HeaderValue::from_static("application/cbor");
pub const CONTENT_TYPE_OCTET_STREAM: HeaderValue =
HeaderValue::from_static("application/octet-stream");
pub const HEADER_HSTS: HeaderValue =
HeaderValue::from_static("max-age=31536000; includeSubDomains");
pub const X_CONTENT_TYPE_OPTIONS_NO_SNIFF: HeaderValue = HeaderValue::from_static("nosniff");
pub const X_FRAME_OPTIONS_DENY: HeaderValue = HeaderValue::from_static("DENY");
1 change: 1 addition & 0 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod client;
pub mod dns;
pub mod headers;
pub mod server;

use std::{
Expand Down
5 changes: 1 addition & 4 deletions src/metrics/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use tracing::warn;
use url::Url;
use vector_lib::{codecs::encoding::NativeSerializer, config::LogNamespace, event::Event};

use crate::{cli, http};

#[allow(clippy::declare_interior_mutable_const)]
const CONTENT_TYPE_OCTET_STREAM: HeaderValue = HeaderValue::from_static("application/octet-stream");
use crate::{cli, http, http::headers::CONTENT_TYPE_OCTET_STREAM};

/// Encodes Vector events into a native format with length delimiting
#[derive(Clone)]
Expand Down
21 changes: 11 additions & 10 deletions src/routing/ic/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ use http::HeaderValue;
use http_body_util::{BodyExt, LengthLimitError, Limited};
use ic_http_gateway::{CanisterRequest, HttpGatewayClient, HttpGatewayRequestArgs};

use crate::routing::{
error_cause::ErrorCause,
ic::{
transport::{PassHeaders, PASS_HEADERS},
IcResponseStatus,
use crate::{
http::headers::X_REQUEST_ID,
routing::{
error_cause::ErrorCause,
ic::{
transport::{PassHeaders, PASS_HEADERS},
IcResponseStatus,
},
middleware::request_id::RequestId,
CanisterId, RequestCtx,
},
middleware::{self, request_id::RequestId},
CanisterId, RequestCtx,
};

use super::BNResponseMetadata;
Expand Down Expand Up @@ -70,9 +73,7 @@ pub async fn handler(
let hdr =
HeaderValue::from_maybe_shared(Bytes::from(request_id.to_string())).unwrap();

x.borrow_mut()
.headers_out
.insert(middleware::X_REQUEST_ID, hdr)
x.borrow_mut().headers_out.insert(X_REQUEST_ID, hdr)
});

// Execute the request
Expand Down
28 changes: 11 additions & 17 deletions src/routing/ic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,17 @@ use http_body_util::Either;
use ic_agent::agent::http_transport::route_provider::RouteProvider;
use ic_http_gateway::{HttpGatewayClient, HttpGatewayResponse, HttpGatewayResponseMetadata};

use crate::{http::Client as HttpClient, Cli};

pub const HEADER_IC_CACHE_STATUS: HeaderName = HeaderName::from_static("x-ic-cache-status");
pub const HEADER_IC_CACHE_BYPASS_REASON: HeaderName =
HeaderName::from_static("x-ic-cache-bypass-reason");
pub const HEADER_IC_SUBNET_ID: HeaderName = HeaderName::from_static("x-ic-subnet-id");
pub const HEADER_IC_NODE_ID: HeaderName = HeaderName::from_static("x-ic-node-id");
pub const HEADER_IC_SUBNET_TYPE: HeaderName = HeaderName::from_static("x-ic-subnet-type");
pub const HEADER_IC_CANISTER_ID_CBOR: HeaderName = HeaderName::from_static("x-ic-canister-id-cbor");
pub const HEADER_IC_METHOD_NAME: HeaderName = HeaderName::from_static("x-ic-method-name");
pub const HEADER_IC_SENDER: HeaderName = HeaderName::from_static("x-ic-sender");
pub const HEADER_IC_RETRIES: HeaderName = HeaderName::from_static("x-ic-retries");
pub const HEADER_IC_ERROR_CAUSE: HeaderName = HeaderName::from_static("x-ic-error-cause");
pub const HEADER_IC_REQUEST_TYPE: HeaderName = HeaderName::from_static("x-ic-request-type");
pub const HEADER_IC_CANISTER_ID: HeaderName = HeaderName::from_static("x-ic-canister-id");
pub const HEADER_IC_COUNTRY_CODE: http::HeaderName =
http::HeaderName::from_static("x-ic-country-code");
use crate::{
http::{
headers::{
HEADER_IC_CACHE_BYPASS_REASON, HEADER_IC_CACHE_STATUS, HEADER_IC_CANISTER_ID_CBOR,
HEADER_IC_ERROR_CAUSE, HEADER_IC_METHOD_NAME, HEADER_IC_NODE_ID, HEADER_IC_RETRIES,
HEADER_IC_SENDER, HEADER_IC_SUBNET_ID, HEADER_IC_SUBNET_TYPE,
},
Client as HttpClient,
},
Cli,
};

/// Metadata about the request by a Boundary Node (ic-boundary)
#[derive(Clone)]
Expand Down
3 changes: 1 addition & 2 deletions src/routing/ic/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ use reqwest::{
use tokio::task_local;
use url::Url;

use crate::http::Client as HttpClient;
use crate::http::{headers::CONTENT_TYPE_CBOR, Client as HttpClient};

type AgentFuture<'a, V> = Pin<Box<dyn Future<Output = Result<V, AgentError>> + Send + 'a>>;

const CONTENT_TYPE_CBOR: HeaderValue = HeaderValue::from_static("application/cbor");
const MAX_RESPONSE_SIZE: usize = 2 * 1_048_576;

pub struct PassHeaders {
Expand Down
3 changes: 1 addition & 2 deletions src/routing/middleware/cors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ use http::{
};
use tower_http::cors::{Any, CorsLayer};

use super::{X_REQUESTED_WITH, X_REQUEST_ID};
use crate::routing::ic::HEADER_IC_CANISTER_ID;
use crate::http::headers::{HEADER_IC_CANISTER_ID, X_REQUESTED_WITH, X_REQUEST_ID};

const MINUTE: Duration = Duration::from_secs(60);

Expand Down
15 changes: 9 additions & 6 deletions src/routing/middleware/headers.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#![allow(clippy::declare_interior_mutable_const)]
#![allow(clippy::borrow_interior_mutable_const)]

use axum::{extract::Request, middleware::Next, response::Response};
use bytes::Bytes;
use http::header::{HeaderName, HeaderValue, STRICT_TRANSPORT_SECURITY};

use crate::routing::{ic::*, CanisterId};

const HEADER_HSTS: HeaderValue = HeaderValue::from_static("max-age=31536000; includeSubDomains");
use crate::{
http::headers::{
HEADER_HSTS, HEADER_IC_CACHE_BYPASS_REASON, HEADER_IC_CACHE_STATUS, HEADER_IC_CANISTER_ID,
HEADER_IC_CANISTER_ID_CBOR, HEADER_IC_COUNTRY_CODE, HEADER_IC_ERROR_CAUSE,
HEADER_IC_METHOD_NAME, HEADER_IC_NODE_ID, HEADER_IC_REQUEST_TYPE, HEADER_IC_RETRIES,
HEADER_IC_SENDER, HEADER_IC_SUBNET_ID, HEADER_IC_SUBNET_TYPE,
},
routing::CanisterId,
};

const HEADERS_REMOVE: [HeaderName; 12] = [
HEADER_IC_CACHE_BYPASS_REASON,
Expand Down
5 changes: 0 additions & 5 deletions src/routing/middleware/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ use std::str::FromStr;

use axum::extract::Request;
use fqdn::FQDN;
use http::HeaderName;

// Headers
pub const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
pub const X_REQUESTED_WITH: HeaderName = HeaderName::from_static("x-requested-with");

// Attempts to extract host from HTTP2 "authority" pseudo-header or from HTTP/1.1 "Host" header
fn extract_authority(request: &Request) -> Option<FQDN> {
Expand Down
2 changes: 1 addition & 1 deletion src/routing/middleware/request_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bytes::Bytes;
use http::header::HeaderValue;
use uuid::Uuid;

use super::X_REQUEST_ID;
use crate::http::headers::X_REQUEST_ID;

#[derive(Clone, Copy)]
pub struct RequestId(pub Uuid);
Expand Down
22 changes: 20 additions & 2 deletions src/routing/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ use axum::{
};
use candid::Principal;
use derive_new::new;
use http::header::{CONTENT_TYPE, X_CONTENT_TYPE_OPTIONS, X_FRAME_OPTIONS};
use ic_agent::agent::http_transport::route_provider::RouteProvider;
use regex::Regex;
use url::Url;

use super::{body, error_cause::ErrorCause, ic::BNResponseMetadata};
use crate::http::Client;
use crate::http::{
headers::{CONTENT_TYPE_CBOR, X_CONTENT_TYPE_OPTIONS_NO_SNIFF, X_FRAME_OPTIONS_DENY},
Client,
};

lazy_static::lazy_static! {
pub static ref REGEX_REG_ID: Regex = Regex::new(r"^[a-zA-Z0-9]+$").unwrap();
Expand Down Expand Up @@ -56,7 +60,7 @@ pub struct ApiProxyState {
route_provider: Arc<dyn RouteProvider>,
}

// Proxies /api/v2/... endpoints to the IC
// Proxies /api/v2/... and /api/v3/... endpoints to the IC
pub async fn api_proxy(
State(state): State<Arc<ApiProxyState>>,
OriginalUri(original_uri): OriginalUri,
Expand Down Expand Up @@ -85,6 +89,20 @@ pub async fn api_proxy(
.await
.map_err(ErrorCause::from)?;

// Set the correct content-type for all replies if it's not an error
// The replica and the API boundary nodes should set these headers. This is just for redundancy.
if response.status().is_success() {
response
.headers_mut()
.insert(CONTENT_TYPE, CONTENT_TYPE_CBOR);
response
.headers_mut()
.insert(X_CONTENT_TYPE_OPTIONS, X_CONTENT_TYPE_OPTIONS_NO_SNIFF);
response
.headers_mut()
.insert(X_FRAME_OPTIONS, X_FRAME_OPTIONS_DENY);
}

let bn_metadata = BNResponseMetadata::from(response.headers_mut());
response.extensions_mut().insert(bn_metadata);
response.extensions_mut().insert(matched_path);
Expand Down

0 comments on commit 45e7d3e

Please sign in to comment.