use std::{convert::TryFrom, str::FromStr};
use ntex_bytes::{Buf, BufMut, BytePages};
use ntex_error::Error;
use ntex_h2::{self as h2};
use ntex_http::{HeaderMap, Method, header};
use super::{Client, ClientError, Transport, request::RequestContext, request::Response};
use crate::{DecodeError, GrpcStatus, Message, consts, service::MethodDef, utils::Data};
impl<T: MethodDef> Transport<T> for Client {
type Error = Error<ClientError>;
#[inline]
async fn request(
&self,
val: &T::Input,
ctx: &RequestContext,
) -> Result<Response<T>, Self::Error> {
Transport::request(&self.0, val, ctx).await
}
}
impl<T: MethodDef> Transport<T> for h2::client::Client {
type Error = Error<ClientError>;
#[inline]
async fn request(
&self,
val: &T::Input,
ctx: &RequestContext,
) -> Result<Response<T>, Self::Error> {
Transport::request(
&self.client().await.map_err(|e| e.map(ClientError::from))?,
val,
ctx,
)
.await
}
}
impl<T: MethodDef> Transport<T> for h2::client::SimpleClient {
type Error = Error<ClientError>;
#[allow(clippy::too_many_lines)]
async fn request(
&self,
val: &T::Input,
ctx: &RequestContext,
) -> Result<Response<T>, Self::Error> {
let len = val.encoded_len();
let mut buf = BytePages::default();
buf.put_u8(0); buf.put_u32(len as u32); val.write(&mut buf);
let req_size = buf.len();
let mut hdrs = HeaderMap::new();
hdrs.append(header::CONTENT_TYPE, consts::HDRV_CT_GRPC);
hdrs.append(header::USER_AGENT, consts::HDRV_USER_AGENT);
hdrs.insert(header::TE, consts::HDRV_TRAILERS);
hdrs.insert(consts::GRPC_ENCODING, consts::IDENTITY);
hdrs.insert(consts::GRPC_ACCEPT_ENCODING, consts::IDENTITY);
for (key, val) in ctx.headers() {
hdrs.insert(key.clone(), val.clone());
}
let (snd_stream, rcv_stream) = self
.send(Method::POST, T::PATH, hdrs, false)
.await
.map_err(|e| e.map(ClientError::from))?;
if ctx.get_disconnect_on_drop() {
snd_stream.disconnect_on_drop();
}
snd_stream
.send_pages(buf, true)
.await
.map_err(|e| e.map(ClientError::from))?;
let mut status = None;
let mut hdrs = HeaderMap::default();
let mut trailers = HeaderMap::default();
let mut payload = Data::Empty;
async {
loop {
let Some(msg) = rcv_stream.recv().await else {
return Err(Error::from(ClientError::UnexpectedEof(status, hdrs)));
};
match msg.kind {
h2::MessageKind::Headers {
headers,
pseudo,
eof,
} => {
if eof {
match check_grpc_status(&headers) {
Some(Ok(GrpcStatus::DeadlineExceeded)) => {
return Err(Error::from(ClientError::DeadlineExceeded(hdrs)));
}
Some(Ok(status)) if status != GrpcStatus::Ok => {
return Err(Error::from(ClientError::GrpcStatus(
status, headers,
)));
}
Some(Err(())) => {
return Err(Error::from(ClientError::Decode(
DecodeError::new("Cannot parse grpc status"),
)));
}
Some(Ok(_)) | None => {}
}
return Err(Error::from(ClientError::UnexpectedEof(
pseudo.status,
headers,
)));
}
hdrs = headers;
status = pseudo.status;
continue;
}
h2::MessageKind::Data(data, _cap) => {
payload.push(data);
continue;
}
h2::MessageKind::Eof(data) => {
match data {
h2::StreamEof::Data(data) => {
payload.push(data);
}
h2::StreamEof::Trailers(hdrs) => {
match check_grpc_status(&hdrs) {
Some(Ok(GrpcStatus::Ok)) | None => Ok(()),
Some(Ok(GrpcStatus::DeadlineExceeded)) => {
return Err(Error::from(ClientError::DeadlineExceeded(
hdrs,
)));
}
Some(Ok(st)) => {
return Err(Error::from(ClientError::GrpcStatus(
st, hdrs,
)));
}
Some(Err(())) => Err(Error::from(ClientError::Decode(
DecodeError::new("Cannot parse grpc status"),
))),
}?;
trailers = hdrs;
}
h2::StreamEof::Error(err) => {
return Err(err.map(ClientError::Stream));
}
}
}
h2::MessageKind::Disconnect(err) => {
return Err(err.map(ClientError::Operation));
}
}
let mut data = payload.get();
match status {
Some(st) => {
if !st.is_success() {
return Err(Error::from(ClientError::Response(Some(st), hdrs, data)));
}
}
None => return Err(Error::from(ClientError::Response(None, hdrs, data))),
}
let _compressed = data.get_u8();
let len = data.get_u32();
let Some(mut block) = data.split_to_checked(len as usize) else {
return Err(Error::from(ClientError::UnexpectedEof(None, hdrs)));
};
return match <T::Output as Message>::read(&mut block) {
Ok(output) => Ok(Response {
output,
trailers,
req_size,
headers: hdrs,
res_size: data.len(),
}),
Err(e) => Err(Error::from(ClientError::Decode(e))),
};
}
}
.await
.map_err(|e| e.set_service(self.service()))
}
}
fn check_grpc_status(hdrs: &HeaderMap) -> Option<Result<GrpcStatus, ()>> {
if let Some(val) = hdrs.get(consts::GRPC_STATUS) {
if let Ok(status) = val
.to_str()
.map_err(|_| ())
.and_then(|v| u8::from_str(v).map_err(|_| ()))
.and_then(GrpcStatus::try_from)
{
Some(Ok(status))
} else {
Some(Err(()))
}
} else {
None
}
}