feat: publish Dever core with project scaffolding and versioned AI skills

This commit is contained in:
Antigravity committed 2026-10-04 13:37:30 +00:00
1 parent 70fa73cdc2
commit ac458ffa7f
960 files changed
+221319 -1163

No files matched your search

+548
View File
@@ -0,0 +1,548 @@
use std::sync::Arc;
use std::time::Duration;
use hyper::body::Incoming;
use hyper_util::rt::TokioIo;
use tokio::sync::{Mutex, OwnedSemaphorePermit, mpsc};
use tokio::time::Instant;
use super::client_body::{RequestBody, download, hold_upload, upload};
use super::client_driver::{self, Control, Driver, Entry, Http2Entry, Http2Lease, Http2Pool, Idle};
use super::{Budget, Header, Limits, Request, Response, message};
use crate::{async_stream::AsyncStream, bytes::Bytes, task, tls::ClientTls};
#[derive(Clone, Copy, Debug)]
pub struct PoolLimits {
pub idle_ms: i64,
pub chunk_bytes: i64,
pub read_ms: i64,
}
#[derive(Clone, Debug)]
pub struct StreamRequest {
pub method: String,
pub target: String,
pub headers: Vec<Header>,
pub body: AsyncStream<Result<Bytes, String>>,
}
#[derive(Clone, Debug)]
pub struct StreamResponse {
pub status: i64,
pub headers: Vec<Header>,
pub body: AsyncStream<Result<Bytes, String>>,
}
struct State {
authority: String,
scheme: &'static str,
host: String,
port: i64,
tls: Option<ClientTls>,
budget: Budget,
chunks: usize,
read_timeout: Duration,
control: Arc<Control>,
idle: Arc<Idle>,
http2: Option<Arc<Http2Pool>>,
queue: mpsc::Sender<Driver>,
driver: Mutex<Option<task::Task<()>>>,
}
impl Drop for State {
fn drop(&mut self) {
self.control.close();
}
}
#[derive(Clone)]
pub struct HttpClient(Arc<State>);
impl std::fmt::Debug for HttpClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("HttpClient(<resource>)")
}
}
pub async fn client(
origin: &str,
tls: ClientTls,
limits: Limits,
pool: PoolLimits,
) -> Result<HttpClient, String> {
let budget = limits.check()?;
let idle_timeout = task::positive_duration(pool.idle_ms)?;
let read_timeout = task::positive_duration(pool.read_ms)?;
let chunks = usize::try_from(pool.chunk_bytes)
.ok()
.filter(|size| *size > 0)
.ok_or("HTTP chunk_bytes must be positive")?;
let uri: hyper::Uri = origin
.parse()
.map_err(|error| format!("invalid HTTP origin: {error}"))?;
let scheme = match uri.scheme_str() {
Some("http") => "http",
Some("https") => "https",
_ => return Err("HTTP origin must use http or https".into()),
};
let secure = scheme == "https";
let authority = uri
.authority()
.ok_or("HTTP origin requires a host")?
.as_str()
.to_owned();
if authority.contains('@')
|| origin.contains('#')
|| uri.query().is_some()
|| !matches!(uri.path(), "" | "/")
{
return Err(
"HTTP client origin cannot contain credentials, path, query or fragment".into(),
);
}
let host = uri
.host()
.ok_or("HTTP origin requires a host")?
.trim_matches(['[', ']'])
.to_owned();
let port = i64::from(uri.port_u16().unwrap_or(if secure { 443 } else { 80 }));
let connections = limits.connections as usize;
let request_capacity = budget
.http2
.map(|http2| connections * http2.streams as usize)
.unwrap_or(connections);
let control = Control::new(request_capacity, connections);
let idle = Idle::new(idle_timeout);
let http2 = budget
.http2
.map(|limits| Http2Pool::new(limits.streams as usize, idle_timeout));
let (queue, driver) =
client_driver::start(control.clone(), idle.clone(), http2.clone(), connections).await?;
Ok(HttpClient(Arc::new(State {
authority,
scheme,
host,
port,
tls: secure.then(|| super::protocol::client_tls(tls, budget.http2)),
budget,
chunks,
read_timeout,
control,
idle,
http2,
queue,
driver: Mutex::new(Some(driver)),
})))
}
impl HttpClient {
pub(super) fn control(&self) -> &Arc<Control> {
&self.0.control
}
pub(super) fn chunk_limit(&self) -> usize {
self.0.chunks
}
pub(super) fn read_timeout(&self) -> Duration {
self.0.read_timeout
}
fn encode(
&self,
mut request: Request,
) -> Result<hyper::Request<http_body_util::Full<bytes::Bytes>>, String> {
if !request
.headers
.iter()
.any(|header| header.name.eq_ignore_ascii_case("host"))
{
request.headers.push(Header {
name: "host".into(),
value: Bytes::from_string(self.0.authority.clone()),
});
}
let mut request = message::encode_request(request, self.0.budget)?;
if self.0.http2.is_some() {
message::http2_request(&mut request, self.0.scheme)?;
}
Ok(request)
}
async fn http1_connection(&self) -> Result<Entry, String> {
while let Some(mut entry) = self.0.idle.take() {
if entry.sender.ready().await.is_ok() {
return Ok(entry);
}
// Discard a closed idle transport before sending any request bytes.
}
let permit = self
.0
.control
.connections
.clone()
.acquire_owned()
.await
.map_err(|_| "HTTP client is closed".to_owned())?;
let transport =
crate::transport::connect(&self.0.host, self.0.port, self.0.tls.as_ref()).await?;
super::protocol::check_alpn(&transport, self.0.budget.http2)?;
let (sender, connection) = hyper::client::conn::http1::Builder::new()
.max_buf_size(self.0.budget.headers)
.handshake(TokioIo::new(transport))
.await
.map_err(|error| error.to_string())?;
let (abort, driver) = client_driver::driver(connection, permit);
// The connection permit bounds queued plus running drivers together.
self.0
.queue
.try_send(driver)
.map_err(|_| "HTTP client driver is closed".to_owned())?;
Ok(Entry { sender, abort })
}
async fn new_http2_connection(
&self,
permit: OwnedSemaphorePermit,
) -> Result<Arc<Http2Entry>, String> {
let limits = self.0.budget.http2.expect("HTTP/2 client limits");
let pool = self.0.http2.as_ref().expect("HTTP/2 client pool");
let transport =
crate::transport::connect(&self.0.host, self.0.port, self.0.tls.as_ref()).await?;
super::protocol::check_alpn(&transport, self.0.budget.http2)?;
let (driver, handshake) = client_driver::http2_handshake(
transport,
permit,
limits,
self.0.budget.headers,
self.0.budget.timeout,
Arc::clone(pool),
);
self.0
.queue
.try_send(driver)
.map_err(|_| "HTTP client driver is closed".to_owned())?;
handshake
.await
.map_err(|_| "HTTP client driver closed during HTTP/2 handshake".to_owned())?
}
async fn http2_connection(&self) -> Result<(Arc<Http2Entry>, OwnedSemaphorePermit), String> {
let pool = self.0.http2.as_ref().expect("HTTP/2 client pool");
loop {
let changed = pool.changed();
tokio::pin!(changed);
changed.as_mut().enable();
if let Some(connection) = pool.take() {
return Ok(connection);
}
let connection = self.0.control.connections.clone().acquire_owned();
tokio::pin!(connection);
tokio::select! {
biased;
_ = changed => continue,
permit = &mut connection => {
let permit = permit.map_err(|_| "HTTP client is closed".to_owned())?;
let entry = self.new_http2_connection(permit).await?;
if let Some(stream) = entry.acquire() {
return Ok((entry, stream));
}
}
}
}
}
async fn exchange(
&self,
request: hyper::Request<RequestBody>,
) -> Result<StreamResponse, String> {
if self.0.http2.is_some() {
self.exchange_http2(request).await
} else {
self.exchange_http1(request).await
}
}
async fn exchange_http1(
&self,
request: hyper::Request<RequestBody>,
) -> Result<StreamResponse, String> {
self.0
.control
.while_open(async {
tokio::time::timeout(self.0.budget.timeout, async {
let permit = self
.0
.control
.requests
.clone()
.acquire_owned()
.await
.map_err(|_| "HTTP client is closed".to_owned())?;
let mut entry = self.http1_connection().await?;
let response = entry
.sender
.send_request(request)
.await
.map_err(|error| error.to_string())?;
let lease = Lease::Http1 {
entry: Some(entry),
client: self.clone(),
_permit: permit,
};
self.response(response, lease)
})
.await
.map_err(|_| "HTTP request timed out".to_owned())?
})
.await
}
async fn exchange_http2(
&self,
request: hyper::Request<RequestBody>,
) -> Result<StreamResponse, String> {
self.0
.control
.while_open(async {
tokio::time::timeout(self.0.budget.timeout, async {
let (head, body) = request.into_parts();
let (body, upload_lease) = hold_upload(body);
let mut pending = Some(hyper::Request::from_parts(head, body));
loop {
let request_permit = self
.0
.control
.requests
.clone()
.acquire_owned()
.await
.map_err(|_| "HTTP client is closed".to_owned())?;
let (entry, stream_permit) = self.http2_connection().await?;
let stream = Http2Lease::new(entry.clone(), stream_permit, request_permit);
let mut sender = entry.sender.clone();
if sender.ready().await.is_err() {
entry.drain();
continue;
}
upload_lease.replace(stream.clone());
let request = pending.take().expect("pending HTTP/2 request");
let response = match sender.try_send_request(request).await {
Ok(response) => response,
Err(mut error) => {
if sender.is_closed() {
entry.drain();
}
if let Some(request) = error.take_message() {
upload_lease.clear();
drop(stream);
pending = Some(request);
continue;
}
return Err(error.into_error().to_string());
}
};
let lease = Lease::Http2 {
client: self.clone(),
_stream: stream,
};
return self.response(response, lease);
}
})
.await
.map_err(|_| "HTTP request timed out".to_owned())?
})
.await
}
fn response(
&self,
response: hyper::Response<Incoming>,
lease: Lease,
) -> Result<StreamResponse, String> {
if response.status() == hyper::StatusCode::SWITCHING_PROTOCOLS {
return Err("HTTP client upgrades require the WebSocket API".into());
}
if message::header_size(response.headers()) + 32 > self.0.budget.headers {
return Err("HTTP response headers exceed header_bytes".into());
}
let (head, body) = response.into_parts();
Ok(StreamResponse {
status: i64::from(head.status.as_u16()),
headers: message::decode_headers(head.headers),
body: download(body, lease),
})
}
}
pub(super) enum Lease {
Http1 {
entry: Option<Entry>,
client: HttpClient,
_permit: OwnedSemaphorePermit,
},
Http2 {
client: HttpClient,
_stream: Http2Lease,
},
}
impl Lease {
pub fn client(&self) -> &HttpClient {
match self {
Self::Http1 { client, .. } | Self::Http2 { client, .. } => client,
}
}
pub async fn complete(&mut self) {
let Self::Http1 { entry, client, .. } = self else {
return;
};
let mut entry = entry.take().expect("active HTTP lease");
if !client.control().closed.is_closed() && entry.sender.ready().await.is_ok() {
client.0.idle.put(entry);
}
}
}
pub(super) async fn send_http2(
address: &str,
port: i64,
mut request: hyper::Request<http_body_util::Full<bytes::Bytes>>,
budget: Budget,
) -> Result<Response, String> {
use http_body_util::BodyExt;
message::http2_request(&mut request, "http")?;
let deadline = Instant::now() + budget.timeout;
let timeout_ms = i64::try_from(budget.timeout.as_millis())
.map_err(|_| "HTTP timeout is too large".to_owned())?;
let transport = tokio::time::timeout_at(
deadline,
crate::net::connect_stream_timeout(address, port, timeout_ms),
)
.await
.map_err(|_| "HTTP request timed out".to_owned())??;
let (mut owner, spawner, fault) = task::scoped_owner(())?;
let limits = budget.http2.expect("HTTP/2 send limits");
let builder = limits.client_builder(super::executor::Http2Executor(spawner), budget.headers);
let handshake = {
let handshake = tokio::time::timeout_at(
deadline,
owner.enter(builder.handshake(TokioIo::new(transport))),
);
tokio::pin!(handshake);
tokio::select! {
result = &mut handshake => match result {
Ok(result) => result.map_err(|error| error.to_string()),
Err(_) => Err("HTTP request timed out".to_owned()),
},
error = fault.wait() => Err(error),
}
};
let (mut sender, connection) = match handshake {
Ok(connection) => connection,
Err(error) => {
let _ = owner.stop().await;
return Err(error);
}
};
let request = request.map(|body| body.map_err(|never| match never {}).boxed_unsync());
let exchange = async {
let response = sender
.send_request(request)
.await
.map_err(|error| error.to_string())?;
if response.status() == hyper::StatusCode::SWITCHING_PROTOCOLS {
return Err("HTTP upgrades are not supported by send".into());
}
if message::header_size(response.headers()) + 32 > budget.headers {
return Err("HTTP response headers exceed header_bytes".into());
}
let (head, body) = response.into_parts();
let body = message::collect_body(body, budget.body)
.await
.map_err(|status| format!("HTTP response body rejected: {status}"))?;
Ok(Response {
status: i64::from(head.status.as_u16()),
headers: message::decode_headers(head.headers),
body,
})
};
let result = {
let connection = owner.enter(connection);
tokio::pin!(connection, exchange);
match tokio::time::timeout_at(deadline, async {
tokio::select! {
result = &mut exchange => result,
result = &mut connection => {
result.map_err(|error| error.to_string())?;
exchange.await
}
error = fault.wait() => Err(error),
}
})
.await
{
Ok(result) => result,
Err(_) => Err("HTTP request timed out".to_owned()),
}
};
let cleanup = owner.stop().await;
match result {
Ok(response) => {
cleanup?;
Ok(response)
}
Err(error) => Err(error),
}
}
pub async fn open(client: &HttpClient, request: Request) -> Result<StreamResponse, String> {
use http_body_util::BodyExt;
let request = client
.encode(request)?
.map(|body| body.map_err(|never| match never {}).boxed_unsync());
client.exchange(request).await
}
pub async fn open_stream(
client: &HttpClient,
request: StreamRequest,
) -> Result<StreamResponse, String> {
let head = client.encode(Request {
method: request.method,
target: request.target,
headers: request.headers,
body: Bytes::new(Vec::new()),
})?;
let request = head.map(|_| upload(request.body, client.chunk_limit(), client.read_timeout()));
client.exchange(request).await
}
pub async fn request(client: &HttpClient, request: Request) -> Result<Response, String> {
tokio::time::timeout(client.0.budget.timeout, async {
let response = open(client, request).await?;
let _owner = response.body.close_on_drop();
let mut body = Bytes::new(Vec::new());
while let Some(chunk) = response.body.pull().await {
let chunk = chunk?;
if body.values().len() + chunk.values().len() > client.0.budget.body {
return Err("HTTP response body exceeds body_bytes".into());
}
body = body.concat(&chunk);
}
Ok(Response {
status: response.status,
headers: response.headers,
body,
})
})
.await
.map_err(|_| "HTTP request timed out".to_owned())?
}
pub async fn close_client(client: &HttpClient) -> Result<(), String> {
client.0.control.close();
task::stop_resource(&mut *client.0.driver.lock().await).await
}
@@ -0,0 +1,167 @@
use std::io;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Duration;
use futures_util::stream;
use http_body_util::{BodyExt, StreamBody, combinators::UnsyncBoxBody};
use hyper::body::{Body, Frame, Incoming};
use super::client::Lease;
use super::client_driver::Http2Lease;
use crate::{async_stream::AsyncStream, bytes::Bytes, resource::Pull};
pub(super) type RequestBody = UnsyncBoxBody<bytes::Bytes, io::Error>;
struct LeasedRequestBody {
body: RequestBody,
_lease: UploadLease,
}
#[derive(Clone)]
pub(super) struct UploadLease(Arc<Mutex<Option<Http2Lease>>>);
impl UploadLease {
pub(super) fn replace(&self, lease: Http2Lease) {
*self.0.lock().expect("HTTP/2 upload lease lock") = Some(lease);
}
pub(super) fn clear(&self) {
self.0.lock().expect("HTTP/2 upload lease lock").take();
}
}
impl Body for LeasedRequestBody {
type Data = bytes::Bytes;
type Error = io::Error;
fn poll_frame(
mut self: Pin<&mut Self>,
context: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
Pin::new(&mut self.body).poll_frame(context)
}
fn is_end_stream(&self) -> bool {
self.body.is_end_stream()
}
fn size_hint(&self) -> hyper::body::SizeHint {
self.body.size_hint()
}
}
pub(super) fn hold_upload(body: RequestBody) -> (RequestBody, UploadLease) {
let lease = UploadLease(Arc::new(Mutex::new(None)));
let body = LeasedRequestBody {
body,
_lease: lease.clone(),
}
.boxed_unsync();
(body, lease)
}
pub(super) fn upload(
source: AsyncStream<Result<Bytes, String>>,
maximum: usize,
timeout: Duration,
) -> RequestBody {
let owner = source.close_on_drop();
StreamBody::new(stream::unfold(
(source, owner),
move |(source, owner)| async move {
let chunk = tokio::time::timeout(timeout, source.pull()).await;
let result = match chunk {
Ok(Some(Ok(bytes))) if bytes.values().len() <= maximum => {
Ok(Frame::data(bytes.into_http()))
}
Ok(Some(Ok(_))) => Err(io::Error::other("HTTP upload chunk exceeds chunk_bytes")),
Ok(Some(Err(error))) => Err(io::Error::other(error)),
Ok(None) => return None,
Err(_) => Err(io::Error::new(
io::ErrorKind::TimedOut,
"HTTP upload timed out",
)),
};
if result.is_err() {
source.close();
}
Some((result, (source, owner)))
},
))
.boxed_unsync()
}
struct Download {
body: Incoming,
pending: bytes::Bytes,
lease: Lease,
}
pub(super) fn download(body: Incoming, lease: Lease) -> AsyncStream<Result<Bytes, String>> {
AsyncStream::new(stream::unfold(
Download {
body,
pending: bytes::Bytes::new(),
lease,
},
|mut state| async move {
let client = state.lease.client().clone();
let next = client
.control()
.while_open(async {
tokio::time::timeout(client.read_timeout(), state.next())
.await
.map_err(|_| "HTTP response read timed out".to_owned())?
})
.await;
match next {
Ok(Some((chunk, last))) => {
let item = if last {
Pull::Last(Ok(chunk))
} else {
Pull::Item(Ok(chunk))
};
Some((item, state))
}
Ok(None) => None,
Err(error) => Some((Pull::Last(Err(error)), state)),
}
},
))
}
impl Download {
async fn next(&mut self) -> Result<Option<(Bytes, bool)>, String> {
while self.pending.is_empty() {
if self.body.is_end_stream() {
self.lease.complete().await;
return Ok(None);
}
let Some(frame) = self.body.frame().await else {
self.lease.complete().await;
return Ok(None);
};
let frame = frame.map_err(|error| error.to_string())?;
match frame.into_data() {
Ok(bytes) => self.pending = bytes,
Err(frame)
if frame
.trailers_ref()
.is_some_and(|trailers| !trailers.is_empty()) =>
{
return Err("HTTP response trailers are not supported".into());
}
Err(_) => {}
}
}
let length = self.pending.len().min(self.lease.client().chunk_limit());
let chunk = Bytes::from_http(self.pending.split_to(length));
let last = self.pending.is_empty() && self.body.is_end_stream();
if last {
self.lease.complete().await;
}
Ok(Some((chunk, last)))
}
}
@@ -0,0 +1,443 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use futures_util::future::{AbortHandle, Abortable};
use futures_util::{StreamExt, stream::FuturesUnordered};
use hyper_util::rt::TokioIo;
use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore, mpsc, oneshot};
use tokio::time::Instant;
use super::client_body::RequestBody;
use super::executor::Http2Executor;
use super::protocol::Http2Limits;
use crate::transport::Transport;
pub(super) type Driver = Pin<Box<dyn Future<Output = ()> + Send>>;
pub(super) struct Entry {
pub sender: hyper::client::conn::http1::SendRequest<RequestBody>,
pub abort: AbortHandle,
}
impl Drop for Entry {
fn drop(&mut self) {
self.abort.abort();
}
}
pub(super) struct Idle {
entries: Mutex<Vec<(Instant, Entry)>>,
changed: Notify,
timeout: Duration,
}
impl Idle {
pub fn new(timeout: Duration) -> Arc<Self> {
Arc::new(Self {
entries: Mutex::new(Vec::new()),
changed: Notify::new(),
timeout,
})
}
pub fn take(&self) -> Option<Entry> {
let mut entries = self.entries.lock().expect("pool state lock");
entries.retain(|(deadline, entry)| *deadline > Instant::now() && !entry.sender.is_closed());
entries.pop().map(|(_, entry)| entry)
}
pub fn put(&self, entry: Entry) {
self.entries
.lock()
.expect("pool state lock")
.push((Instant::now() + self.timeout, entry));
self.changed.notify_one();
}
fn deadline(&self) -> Option<Instant> {
let mut entries = self.entries.lock().expect("pool state lock");
entries.retain(|(deadline, entry)| *deadline > Instant::now() && !entry.sender.is_closed());
entries.iter().map(|(deadline, _)| *deadline).min()
}
}
pub(super) struct Control {
pub closed: Semaphore,
pub requests: Arc<Semaphore>,
pub connections: Arc<Semaphore>,
}
impl Control {
pub fn new(requests: usize, connections: usize) -> Arc<Self> {
Arc::new(Self {
closed: Semaphore::new(0),
requests: Arc::new(Semaphore::new(requests)),
connections: Arc::new(Semaphore::new(connections)),
})
}
pub fn close(&self) {
self.closed.close();
self.requests.close();
self.connections.close();
}
pub async fn while_open<T>(
&self,
future: impl Future<Output = Result<T, String>>,
) -> Result<T, String> {
tokio::select! {
biased;
_ = self.closed.acquire() => Err("HTTP client is closed".into()),
result = future => result,
}
}
}
pub(super) struct Http2Entry {
pub sender: hyper::client::conn::http2::SendRequest<RequestBody>,
streams: Arc<Semaphore>,
accepting: AtomicBool,
active: AtomicUsize,
idle_deadline: Mutex<Option<Instant>>,
abort: AbortHandle,
changed: Arc<Notify>,
idle_timeout: Duration,
}
impl Http2Entry {
fn new(
sender: hyper::client::conn::http2::SendRequest<RequestBody>,
streams: usize,
abort: AbortHandle,
changed: Arc<Notify>,
idle_timeout: Duration,
) -> Arc<Self> {
Arc::new(Self {
sender,
streams: Arc::new(Semaphore::new(streams)),
accepting: AtomicBool::new(true),
active: AtomicUsize::new(0),
idle_deadline: Mutex::new(Some(Instant::now() + idle_timeout)),
abort,
changed,
idle_timeout,
})
}
pub fn acquire(self: &Arc<Self>) -> Option<OwnedSemaphorePermit> {
if !self.accepting.load(Ordering::Acquire) || self.sender.is_closed() {
self.drain();
return None;
}
let permit = self.streams.clone().try_acquire_owned().ok()?;
if !self.accepting.load(Ordering::Acquire) || self.sender.is_closed() {
self.drain();
return None;
}
self.active.fetch_add(1, Ordering::AcqRel);
*self.idle_deadline.lock().expect("HTTP/2 pool state lock") = None;
Some(permit)
}
fn release(&self) {
let previous = self.active.fetch_sub(1, Ordering::AcqRel);
debug_assert!(previous > 0, "active HTTP/2 stream count");
if previous == 1 && self.accepting.load(Ordering::Acquire) {
*self.idle_deadline.lock().expect("HTTP/2 pool state lock") =
Some(Instant::now() + self.idle_timeout);
}
self.changed.notify_waiters();
}
pub fn drain(&self) {
if self.accepting.swap(false, Ordering::AcqRel) {
self.streams.close();
*self.idle_deadline.lock().expect("HTTP/2 pool state lock") = None;
self.changed.notify_waiters();
}
}
fn retire(&self) {
self.drain();
self.abort.abort();
}
fn deadline(&self) -> Option<Instant> {
*self.idle_deadline.lock().expect("HTTP/2 pool state lock")
}
}
pub(super) struct Http2Pool {
entries: Mutex<Vec<Arc<Http2Entry>>>,
changed: Arc<Notify>,
streams: usize,
idle_timeout: Duration,
}
impl Http2Pool {
pub fn new(streams: usize, idle_timeout: Duration) -> Arc<Self> {
Arc::new(Self {
entries: Mutex::new(Vec::new()),
changed: Arc::new(Notify::new()),
streams,
idle_timeout,
})
}
pub fn take(&self) -> Option<(Arc<Http2Entry>, OwnedSemaphorePermit)> {
let mut entries = self.entries.lock().expect("HTTP/2 pool state lock");
entries.retain(|entry| entry.accepting.load(Ordering::Acquire));
entries
.iter()
.find_map(|entry| entry.acquire().map(|permit| (Arc::clone(entry), permit)))
}
pub fn add(
&self,
sender: hyper::client::conn::http2::SendRequest<RequestBody>,
abort: AbortHandle,
) -> Arc<Http2Entry> {
let entry = Http2Entry::new(
sender,
self.streams,
abort,
Arc::clone(&self.changed),
self.idle_timeout,
);
self.entries
.lock()
.expect("HTTP/2 pool state lock")
.push(Arc::clone(&entry));
self.changed.notify_waiters();
entry
}
pub fn changed(&self) -> tokio::sync::futures::OwnedNotified {
self.changed.clone().notified_owned()
}
fn expire_and_deadline(&self) -> Option<Instant> {
let now = Instant::now();
let mut entries = self.entries.lock().expect("HTTP/2 pool state lock");
entries.retain(|entry| {
if !entry.accepting.load(Ordering::Acquire) || entry.sender.is_closed() {
entry.drain();
return false;
}
if entry.active.load(Ordering::Acquire) == 0
&& entry.deadline().is_some_and(|deadline| deadline <= now)
{
entry.retire();
return false;
}
true
});
entries.iter().filter_map(|entry| entry.deadline()).min()
}
fn close(&self) {
let entries = std::mem::take(&mut *self.entries.lock().expect("HTTP/2 pool state lock"));
for entry in entries {
entry.retire();
}
self.changed.notify_waiters();
}
}
#[derive(Clone)]
pub(super) struct Http2Lease {
_owner: Arc<Http2LeaseOwner>,
}
struct Http2LeaseOwner {
entry: Arc<Http2Entry>,
stream: Option<OwnedSemaphorePermit>,
request: Option<OwnedSemaphorePermit>,
}
impl Http2Lease {
pub fn new(
entry: Arc<Http2Entry>,
stream: OwnedSemaphorePermit,
request: OwnedSemaphorePermit,
) -> Self {
Self {
_owner: Arc::new(Http2LeaseOwner {
entry,
stream: Some(stream),
request: Some(request),
}),
}
}
}
impl Drop for Http2LeaseOwner {
fn drop(&mut self) {
self.stream.take();
self.request.take();
self.entry.release();
}
}
struct DriverOwner {
control: Arc<Control>,
idle: Arc<Idle>,
http2: Option<Arc<Http2Pool>>,
}
impl Drop for DriverOwner {
fn drop(&mut self) {
self.control.close();
self.idle.entries.lock().expect("pool state lock").clear();
if let Some(http2) = &self.http2 {
http2.close();
}
}
}
pub(super) async fn start(
control: Arc<Control>,
idle: Arc<Idle>,
http2: Option<Arc<Http2Pool>>,
capacity: usize,
) -> Result<(mpsc::Sender<Driver>, crate::task::Task<()>), String> {
let (queue, mut receiver) = mpsc::channel::<Driver>(capacity);
// The parent may finish before this future is first polled.
let owner = DriverOwner {
control: control.clone(),
idle: idle.clone(),
http2: http2.clone(),
};
let task = crate::task::run(async move {
let _owner = owner;
let mut drivers = FuturesUnordered::<Driver>::new();
loop {
let deadline = [
idle.deadline(),
http2.as_ref().and_then(|pool| pool.expire_and_deadline()),
]
.into_iter()
.flatten()
.min();
let expire = async {
match deadline {
Some(deadline) => tokio::time::sleep_until(deadline).await,
None => std::future::pending().await,
}
};
let http2_changed = async {
match &http2 {
Some(pool) => pool.changed().await,
None => std::future::pending().await,
}
};
tokio::select! {
biased;
_ = control.closed.acquire() => return Ok(()),
Some(()) = drivers.next(), if !drivers.is_empty() => {},
_ = idle.changed.notified() => {},
_ = http2_changed => {},
_ = expire => {},
next = receiver.recv() => match next {
Some(driver) => drivers.push(driver),
None => return Ok(()),
},
}
}
})
.await?;
Ok((queue, task))
}
pub(super) fn driver(
connection: impl Future<Output = Result<(), hyper::Error>> + Send + 'static,
permit: tokio::sync::OwnedSemaphorePermit,
) -> (AbortHandle, Driver) {
let (abort, registration) = AbortHandle::new_pair();
let driver = Box::pin(async move {
let _permit = permit;
// Protocol/I/O failures are observed through SendRequest/Incoming.
let _completion = Abortable::new(connection, registration).await;
});
(abort, driver)
}
async fn drive_http2<C>(
connection: C,
mut owner: crate::task::ScopedOwner,
fault: crate::task::ScopeFault,
entry: Arc<Http2Entry>,
registration: futures_util::future::AbortRegistration,
) where
C: Future<Output = Result<(), hyper::Error>> + Send + 'static,
{
let completion = {
let connection = Abortable::new(owner.enter(connection), registration);
tokio::pin!(connection);
tokio::select! {
result = &mut connection => Some(result),
_ = fault.wait() => None,
}
};
entry.drain();
match completion {
Some(Ok(Ok(()))) => {
let _ = owner.finish().await;
}
Some(Ok(Err(_))) | Some(Err(_)) | None => {
let _ = owner.stop().await;
}
}
}
pub(super) fn http2_handshake(
transport: Transport,
permit: OwnedSemaphorePermit,
limits: Http2Limits,
headers: usize,
timeout: Duration,
pool: Arc<Http2Pool>,
) -> (Driver, oneshot::Receiver<Result<Arc<Http2Entry>, String>>) {
let (result, receiver) = oneshot::channel();
let driver = Box::pin(async move {
let (mut owner, spawner, fault) = match crate::task::scoped_owner(permit) {
Ok(owner) => owner,
Err(error) => {
let _ = result.send(Err(error));
return;
}
};
let handshake = {
let builder = limits.client_builder(Http2Executor(spawner), headers);
let handshake = tokio::time::timeout(
timeout,
owner.enter(builder.handshake(TokioIo::new(transport))),
);
tokio::pin!(handshake);
tokio::select! {
result = &mut handshake => match result {
Ok(result) => result.map_err(|error| error.to_string()),
Err(_) => Err("HTTP/2 handshake timed out".to_owned()),
},
error = fault.wait() => Err(error),
}
};
let (sender, connection) = match handshake {
Ok(connection) => connection,
Err(error) => {
let _ = result.send(Err(error));
let _ = owner.stop().await;
return;
}
};
let (abort, registration) = AbortHandle::new_pair();
let entry = pool.add(sender, abort);
let _ = result.send(Ok(Arc::clone(&entry)));
drive_http2(connection, owner, fault, entry, registration).await;
});
(driver, receiver)
}
+17
View File
@@ -0,0 +1,17 @@
use std::future::Future;
use hyper::rt::Executor;
use crate::task::ScopedSpawner;
#[derive(Clone)]
pub(super) struct Http2Executor(pub(super) ScopedSpawner);
impl<F> Executor<F> for Http2Executor
where
F: Future<Output = ()> + Send + 'static,
{
fn execute(&self, future: F) {
self.0.spawn(future);
}
}
+289
View File
@@ -0,0 +1,289 @@
use std::future::Future;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use tokio::sync::Mutex;
use super::reply::{HttpReply, LiveLimits, Outgoing, Session};
use super::write_timeout::WriteTimeout;
use super::{Budget, Limits, Request, RequestContext, read_request, reject};
use crate::{net, task};
/// HTTP/1 每条连接最多一个活跃 handler;升级后仍占用同一个连接和任务名额。
pub async fn serve_live<F, Fut>(
route: F,
listener: net::Listener,
limits: Limits,
live: LiveLimits,
) -> Result<(), String>
where
F: Fn(Request, HttpReply) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<(), String>> + Send + 'static,
{
serve_transport(route, listener, limits, live, None).await
}
pub async fn serve_live_tls<F, Fut>(
route: F,
listener: net::Listener,
limits: Limits,
live: LiveLimits,
tls: crate::tls::ServerTls,
) -> Result<(), String>
where
F: Fn(Request, HttpReply) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<(), String>> + Send + 'static,
{
serve_transport(route, listener, limits, live, Some(tls)).await
}
async fn serve_transport<F, Fut>(
route: F,
listener: net::Listener,
limits: Limits,
live: LiveLimits,
tls: Option<crate::tls::ServerTls>,
) -> Result<(), String>
where
F: Fn(Request, HttpReply) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<(), String>> + Send + 'static,
{
let budget = limits.check()?;
live.check()?;
let tls = tls.map(|tls| super::protocol::server_tls(tls, budget.http2));
task::parallel_each_stream(
net::incoming(listener.clone(), true),
limits.connections,
move |connection| {
let route = route.clone();
let tls = tls.clone();
let listener = listener.clone();
async move {
let stream = connection?;
let stream = match crate::tls::accept(stream, tls.as_ref(), limits.timeout_ms).await
{
Ok(stream) => stream,
Err(_) => return Ok(()),
};
if super::protocol::check_alpn(&stream, budget.http2).is_err() {
return Ok(());
}
if let Some(http2) = budget.http2 {
return serve_http2(route, listener, stream, budget, live, http2).await;
}
let handlers = Arc::new(Mutex::new(Some(task::Group::new(1)?)));
let upgraded = Arc::new(AtomicBool::new(false));
let service = {
let handlers = handlers.clone();
let upgraded = upgraded.clone();
service_fn(move |request| {
dispatch(
request,
route.clone(),
budget,
live,
handlers.clone(),
upgraded.clone(),
)
})
};
let connection = super::server_builder(budget)
.serve_connection(
TokioIo::new(WriteTimeout::new(stream, budget.timeout)),
service,
)
.with_upgrades();
tokio::pin!(connection);
let result = tokio::select! {
result = &mut connection => result,
_ = listener.closed() => {
connection.as_mut().graceful_shutdown();
connection.await
}
};
let group = handlers
.lock()
.await
.take()
.expect("connection owns handlers");
if result.is_ok() && upgraded.load(Ordering::Acquire) {
group.wait().await?;
} else {
group.stop().await?;
}
super::connection_result(result)
}
},
)
.await
}
async fn serve_http2<F, Fut>(
route: F,
listener: net::Listener,
stream: crate::transport::Transport,
budget: Budget,
live: LiveLimits,
http2: super::Http2Limits,
) -> Result<(), String>
where
F: Fn(Request, HttpReply) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<(), String>> + Send + 'static,
{
let (mut owner, spawner, fault) = task::scoped_owner(())?;
let first_request = super::server_h2::FirstRequest::new();
let service = {
let first_request = first_request.clone();
service_fn(move |request| {
first_request.mark();
let route = route.clone();
dispatch_http2(request, route, budget, live)
})
};
let mut builder = http2.server_builder(super::executor::Http2Executor(spawner), budget.headers);
super::server_h2::configure_liveness(&mut builder, budget.timeout);
let connection = builder.serve_connection(
hyper_util::rt::TokioIo::new(WriteTimeout::new(stream, budget.timeout)),
service,
);
let result = owner
.enter(super::server_h2::drive(
connection,
&listener,
&first_request,
budget.timeout,
&fault,
|connection| connection.graceful_shutdown(),
))
.await;
super::server_h2::complete(owner, result).await
}
async fn dispatch<F, Fut>(
mut request: hyper::Request<hyper::body::Incoming>,
route: F,
budget: Budget,
live: LiveLimits,
handlers: Arc<Mutex<Option<task::Group>>>,
upgraded: Arc<AtomicBool>,
) -> Result<hyper::Response<Outgoing>, std::convert::Infallible>
where
F: Fn(Request, HttpReply) -> Fut + Send + 'static,
Fut: Future<Output = Result<(), String>> + Send + 'static,
{
let upgrade = if request.headers().contains_key(hyper::header::UPGRADE) {
let upgrade = hyper::upgrade::on(&mut request);
let mut handshake = hyper::Request::new(());
*handshake.method_mut() = request.method().clone();
*handshake.uri_mut() = request.uri().clone();
*handshake.version_mut() = request.version();
*handshake.headers_mut() = request.headers().clone();
Some((handshake, upgrade))
} else {
None
};
let request = match read_request(request, budget).await {
Ok(request) => request,
Err(status) => return Ok(reject(status, budget).map(Outgoing::Full)),
};
let context = RequestContext::new("http/1.1", request.target.clone(), None);
let (session, head) = Session::new(budget, live, upgrade, upgraded);
let reply = session.reply();
let state = reply.clone();
let handler_context = context.clone();
let started = handlers
.lock()
.await
.as_mut()
.expect("active connection")
.run(async move {
let result = session.run(route(request, reply)).await;
if let Err(error) = result {
handler_context.report(error);
}
Ok(())
})
.await;
if let Err(error) = started {
context.report(error);
return Ok(reject(hyper::StatusCode::INTERNAL_SERVER_ERROR, budget).map(Outgoing::Full));
}
match tokio::time::timeout(budget.timeout, head).await {
Ok(Ok(response)) => Ok(response),
Ok(Err(_)) => {
if state.fault().is_none() {
context.report("HTTP live handler ended without a response");
}
Ok(reject(hyper::StatusCode::INTERNAL_SERVER_ERROR, budget).map(Outgoing::Full))
}
Err(_) => Ok(reject(hyper::StatusCode::GATEWAY_TIMEOUT, budget).map(Outgoing::Full)),
}
}
async fn dispatch_http2<F, Fut>(
request: hyper::Request<hyper::body::Incoming>,
route: F,
budget: Budget,
live: LiveLimits,
) -> Result<hyper::Response<Outgoing>, std::convert::Infallible>
where
F: Fn(Request, HttpReply) -> Fut + Send + 'static,
Fut: Future<Output = Result<(), String>> + Send + 'static,
{
if request.method() == hyper::Method::CONNECT
|| request.headers().contains_key(hyper::header::UPGRADE)
{
return Ok(reject(hyper::StatusCode::NOT_IMPLEMENTED, budget).map(Outgoing::Full));
}
let request = match read_request(request, budget).await {
Ok(request) => request,
Err(status) => return Ok(reject(status, budget).map(Outgoing::Full)),
};
let context = RequestContext::new("http/2", request.target.clone(), None);
let (session, head) = Session::new(budget, live, None, Arc::new(AtomicBool::new(false)));
let reply = session.reply();
let state = reply.clone();
let mut handler = match task::Group::new(1) {
Ok(handler) => handler,
Err(error) => {
context.report(error);
return Ok(reject(hyper::StatusCode::INTERNAL_SERVER_ERROR, budget).map(Outgoing::Full));
}
};
let handler_context = context.clone();
let started = handler
.run(async move {
let result = session.run(route(request, reply)).await;
if let Err(error) = result {
handler_context.report(error);
}
Ok(())
})
.await;
if let Err(error) = started {
context.report(error);
return Ok(reject(hyper::StatusCode::INTERNAL_SERVER_ERROR, budget).map(Outgoing::Full));
}
match tokio::time::timeout(budget.timeout, head).await {
Ok(Ok(response)) => Ok(response.map(|body| body.with_handler(handler))),
Ok(Err(_)) => {
if let Err(error) = handler.wait().await {
context.report(error);
} else if state.fault().is_none() {
context.report("HTTP live handler ended without a response");
}
Ok(reject(hyper::StatusCode::INTERNAL_SERVER_ERROR, budget).map(Outgoing::Full))
}
Err(_) => match handler.stop().await {
Ok(()) => Ok(reject(hyper::StatusCode::GATEWAY_TIMEOUT, budget).map(Outgoing::Full)),
Err(error) => {
context.report(error);
Ok(reject(hyper::StatusCode::GATEWAY_TIMEOUT, budget).map(Outgoing::Full))
}
},
}
}
+208
View File
@@ -0,0 +1,208 @@
use http_body_util::{BodyExt, Full, LengthLimitError, Limited};
use hyper::body::{Body, Incoming};
use hyper::header::{CONTENT_LENGTH, HOST, TRANSFER_ENCODING, UPGRADE};
use hyper::{HeaderMap, Method, StatusCode, Uri};
use super::{Budget, Header, Request, Response};
use crate::bytes::Bytes;
#[derive(Clone, Copy, Eq, PartialEq)]
enum MessageKind {
Request,
Response,
}
pub(super) fn header_size(headers: &HeaderMap) -> usize {
headers
.iter()
.map(|(name, value)| name.as_str().len() + value.as_bytes().len() + 4)
.sum::<usize>()
+ 2
}
pub(super) fn decode_headers(headers: HeaderMap) -> Vec<Header> {
headers
.iter()
.map(|(name, value)| Header {
name: name.to_string(),
value: Bytes::new(value.as_bytes().to_vec()),
})
.collect()
}
fn encode_headers(
headers: Vec<Header>,
budget: Budget,
kind: MessageKind,
) -> Result<HeaderMap, String> {
let mut result = HeaderMap::new();
let mut length = 2;
for header in headers {
length += header.name.len() + header.value.values().len() + 4;
if length > budget.headers {
return Err("HTTP headers exceed header_bytes".into());
}
let name = hyper::header::HeaderName::from_bytes(header.name.as_bytes())
.map_err(|error| error.to_string())?;
if name == CONTENT_LENGTH || name == TRANSFER_ENCODING {
return Err("HTTP framing headers are owned by the engine".into());
}
if name == UPGRADE {
return Err("HTTP upgrades are not supported yet".into());
}
let value = hyper::header::HeaderValue::from_maybe_shared(header.value.into_http())
.map_err(|error| error.to_string())?;
if budget.http2.is_some() {
check_http2_header(name.as_str(), value.as_bytes(), kind)?;
}
result
.try_append(name, value)
.map_err(|error| error.to_string())?;
}
Ok(result)
}
fn check_http2_header(name: &str, value: &[u8], kind: MessageKind) -> Result<(), String> {
if matches!(
name,
"connection" | "keep-alive" | "proxy-connection" | "transfer-encoding" | "upgrade"
) || (name == "te"
&& (kind == MessageKind::Response || !value.eq_ignore_ascii_case(b"trailers")))
{
return Err(format!("HTTP/2 forbids connection-specific header {name}"));
}
Ok(())
}
// Source 继续使用 origin-form;scheme/authority 只在 HTTP/2 线协议边界补齐。
pub(super) fn http2_request<B>(
request: &mut hyper::Request<B>,
scheme: &str,
) -> Result<(), String> {
let host = request
.headers()
.get(HOST)
.ok_or("HTTP request requires Host")?
.to_str()
.map_err(|error| error.to_string())?;
let authority = host
.parse::<hyper::http::uri::Authority>()
.map_err(|error| error.to_string())?;
if authority.as_str().contains('@') {
return Err("HTTP/2 authority cannot contain credentials".into());
}
let mut uri = request.uri().clone().into_parts();
uri.scheme = Some(
scheme
.parse::<hyper::http::uri::Scheme>()
.map_err(|error| error.to_string())?,
);
uri.authority = Some(authority);
*request.uri_mut() = Uri::from_parts(uri).map_err(|error| error.to_string())?;
*request.version_mut() = hyper::Version::HTTP_2;
Ok(())
}
fn check_message(
headers: &HeaderMap,
body: &Bytes,
start_bytes: usize,
budget: Budget,
) -> Result<(), String> {
// 预留引擎生成的 Content-Length 和 Date;不截断用户内容。
if header_size(headers) + start_bytes + 80 > budget.headers {
return Err("HTTP headers exceed header_bytes".into());
}
if body.values().len() > budget.body {
return Err("HTTP body exceeds body_bytes".into());
}
Ok(())
}
pub(super) fn encode_request(
request: Request,
budget: Budget,
) -> Result<hyper::Request<Full<bytes::Bytes>>, String> {
let method =
Method::from_bytes(request.method.as_bytes()).map_err(|error| error.to_string())?;
let uri = request
.target
.parse::<Uri>()
.map_err(|error| error.to_string())?;
if method == Method::CONNECT
|| uri.scheme().is_some()
|| uri.authority().is_some()
|| !(request.target.starts_with('/')
|| (method == Method::OPTIONS && request.target == "*"))
{
return Err(
"HTTP send requires an origin-form target or OPTIONS *; CONNECT is not supported"
.into(),
);
}
let headers = encode_headers(request.headers, budget, MessageKind::Request)?;
if headers.get_all(HOST).iter().count() != 1 {
return Err("HTTP request requires exactly one Host header".into());
}
let start_bytes = request.method.len() + request.target.len() + 14;
check_message(&headers, &request.body, start_bytes, budget)?;
let mut result = hyper::Request::new(Full::new(request.body.into_http()));
*result.method_mut() = method;
*result.uri_mut() = uri;
*result.headers_mut() = headers;
Ok(result)
}
pub(super) fn encode_response(
response: Response,
budget: Budget,
) -> Result<hyper::Response<Full<bytes::Bytes>>, String> {
let status = u16::try_from(response.status)
.ok()
.filter(|status| (200..=599).contains(status))
.ok_or("HTTP response status must be between 200 and 599")?;
let status = StatusCode::from_u16(status).map_err(|error| error.to_string())?;
let no_body = matches!(
status,
StatusCode::NO_CONTENT | StatusCode::RESET_CONTENT | StatusCode::NOT_MODIFIED
);
if no_body && !response.body.values().is_empty() {
return Err("HTTP 204, 205 and 304 responses cannot contain a body".into());
}
let headers = encode_headers(response.headers, budget, MessageKind::Response)?;
check_message(&headers, &response.body, 32, budget)?;
let mut result = hyper::Response::new(Full::new(response.body.into_http()));
*result.status_mut() = status;
*result.headers_mut() = headers;
Ok(result)
}
pub(super) async fn collect_body(body: Incoming, limit: usize) -> Result<Bytes, StatusCode> {
if body.size_hint().lower() > limit as u64 {
return Err(StatusCode::PAYLOAD_TOO_LARGE);
}
let mut incoming = Limited::new(body, limit);
let mut body = Bytes::new(Vec::new());
// 单帧直接共享,多帧逐步扩容;不为大量微小帧保留一份元数据队列。
while let Some(frame) = incoming.frame().await {
let frame = frame.map_err(|error| {
if error.is::<LengthLimitError>() {
StatusCode::PAYLOAD_TOO_LARGE
} else {
StatusCode::BAD_REQUEST
}
})?;
match frame.into_data() {
Ok(bytes) => body = body.concat(&Bytes::from_http(bytes)),
Err(frame)
if frame
.trailers_ref()
.is_some_and(|trailers| !trailers.is_empty()) =>
{
return Err(StatusCode::NOT_IMPLEMENTED);
}
Err(_) => {}
}
}
Ok(body)
}
+550
View File
@@ -0,0 +1,550 @@
mod client;
mod client_body;
mod client_driver;
mod executor;
mod live;
mod message;
mod protocol;
pub(crate) mod reply;
mod server_h2;
mod write_timeout;
pub use protocol::Http2Limits;
pub use client::{
HttpClient, PoolLimits, StreamRequest, StreamResponse, client, close_client, open, open_stream,
request,
};
pub use live::{serve_live, serve_live_tls};
pub use reply::{HttpReply, LiveLimits, finish, respond, start, write};
use std::convert::Infallible;
use std::future::Future;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::service::service_fn;
use hyper::{Method, StatusCode};
use hyper_util::rt::{TokioIo, TokioTimer};
use crate::bytes::Bytes;
use crate::{net, task};
use message::{collect_body, decode_headers, encode_request, encode_response, header_size};
use write_timeout::WriteTimeout;
static NEXT_REQUEST_ID: AtomicU64 = AtomicU64::new(1);
tokio::task_local! { static CURRENT_REQUEST: RequestContext; }
#[cfg(feature = "wire")]
pub(crate) fn request_id() -> u64 {
CURRENT_REQUEST
.try_with(|request| request.id)
.unwrap_or_else(|_| NEXT_REQUEST_ID.fetch_add(1, Ordering::Relaxed))
}
#[cfg(feature = "wire")]
pub(crate) fn client_address() -> Option<String> {
CURRENT_REQUEST
.try_with(|request| request.client_address.clone())
.ok()
.flatten()
}
#[derive(Clone)]
struct RequestContext {
id: u64,
protocol: &'static str,
target: String,
client_address: Option<String>,
}
impl RequestContext {
fn new(protocol: &'static str, target: String, client_address: Option<String>) -> Self {
Self {
id: NEXT_REQUEST_ID.fetch_add(1, Ordering::Relaxed),
protocol,
target,
client_address,
}
}
fn report(&self, error: impl Into<String>) {
crate::log::error(
error,
vec![
crate::log::Field {
name: "boundary".into(),
value: "http".into(),
},
crate::log::Field {
name: "request_id".into(),
value: self.id.to_string(),
},
crate::log::Field {
name: "protocol".into(),
value: self.protocol.into(),
},
crate::log::Field {
name: "target".into(),
value: self.target.clone(),
},
],
);
}
}
#[derive(Clone, Debug)]
pub struct Header {
pub name: String,
pub value: Bytes,
}
#[derive(Clone, Debug)]
pub struct Request {
pub method: String,
pub target: String,
pub headers: Vec<Header>,
pub body: Bytes,
}
/// Server-only body ownership. Source-level HTTP values remain bounded Request values.
pub struct IncomingRequest {
pub head: Request,
pub body: Incoming,
}
#[derive(Clone, Debug)]
pub struct Response {
pub status: i64,
pub headers: Vec<Header>,
pub body: Bytes,
}
#[derive(Clone, Copy, Debug)]
pub struct Limits {
pub header_bytes: i64,
pub body_bytes: i64,
pub timeout_ms: i64,
pub connections: i64,
pub http2: Option<Http2Limits>,
}
#[derive(Clone, Copy)]
struct Budget {
headers: usize,
body: usize,
timeout: Duration,
http2: Option<Http2Limits>,
}
impl Limits {
fn check(self) -> Result<Budget, String> {
let headers = usize::try_from(self.header_bytes)
.ok()
.filter(|value| *value >= 8192)
.ok_or("HTTP header_bytes must be at least 8192")?;
let body =
usize::try_from(self.body_bytes).map_err(|_| "HTTP body_bytes must be nonnegative")?;
let millis = u64::try_from(self.timeout_ms)
.ok()
.filter(|value| *value > 0)
.ok_or("HTTP timeout_ms must be positive")?;
if !(1..=65536).contains(&self.connections) {
return Err("HTTP connections must be between 1 and 65536".into());
}
if let Some(http2) = self.http2 {
http2.check(self.connections, headers)?;
}
Ok(Budget {
headers,
body,
timeout: Duration::from_millis(millis),
http2: self.http2,
})
}
}
/// 连接由现有任务组持有,取消 serve 会沿 Scope 排空连接和 handler 子任务。
pub async fn serve<F, Fut>(route: F, listener: net::Listener, limits: Limits) -> Result<(), String>
where
F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<Response, String>> + Send + 'static,
{
serve_buffered(route, listener, limits, None).await
}
pub async fn serve_tls<F, Fut>(
route: F,
listener: net::Listener,
limits: Limits,
tls: crate::tls::ServerTls,
) -> Result<(), String>
where
F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<Response, String>> + Send + 'static,
{
serve_buffered(route, listener, limits, Some(tls)).await
}
async fn serve_buffered<F, Fut>(
route: F,
listener: net::Listener,
limits: Limits,
tls: Option<crate::tls::ServerTls>,
) -> Result<(), String>
where
F: Fn(Request) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<Response, String>> + Send + 'static,
{
let budget = limits.check()?;
serve_transport(
move |incoming: IncomingRequest| {
let route = route.clone();
async move {
let mut request = incoming.head;
request.body = match tokio::time::timeout(
budget.timeout,
collect_body(incoming.body, budget.body),
)
.await
{
Ok(Ok(body)) => body,
Ok(Err(status)) => {
return Ok(Response {
status: status.as_u16().into(),
headers: Vec::new(),
body: Bytes::new(Vec::new()),
});
}
Err(_) => {
return Ok(Response {
status: 408,
headers: Vec::new(),
body: Bytes::new(Vec::new()),
});
}
};
route(request).await
}
},
listener,
limits,
tls,
)
.await
}
pub async fn serve_incoming<F, Fut>(
route: F,
listener: net::Listener,
limits: Limits,
) -> Result<(), String>
where
F: Fn(IncomingRequest) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<Response, String>> + Send + 'static,
{
serve_transport(route, listener, limits, None).await
}
async fn serve_transport<F, Fut>(
route: F,
listener: net::Listener,
limits: Limits,
tls: Option<crate::tls::ServerTls>,
) -> Result<(), String>
where
F: Fn(IncomingRequest) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<Response, String>> + Send + 'static,
{
let budget = limits.check()?;
let tls = tls.map(|tls| protocol::server_tls(tls, budget.http2));
task::parallel_each_stream(
net::incoming(listener.clone(), true),
limits.connections,
move |connection| {
let route = route.clone();
let tls = tls.clone();
let listener = listener.clone();
async move {
let stream = connection?;
let client_address = stream
.peer_addr()
.ok()
.map(|address| address.ip().to_string());
let stream = match crate::tls::accept(stream, tls.as_ref(), limits.timeout_ms).await
{
Ok(stream) => stream,
Err(_) => return Ok(()), // A peer's TLS failure does not terminate the listener.
};
if protocol::check_alpn(&stream, budget.http2).is_err() {
return Ok(());
}
if let Some(http2) = budget.http2 {
return serve_http2(route, listener, stream, budget, http2, client_address)
.await;
}
let service = service_fn(move |request| {
dispatch(request, route.clone(), budget, client_address.clone())
});
let connection = server_builder(budget).serve_connection(
TokioIo::new(WriteTimeout::new(stream, budget.timeout)),
service,
);
tokio::pin!(connection);
let result = tokio::select! {
result = &mut connection => result,
_ = listener.closed() => {
connection.as_mut().graceful_shutdown();
connection.await
}
};
connection_result(result)
}
},
)
.await
}
async fn serve_http2<F, Fut>(
route: F,
listener: net::Listener,
stream: crate::transport::Transport,
budget: Budget,
http2: Http2Limits,
client_address: Option<String>,
) -> Result<(), String>
where
F: Fn(IncomingRequest) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<Response, String>> + Send + 'static,
{
let (mut owner, spawner, fault) = task::scoped_owner(())?;
let first_request = server_h2::FirstRequest::new();
let service = {
let first_request = first_request.clone();
service_fn(move |request| {
first_request.mark();
let route = route.clone();
dispatch(request, route, budget, client_address.clone())
})
};
let mut builder = http2.server_builder(executor::Http2Executor(spawner), budget.headers);
server_h2::configure_liveness(&mut builder, budget.timeout);
let connection = builder.serve_connection(
TokioIo::new(WriteTimeout::new(stream, budget.timeout)),
service,
);
let result = owner
.enter(server_h2::drive(
connection,
&listener,
&first_request,
budget.timeout,
&fault,
|connection| connection.graceful_shutdown(),
))
.await;
server_h2::complete(owner, result).await
}
fn server_builder(budget: Budget) -> hyper::server::conn::http1::Builder {
let mut builder = hyper::server::conn::http1::Builder::new();
builder
.timer(TokioTimer::new())
.header_read_timeout(budget.timeout)
.max_buf_size(budget.headers);
builder
}
fn connection_result(result: Result<(), hyper::Error>) -> Result<(), String> {
// 对端断开、畸形报文和 I/O 超时只结束该连接;请求故障已在 dispatch 边界处理。
let _ = result;
Ok(())
}
async fn dispatch<F, Fut>(
request: hyper::Request<Incoming>,
route: F,
budget: Budget,
client_address: Option<String>,
) -> Result<hyper::Response<Full<bytes::Bytes>>, Infallible>
where
F: Fn(IncomingRequest) -> Fut,
Fut: Future<Output = Result<Response, String>>,
{
if request.method() == Method::CONNECT || request.headers().contains_key(hyper::header::UPGRADE)
{
return Ok(reject(StatusCode::NOT_IMPLEMENTED, budget));
}
let request = match read_incoming(request, budget) {
Ok(request) => request,
Err(status) => return Ok(reject(status, budget)),
};
let context = RequestContext::new(
if budget.http2.is_some() {
"http/2"
} else {
"http/1.1"
},
request.head.target.clone(),
client_address,
);
let response = match tokio::time::timeout(
budget.timeout,
CURRENT_REQUEST.scope(context.clone(), route(request)),
)
.await
{
Ok(Ok(response)) => response,
Ok(Err(error)) => {
context.report(error);
return Ok(reject(StatusCode::INTERNAL_SERVER_ERROR, budget));
}
Err(_) => return Ok(reject(StatusCode::GATEWAY_TIMEOUT, budget)),
};
match encode_response(response, budget) {
Ok(response) => Ok(response),
Err(error) => {
context.report(error);
Ok(reject(StatusCode::INTERNAL_SERVER_ERROR, budget))
}
}
}
async fn read_request(
request: hyper::Request<Incoming>,
budget: Budget,
) -> Result<Request, StatusCode> {
let incoming = read_incoming(request, budget)?;
let mut request = incoming.head;
request.body = tokio::time::timeout(budget.timeout, collect_body(incoming.body, budget.body))
.await
.map_err(|_| StatusCode::REQUEST_TIMEOUT)??;
Ok(request)
}
fn read_incoming(
mut request: hyper::Request<Incoming>,
budget: Budget,
) -> Result<IncomingRequest, StatusCode> {
if request.method() == Method::CONNECT {
return Err(StatusCode::NOT_IMPLEMENTED);
}
let host_count = request
.headers()
.get_all(hyper::header::HOST)
.iter()
.count();
let target = if request.version() == hyper::Version::HTTP_2 {
let authority = request
.uri()
.authority()
.ok_or(StatusCode::BAD_REQUEST)?
.as_str()
.to_owned();
if host_count > 1 {
return Err(StatusCode::BAD_REQUEST);
}
if let Some(host) = request.headers().get(hyper::header::HOST) {
if !host.as_bytes().eq_ignore_ascii_case(authority.as_bytes()) {
return Err(StatusCode::BAD_REQUEST);
}
} else {
let host = hyper::header::HeaderValue::from_str(&authority)
.map_err(|_| StatusCode::BAD_REQUEST)?;
request.headers_mut().insert(hyper::header::HOST, host);
}
request
.uri()
.path_and_query()
.map(|value| value.as_str())
.unwrap_or("/")
.to_owned()
} else {
if request.version() == hyper::Version::HTTP_11 && host_count != 1 {
return Err(StatusCode::BAD_REQUEST);
}
request.uri().to_string()
};
let start_bytes = request.method().as_str().len() + target.len() + 14;
if header_size(request.headers()) + start_bytes > budget.headers {
return Err(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
}
let (head, body) = request.into_parts();
Ok(IncomingRequest {
head: Request {
method: head.method.to_string(),
target,
headers: decode_headers(head.headers),
body: Bytes::new(Vec::new()),
},
body,
})
}
fn reject(status: StatusCode, budget: Budget) -> hyper::Response<Full<bytes::Bytes>> {
let mut response = hyper::Response::new(Full::new(bytes::Bytes::new()));
*response.status_mut() = status;
if budget.http2.is_none() {
response.headers_mut().insert(
hyper::header::CONNECTION,
hyper::header::HeaderValue::from_static("close"),
);
}
response
}
/// 单次请求独占驱动 future;完成、错误和取消都直接释放连接,不产生后台任务。
pub async fn send(
address: &str,
port: i64,
request: Request,
limits: Limits,
) -> Result<Response, String> {
let budget = limits.check()?;
let request = encode_request(request, budget)?;
if budget.http2.is_some() {
return client::send_http2(address, port, request, budget).await;
}
tokio::time::timeout(budget.timeout, async {
let stream = net::connect_stream_timeout(address, port, limits.timeout_ms).await?;
let (mut sender, connection) = hyper::client::conn::http1::Builder::new()
.max_buf_size(budget.headers)
.handshake(TokioIo::new(stream))
.await
.map_err(|error| error.to_string())?;
let exchange = async {
let response = sender
.send_request(request)
.await
.map_err(|error| error.to_string())?;
if response.status() == StatusCode::SWITCHING_PROTOCOLS {
return Err("HTTP upgrades are not supported by send".into());
}
if header_size(response.headers()) + 32 > budget.headers {
return Err("HTTP response headers exceed header_bytes".into());
}
let (head, body) = response.into_parts();
let body = collect_body(body, budget.body)
.await
.map_err(|status| format!("HTTP response body rejected: {status}"))?;
Ok(Response {
status: i64::from(head.status.as_u16()),
headers: decode_headers(head.headers),
body,
})
};
tokio::pin!(exchange, connection);
tokio::select! {
result = &mut exchange => result,
result = &mut connection => {
result.map_err(|error| error.to_string())?;
// Connection: close 的最后一帧可能已入队,继续取完响应。
exchange.await
}
}
})
.await
.map_err(|_| "HTTP request timed out".to_owned())?
}
+97
View File
@@ -0,0 +1,97 @@
use crate::{
tls::{ClientTls, ServerTls},
transport::Transport,
};
#[derive(Clone, Copy, Debug)]
pub struct Http2Limits {
pub streams: i64,
pub stream_window_bytes: i64,
pub connection_window_bytes: i64,
}
impl Http2Limits {
pub(super) fn check(self, connections: i64, headers: usize) -> Result<(), String> {
if !(1..=65536).contains(&self.streams) || connections * self.streams > 65536 {
return Err("HTTP/2 connections * streams must be between 1 and 65536".into());
}
if !(1..=i32::MAX as i64).contains(&self.stream_window_bytes) {
return Err("HTTP/2 stream_window_bytes must be between 1 and 2147483647".into());
}
if !(65535..=i32::MAX as i64).contains(&self.connection_window_bytes) {
return Err(
"HTTP/2 connection_window_bytes must be between 65535 and 2147483647".into(),
);
}
if u32::try_from(headers).is_err() {
return Err("HTTP/2 header_bytes must fit an unsigned 32-bit limit".into());
}
Ok(())
}
pub(super) fn client_builder<E: Clone>(
self,
executor: E,
headers: usize,
) -> hyper::client::conn::http2::Builder<E> {
let mut builder = hyper::client::conn::http2::Builder::new(executor);
builder
.timer(hyper_util::rt::TokioTimer::new())
.adaptive_window(false)
.initial_stream_window_size(self.stream_window_bytes as u32)
.initial_connection_window_size(self.connection_window_bytes as u32)
.initial_max_send_streams(self.streams as usize)
.max_concurrent_streams(0)
.max_header_list_size(headers as u32)
.max_frame_size(FRAME_BYTES)
.max_send_buf_size(FRAME_BYTES as usize);
builder
}
pub(super) fn server_builder<E>(
self,
executor: E,
headers: usize,
) -> hyper::server::conn::http2::Builder<E> {
let mut builder = hyper::server::conn::http2::Builder::new(executor);
builder
.timer(hyper_util::rt::TokioTimer::new())
.adaptive_window(false)
.initial_stream_window_size(self.stream_window_bytes as u32)
.initial_connection_window_size(self.connection_window_bytes as u32)
.max_concurrent_streams(self.streams as u32)
.max_header_list_size(headers as u32)
.max_frame_size(FRAME_BYTES)
.max_send_buf_size(FRAME_BYTES as usize);
builder
}
}
const FRAME_BYTES: u32 = 16384;
// HTTP 单独派生 ALPN;不能修改 WSS 和其他 TLS 使用者共享的配置。
pub(super) fn client_tls(tls: ClientTls, http2: Option<Http2Limits>) -> ClientTls {
let mut config = (*tls.0).clone();
config.alpn_protocols = vec![alpn(http2).to_vec()];
ClientTls(std::sync::Arc::new(config))
}
pub(super) fn server_tls(tls: ServerTls, http2: Option<Http2Limits>) -> ServerTls {
let mut config = (*tls.0).clone();
config.alpn_protocols = vec![alpn(http2).to_vec()];
ServerTls(std::sync::Arc::new(config))
}
fn alpn(http2: Option<Http2Limits>) -> &'static [u8] {
if http2.is_some() { b"h2" } else { b"http/1.1" }
}
pub(super) fn check_alpn(stream: &Transport, http2: Option<Http2Limits>) -> Result<(), String> {
if let Transport::Tls(stream) = stream
&& http2.is_some()
&& stream.get_ref().1.alpn_protocol() != Some(b"h2")
{
return Err("HTTP/2 TLS peer did not negotiate h2".into());
}
Ok(())
}
+456
View File
@@ -0,0 +1,456 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
};
use std::task::{Context, Poll};
use std::time::Duration;
use http_body_util::Full;
use hyper::body::{Body, Frame, SizeHint};
use tokio::sync::{Mutex as AsyncMutex, Semaphore, mpsc, oneshot};
use tokio::time::Sleep;
use super::{Budget, Header, Response, message::encode_response};
use crate::{bytes::Bytes, task, websocket};
#[derive(Clone, Copy, Debug)]
pub struct LiveLimits {
pub chunk_bytes: i64,
pub idle_ms: i64,
pub heartbeat_ms: i64,
}
impl LiveLimits {
pub(super) fn check(self) -> Result<(), String> {
if self.chunk_bytes <= 0 || usize::try_from(self.chunk_bytes).is_err() {
return Err("HTTP chunk_bytes must be positive".into());
}
if self.idle_ms <= 0 || self.heartbeat_ms <= 0 || self.heartbeat_ms >= self.idle_ms {
return Err("HTTP live limits require 0 < heartbeat_ms < idle_ms".into());
}
Ok(())
}
}
type Head = hyper::Response<Outgoing>;
enum Mode {
Pending,
Streaming {
sender: mpsc::Sender<bytes::Bytes>,
sse: bool,
},
Finished,
Upgraded(Option<websocket::WebSocket>),
}
struct State {
mode: Mode,
head: Option<oneshot::Sender<Head>>,
upgrade: Option<(hyper::Request<()>, hyper::upgrade::OnUpgrade)>,
fault: Option<String>,
body_cancelled: bool,
}
struct Shared {
state: Mutex<State>,
writer: AsyncMutex<()>,
closed: Semaphore,
budget: Budget,
live: LiveLimits,
upgraded: Arc<AtomicBool>,
}
impl Shared {
fn close_body(&self, cancelled: bool) {
if cancelled {
self.state
.lock()
.expect("reply lock poisoned")
.body_cancelled = true;
}
self.closed.close();
}
}
#[derive(Clone)]
pub struct HttpReply(Arc<Shared>);
impl std::fmt::Debug for HttpReply {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("HttpReply(<resource>)")
}
}
impl HttpReply {
pub(crate) fn chunk_limit(&self) -> usize {
self.0.live.chunk_bytes as usize
}
pub(crate) fn fault(&self) -> Option<String> {
self.0
.state
.lock()
.expect("reply lock poisoned")
.fault
.clone()
}
fn publish(&self, prepare: impl FnOnce() -> (Head, Mode)) -> Result<(), String> {
let mut state = self.0.state.lock().expect("reply lock poisoned");
if !matches!(state.mode, Mode::Pending) {
return Err("HTTP response has already started".into());
}
let head = state.head.take().ok_or("HTTP response is closed")?;
// 只有真正提交的正文才能拥有取消信号;拒绝重复提交时不能关闭已有响应。
let (response, mode) = prepare();
state.upgrade.take();
state.mode = mode;
// SendError 会析构响应正文;先释放状态锁,正文 Drop 才能登记传输取消。
drop(state);
if head.send(response).is_err() {
// 空正文从创建起就是 end-stream,不能只依赖 BufferedBody::drop 判断取消。
self.0.close_body(true);
return Err("HTTP peer is closed".into());
}
Ok(())
}
pub(crate) async fn upgrade(
&self,
limits: websocket::Limits,
) -> Result<websocket::WebSocket, String> {
limits.check()?;
let (head, response, upgrade) = {
let mut state = self.0.state.lock().expect("reply lock poisoned");
if !matches!(state.mode, Mode::Pending) {
return Err("HTTP response has already started".into());
}
let (request, upgrade) = state.upgrade.take().ok_or("HTTP upgrade is unavailable")?;
let response =
tokio_tungstenite::tungstenite::handshake::server::create_response(&request)
.map_err(|error| error.to_string())?;
let response = response.map(|()| Outgoing::Full(Full::new(bytes::Bytes::new())));
state.mode = Mode::Upgraded(None);
self.0.upgraded.store(true, Ordering::Release);
let head = state.head.take().ok_or("HTTP response is closed")?;
(head, response, upgrade)
};
if head.send(response).is_err() {
self.0.close_body(true);
return Err("HTTP peer is closed".into());
}
let stream = tokio::time::timeout(self.0.budget.timeout, upgrade)
.await
.map_err(|_| "WebSocket upgrade timed out")?
.map_err(|error| error.to_string())?;
// 取回已知的底层 I/O 和预读帧;升级后不再继承 HTTP 写入期限。
let parts = stream
.downcast::<hyper_util::rt::TokioIo<super::write_timeout::WriteTimeout>>()
.map_err(|_| "WebSocket upgrade transport does not match the HTTP server")?;
let socket = websocket::from_server(
parts.io.into_inner().into_inner(),
parts.read_buf.to_vec(),
limits,
)
.await?;
let mut state = self.0.state.lock().expect("reply lock poisoned");
state.mode = Mode::Upgraded(Some(socket.clone()));
Ok(socket)
}
}
pub(super) struct Session(HttpReply);
impl Session {
pub(super) fn new(
budget: Budget,
live: LiveLimits,
upgrade: Option<(hyper::Request<()>, hyper::upgrade::OnUpgrade)>,
upgraded: Arc<AtomicBool>,
) -> (Self, oneshot::Receiver<Head>) {
let (sender, receiver) = oneshot::channel();
(
Self(HttpReply(Arc::new(Shared {
state: Mutex::new(State {
mode: Mode::Pending,
head: Some(sender),
upgrade,
fault: None,
body_cancelled: false,
}),
writer: AsyncMutex::new(()),
closed: Semaphore::new(0),
budget,
live,
upgraded,
}))),
receiver,
)
}
pub(super) fn reply(&self) -> HttpReply {
self.0.clone()
}
pub(super) async fn run(
self,
route: impl Future<Output = Result<(), String>>,
) -> Result<(), String> {
let result = tokio::select! {
biased;
result = route => result,
_ = self.0.0.closed.acquire() => Ok(()),
};
if let Err(error) = &result {
let mut state = self.0.0.state.lock().expect("reply lock poisoned");
// 传输侧先取消正文时,随后被唤醒的 write 失败属于该流,不是程序故障。
if state.body_cancelled {
return Ok(());
}
state.fault = Some(error.clone());
}
result
}
}
impl Drop for Session {
fn drop(&mut self) {
let mut state = self.0.0.state.lock().expect("reply lock poisoned");
// 函数返回或取消都释放正文发送端/升级连接;外部别名不能延长会话。
if let Mode::Upgraded(Some(socket)) = &state.mode {
socket.abort();
}
state.mode = Mode::Finished;
state.upgrade.take();
state.head.take();
}
}
pub async fn respond(reply: &HttpReply, response: Response) -> Result<(), String> {
let _writer = reply.0.writer.lock().await;
let response = encode_response(response, reply.0.budget)?;
reply.publish(|| {
(
response.map(|body| {
Outgoing::Buffered(BufferedBody {
body,
reply: reply.clone(),
})
}),
Mode::Finished,
)
})
}
pub async fn start(reply: &HttpReply, status: i64, headers: Vec<Header>) -> Result<(), String> {
start_stream(reply, status, headers, false).await
}
pub(crate) async fn start_stream(
reply: &HttpReply,
status: i64,
headers: Vec<Header>,
sse: bool,
) -> Result<(), String> {
if matches!(status, 204 | 205 | 304) {
return Err("bodyless status requires http.respond".into());
}
let _writer = reply.0.writer.lock().await;
let response = encode_response(
Response {
status,
headers,
body: Bytes::new(Vec::new()),
},
reply.0.budget,
)?;
reply.publish(|| {
let (sender, receiver) = mpsc::channel(1);
let millis = if sse {
reply.0.live.heartbeat_ms
} else {
reply.0.live.idle_ms
};
let interval = Duration::from_millis(millis as u64);
let body = StreamBody {
receiver,
reply: reply.clone(),
interval,
deadline: Box::pin(tokio::time::sleep(interval)),
sse,
ended: false,
};
(
response.map(|_| Outgoing::Stream(body)),
Mode::Streaming { sender, sse },
)
})
}
pub async fn write(reply: &HttpReply, bytes: Bytes) -> Result<(), String> {
write_chunk(reply, bytes, false).await
}
pub(crate) async fn write_chunk(reply: &HttpReply, bytes: Bytes, sse: bool) -> Result<(), String> {
if bytes.values().len() > reply.chunk_limit() {
return Err("HTTP chunk exceeds chunk_bytes".into());
}
tokio::time::timeout(reply.0.budget.timeout, async {
let _writer = reply.0.writer.lock().await;
let sender = {
let state = reply.0.state.lock().expect("reply lock poisoned");
match &state.mode {
Mode::Streaming { sender, sse: mode } if *mode == sse => sender.clone(),
_ => return Err("HTTP reply is not an open stream of this kind".into()),
}
};
sender
.send(bytes.into_http())
.await
.map_err(|_| "HTTP peer is closed".into())
})
.await
.map_err(|_| "HTTP stream write timed out".to_owned())?
}
pub async fn finish(reply: &HttpReply) -> Result<(), String> {
let _writer = reply.0.writer.lock().await;
let mut state = reply.0.state.lock().expect("reply lock poisoned");
if !matches!(state.mode, Mode::Streaming { .. }) {
return Err("HTTP reply is not an open stream".into());
}
state.mode = Mode::Finished;
Ok(())
}
pub(super) enum Outgoing {
Full(Full<bytes::Bytes>),
Buffered(BufferedBody),
Stream(StreamBody),
Handler(HandlerBody),
}
impl Outgoing {
pub(super) fn with_handler(self, handler: task::Group) -> Self {
Self::Handler(HandlerBody {
body: Box::new(self),
_handler: handler,
})
}
}
pub(super) struct HandlerBody {
body: Box<Outgoing>,
_handler: task::Group,
}
pub(super) struct BufferedBody {
body: Full<bytes::Bytes>,
reply: HttpReply,
}
impl Drop for BufferedBody {
fn drop(&mut self) {
self.reply.0.close_body(!self.body.is_end_stream());
}
}
pub(super) struct StreamBody {
receiver: mpsc::Receiver<bytes::Bytes>,
reply: HttpReply,
interval: Duration,
deadline: Pin<Box<Sleep>>,
sse: bool,
ended: bool,
}
impl Drop for StreamBody {
fn drop(&mut self) {
self.reply.0.close_body(!self.ended);
}
}
impl Body for Outgoing {
type Data = bytes::Bytes;
type Error = std::io::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
let body = match self.get_mut() {
Self::Stream(body) => body,
Self::Full(body) => {
return Pin::new(body)
.poll_frame(cx)
.map(|frame| frame.map(|frame| frame.map_err(|never| match never {})));
}
Self::Buffered(body) => {
return Pin::new(&mut body.body)
.poll_frame(cx)
.map(|frame| frame.map(|frame| frame.map_err(|never| match never {})));
}
Self::Handler(body) => return Pin::new(body.body.as_mut()).poll_frame(cx),
};
if body.ended {
return Poll::Ready(None);
}
match body.receiver.poll_recv(cx) {
Poll::Ready(Some(bytes)) => {
body.deadline
.as_mut()
.reset(tokio::time::Instant::now() + body.interval);
return Poll::Ready(Some(Ok(Frame::data(bytes))));
}
Poll::Ready(None) => {
body.ended = true;
let fault = body
.reply
.0
.state
.lock()
.expect("reply lock poisoned")
.fault
.clone();
return Poll::Ready(fault.map(|fault| Err(std::io::Error::other(fault))));
}
Poll::Pending => {}
}
if body.deadline.as_mut().poll(cx).is_pending() {
return Poll::Pending;
}
body.deadline
.as_mut()
.reset(tokio::time::Instant::now() + body.interval);
if body.sse {
Poll::Ready(Some(Ok(Frame::data(bytes::Bytes::from_static(b":\n\n")))))
} else {
body.ended = true;
body.reply.0.close_body(true);
Poll::Ready(Some(Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"HTTP stream idle timeout",
))))
}
}
fn is_end_stream(&self) -> bool {
match self {
Self::Full(body) => body.is_end_stream(),
Self::Buffered(body) => body.body.is_end_stream(),
Self::Stream(body) => body.ended,
Self::Handler(body) => body.body.is_end_stream(),
}
}
fn size_hint(&self) -> SizeHint {
match self {
Self::Full(body) => body.size_hint(),
Self::Buffered(body) => body.body.size_hint(),
Self::Stream(_) => SizeHint::default(),
Self::Handler(body) => body.body.size_hint(),
}
}
}
+110
View File
@@ -0,0 +1,110 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::sync::Notify;
use crate::{
net,
task::{ScopeFault, ScopedOwner},
};
pub(super) fn configure_liveness<E>(
builder: &mut hyper::server::conn::http2::Builder<E>,
timeout: Duration,
) {
builder
.keep_alive_interval(Some(timeout))
.keep_alive_timeout(timeout);
}
#[derive(Clone)]
pub(super) struct FirstRequest(Arc<FirstRequestState>);
struct FirstRequestState {
seen: AtomicBool,
changed: Notify,
}
impl FirstRequest {
pub(super) fn new() -> Self {
Self(Arc::new(FirstRequestState {
seen: AtomicBool::new(false),
changed: Notify::new(),
}))
}
pub(super) fn mark(&self) {
if !self.0.seen.swap(true, Ordering::AcqRel) {
self.0.changed.notify_one();
}
}
async fn wait(&self) {
loop {
let changed = self.0.changed.notified();
if self.0.seen.load(Ordering::Acquire) {
return;
}
changed.await;
}
}
}
pub(super) async fn drive<C, Shutdown>(
connection: C,
listener: &net::Listener,
first_request: &FirstRequest,
timeout: Duration,
fault: &ScopeFault,
mut shutdown: Shutdown,
) -> Result<(), String>
where
C: Future<Output = Result<(), hyper::Error>>,
Shutdown: for<'a> FnMut(Pin<&'a mut C>),
{
tokio::pin!(connection);
tokio::select! {
biased;
error = fault.wait() => return Err(error),
result = &mut connection => return super::connection_result(result),
_ = listener.closed() => {
shutdown(connection.as_mut());
return drain(connection.as_mut(), fault).await;
}
_ = first_request.wait() => {}
_ = tokio::time::sleep(timeout) => return Ok(()),
}
tokio::select! {
biased;
error = fault.wait() => Err(error),
result = &mut connection => super::connection_result(result),
_ = listener.closed() => {
shutdown(connection.as_mut());
drain(connection.as_mut(), fault).await
}
}
}
async fn drain<C>(mut connection: Pin<&mut C>, fault: &ScopeFault) -> Result<(), String>
where
C: Future<Output = Result<(), hyper::Error>>,
{
tokio::select! {
biased;
error = fault.wait() => Err(error),
result = connection.as_mut() => super::connection_result(result),
}
}
pub(super) async fn complete(owner: ScopedOwner, result: Result<(), String>) -> Result<(), String> {
match result {
Ok(()) => owner.finish().await,
Err(error) => {
let _ = owner.stop().await;
Err(error)
}
}
}
@@ -0,0 +1,96 @@
use std::future::Future;
use std::io::{self, IoSlice};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use crate::transport::Transport;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::time::Sleep;
// Header/body 的期限由协议层持有;这里仅限制无法取得进展的写操作。
pub(super) struct WriteTimeout {
stream: Transport,
timeout: Duration,
deadline: Option<Pin<Box<Sleep>>>,
}
impl WriteTimeout {
pub(super) fn into_inner(self) -> Transport {
self.stream
}
pub fn new(stream: Transport, timeout: Duration) -> Self {
Self {
stream,
timeout,
deadline: None,
}
}
fn output<T>(
&mut self,
context: &mut Context<'_>,
write: impl FnOnce(Pin<&mut Transport>, &mut Context<'_>) -> Poll<io::Result<T>>,
) -> Poll<io::Result<T>> {
match write(Pin::new(&mut self.stream), context) {
Poll::Ready(result) => {
self.deadline = None;
Poll::Ready(result)
}
Poll::Pending => {
let deadline = self
.deadline
.get_or_insert_with(|| Box::pin(tokio::time::sleep(self.timeout)));
match deadline.as_mut().poll(context) {
Poll::Ready(()) => Poll::Ready(Err(io::Error::new(
io::ErrorKind::TimedOut,
"HTTP write timed out",
))),
Poll::Pending => Poll::Pending,
}
}
}
}
}
impl AsyncRead for WriteTimeout {
fn poll_read(
mut self: Pin<&mut Self>,
context: &mut Context<'_>,
buffer: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(&mut self.stream).poll_read(context, buffer)
}
}
impl AsyncWrite for WriteTimeout {
fn poll_write(
mut self: Pin<&mut Self>,
context: &mut Context<'_>,
bytes: &[u8],
) -> Poll<io::Result<usize>> {
self.output(context, |stream, context| stream.poll_write(context, bytes))
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
context: &mut Context<'_>,
buffers: &[IoSlice<'_>],
) -> Poll<io::Result<usize>> {
self.output(context, |stream, context| {
stream.poll_write_vectored(context, buffers)
})
}
fn is_write_vectored(&self) -> bool {
self.stream.is_write_vectored()
}
fn poll_flush(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<io::Result<()>> {
self.output(context, |stream, context| stream.poll_flush(context))
}
fn poll_shutdown(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<io::Result<()>> {
self.output(context, |stream, context| stream.poll_shutdown(context))
}
}