Update HTTP service protocol
This commit is contained in:
parent
80ad0914ed
commit
94598a574b
|
@ -10,6 +10,7 @@ use hyper::header::HeaderValue;
|
||||||
|
|
||||||
use syndicate::actor::*;
|
use syndicate::actor::*;
|
||||||
use syndicate::error::Error;
|
use syndicate::error::Error;
|
||||||
|
use syndicate::relay::Mutex;
|
||||||
use syndicate::trace;
|
use syndicate::trace;
|
||||||
use syndicate::value::Map;
|
use syndicate::value::Map;
|
||||||
use syndicate::value::NestedValue;
|
use syndicate::value::NestedValue;
|
||||||
|
@ -33,17 +34,23 @@ pub fn empty_response(code: StatusCode) -> Response<Body> {
|
||||||
type ChunkItem = Result<body::Bytes, Box<dyn std::error::Error + Send + Sync>>;
|
type ChunkItem = Result<body::Bytes, Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
|
||||||
struct ResponseCollector {
|
struct ResponseCollector {
|
||||||
|
framing_handle: Option<Handle>,
|
||||||
|
context_handle: Arc<Mutex<Option<Handle>>>,
|
||||||
tx_res: Option<(oneshot::Sender<Response<Body>>, Response<Body>)>,
|
tx_res: Option<(oneshot::Sender<Response<Body>>, Response<Body>)>,
|
||||||
body_tx: Option<UnboundedSender<ChunkItem>>,
|
body_tx: Option<UnboundedSender<ChunkItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseCollector {
|
impl ResponseCollector {
|
||||||
fn new(tx: oneshot::Sender<Response<Body>>) -> Self {
|
fn new(tx: oneshot::Sender<Response<Body>>, context_handle: Arc<Mutex<Option<Handle>>>) -> Self {
|
||||||
let (body_tx, body_rx) = unbounded_channel();
|
let (body_tx, body_rx) = unbounded_channel();
|
||||||
let body_stream: Box<dyn futures::Stream<Item = ChunkItem> + Send> =
|
let body_stream: Box<dyn futures::Stream<Item = ChunkItem> + Send> =
|
||||||
Box::new(UnboundedReceiverStream::new(body_rx));
|
Box::new(UnboundedReceiverStream::new(body_rx));
|
||||||
|
let mut res = Response::new(body_stream.into());
|
||||||
|
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||||
ResponseCollector {
|
ResponseCollector {
|
||||||
tx_res: Some((tx, Response::new(body_stream.into()))),
|
framing_handle: None,
|
||||||
|
context_handle,
|
||||||
|
tx_res: Some((tx, res)),
|
||||||
body_tx: Some(body_tx),
|
body_tx: Some(body_tx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,15 +81,42 @@ impl ResponseCollector {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&mut self) -> ActorResult {
|
fn finish(&mut self, t: &mut Activation) -> ActorResult {
|
||||||
self.deliver_res();
|
self.deliver_res();
|
||||||
self.body_tx = None;
|
self.body_tx = None;
|
||||||
|
if let Some(h) = self.context_handle.lock().take() {
|
||||||
|
t.retract(h);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity<http::HttpResponse> for ResponseCollector {
|
impl Entity<http::HttpResponse> for ResponseCollector {
|
||||||
fn message(&mut self, _turn: &mut Activation, message: http::HttpResponse) -> ActorResult {
|
fn assert(&mut self, _t: &mut Activation, assertion: http::HttpResponse, handle: Handle) -> ActorResult {
|
||||||
|
match assertion {
|
||||||
|
http::HttpResponse::Processing => {
|
||||||
|
self.framing_handle = Some(handle);
|
||||||
|
self.with_res(|r| {
|
||||||
|
*r.status_mut() = StatusCode::OK;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(format!("Unexpected assertion {:?}", assertion))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retract(&mut self, t: &mut Activation, handle: Handle) -> ActorResult {
|
||||||
|
if self.framing_handle == Some(handle) {
|
||||||
|
self.finish(t)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(&mut self, t: &mut Activation, message: http::HttpResponse) -> ActorResult {
|
||||||
|
if self.framing_handle.is_none() {
|
||||||
|
self.finish(t)?;
|
||||||
|
Err("Attempt to reply before <processing> has been asserted")?;
|
||||||
|
}
|
||||||
match message {
|
match message {
|
||||||
http::HttpResponse::Status { code, .. } => self.with_res(|r| {
|
http::HttpResponse::Status { code, .. } => self.with_res(|r| {
|
||||||
*r.status_mut() = StatusCode::from_u16(
|
*r.status_mut() = StatusCode::from_u16(
|
||||||
|
@ -94,11 +128,8 @@ impl Entity<http::HttpResponse> for ResponseCollector {
|
||||||
HeaderValue::from_str(value.as_str())?);
|
HeaderValue::from_str(value.as_str())?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}),
|
}),
|
||||||
http::HttpResponse::Chunk { chunk } => self.add_chunk(*chunk),
|
http::HttpResponse::Body { chunk } => self.add_chunk(*chunk),
|
||||||
http::HttpResponse::Done { chunk } => {
|
_ => Err(format!("Unexpected message {:?}", message))?,
|
||||||
self.add_chunk(*chunk)?;
|
|
||||||
self.finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +191,6 @@ pub async fn serve(
|
||||||
let account = Account::new(Some(AnyValue::symbol("http")), trace_collector);
|
let account = Account::new(Some(AnyValue::symbol("http")), trace_collector);
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let mut req_handle: Option<Handle> = None;
|
|
||||||
|
|
||||||
facet.activate(&account, Some(trace::TurnCause::external("http")), |t| {
|
facet.activate(&account, Some(trace::TurnCause::external("http")), |t| {
|
||||||
let sreq = http::HttpRequest {
|
let sreq = http::HttpRequest {
|
||||||
|
@ -174,20 +204,17 @@ pub async fn serve(
|
||||||
body,
|
body,
|
||||||
};
|
};
|
||||||
tracing::debug!(?sreq);
|
tracing::debug!(?sreq);
|
||||||
let srep = Cap::guard(&language().syndicate, t.create(ResponseCollector::new(tx)));
|
let context_handle: Arc<Mutex<Option<Handle>>> = Arc::new(Mutex::new(None));
|
||||||
req_handle = httpd.assert(t, language(), &http::HttpContext { req: sreq, res: srep });
|
let srep = Cap::guard(&language().syndicate, t.create(ResponseCollector::new(
|
||||||
|
tx,
|
||||||
|
Arc::clone(&context_handle))));
|
||||||
|
*(context_handle.lock()) = httpd.assert(
|
||||||
|
t, language(), &http::HttpContext { req: sreq, res: srep });
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
let response_result = rx.await;
|
let response_result = rx.await;
|
||||||
|
|
||||||
facet.activate(&account, Some(trace::TurnCause::external("http")), |t| {
|
|
||||||
if let Some(h) = req_handle {
|
|
||||||
t.retract(h);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
match response_result {
|
match response_result {
|
||||||
Ok(response) => Ok(response),
|
Ok(response) => Ok(response),
|
||||||
Err(_ /* sender dropped */) => Ok(empty_response(StatusCode::INTERNAL_SERVER_ERROR)),
|
Err(_ /* sender dropped */) => Ok(empty_response(StatusCode::INTERNAL_SERVER_ERROR)),
|
||||||
|
|
|
@ -141,12 +141,12 @@ fn run(t: &mut Activation, ds: Arc<Cap>, spec: HttpRouter) -> ActorResult {
|
||||||
http::MethodPattern::Specific(m) => m.to_uppercase(),
|
http::MethodPattern::Specific(m) => m.to_uppercase(),
|
||||||
http::MethodPattern::Any => unreachable!(),
|
http::MethodPattern::Any => unreachable!(),
|
||||||
}).collect::<Vec<String>>().join(", ");
|
}).collect::<Vec<String>>().join(", ");
|
||||||
|
let h = res.assert(t, language(), &http::HttpResponse::Processing);
|
||||||
res.message(t, language(), &http::HttpResponse::Status {
|
res.message(t, language(), &http::HttpResponse::Status {
|
||||||
code: 405.into(), message: "Method Not Allowed".into() });
|
code: 405.into(), message: "Method Not Allowed".into() });
|
||||||
res.message(t, language(), &http::HttpResponse::Header {
|
res.message(t, language(), &http::HttpResponse::Header {
|
||||||
name: "allow".into(), value: allowed });
|
name: "allow".into(), value: allowed });
|
||||||
res.message(t, language(), &http::HttpResponse::Done {
|
if let Some(h) = h { t.retract(h); }
|
||||||
chunk: Box::new(http::Chunk::Bytes(vec![])) });
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,10 +166,10 @@ fn run(t: &mut Activation, ds: Arc<Cap>, spec: HttpRouter) -> ActorResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_empty(t: &mut Activation, res: &Arc<Cap>, code: u16, message: &str) -> ActorResult {
|
fn send_empty(t: &mut Activation, res: &Arc<Cap>, code: u16, message: &str) -> ActorResult {
|
||||||
|
let h = res.assert(t, language(), &http::HttpResponse::Processing);
|
||||||
res.message(t, language(), &http::HttpResponse::Status {
|
res.message(t, language(), &http::HttpResponse::Status {
|
||||||
code: code.into(), message: message.into() });
|
code: code.into(), message: message.into() });
|
||||||
res.message(t, language(), &http::HttpResponse::Done {
|
if let Some(h) = h { t.retract(h); }
|
||||||
chunk: Box::new(http::Chunk::Bytes(vec![])) });
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,12 +268,12 @@ impl HttpStaticFileServer {
|
||||||
Ok(mut fh) => {
|
Ok(mut fh) => {
|
||||||
if fh.metadata().is_ok_and(|m| m.is_dir()) {
|
if fh.metadata().is_ok_and(|m| m.is_dir()) {
|
||||||
drop(fh);
|
drop(fh);
|
||||||
|
let h = res.assert(t, language(), &http::HttpResponse::Processing);
|
||||||
res.message(t, language(), &http::HttpResponse::Status {
|
res.message(t, language(), &http::HttpResponse::Status {
|
||||||
code: 301.into(), message: "Moved permanently".into() });
|
code: 301.into(), message: "Moved permanently".into() });
|
||||||
res.message(t, language(), &http::HttpResponse::Header {
|
res.message(t, language(), &http::HttpResponse::Header {
|
||||||
name: "location".into(), value: format!("/{}/", req.path.join("/")) });
|
name: "location".into(), value: format!("/{}/", req.path.join("/")) });
|
||||||
res.message(t, language(), &http::HttpResponse::Done {
|
if let Some(h) = h { t.retract(h); }
|
||||||
chunk: Box::new(http::Chunk::Bytes(vec![])) });
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
} else {
|
} else {
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
@ -287,14 +287,17 @@ impl HttpStaticFileServer {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let h = res.assert(t, language(), &http::HttpResponse::Processing);
|
||||||
res.message(t, language(), &http::HttpResponse::Status {
|
res.message(t, language(), &http::HttpResponse::Status {
|
||||||
code: 200.into(), message: "OK".into() });
|
code: 200.into(), message: "OK".into() });
|
||||||
if let Some(mime_type) = mime_type {
|
if let Some(mime_type) = mime_type {
|
||||||
res.message(t, language(), &http::HttpResponse::Header {
|
res.message(t, language(), &http::HttpResponse::Header {
|
||||||
name: "content-type".into(), value: mime_type.to_owned() });
|
name: "content-type".into(), value: mime_type.to_owned() });
|
||||||
}
|
}
|
||||||
res.message(t, language(), &http::HttpResponse::Done {
|
res.message(t, language(), &http::HttpResponse::Body {
|
||||||
chunk: Box::new(http::Chunk::Bytes(body)) });
|
chunk: Box::new(http::Chunk::Bytes(body)) });
|
||||||
|
if let Some(h) = h { t.retract(h); }
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue