mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-28 01:32:57 +01:00
added support for websocket testing
This commit is contained in:
parent
76f9542df7
commit
577f91206c
@ -78,7 +78,8 @@ tokio-openssl = { version="0.2", optional = true }
|
|||||||
|
|
||||||
[dependencies.actix]
|
[dependencies.actix]
|
||||||
#version = "^0.4.6"
|
#version = "^0.4.6"
|
||||||
git = "https://github.com/actix/actix.git"
|
#git = "https://github.com/actix/actix.git"
|
||||||
|
path = "../actix"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.5"
|
env_logger = "0.5"
|
||||||
|
@ -95,3 +95,54 @@ fn main() {
|
|||||||
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request
|
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## WebSocket server tests
|
||||||
|
|
||||||
|
It is possible to register *handler* with `TestApp::handler()` method that
|
||||||
|
initiate web socket connection. *TestServer* provides `ws()` which connects to
|
||||||
|
websocket server and returns ws reader and writer objects. *TestServer* also
|
||||||
|
provides `execute()` method which runs future object to completion and returns
|
||||||
|
result of the future computation.
|
||||||
|
|
||||||
|
Here is simple example, that shows how to test server websocket handler.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
|
# extern crate futures;
|
||||||
|
# extern crate http;
|
||||||
|
# extern crate bytes;
|
||||||
|
|
||||||
|
use actix_web::*;
|
||||||
|
use futures::Stream;
|
||||||
|
# use actix::prelude::*;
|
||||||
|
|
||||||
|
struct Ws; // <- WebSocket actor
|
||||||
|
|
||||||
|
impl Actor for Ws {
|
||||||
|
type Context = ws::WebsocketContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<ws::Message> for Ws {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
|
match msg {
|
||||||
|
ws::Message::Text(text) => ctx.text(&text),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut srv = test::TestServer::new( // <- start our server with ws handler
|
||||||
|
|app| app.handler(|req| ws::start(req, Ws)));
|
||||||
|
|
||||||
|
let (reader, mut writer) = srv.ws().unwrap(); // <- connect to ws server
|
||||||
|
|
||||||
|
writer.text("text"); // <- send message to server
|
||||||
|
|
||||||
|
let (item, reader) = srv.execute(reader.into_future()).unwrap(); // <- wait for one message
|
||||||
|
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
27
src/test.rs
27
src/test.rs
@ -6,7 +6,7 @@ use std::sync::mpsc;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use actix::{Arbiter, SyncAddress, System, msgs};
|
use actix::{Arbiter, SyncAddress, System, SystemRunner, msgs};
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use http::{Uri, Method, Version, HeaderMap, HttpTryFrom};
|
use http::{Uri, Method, Version, HeaderMap, HttpTryFrom};
|
||||||
use http::header::{HeaderName, HeaderValue};
|
use http::header::{HeaderName, HeaderValue};
|
||||||
@ -25,6 +25,7 @@ use payload::Payload;
|
|||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use server::{HttpServer, HttpHandler, IntoHttpHandler, ServerSettings};
|
use server::{HttpServer, HttpHandler, IntoHttpHandler, ServerSettings};
|
||||||
|
use ws::{WsClient, WsClientError, WsClientReader, WsClientWriter};
|
||||||
|
|
||||||
/// The `TestServer` type.
|
/// The `TestServer` type.
|
||||||
///
|
///
|
||||||
@ -54,7 +55,8 @@ use server::{HttpServer, HttpHandler, IntoHttpHandler, ServerSettings};
|
|||||||
pub struct TestServer {
|
pub struct TestServer {
|
||||||
addr: net::SocketAddr,
|
addr: net::SocketAddr,
|
||||||
thread: Option<thread::JoinHandle<()>>,
|
thread: Option<thread::JoinHandle<()>>,
|
||||||
sys: SyncAddress<System>,
|
system: SystemRunner,
|
||||||
|
server_sys: SyncAddress<System>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestServer {
|
impl TestServer {
|
||||||
@ -95,7 +97,8 @@ impl TestServer {
|
|||||||
TestServer {
|
TestServer {
|
||||||
addr: addr,
|
addr: addr,
|
||||||
thread: Some(join),
|
thread: Some(join),
|
||||||
sys: sys,
|
system: System::new("actix-test"),
|
||||||
|
server_sys: sys,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +134,8 @@ impl TestServer {
|
|||||||
TestServer {
|
TestServer {
|
||||||
addr: addr,
|
addr: addr,
|
||||||
thread: Some(join),
|
thread: Some(join),
|
||||||
sys: sys,
|
system: System::new("actix-test"),
|
||||||
|
server_sys: sys,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,10 +166,23 @@ impl TestServer {
|
|||||||
/// Stop http server
|
/// Stop http server
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
if let Some(handle) = self.thread.take() {
|
if let Some(handle) = self.thread.take() {
|
||||||
self.sys.send(msgs::SystemExit(0));
|
self.server_sys.send(msgs::SystemExit(0));
|
||||||
let _ = handle.join();
|
let _ = handle.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute future on current core
|
||||||
|
pub fn execute<F, I, E>(&mut self, fut: F) -> Result<I, E>
|
||||||
|
where F: Future<Item=I, Error=E>
|
||||||
|
{
|
||||||
|
self.system.run_until_complete(fut)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to websocket server
|
||||||
|
pub fn ws(&mut self) -> Result<(WsClientReader, WsClientWriter), WsClientError> {
|
||||||
|
let url = self.url("/");
|
||||||
|
self.system.run_until_complete(WsClient::new(url).connect().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TestServer {
|
impl Drop for TestServer {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Http client request
|
//! Http client request
|
||||||
#![allow(unused_imports, dead_code)]
|
#![allow(unused_imports, dead_code)]
|
||||||
use std::{io, str};
|
use std::{fmt, io, str};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
@ -299,8 +299,8 @@ impl Future for WsHandshake {
|
|||||||
let match_key = if let Some(key) = resp.headers().get(
|
let match_key = if let Some(key) = resp.headers().get(
|
||||||
HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap())
|
HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap())
|
||||||
{
|
{
|
||||||
// ... field is constructed by concatenating /key/ ...
|
// field is constructed by concatenating /key/
|
||||||
// ... with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455)
|
// with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455)
|
||||||
const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
let mut sha1 = Sha1::new();
|
let mut sha1 = Sha1::new();
|
||||||
sha1.update(self.key.as_ref());
|
sha1.update(self.key.as_ref());
|
||||||
@ -336,6 +336,12 @@ pub struct WsClientReader {
|
|||||||
inner: Rc<UnsafeCell<Inner>>
|
inner: Rc<UnsafeCell<Inner>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for WsClientReader {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "WsClientReader()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WsClientReader {
|
impl WsClientReader {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn as_mut(&mut self) -> &mut Inner {
|
fn as_mut(&mut self) -> &mut Inner {
|
||||||
|
@ -74,7 +74,7 @@ const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION";
|
|||||||
|
|
||||||
|
|
||||||
/// `WebSocket` Message
|
/// `WebSocket` Message
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Text(String),
|
Text(String),
|
||||||
Binary(Binary),
|
Binary(Binary),
|
||||||
|
49
tests/test_ws.rs
Normal file
49
tests/test_ws.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate http;
|
||||||
|
extern crate bytes;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::Stream;
|
||||||
|
|
||||||
|
use actix_web::*;
|
||||||
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
struct Ws;
|
||||||
|
|
||||||
|
impl Actor for Ws {
|
||||||
|
type Context = ws::WebsocketContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<ws::Message> for Ws {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
|
match msg {
|
||||||
|
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||||
|
ws::Message::Text(text) => ctx.text(&text),
|
||||||
|
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple() {
|
||||||
|
let mut srv = test::TestServer::new(
|
||||||
|
|app| app.handler(|req| ws::start(req, Ws)));
|
||||||
|
let (reader, mut writer) = srv.ws().unwrap();
|
||||||
|
|
||||||
|
writer.text("text");
|
||||||
|
let (item, reader) = srv.execute(reader.into_future()).unwrap();
|
||||||
|
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
||||||
|
|
||||||
|
writer.binary(b"text".as_ref());
|
||||||
|
let (item, reader) = srv.execute(reader.into_future()).unwrap();
|
||||||
|
assert_eq!(item, Some(ws::Message::Binary(Bytes::from_static(b"text").into())));
|
||||||
|
|
||||||
|
writer.ping("ping");
|
||||||
|
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
||||||
|
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user