Skip to content

Commit

Permalink
HTTP/2 Cleartext protocol (h2c) support for subgraph connections (#3852)
Browse files Browse the repository at this point in the history
Fix #3535
The router can now connect to subgraphs over HTTP/2 Cleartext, which uses the HTTP/2 binary protocol directly over TCP without TLS. To activate it, set the `experimental_http2` option to `http2_only`.

---------

Co-authored-by: Edward Huang <[email protected]>
Co-authored-by: Jeremy Lempereur <[email protected]>
  • Loading branch information
3 people authored Sep 25, 2023
1 parent 20b727b commit 7d45877
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 74 deletions.
5 changes: 5 additions & 0 deletions .changesets/feat_geal_h2c.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### HTTP/2 Cleartext protocol (h2c) support for subgraph connections ([Issue #3535](https://github.com/apollographql/router/issues/3535))

The router can now connect to subgraphs over HTTP/2 Cleartext, which uses the HTTP/2 binary protocol directly over TCP without TLS. To activate it, set the `experimental_http2` option to `http2_only`.

By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/3852
2 changes: 1 addition & 1 deletion apollo-router/src/configuration/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ impl Metrics {
opt.subgraph.rate_limit,
"$[?(@.all.global_rate_limit || @.subgraphs..global_rate_limit)]",
opt.subgraph.http2,
"$[?(@.all.experimental_enable_http2 == true || @.subgraphs..experimental_enable_http2 == true)]",
"$[?(@.all.experimental_http2 == 'enable' || @.all.experimental_http2 == 'http2only' || @.subgraphs..experimental_http2 == 'enable' || @.subgraphs..experimental_http2 == 'http2only')]",
opt.subgraph.compression,
"$[?(@.all.compression || @.subgraphs..compression)]",
opt.subgraph.deduplicate_query,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
description: Move experimental_enable_http2 to experimental_http2
actions:
- type: change
path: traffic_shaping.all.experimental_enable_http2
from: true
to: enable
- type: change
path: traffic_shaping.all.experimental_enable_http2
from: false
to: disable
- type: move
from: traffic_shaping.all.experimental_enable_http2
to: traffic_shaping.all.experimental_http2

- type: change
path: traffic_shaping.subgraphs..experimental_enable_http2
from: true
to: enable
- type: change
path: traffic_shaping.subgraphs..experimental_enable_http2
from: false
to: disable
- type: move
from: traffic_shaping.subgraphs..experimental_enable_http2
to: traffic_shaping.subgraphs..experimental_http2
Original file line number Diff line number Diff line change
Expand Up @@ -5558,11 +5558,6 @@ expression: "&schema"
"type": "boolean",
"nullable": true
},
"experimental_enable_http2": {
"description": "Enable HTTP2 for subgraphs",
"type": "boolean",
"nullable": true
},
"experimental_entity_caching": {
"description": "Enable entity caching",
"type": "object",
Expand All @@ -5578,6 +5573,33 @@ expression: "&schema"
"additionalProperties": false,
"nullable": true
},
"experimental_http2": {
"description": "Enable HTTP2 for subgraphs",
"oneOf": [
{
"description": "Enable HTTP2 for subgraphs",
"type": "string",
"enum": [
"enable"
]
},
{
"description": "Disable HTTP2 for subgraphs",
"type": "string",
"enum": [
"disable"
]
},
{
"description": "Only HTTP2 is active",
"type": "string",
"enum": [
"http2only"
]
}
],
"nullable": true
},
"experimental_retry": {
"description": "Retry configuration",
"type": "object",
Expand Down Expand Up @@ -5746,11 +5768,6 @@ expression: "&schema"
"type": "boolean",
"nullable": true
},
"experimental_enable_http2": {
"description": "Enable HTTP2 for subgraphs",
"type": "boolean",
"nullable": true
},
"experimental_entity_caching": {
"description": "Enable entity caching",
"type": "object",
Expand All @@ -5766,6 +5783,33 @@ expression: "&schema"
"additionalProperties": false,
"nullable": true
},
"experimental_http2": {
"description": "Enable HTTP2 for subgraphs",
"oneOf": [
{
"description": "Enable HTTP2 for subgraphs",
"type": "string",
"enum": [
"enable"
]
},
{
"description": "Disable HTTP2 for subgraphs",
"type": "string",
"enum": [
"disable"
]
},
{
"description": "Only HTTP2 is active",
"type": "string",
"enum": [
"http2only"
]
}
],
"nullable": true
},
"experimental_retry": {
"description": "Retry configuration",
"type": "object",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
source: apollo-router/src/configuration/upgrade.rs
expression: "apply_migration(&source_doc(),\n &Migration::builder().action(Action::Change {\n path: \"obj.field1\".to_string(),\n from: Value::Number(1u64.into()),\n to: Value::String(\"a\".into()),\n }).description(\"change field1\").build()).expect(\"expected successful migration\")"
---
{
"obj": {
"field1": "a",
"field2": 2
},
"arr": [
"v1",
"v2"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ traffic_shaping:
global_rate_limit:
capacity: 100
interval: 1s
experimental_enable_http2: true
experimental_http2: enable
experimental_retry:
ttl: 1s
min_per_sec: 2
Expand Down
32 changes: 32 additions & 0 deletions apollo-router/src/configuration/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ enum Action {
from: String,
to: String,
},
Change {
path: String,
from: Value,
to: Value,
},
}

const REMOVAL_VALUE: &str = "__PLEASE_DELETE_ME";
Expand Down Expand Up @@ -129,6 +134,17 @@ fn apply_migration(config: &Value, migration: &Migration) -> Result<Value, Confi
);
}
}
Action::Change { path, from, to } => {
if !jsonpath_lib::select(config, &format!("$.{path} == {from}"))
.unwrap_or_default()
.is_empty()
{
transformer_builder = transformer_builder.add_action(
Parser::parse(&format!(r#"const({to})"#), path)
.expect("migration must be valid"),
);
}
}
}
}
let transformer = transformer_builder
Expand Down Expand Up @@ -423,4 +439,20 @@ mod test {
)
.expect("expected successful migration"));
}

#[test]
fn change_field() {
insta::assert_json_snapshot!(apply_migration(
&source_doc(),
&Migration::builder()
.action(Action::Change {
path: "obj.field1".to_string(),
from: Value::Number(1u64.into()),
to: Value::String("a".into()),
})
.description("change field1")
.build(),
)
.expect("expected successful migration"));
}
}
89 changes: 52 additions & 37 deletions apollo-router/src/plugins/traffic_shaping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,19 @@ struct Shaping {
// *experimental feature*: Enables request retry
experimental_retry: Option<RetryConfig>,
/// Enable HTTP2 for subgraphs
experimental_enable_http2: Option<bool>,
experimental_http2: Option<Http2Config>,
}

#[derive(PartialEq, Default, Debug, Clone, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub(crate) enum Http2Config {
#[default]
/// Enable HTTP2 for subgraphs
Enable,
/// Disable HTTP2 for subgraphs
Disable,
/// Only HTTP2 is active
Http2Only,
}

impl Merge for Shaping {
Expand All @@ -94,10 +106,10 @@ impl Merge for Shaping {
.as_ref()
.or(fallback.experimental_retry.as_ref())
.cloned(),
experimental_enable_http2: self
.experimental_enable_http2
experimental_http2: self
.experimental_http2
.as_ref()
.or(fallback.experimental_enable_http2.as_ref())
.or(fallback.experimental_http2.as_ref())
.cloned(),
},
}
Expand Down Expand Up @@ -458,13 +470,13 @@ impl TrafficShaping {
}
}

pub(crate) fn enable_subgraph_http2(&self, service_name: &str) -> bool {
pub(crate) fn enable_subgraph_http2(&self, service_name: &str) -> Http2Config {
Self::merge_config(
self.config.all.as_ref(),
self.config.subgraphs.get(service_name),
)
.and_then(|config| config.shaping.experimental_enable_http2)
.unwrap_or(true)
.and_then(|config| config.shaping.experimental_http2)
.unwrap_or(Http2Config::Enable)
}
}

Expand Down Expand Up @@ -714,52 +726,55 @@ mod test {
let config = serde_yaml::from_str::<Config>(
r#"
all:
experimental_enable_http2: false
experimental_http2: disable
subgraphs:
products:
experimental_enable_http2: true
experimental_http2: enable
reviews:
experimental_enable_http2: false
experimental_http2: disable
router:
timeout: 65s
"#,
)
.unwrap();

assert!(TrafficShaping::merge_config(
config.all.as_ref(),
config.subgraphs.get("products")
)
.unwrap()
.shaping
.experimental_enable_http2
.unwrap());
assert!(!TrafficShaping::merge_config(
config.all.as_ref(),
config.subgraphs.get("reviews")
)
.unwrap()
.shaping
.experimental_enable_http2
.unwrap());
assert!(!TrafficShaping::merge_config(config.all.as_ref(), None)
.unwrap()
.shaping
.experimental_enable_http2
.unwrap());
assert!(
TrafficShaping::merge_config(config.all.as_ref(), config.subgraphs.get("products"))
.unwrap()
.shaping
.experimental_http2
.unwrap()
== Http2Config::Enable
);
assert!(
TrafficShaping::merge_config(config.all.as_ref(), config.subgraphs.get("reviews"))
.unwrap()
.shaping
.experimental_http2
.unwrap()
== Http2Config::Disable
);
assert!(
TrafficShaping::merge_config(config.all.as_ref(), None)
.unwrap()
.shaping
.experimental_http2
.unwrap()
== Http2Config::Disable
);
}

#[tokio::test]
async fn test_enable_subgraph_http2() {
let config = serde_yaml::from_str::<Config>(
r#"
all:
experimental_enable_http2: false
experimental_http2: disable
subgraphs:
products:
experimental_enable_http2: true
experimental_http2: enable
reviews:
experimental_enable_http2: false
experimental_http2: disable
router:
timeout: 65s
"#,
Expand All @@ -770,9 +785,9 @@ mod test {
.await
.unwrap();

assert!(shaping_config.enable_subgraph_http2("products"));
assert!(!shaping_config.enable_subgraph_http2("reviews"));
assert!(!shaping_config.enable_subgraph_http2("this_doesnt_exist"));
assert!(shaping_config.enable_subgraph_http2("products") == Http2Config::Enable);
assert!(shaping_config.enable_subgraph_http2("reviews") == Http2Config::Disable);
assert!(shaping_config.enable_subgraph_http2("this_doesnt_exist") == Http2Config::Disable);
}

#[tokio::test(flavor = "multi_thread")]
Expand Down
Loading

0 comments on commit 7d45877

Please sign in to comment.