1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 01:51:30 +02:00

Compare commits

...

12 Commits

Author SHA1 Message Date
a9728abfc8 run coverage report on 1.24 2018-05-20 21:10:50 -07:00
14d1b8e2b6 prepare release 2018-05-20 21:09:54 -07:00
285c73e95e Re-use tcp listener on pause/resume 2018-05-20 20:47:20 -07:00
483db7028c expose low level data 2018-05-20 20:37:19 -07:00
082ff46041 Fix scope resource path extractor #234 2018-05-20 17:04:23 -07:00
f32e8f22c8 Merge pull request #231 from qrvaelet/ranges
NamedFile: range upgrade
2018-05-20 09:18:46 -07:00
766dde7c42 Merge branch 'master' into ranges 2018-05-20 08:51:07 -07:00
b68687044e range header syntax fix, change range to content-range in responses, enabled accept ranges, tests for content-range, content-length, and range status code 2018-05-20 17:40:36 +02:00
c9e84e9dd3 Merge pull request #233 from sindreij/patch-1
Fix some typos in server/srv.rs
2018-05-20 06:19:53 -07:00
0126ac46fc Fix some typos in server/srv.rs
Hello! This looks like a great library, thanks for creating it! While reading through the documentation I found a few typos.
2018-05-20 14:43:26 +02:00
9b7ea836d0 bump version 2018-05-17 18:34:09 -07:00
537b420d35 Fix compilation with --no-default-features 2018-05-17 18:33:48 -07:00
15 changed files with 297 additions and 77 deletions

View File

