Skip to content

Commit

Permalink
📞 Support the gRPC flag for dynamic backends (#308)
Browse files Browse the repository at this point in the history
Enabling this flags adds **experimental** support for gRPC origins for compute services.

* Add a grpc flag for backends.
* Add some ALPN machinery.
* A simple test case for the http2 connection.
* Comment out the test until the feature is in the released SDK.
  • Loading branch information
acw authored Sep 15, 2023
1 parent 542dc04 commit 9404a2d
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 1 deletion.
1 change: 1 addition & 0 deletions cli/tests/integration/common/backends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ impl TestBackends {
override_host: backend.override_host.clone(),
cert_host: backend.cert_host.clone(),
use_sni: backend.use_sni,
grpc: false,
client_cert: None,
};
backends.insert(name.to_string(), Arc::new(backend_config));
Expand Down
39 changes: 39 additions & 0 deletions cli/tests/integration/grpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//use crate::common::{Test, TestResult};
//use hyper::http::response;
//use hyper::server::conn::AddrIncoming;
//use hyper::service::{make_service_fn, service_fn};
//use hyper::{Request, Server, StatusCode};
//use std::net::SocketAddr;
//
//#[tokio::test(flavor = "multi_thread")]
//async fn grpc_creates_h2_connection() -> TestResult {
// let test = Test::using_fixture("grpc.wasm");
// let server_addr: SocketAddr = "127.0.0.1:0".parse().expect("localhost parses");
// let incoming = AddrIncoming::bind(&server_addr).expect("bind");
// let bound_port = incoming.local_addr().port();
//
// let service = make_service_fn(|_| async move {
// Ok::<_, std::io::Error>(service_fn(move |_req| async {
// response::Builder::new()
// .status(200)
// .body("Hello!".to_string())
// }))
// });
//
// let server = Server::builder(incoming).http2_only(true).serve(service);
// tokio::spawn(server);
//
// let resp = test
// .against(
// Request::post("/")
// .header("port", bound_port)
// .body("Hello, Viceroy!")
// .unwrap(),
// )
// .await;
// assert_eq!(resp.status(), StatusCode::OK);
// assert_eq!(resp.into_body().read_into_string().await?, "Hello!");
//
// Ok(())
//}
//
1 change: 1 addition & 0 deletions cli/tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod dictionary_lookup;
mod downstream_req;
mod env_vars;
mod geolocation_lookup;
mod grpc;
mod http_semantics;
mod kv_store;
mod logging;
Expand Down
14 changes: 14 additions & 0 deletions lib/src/config/backends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct Backend {
pub override_host: Option<HeaderValue>,
pub cert_host: Option<String>,
pub use_sni: bool,
pub grpc: bool,
pub client_cert: Option<ClientCertInfo>,
}

Expand Down Expand Up @@ -126,13 +127,26 @@ mod deserialization {
.transpose()?
.unwrap_or(true);

let grpc = toml
.remove("grpc")
.map(|grpc| {
if let Value::Boolean(grpc) = grpc {
Ok(grpc)
} else {
Err(BackendConfigError::InvalidGrpcEntry)
}
})
.transpose()?
.unwrap_or(true);

check_for_unrecognized_keys(&toml)?;

Ok(Self {
uri,
override_host,
cert_host,
use_sni,
grpc,
// NOTE: Update when we support client certs in static backends
client_cert: None,
})
Expand Down
9 changes: 8 additions & 1 deletion lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ pub enum Error {

#[error("invalid client certificate")]
InvalidClientCert(#[from] crate::config::ClientCertError),

#[error("Invalid response to ALPN request; wanted '{0}', got '{1}'")]
InvalidAlpnRepsonse(&'static str, String),
}

impl Error {
Expand Down Expand Up @@ -202,7 +205,8 @@ impl Error {
| Error::ObjectStoreKeyValidationError(_)
| Error::UnfinishedStreamingBody
| Error::SharedMemory
| Error::ToStr(_) => FastlyStatus::Error,
| Error::ToStr(_)
| Error::InvalidAlpnRepsonse(_, _) => FastlyStatus::Error,
}
}

Expand Down Expand Up @@ -393,6 +397,9 @@ pub enum BackendConfigError {
#[error("'use_sni' field was not a boolean")]
InvalidUseSniEntry,

#[error("'grpc' field was not a boolean")]
InvalidGrpcEntry,

#[error("invalid url: {0}")]
InvalidUrl(#[from] http::uri::InvalidUri),

Expand Down
28 changes: 28 additions & 0 deletions lib/src/upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ impl hyper::service::Service<Uri> for BackendConnector {
config.partial_config.with_no_client_auth()
};
config.enable_sni = backend.use_sni;
if backend.grpc {
config.alpn_protocols = vec![b"h2".to_vec()];
}
let connector = TlsConnector::from(Arc::new(config));

let cert_host = backend
Expand All @@ -138,6 +141,29 @@ impl hyper::service::Service<Uri> for BackendConnector {
let dnsname = ServerName::try_from(cert_host).map_err(Box::new)?;

let tls = connector.connect(dnsname, tcp).await.map_err(Box::new)?;

if backend.grpc {
let (_, tls_state) = tls.get_ref();

match tls_state.alpn_protocol() {
None => {
tracing::warn!(
"Unexpected; request h2 for grpc, but got nothing back from ALPN"
);
}

Some(b"h2") => {}

Some(other_value) => {
return Err(Error::InvalidAlpnRepsonse(
"h2",
String::from_utf8_lossy(other_value).to_string(),
)
.into())
}
}
}

Ok(Connection::Https(Box::new(tls)))
} else {
Ok(Connection::Http(tcp))
Expand Down Expand Up @@ -237,9 +263,11 @@ pub fn send_request(
req.headers_mut().insert(hyper::header::HOST, host);
*req.uri_mut() = uri;

let h2only = backend.grpc;
async move {
let basic_response = Client::builder()
.set_host(false)
.http2_only(h2only)
.build(connector)
.request(req)
.await
Expand Down
3 changes: 3 additions & 0 deletions lib/src/wiggle_abi/req_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ impl FastlyHttpReq for Session {
None
};

let grpc = backend_info_mask.contains(BackendConfigOptions::GRPC);

let new_backend = Backend {
uri: Uri::builder()
.scheme(scheme)
Expand All @@ -384,6 +386,7 @@ impl FastlyHttpReq for Session {
override_host,
cert_host,
use_sni,
grpc,
client_cert,
};

Expand Down
27 changes: 27 additions & 0 deletions test-fixtures/src/bin/grpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use fastly::{Backend, Error, Request};
//use fastly::experimental::GrpcBackend;
use std::str::FromStr;

/// Pass everything from the downstream request through to the backend, then pass everything back
/// from the upstream request to the downstream response.
fn main() -> Result<(), Error> {
let client_req = Request::from_client();
let Some(port_str) = client_req.get_header_str("Port") else {
panic!("Couldn't find out what port to use!");
};
let port = u16::from_str(port_str).unwrap();

let backend = Backend::builder("grpc-backend", format!("localhost:{}", port))
// .for_grpc(true)
.finish()
.expect("can build backend");

Request::get("http://localhost/")
.with_header("header", "is-a-thing")
.with_body("hello")
.send(backend)
.unwrap()
.send_to_client();

Ok(())
}

0 comments on commit 9404a2d

Please sign in to comment.