use std::fmt;
use std::str::FromStr;
use bytes::Bytes;
use crate::errors::Error;
use crate::helpers::compression::Compression;
use crate::helpers::fields::HeaderField;
use crate::helpers::scan;
use crate::helpers::text::Text;
use crate::tls::Security;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportKind {
Stream,
QUIC,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Port {
UDS(String),
TCP(u16),
QUIC(u16),
}
impl Port {
pub fn transport(&self) -> TransportKind {
match self {
Self::UDS(_) | Self::TCP(_) => TransportKind::Stream,
Self::QUIC(_) => TransportKind::QUIC,
}
}
pub fn carries(&self, version: Version) -> bool {
self.transport() == version.transport()
}
pub fn offers(&self, versions: &[Version]) -> Vec<Version> {
let mut offered: Vec<Version> = versions.iter().copied().filter(|version| self.carries(*version)).collect();
if self.transport() == TransportKind::QUIC {
offered.truncate(1);
}
offered
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct URL {
pub scheme: String,
pub host: String,
pub port: u16,
pub target: String,
}
impl URL {
pub fn default_port(scheme: &str) -> u16 {
match scheme {
"https" | "wss" => 443,
_ => 80,
}
}
pub fn is_target(target: &str) -> bool {
!target.is_empty() && scan::all_visible(target.as_bytes())
}
pub fn is_authority(authority: &str) -> bool {
!authority.is_empty() && scan::all_visible(authority.as_bytes())
}
pub fn is_host(host: &str) -> bool {
!host.is_empty()
&& host.bytes().all(|byte| byte > 0x20 && byte != 0x7f && !b"/?#@[]:".contains(&byte))
}
pub fn secure(&self) -> bool {
matches!(self.scheme.as_str(), "https" | "wss")
}
pub fn authority(&self) -> String {
Self::authority_of(&self.scheme, &self.host, self.port)
}
pub fn authority_of(scheme: &str, host: &str, port: u16) -> String {
let bracketed = host.contains(':');
let default = port == Self::default_port(scheme);
match (bracketed, default) {
(true, true) => format!("[{host}]"),
(true, false) => format!("[{host}]:{port}"),
(false, true) => host.to_owned(),
(false, false) => format!("{host}:{port}"),
}
}
pub fn parse(text: &str) -> Result<Self, Error> {
let (scheme, rest) = text.split_once("://").ok_or_else(|| Error::Protocol(format!("url {text:?} has no scheme")))?;
let scheme = scheme.to_ascii_lowercase();
let end = rest.find(['/', '?', '#']).unwrap_or(rest.len());
let (authority, tail) = rest.split_at(end);
let target = if tail.is_empty() { "/".to_owned() } else { tail.to_owned() };
if !Self::is_target(&target) {
return Err(Error::Protocol(format!("request target {target:?} is malformed")));
}
let authority = authority.rsplit('@').next().unwrap_or(authority);
let (host, port) = if let Some(rest) = authority.strip_prefix('[') {
let (host, after) = rest
.split_once(']')
.ok_or_else(|| Error::Protocol("IPv6 authority is missing its closing bracket".into()))?;
let port = match after.strip_prefix(':') {
Some(digits) => Some(digits.parse().map_err(|_| Error::Protocol(format!("port {digits:?} is not a number")))?),
None if after.is_empty() => None,
None => return Err(Error::Protocol("IPv6 authority has trailing characters".into())),
};
if host.is_empty() || !host.bytes().all(|byte| byte.is_ascii_hexdigit() || byte == b':' || byte == b'.') {
return Err(Error::Protocol(format!("IPv6 authority {host:?} is malformed")));
}
(host.to_owned(), port)
} else if let Some((host, digits)) = authority.rsplit_once(':') {
let port = digits.parse().map_err(|_| Error::Protocol(format!("port {digits:?} is not a number")))?;
(host.to_owned(), Some(port))
} else {
(authority.to_owned(), None)
};
if host.is_empty() {
return Err(Error::Protocol(format!("url {text:?} has no host")));
}
if !authority.starts_with('[') && !Self::is_host(&host) {
return Err(Error::Protocol(format!("host {host:?} is malformed")));
}
let port = port.unwrap_or(Self::default_port(&scheme));
Ok(Self { scheme, host, port, target })
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Version {
V1_0,
V1_1,
V2_0,
V3_0,
}
impl Version {
pub fn alpn(&self) -> &'static str {
match self {
Self::V1_0 => "http/1.0",
Self::V1_1 => "http/1.1",
Self::V2_0 => "h2",
Self::V3_0 => "h3",
}
}
pub fn from_alpn(alpn: &[u8]) -> Option<Self> {
match alpn {
b"http/1.0" => Some(Self::V1_0),
b"http/1.1" => Some(Self::V1_1),
b"h2" => Some(Self::V2_0),
b"h3" => Some(Self::V3_0),
_ => None,
}
}
pub fn major(&self) -> u8 {
match self {
Self::V1_0 | Self::V1_1 => 1,
Self::V2_0 => 2,
Self::V3_0 => 3,
}
}
pub fn transport(&self) -> TransportKind {
match self {
Self::V1_0 | Self::V1_1 | Self::V2_0 => TransportKind::Stream,
Self::V3_0 => TransportKind::QUIC,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::V1_0 => "HTTP/1.0",
Self::V1_1 => "HTTP/1.1",
Self::V2_0 => "HTTP/2",
Self::V3_0 => "HTTP/3",
}
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Version {
type Err = ();
fn from_str(text: &str) -> Result<Self, Self::Err> {
match text {
"HTTP/1.0" => Ok(Self::V1_0),
"HTTP/1.1" => Ok(Self::V1_1),
"HTTP/2" => Ok(Self::V2_0),
"HTTP/3" => Ok(Self::V3_0),
_ => Err(()),
}
}
}
pub struct ALPN;
impl ALPN {
pub fn list(versions: &[Version]) -> Vec<Vec<u8>> {
versions.iter().map(|version| version.alpn().as_bytes().to_vec()).collect()
}
pub fn wire(versions: &[Version]) -> Vec<u8> {
let mut out = Vec::new();
for version in versions {
let protocol = version.alpn().as_bytes();
out.push(protocol.len() as u8);
out.extend_from_slice(protocol);
}
out
}
pub fn select<'a>(offered: &[Vec<u8>], client: &'a [u8]) -> Option<&'a [u8]> {
for wanted in offered {
let mut index = 0;
while index < client.len() {
let length = client[index] as usize;
let end = index + 1 + length;
let Some(protocol) = client.get(index + 1..end) else {
break;
};
if protocol == wanted.as_slice() {
return Some(protocol);
}
index = end;
}
}
None
}
pub fn negotiated(alpn: Option<&[u8]>, versions: &[Version]) -> Result<Version, Error> {
let Some(alpn) = alpn else {
return versions
.iter()
.copied()
.find(|version| version.major() == 1)
.ok_or_else(|| Error::Version("the peer selected no protocol".into()));
};
Version::from_alpn(alpn)
.filter(|version| versions.contains(version))
.ok_or_else(|| Error::Version(format!("the peer selected {:?}", String::from_utf8_lossy(alpn))))
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Method {
GET,
HEAD,
POST,
PUT,
DELETE,
CONNECT,
OPTIONS,
TRACE,
PATCH,
}
impl Method {
pub fn as_str(&self) -> &'static str {
match self {
Self::GET => "GET",
Self::HEAD => "HEAD",
Self::POST => "POST",
Self::PUT => "PUT",
Self::DELETE => "DELETE",
Self::CONNECT => "CONNECT",
Self::OPTIONS => "OPTIONS",
Self::TRACE => "TRACE",
Self::PATCH => "PATCH",
}
}
pub fn safe(&self) -> bool {
matches!(self, Self::GET | Self::HEAD | Self::OPTIONS | Self::TRACE)
}
pub fn idempotent(&self) -> bool {
self.safe() || matches!(self, Self::PUT | Self::DELETE)
}
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Method {
type Err = ();
fn from_str(text: &str) -> Result<Self, Self::Err> {
match text {
"GET" => Ok(Self::GET),
"HEAD" => Ok(Self::HEAD),
"POST" => Ok(Self::POST),
"PUT" => Ok(Self::PUT),
"DELETE" => Ok(Self::DELETE),
"CONNECT" => Ok(Self::CONNECT),
"OPTIONS" => Ok(Self::OPTIONS),
"TRACE" => Ok(Self::TRACE),
"PATCH" => Ok(Self::PATCH),
_ => Err(()),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
UserAgent,
Origin,
Proxy,
Gateway,
Tunnel,
}
impl Role {
pub fn is_client(&self) -> bool {
matches!(self, Self::UserAgent | Self::Proxy)
}
pub fn is_server(&self) -> bool {
matches!(self, Self::Origin | Self::Gateway)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeaderCase {
Title,
Lower,
}
impl HeaderCase {
pub fn write(&self, name: &str, out: &mut bytes::BytesMut) {
let start = out.len();
out.extend_from_slice(name.as_bytes());
self.apply_in_place(&mut out[start..]);
}
pub fn apply_in_place(&self, written: &mut [u8]) {
written.make_ascii_lowercase();
if matches!(self, Self::Title) {
if let Some(first) = written.first_mut() {
*first = first.to_ascii_uppercase();
}
let mut at = 0;
while let Some(offset) = scan::find(&written[at..], b'-') {
at += offset + 1;
match written.get_mut(at) {
Some(octet) => *octet = octet.to_ascii_uppercase(),
None => break,
}
}
}
}
pub fn apply(&self, name: &str) -> String {
let mut out = bytes::BytesMut::with_capacity(name.len());
self.write(name, &mut out);
String::from_utf8(Vec::from(out)).unwrap_or_default()
}
pub fn from_version(version: Version) -> Self {
match version {
Version::V1_0 | Version::V1_1 => Self::Title,
Version::V2_0 => Self::Lower,
Version::V3_0 => Self::Lower,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Body {
Data(Bytes),
Text(String),
File(String),
}
impl Body {
pub fn len(&self) -> Option<usize> {
match self {
Self::Data(data) => Some(data.len()),
Self::Text(text) => Some(text.len()),
Self::File(_) => None,
}
}
pub fn is_empty(&self) -> bool {
self.len() == Some(0)
}
pub async fn bytes(&self) -> Result<Bytes, std::io::Error> {
match self {
Self::Data(data) => Ok(data.clone()),
Self::Text(text) => Ok(Bytes::copy_from_slice(text.as_bytes())),
Self::File(path) => Ok(Bytes::from(Box::pin(tokio::fs::read(path)).await?)),
}
}
pub async fn into_bytes(self) -> Result<Bytes, std::io::Error> {
match self.into_inline() {
Ok(data) => Ok(data),
Err(path) => Ok(Bytes::from(Box::pin(tokio::fs::read(path)).await?)),
}
}
pub fn into_inline(self) -> Result<Bytes, String> {
match self {
Self::Data(data) => Ok(data),
Self::Text(text) => Ok(Bytes::from(text.into_bytes())),
Self::File(path) => Err(path),
}
}
pub fn inline(&self) -> Option<Bytes> {
match self {
Self::Data(data) => Some(data.clone()),
Self::Text(text) => Some(Bytes::copy_from_slice(text.as_bytes())),
Self::File(_) => None,
}
}
}
#[derive(Debug, Clone)]
pub struct Headers {
fields: Vec<HeaderField>,
present: u32,
}
impl Headers {
#[inline]
pub fn bit(matched: bool, index: u32) -> u32 {
(matched as u32) << index
}
#[inline]
pub fn well_known(name: &str) -> u32 {
let octets = name.as_bytes();
let Some(first) = octets.first() else {
return 0;
};
match (octets.len(), first) {
(2, b't') => Self::bit(name == "te", 0),
(4, b'h') => Self::bit(name == "host", 1),
(4, b'd') => Self::bit(name == "date", 2),
(6, b's') => Self::bit(name == "server", 3),
(6, b'c') => Self::bit(name == "cookie", 4),
(7, b'u') => Self::bit(name == "upgrade", 5),
(8, b'l') => Self::bit(name == "location", 6),
(10, b'c') => Self::bit(name == "connection", 7),
(10, b'k') => Self::bit(name == "keep-alive", 8),
(10, b's') => Self::bit(name == "set-cookie", 9),
(12, b'c') => Self::bit(name == "content-type", 10),
(14, b'c') => Self::bit(name == "content-length", 11),
(15, b'a') => Self::bit(name == "accept-encoding", 12),
(16, b'c') => Self::bit(name == "content-encoding", 13),
(16, b'p') => Self::bit(name == "proxy-connection", 14),
(17, b't') => Self::bit(name == "transfer-encoding", 15),
(25, b's') => Self::bit(name == "strict-transport-security", 16),
_ => 0,
}
}
pub fn new() -> Self {
Self { fields: Vec::new(), present: 0 }
}
pub const MAXIMUM_CAPACITY: usize = 64 * 1024;
pub fn with_capacity(fields: usize) -> Self {
Self { fields: Vec::with_capacity(fields.min(Self::MAXIMUM_CAPACITY)), present: 0 }
}
#[inline]
pub fn len(&self) -> usize {
self.fields.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
#[inline]
pub fn named(stored: &str, name: &str) -> bool {
if stored.len() != name.len() {
return false;
}
match (stored.as_bytes().first(), name.as_bytes().first()) {
(Some(stored), Some(name)) if !stored.eq_ignore_ascii_case(name) => return false,
_ => {}
}
scan::same(stored.as_bytes(), name.as_bytes()) || stored.eq_ignore_ascii_case(name)
}
#[inline]
pub fn absent(&self, name: &str) -> bool {
let bit = Self::well_known(name);
bit != 0 && self.present & bit == 0
}
#[inline]
pub fn contains(&self, name: &str) -> bool {
!self.absent(name) && self.fields.iter().any(|field| Self::named(&field.name, name))
}
#[inline]
pub fn get(&self, name: &str) -> Option<&str> {
if self.absent(name) {
return None;
}
self.fields.iter().find(|field| Self::named(&field.name, name)).map(|field| field.value.as_str())
}
#[inline]
pub fn get_all<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> {
let fields = if self.absent(name) { &self.fields[..0] } else { &self.fields[..] };
fields.iter().filter(move |field| Self::named(&field.name, name)).map(|field| field.value.as_str())
}
pub fn append(&mut self, name: impl Into<Text>, value: impl Into<Text>) {
let mut name = name.into();
name.make_ascii_lowercase();
self.present |= Self::well_known(&name);
self.fields.push(HeaderField { name, value: value.into() });
}
pub fn append_lowercase(&mut self, name: impl Into<Text>, value: impl Into<Text>) {
let name = name.into();
debug_assert!(!name.bytes().any(|byte| byte.is_ascii_uppercase()), "{name:?} is not lowercase");
self.present |= Self::well_known(&name);
self.fields.push(HeaderField { name, value: value.into() });
}
pub fn insert(&mut self, name: impl Into<Text>, value: impl Into<Text>) {
let mut name = name.into();
name.make_ascii_lowercase();
if !self.absent(&name) {
self.fields.retain(|field| field.name != name);
}
self.present |= Self::well_known(&name);
self.fields.push(HeaderField { name, value: value.into() });
}
pub fn remove(&mut self, name: &str) -> bool {
if self.absent(name) {
return false;
}
let len_before = self.fields.len();
self.fields.retain(|field| !Self::named(&field.name, name));
if self.fields.len() == len_before {
return false;
}
self.present = Self::presence(&self.fields);
true
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.fields.iter().map(|field| (field.name.as_str(), field.value.as_str()))
}
#[inline]
pub fn fields(&self) -> &[HeaderField] {
&self.fields
}
#[inline]
pub fn presence(fields: &[HeaderField]) -> u32 {
fields.iter().fold(0, |present, field| present | Self::well_known(&field.name))
}
pub fn from_fields(fields: Vec<HeaderField>) -> Self {
let mut fields = fields;
for field in &mut fields {
if field.name.bytes().any(|byte| byte.is_ascii_uppercase()) {
field.name.make_ascii_lowercase();
}
}
Self { present: Self::presence(&fields), fields }
}
pub fn adopt(fields: Vec<HeaderField>, present: u32) -> Self {
debug_assert!(
!fields.iter().any(|field| field.name.bytes().any(|byte| byte.is_ascii_uppercase())),
"a field name handed to Headers::adopt is not lowercase"
);
debug_assert_eq!(present, Self::presence(&fields), "the presence bits handed to Headers::adopt are not the fields'");
Self { fields, present }
}
pub fn into_fields(self) -> Vec<HeaderField> {
self.fields
}
}
impl PartialEq for Headers {
fn eq(&self, other: &Self) -> bool {
self.fields == other.fields
}
}
impl Eq for Headers {}
impl Default for Headers {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StreamID(pub u64);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConnectionID(pub Bytes);
#[derive(Debug, PartialEq, Eq)]
pub struct Message {
pub version: Version,
pub body: Option<Body>,
pub compression: Option<Compression>,
pub headers: Option<Headers>,
pub trailers: Option<Headers>,
pub stream_id: Option<StreamID>,
pub connection_id: Option<ConnectionID>,
pub client: Option<std::net::SocketAddr>,
pub security: Security,
pub method: Option<Method>,
pub target: Option<Text>,
pub status_code: Option<u16>,
}
impl Message {
pub fn tunneling(&self, method: Option<Method>) -> bool {
method == Some(Method::CONNECT) && (self.is_request() || matches!(self.status_code, Some(200..=299)))
}
pub fn new(version: Version) -> Self {
Self {
version,
body: None,
compression: None,
headers: Some(Headers::new()),
trailers: None,
stream_id: None,
connection_id: None,
client: None,
security: Security::default(),
method: None,
target: None,
status_code: None,
}
}
pub fn request(method: Method, target: impl Into<Text>, version: Version) -> Self {
Self { method: Some(method), target: Some(target.into()), ..Self::new(version) }
}
pub fn response(status_code: u16, version: Version) -> Self {
Self { status_code: Some(status_code), ..Self::new(version) }
}
pub fn is_request(&self) -> bool {
self.method.is_some()
}
pub fn is_response(&self) -> bool {
self.status_code.is_some()
}
pub fn is_informational(&self) -> bool {
matches!(self.status_code, Some(100..=199))
}
pub fn bodyless(&self, method: Option<Method>) -> bool {
match self.status_code {
Some(status_code) => matches!(status_code, 100..=199 | 204 | 304) || method == Some(Method::HEAD),
None => false,
}
}
pub fn compressed(&self) -> bool {
self.headers.as_ref().is_some_and(|headers| Compression::encoded(headers.get_all("content-encoding")))
}
pub fn accepted(&self) -> Option<Compression> {
self.headers.as_ref().and_then(|headers| Compression::accepted(headers.get_all("accept-encoding")))
}
pub fn codable(&self) -> bool {
let bodyless = matches!(self.status_code, Some(100..=199 | 204 | 304));
let partial = self.status_code == Some(206) || self.headers.as_ref().is_some_and(|headers| headers.contains("content-range"));
!bodyless && !partial && self.method != Some(Method::CONNECT)
}
pub async fn materialize(&mut self) -> Result<(), Error> {
if let Some(body) = self.body.take() {
self.body = Some(Body::Data(body.into_bytes().await?));
}
Ok(())
}
pub fn compress(&mut self, accepted: Option<Compression>) -> Result<(), Error> {
let Some(compression) = self.compression else {
return Ok(());
};
let settled = match compression {
Compression::Auto => accepted,
coding => Some(coding),
};
let Some(coding) = settled else {
self.compression = None;
return Ok(());
};
if self.compressed() || !self.codable() || self.body.as_ref().is_none_or(Body::is_empty) {
self.compression = None;
return Ok(());
}
let Some(body) = self.body.as_ref().and_then(Body::inline) else {
return Err(Error::Protocol("a body that is still a file cannot be coded".into()));
};
let encoded = coding.encode(&body)?;
let length = encoded.len();
let varying = compression == Compression::Auto && self.is_response();
self.body = Some(Body::Data(encoded));
self.compression = Some(coding);
let headers = self.headers.get_or_insert_with(Headers::new);
headers.append_lowercase("content-encoding", coding.as_str());
if headers.contains("content-length") {
headers.insert("content-length", length.to_string());
}
if varying && !headers.contains("vary") {
headers.append_lowercase("vary", "Accept-Encoding");
}
Ok(())
}
pub fn decompress(&mut self, max: u64) -> Result<(), Error> {
let Some(headers) = self.headers.as_mut() else {
return Ok(());
};
let Some(coding) = Compression::applied(headers.get_all("content-encoding")) else {
return Ok(());
};
let Some(body) = self.body.as_ref().and_then(Body::inline) else {
return Ok(());
};
let decoded = coding.decode(&body, max)?;
let length = decoded.len();
self.body = Some(Body::Data(decoded));
self.compression = Some(coding);
headers.remove("content-encoding");
if headers.contains("content-length") {
headers.insert("content-length", length.to_string());
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Limits {
pub max_message_size: u64,
pub max_message_body_size: u64,
pub max_decompressed_body_size: u64,
pub max_startline_size: u32,
pub max_headers_size: u64,
pub max_header_count: u16,
pub max_chunk_header_size: u32,
pub read_chunk_size: u64,
pub idle_capacity: u64,
pub max_pending_handshakes: u32,
pub handshake_timeout: f64,
pub read_timeout: f64,
pub write_timeout: f64,
pub receive_timeout: f64,
pub send_timeout: f64,
pub inline_body_size: u64,
pub max_concurrent_streams: u32,
pub max_connection_buffer_size: u64,
pub max_premature_resets: u32,
pub max_encoder_table_size: u64,
pub max_idle_frames: u32,
pub output_high_water: u64,
pub max_requests_per_connection: u64,
pub qpack_block_timeout: f64,
pub max_peer_uni_streams: u32,
pub max_outstanding_sections: u32,
pub max_blocked_streams: u32,
pub tunnel_backlog: u32,
pub command_backlog: u32,
pub ws_linger_timeout: f64,
pub ws_max_fragments: u16,
pub max_cookies: u32,
pub max_cookies_per_domain: u16,
pub max_hsts_entries: u32,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_message_size: 64 * 1024 * 1024,
max_message_body_size: 64 * 1024 * 1024,
max_decompressed_body_size: 256 * 1024 * 1024,
max_startline_size: 8 * 1024,
max_headers_size: 64 * 1024,
max_header_count: 100,
max_chunk_header_size: 128,
read_chunk_size: 16 * 1024,
idle_capacity: 64 * 1024,
max_pending_handshakes: 256,
handshake_timeout: 10.0,
read_timeout: 30.0,
write_timeout: 30.0,
receive_timeout: 300.0,
send_timeout: 1800.0,
inline_body_size: 64 * 1024,
max_concurrent_streams: 100,
max_connection_buffer_size: 64 * 1024 * 1024,
max_premature_resets: 1000,
max_encoder_table_size: 64 * 1024,
max_idle_frames: 1000,
output_high_water: 64 * 1024,
max_requests_per_connection: 10_000,
qpack_block_timeout: 5.0,
max_peer_uni_streams: 32,
max_outstanding_sections: 512,
max_blocked_streams: 16,
tunnel_backlog: 32,
command_backlog: 256,
ws_linger_timeout: 10.0,
ws_max_fragments: 4096,
max_cookies: 3000,
max_cookies_per_domain: 50,
max_hsts_entries: 4096,
}
}
}