mirror of
https://github.com/emmett-framework/granian.git
synced 2025-08-04 17:08:02 +00:00
Add client disconnection handling in ASGI and RSGI (#524)
* Handle client disconnection in ASGI * Add `RSGIHTTPProtocol.client_disconnect`
This commit is contained in:
parent
74020ed9d2
commit
e804c9edf7
15 changed files with 231 additions and 58 deletions
|
@ -1,6 +1,6 @@
|
||||||
# RSGI Specification
|
# RSGI Specification
|
||||||
|
|
||||||
**Version:** 1.4
|
**Version:** 1.5
|
||||||
|
|
||||||
## Abstract
|
## Abstract
|
||||||
|
|
||||||
|
@ -165,10 +165,11 @@ And here are descriptions for the upper attributes:
|
||||||
|
|
||||||
#### HTTP protocol interface
|
#### HTTP protocol interface
|
||||||
|
|
||||||
HTTP protocol object implements two awaitable methods to receive the request body, and five different methods to send data, in particular:
|
HTTP protocol object implements two awaitable methods to receive the request body, five different methods to send data, and one awaitable method to wait for client disconnection, in particular:
|
||||||
|
|
||||||
- `__call__` to receive the entire body in `bytes` format
|
- `__call__` to receive the entire body in `bytes` format
|
||||||
- `__aiter__` to receive the body in `bytes` chunks
|
- `__aiter__` to receive the body in `bytes` chunks
|
||||||
|
- `client_disconnect` to watch for client disconnection
|
||||||
- `response_empty` to send back an empty response
|
- `response_empty` to send back an empty response
|
||||||
- `response_str` to send back a response with a `str` body
|
- `response_str` to send back a response with a `str` body
|
||||||
- `response_bytes` to send back a response with `bytes` body
|
- `response_bytes` to send back a response with `bytes` body
|
||||||
|
@ -180,6 +181,7 @@ All the upper-mentioned response methods accepts an integer `status` parameter,
|
||||||
```
|
```
|
||||||
coroutine __call__() -> body
|
coroutine __call__() -> body
|
||||||
asynciterator __aiter__() -> body chunks
|
asynciterator __aiter__() -> body chunks
|
||||||
|
coroutine client_disconnect()
|
||||||
function response_empty(status, headers)
|
function response_empty(status, headers)
|
||||||
function response_str(status, headers, body)
|
function response_str(status, headers, body)
|
||||||
function response_bytes(status, headers, body)
|
function response_bytes(status, headers, body)
|
||||||
|
@ -197,6 +199,10 @@ coroutine send_bytes(bytes)
|
||||||
coroutine send_str(str)
|
coroutine send_str(str)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `client_disconnect` method will return a future that resolve ones the client has disconnected.
|
||||||
|
|
||||||
|
> **Note:** as HTTP supports keep-alived connections, the lifecycle of the client connection might not be the same of the single request. This is why the RSGI specification doesn't imply `client_disconnect` should resolve in case a client sends multiple requests within the same connection, and thus the protocol delegates to the application the responsibility to cancel the disconnection watcher once the response is sent.
|
||||||
|
|
||||||
### Websocket protocol
|
### Websocket protocol
|
||||||
|
|
||||||
WebSockets share some HTTP details - they have a path and headers - but also have more state. Again, most of that state is in the scope, which will live as long as the socket does.
|
WebSockets share some HTTP details - they have a path and headers - but also have more state. Again, most of that state is in the scope, which will live as long as the socket does.
|
||||||
|
|
|
@ -21,6 +21,7 @@ class RSGIHTTPStreamTransport:
|
||||||
class RSGIHTTPProtocol:
|
class RSGIHTTPProtocol:
|
||||||
async def __call__(self) -> bytes: ...
|
async def __call__(self) -> bytes: ...
|
||||||
def __aiter__(self) -> Any: ...
|
def __aiter__(self) -> Any: ...
|
||||||
|
async def client_disconnect(self) -> None: ...
|
||||||
def response_empty(self, status: int, headers: List[Tuple[str, str]]): ...
|
def response_empty(self, status: int, headers: List[Tuple[str, str]]): ...
|
||||||
def response_str(self, status: int, headers: List[Tuple[str, str]], body: str): ...
|
def response_str(self, status: int, headers: List[Tuple[str, str]], body: str): ...
|
||||||
def response_bytes(self, status: int, headers: List[Tuple[str, str]], body: bytes): ...
|
def response_bytes(self, status: int, headers: List[Tuple[str, str]], body: bytes): ...
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyDict;
|
use pyo3::types::PyDict;
|
||||||
use std::{net::SocketAddr, sync::OnceLock};
|
use std::{
|
||||||
use tokio::sync::oneshot;
|
net::SocketAddr,
|
||||||
|
sync::{Arc, OnceLock},
|
||||||
|
};
|
||||||
|
use tokio::sync::{oneshot, Notify};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
io::{ASGIHTTPProtocol as HTTPProtocol, ASGIWebsocketProtocol as WebsocketProtocol, WebsocketDetachedTransport},
|
io::{ASGIHTTPProtocol as HTTPProtocol, ASGIWebsocketProtocol as WebsocketProtocol, WebsocketDetachedTransport},
|
||||||
|
@ -145,6 +148,7 @@ impl CallbackWatcherWebsocket {
|
||||||
pub(crate) fn call_http(
|
pub(crate) fn call_http(
|
||||||
cb: ArcCBScheduler,
|
cb: ArcCBScheduler,
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
client_addr: SocketAddr,
|
client_addr: SocketAddr,
|
||||||
scheme: &str,
|
scheme: &str,
|
||||||
|
@ -152,7 +156,7 @@ pub(crate) fn call_http(
|
||||||
body: hyper::body::Incoming,
|
body: hyper::body::Incoming,
|
||||||
) -> oneshot::Receiver<HTTPResponse> {
|
) -> oneshot::Receiver<HTTPResponse> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let protocol = HTTPProtocol::new(rt.clone(), body, tx);
|
let protocol = HTTPProtocol::new(rt.clone(), body, tx, disconnect_guard);
|
||||||
let scheme: Box<str> = scheme.into();
|
let scheme: Box<str> = scheme.into();
|
||||||
|
|
||||||
rt.spawn_blocking(move |py| {
|
rt.spawn_blocking(move |py| {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
use hyper::{header::SERVER as HK_SERVER, http::response::Builder as ResponseBuilder, StatusCode};
|
use hyper::{header::SERVER as HK_SERVER, http::response::Builder as ResponseBuilder, StatusCode};
|
||||||
use std::net::SocketAddr;
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::{mpsc, Notify};
|
||||||
|
|
||||||
use super::callbacks::{call_http, call_ws};
|
use super::callbacks::{call_http, call_ws};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -16,8 +16,19 @@ const SCHEME_WS: &str = "ws";
|
||||||
const SCHEME_WSS: &str = "wss";
|
const SCHEME_WSS: &str = "wss";
|
||||||
|
|
||||||
macro_rules! handle_http_response {
|
macro_rules! handle_http_response {
|
||||||
($handler:expr, $rt:expr, $callback:expr, $server_addr:expr, $client_addr:expr, $scheme:expr, $req:expr, $body:expr) => {
|
($handler:expr, $rt:expr, $disconnect_guard:expr, $callback:expr, $server_addr:expr, $client_addr:expr, $scheme:expr, $req:expr, $body:expr) => {
|
||||||
match $handler($callback, $rt, $server_addr, $client_addr, $req, $scheme, $body).await {
|
match $handler(
|
||||||
|
$callback,
|
||||||
|
$rt,
|
||||||
|
$disconnect_guard,
|
||||||
|
$server_addr,
|
||||||
|
$client_addr,
|
||||||
|
$req,
|
||||||
|
$scheme,
|
||||||
|
$body,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
_ => {
|
_ => {
|
||||||
log::error!("ASGI protocol failure");
|
log::error!("ASGI protocol failure");
|
||||||
|
@ -32,6 +43,7 @@ macro_rules! handle_request {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn $func_name(
|
pub(crate) async fn $func_name(
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
callback: ArcCBScheduler,
|
callback: ArcCBScheduler,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
client_addr: SocketAddr,
|
client_addr: SocketAddr,
|
||||||
|
@ -42,6 +54,7 @@ macro_rules! handle_request {
|
||||||
handle_http_response!(
|
handle_http_response!(
|
||||||
$handler,
|
$handler,
|
||||||
rt,
|
rt,
|
||||||
|
disconnect_guard,
|
||||||
callback,
|
callback,
|
||||||
server_addr,
|
server_addr,
|
||||||
client_addr,
|
client_addr,
|
||||||
|
@ -58,6 +71,7 @@ macro_rules! handle_request_with_ws {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn $func_name(
|
pub(crate) async fn $func_name(
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
callback: ArcCBScheduler,
|
callback: ArcCBScheduler,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
client_addr: SocketAddr,
|
client_addr: SocketAddr,
|
||||||
|
@ -142,6 +156,7 @@ macro_rules! handle_request_with_ws {
|
||||||
handle_http_response!(
|
handle_http_response!(
|
||||||
$handler_req,
|
$handler_req,
|
||||||
rt,
|
rt,
|
||||||
|
disconnect_guard,
|
||||||
callback,
|
callback,
|
||||||
server_addr,
|
server_addr,
|
||||||
client_addr,
|
client_addr,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::File,
|
fs::File,
|
||||||
sync::{mpsc, oneshot, Mutex as AsyncMutex},
|
sync::{mpsc, oneshot, Mutex as AsyncMutex, Notify},
|
||||||
};
|
};
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
|
@ -25,7 +25,9 @@ use super::{
|
||||||
use crate::{
|
use crate::{
|
||||||
conversion::FutureResultToPy,
|
conversion::FutureResultToPy,
|
||||||
http::{response_404, HTTPResponse, HTTPResponseBody, HV_SERVER},
|
http::{response_404, HTTPResponse, HTTPResponseBody, HV_SERVER},
|
||||||
runtime::{empty_future_into_py, err_future_into_py, future_into_py_futlike, Runtime, RuntimeRef},
|
runtime::{
|
||||||
|
done_future_into_py, empty_future_into_py, err_future_into_py, future_into_py_futlike, Runtime, RuntimeRef,
|
||||||
|
},
|
||||||
ws::{HyperWebsocket, UpgradeData, WSRxStream, WSTxStream},
|
ws::{HyperWebsocket, UpgradeData, WSRxStream, WSTxStream},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,27 +39,36 @@ static WS_SUBPROTO_HNAME: &str = "Sec-WebSocket-Protocol";
|
||||||
pub(crate) struct ASGIHTTPProtocol {
|
pub(crate) struct ASGIHTTPProtocol {
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
tx: Mutex<Option<oneshot::Sender<HTTPResponse>>>,
|
tx: Mutex<Option<oneshot::Sender<HTTPResponse>>>,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
request_body: Arc<AsyncMutex<http_body_util::BodyStream<body::Incoming>>>,
|
request_body: Arc<AsyncMutex<http_body_util::BodyStream<body::Incoming>>>,
|
||||||
response_started: atomic::AtomicBool,
|
response_started: atomic::AtomicBool,
|
||||||
response_chunked: atomic::AtomicBool,
|
response_chunked: atomic::AtomicBool,
|
||||||
response_intent: Mutex<Option<(u16, HeaderMap)>>,
|
response_intent: Mutex<Option<(u16, HeaderMap)>>,
|
||||||
body_tx: Mutex<Option<mpsc::Sender<Result<body::Bytes, anyhow::Error>>>>,
|
body_tx: Mutex<Option<mpsc::Sender<Result<body::Bytes, anyhow::Error>>>>,
|
||||||
flow_rx_exhausted: Arc<atomic::AtomicBool>,
|
flow_rx_exhausted: Arc<atomic::AtomicBool>,
|
||||||
|
flow_rx_closed: Arc<atomic::AtomicBool>,
|
||||||
flow_tx_waiter: Arc<tokio::sync::Notify>,
|
flow_tx_waiter: Arc<tokio::sync::Notify>,
|
||||||
sent_response_code: Arc<atomic::AtomicU16>,
|
sent_response_code: Arc<atomic::AtomicU16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ASGIHTTPProtocol {
|
impl ASGIHTTPProtocol {
|
||||||
pub fn new(rt: RuntimeRef, body: hyper::body::Incoming, tx: oneshot::Sender<HTTPResponse>) -> Self {
|
pub fn new(
|
||||||
|
rt: RuntimeRef,
|
||||||
|
body: hyper::body::Incoming,
|
||||||
|
tx: oneshot::Sender<HTTPResponse>,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rt,
|
rt,
|
||||||
tx: Mutex::new(Some(tx)),
|
tx: Mutex::new(Some(tx)),
|
||||||
|
disconnect_guard,
|
||||||
request_body: Arc::new(AsyncMutex::new(http_body_util::BodyStream::new(body))),
|
request_body: Arc::new(AsyncMutex::new(http_body_util::BodyStream::new(body))),
|
||||||
response_started: false.into(),
|
response_started: false.into(),
|
||||||
response_chunked: false.into(),
|
response_chunked: false.into(),
|
||||||
response_intent: Mutex::new(None),
|
response_intent: Mutex::new(None),
|
||||||
body_tx: Mutex::new(None),
|
body_tx: Mutex::new(None),
|
||||||
flow_rx_exhausted: Arc::new(atomic::AtomicBool::new(false)),
|
flow_rx_exhausted: Arc::new(atomic::AtomicBool::new(false)),
|
||||||
|
flow_rx_closed: Arc::new(atomic::AtomicBool::new(false)),
|
||||||
flow_tx_waiter: Arc::new(tokio::sync::Notify::new()),
|
flow_tx_waiter: Arc::new(tokio::sync::Notify::new()),
|
||||||
sent_response_code: Arc::new(atomic::AtomicU16::new(500)),
|
sent_response_code: Arc::new(atomic::AtomicU16::new(500)),
|
||||||
}
|
}
|
||||||
|
@ -108,10 +119,22 @@ impl ASGIHTTPProtocol {
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl ASGIHTTPProtocol {
|
impl ASGIHTTPProtocol {
|
||||||
fn receive<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyAny>> {
|
fn receive<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyAny>> {
|
||||||
|
if self.flow_rx_closed.load(atomic::Ordering::Acquire) {
|
||||||
|
return done_future_into_py(
|
||||||
|
py,
|
||||||
|
super::conversion::message_into_py(py, ASGIMessageType::HTTPDisconnect).map(Bound::unbind),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if self.flow_rx_exhausted.load(atomic::Ordering::Acquire) {
|
if self.flow_rx_exhausted.load(atomic::Ordering::Acquire) {
|
||||||
let flow_hld = self.flow_tx_waiter.clone();
|
let flow_hld = self.flow_tx_waiter.clone();
|
||||||
|
let flow_dgr = self.disconnect_guard.clone();
|
||||||
|
let flow_dsr = self.flow_rx_closed.clone();
|
||||||
return future_into_py_futlike(self.rt.clone(), py, async move {
|
return future_into_py_futlike(self.rt.clone(), py, async move {
|
||||||
let () = flow_hld.notified().await;
|
tokio::select! {
|
||||||
|
() = flow_hld.notified() => {},
|
||||||
|
() = flow_dgr.notified() => flow_dsr.store(true, atomic::Ordering::Release),
|
||||||
|
}
|
||||||
FutureResultToPy::ASGIMessage(ASGIMessageType::HTTPDisconnect)
|
FutureResultToPy::ASGIMessage(ASGIMessageType::HTTPDisconnect)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -119,24 +142,33 @@ impl ASGIHTTPProtocol {
|
||||||
let body_ref = self.request_body.clone();
|
let body_ref = self.request_body.clone();
|
||||||
let flow_ref = self.flow_rx_exhausted.clone();
|
let flow_ref = self.flow_rx_exhausted.clone();
|
||||||
let flow_hld = self.flow_tx_waiter.clone();
|
let flow_hld = self.flow_tx_waiter.clone();
|
||||||
|
let flow_dgr = self.disconnect_guard.clone();
|
||||||
|
let flow_dsr = self.flow_rx_closed.clone();
|
||||||
future_into_py_futlike(self.rt.clone(), py, async move {
|
future_into_py_futlike(self.rt.clone(), py, async move {
|
||||||
let mut bodym = body_ref.lock().await;
|
let mut bodym = body_ref.lock().await;
|
||||||
let body = &mut *bodym;
|
let body = &mut *bodym;
|
||||||
let mut more_body = false;
|
let mut more_body = false;
|
||||||
let chunk = match body.next().await {
|
|
||||||
Some(Ok(buf)) => {
|
let chunk = tokio::select! {
|
||||||
more_body = true;
|
frame = body.next() => match frame {
|
||||||
Ok(buf.into_data().unwrap_or_default())
|
Some(Ok(buf)) => {
|
||||||
|
more_body = true;
|
||||||
|
Some(buf.into_data().unwrap_or_default())
|
||||||
|
}
|
||||||
|
Some(Err(_)) => None,
|
||||||
|
_ => Some(body::Bytes::new()),
|
||||||
|
},
|
||||||
|
() = flow_dgr.notified() => {
|
||||||
|
flow_dsr.store(true, atomic::Ordering::Release);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
Some(Err(err)) => Err(err),
|
|
||||||
_ => Ok(body::Bytes::new()),
|
|
||||||
};
|
};
|
||||||
if !more_body {
|
if !more_body {
|
||||||
flow_ref.store(true, atomic::Ordering::Release);
|
flow_ref.store(true, atomic::Ordering::Release);
|
||||||
}
|
}
|
||||||
|
|
||||||
match chunk {
|
match chunk {
|
||||||
Ok(data) => FutureResultToPy::ASGIMessage(ASGIMessageType::HTTPRequestBody((data, more_body))),
|
Some(data) => FutureResultToPy::ASGIMessage(ASGIMessageType::HTTPRequestBody((data, more_body))),
|
||||||
_ => {
|
_ => {
|
||||||
flow_hld.notify_one();
|
flow_hld.notify_one();
|
||||||
FutureResultToPy::ASGIMessage(ASGIMessageType::HTTPDisconnect)
|
FutureResultToPy::ASGIMessage(ASGIMessageType::HTTPDisconnect)
|
||||||
|
|
|
@ -333,6 +333,35 @@ impl PyEmptyAwaitable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(frozen, module = "granian._granian")]
|
||||||
|
pub(crate) struct PyDoneAwaitable {
|
||||||
|
result: PyResult<PyObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyDoneAwaitable {
|
||||||
|
pub(crate) fn new(result: PyResult<PyObject>) -> Self {
|
||||||
|
Self { result }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyDoneAwaitable {
|
||||||
|
fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
|
||||||
|
pyself
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
|
||||||
|
pyself
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __next__(&self, py: Python) -> PyResult<PyObject> {
|
||||||
|
self.result
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.clone_ref(py))
|
||||||
|
.map_err(|v| v.clone_ref(py))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pyclass(frozen, module = "granian._granian")]
|
#[pyclass(frozen, module = "granian._granian")]
|
||||||
pub(crate) struct PyErrAwaitable {
|
pub(crate) struct PyErrAwaitable {
|
||||||
result: PyResult<()>,
|
result: PyResult<()>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use std::sync::OnceLock;
|
use std::sync::{Arc, OnceLock};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::{oneshot, Notify};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
io::{RSGIHTTPProtocol as HTTPProtocol, RSGIWebsocketProtocol as WebsocketProtocol, WebsocketDetachedTransport},
|
io::{RSGIHTTPProtocol as HTTPProtocol, RSGIWebsocketProtocol as WebsocketProtocol, WebsocketDetachedTransport},
|
||||||
|
@ -118,11 +118,12 @@ impl CallbackWatcherWebsocket {
|
||||||
pub(crate) fn call_http(
|
pub(crate) fn call_http(
|
||||||
cb: ArcCBScheduler,
|
cb: ArcCBScheduler,
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
body: hyper::body::Incoming,
|
body: hyper::body::Incoming,
|
||||||
scope: HTTPScope,
|
scope: HTTPScope,
|
||||||
) -> oneshot::Receiver<PyResponse> {
|
) -> oneshot::Receiver<PyResponse> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let protocol = HTTPProtocol::new(rt.clone(), tx, body);
|
let protocol = HTTPProtocol::new(rt.clone(), tx, body, disconnect_guard);
|
||||||
|
|
||||||
rt.spawn_blocking(move |py| {
|
rt.spawn_blocking(move |py| {
|
||||||
if let Ok(watcher) = CallbackWatcherHTTP::new(py, protocol, scope) {
|
if let Ok(watcher) = CallbackWatcherHTTP::new(py, protocol, scope) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use futures::sink::SinkExt;
|
use futures::sink::SinkExt;
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
use hyper::{header::SERVER as HK_SERVER, http::response::Builder as ResponseBuilder, StatusCode};
|
use hyper::{header::SERVER as HK_SERVER, http::response::Builder as ResponseBuilder, StatusCode};
|
||||||
use std::net::SocketAddr;
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::{mpsc, Notify};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
callbacks::{call_http, call_ws},
|
callbacks::{call_http, call_ws},
|
||||||
|
@ -30,8 +30,8 @@ macro_rules! build_scope {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! handle_http_response {
|
macro_rules! handle_http_response {
|
||||||
($handler:expr, $rt:expr, $callback:expr, $body:expr, $scope:expr) => {
|
($handler:expr, $rt:expr, $disconnect_guard:expr, $callback:expr, $body:expr, $scope:expr) => {
|
||||||
match $handler($callback, $rt, $body, $scope).await {
|
match $handler($callback, $rt, $disconnect_guard, $body, $scope).await {
|
||||||
Ok(PyResponse::Body(pyres)) => pyres.to_response(),
|
Ok(PyResponse::Body(pyres)) => pyres.to_response(),
|
||||||
Ok(PyResponse::File(pyres)) => pyres.to_response().await,
|
Ok(PyResponse::File(pyres)) => pyres.to_response().await,
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -47,6 +47,7 @@ macro_rules! handle_request {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn $func_name(
|
pub(crate) async fn $func_name(
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
callback: ArcCBScheduler,
|
callback: ArcCBScheduler,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
client_addr: SocketAddr,
|
client_addr: SocketAddr,
|
||||||
|
@ -55,7 +56,7 @@ macro_rules! handle_request {
|
||||||
) -> HTTPResponse {
|
) -> HTTPResponse {
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
let scope = build_scope!(HTTPScope, server_addr, client_addr, parts, scheme);
|
let scope = build_scope!(HTTPScope, server_addr, client_addr, parts, scheme);
|
||||||
handle_http_response!($handler, rt, callback, body, scope)
|
handle_http_response!($handler, rt, disconnect_guard, callback, body, scope)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -65,6 +66,7 @@ macro_rules! handle_request_with_ws {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn $func_name(
|
pub(crate) async fn $func_name(
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
callback: ArcCBScheduler,
|
callback: ArcCBScheduler,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
client_addr: SocketAddr,
|
client_addr: SocketAddr,
|
||||||
|
@ -131,7 +133,7 @@ macro_rules! handle_request_with_ws {
|
||||||
|
|
||||||
let (parts, body) = req.into_parts();
|
let (parts, body) = req.into_parts();
|
||||||
let scope = build_scope!(HTTPScope, server_addr, client_addr, parts, scheme);
|
let scope = build_scope!(HTTPScope, server_addr, client_addr, parts, scheme);
|
||||||
handle_http_response!($handler_req, rt, callback, body, scope)
|
handle_http_response!($handler_req, rt, disconnect_guard, callback, body, scope)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ use hyper::body;
|
||||||
use pyo3::{prelude::*, pybacked::PyBackedStr};
|
use pyo3::{prelude::*, pybacked::PyBackedStr};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{atomic, Arc, Mutex, RwLock},
|
||||||
};
|
};
|
||||||
use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex};
|
use tokio::sync::{mpsc, oneshot, Mutex as AsyncMutex, Notify};
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -15,7 +15,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
conversion::FutureResultToPy,
|
conversion::FutureResultToPy,
|
||||||
runtime::{future_into_py_futlike, RuntimeRef},
|
runtime::{empty_future_into_py, future_into_py_futlike, RuntimeRef},
|
||||||
ws::{HyperWebsocket, UpgradeData, WSRxStream, WSTxStream},
|
ws::{HyperWebsocket, UpgradeData, WSRxStream, WSTxStream},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,17 +63,26 @@ impl RSGIHTTPStreamTransport {
|
||||||
pub(crate) struct RSGIHTTPProtocol {
|
pub(crate) struct RSGIHTTPProtocol {
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
tx: Mutex<Option<oneshot::Sender<PyResponse>>>,
|
tx: Mutex<Option<oneshot::Sender<PyResponse>>>,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
body: Mutex<Option<body::Incoming>>,
|
body: Mutex<Option<body::Incoming>>,
|
||||||
body_stream: Arc<AsyncMutex<Option<http_body_util::BodyStream<body::Incoming>>>>,
|
body_stream: Arc<AsyncMutex<Option<http_body_util::BodyStream<body::Incoming>>>>,
|
||||||
|
disconnected: Arc<atomic::AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RSGIHTTPProtocol {
|
impl RSGIHTTPProtocol {
|
||||||
pub fn new(rt: RuntimeRef, tx: oneshot::Sender<PyResponse>, body: body::Incoming) -> Self {
|
pub fn new(
|
||||||
|
rt: RuntimeRef,
|
||||||
|
tx: oneshot::Sender<PyResponse>,
|
||||||
|
body: body::Incoming,
|
||||||
|
disconnect_guard: Arc<Notify>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rt,
|
rt,
|
||||||
tx: Mutex::new(Some(tx)),
|
tx: Mutex::new(Some(tx)),
|
||||||
|
disconnect_guard,
|
||||||
body: Mutex::new(Some(body)),
|
body: Mutex::new(Some(body)),
|
||||||
body_stream: Arc::new(AsyncMutex::new(None)),
|
body_stream: Arc::new(AsyncMutex::new(None)),
|
||||||
|
disconnected: Arc::new(atomic::AtomicBool::new(false)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +119,7 @@ impl RSGIHTTPProtocol {
|
||||||
if self.body_stream.blocking_lock().is_none() {
|
if self.body_stream.blocking_lock().is_none() {
|
||||||
return Err(pyo3::exceptions::PyStopAsyncIteration::new_err("stream exhausted"));
|
return Err(pyo3::exceptions::PyStopAsyncIteration::new_err("stream exhausted"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let body_stream = self.body_stream.clone();
|
let body_stream = self.body_stream.clone();
|
||||||
future_into_py_futlike(self.rt.clone(), py, async move {
|
future_into_py_futlike(self.rt.clone(), py, async move {
|
||||||
let guard = &mut *body_stream.lock().await;
|
let guard = &mut *body_stream.lock().await;
|
||||||
|
@ -121,38 +131,52 @@ impl RSGIHTTPProtocol {
|
||||||
FutureResultToPy::Bytes(chunk)
|
FutureResultToPy::Bytes(chunk)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let _ = guard.take();
|
_ = guard.take();
|
||||||
FutureResultToPy::Bytes(body::Bytes::new())
|
FutureResultToPy::Bytes(body::Bytes::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn client_disconnect<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyAny>> {
|
||||||
|
if self.disconnected.load(atomic::Ordering::Acquire) {
|
||||||
|
return empty_future_into_py(py);
|
||||||
|
}
|
||||||
|
|
||||||
|
let guard = self.disconnect_guard.clone();
|
||||||
|
let state = self.disconnected.clone();
|
||||||
|
future_into_py_futlike(self.rt.clone(), py, async move {
|
||||||
|
guard.notified().await;
|
||||||
|
state.store(true, atomic::Ordering::Release);
|
||||||
|
FutureResultToPy::None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (status=200, headers=vec![]))]
|
#[pyo3(signature = (status=200, headers=vec![]))]
|
||||||
fn response_empty(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>) {
|
fn response_empty(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>) {
|
||||||
if let Some(tx) = self.tx.lock().unwrap().take() {
|
if let Some(tx) = self.tx.lock().unwrap().take() {
|
||||||
let _ = tx.send(PyResponse::Body(PyResponseBody::empty(status, headers)));
|
_ = tx.send(PyResponse::Body(PyResponseBody::empty(status, headers)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (status=200, headers=vec![], body=vec![].into()))]
|
#[pyo3(signature = (status=200, headers=vec![], body=vec![].into()))]
|
||||||
fn response_bytes(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>, body: Cow<[u8]>) {
|
fn response_bytes(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>, body: Cow<[u8]>) {
|
||||||
if let Some(tx) = self.tx.lock().unwrap().take() {
|
if let Some(tx) = self.tx.lock().unwrap().take() {
|
||||||
let _ = tx.send(PyResponse::Body(PyResponseBody::from_bytes(status, headers, body)));
|
_ = tx.send(PyResponse::Body(PyResponseBody::from_bytes(status, headers, body)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (status=200, headers=vec![], body=String::new()))]
|
#[pyo3(signature = (status=200, headers=vec![], body=String::new()))]
|
||||||
fn response_str(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>, body: String) {
|
fn response_str(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>, body: String) {
|
||||||
if let Some(tx) = self.tx.lock().unwrap().take() {
|
if let Some(tx) = self.tx.lock().unwrap().take() {
|
||||||
let _ = tx.send(PyResponse::Body(PyResponseBody::from_string(status, headers, body)));
|
_ = tx.send(PyResponse::Body(PyResponseBody::from_string(status, headers, body)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(signature = (status, headers, file))]
|
#[pyo3(signature = (status, headers, file))]
|
||||||
fn response_file(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>, file: String) {
|
fn response_file(&self, status: u16, headers: Vec<(PyBackedStr, PyBackedStr)>, file: String) {
|
||||||
if let Some(tx) = self.tx.lock().unwrap().take() {
|
if let Some(tx) = self.tx.lock().unwrap().take() {
|
||||||
let _ = tx.send(PyResponse::File(PyResponseFile::new(status, headers, file)));
|
_ = tx.send(PyResponse::File(PyResponseFile::new(status, headers, file)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +192,7 @@ impl RSGIHTTPProtocol {
|
||||||
let body_stream = http_body_util::StreamBody::new(
|
let body_stream = http_body_util::StreamBody::new(
|
||||||
tokio_stream::wrappers::ReceiverStream::new(body_rx).map_ok(body::Frame::data),
|
tokio_stream::wrappers::ReceiverStream::new(body_rx).map_ok(body::Frame::data),
|
||||||
);
|
);
|
||||||
let _ = tx.send(PyResponse::Body(PyResponseBody::new(
|
_ = tx.send(PyResponse::Body(PyResponseBody::new(
|
||||||
status,
|
status,
|
||||||
headers,
|
headers,
|
||||||
BodyExt::boxed(BodyExt::map_err(body_stream, std::convert::Into::into)),
|
BodyExt::boxed(BodyExt::map_err(body_stream, std::convert::Into::into)),
|
||||||
|
|
|
@ -16,7 +16,7 @@ use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use crate::http::{empty_body, response_404, HTTPResponseBody, HV_SERVER};
|
use crate::http::{empty_body, response_404, HTTPResponseBody, HV_SERVER};
|
||||||
|
|
||||||
const RSGI_PROTO_VERSION: &str = "1.4";
|
const RSGI_PROTO_VERSION: &str = "1.5";
|
||||||
|
|
||||||
#[pyclass(frozen, module = "granian._granian")]
|
#[pyclass(frozen, module = "granian._granian")]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -14,7 +14,7 @@ use super::callbacks::PyFutureAwaitable;
|
||||||
use super::callbacks::{PyFutureDoneCallback, PyFutureResultSetter};
|
use super::callbacks::{PyFutureDoneCallback, PyFutureResultSetter};
|
||||||
|
|
||||||
use super::blocking::BlockingRunner;
|
use super::blocking::BlockingRunner;
|
||||||
use super::callbacks::{PyEmptyAwaitable, PyErrAwaitable, PyIterAwaitable};
|
use super::callbacks::{PyDoneAwaitable, PyEmptyAwaitable, PyErrAwaitable, PyIterAwaitable};
|
||||||
use super::conversion::FutureResultToPy;
|
use super::conversion::FutureResultToPy;
|
||||||
|
|
||||||
pub trait JoinError {
|
pub trait JoinError {
|
||||||
|
@ -168,6 +168,11 @@ pub(crate) fn empty_future_into_py(py: Python) -> PyResult<Bound<PyAny>> {
|
||||||
PyEmptyAwaitable.into_bound_py_any(py)
|
PyEmptyAwaitable.into_bound_py_any(py)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn done_future_into_py(py: Python, result: PyResult<PyObject>) -> PyResult<Bound<PyAny>> {
|
||||||
|
PyDoneAwaitable::new(result).into_bound_py_any(py)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn err_future_into_py(py: Python, err: PyResult<()>) -> PyResult<Bound<PyAny>> {
|
pub(crate) fn err_future_into_py(py: Python, err: PyResult<()>) -> PyResult<Bound<PyAny>> {
|
||||||
PyErrAwaitable::new(err).into_bound_py_any(py)
|
PyErrAwaitable::new(err).into_bound_py_any(py)
|
||||||
|
|
|
@ -202,13 +202,25 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! build_service {
|
macro_rules! build_service {
|
||||||
($local_addr:expr, $remote_addr:expr, $callback_wrapper:expr, $rt:expr, $target:expr, $proto:expr) => {
|
($local_addr:expr, $remote_addr:expr, $callback_wrapper:expr, $rt:expr, $disconnect_guard:expr, $target:expr, $proto:expr) => {
|
||||||
hyper::service::service_fn(move |request: crate::http::HTTPRequest| {
|
hyper::service::service_fn(move |request: crate::http::HTTPRequest| {
|
||||||
let callback_wrapper = $callback_wrapper.clone();
|
let callback_wrapper = $callback_wrapper.clone();
|
||||||
let rth = $rt.clone();
|
let rth = $rt.clone();
|
||||||
|
let disconnect_guard = $disconnect_guard.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
Ok::<_, anyhow::Error>($target(rth, callback_wrapper, $local_addr, $remote_addr, request, $proto).await)
|
Ok::<_, anyhow::Error>(
|
||||||
|
$target(
|
||||||
|
rth,
|
||||||
|
disconnect_guard,
|
||||||
|
callback_wrapper,
|
||||||
|
$local_addr,
|
||||||
|
$remote_addr,
|
||||||
|
request,
|
||||||
|
$proto,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -274,9 +286,18 @@ macro_rules! handle_connection_http1 {
|
||||||
let rth = $rth.clone();
|
let rth = $rth.clone();
|
||||||
let callback_wrapper = $callback.clone();
|
let callback_wrapper = $callback.clone();
|
||||||
$spawner(async move {
|
$spawner(async move {
|
||||||
let svc =
|
let disconnect_guard = std::sync::Arc::new(tokio::sync::Notify::new());
|
||||||
crate::workers::build_service!(local_addr, remote_addr, callback_wrapper, rth, $target, $proto);
|
let disconnect_tx = disconnect_guard.clone();
|
||||||
let _ = hyper::server::conn::http1::Builder::new()
|
let svc = crate::workers::build_service!(
|
||||||
|
local_addr,
|
||||||
|
remote_addr,
|
||||||
|
callback_wrapper,
|
||||||
|
rth,
|
||||||
|
disconnect_guard,
|
||||||
|
$target,
|
||||||
|
$proto
|
||||||
|
);
|
||||||
|
_ = hyper::server::conn::http1::Builder::new()
|
||||||
.timer(crate::io::TokioTimer::new())
|
.timer(crate::io::TokioTimer::new())
|
||||||
.header_read_timeout($http_opts.header_read_timeout)
|
.header_read_timeout($http_opts.header_read_timeout)
|
||||||
.keep_alive($http_opts.keep_alive)
|
.keep_alive($http_opts.keep_alive)
|
||||||
|
@ -284,6 +305,7 @@ macro_rules! handle_connection_http1 {
|
||||||
.pipeline_flush($http_opts.pipeline_flush)
|
.pipeline_flush($http_opts.pipeline_flush)
|
||||||
.serve_connection($stream_wrapper(stream), svc)
|
.serve_connection($stream_wrapper(stream), svc)
|
||||||
.await;
|
.await;
|
||||||
|
disconnect_tx.notify_one();
|
||||||
drop(permit);
|
drop(permit);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -296,9 +318,18 @@ macro_rules! handle_connection_http1_upgrades {
|
||||||
let rth = $rth.clone();
|
let rth = $rth.clone();
|
||||||
let callback_wrapper = $callback.clone();
|
let callback_wrapper = $callback.clone();
|
||||||
$spawner(async move {
|
$spawner(async move {
|
||||||
let svc =
|
let disconnect_guard = std::sync::Arc::new(tokio::sync::Notify::new());
|
||||||
crate::workers::build_service!(local_addr, remote_addr, callback_wrapper, rth, $target, $proto);
|
let disconnect_tx = disconnect_guard.clone();
|
||||||
let _ = hyper::server::conn::http1::Builder::new()
|
let svc = crate::workers::build_service!(
|
||||||
|
local_addr,
|
||||||
|
remote_addr,
|
||||||
|
callback_wrapper,
|
||||||
|
rth,
|
||||||
|
disconnect_guard,
|
||||||
|
$target,
|
||||||
|
$proto
|
||||||
|
);
|
||||||
|
_ = hyper::server::conn::http1::Builder::new()
|
||||||
.timer(crate::io::TokioTimer::new())
|
.timer(crate::io::TokioTimer::new())
|
||||||
.header_read_timeout($http_opts.header_read_timeout)
|
.header_read_timeout($http_opts.header_read_timeout)
|
||||||
.keep_alive($http_opts.keep_alive)
|
.keep_alive($http_opts.keep_alive)
|
||||||
|
@ -307,6 +338,7 @@ macro_rules! handle_connection_http1_upgrades {
|
||||||
.serve_connection($stream_wrapper(stream), svc)
|
.serve_connection($stream_wrapper(stream), svc)
|
||||||
.with_upgrades()
|
.with_upgrades()
|
||||||
.await;
|
.await;
|
||||||
|
disconnect_tx.notify_one();
|
||||||
drop(permit);
|
drop(permit);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -319,9 +351,18 @@ macro_rules! handle_connection_http2 {
|
||||||
let rth = $rth.clone();
|
let rth = $rth.clone();
|
||||||
let callback_wrapper = $callback.clone();
|
let callback_wrapper = $callback.clone();
|
||||||
$spawner(async move {
|
$spawner(async move {
|
||||||
let svc =
|
let disconnect_guard = std::sync::Arc::new(tokio::sync::Notify::new());
|
||||||
crate::workers::build_service!(local_addr, remote_addr, callback_wrapper, rth, $target, $proto);
|
let disconnect_tx = disconnect_guard.clone();
|
||||||
let _ = hyper::server::conn::http2::Builder::new($executor_builder())
|
let svc = crate::workers::build_service!(
|
||||||
|
local_addr,
|
||||||
|
remote_addr,
|
||||||
|
callback_wrapper,
|
||||||
|
rth,
|
||||||
|
disconnect_guard,
|
||||||
|
$target,
|
||||||
|
$proto
|
||||||
|
);
|
||||||
|
_ = hyper::server::conn::http2::Builder::new($executor_builder())
|
||||||
.timer(crate::io::TokioTimer::new())
|
.timer(crate::io::TokioTimer::new())
|
||||||
.adaptive_window($http_opts.adaptive_window)
|
.adaptive_window($http_opts.adaptive_window)
|
||||||
.initial_connection_window_size($http_opts.initial_connection_window_size)
|
.initial_connection_window_size($http_opts.initial_connection_window_size)
|
||||||
|
@ -334,6 +375,7 @@ macro_rules! handle_connection_http2 {
|
||||||
.max_send_buf_size($http_opts.max_send_buffer_size)
|
.max_send_buf_size($http_opts.max_send_buffer_size)
|
||||||
.serve_connection($stream_wrapper(stream), svc)
|
.serve_connection($stream_wrapper(stream), svc)
|
||||||
.await;
|
.await;
|
||||||
|
disconnect_tx.notify_one();
|
||||||
drop(permit);
|
drop(permit);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -346,8 +388,17 @@ macro_rules! handle_connection_httpa {
|
||||||
let rth = $rth.clone();
|
let rth = $rth.clone();
|
||||||
let callback_wrapper = $callback.clone();
|
let callback_wrapper = $callback.clone();
|
||||||
$spawner(async move {
|
$spawner(async move {
|
||||||
let svc =
|
let disconnect_guard = std::sync::Arc::new(tokio::sync::Notify::new());
|
||||||
crate::workers::build_service!(local_addr, remote_addr, callback_wrapper, rth, $target, $proto);
|
let disconnect_tx = disconnect_guard.clone();
|
||||||
|
let svc = crate::workers::build_service!(
|
||||||
|
local_addr,
|
||||||
|
remote_addr,
|
||||||
|
callback_wrapper,
|
||||||
|
rth,
|
||||||
|
disconnect_guard,
|
||||||
|
$target,
|
||||||
|
$proto
|
||||||
|
);
|
||||||
let mut conn = hyper_util::server::conn::auto::Builder::new($executor_builder());
|
let mut conn = hyper_util::server::conn::auto::Builder::new($executor_builder());
|
||||||
conn.http1()
|
conn.http1()
|
||||||
.timer(crate::io::TokioTimer::new())
|
.timer(crate::io::TokioTimer::new())
|
||||||
|
@ -366,7 +417,8 @@ macro_rules! handle_connection_httpa {
|
||||||
.max_frame_size($http2_opts.max_frame_size)
|
.max_frame_size($http2_opts.max_frame_size)
|
||||||
.max_header_list_size($http2_opts.max_headers_size)
|
.max_header_list_size($http2_opts.max_headers_size)
|
||||||
.max_send_buf_size($http2_opts.max_send_buffer_size);
|
.max_send_buf_size($http2_opts.max_send_buffer_size);
|
||||||
let _ = conn.$conn_method($stream_wrapper(stream), svc).await;
|
_ = conn.$conn_method($stream_wrapper(stream), svc).await;
|
||||||
|
disconnect_tx.notify_one();
|
||||||
drop(permit);
|
drop(permit);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use hyper::Response;
|
use hyper::Response;
|
||||||
use std::net::SocketAddr;
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
use super::callbacks::call_http;
|
use super::callbacks::call_http;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -19,6 +20,7 @@ fn build_response(status: u16, pyheaders: hyper::HeaderMap, body: HTTPResponseBo
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) async fn handle(
|
pub(crate) async fn handle(
|
||||||
rt: RuntimeRef,
|
rt: RuntimeRef,
|
||||||
|
_disconnect_guard: Arc<Notify>,
|
||||||
callback: ArcCBScheduler,
|
callback: ArcCBScheduler,
|
||||||
server_addr: SocketAddr,
|
server_addr: SocketAddr,
|
||||||
client_addr: SocketAddr,
|
client_addr: SocketAddr,
|
||||||
|
|
|
@ -17,7 +17,7 @@ async def test_scope(rsgi_server, runtime_mode):
|
||||||
data = res.json()
|
data = res.json()
|
||||||
assert data['proto'] == 'http'
|
assert data['proto'] == 'http'
|
||||||
assert data['http_version'] == '1.1'
|
assert data['http_version'] == '1.1'
|
||||||
assert data['rsgi_version'] == '1.4'
|
assert data['rsgi_version'] == '1.5'
|
||||||
assert data['scheme'] == 'http'
|
assert data['scheme'] == 'http'
|
||||||
assert data['method'] == 'GET'
|
assert data['method'] == 'GET'
|
||||||
assert data['path'] == '/info'
|
assert data['path'] == '/info'
|
||||||
|
|
|
@ -62,7 +62,7 @@ async def test_rsgi_scope(rsgi_server, runtime_mode):
|
||||||
data = json.loads(res)
|
data = json.loads(res)
|
||||||
assert data['proto'] == 'ws'
|
assert data['proto'] == 'ws'
|
||||||
assert data['http_version'] == '1.1'
|
assert data['http_version'] == '1.1'
|
||||||
assert data['rsgi_version'] == '1.4'
|
assert data['rsgi_version'] == '1.5'
|
||||||
assert data['scheme'] == 'http'
|
assert data['scheme'] == 'http'
|
||||||
assert data['method'] == 'GET'
|
assert data['method'] == 'GET'
|
||||||
assert data['path'] == '/ws_info'
|
assert data['path'] == '/ws_info'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue