mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-22 13:45:13 +02:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6813ce789d | ||
|
cc6e0c6d04 | ||
|
d9496d46d1 | ||
|
bf8262196f | ||
|
17ecdd63d2 | ||
|
cc7f6b5eef | ||
|
ceca96da28 | ||
|
42f030d3f4 | ||
|
6d11ee683f | ||
|
80d4cbe301 | ||
|
69d710dbce | ||
|
0059a55dfb | ||
|
c695358bcb | ||
|
b018e4abaf | ||
|
346d85a884 | ||
|
9968afe4a6 | ||
|
f5bec968c7 | ||
|
a534fdd125 | ||
|
3431fff4d7 | ||
|
d6df2e3399 |
25
CHANGES.md
25
CHANGES.md
@@ -1,5 +1,30 @@
|
||||
# Changes
|
||||
|
||||
## [0.7.19] - 2019-03-29
|
||||
|
||||
### Added
|
||||
|
||||
* Add `from_file` and `from_file_with_config` to `NamedFile` to allow sending files without a known path. #670
|
||||
|
||||
* Add `insert` and `remove` methods to `HttpResponseBuilder`
|
||||
|
||||
* Add client HTTP Authentication methods `.basic_auth()` and `.bearer_auth()`. #540
|
||||
|
||||
* Add support for PATCH HTTP method
|
||||
|
||||
### Fixed
|
||||
|
||||
* Ignored the `If-Modified-Since` if `If-None-Match` is specified. #680
|
||||
|
||||
* Do not remove `Content-Length` on `Body::Empty` and insert zero value if it is missing for `POST` and `PUT` methods.
|
||||
|
||||
* Fix preflight CORS header compliance; refactor previous patch (#603). #717
|
||||
|
||||
* Fix never-ending HTTP2 request when response is empty (#709). #737
|
||||
|
||||
* Fix client payload decompression #674
|
||||
|
||||
|
||||
## [0.7.18] - 2019-01-10
|
||||
|
||||
### Added
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.7.18"
|
||||
version = "0.7.19"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@@ -64,7 +64,7 @@ cell = ["actix-net/cell"]
|
||||
actix = "0.7.9"
|
||||
actix-net = "0.2.6"
|
||||
|
||||
v_htmlescape = "0.3.2"
|
||||
v_htmlescape = "0.4"
|
||||
base64 = "0.10"
|
||||
bitflags = "1.0"
|
||||
failure = "^0.1.2"
|
||||
|
@@ -5,9 +5,9 @@
|
||||
//! # extern crate actix;
|
||||
//! # extern crate futures;
|
||||
//! # extern crate tokio;
|
||||
//! # use futures::Future;
|
||||
//! # use std::process;
|
||||
//! use actix_web::client;
|
||||
//! use futures::Future;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! actix::run(
|
||||
@@ -66,9 +66,9 @@ impl ResponseError for SendRequestError {
|
||||
/// # extern crate futures;
|
||||
/// # extern crate tokio;
|
||||
/// # extern crate env_logger;
|
||||
/// # use futures::Future;
|
||||
/// # use std::process;
|
||||
/// use actix_web::client;
|
||||
/// use futures::Future;
|
||||
///
|
||||
/// fn main() {
|
||||
/// actix::run(
|
||||
@@ -105,6 +105,13 @@ pub fn post<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
||||
builder
|
||||
}
|
||||
|
||||
/// Create request builder for `PATCH` requests
|
||||
pub fn patch<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
||||
let mut builder = ClientRequest::build();
|
||||
builder.method(Method::PATCH).uri(uri);
|
||||
builder
|
||||
}
|
||||
|
||||
/// Create request builder for `PUT` requests
|
||||
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
||||
let mut builder = ClientRequest::build();
|
||||
|
@@ -6,8 +6,8 @@ use std::time::{Duration, Instant};
|
||||
use std::{io, mem};
|
||||
use tokio_timer::Delay;
|
||||
|
||||
use actix_inner::dev::Request;
|
||||
use actix::{Addr, SystemService};
|
||||
use actix_inner::dev::Request;
|
||||
|
||||
use super::{
|
||||
ClientConnector, ClientConnectorError, ClientRequest, ClientResponse, Connect,
|
||||
@@ -88,7 +88,8 @@ impl SendRequest {
|
||||
}
|
||||
|
||||
pub(crate) fn with_connector(
|
||||
req: ClientRequest, conn: Addr<ClientConnector>,
|
||||
req: ClientRequest,
|
||||
conn: Addr<ClientConnector>,
|
||||
) -> SendRequest {
|
||||
SendRequest {
|
||||
req,
|
||||
@@ -363,11 +364,11 @@ impl Pipeline {
|
||||
if let Some(ref mut decompress) = self.decompress {
|
||||
match decompress.feed_data(b) {
|
||||
Ok(Some(b)) => return Ok(Async::Ready(Some(b))),
|
||||
Ok(None) => return Ok(Async::NotReady),
|
||||
Ok(None) => continue,
|
||||
Err(ref err)
|
||||
if err.kind() == io::ErrorKind::WouldBlock =>
|
||||
{
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ use serde::Serialize;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
use url::Url;
|
||||
use base64::encode;
|
||||
|
||||
use super::connector::{ClientConnector, Connection};
|
||||
use super::pipeline::SendRequest;
|
||||
@@ -111,6 +112,13 @@ impl ClientRequest {
|
||||
builder
|
||||
}
|
||||
|
||||
/// Create request builder for `PATCH` request
|
||||
pub fn patch<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
||||
let mut builder = ClientRequest::build();
|
||||
builder.method(Method::PATCH).uri(uri);
|
||||
builder
|
||||
}
|
||||
|
||||
/// Create request builder for `PUT` request
|
||||
pub fn put<U: AsRef<str>>(uri: U) -> ClientRequestBuilder {
|
||||
let mut builder = ClientRequest::build();
|
||||
@@ -485,6 +493,29 @@ impl ClientRequestBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set HTTP basic authorization
|
||||
pub fn basic_auth<U, P>(&mut self, username: U, password: Option<P>) -> &mut Self
|
||||
where
|
||||
U: fmt::Display,
|
||||
P: fmt::Display,
|
||||
{
|
||||
let auth = match password {
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}", username)
|
||||
};
|
||||
let header_value = format!("Basic {}", encode(&auth));
|
||||
self.header(header::AUTHORIZATION, &*header_value)
|
||||
}
|
||||
|
||||
/// Set HTTP bearer authentication
|
||||
pub fn bearer_auth<T>( &mut self, token: T) -> &mut Self
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
let header_value = format!("Bearer {}", token);
|
||||
self.header(header::AUTHORIZATION, &*header_value)
|
||||
}
|
||||
|
||||
/// Set content length
|
||||
#[inline]
|
||||
pub fn content_length(&mut self, len: u64) -> &mut Self {
|
||||
|
@@ -16,9 +16,9 @@ use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
use flate2::Compression;
|
||||
use futures::{Async, Poll};
|
||||
use http::header::{
|
||||
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||
self, HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||
};
|
||||
use http::{HttpTryFrom, Version};
|
||||
use http::{Method, HttpTryFrom, Version};
|
||||
use time::{self, Duration};
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
@@ -223,7 +223,19 @@ fn content_encoder(buf: BytesMut, req: &mut ClientRequest) -> Output {
|
||||
|
||||
let transfer = match body {
|
||||
Body::Empty => {
|
||||
req.headers_mut().remove(CONTENT_LENGTH);
|
||||
match req.method() {
|
||||
//Insert zero content-length only if user hasn't added it.
|
||||
//We don't really need it for other methods as they are not supposed to carry payload
|
||||
&Method::POST | &Method::PUT | &Method::PATCH => {
|
||||
req.headers_mut()
|
||||
.entry(CONTENT_LENGTH)
|
||||
.expect("CONTENT_LENGTH to be valid header name")
|
||||
.or_insert(header::HeaderValue::from_static("0"));
|
||||
},
|
||||
_ => {
|
||||
req.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
}
|
||||
return Output::Empty(buf);
|
||||
}
|
||||
Body::Binary(ref mut bytes) => {
|
||||
@@ -410,3 +422,76 @@ impl CachedDate {
|
||||
self.next_update.nsec = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_content_encoder_empty_body() {
|
||||
let mut req = ClientRequest::post("http://google.com").finish().expect("Create request");
|
||||
|
||||
let result = content_encoder(BytesMut::new(), &mut req);
|
||||
|
||||
match result {
|
||||
Output::Empty(buf) => {
|
||||
assert_eq!(buf.len(), 0);
|
||||
let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty POST");
|
||||
assert_eq!(content_len, "0");
|
||||
},
|
||||
_ => panic!("Unexpected result, should be Output::Empty"),
|
||||
}
|
||||
|
||||
req.set_method(Method::GET);
|
||||
|
||||
let result = content_encoder(BytesMut::new(), &mut req);
|
||||
|
||||
match result {
|
||||
Output::Empty(buf) => {
|
||||
assert_eq!(buf.len(), 0);
|
||||
assert!(!req.headers().contains_key(CONTENT_LENGTH));
|
||||
},
|
||||
_ => panic!("Unexpected result, should be Output::Empty"),
|
||||
}
|
||||
|
||||
req.set_method(Method::PUT);
|
||||
|
||||
let result = content_encoder(BytesMut::new(), &mut req);
|
||||
|
||||
match result {
|
||||
Output::Empty(buf) => {
|
||||
assert_eq!(buf.len(), 0);
|
||||
let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty PUT");
|
||||
assert_eq!(content_len, "0");
|
||||
},
|
||||
_ => panic!("Unexpected result, should be Output::Empty"),
|
||||
}
|
||||
|
||||
req.set_method(Method::DELETE);
|
||||
|
||||
let result = content_encoder(BytesMut::new(), &mut req);
|
||||
|
||||
match result {
|
||||
Output::Empty(buf) => {
|
||||
assert_eq!(buf.len(), 0);
|
||||
assert!(!req.headers().contains_key(CONTENT_LENGTH));
|
||||
},
|
||||
_ => panic!("Unexpected result, should be Output::Empty"),
|
||||
}
|
||||
|
||||
req.set_method(Method::PATCH);
|
||||
|
||||
let result = content_encoder(BytesMut::new(), &mut req);
|
||||
|
||||
match result {
|
||||
Output::Empty(buf) => {
|
||||
assert_eq!(buf.len(), 0);
|
||||
let content_len = req.headers().get(CONTENT_LENGTH).expect("To set Content-Length for empty PATCH");
|
||||
assert_eq!(content_len, "0");
|
||||
},
|
||||
_ => panic!("Unexpected result, should be Output::Empty"),
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -193,7 +193,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from from the request's query.
|
||||
/// Extract typed information from the request's query.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
237
src/fs.rs
237
src/fs.rs
@@ -11,7 +11,7 @@ use std::{cmp, io};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use v_htmlescape::HTMLEscape;
|
||||
use v_htmlescape::escape as escape_html_entity;
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use futures_cpupool::{CpuFuture, CpuPool};
|
||||
@@ -120,6 +120,32 @@ pub struct NamedFile<C = DefaultConfig> {
|
||||
}
|
||||
|
||||
impl NamedFile {
|
||||
/// Creates an instance from a previously opened file.
|
||||
///
|
||||
/// The given `path` need not exist and is only used to determine the `ContentType` and
|
||||
/// `ContentDisposition` headers.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// extern crate actix_web;
|
||||
///
|
||||
/// use actix_web::fs::NamedFile;
|
||||
/// use std::io::{self, Write};
|
||||
/// use std::env;
|
||||
/// use std::fs::File;
|
||||
///
|
||||
/// fn main() -> io::Result<()> {
|
||||
/// let mut file = File::create("foo.txt")?;
|
||||
/// file.write_all(b"Hello, world!")?;
|
||||
/// let named_file = NamedFile::from_file(file, "bar.txt")?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from_file<P: AsRef<Path>>(file: File, path: P) -> io::Result<NamedFile> {
|
||||
Self::from_file_with_config(file, path, DefaultConfig)
|
||||
}
|
||||
|
||||
/// Attempts to open a file in read-only mode.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -135,16 +161,29 @@ impl NamedFile {
|
||||
}
|
||||
|
||||
impl<C: StaticFileConfig> NamedFile<C> {
|
||||
/// Attempts to open a file in read-only mode using provided configiration.
|
||||
/// Creates an instance from a previously opened file using the provided configuration.
|
||||
///
|
||||
/// The given `path` need not exist and is only used to determine the `ContentType` and
|
||||
/// `ContentDisposition` headers.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::fs::{DefaultConfig, NamedFile};
|
||||
/// ```no_run
|
||||
/// extern crate actix_web;
|
||||
///
|
||||
/// let file = NamedFile::open_with_config("foo.txt", DefaultConfig);
|
||||
/// use actix_web::fs::{DefaultConfig, NamedFile};
|
||||
/// use std::io::{self, Write};
|
||||
/// use std::env;
|
||||
/// use std::fs::File;
|
||||
///
|
||||
/// fn main() -> io::Result<()> {
|
||||
/// let mut file = File::create("foo.txt")?;
|
||||
/// file.write_all(b"Hello, world!")?;
|
||||
/// let named_file = NamedFile::from_file_with_config(file, "bar.txt", DefaultConfig)?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn open_with_config<P: AsRef<Path>>(path: P, _: C) -> io::Result<NamedFile<C>> {
|
||||
pub fn from_file_with_config<P: AsRef<Path>>(file: File, path: P, _: C) -> io::Result<NamedFile<C>> {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
|
||||
// Get the name of the file and use it to construct default Content-Type
|
||||
@@ -169,7 +208,6 @@ impl<C: StaticFileConfig> NamedFile<C> {
|
||||
(ct, cd)
|
||||
};
|
||||
|
||||
let file = File::open(&path)?;
|
||||
let md = file.metadata()?;
|
||||
let modified = md.modified().ok();
|
||||
let cpu_pool = None;
|
||||
@@ -188,6 +226,19 @@ impl<C: StaticFileConfig> NamedFile<C> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to open a file in read-only mode using provided configuration.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use actix_web::fs::{DefaultConfig, NamedFile};
|
||||
///
|
||||
/// let file = NamedFile::open_with_config("foo.txt", DefaultConfig);
|
||||
/// ```
|
||||
pub fn open_with_config<P: AsRef<Path>>(path: P, config: C) -> io::Result<NamedFile<C>> {
|
||||
Self::from_file_with_config(File::open(&path)?, path, config)
|
||||
}
|
||||
|
||||
/// Returns reference to the underlying `File` object.
|
||||
#[inline]
|
||||
pub fn file(&self) -> &File {
|
||||
@@ -390,6 +441,8 @@ impl<C: StaticFileConfig> Responder for NamedFile<C> {
|
||||
// check last modified
|
||||
let not_modified = if !none_match(etag.as_ref(), req) {
|
||||
true
|
||||
} else if req.headers().contains_key(header::IF_NONE_MATCH) {
|
||||
false
|
||||
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
|
||||
(last_modified, req.get_header())
|
||||
{
|
||||
@@ -569,11 +622,6 @@ macro_rules! encode_file_url {
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn escape_html_entity(s: &str) -> HTMLEscape {
|
||||
HTMLEscape::from(s)
|
||||
}
|
||||
|
||||
// " -- " & -- & ' -- ' < -- < > -- > / -- /
|
||||
macro_rules! encode_file_name {
|
||||
($entry:ident) => {
|
||||
@@ -744,7 +792,7 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||
|
||||
/// Set index file
|
||||
///
|
||||
/// Redirects to specific index file for directory "/" instead of
|
||||
/// Shows specific index file for directory "/" instead of
|
||||
/// showing files listing.
|
||||
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S, C> {
|
||||
self.index = Some(index.into());
|
||||
@@ -769,17 +817,11 @@ impl<S: 'static, C: StaticFileConfig> StaticFiles<S, C> {
|
||||
|
||||
if path.is_dir() {
|
||||
if let Some(ref redir_index) = self.index {
|
||||
// TODO: Don't redirect, just return the index content.
|
||||
// TODO: It'd be nice if there were a good usable URL manipulation
|
||||
// library
|
||||
let mut new_path: String = req.path().to_owned();
|
||||
if !new_path.ends_with('/') {
|
||||
new_path.push('/');
|
||||
}
|
||||
new_path.push_str(redir_index);
|
||||
HttpResponse::Found()
|
||||
.header(header::LOCATION, new_path.as_str())
|
||||
.finish()
|
||||
let path = path.join(redir_index);
|
||||
|
||||
NamedFile::open_with_config(path, C::default())?
|
||||
.set_cpu_pool(self.cpu_pool.clone())
|
||||
.respond_to(&req)?
|
||||
.respond_to(&req)
|
||||
} else if self.show_index {
|
||||
let dir = Directory::new(self.directory.clone(), path);
|
||||
@@ -904,6 +946,8 @@ impl HttpRange {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
use std::time::Duration;
|
||||
use std::ops::Add;
|
||||
|
||||
use super::*;
|
||||
use application::App;
|
||||
@@ -923,6 +967,43 @@ mod tests {
|
||||
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_modified_since_without_if_none_match() {
|
||||
let mut file = NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
.set_cpu_pool(CpuPool::new(1));
|
||||
let since = header::HttpDate::from(
|
||||
SystemTime::now().add(Duration::from_secs(60)));
|
||||
|
||||
let req = TestRequest::default()
|
||||
.header(header::IF_MODIFIED_SINCE, since)
|
||||
.finish();
|
||||
let resp = file.respond_to(&req).unwrap();
|
||||
assert_eq!(
|
||||
resp.status(),
|
||||
StatusCode::NOT_MODIFIED
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_modified_since_with_if_none_match() {
|
||||
let mut file = NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
.set_cpu_pool(CpuPool::new(1));
|
||||
let since = header::HttpDate::from(
|
||||
SystemTime::now().add(Duration::from_secs(60)));
|
||||
|
||||
let req = TestRequest::default()
|
||||
.header(header::IF_NONE_MATCH, "miss_etag")
|
||||
.header(header::IF_MODIFIED_SINCE, since)
|
||||
.finish();
|
||||
let resp = file.respond_to(&req).unwrap();
|
||||
assert_ne!(
|
||||
resp.status(),
|
||||
StatusCode::NOT_MODIFIED
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_named_file_text() {
|
||||
assert!(NamedFile::open("test--").is_err());
|
||||
@@ -1436,43 +1517,66 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_redirect_to_index() {
|
||||
let st = StaticFiles::new(".").unwrap().index_file("index.html");
|
||||
fn test_serve_index() {
|
||||
let st = StaticFiles::new(".").unwrap().index_file("test.binary");
|
||||
let req = TestRequest::default().uri("/tests").finish();
|
||||
|
||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::LOCATION).unwrap(),
|
||||
"/tests/index.html"
|
||||
resp.headers().get(header::CONTENT_TYPE).expect("content type"),
|
||||
"application/octet-stream"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_DISPOSITION).expect("content disposition"),
|
||||
"attachment; filename=\"test.binary\""
|
||||
);
|
||||
|
||||
let req = TestRequest::default().uri("/tests/").finish();
|
||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::LOCATION).unwrap(),
|
||||
"/tests/index.html"
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"application/octet-stream"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||
"attachment; filename=\"test.binary\""
|
||||
);
|
||||
|
||||
// nonexistent index file
|
||||
let req = TestRequest::default().uri("/tests/unknown").finish();
|
||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let req = TestRequest::default().uri("/tests/unknown/").finish();
|
||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_redirect_to_index_nested() {
|
||||
fn test_serve_index_nested() {
|
||||
let st = StaticFiles::new(".").unwrap().index_file("mod.rs");
|
||||
let req = TestRequest::default().uri("/src/client").finish();
|
||||
let resp = st.handle(&req).respond_to(&req).unwrap();
|
||||
let resp = resp.as_msg();
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::LOCATION).unwrap(),
|
||||
"/src/client/mod.rs"
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"text/x-rust"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||
"inline; filename=\"mod.rs\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_redirect_to_index_with_prefix() {
|
||||
fn integration_serve_index_with_prefix() {
|
||||
let mut srv = test::TestServer::with_factory(|| {
|
||||
App::new()
|
||||
.prefix("public")
|
||||
@@ -1481,29 +1585,21 @@ mod tests {
|
||||
|
||||
let request = srv.get().uri(srv.url("/public")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::FOUND);
|
||||
let loc = response
|
||||
.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert_eq!(loc, "/public/Cargo.toml");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||
assert_eq!(bytes, data);
|
||||
|
||||
let request = srv.get().uri(srv.url("/public/")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::FOUND);
|
||||
let loc = response
|
||||
.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert_eq!(loc, "/public/Cargo.toml");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||
assert_eq!(bytes, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_redirect_to_index() {
|
||||
fn integration_serve_index() {
|
||||
let mut srv = test::TestServer::with_factory(|| {
|
||||
App::new().handler(
|
||||
"test",
|
||||
@@ -1513,25 +1609,26 @@ mod tests {
|
||||
|
||||
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::FOUND);
|
||||
let loc = response
|
||||
.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert_eq!(loc, "/test/Cargo.toml");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||
assert_eq!(bytes, data);
|
||||
|
||||
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::FOUND);
|
||||
let loc = response
|
||||
.headers()
|
||||
.get(header::LOCATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
assert_eq!(loc, "/test/Cargo.toml");
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
||||
assert_eq!(bytes, data);
|
||||
|
||||
// nonexistent index file
|
||||
let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap();
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@@ -48,10 +48,10 @@ impl HttpResponse {
|
||||
self.0.as_mut()
|
||||
}
|
||||
|
||||
/// Create http response builder with specific status.
|
||||
/// Create a new HTTP response builder with specific status.
|
||||
#[inline]
|
||||
pub fn build(status: StatusCode) -> HttpResponseBuilder {
|
||||
HttpResponsePool::get(status)
|
||||
HttpResponseBuilder::new(status)
|
||||
}
|
||||
|
||||
/// Create http response builder
|
||||
@@ -246,7 +246,7 @@ impl HttpResponse {
|
||||
self
|
||||
}
|
||||
|
||||
/// Get body os this response
|
||||
/// Get body of this response
|
||||
#[inline]
|
||||
pub fn body(&self) -> &Body {
|
||||
&self.get_ref().body
|
||||
@@ -346,6 +346,12 @@ pub struct HttpResponseBuilder {
|
||||
}
|
||||
|
||||
impl HttpResponseBuilder {
|
||||
/// Create a new HTTP response builder with specific status.
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> HttpResponseBuilder {
|
||||
HttpResponsePool::get(status)
|
||||
}
|
||||
|
||||
/// Set HTTP status code of this response.
|
||||
#[inline]
|
||||
pub fn status(&mut self, status: StatusCode) -> &mut Self {
|
||||
@@ -366,7 +372,7 @@ impl HttpResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header.
|
||||
/// Append a header.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
@@ -394,7 +400,7 @@ impl HttpResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header.
|
||||
/// Append a header.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
@@ -426,6 +432,65 @@ impl HttpResponseBuilder {
|
||||
}
|
||||
self
|
||||
}
|
||||
/// Set or replace a header with a single value.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, HttpRequest, HttpResponse};
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// HttpResponse::Ok()
|
||||
/// .insert("X-TEST", "value")
|
||||
/// .insert(http::header::CONTENT_TYPE, "application/json")
|
||||
/// .finish()
|
||||
/// }
|
||||
/// fn main() {}
|
||||
/// ```
|
||||
pub fn insert<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||
where
|
||||
HeaderName: HttpTryFrom<K>,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match value.try_into() {
|
||||
Ok(value) => {
|
||||
parts.headers.insert(key, value);
|
||||
}
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
},
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Remove all instances of a header already set on this `HttpResponseBuilder`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{http, HttpRequest, HttpResponse};
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// HttpResponse::Ok()
|
||||
/// .header(http::header::CONTENT_TYPE, "nevermind") // won't be used
|
||||
/// .remove(http::header::CONTENT_TYPE)
|
||||
/// .finish()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn remove<K>(&mut self, key: K) -> &mut Self
|
||||
where HeaderName: HttpTryFrom<K>
|
||||
{
|
||||
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => {
|
||||
parts.headers.remove(key);
|
||||
},
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the custom reason for the response.
|
||||
#[inline]
|
||||
@@ -1118,6 +1183,14 @@ mod tests {
|
||||
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_new() {
|
||||
let resp = HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
|
||||
.finish();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_basic_builder() {
|
||||
let resp = HttpResponse::Ok()
|
||||
@@ -1128,6 +1201,40 @@ mod tests {
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert() {
|
||||
let resp = HttpResponse::Ok()
|
||||
.insert("deleteme", "old value")
|
||||
.insert("deleteme", "new value")
|
||||
.finish();
|
||||
assert_eq!("new value", resp.headers().get("deleteme").expect("new value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let resp = HttpResponse::Ok()
|
||||
.header("deleteme", "value")
|
||||
.remove("deleteme")
|
||||
.finish();
|
||||
assert!(resp.headers().get("deleteme").is_none())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_replace() {
|
||||
let resp = HttpResponse::Ok()
|
||||
.header("some-header", "old_value1")
|
||||
.header("some-header", "old_value2")
|
||||
.remove("some-header")
|
||||
.header("some-header", "new_value1")
|
||||
.header("some-header", "new_value2")
|
||||
.remove("unrelated-header")
|
||||
.finish();
|
||||
let mut v = resp.headers().get_all("some-header").into_iter();
|
||||
assert_eq!("new_value1", v.next().unwrap());
|
||||
assert_eq!("new_value2", v.next().unwrap());
|
||||
assert_eq!(None, v.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upgrade() {
|
||||
let resp = HttpResponse::build(StatusCode::OK).upgrade().finish();
|
||||
|
@@ -307,6 +307,32 @@ impl Cors {
|
||||
}
|
||||
}
|
||||
|
||||
fn access_control_allow_origin(&self, req: &Request) -> Option<HeaderValue> {
|
||||
match self.inner.origins {
|
||||
AllOrSome::All => {
|
||||
if self.inner.send_wildcard {
|
||||
Some(HeaderValue::from_static("*"))
|
||||
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
|
||||
Some(origin.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
AllOrSome::Some(ref origins) => {
|
||||
if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| {
|
||||
match o.to_str() {
|
||||
Ok(os) => origins.contains(os),
|
||||
_ => false
|
||||
}
|
||||
}) {
|
||||
Some(origin.clone())
|
||||
} else {
|
||||
Some(self.inner.origins_str.as_ref().unwrap().clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_allowed_method(&self, req: &Request) -> Result<(), CorsError> {
|
||||
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
|
||||
if let Ok(meth) = hdr.to_str() {
|
||||
@@ -390,21 +416,9 @@ impl<S> Middleware<S> for Cors {
|
||||
}).if_some(headers, |headers, resp| {
|
||||
let _ =
|
||||
resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
|
||||
}).if_true(self.inner.origins.is_all(), |resp| {
|
||||
if self.inner.send_wildcard {
|
||||
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
} else {
|
||||
let origin = req.headers().get(header::ORIGIN).unwrap();
|
||||
resp.header(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
origin.clone(),
|
||||
);
|
||||
}
|
||||
}).if_true(self.inner.origins.is_some(), |resp| {
|
||||
resp.header(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
self.inner.origins_str.as_ref().unwrap().clone(),
|
||||
);
|
||||
}).if_some(self.access_control_allow_origin(&req), |origin, resp| {
|
||||
let _ =
|
||||
resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
}).if_true(self.inner.supports_credentials, |resp| {
|
||||
resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
}).header(
|
||||
@@ -430,37 +444,11 @@ impl<S> Middleware<S> for Cors {
|
||||
fn response(
|
||||
&self, req: &HttpRequest<S>, mut resp: HttpResponse,
|
||||
) -> Result<Response> {
|
||||
match self.inner.origins {
|
||||
AllOrSome::All => {
|
||||
if self.inner.send_wildcard {
|
||||
resp.headers_mut().insert(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
HeaderValue::from_static("*"),
|
||||
);
|
||||
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
|
||||
resp.headers_mut()
|
||||
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
||||
}
|
||||
}
|
||||
AllOrSome::Some(ref origins) => {
|
||||
if let Some(origin) = req.headers().get(header::ORIGIN).filter(|o| {
|
||||
match o.to_str() {
|
||||
Ok(os) => origins.contains(os),
|
||||
_ => false
|
||||
}
|
||||
}) {
|
||||
resp.headers_mut().insert(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
origin.clone(),
|
||||
);
|
||||
} else {
|
||||
resp.headers_mut().insert(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
self.inner.origins_str.as_ref().unwrap().clone()
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(origin) = self.access_control_allow_origin(req) {
|
||||
resp.headers_mut()
|
||||
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
||||
};
|
||||
|
||||
if let Some(ref expose) = self.inner.expose_hdrs {
|
||||
resp.headers_mut().insert(
|
||||
@@ -1201,7 +1189,6 @@ mod tests {
|
||||
let resp: HttpResponse = HttpResponse::Ok().into();
|
||||
|
||||
let resp = cors.response(&req, resp).unwrap().response();
|
||||
print!("{:?}", resp);
|
||||
assert_eq!(
|
||||
&b"https://example.com"[..],
|
||||
resp.headers()
|
||||
@@ -1224,4 +1211,42 @@ mod tests {
|
||||
.as_bytes()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_origins_preflight() {
|
||||
let cors = Cors::build()
|
||||
.allowed_origin("https://example.com")
|
||||
.allowed_origin("https://example.org")
|
||||
.allowed_methods(vec![Method::GET])
|
||||
.finish();
|
||||
|
||||
|
||||
let req = TestRequest::with_header("Origin", "https://example.com")
|
||||
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
|
||||
let resp = cors.start(&req).ok().unwrap().response();
|
||||
assert_eq!(
|
||||
&b"https://example.com"[..],
|
||||
resp.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
.unwrap()
|
||||
.as_bytes()
|
||||
);
|
||||
|
||||
let req = TestRequest::with_header("Origin", "https://example.org")
|
||||
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
|
||||
let resp = cors.start(&req).ok().unwrap().response();
|
||||
assert_eq!(
|
||||
&b"https://example.org"[..],
|
||||
resp.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
.unwrap()
|
||||
.as_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -107,6 +107,12 @@ impl<S: 'static> Resource<S> {
|
||||
self.routes.last_mut().unwrap().filter(pred::Post())
|
||||
}
|
||||
|
||||
/// Register a new `PATCH` route.
|
||||
pub fn patch(&mut self) -> &mut Route<S> {
|
||||
self.routes.push(Route::default());
|
||||
self.routes.last_mut().unwrap().filter(pred::Patch())
|
||||
}
|
||||
|
||||
/// Register a new `PUT` route.
|
||||
pub fn put(&mut self) -> &mut Route<S> {
|
||||
self.routes.push(Route::default());
|
||||
|
@@ -234,6 +234,16 @@ impl<H: 'static> Writer for H2Writer<H> {
|
||||
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
|
||||
}
|
||||
|
||||
if self.flags.contains(Flags::EOF)
|
||||
&& !self.flags.contains(Flags::RESERVED)
|
||||
&& self.buffer.is_empty()
|
||||
{
|
||||
if let Err(e) = stream.send_data(Bytes::new(), true) {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, e));
|
||||
}
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
|
||||
loop {
|
||||
match stream.poll_capacity() {
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
|
@@ -451,9 +451,8 @@ impl<H: IntoHttpHandler, F: Fn() -> H + Send + Clone> HttpServer<H, F> {
|
||||
/// For each address this method starts separate thread which does
|
||||
/// `accept()` in a loop.
|
||||
///
|
||||
/// This methods panics if no socket addresses get bound.
|
||||
///
|
||||
/// This method requires to run within properly configured `Actix` system.
|
||||
/// This methods panics if no socket address can be bound or an `Actix` system is not yet
|
||||
/// configured.
|
||||
///
|
||||
/// ```rust
|
||||
/// extern crate actix_web;
|
||||
|
@@ -303,6 +303,8 @@ pub trait IoStream: AsyncRead + AsyncWrite + 'static {
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
} else if e.kind() == io::ErrorKind::ConnectionReset && read_some {
|
||||
Ok(Async::Ready((read_some, true)))
|
||||
} else {
|
||||
Err(e)
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use std::net::{Shutdown, SocketAddr};
|
||||
use std::rc::Rc;
|
||||
use std::{io, time};
|
||||
|
||||
use actix_net::ssl;
|
||||
@@ -6,6 +7,7 @@ use openssl::ssl::{AlpnError, SslAcceptor, SslAcceptorBuilder};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use tokio_openssl::SslStream;
|
||||
|
||||
use extensions::Extensions;
|
||||
use server::{IoStream, ServerFlags};
|
||||
|
||||
/// Support `SSL` connections via openssl package
|
||||
@@ -84,4 +86,14 @@ impl<T: IoStream> IoStream for SslStream<T> {
|
||||
fn set_keepalive(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||
self.get_mut().get_mut().set_keepalive(dur)
|
||||
}
|
||||
|
||||
fn extensions(&self) -> Option<Rc<Extensions>> {
|
||||
if let Some(x509) = self.get_ref().ssl().peer_certificate() {
|
||||
let mut extensions = Extensions::new();
|
||||
extensions.insert(x509);
|
||||
Some(Rc::new(extensions))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -239,6 +239,11 @@ impl TestServer {
|
||||
ClientRequest::post(self.url("/").as_str())
|
||||
}
|
||||
|
||||
/// Create `PATCH` request
|
||||
pub fn patch(&self) -> ClientRequestBuilder {
|
||||
ClientRequest::patch(self.url("/").as_str())
|
||||
}
|
||||
|
||||
/// Create `HEAD` request
|
||||
pub fn head(&self) -> ClientRequestBuilder {
|
||||
ClientRequest::head(self.url("/").as_str())
|
||||
@@ -558,7 +563,7 @@ impl<S: 'static> TestRequest<S> {
|
||||
|
||||
/// set cookie of this request
|
||||
pub fn cookie(mut self, cookie: Cookie<'static>) -> Self {
|
||||
if self.cookies.is_none() {
|
||||
if self.cookies.is_some() {
|
||||
let mut should_insert = true;
|
||||
let old_cookies = self.cookies.as_mut().unwrap();
|
||||
for old_cookie in old_cookies.iter() {
|
||||
|
@@ -506,3 +506,31 @@ fn client_read_until_eof() {
|
||||
let bytes = sys.block_on(response.body()).unwrap();
|
||||
assert_eq!(bytes, Bytes::from_static(b"welcome!"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_basic_auth() {
|
||||
let mut srv =
|
||||
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
|
||||
/// set authorization header to Basic <base64 encoded username:password>
|
||||
let request = srv
|
||||
.get()
|
||||
.basic_auth("username", Some("password"))
|
||||
.finish()
|
||||
.unwrap();
|
||||
let repr = format!("{:?}", request);
|
||||
assert!(repr.contains("Basic dXNlcm5hbWU6cGFzc3dvcmQ="));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_bearer_auth() {
|
||||
let mut srv =
|
||||
test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
|
||||
/// set authorization header to Bearer <token>
|
||||
let request = srv
|
||||
.get()
|
||||
.bearer_auth("someS3cr3tAutht0k3n")
|
||||
.finish()
|
||||
.unwrap();
|
||||
let repr = format!("{:?}", request);
|
||||
assert!(repr.contains("Bearer someS3cr3tAutht0k3n"));
|
||||
}
|
@@ -1398,3 +1398,11 @@ fn test_content_length() {
|
||||
assert_eq!(response.headers().get(&header), Some(&value));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_patch_method() {
|
||||
let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok()));
|
||||
let req = srv.patch().finish().unwrap();
|
||||
let response = srv.execute(req.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
Reference in New Issue
Block a user