@ -31,12 +31,12 @@ before_script:
script: script:
- | - |
if [[ "$TRAVIS_RUST_VERSION" != "beta" ]]; then if [[ "$TRAVIS_RUST_VERSION" != "1.24.0" ]]; then
cargo clean cargo clean
cargo test --features="alpn,tls" -- --nocapture cargo test --features="alpn,tls" -- --nocapture
fi fi
- | - |
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then if [[ "$TRAVIS_RUST_VERSION" == "1.24.0" ]]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count USE_SKEPTIC=1 cargo tarpaulin --out Xml --no-count
bash <(curl -s https://codecov.io/bash) bash <(curl -s https://codecov.io/bash)

View File

@ -1,5 +1,17 @@
# Changes # Changes
## 0.6.8 (2018-05-20)
* Fix scope resource path extractor #234
* Re-use tcp listener on pause/resume
## 0.6.7 (2018-05-17)
* Fix compilation with --no-default-features
## 0.6.6 (2018-05-17) ## 0.6.6 (2018-05-17)
* Panic during middleware execution #226 * Panic during middleware execution #226

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "0.6.6" version = "0.6.8"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"

116
src/fs.rs
View File

@ -289,30 +289,28 @@ impl Responder for NamedFile {
resp.set(header::ETag(etag)); resp.set(header::ETag(etag));
}); });
// TODO: Debug, enabling "accept-ranges: bytes" causes problems with resp.header(header::ACCEPT_RANGES, "bytes");
// certain clients when not using the ranges header.
//resp.header(header::ACCEPT_RANGES, format!("bytes"));
let mut length = self.md.len(); let mut length = self.md.len();
let mut offset = 0; let mut offset = 0;
// check for ranges header // check for range header
if let Some(ranges) = req.headers().get(header::RANGE) { if let Some(ranges) = req.headers().get(header::RANGE) {
if let Ok(rangesheader) = ranges.to_str() { if let Ok(rangesheader) = ranges.to_str() {
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
length = rangesvec[0].length - 1; length = rangesvec[0].length;
offset = rangesvec[0].start; offset = rangesvec[0].start;
resp.header( resp.header(
header::RANGE, header::CONTENT_RANGE,
format!( format!(
"bytes={}-{}/{}", "bytes {}-{}/{}",
offset, offset,
offset + length, offset + length - 1,
self.md.len() self.md.len()
), )
); );
} else { } else {
resp.header(header::RANGE, format!("*/{}", length)); resp.header(header::CONTENT_RANGE, format!("bytes */{}", length));
return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish());
}; };
} else { } else {
@ -778,6 +776,7 @@ mod tests {
App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml")) App::new().handler("test", StaticFiles::new(".").index_file("Cargo.toml"))
}); });
// Valid range header
let request = srv let request = srv
.get() .get()
.uri(srv.url("/t%65st/Cargo.toml")) .uri(srv.url("/t%65st/Cargo.toml"))
@ -787,10 +786,21 @@ mod tests {
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/Cargo.toml"))
.header(header::RANGE, "bytes=1-0")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
} }
#[test] #[test]
fn test_named_file_ranges_headers() { fn test_named_file_content_range_headers() {
let mut srv = test::TestServer::with_factory(|| { let mut srv = test::TestServer::with_factory(|| {
App::new().handler( App::new().handler(
"test", "test",
@ -798,13 +808,64 @@ mod tests {
) )
}); });
// Valid range header
let request = srv let request = srv
.get() .get()
.uri(srv.url("/t%65st/tests/test.binary")) .uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-20")
.finish() .finish()
.unwrap(); .unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentrange, "bytes 10-20/100");
// Invalid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-5")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentrange, "bytes */100");
}
#[test]
fn test_named_file_content_length_headers() {
let mut srv = test::TestServer::with_factory(|| {
App::new().handler(
"test",
StaticFiles::new(".").index_file("tests/test.binary"),
)
});
// Valid range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-20")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response let contentlength = response
.headers() .headers()
.get(header::CONTENT_LENGTH) .get(header::CONTENT_LENGTH)
@ -812,23 +873,44 @@ mod tests {
.to_str() .to_str()
.unwrap(); .unwrap();
assert_eq!(contentlength, "10"); assert_eq!(contentlength, "11");
// Invalid range header
let request = srv let request = srv
.get() .get()
.uri(srv.url("/t%65st/tests/test.binary")) .uri(srv.url("/t%65st/tests/test.binary"))
.header(header::RANGE, "bytes=10-20") .header(header::RANGE, "bytes=10-8")
.finish() .finish()
.unwrap(); .unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
let range = response
let contentlength = response
.headers() .headers()
.get(header::RANGE) .get(header::CONTENT_LENGTH)
.unwrap() .unwrap()
.to_str() .to_str()
.unwrap(); .unwrap();
assert_eq!(range, "bytes=10-20/100"); assert_eq!(contentlength, "0");
// Without range header
let request = srv
.get()
.uri(srv.url("/t%65st/tests/test.binary"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
let contentlength = response
.headers()
.get(header::CONTENT_LENGTH)
.unwrap()
.to_str()
.unwrap();
assert_eq!(contentlength, "100");
} }
#[test] #[test]

View File

@ -58,6 +58,16 @@ impl<'a> Params<'a> {
self.0.push((name, value)); self.0.push((name, value));
} }
pub(crate) fn remove(&mut self, name: &str)
{
for idx in (0..self.0.len()).rev() {
if self.0[idx].0 == name {
self.0.remove(idx);
return
}
}
}
/// Check if there are any matched patterns /// Check if there are any matched patterns
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.0.is_empty()

View File

@ -336,6 +336,7 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
if pattern.match_with_params(path, req.match_info_mut()) { if pattern.match_with_params(path, req.match_info_mut()) {
let default = unsafe { &mut *self.default.as_ref().get() }; let default = unsafe { &mut *self.default.as_ref().get() };
req.match_info_mut().remove("tail");
if self.middlewares.is_empty() { if self.middlewares.is_empty() {
let resource = unsafe { &mut *resource.get() }; let resource = unsafe { &mut *resource.get() };
return resource.handle(req, Some(default)); return resource.handle(req, Some(default));

View File

@ -33,6 +33,7 @@ pub(crate) enum PayloadType {
} }
impl PayloadType { impl PayloadType {
#[cfg(any(feature = "brotli", feature = "flate2"))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
// check content-encoding // check content-encoding
let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) {
@ -52,6 +53,11 @@ impl PayloadType {
_ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))),
} }
} }
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType {
PayloadType::Sender(sender)
}
} }
impl PayloadWriter for PayloadType { impl PayloadWriter for PayloadType {
@ -399,6 +405,7 @@ impl ContentEncoder {
// Enable content encoding only if response does not contain Content-Encoding // Enable content encoding only if response does not contain Content-Encoding
// header // header
#[cfg(any(feature = "brotli", feature = "flate2"))]
let mut encoding = if has_body { let mut encoding = if has_body {
let encoding = match response_encoding { let encoding = match response_encoding {
ContentEncoding::Auto => { ContentEncoding::Auto => {
@ -425,6 +432,8 @@ impl ContentEncoder {
} else { } else {
ContentEncoding::Identity ContentEncoding::Identity
}; };
#[cfg(not(any(feature = "brotli", feature = "flate2")))]
let mut encoding = ContentEncoding::Identity;
#[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))] #[cfg_attr(feature = "cargo-clippy", allow(match_ref_pats))]
let mut transfer = match resp.body() { let mut transfer = match resp.body() {
@ -435,40 +444,42 @@ impl ContentEncoder {
TransferEncoding::length(0, buf) TransferEncoding::length(0, buf)
} }
&Body::Binary(_) => { &Body::Binary(_) => {
if !(encoding == ContentEncoding::Identity #[cfg(any(feature = "brotli", feature = "flate2"))]
|| encoding == ContentEncoding::Auto)
{ {
let tmp = SharedBytes::default(); if !(encoding == ContentEncoding::Identity
let transfer = TransferEncoding::eof(tmp.clone()); || encoding == ContentEncoding::Auto)
let mut enc = match encoding { {
#[cfg(feature = "flate2")] let tmp = SharedBytes::default();
ContentEncoding::Deflate => ContentEncoder::Deflate( let transfer = TransferEncoding::eof(tmp.clone());
DeflateEncoder::new(transfer, Compression::fast()), let mut enc = match encoding {
), #[cfg(feature = "flate2")]
#[cfg(feature = "flate2")] ContentEncoding::Deflate => ContentEncoder::Deflate(
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new( DeflateEncoder::new(transfer, Compression::fast()),
transfer, ),
Compression::fast(), #[cfg(feature = "flate2")]
)), ContentEncoding::Gzip => ContentEncoder::Gzip(
#[cfg(feature = "brotli")] GzEncoder::new(transfer, Compression::fast()),
ContentEncoding::Br => { ),
ContentEncoder::Br(BrotliEncoder::new(transfer, 3)) #[cfg(feature = "brotli")]
} ContentEncoding::Br => {
ContentEncoding::Identity | ContentEncoding::Auto => { ContentEncoder::Br(BrotliEncoder::new(transfer, 3))
unreachable!() }
} ContentEncoding::Identity | ContentEncoding::Auto => {
}; unreachable!()
}
};
let bin = resp.replace_body(Body::Empty).binary(); let bin = resp.replace_body(Body::Empty).binary();
// TODO return error! // TODO return error!
let _ = enc.write(bin); let _ = enc.write(bin);
let _ = enc.write_eof(); let _ = enc.write_eof();
let body = tmp.take(); let body = tmp.take();
len = body.len(); len = body.len();
encoding = ContentEncoding::Identity; encoding = ContentEncoding::Identity;
resp.replace_body(Binary::from(body)); resp.replace_body(Binary::from(body));
}
} }
if is_head { if is_head {

View File

@ -1,6 +1,6 @@
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))] #![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
use bytes::BufMut; use bytes::{BytesMut, BufMut};
use futures::{Async, Poll}; use futures::{Async, Poll};
use std::io; use std::io;
use std::rc::Rc; use std::rc::Rc;
@ -45,7 +45,7 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>, stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>,
) -> H1Writer<T, H> { ) -> H1Writer<T, H> {
H1Writer { H1Writer {
flags: Flags::empty(), flags: Flags::KEEPALIVE,
encoder: ContentEncoder::empty(buf.clone()), encoder: ContentEncoder::empty(buf.clone()),
written: 0, written: 0,
headers_size: 0, headers_size: 0,
@ -62,7 +62,7 @@ impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.written = 0; self.written = 0;
self.flags = Flags::empty(); self.flags = Flags::KEEPALIVE;
} }
pub fn disconnected(&mut self) { pub fn disconnected(&mut self) {
@ -100,6 +100,16 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
self.written self.written
} }
#[inline]
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,
@ -108,9 +118,9 @@ impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
self.encoder = self.encoder =
ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding); ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) { if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags.insert(Flags::STARTED | Flags::KEEPALIVE); self.flags = Flags::STARTED | Flags::KEEPALIVE;
} else { } else {
self.flags.insert(Flags::STARTED); self.flags = Flags::STARTED;
} }
// Connection upgrade // Connection upgrade

View File

@ -71,6 +71,16 @@ impl<H: 'static> Writer for H2Writer<H> {
self.written self.written
} }
#[inline]
fn set_date(&self, dst: &mut BytesMut) {
self.settings.set_date(dst)
}
#[inline]
fn buffer(&self) -> &mut BytesMut {
self.buffer.get_mut()
}
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,

View File

@ -143,7 +143,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
} }
/// NOTE: bytes object has to contain enough space /// NOTE: bytes object has to contain enough space
pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) { pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
if n < 10 { if n < 10 {
let mut buf: [u8; 21] = [ let mut buf: [u8; 21] = [
b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e', b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', b'e',

View File

@ -3,7 +3,8 @@ use std::net::Shutdown;
use std::{io, time}; use std::{io, time};
use actix; use actix;
use futures::Poll; use bytes::BytesMut;
use futures::{Async, Poll};
use tokio_core::net::TcpStream; use tokio_core::net::TcpStream;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
@ -24,6 +25,9 @@ mod worker;
pub use self::settings::ServerSettings; pub use self::settings::ServerSettings;
pub use self::srv::HttpServer; pub use self::srv::HttpServer;
#[doc(hidden)]
pub use self::helpers::write_content_length;
use body::Binary; use body::Binary;
use error::Error; use error::Error;
use header::ContentEncoding; use header::ContentEncoding;
@ -132,13 +136,15 @@ impl HttpHandler for Box<HttpHandler> {
#[doc(hidden)] #[doc(hidden)]
pub trait HttpHandlerTask { pub trait HttpHandlerTask {
/// Poll task, this method is used before or after *io* object is available /// Poll task, this method is used before or after *io* object is available
fn poll(&mut self) -> Poll<(), Error>; fn poll(&mut self) -> Poll<(), Error>{
Ok(Async::Ready(()))
}
/// Poll task when *io* object is available /// Poll task when *io* object is available
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>; fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
/// Connection is disconnected /// Connection is disconnected
fn disconnected(&mut self); fn disconnected(&mut self) {}
} }
/// Conversion helper trait /// Conversion helper trait
@ -168,8 +174,16 @@ pub enum WriterState {
#[doc(hidden)] #[doc(hidden)]
/// Stream writer /// Stream writer
pub trait Writer { pub trait Writer {
/// number of bytes written to the stream
fn written(&self) -> u64; fn written(&self) -> u64;
#[doc(hidden)]
fn set_date(&self, st: &mut BytesMut);
#[doc(hidden)]
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref))]
fn buffer(&self) -> &mut BytesMut;
fn start( fn start(
&mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse, &mut self, req: &mut HttpInnerMessage, resp: &mut HttpResponse,
encoding: ContentEncoding, encoding: ContentEncoding,

View File

@ -309,7 +309,7 @@ where
/// The socket address to bind /// The socket address to bind
/// ///
/// To mind multiple addresses this method can be call multiple times. /// To bind multiple addresses this method can be called multiple times.
pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> { pub fn bind<S: net::ToSocketAddrs>(mut self, addr: S) -> io::Result<Self> {
let sockets = self.bind2(addr)?; let sockets = self.bind2(addr)?;
self.sockets.extend(sockets); self.sockets.extend(sockets);
@ -319,7 +319,7 @@ where
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
/// The ssl socket address to bind /// The ssl socket address to bind
/// ///
/// To mind multiple addresses this method can be call multiple times. /// To bind multiple addresses this method can be called multiple times.
pub fn bind_tls<S: net::ToSocketAddrs>( pub fn bind_tls<S: net::ToSocketAddrs>(
mut self, addr: S, acceptor: TlsAcceptor, mut self, addr: S, acceptor: TlsAcceptor,
) -> io::Result<Self> { ) -> io::Result<Self> {
@ -450,7 +450,6 @@ impl<H: IntoHttpHandler> HttpServer<H> {
self.accept.push(start_accept_thread( self.accept.push(start_accept_thread(
token, token,
sock, sock,
self.backlog,
tx.clone(), tx.clone(),
socks.clone(), socks.clone(),
workers.clone(), workers.clone(),
@ -782,7 +781,7 @@ enum Command {
} }
fn start_accept_thread( fn start_accept_thread(
token: usize, sock: Socket, backlog: i32, srv: mpsc::UnboundedSender<ServerCommand>, token: usize, sock: Socket, srv: mpsc::UnboundedSender<ServerCommand>,
socks: Slab<SocketInfo>, socks: Slab<SocketInfo>,
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>, mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) { ) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
@ -892,8 +891,8 @@ fn start_accept_thread(
}, },
CMD => match rx.try_recv() { CMD => match rx.try_recv() {
Ok(cmd) => match cmd { Ok(cmd) => match cmd {
Command::Pause => if let Some(server) = server.take() { Command::Pause => if let Some(ref server) = server {
if let Err(err) = poll.deregister(&server) { if let Err(err) = poll.deregister(server) {
error!( error!(
"Can not deregister server socket {}", "Can not deregister server socket {}",
err err
@ -906,15 +905,6 @@ fn start_accept_thread(
} }
}, },
Command::Resume => { Command::Resume => {
let lst = create_tcp_listener(addr, backlog)
.expect("Can not create net::TcpListener");
server = Some(
mio::net::TcpListener::from_std(lst).expect(
"Can not create mio::net::TcpListener",
),
);
if let Some(ref server) = server { if let Some(ref server) = server {
if let Err(err) = poll.register( if let Err(err) = poll.register(
server, server,

View File

@ -239,9 +239,9 @@ impl StreamHandlerType {
match *self { match *self {
StreamHandlerType::Normal => "http", StreamHandlerType::Normal => "http",
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
StreamHandlerType::Tls(ref acceptor) => "https", StreamHandlerType::Tls(_) => "https",
#[cfg(feature = "alpn")] #[cfg(feature = "alpn")]
StreamHandlerType::Alpn(ref acceptor) => "https", StreamHandlerType::Alpn(_) => "https",
} }
} }
} }

View File

@ -368,6 +368,80 @@ fn test_path_and_query_extractor2_async4() {
assert_eq!(response.status(), StatusCode::BAD_REQUEST); assert_eq!(response.status(), StatusCode::BAD_REQUEST);
} }
#[test]
fn test_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route()
.with(|p: Path<(usize,)>| {
format!("Welcome {}!", p.0)
})
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_nested_scope_and_path_extractor() {
let mut srv = test::TestServer::with_factory(move || {
App::new().scope("/sc", |scope| {
scope.nested("/{num}", |scope| {
scope.resource("/{num}/index.html", |r| {
r.route()
.with(|p: Path<(usize, usize)>| {
format!("Welcome {} {}!", p.0, p.1)
})
})
})
})
});
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/12/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.execute(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(b"Welcome 10 12!"));
// client request
let request = srv
.get()
.uri(srv.url("/sc/10/test1/index.html"))
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[cfg(actix_impl_trait)] #[cfg(actix_impl_trait)]
fn test_impl_trait( fn test_impl_trait(
data: (Json<Value>, Path<PParam>, Query<PParam>), data: (Json<Value>, Path<PParam>, Query<PParam>),

View File

@ -86,7 +86,13 @@ fn test_start() {
// pause // pause
let _ = srv_addr.send(server::PauseServer).wait(); let _ = srv_addr.send(server::PauseServer).wait();
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(200));
assert!(net::TcpStream::connect(addr).is_err()); {
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.timeout(time::Duration::from_millis(200))
.finish()
.unwrap();
assert!(sys.run_until_complete(req.send()).is_err());
}
// resume // resume
let _ = srv_addr.send(server::ResumeServer).wait(); let _ = srv_addr.send(server::ResumeServer).wait();