Add client, client codegen, helloworld and routeguide client examples
This commit is contained in:
@@ -10,14 +10,18 @@ edition = "2018"
|
|||||||
name = "helloworld-server"
|
name = "helloworld-server"
|
||||||
path = "src/helloworld/server.rs"
|
path = "src/helloworld/server.rs"
|
||||||
|
|
||||||
# [[bin]]
|
[[bin]]
|
||||||
# name = "helloworld-client"
|
name = "helloworld-client"
|
||||||
# path = "src/helloworld/client.rs"
|
path = "src/helloworld/client.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "routeguide-server"
|
name = "routeguide-server"
|
||||||
path = "src/routeguide/server.rs"
|
path = "src/routeguide/server.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "routeguide-client"
|
||||||
|
path = "src/routeguide/client.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tonic = { path = "../tonic" }
|
tonic = { path = "../tonic" }
|
||||||
tower-h2 = { path = "../tower-h2" }
|
tower-h2 = { path = "../tower-h2" }
|
||||||
@@ -29,6 +33,7 @@ bytes = "0.4"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
async-stream = "0.1"
|
async-stream = "0.1"
|
||||||
|
http = "0.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = { path = "../tonic-build" }
|
tonic-build = { path = "../tonic-build" }
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#![feature(async_await)]
|
||||||
|
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tower_h2::{add_origin::AddOrigin, Connection};
|
||||||
|
|
||||||
|
pub mod hello_world {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/helloworld.rs"));
|
||||||
|
tonic::client!(service = "helloworld.Greeter", proto = "self");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let addr = "[::1]:50051".parse()?;
|
||||||
|
let io = TcpStream::connect(&addr).await?;
|
||||||
|
|
||||||
|
let origin = http::Uri::from_shared(format!("http://{}", addr).into()).unwrap();
|
||||||
|
|
||||||
|
let svc = Connection::handshake(io).await?;
|
||||||
|
let svc = AddOrigin::new(svc, origin);
|
||||||
|
|
||||||
|
let mut client = hello_world::GreeterClient::new(svc);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(hello_world::HelloRequest {
|
||||||
|
name: "hello".into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = client.say_hello(request).await?;
|
||||||
|
|
||||||
|
println!("RESPONSE={:?}", response);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#![feature(async_await)]
|
||||||
|
|
||||||
|
use route_guide::{Point, RouteNote};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tokio::{net::TcpStream, timer::Interval};
|
||||||
|
use tonic::Request;
|
||||||
|
use tower_h2::{add_origin::AddOrigin, Connection};
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
|
mod route_guide {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/routeguide.rs"));
|
||||||
|
tonic::client!(service = "routeguide.RouteGuide", proto = "self");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let addr = "[::1]:10000".parse()?;
|
||||||
|
let io = TcpStream::connect(&addr).await?;
|
||||||
|
|
||||||
|
let origin = http::Uri::from_shared(format!("http://{}", addr).into()).unwrap();
|
||||||
|
|
||||||
|
let svc = Connection::handshake(io).await?;
|
||||||
|
let svc = AddOrigin::new(svc, origin);
|
||||||
|
|
||||||
|
let mut client = route_guide::RouteGuideClient::new(svc);
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.get_feature(Request::new(Point {
|
||||||
|
latitude: 409146138,
|
||||||
|
longitude: -746188906,
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("FEATURE = {:?}", response);
|
||||||
|
|
||||||
|
let outbound = async_stream::try_stream! {
|
||||||
|
let mut interval = Interval::new_interval(Duration::from_secs(1));
|
||||||
|
|
||||||
|
while let Some(time) = interval.next().await {
|
||||||
|
let elapsed = time.duration_since(start);
|
||||||
|
let note = RouteNote {
|
||||||
|
location: Some(Point {
|
||||||
|
latitude: 409146138 + elapsed.as_secs() as i32,
|
||||||
|
longitude: -746188906,
|
||||||
|
}),
|
||||||
|
message: format!("at {:?}", elapsed),
|
||||||
|
};
|
||||||
|
|
||||||
|
yield note;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = Request::new(outbound);
|
||||||
|
|
||||||
|
let response = client.route_chat(request).await?;
|
||||||
|
|
||||||
|
let mut inbound = response.into_inner();
|
||||||
|
|
||||||
|
while let Some(note) = inbound.try_next().await? {
|
||||||
|
println!("NOTE = {:?}", note);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -3,19 +3,22 @@
|
|||||||
mod data;
|
mod data;
|
||||||
|
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use tokio::{net::TcpListener, sync::{mpsc, Lock}};
|
|
||||||
use tonic::{Request, Response, Status};
|
|
||||||
use tower_h2::Server;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use tokio::{
|
||||||
|
net::TcpListener,
|
||||||
|
sync::{mpsc, Lock},
|
||||||
|
};
|
||||||
|
use tonic::{Request, Response, Status};
|
||||||
|
use tower_h2::Server;
|
||||||
|
|
||||||
pub mod routeguide {
|
pub mod routeguide {
|
||||||
include!(concat!(env!("OUT_DIR"), "/routeguide.rs"));
|
include!(concat!(env!("OUT_DIR"), "/routeguide.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
use routeguide::{Point, Rectangle, Feature, RouteNote, RouteSummary};
|
use routeguide::{Feature, Point, Rectangle, RouteNote, RouteSummary};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RouteGuide {
|
pub struct RouteGuide {
|
||||||
@@ -51,7 +54,7 @@ impl RouteGuide {
|
|||||||
&self,
|
&self,
|
||||||
request: Request<Rectangle>,
|
request: Request<Rectangle>,
|
||||||
) -> Result<Response<mpsc::Receiver<Result<Feature, Status>>>, Status> {
|
) -> Result<Response<mpsc::Receiver<Result<Feature, Status>>>, Status> {
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
println!("ListFeatures = {:?}", request);
|
println!("ListFeatures = {:?}", request);
|
||||||
|
|
||||||
@@ -70,22 +73,20 @@ impl RouteGuide {
|
|||||||
println!(" /// done sending");
|
println!(" /// done sending");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Ok(Response::new(rx))
|
Ok(Response::new(rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn record_route(
|
pub async fn record_route(
|
||||||
&self,
|
&self,
|
||||||
request: Request<impl Stream<Item = Result<Point, Status>>>,
|
request: Request<impl Stream<Item = Result<Point, Status>>>,
|
||||||
) -> Result<Response<RouteSummary>, Status>
|
) -> Result<Response<RouteSummary>, Status> {
|
||||||
{
|
|
||||||
println!("RecordRoute");
|
println!("RecordRoute");
|
||||||
|
|
||||||
let stream = request.into_inner();
|
let stream = request.into_inner();
|
||||||
|
|
||||||
// Pin the inbound stream to the stack so that we can call next on it
|
// Pin the inbound stream to the stack so that we can call next on it
|
||||||
futures::pin_mut!(stream);
|
futures::pin_mut!(stream);
|
||||||
|
|
||||||
let mut summary = RouteSummary::default();
|
let mut summary = RouteSummary::default();
|
||||||
let mut last_point = None;
|
let mut last_point = None;
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
@@ -127,8 +128,6 @@ impl RouteGuide {
|
|||||||
let stream = request.into_inner();
|
let stream = request.into_inner();
|
||||||
let mut state = self.state.clone();
|
let mut state = self.state.clone();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let output = async_stream::try_stream! {
|
let output = async_stream::try_stream! {
|
||||||
futures::pin_mut!(stream);
|
futures::pin_mut!(stream);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
use super::{Method, Service};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::Path;
|
||||||
|
|
||||||
|
pub(crate) fn generate(service: Service, proto: String) -> TokenStream {
|
||||||
|
let mut stream = TokenStream::new();
|
||||||
|
|
||||||
|
for method in &service.methods {
|
||||||
|
let path = format!(
|
||||||
|
"/{}.{}/{}",
|
||||||
|
service.package, service.proto_name, method.proto_name
|
||||||
|
);
|
||||||
|
|
||||||
|
let method = match (method.client_streaming, method.server_streaming) {
|
||||||
|
(false, false) => generate_unary(method, &proto, path),
|
||||||
|
(false, true) => generate_server_streaming(method, &proto, path),
|
||||||
|
(true, false) => generate_client_streaming(method, &proto, path),
|
||||||
|
(true, true) => generate_streaming(method, &proto, path),
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.extend(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_unary(method: &Method, proto: &str, path: String) -> TokenStream {
|
||||||
|
let ident = format_ident!("{}", method.name);
|
||||||
|
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
|
||||||
|
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub async fn #ident (&mut self, request: tonic::Request<#request>)
|
||||||
|
-> Result<tonic::Response<#response>, tonic::Status> {
|
||||||
|
let codec = tonic::codec::ProstCodec::new();
|
||||||
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
|
self.inner.unary(request, path, codec).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_server_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
|
||||||
|
let ident = format_ident!("{}", method.name);
|
||||||
|
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
|
||||||
|
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub async fn #ident (&mut self, request: tonic::Request<#request>)
|
||||||
|
-> Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status> {
|
||||||
|
let codec = tonic::codec::ProstCodec::new();
|
||||||
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
|
self.inner.server_streaming(request, path, codec).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_client_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
|
||||||
|
let ident = format_ident!("{}", method.name);
|
||||||
|
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
|
||||||
|
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub async fn #ident <S>(&mut self, request: tonic::Request<S>)
|
||||||
|
-> Result<tonic::Response<#response>, tonic::Status>
|
||||||
|
where S: tonic::_codegen::Stream<Item = Result<#request, tonic::Status>> + Send + 'static,
|
||||||
|
{
|
||||||
|
let codec = tonic::codec::ProstCodec::new();
|
||||||
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
|
let request = request.map(|s| Box::pin(s));
|
||||||
|
self.inner.client_streaming(request, path, codec).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
|
||||||
|
let ident = format_ident!("{}", method.name);
|
||||||
|
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
|
||||||
|
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub async fn #ident <S>(&mut self, request: tonic::Request<S>)
|
||||||
|
-> Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status>
|
||||||
|
where S: tonic::_codegen::Stream<Item = Result<#request, tonic::Status>> + Send + 'static,
|
||||||
|
{
|
||||||
|
let codec = tonic::codec::ProstCodec::new();
|
||||||
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
|
let request = request.map(|s| Box::pin(s));
|
||||||
|
self.inner.streaming(request, path, codec).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,44 @@
|
|||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
mod client;
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use syn::{AttributeArgs, ItemImpl};
|
use syn::{AttributeArgs, ItemImpl};
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn client(attr: TokenStream) -> TokenStream {
|
||||||
|
let args = syn::parse_macro_input!(attr as AttributeArgs);
|
||||||
|
let (service, proto_path) = load_service(args);
|
||||||
|
|
||||||
|
let service_ident = quote::format_ident!("{}Client", service.name);
|
||||||
|
let methods = client::generate(service, proto_path);
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
pub struct #service_ident <T> {
|
||||||
|
inner: tonic::client::Grpc<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> #service_ident <T>
|
||||||
|
where T: tonic::GrpcService<tonic::body::BoxAsyncBody>,
|
||||||
|
T::ResponseBody: tonic::body::Body + tonic::_codegen::HttpBody + Send + 'static,
|
||||||
|
<T::ResponseBody as tonic::_codegen::HttpBody>::Error: Into<tonic::error::Error> + Send,
|
||||||
|
<T::ResponseBody as tonic::_codegen::HttpBody>::Data: Send, {
|
||||||
|
pub fn new(inner: T) -> Self {
|
||||||
|
let inner = tonic::client::Grpc::new(inner);
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
#methods
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(output)
|
||||||
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn server(attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn server(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let mut original = item.clone();
|
let mut original = item.clone();
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ fn generate_client_streaming(
|
|||||||
struct #service_ident(pub std::sync::Arc<#service_impl>);
|
struct #service_ident(pub std::sync::Arc<#service_impl>);
|
||||||
|
|
||||||
impl<S> tonic::server::ClientStreamingService<S> for #service_ident
|
impl<S> tonic::server::ClientStreamingService<S> for #service_ident
|
||||||
where S: Stream<Item = Result<#request, Status>> + Unpin + Send + 'static {
|
where S: tonic::_codegen::Stream<Item = Result<#request, Status>> + Unpin + Send + 'static {
|
||||||
type Response = #response;
|
type Response = #response;
|
||||||
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
|
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
|
||||||
|
|
||||||
|
|||||||
+55
-6
@@ -1,14 +1,63 @@
|
|||||||
use crate::{Code, Status};
|
use crate::{Code, Error, Status};
|
||||||
use bytes::{Bytes, IntoBuf};
|
use bytes::{Buf, Bytes, IntoBuf};
|
||||||
use futures_core::{Stream, TryStream};
|
use futures_core::{Stream, TryStream};
|
||||||
use futures_util::{ready, TryStreamExt};
|
use futures_util::{ready, TryStreamExt};
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
use http_body::Body;
|
use http_body::Body as HttpBody;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
pub type BytesBuf = <Bytes as IntoBuf>::Buf;
|
pub type BytesBuf = <Bytes as IntoBuf>::Buf;
|
||||||
|
|
||||||
|
pub trait Body: sealed::Sealed {
|
||||||
|
type Data: Buf;
|
||||||
|
type Error: Into<Error>;
|
||||||
|
|
||||||
|
fn is_end_stream(&self) -> bool;
|
||||||
|
|
||||||
|
fn poll_data(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Self::Data, Self::Error>>>;
|
||||||
|
|
||||||
|
fn poll_trailers(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Body for T
|
||||||
|
where
|
||||||
|
T: HttpBody,
|
||||||
|
T::Error: Into<Error>,
|
||||||
|
{
|
||||||
|
type Data = T::Data;
|
||||||
|
type Error = T::Error;
|
||||||
|
|
||||||
|
fn is_end_stream(&self) -> bool {
|
||||||
|
HttpBody::is_end_stream(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_data(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||||
|
HttpBody::poll_data(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_trailers(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
|
||||||
|
HttpBody::poll_trailers(self, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> sealed::Sealed for T
|
||||||
|
where
|
||||||
|
T: HttpBody,
|
||||||
|
T::Error: Into<Error>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed {}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BoxBody {
|
pub struct BoxBody {
|
||||||
inner: Box<dyn Body<Data = BytesBuf, Error = Status> + Send>,
|
inner: Box<dyn Body<Data = BytesBuf, Error = Status> + Send>,
|
||||||
}
|
}
|
||||||
@@ -25,7 +74,7 @@ impl BoxBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Body for BoxBody {
|
impl HttpBody for BoxBody {
|
||||||
type Data = BytesBuf;
|
type Data = BytesBuf;
|
||||||
type Error = Status;
|
type Error = Status;
|
||||||
|
|
||||||
@@ -72,7 +121,7 @@ impl BoxAsyncBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Body for BoxAsyncBody {
|
impl HttpBody for BoxAsyncBody {
|
||||||
type Data = BytesBuf;
|
type Data = BytesBuf;
|
||||||
type Error = Status;
|
type Error = Status;
|
||||||
|
|
||||||
@@ -114,7 +163,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> Body for AsyncBody<S>
|
impl<S> HttpBody for AsyncBody<S>
|
||||||
where
|
where
|
||||||
S: Stream<Item = Result<crate::body::BytesBuf, Status>> + Unpin,
|
S: Stream<Item = Result<crate::body::BytesBuf, Status>> + Unpin,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
use crate::{
|
||||||
|
body::{Body, BoxAsyncBody},
|
||||||
|
codec::{decode, encode, Codec, Streaming},
|
||||||
|
Code, GrpcService, Request, Response, Status,
|
||||||
|
};
|
||||||
|
use futures_core::Stream;
|
||||||
|
use futures_util::{future, stream, TryStreamExt};
|
||||||
|
use http::{
|
||||||
|
header::{HeaderValue, CONTENT_TYPE, TE},
|
||||||
|
uri::{Parts, PathAndQuery, Uri},
|
||||||
|
};
|
||||||
|
use http_body::Body as HttpBody;
|
||||||
|
|
||||||
|
pub struct Grpc<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Grpc<T> {
|
||||||
|
pub fn new(inner: T) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn unary<M1, M2, C>(
|
||||||
|
&mut self,
|
||||||
|
request: Request<M1>,
|
||||||
|
path: PathAndQuery,
|
||||||
|
codec: C,
|
||||||
|
) -> Result<Response<M2>, Status>
|
||||||
|
where
|
||||||
|
T: GrpcService<BoxAsyncBody>,
|
||||||
|
T::ResponseBody: Body + HttpBody + Send + 'static,
|
||||||
|
<T::ResponseBody as HttpBody>::Error: Into<crate::Error> + Send,
|
||||||
|
<T::ResponseBody as HttpBody>::Data: Send,
|
||||||
|
C: Codec<Encode = M1, Decode = M2>,
|
||||||
|
C::Encoder: Send + 'static,
|
||||||
|
C::Decoder: Send + 'static,
|
||||||
|
M1: Send + 'static,
|
||||||
|
M2: Send + Unpin + 'static,
|
||||||
|
{
|
||||||
|
let request = request.map(|m| stream::once(future::ok(m)));
|
||||||
|
self.client_streaming(request, path, codec).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn client_streaming<S, M1, M2, C>(
|
||||||
|
&mut self,
|
||||||
|
request: Request<S>,
|
||||||
|
path: PathAndQuery,
|
||||||
|
codec: C,
|
||||||
|
) -> Result<Response<M2>, Status>
|
||||||
|
where
|
||||||
|
T: GrpcService<BoxAsyncBody>,
|
||||||
|
T::ResponseBody: Body + HttpBody + Send + 'static,
|
||||||
|
<T::ResponseBody as HttpBody>::Error: Into<crate::Error> + Send,
|
||||||
|
<T::ResponseBody as HttpBody>::Data: Send,
|
||||||
|
S: Stream<Item = Result<M1, Status>> + Send + 'static,
|
||||||
|
C: Codec<Encode = M1, Decode = M2>,
|
||||||
|
C::Encoder: Send + 'static,
|
||||||
|
C::Decoder: Send + 'static,
|
||||||
|
M1: Send,
|
||||||
|
M2: Send + Unpin + 'static,
|
||||||
|
{
|
||||||
|
let response = self.streaming(request, path, codec).await?;
|
||||||
|
|
||||||
|
// TODO: use response to parts
|
||||||
|
let mut body = response.into_inner();
|
||||||
|
let message = body
|
||||||
|
.try_next()
|
||||||
|
.await?
|
||||||
|
.ok_or(Status::new(Code::Internal, "Missing response message."))?;
|
||||||
|
|
||||||
|
Ok(Response::new(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn server_streaming<M1, M2, C>(
|
||||||
|
&mut self,
|
||||||
|
request: Request<M1>,
|
||||||
|
path: PathAndQuery,
|
||||||
|
codec: C,
|
||||||
|
) -> Result<Response<Streaming<M2>>, Status>
|
||||||
|
where
|
||||||
|
T: GrpcService<BoxAsyncBody>,
|
||||||
|
T::ResponseBody: Body + HttpBody + Send + 'static,
|
||||||
|
<T::ResponseBody as HttpBody>::Error: Into<crate::Error> + Send,
|
||||||
|
<T::ResponseBody as HttpBody>::Data: Send,
|
||||||
|
C: Codec<Encode = M1, Decode = M2>,
|
||||||
|
C::Encoder: Send + 'static,
|
||||||
|
C::Decoder: Send + 'static,
|
||||||
|
M1: Send + 'static,
|
||||||
|
M2: Send + Unpin + 'static,
|
||||||
|
{
|
||||||
|
let request = request.map(|m| stream::once(future::ok(m)));
|
||||||
|
self.streaming(request, path, codec).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn streaming<S, M1, M2, C>(
|
||||||
|
&mut self,
|
||||||
|
request: Request<S>,
|
||||||
|
path: PathAndQuery,
|
||||||
|
mut codec: C,
|
||||||
|
) -> Result<Response<Streaming<M2>>, Status>
|
||||||
|
where
|
||||||
|
T: GrpcService<BoxAsyncBody>,
|
||||||
|
T::ResponseBody: Body + HttpBody + Send + 'static,
|
||||||
|
<T::ResponseBody as HttpBody>::Error: Into<crate::Error> + Send,
|
||||||
|
<T::ResponseBody as HttpBody>::Data: Send,
|
||||||
|
S: Stream<Item = Result<M1, Status>> + Send + 'static,
|
||||||
|
C: Codec<Encode = M1, Decode = M2>,
|
||||||
|
C::Encoder: Send + 'static,
|
||||||
|
C::Decoder: Send + 'static,
|
||||||
|
M1: Send,
|
||||||
|
M2: Send + Unpin + 'static,
|
||||||
|
{
|
||||||
|
let mut parts = Parts::default();
|
||||||
|
parts.path_and_query = Some(path);
|
||||||
|
|
||||||
|
let uri = Uri::from_parts(parts).expect("path_and_query only is valid Uri");
|
||||||
|
|
||||||
|
let request = request
|
||||||
|
.map(|s| encode(codec.encoder(), Box::pin(s)))
|
||||||
|
.map(BoxAsyncBody::new_try);
|
||||||
|
|
||||||
|
let mut request = request.into_http(uri);
|
||||||
|
|
||||||
|
// Add the gRPC related HTTP headers
|
||||||
|
request
|
||||||
|
.headers_mut()
|
||||||
|
.insert(TE, HeaderValue::from_static("trailers"));
|
||||||
|
|
||||||
|
// Set the content type
|
||||||
|
// TODO: Don't hard code this here
|
||||||
|
let content_type = <C as Codec>::CONTENT_TYPE;
|
||||||
|
request
|
||||||
|
.headers_mut()
|
||||||
|
.insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.inner
|
||||||
|
.call(request)
|
||||||
|
.await
|
||||||
|
.map_err(|err| Status::from_error(&*(err.into())))?;
|
||||||
|
|
||||||
|
let status_code = response.status();
|
||||||
|
let trailers_only_status = Status::from_header_map(response.headers());
|
||||||
|
|
||||||
|
if let Some(status) = trailers_only_status {
|
||||||
|
if status.code() != Code::Ok {
|
||||||
|
return Err(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response
|
||||||
|
.map(|b| decode(codec.decoder(), b).into_stream())
|
||||||
|
.map(Streaming::new);
|
||||||
|
|
||||||
|
Ok(Response::from_http(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mod grpc;
|
||||||
|
|
||||||
|
pub use self::grpc::Grpc;
|
||||||
@@ -1,314 +0,0 @@
|
|||||||
use crate::{body::BytesBuf, Code, Status};
|
|
||||||
use async_stream::stream;
|
|
||||||
use bytes::{Buf, BufMut, BytesMut, IntoBuf};
|
|
||||||
use futures_core::{Stream, TryStream};
|
|
||||||
use futures_util::{future, StreamExt};
|
|
||||||
use http_body::Body;
|
|
||||||
use prost::Message;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use tokio_codec::{Decoder, Encoder};
|
|
||||||
use tracing::{debug, trace};
|
|
||||||
|
|
||||||
pub trait Codec {
|
|
||||||
type Encode;
|
|
||||||
type Decode;
|
|
||||||
|
|
||||||
type Encoder: Encoder<Item = Self::Encode, Error = Status>;
|
|
||||||
type Decoder: Decoder<Item = Self::Decode, Error = Status>;
|
|
||||||
|
|
||||||
const CONTENT_TYPE: &'static str;
|
|
||||||
|
|
||||||
fn encoder(&mut self) -> Self::Encoder;
|
|
||||||
fn decoder(&mut self) -> Self::Decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Streaming<T> {
|
|
||||||
inner: Pin<Box<dyn Stream<Item = Result<T, Status>> + Send + 'static>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Streaming<T> {
|
|
||||||
pub fn new(inner: impl Stream<Item = Result<T, Status>> + Send + 'static) -> Self {
|
|
||||||
let inner = Box::pin(inner);
|
|
||||||
Self { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
impl<T> Stream for Streaming<T> {
|
|
||||||
type Item = Result<T, Status>;
|
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
|
||||||
Pin::new(&mut self.inner).poll_next(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encode<T, U>(mut encoder: T, mut source: U) -> impl TryStream<Ok = BytesBuf, Error = Status>
|
|
||||||
where
|
|
||||||
T: Encoder<Error = Status>,
|
|
||||||
U: Stream<Item = Result<T::Item, Status>> + Unpin,
|
|
||||||
{
|
|
||||||
stream! {
|
|
||||||
let mut buf = BytesMut::with_capacity(1024);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match source.next().await {
|
|
||||||
Some(Ok(item)) => {
|
|
||||||
buf.reserve(5);
|
|
||||||
unsafe {
|
|
||||||
buf.advance_mut(5);
|
|
||||||
}
|
|
||||||
encoder.encode(item, &mut buf).map_err(drop).unwrap();
|
|
||||||
|
|
||||||
// now that we know length, we can write the header
|
|
||||||
let len = buf.len() - 5;
|
|
||||||
assert!(len <= ::std::u32::MAX as usize);
|
|
||||||
{
|
|
||||||
let mut cursor = ::std::io::Cursor::new(&mut buf[..5]);
|
|
||||||
cursor.put_u8(0); // byte must be 0, reserve doesn't auto-zero
|
|
||||||
cursor.put_u32_be(len as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield Ok(buf.split_to(len + 5).freeze().into_buf());
|
|
||||||
},
|
|
||||||
Some(Err(status)) => yield Err(status),
|
|
||||||
None => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode<T, B>(
|
|
||||||
mut decoder: T,
|
|
||||||
mut source: B,
|
|
||||||
) -> impl TryStream<Ok = T::Item, Error = Status> + 'static
|
|
||||||
where
|
|
||||||
T: Decoder<Error = Status> + 'static,
|
|
||||||
T::Item: Unpin + 'static,
|
|
||||||
B: Body + 'static,
|
|
||||||
B::Error: Into<crate::Error>,
|
|
||||||
{
|
|
||||||
stream! {
|
|
||||||
let mut buf = BytesMut::with_capacity(1024);
|
|
||||||
let mut state = State::ReadHeader;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// TODO: use try_stream! and ?
|
|
||||||
if let Some(item) = decode_chunk(&mut decoder, &mut buf, &mut state).unwrap() {
|
|
||||||
yield Ok(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunk = match future::poll_fn(|cx| source.poll_data(cx)).await {
|
|
||||||
Some(Ok(d)) => Some(d),
|
|
||||||
Some(Err(e)) => {
|
|
||||||
let err = e.into();
|
|
||||||
debug!("decoder inner stream error: {:?}", err);
|
|
||||||
let status = Status::from_error(&*err);
|
|
||||||
yield Err(status);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(data)= chunk {
|
|
||||||
buf.put(data);
|
|
||||||
} else {
|
|
||||||
if buf.has_remaining_mut() {
|
|
||||||
trace!("unexpected EOF decoding stream");
|
|
||||||
yield Err(Status::new(
|
|
||||||
Code::Internal,
|
|
||||||
"Unexpected EOF decoding stream.".to_string(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: poll_trailers for Response status code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_chunk<T>(
|
|
||||||
decoder: &mut T,
|
|
||||||
buf1: &mut BytesMut,
|
|
||||||
state: &mut State,
|
|
||||||
) -> Result<Option<T::Item>, Status>
|
|
||||||
where
|
|
||||||
T: Decoder<Error = Status>,
|
|
||||||
{
|
|
||||||
let mut buf = (&buf1[..]).into_buf();
|
|
||||||
|
|
||||||
if let State::ReadHeader = state {
|
|
||||||
println!("reading header");
|
|
||||||
if buf.remaining() < 5 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_compressed = match buf.get_u8() {
|
|
||||||
0 => false,
|
|
||||||
1 => {
|
|
||||||
trace!("message compressed, compression not supported yet");
|
|
||||||
return Err(crate::Status::new(
|
|
||||||
crate::Code::Unimplemented,
|
|
||||||
"Message compressed, compression not supported yet.".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
f => {
|
|
||||||
trace!("unexpected compression flag");
|
|
||||||
return Err(crate::Status::new(
|
|
||||||
crate::Code::Internal,
|
|
||||||
format!("Unexpected compression flag: {}", f),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let len = buf.get_u32_be() as usize;
|
|
||||||
|
|
||||||
*state = State::ReadBody {
|
|
||||||
compression: is_compressed,
|
|
||||||
len,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let State::ReadBody { len, .. } = state {
|
|
||||||
println!("reading body");
|
|
||||||
if buf.remaining() < *len {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance past the header
|
|
||||||
buf1.advance(5);
|
|
||||||
|
|
||||||
match decoder.decode(buf1) {
|
|
||||||
Ok(Some(msg)) => {
|
|
||||||
*state = State::ReadHeader;
|
|
||||||
return Ok(Some(msg));
|
|
||||||
}
|
|
||||||
Ok(None) => return Ok(None),
|
|
||||||
Err(e) => {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ProstCodec<T, U> {
|
|
||||||
_pd: PhantomData<(T, U)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> ProstCodec<T, U> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { _pd: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> Codec for ProstCodec<T, U>
|
|
||||||
where
|
|
||||||
T: Message,
|
|
||||||
U: Message + Default,
|
|
||||||
{
|
|
||||||
type Encode = T;
|
|
||||||
type Decode = U;
|
|
||||||
|
|
||||||
type Encoder = ProstEncoder<T>;
|
|
||||||
type Decoder = ProstDecoder<U>;
|
|
||||||
|
|
||||||
const CONTENT_TYPE: &'static str = "application/groc+proto";
|
|
||||||
|
|
||||||
fn encoder(&mut self) -> Self::Encoder {
|
|
||||||
ProstEncoder(PhantomData)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decoder(&mut self) -> Self::Decoder {
|
|
||||||
ProstDecoder(PhantomData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProstEncoder<T>(PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T: Message> Encoder for ProstEncoder<T> {
|
|
||||||
type Item = T;
|
|
||||||
type Error = Status;
|
|
||||||
|
|
||||||
fn encode(&mut self, item: Self::Item, buf: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
let len = item.encoded_len();
|
|
||||||
|
|
||||||
if buf.remaining_mut() < len {
|
|
||||||
buf.reserve(len);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.encode(buf)
|
|
||||||
.map_err(|_| unreachable!("Message only errors if not enough space"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProstDecoder<U>(PhantomData<U>);
|
|
||||||
|
|
||||||
impl<U: Message + Default> Decoder for ProstDecoder<U> {
|
|
||||||
type Item = U;
|
|
||||||
type Error = Status;
|
|
||||||
|
|
||||||
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
Message::decode(buf.take())
|
|
||||||
.map(Option::Some)
|
|
||||||
.map_err(from_decode_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_decode_error(error: prost::DecodeError) -> crate::Status {
|
|
||||||
// Map Protobuf parse errors to an INTERNAL status code, as per
|
|
||||||
// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
|
|
||||||
Status::new(Code::Internal, error.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct UnitCodec;
|
|
||||||
|
|
||||||
impl Codec for UnitCodec {
|
|
||||||
type Encode = ();
|
|
||||||
type Decode = ();
|
|
||||||
|
|
||||||
type Encoder = UnitEncoder;
|
|
||||||
type Decoder = UnitDecoder;
|
|
||||||
|
|
||||||
const CONTENT_TYPE: &'static str = "()";
|
|
||||||
|
|
||||||
fn encoder(&mut self) -> Self::Encoder {
|
|
||||||
UnitEncoder
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decoder(&mut self) -> Self::Decoder {
|
|
||||||
UnitDecoder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UnitEncoder;
|
|
||||||
|
|
||||||
impl Encoder for UnitEncoder {
|
|
||||||
type Item = ();
|
|
||||||
type Error = crate::Status;
|
|
||||||
|
|
||||||
fn encode(&mut self, _item: Self::Item, _buf: &mut BytesMut) -> Result<(), Self::Error> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UnitDecoder;
|
|
||||||
|
|
||||||
impl Decoder for UnitDecoder {
|
|
||||||
type Item = ();
|
|
||||||
type Error = Status;
|
|
||||||
|
|
||||||
fn decode(&mut self, _buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum State {
|
|
||||||
ReadHeader,
|
|
||||||
ReadBody { compression: bool, len: usize },
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
use crate::{Code, Status};
|
||||||
|
use bytes::{Buf, BufMut, BytesMut, IntoBuf};
|
||||||
|
use futures_core::{Stream, TryStream};
|
||||||
|
use futures_util::future;
|
||||||
|
use http::StatusCode;
|
||||||
|
use http_body::Body;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use tokio_codec::Decoder;
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
|
pub struct Streaming<T> {
|
||||||
|
inner: Pin<Box<dyn Stream<Item = Result<T, Status>> + Send + 'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Streaming<T> {
|
||||||
|
pub fn new(inner: impl Stream<Item = Result<T, Status>> + Send + 'static) -> Self {
|
||||||
|
let inner = Box::pin(inner);
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
impl<T> Stream for Streaming<T> {
|
||||||
|
type Item = Result<T, Status>;
|
||||||
|
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
Pin::new(&mut self.inner).poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum State {
|
||||||
|
ReadHeader,
|
||||||
|
ReadBody { compression: bool, len: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Direction {
|
||||||
|
Request,
|
||||||
|
Response(StatusCode),
|
||||||
|
EmptyResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode<T, B>(
|
||||||
|
mut decoder: T,
|
||||||
|
mut source: B,
|
||||||
|
) -> impl TryStream<Ok = T::Item, Error = Status> + 'static
|
||||||
|
where
|
||||||
|
T: Decoder<Error = Status> + 'static,
|
||||||
|
T::Item: Unpin + 'static,
|
||||||
|
B: Body + 'static,
|
||||||
|
B::Error: Into<crate::Error>,
|
||||||
|
{
|
||||||
|
async_stream::stream! {
|
||||||
|
let mut buf = BytesMut::with_capacity(1024 * 1024);
|
||||||
|
let mut state = State::ReadHeader;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// TODO: use try_stream! and ?
|
||||||
|
if let Some(item) = decode_chunk(&mut decoder, &mut buf, &mut state).unwrap() {
|
||||||
|
yield Ok(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = match future::poll_fn(|cx| source.poll_data(cx)).await {
|
||||||
|
Some(Ok(d)) => Some(d),
|
||||||
|
Some(Err(e)) => {
|
||||||
|
let err = e.into();
|
||||||
|
debug!("decoder inner stream error: {:?}", err);
|
||||||
|
let status = Status::from_error(&*err);
|
||||||
|
yield Err(status);
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(data) = chunk {
|
||||||
|
buf.put(data);
|
||||||
|
} else {
|
||||||
|
if buf.has_remaining_mut() {
|
||||||
|
trace!("unexpected EOF decoding stream");
|
||||||
|
yield Err(Status::new(
|
||||||
|
Code::Internal,
|
||||||
|
"Unexpected EOF decoding stream.".to_string(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: poll_trailers for Response status code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_chunk<T>(
|
||||||
|
decoder: &mut T,
|
||||||
|
buf1: &mut BytesMut,
|
||||||
|
state: &mut State,
|
||||||
|
) -> Result<Option<T::Item>, Status>
|
||||||
|
where
|
||||||
|
T: Decoder<Error = Status>,
|
||||||
|
{
|
||||||
|
let mut buf = (&buf1[..]).into_buf();
|
||||||
|
|
||||||
|
if let State::ReadHeader = state {
|
||||||
|
if buf.remaining() < 5 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_compressed = match buf.get_u8() {
|
||||||
|
0 => false,
|
||||||
|
1 => {
|
||||||
|
trace!("message compressed, compression not supported yet");
|
||||||
|
return Err(crate::Status::new(
|
||||||
|
crate::Code::Unimplemented,
|
||||||
|
"Message compressed, compression not supported yet.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
f => {
|
||||||
|
trace!("unexpected compression flag");
|
||||||
|
return Err(crate::Status::new(
|
||||||
|
crate::Code::Internal,
|
||||||
|
format!("Unexpected compression flag: {}", f),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let len = buf.get_u32_be() as usize;
|
||||||
|
|
||||||
|
*state = State::ReadBody {
|
||||||
|
compression: is_compressed,
|
||||||
|
len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let State::ReadBody { len, .. } = state {
|
||||||
|
if buf.remaining() < *len {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance past the header
|
||||||
|
buf1.advance(5);
|
||||||
|
|
||||||
|
match decoder.decode(buf1) {
|
||||||
|
Ok(Some(msg)) => {
|
||||||
|
*state = State::ReadHeader;
|
||||||
|
return Ok(Some(msg));
|
||||||
|
}
|
||||||
|
Ok(None) => return Ok(None),
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use crate::{body::BytesBuf, Status};
|
||||||
|
use bytes::{BufMut, BytesMut, IntoBuf};
|
||||||
|
use futures_core::{Stream, TryStream};
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use tokio_codec::Encoder;
|
||||||
|
|
||||||
|
pub fn encode<T, U>(mut encoder: T, mut source: U) -> impl TryStream<Ok = BytesBuf, Error = Status>
|
||||||
|
where
|
||||||
|
T: Encoder<Error = Status>,
|
||||||
|
U: Stream<Item = Result<T::Item, Status>> + Unpin,
|
||||||
|
{
|
||||||
|
async_stream::stream! {
|
||||||
|
let mut buf = BytesMut::with_capacity(1024);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match source.next().await {
|
||||||
|
Some(Ok(item)) => {
|
||||||
|
buf.reserve(5);
|
||||||
|
unsafe {
|
||||||
|
buf.advance_mut(5);
|
||||||
|
}
|
||||||
|
encoder.encode(item, &mut buf).map_err(drop).unwrap();
|
||||||
|
|
||||||
|
// now that we know length, we can write the header
|
||||||
|
let len = buf.len() - 5;
|
||||||
|
assert!(len <= std::u32::MAX as usize);
|
||||||
|
{
|
||||||
|
let mut cursor = std::io::Cursor::new(&mut buf[..5]);
|
||||||
|
cursor.put_u8(0); // byte must be 0, reserve doesn't auto-zero
|
||||||
|
cursor.put_u32_be(len as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield Ok(buf.split_to(len + 5).freeze().into_buf());
|
||||||
|
},
|
||||||
|
Some(Err(status)) => yield Err(status),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
mod decode;
|
||||||
|
mod encode;
|
||||||
|
mod prost;
|
||||||
|
|
||||||
|
pub use self::decode::{decode, Streaming};
|
||||||
|
pub use self::encode::encode;
|
||||||
|
pub use self::prost::ProstCodec;
|
||||||
|
|
||||||
|
use crate::Status;
|
||||||
|
use tokio_codec::{Decoder, Encoder};
|
||||||
|
|
||||||
|
pub trait Codec {
|
||||||
|
type Encode;
|
||||||
|
type Decode;
|
||||||
|
|
||||||
|
type Encoder: Encoder<Item = Self::Encode, Error = Status>;
|
||||||
|
type Decoder: Decoder<Item = Self::Decode, Error = Status>;
|
||||||
|
|
||||||
|
const CONTENT_TYPE: &'static str;
|
||||||
|
|
||||||
|
fn encoder(&mut self) -> Self::Encoder;
|
||||||
|
fn decoder(&mut self) -> Self::Decoder;
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
use super::Codec;
|
||||||
|
use crate::{Code, Status};
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use prost::Message;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use tokio_codec::{Decoder, Encoder};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProstCodec<T, U> {
|
||||||
|
_pd: PhantomData<(T, U)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> ProstCodec<T, U> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { _pd: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Codec for ProstCodec<T, U>
|
||||||
|
where
|
||||||
|
T: Message,
|
||||||
|
U: Message + Default,
|
||||||
|
{
|
||||||
|
type Encode = T;
|
||||||
|
type Decode = U;
|
||||||
|
|
||||||
|
type Encoder = ProstEncoder<T>;
|
||||||
|
type Decoder = ProstDecoder<U>;
|
||||||
|
|
||||||
|
const CONTENT_TYPE: &'static str = "application/groc+proto";
|
||||||
|
|
||||||
|
fn encoder(&mut self) -> Self::Encoder {
|
||||||
|
ProstEncoder(PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decoder(&mut self) -> Self::Decoder {
|
||||||
|
ProstDecoder(PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProstEncoder<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T: Message> Encoder for ProstEncoder<T> {
|
||||||
|
type Item = T;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn encode(&mut self, item: Self::Item, buf: &mut BytesMut) -> Result<(), Self::Error> {
|
||||||
|
let len = item.encoded_len();
|
||||||
|
|
||||||
|
if buf.remaining_mut() < len {
|
||||||
|
buf.reserve(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.encode(buf)
|
||||||
|
.map_err(|_| unreachable!("Message only errors if not enough space"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProstDecoder<U>(PhantomData<U>);
|
||||||
|
|
||||||
|
impl<U: Message + Default> Decoder for ProstDecoder<U> {
|
||||||
|
type Item = U;
|
||||||
|
type Error = Status;
|
||||||
|
|
||||||
|
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
|
Message::decode(buf.take())
|
||||||
|
.map(Option::Some)
|
||||||
|
.map_err(from_decode_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_decode_error(error: prost::DecodeError) -> crate::Status {
|
||||||
|
// Map Protobuf parse errors to an INTERNAL status code, as per
|
||||||
|
// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
|
||||||
|
Status::new(Code::Internal, error.to_string())
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) type Error = Box<dyn std::error::Error + Send + Sync>;
|
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
+6
-1
@@ -4,6 +4,7 @@
|
|||||||
//! gRPC implementation
|
//! gRPC implementation
|
||||||
|
|
||||||
pub mod body;
|
pub mod body;
|
||||||
|
pub mod client;
|
||||||
pub mod codec;
|
pub mod codec;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
@@ -12,13 +13,15 @@ pub mod server;
|
|||||||
|
|
||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
|
mod service;
|
||||||
mod status;
|
mod status;
|
||||||
|
|
||||||
pub use body::{BoxAsyncBody, BoxBody};
|
pub use body::{BoxAsyncBody, BoxBody};
|
||||||
pub use request::Request;
|
pub use request::Request;
|
||||||
pub use response::Response;
|
pub use response::Response;
|
||||||
|
pub use service::GrpcService;
|
||||||
pub use status::{Code, Status};
|
pub use status::{Code, Status};
|
||||||
pub use tonic_macros::server;
|
pub use tonic_macros::{client, server};
|
||||||
|
|
||||||
pub(crate) use error::Error;
|
pub(crate) use error::Error;
|
||||||
|
|
||||||
@@ -35,7 +38,9 @@ pub trait GrpcInnerService<Request> {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
|
||||||
pub mod _codegen {
|
pub mod _codegen {
|
||||||
|
pub use futures_core::Stream;
|
||||||
pub use futures_util::future::{ok, Ready};
|
pub use futures_util::future::{ok, Ready};
|
||||||
|
pub use http_body::Body as HttpBody;
|
||||||
pub use std::future::Future;
|
pub use std::future::Future;
|
||||||
pub use std::pin::Pin;
|
pub use std::pin::Pin;
|
||||||
pub use std::task::{Context, Poll};
|
pub use std::task::{Context, Poll};
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ where
|
|||||||
self.map_response(response).map(BoxAsyncBody::new_try)
|
self.map_response(response).map(BoxAsyncBody::new_try)
|
||||||
}
|
}
|
||||||
|
|
||||||
//BoxStream<T::Decode>,
|
//BoxStream<T::Decode>,
|
||||||
pub async fn client_streaming<S, B>(
|
pub async fn client_streaming<S, B>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut service: S,
|
mut service: S,
|
||||||
@@ -154,9 +154,7 @@ where
|
|||||||
B::Error: Into<crate::Error> + Send,
|
B::Error: Into<crate::Error> + Send,
|
||||||
{
|
{
|
||||||
Request::from_http(
|
Request::from_http(
|
||||||
request.map(|b| {
|
request.map(|b| Streaming::new(decode(self.codec.decoder(), b).into_stream())),
|
||||||
Streaming::new(decode(self.codec.decoder(), b).into_stream())
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::body::Body;
|
||||||
|
use http::{Request, Response};
|
||||||
|
use http_body::Body as HttpBody;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
pub trait GrpcService<ReqBody> {
|
||||||
|
type ResponseBody: Body + HttpBody;
|
||||||
|
type Error: Into<crate::Error>;
|
||||||
|
|
||||||
|
type Future: Future<Output = Result<Response<Self::ResponseBody>, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||||
|
|
||||||
|
fn call(&mut self, request: Request<ReqBody>) -> Self::Future;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, ReqBody, ResBody> GrpcService<ReqBody> for T
|
||||||
|
where
|
||||||
|
T: Service<Request<ReqBody>, Response = Response<ResBody>>,
|
||||||
|
T::Error: Into<crate::Error>,
|
||||||
|
ResBody: Body + HttpBody,
|
||||||
|
<ResBody as HttpBody>::Error: Into<crate::Error>,
|
||||||
|
{
|
||||||
|
type ResponseBody = ResBody;
|
||||||
|
type Error = T::Error;
|
||||||
|
type Future = T::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Service::poll_ready(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, request: Request<ReqBody>) -> Self::Future {
|
||||||
|
Service::call(self, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-10
@@ -1,14 +1,14 @@
|
|||||||
#![feature(async_await, type_alias_impl_trait)]
|
#![feature(async_await, type_alias_impl_trait)]
|
||||||
|
|
||||||
use futures_util::future;
|
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
use std::pin::Pin;
|
use futures_util::future;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tonic::{
|
use tonic::{
|
||||||
body,
|
body,
|
||||||
server::{Grpc, UnaryService, ClientStreamingService},
|
server::{ClientStreamingService, Grpc, UnaryService},
|
||||||
Request, Response, Status,
|
Request, Response, Status,
|
||||||
};
|
};
|
||||||
use tower_h2::{RecvBody, Server};
|
use tower_h2::{RecvBody, Server};
|
||||||
@@ -47,15 +47,20 @@ impl UnaryService<HelloRequest> for SayHello {
|
|||||||
|
|
||||||
struct SayHelloStream;
|
struct SayHelloStream;
|
||||||
|
|
||||||
impl<S> ClientStreamingService<S> for SayHelloStream
|
impl<S> ClientStreamingService<S> for SayHelloStream
|
||||||
where S: Stream<Item = Result<HelloRequest, Status>> + Unpin + Send + 'static {
|
where
|
||||||
|
S: Stream<Item = Result<HelloRequest, Status>> + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
type Response = HelloReply;
|
type Response = HelloReply;
|
||||||
// type Future = impl Future<Output = Result<Response<Self::Response>, Status>>;
|
// type Future = impl Future<Output = Result<Response<Self::Response>, Status>>;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Response<Self::Response>, Status>> + Send + 'static>>;
|
type Future =
|
||||||
|
Pin<Box<dyn Future<Output = Result<Response<Self::Response>, Status>> + Send + 'static>>;
|
||||||
|
|
||||||
fn call(&mut self, _req: Request<S>) -> Self::Future {
|
fn call(&mut self, _req: Request<S>) -> Self::Future {
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
Ok(Response::new(HelloReply { message: "hello".into()}))
|
Ok(Response::new(HelloReply {
|
||||||
|
message: "hello".into(),
|
||||||
|
}))
|
||||||
};
|
};
|
||||||
Box::pin(fut)
|
Box::pin(fut)
|
||||||
}
|
}
|
||||||
@@ -116,9 +121,8 @@ impl Service<http::Request<RecvBody>> for Svc {
|
|||||||
Box::pin(fut)
|
Box::pin(fut)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unimplemented!()
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-7
@@ -1,13 +1,13 @@
|
|||||||
#![feature(async_await, type_alias_impl_trait)]
|
#![feature(async_await, type_alias_impl_trait)]
|
||||||
|
|
||||||
|
use futures_core::Stream;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use tokio_buf::BufStream;
|
use tokio_buf::BufStream;
|
||||||
use tonic::codec::ProstCodec;
|
use tonic::codec::ProstCodec;
|
||||||
use tonic::server::*;
|
use tonic::server::*;
|
||||||
use tonic::{Request, Response, Status};
|
use tonic::{Request, Response, Status};
|
||||||
use std::pin::Pin;
|
|
||||||
use futures_core::Stream;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, prost::Message)]
|
#[derive(Clone, PartialEq, prost::Message)]
|
||||||
pub struct HelloRequest {
|
pub struct HelloRequest {
|
||||||
@@ -28,20 +28,31 @@ impl UnaryService<HelloRequest> for SayHello {
|
|||||||
type Future = impl Future<Output = Result<Response<Self::Response>, Status>>;
|
type Future = impl Future<Output = Result<Response<Self::Response>, Status>>;
|
||||||
|
|
||||||
fn call(&mut self, _request: Request<HelloRequest>) -> Self::Future {
|
fn call(&mut self, _request: Request<HelloRequest>) -> Self::Future {
|
||||||
async move { Ok(Response::new(HelloReply { message: "hello".into()})) }
|
async move {
|
||||||
|
Ok(Response::new(HelloReply {
|
||||||
|
message: "hello".into(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SayHelloStream;
|
struct SayHelloStream;
|
||||||
|
|
||||||
impl<S> ClientStreamingService<S> for SayHelloStream
|
impl<S> ClientStreamingService<S> for SayHelloStream
|
||||||
where S: Stream<Item = Result<HelloRequest, Status>> + Unpin + Send + 'static {
|
where
|
||||||
|
S: Stream<Item = Result<HelloRequest, Status>> + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
type Response = HelloReply;
|
type Response = HelloReply;
|
||||||
// type Future = impl Future<Output = Result<Response<Self::Response>, Status>>;
|
// type Future = impl Future<Output = Result<Response<Self::Response>, Status>>;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Response<Self::Response>, Status>> + Send + 'static>>;
|
type Future =
|
||||||
|
Pin<Box<dyn Future<Output = Result<Response<Self::Response>, Status>> + Send + 'static>>;
|
||||||
|
|
||||||
fn call(&mut self, _: Request<S>) -> Self::Future {
|
fn call(&mut self, _: Request<S>) -> Self::Future {
|
||||||
let fut = async move { Ok(Response::new(HelloReply { message: "hello".into()})) };
|
let fut = async move {
|
||||||
|
Ok(Response::new(HelloReply {
|
||||||
|
message: "hello".into(),
|
||||||
|
}))
|
||||||
|
};
|
||||||
Box::pin(fut)
|
Box::pin(fut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use http::{Request, Uri};
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AddOrigin<T> {
|
||||||
|
inner: T,
|
||||||
|
origin: Uri,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AddOrigin<T> {
|
||||||
|
pub fn new(inner: T, origin: Uri) -> Self {
|
||||||
|
Self { inner, origin }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, ReqBody> Service<Request<ReqBody>> for AddOrigin<T>
|
||||||
|
where
|
||||||
|
T: Service<Request<ReqBody>>,
|
||||||
|
{
|
||||||
|
type Response = T::Response;
|
||||||
|
type Error = T::Error;
|
||||||
|
type Future = T::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.inner.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
||||||
|
// Split the request into the head and the body.
|
||||||
|
let (mut head, body) = req.into_parts();
|
||||||
|
|
||||||
|
// Split the request URI into parts.
|
||||||
|
let mut uri: http::uri::Parts = head.uri.into();
|
||||||
|
let set_uri = self.origin.clone().into_parts();
|
||||||
|
|
||||||
|
// Update the URI parts, setting hte scheme and authority
|
||||||
|
uri.scheme = Some(set_uri.scheme.expect("expected scheme").clone());
|
||||||
|
uri.authority = Some(set_uri.authority.expect("expected authority").clone());
|
||||||
|
|
||||||
|
// Update the the request URI
|
||||||
|
head.uri = http::Uri::from_parts(uri).expect("valid uri");
|
||||||
|
|
||||||
|
let request = Request::from_parts(head, body);
|
||||||
|
|
||||||
|
self.inner.call(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{buf::SendBuf, flush::Flush, recv_body::RecvBody};
|
use crate::{buf::SendBuf, flush::Flush, recv_body::RecvBody};
|
||||||
use futures_util::{future, FutureExt, TryFutureExt};
|
use futures_util::{future, FutureExt, TryFutureExt};
|
||||||
use h2::{client::SendRequest, RecvStream};
|
use h2::client::SendRequest;
|
||||||
use http::{Request, Response};
|
use http::{Request, Response};
|
||||||
use http_body::Body;
|
use http_body::Body;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ where
|
|||||||
match ready!(self.h2.poll_capacity(cx)) {
|
match ready!(self.h2.poll_capacity(cx)) {
|
||||||
Some(Ok(0)) => {}
|
Some(Ok(0)) => {}
|
||||||
Some(Ok(_)) => break,
|
Some(Ok(_)) => break,
|
||||||
Some(Err(e)) => return panic!("error {:?}", e),
|
Some(Err(e)) => panic!("error {:?}", e),
|
||||||
None => {
|
None => {
|
||||||
debug!("connection closed early");
|
debug!("connection closed early");
|
||||||
// The error shouldn't really matter at this
|
// The error shouldn't really matter at this
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
pub mod add_origin;
|
||||||
|
|
||||||
mod buf;
|
mod buf;
|
||||||
mod client;
|
mod client;
|
||||||
mod error;
|
mod error;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use bytes::{Buf, Bytes, BytesMut};
|
use bytes::{Buf, Bytes, BytesMut};
|
||||||
use futures_core::Stream;
|
|
||||||
use futures_util::TryStreamExt;
|
use futures_util::TryStreamExt;
|
||||||
use http_body::Body;
|
use http_body::Body;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|||||||
Reference in New Issue
Block a user