mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-23 16:21:06 +01:00
add ws and http2 feature flags (#2618)
This commit is contained in:
parent
3200de3f34
commit
cd511affd5
@ -80,7 +80,7 @@ actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
||||
|
||||
actix-http = "3.0.0-beta.19"
|
||||
actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] }
|
||||
actix-router = "0.5.0-rc.2"
|
||||
actix-web-codegen = "0.5.0-rc.1"
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
- Implement `From<Duration>` for `KeepAlive`. [#2611]
|
||||
- Implement `From<Option<Duration>>` for `KeepAlive`. [#2611]
|
||||
- Implement `Default` for `HttpServiceBuilder`. [#2611]
|
||||
- Crate `ws` feature flag, disabled by default. [#2618]
|
||||
- Crate `http2` feature flag, disabled by default. [#2618]
|
||||
|
||||
### Changed
|
||||
- Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611]
|
||||
@ -27,6 +29,7 @@
|
||||
- `HttpServiceBuilder::new`; use `default` instead. [#2611]
|
||||
|
||||
[#2611]: https://github.com/actix/actix-web/pull/2611
|
||||
[#2618]: https://github.com/actix/actix-web/pull/2618
|
||||
|
||||
|
||||
## 3.0.0-beta.19 - 2022-01-21
|
||||
|
@ -29,54 +29,69 @@ path = "src/lib.rs"
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# openssl
|
||||
# HTTP/2 protocol support
|
||||
http2 = ["h2"]
|
||||
|
||||
# WebSocket protocol implementation
|
||||
ws = [
|
||||
"local-channel",
|
||||
"base64",
|
||||
"rand",
|
||||
"sha-1",
|
||||
]
|
||||
|
||||
# TLS via OpenSSL
|
||||
openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
||||
|
||||
# rustls support
|
||||
# TLS via Rustls
|
||||
rustls = ["actix-tls/accept", "actix-tls/rustls"]
|
||||
|
||||
# enable compression support
|
||||
compress-brotli = ["brotli", "__compress"]
|
||||
compress-gzip = ["flate2", "__compress"]
|
||||
compress-zstd = ["zstd", "__compress"]
|
||||
# Compression codecs
|
||||
compress-brotli = ["__compress", "brotli"]
|
||||
compress-gzip = ["__compress", "flate2"]
|
||||
compress-zstd = ["__compress", "zstd"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2.0.0"
|
||||
actix-service = "2"
|
||||
actix-codec = "0.4.1"
|
||||
actix-utils = "3.0.0"
|
||||
actix-utils = "3"
|
||||
actix-rt = { version = "2.2", default-features = false }
|
||||
|
||||
ahash = "0.7"
|
||||
base64 = "0.13"
|
||||
bitflags = "1.2"
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
derive_more = "0.99.5"
|
||||
encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
h2 = "0.3.9"
|
||||
http = "0.2.5"
|
||||
httparse = "1.5.1"
|
||||
httpdate = "1.0.1"
|
||||
itoa = "1"
|
||||
language-tags = "0.3"
|
||||
local-channel = "0.1"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
pin-project-lite = "0.2"
|
||||
rand = "0.8"
|
||||
sha-1 = "0.10"
|
||||
smallvec = "1.6.1"
|
||||
|
||||
# tls
|
||||
# http2
|
||||
h2 = { version = "0.3.9", optional = true }
|
||||
|
||||
# websockets
|
||||
local-channel = { version = "0.1", optional = true }
|
||||
base64 = { version = "0.13", optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
sha-1 = { version = "0.10", optional = true }
|
||||
|
||||
# openssl/rustls
|
||||
actix-tls = { version = "3.0.0", default-features = false, optional = true }
|
||||
|
||||
# compression
|
||||
# compress-*
|
||||
brotli = { version = "3.3.3", optional = true }
|
||||
flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.9", optional = true }
|
||||
|
@ -6,7 +6,6 @@ use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
|
||||
h2::H2Service,
|
||||
service::HttpService,
|
||||
ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig,
|
||||
};
|
||||
@ -211,7 +210,8 @@ where
|
||||
}
|
||||
|
||||
/// Finish service configuration and create a HTTP service for HTTP/2 protocol.
|
||||
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
|
||||
#[cfg(feature = "http2")]
|
||||
pub fn h2<F, B>(self, service: F) -> crate::h2::H2Service<T, S, B>
|
||||
where
|
||||
F: IntoServiceFactory<S, Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
@ -228,7 +228,8 @@ where
|
||||
self.local_addr,
|
||||
);
|
||||
|
||||
H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext)
|
||||
crate::h2::H2Service::with_config(cfg, service.into_factory())
|
||||
.on_connect_ext(self.on_connect_ext)
|
||||
}
|
||||
|
||||
/// Finish service configuration and create `HttpService` instance.
|
||||
|
@ -117,6 +117,7 @@ impl ServiceConfig {
|
||||
dst.extend_from_slice(&buf);
|
||||
}
|
||||
|
||||
#[allow(unused)] // used with `http2` feature flag
|
||||
pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) {
|
||||
self.0
|
||||
.date_service
|
||||
|
@ -352,7 +352,7 @@ impl ContentEncoder {
|
||||
ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding br encoding: {}", err);
|
||||
log::trace!("Error decoding br encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
@ -361,7 +361,7 @@ impl ContentEncoder {
|
||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding gzip encoding: {}", err);
|
||||
log::trace!("Error decoding gzip encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
@ -370,7 +370,7 @@ impl ContentEncoder {
|
||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding deflate encoding: {}", err);
|
||||
log::trace!("Error decoding deflate encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
@ -379,7 +379,7 @@ impl ContentEncoder {
|
||||
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding ztsd encoding: {}", err);
|
||||
log::trace!("Error decoding ztsd encoding: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err
|
||||
use derive_more::{Display, Error, From};
|
||||
use http::{uri::InvalidUri, StatusCode};
|
||||
|
||||
use crate::{body::BoxBody, ws, Response};
|
||||
use crate::{body::BoxBody, Response};
|
||||
|
||||
pub use http::Error as HttpError;
|
||||
|
||||
@ -61,6 +61,7 @@ impl Error {
|
||||
Self::new(Kind::Encoder)
|
||||
}
|
||||
|
||||
#[allow(unused)] // used with `ws` feature flag
|
||||
pub(crate) fn new_ws() -> Self {
|
||||
Self::new(Kind::Ws)
|
||||
}
|
||||
@ -139,14 +140,16 @@ impl From<HttpError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ws::HandshakeError> for Error {
|
||||
fn from(err: ws::HandshakeError) -> Self {
|
||||
#[cfg(feature = "ws")]
|
||||
impl From<crate::ws::HandshakeError> for Error {
|
||||
fn from(err: crate::ws::HandshakeError) -> Self {
|
||||
Self::new_ws().with_cause(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ws::ProtocolError> for Error {
|
||||
fn from(err: ws::ProtocolError) -> Self {
|
||||
#[cfg(feature = "ws")]
|
||||
impl From<crate::ws::ProtocolError> for Error {
|
||||
fn from(err: crate::ws::ProtocolError) -> Self {
|
||||
Self::new_ws().with_cause(err)
|
||||
}
|
||||
}
|
||||
@ -277,8 +280,9 @@ pub enum PayloadError {
|
||||
UnknownLength,
|
||||
|
||||
/// HTTP/2 payload error.
|
||||
#[cfg(feature = "http2")]
|
||||
#[display(fmt = "{}", _0)]
|
||||
Http2Payload(h2::Error),
|
||||
Http2Payload(::h2::Error),
|
||||
|
||||
/// Generic I/O error.
|
||||
#[display(fmt = "{}", _0)]
|
||||
@ -293,14 +297,16 @@ impl std::error::Error for PayloadError {
|
||||
PayloadError::EncodingCorrupted => None,
|
||||
PayloadError::Overflow => None,
|
||||
PayloadError::UnknownLength => None,
|
||||
#[cfg(feature = "http2")]
|
||||
PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error),
|
||||
PayloadError::Io(err) => Some(err as &dyn std::error::Error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<h2::Error> for PayloadError {
|
||||
fn from(err: h2::Error) -> Self {
|
||||
#[cfg(feature = "http2")]
|
||||
impl From<::h2::Error> for PayloadError {
|
||||
fn from(err: ::h2::Error) -> Self {
|
||||
PayloadError::Http2Payload(err)
|
||||
}
|
||||
}
|
||||
@ -356,6 +362,7 @@ pub enum DispatchError {
|
||||
|
||||
/// HTTP/2 error.
|
||||
#[display(fmt = "{}", _0)]
|
||||
#[cfg(feature = "http2")]
|
||||
H2(h2::Error),
|
||||
|
||||
/// The first request did not complete within the specified timeout.
|
||||
@ -379,7 +386,10 @@ impl StdError for DispatchError {
|
||||
DispatchError::Body(err) => Some(&**err),
|
||||
DispatchError::Io(err) => Some(err),
|
||||
DispatchError::Parse(err) => Some(err),
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
DispatchError::H2(err) => Some(err),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ where
|
||||
DispatchError::SendResponse(err) => {
|
||||
trace!("Error sending HTTP/2 response: {:?}", err)
|
||||
}
|
||||
DispatchError::SendData(err) => warn!("{:?}", err),
|
||||
DispatchError::SendData(err) => log::warn!("{:?}", err),
|
||||
DispatchError::ResponseBody(err) => {
|
||||
error!("Response payload stream error: {:?}", err)
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ where
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
trace!("H2 handshake error: {}", err);
|
||||
log::trace!("H2 handshake error: {}", err);
|
||||
Poll::Ready(Err(err))
|
||||
}
|
||||
},
|
||||
|
@ -55,7 +55,7 @@ pub trait HttpMessage: Sized {
|
||||
""
|
||||
}
|
||||
|
||||
/// Get content type encoding
|
||||
/// Get content type encoding.
|
||||
///
|
||||
/// UTF-8 is used by default, If request charset is not set.
|
||||
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
|
||||
|
@ -24,6 +24,7 @@ impl KeepAlive {
|
||||
!matches!(self, Self::Disabled)
|
||||
}
|
||||
|
||||
#[allow(unused)] // used with `http2` feature flag
|
||||
pub(crate) fn duration(&self) -> Option<Duration> {
|
||||
match self {
|
||||
KeepAlive::Timeout(dur) => Some(*dur),
|
||||
|
@ -24,9 +24,6 @@
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub use ::http::{uri, uri::Uri};
|
||||
pub use ::http::{Method, StatusCode, Version};
|
||||
|
||||
@ -39,6 +36,7 @@ pub mod encoding;
|
||||
pub mod error;
|
||||
mod extensions;
|
||||
pub mod h1;
|
||||
#[cfg(feature = "http2")]
|
||||
pub mod h2;
|
||||
pub mod header;
|
||||
mod helpers;
|
||||
@ -52,6 +50,7 @@ mod requests;
|
||||
mod responses;
|
||||
mod service;
|
||||
pub mod test;
|
||||
#[cfg(feature = "ws")]
|
||||
pub mod ws;
|
||||
|
||||
pub use self::builder::HttpServiceBuilder;
|
||||
|
@ -16,6 +16,18 @@ pub type BoxedPayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadErr
|
||||
#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")]
|
||||
pub type PayloadStream = BoxedPayloadStream;
|
||||
|
||||
#[cfg(not(feature = "http2"))]
|
||||
pin_project! {
|
||||
/// A streaming payload.
|
||||
#[project = PayloadProj]
|
||||
pub enum Payload<S = BoxedPayloadStream> {
|
||||
None,
|
||||
H1 { payload: crate::h1::Payload },
|
||||
Stream { #[pin] payload: S },
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
pin_project! {
|
||||
/// A streaming payload.
|
||||
#[project = PayloadProj]
|
||||
@ -33,14 +45,16 @@ impl<S> From<crate::h1::Payload> for Payload<S> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<S> From<crate::h2::Payload> for Payload<S> {
|
||||
fn from(payload: crate::h2::Payload) -> Self {
|
||||
Payload::H2 { payload }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<h2::RecvStream> for Payload<S> {
|
||||
fn from(stream: h2::RecvStream) -> Self {
|
||||
#[cfg(feature = "http2")]
|
||||
impl<S> From<::h2::RecvStream> for Payload<S> {
|
||||
fn from(stream: ::h2::RecvStream) -> Self {
|
||||
Payload::H2 {
|
||||
payload: crate::h2::Payload::new(stream),
|
||||
}
|
||||
@ -71,7 +85,10 @@ where
|
||||
match self.project() {
|
||||
PayloadProj::None => Poll::Ready(None),
|
||||
PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx),
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx),
|
||||
|
||||
PayloadProj::Stream { payload } => payload.poll_next(cx),
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
builder::HttpServiceBuilder,
|
||||
error::DispatchError,
|
||||
h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig,
|
||||
h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig,
|
||||
};
|
||||
|
||||
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
||||
@ -502,10 +502,11 @@ where
|
||||
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||
|
||||
match proto {
|
||||
#[cfg(feature = "http2")]
|
||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||
state: State::H2Handshake {
|
||||
handshake: Some((
|
||||
h2::handshake_with_timeout(io, &self.cfg),
|
||||
crate::h2::handshake_with_timeout(io, &self.cfg),
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
conn_data,
|
||||
@ -514,6 +515,11 @@ where
|
||||
},
|
||||
},
|
||||
|
||||
#[cfg(not(feature = "http2"))]
|
||||
Protocol::Http2 => {
|
||||
panic!("HTTP/2 support is disabled (enable with the `http2` feature flag)")
|
||||
}
|
||||
|
||||
Protocol::Http1 => HttpServiceHandlerResponse {
|
||||
state: State::H1 {
|
||||
dispatcher: h1::Dispatcher::new(
|
||||
@ -531,6 +537,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "http2"))]
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<T, S, B, X, U>
|
||||
@ -552,10 +559,37 @@ pin_project! {
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
||||
H2 { #[pin] dispatcher: h2::Dispatcher<T, S, B, X, U> },
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead,
|
||||
T: AsyncWrite,
|
||||
T: Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
||||
|
||||
H2 { #[pin] dispatcher: crate::h2::Dispatcher<T, S, B, X, U> },
|
||||
|
||||
H2Handshake {
|
||||
handshake: Option<(
|
||||
h2::HandshakeWithTimeout<T>,
|
||||
crate::h2::HandshakeWithTimeout<T>,
|
||||
ServiceConfig,
|
||||
Rc<HttpFlow<S, X, U>>,
|
||||
OnConnectData,
|
||||
@ -614,21 +648,25 @@ where
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.as_mut().project().state.project() {
|
||||
StateProj::H1 { dispatcher } => dispatcher.poll(cx),
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
StateProj::H2 { dispatcher } => dispatcher.poll(cx),
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
StateProj::H2Handshake { handshake: data } => {
|
||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||
Ok((conn, timer)) => {
|
||||
let (_, config, flow, conn_data, peer_addr) = data.take().unwrap();
|
||||
|
||||
self.as_mut().project().state.set(State::H2 {
|
||||
dispatcher: h2::Dispatcher::new(
|
||||
dispatcher: crate::h2::Dispatcher::new(
|
||||
conn, flow, config, peer_addr, conn_data, timer,
|
||||
),
|
||||
});
|
||||
self.poll(cx)
|
||||
}
|
||||
Err(err) => {
|
||||
trace!("H2 handshake error: {}", err);
|
||||
log::trace!("H2 handshake error: {}", err);
|
||||
Poll::Ready(Err(err))
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,11 @@ use bitflags::bitflags;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytestring::ByteString;
|
||||
|
||||
use super::frame::Parser;
|
||||
use super::proto::{CloseReason, OpCode};
|
||||
use super::ProtocolError;
|
||||
use super::{
|
||||
frame::Parser,
|
||||
proto::{CloseReason, OpCode},
|
||||
ProtocolError,
|
||||
};
|
||||
|
||||
/// A WebSocket message.
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -251,7 +253,7 @@ impl Decoder for Codec {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Unfinished fragment {:?}", opcode);
|
||||
log::error!("Unfinished fragment {:?}", opcode);
|
||||
Err(ProtocolError::ContinuationFragment(opcode))
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_service::{IntoService, Service};
|
||||
|
@ -3,9 +3,11 @@ use std::convert::TryFrom;
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use log::debug;
|
||||
|
||||
use crate::ws::mask::apply_mask;
|
||||
use crate::ws::proto::{CloseCode, CloseReason, OpCode};
|
||||
use crate::ws::ProtocolError;
|
||||
use super::{
|
||||
mask::apply_mask,
|
||||
proto::{CloseCode, CloseReason, OpCode},
|
||||
ProtocolError,
|
||||
};
|
||||
|
||||
/// A struct representing a WebSocket frame.
|
||||
#[derive(Debug)]
|
||||
|
@ -60,7 +60,7 @@ dangerous-h2c = []
|
||||
[dependencies]
|
||||
actix-codec = "0.4.1"
|
||||
actix-service = "2.0.0"
|
||||
actix-http = "3.0.0-beta.19"
|
||||
actix-http = { version = "3.0.0-beta.19", features = ["http2", "ws"] }
|
||||
actix-rt = { version = "2.1", default-features = false }
|
||||
actix-tls = { version = "3.0.0", features = ["connect", "uri"] }
|
||||
actix-utils = "3.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user