http_test_server/
lib.rs

1//! # HTTP Test Server
2//!
3//! Programatically create end-points that listen for connections and return pre-defined
4//! responses.
5//!
6//! - Allows multiple endpoints and simultaneous client connections
7//! - Streaming support
8//! - Helper functions to retrieve data such as request count, number of connected clients and
9//! requests metadata
10//! - Automatically allocates free port and close server after use
11//!
12//! # Examples:
13//!
14//! Accept POST requests:
15//! ```
16//! extern crate http_test_server;
17//!
18//! use http_test_server::{TestServer, Resource};
19//! use http_test_server::http::{Status, Method};
20//!
21//! let server = TestServer::new().unwrap();
22//! let resource = server.create_resource("/some-endpoint/new");
23//!
24//! resource
25//!     .status(Status::Created)
26//!     .method(Method::POST)
27//!     .header("Content-Type", "application/json")
28//!     .header("Cache-Control", "no-cache")
29//!     .body("{ \"message\": \"this is a message\" }");
30//!
31//! // request: POST /some-endpoint/new
32//!
33//! // HTTP/1.1 201 Created\r\n
34//! // Content-Type: application/json\r\n
35//! // Cache-Control: no-cache\r\n
36//! // \r\n
37//! // { "message": "this is a message" }
38//!
39//!
40//! ```
41//!
42//! Use path and query parameters:
43//! ```
44//! extern crate http_test_server;
45//!
46//! use http_test_server::{TestServer, Resource};
47//! use http_test_server::http::{Status, Method};
48//!
49//! let server = TestServer::new().unwrap();
50//! let resource = server.create_resource("/user/{userId}?filter=*");
51
52//! resource
53//!     .status(Status::OK)
54//!     .header("Content-Type", "application/json")
55//!     .header("Cache-Control", "no-cache")
56//!     .body(r#"{ "id": "{path.userId}", "filter": "{query.filter}" }"#);
57//!
58//! // request: GET /user/abc123?filter=all
59//!
60//! // HTTP/1.1 200 Ok\r\n
61//! // Content-Type: application/json\r\n
62//! // Cache-Control: no-cache\r\n
63//! // \r\n
64//! // { "id": "abc123", "filter": "all" }
65//!
66//!
67//! ```
68//!
69//! Expose a persistent stream:
70//! ```
71//! # extern crate http_test_server;
72//! # use http_test_server::{TestServer, Resource};
73//! # use http_test_server::http::{Status, Method};
74//! let server = TestServer::new().unwrap();
75//! let resource = server.create_resource("/sub");
76//!
77//! resource
78//!     .header("Content-Type", "text/event-stream")
79//!     .header("Cache-Control", "no-cache")
80//!     .stream()
81//!     .body(": initial data");
82//!
83//! // ...
84//!
85//! resource
86//!     .send("some data")
87//!     .send(" some extra data\n")
88//!     .send_line("some extra data with line break")
89//!     .close_open_connections();
90//!
91//! // request: GET /sub
92//!
93//! // HTTP/1.1 200 Ok\r\n
94//! // Content-Type: text/event-stream\r\n
95//! // Cache-Control: no-cache\r\n
96//! // \r\n
97//! // : initial data
98//! // some data some extra data\n
99//! // some extra data with line break\n
100//!
101//!
102//! ```
103//! Redirects:
104//! ```
105//! # extern crate http_test_server;
106//! # use http_test_server::{TestServer, Resource};
107//! # use http_test_server::http::{Status, Method};
108//! let server = TestServer::new().unwrap();
109//! let resource_redirect = server.create_resource("/original");
110//! let resource_target = server.create_resource("/new");
111//!
112//! resource_redirect
113//!     .status(Status::SeeOther)
114//!     .header("Location", "/new" );
115//!
116//! resource_target.body("Hi!");
117//!
118//! // request: GET /original
119//!
120//! // HTTP/1.1 303 See Other\r\n
121//! // Location: /new\r\n
122//! // \r\n
123//!
124//!
125//! ```
126//! Regex URI:
127//!
128//! ```
129//! # extern crate http_test_server;
130//! # use http_test_server::{TestServer, Resource};
131//! # use http_test_server::http::{Status, Method};
132//! let server = TestServer::new().unwrap();
133//! let resource = server.create_resource("/hello/[0-9]/[A-z]/.*");
134//!
135//! // request: GET /hello/8/b/doesntmatter-hehe
136//!
137//! // HTTP/1.1 200 Ok\r\n
138//! // \r\n
139//!
140//! ```
141//!
142//! *NOTE*: This is not intended to work as a full featured server. For this reason, many validations
143//! and behaviours are not implemented. e.g: A request with `Accept` header with not supported
144//! `Content-Type` won't trigger a `406 Not Acceptable`.
145//!
146//! As this crate was devised to be used in tests, smart behaviours could be confusing and misleading. Having said that, for the sake of convenience, some default behaviours were implemented:
147//!
148//! - Server returns `404 Not Found` when requested resource was not configured.
149//! - Server returns `405 Method Not Allowed` when trying to reach resource with different method from those configured.
150//! - When a resource is created it responds to `GET` with `200 Ok` by default.
151extern crate regex;
152
153pub mod resource;
154pub mod http;
155
156use std::thread;
157use std::net::TcpListener;
158use std::net::TcpStream;
159use std::io::prelude::*;
160use std::io::Error;
161use std::io::BufReader;
162use std::sync::Arc;
163use std::sync::Mutex;
164use std::sync::mpsc;
165use std::collections::HashMap;
166use http::Method;
167use http::Status;
168pub use resource::Resource;
169
170type ServerResources = Arc<Mutex<Vec<Resource>>>;
171type RequestsTX = Arc<Mutex<Option<mpsc::Sender<Request>>>>;
172
173/// Controls the listener life cycle and creates new resources
174pub struct TestServer {
175    port: u16,
176    resources: ServerResources,
177    requests_tx: RequestsTX
178}
179
180impl TestServer {
181    /// Creates a listener that is bounded to a free port in localhost.
182    /// Listener is closed when the value is dropped.
183    ///
184    /// Any request for non-defined resources will return 404.
185    ///
186    /// ```
187    ///# extern crate http_test_server;
188    ///# use http_test_server::{TestServer, Resource};
189    /// let server = TestServer::new().unwrap();
190    ///
191    /// ```
192    pub fn new() -> Result<TestServer, Error> {
193        TestServer::new_with_port(0)
194    }
195
196    /// Same behaviour as `new`, but tries to bound to given port instead of looking for a free one.
197    /// ```
198    ///# extern crate http_test_server;
199    ///# use http_test_server::{TestServer, Resource};
200    /// let server = TestServer::new_with_port(8080).unwrap();
201    ///
202    /// ```
203    pub fn new_with_port(port: u16) -> Result<TestServer, Error> {
204        let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
205        let port = listener.local_addr()?.port();
206        let resources: ServerResources = Arc::new(Mutex::new(vec!()));
207        let requests_tx = Arc::new(Mutex::new(None));
208
209        let res = Arc::clone(&resources);
210        let tx = Arc::clone(&requests_tx);
211
212        thread::spawn(move || {
213            for stream in listener.incoming() {
214                let stream = stream.unwrap();
215
216                let mut buffer = [0; 512];
217                stream.peek(&mut buffer).unwrap();
218
219                if buffer.starts_with(b"CLOSE") {
220                    break;
221                }
222
223                handle_connection(&stream, res.clone(), tx.clone());
224            }
225        });
226
227        Ok(TestServer{ port, resources, requests_tx })
228    }
229
230    /// Returns associated port number.
231    /// ```
232    ///# extern crate http_test_server;
233    ///# use http_test_server::{TestServer, Resource};
234    /// let server = TestServer::new().unwrap();
235    ///
236    /// assert!(server.port() > 0);
237    /// ```
238    pub fn port(&self) -> u16 {
239       self.port
240    }
241
242    /// Closes listener. Server stops receiving connections. Do nothing if listener is already closed.
243    ///
244    /// In most the cases this method is not required as the listener is automatically closed when
245    /// the value is dropped.
246    ///
247    /// ```
248    ///# extern crate http_test_server;
249    ///# use http_test_server::{TestServer, Resource};
250    /// let server = TestServer::new().unwrap();
251    ///
252    /// server.close();
253    /// ```
254    pub fn close(&self) {
255        if let Ok(mut stream) = TcpStream::connect(format!("127.0.0.1:{}", self.port)) {
256            stream.write_all(b"CLOSE").unwrap();
257            stream.flush().unwrap();
258        }
259    }
260
261    /// Creates a new resource. By default resources answer "200 Ok".
262    ///
263    /// Check [`Resource`] for all possible configurations.
264    ///
265    /// ```
266    ///# extern crate http_test_server;
267    ///# use http_test_server::{TestServer, Resource};
268    /// let server = TestServer::new().unwrap();
269    /// let resource = server.create_resource("/user/settings");
270    /// ```
271    /// [`Resource`]: struct.Resource.html
272    pub fn create_resource(&self, uri: &str) -> Resource {
273        let mut resources = self.resources.lock().unwrap();
274        let resource = Resource::new(uri);
275
276        resources.push(resource.clone());
277
278        resource
279    }
280
281    /// Retrieves information on new requests.
282    ///
283    /// ```no_run
284    ///# extern crate http_test_server;
285    ///# use http_test_server::{TestServer, Resource};
286    ///# use std::collections::HashMap;
287    /// let server = TestServer::new().unwrap();
288    ///
289    /// for request in server.requests().iter() {
290    ///     assert_eq!(request.url, "/endpoint");
291    ///     assert_eq!(request.method, "GET");
292    ///     assert_eq!(request.headers.get("Content-Type").unwrap(), "text");
293    /// }
294    /// ```
295    pub fn requests(&self) -> mpsc::Receiver<Request> {
296        let (tx, rx) = mpsc::channel();
297
298        *self.requests_tx.lock().unwrap() = Some(tx);
299
300        rx
301    }
302}
303
304impl Drop for TestServer {
305    fn drop(&mut self) {
306        self.close();
307    }
308}
309
310fn handle_connection(stream: &TcpStream, resources: ServerResources, requests_tx: RequestsTX) {
311    let stream = stream.try_clone().unwrap();
312
313    thread::spawn(move || {
314        let mut write_stream = stream.try_clone().unwrap();
315        let mut reader = BufReader::new(stream);
316
317        let (method, url) = parse_request_header(&mut reader);
318        let resource = find_resource(method.clone(), url.clone(), resources);
319
320        if let Some(delay) = resource.get_delay() {
321            thread::sleep(delay);
322        }
323
324        write_stream.write_all(resource.build_response(&url).as_bytes()).unwrap();
325        write_stream.flush().unwrap();
326
327        if let Some(ref tx) = *requests_tx.lock().unwrap() {
328            let mut headers = HashMap::new();
329
330            for line in reader.lines() {
331                let line = line.unwrap();
332
333                if line == "" {
334                    break
335                }
336
337                let (name, value) = parse_header(line);
338                headers.insert(name, value);
339            }
340
341            tx.send(Request { url, method, headers }).unwrap();
342        }
343
344        if resource.is_stream() {
345            let receiver = resource.stream_receiver();
346            for line in receiver.iter() {
347                write_stream.write_all(line.as_bytes()).unwrap();
348                write_stream.flush().unwrap();
349            }
350        }
351
352    });
353}
354
355fn parse_header(message: String) -> (String, String) {
356    let parts: Vec<&str> = message.splitn(2, ':').collect();
357    (String::from(parts[0]), String::from(parts[1].trim()))
358}
359
360fn parse_request_header(reader: &mut dyn BufRead) -> (String, String) {
361    let mut request_header = String::from("");
362    reader.read_line(&mut request_header).unwrap();
363
364    let request_header: Vec<&str> = request_header
365        .split_whitespace().collect();
366
367    (request_header[0].to_string(), request_header[1].to_string())
368}
369
370fn find_resource(method: String, url: String, resources: ServerResources) -> Resource {
371    let resources = resources.lock().unwrap();
372
373    match resources.iter().find(|r| r.matches_uri(&url) && r.get_method().equal(&method) ) {
374        Some(resource) => {
375            resource.increment_request_count();
376            resource.clone()
377        },
378        None => {
379            // resource not found, check whether to show 404 or MethodNotAllowed.
380            let resources_for_uri = resources.iter().filter(|r| r.matches_uri(&url));
381            if resources_for_uri.count() == 0 {
382                return Resource::new(&url).status(Status::NotFound).clone();
383            }
384            Resource::new(&url).status(Status::MethodNotAllowed).clone()
385        }
386    }
387}
388
389
390/// Request information
391///
392/// this contains basic information about a request received.
393#[derive(Debug, PartialEq)]
394pub struct Request {
395    /// Request URL
396    pub url: String,
397    /// HTTP method
398    pub method: String,
399    /// Request headers
400    pub headers: HashMap<String, String>
401}
402
403#[cfg(test)]
404mod tests {
405    use std::io::prelude::*;
406    use std::io::BufReader;
407    use std::io::ErrorKind;
408    use std::net::TcpStream;
409    use std::time::Duration;
410    use std::sync::mpsc;
411    use super::*;
412
413    fn make_request(port: u16, uri: &str) -> TcpStream {
414       request(port, uri, "GET")
415    }
416
417    fn make_post_request(port: u16, uri: &str) -> TcpStream {
418       request(port, uri, "POST")
419    }
420
421    fn request(port: u16, uri: &str, method: &str) -> TcpStream {
422        let host = format!("127.0.0.1:{}", port);
423        let mut stream = TcpStream::connect(host).unwrap();
424        let request = format!(
425            "{} {} HTTP/1.1\r\nContent-Type: text\r\n\r\n",
426            method,
427            uri
428        );
429
430        stream.write(request.as_bytes()).unwrap();
431        stream.flush().unwrap();
432
433        stream
434    }
435
436    #[test]
437    fn returns_404_when_requested_enexistent_resource() {
438        let server = TestServer::new().unwrap();
439        let stream = make_request(server.port(), "/something");
440
441        let mut reader = BufReader::new(stream);
442        let mut line = String::new();
443        reader.read_line(&mut line).unwrap();
444
445        assert_eq!(line, "HTTP/1.1 404 Not Found\r\n");
446    }
447
448    #[test]
449    fn server_should_use_random_port() {
450        let server = TestServer::new().unwrap();
451        let server_2 = TestServer::new().unwrap();
452
453        assert_ne!(server.port(), server_2.port());
454    }
455
456    #[test]
457    fn should_close_connection() {
458        let server = TestServer::new().unwrap();
459        server.close();
460
461        thread::sleep(Duration::from_millis(200));
462
463        let host = format!("127.0.0.1:{}", server.port());
464        let stream = TcpStream::connect(host);
465
466        assert!(stream.is_err());
467        if let Err(e) = stream {
468            assert_eq!(e.kind(), ErrorKind::ConnectionRefused);
469        }
470    }
471
472    #[test]
473    fn should_handle_multiple_resources() {
474        let server = TestServer::new().unwrap();
475        let resource = server.create_resource("/this");
476        resource.status(Status::OK).body("<this body>");
477        thread::sleep(Duration::from_millis(200));
478        let resource2 = server.create_resource("/that");
479        resource2.status(Status::OK).body("<that body>");
480
481        assert_eq!(resource.request_count(), 0);
482        assert_eq!(resource2.request_count(), 0);
483
484        let _ = make_request(server.port(), "/this");
485
486        thread::sleep(Duration::from_millis(200));
487        let _ = make_request(server.port(), "/that");
488        thread::sleep(Duration::from_millis(200));
489
490        assert_eq!(resource.request_count(), 1);
491        assert_eq!(resource2.request_count(), 1);
492    }
493
494    #[test]
495    fn should_create_resource() {
496        let server = TestServer::new().unwrap();
497        server.create_resource("/something");
498
499        let stream = make_request(server.port(), "/something");
500
501        let mut reader = BufReader::new(stream);
502        let mut line = String::new();
503        reader.read_line(&mut line).unwrap();
504
505        assert_eq!(line, "HTTP/1.1 200 Ok\r\n");
506    }
507
508    #[test]
509    fn should_return_configured_status_for_resource_resource() {
510        let server = TestServer::new().unwrap();
511        let resource = server.create_resource("/something-else");
512
513        resource.status(Status::OK);
514
515        let stream = make_request(server.port(), "/something-else");
516
517        let mut reader = BufReader::new(stream);
518        let mut line = String::new();
519        reader.read_line(&mut line).unwrap();
520
521        assert_eq!(line, "HTTP/1.1 200 Ok\r\n");
522    }
523
524    #[test]
525    fn should_return_resource_body() {
526        let server = TestServer::new().unwrap();
527        let resource = server.create_resource("/something-else");
528
529        resource.status(Status::OK).body("<some body>");
530
531        let stream = make_request(server.port(), "/something-else");
532
533        let mut reader = BufReader::new(stream);
534        let mut line = String::new();
535        reader.read_to_string(&mut line).unwrap();
536
537        assert_eq!(line, "HTTP/1.1 200 Ok\r\n\r\n<some body>");
538    }
539
540    #[test]
541    fn should_return_resource_body_with_params() {
542        let server = TestServer::new().unwrap();
543        let resource = server.create_resource("/user/{userId}/{thing_id}/{yepyep}");
544
545        resource.status(Status::OK).body("User: {path.userId} Thing: {path.thing_id} Sth: {path.yepyep}");
546
547        let stream = make_request(server.port(), "/user/123/abc/Hello!");
548
549        let mut reader = BufReader::new(stream);
550        let mut line = String::new();
551        reader.read_to_string(&mut line).unwrap();
552
553        assert_eq!(line, "HTTP/1.1 200 Ok\r\n\r\nUser: 123 Thing: abc Sth: Hello!");
554    }
555
556    #[test]
557    fn should_work_with_regex_uri() {
558        let server = TestServer::new().unwrap();
559        let resource = server.create_resource("/hello/[0-9]/[A-z]/.*");
560
561        resource.method(Method::POST).status(Status::OK).body("<some body>");
562
563        let stream = make_post_request(server.port(), "/hello/8/b/doesntmatter-hehe");
564
565        let mut reader = BufReader::new(stream);
566        let mut line = String::new();
567        reader.read_to_string(&mut line).unwrap();
568
569        assert_eq!(line, "HTTP/1.1 200 Ok\r\n\r\n<some body>");
570    }
571
572
573    #[test]
574    fn should_listen_to_defined_method() {
575        let server = TestServer::new().unwrap();
576        let resource = server.create_resource("/something-else");
577
578        resource.method(Method::POST).status(Status::OK).body("<some body>");
579
580        let stream = make_post_request(server.port(), "/something-else");
581
582        let mut reader = BufReader::new(stream);
583        let mut line = String::new();
584        reader.read_to_string(&mut line).unwrap();
585
586        assert_eq!(line, "HTTP/1.1 200 Ok\r\n\r\n<some body>");
587    }
588
589    #[test]
590    fn should_allow_multiple_methods_for_same_uri() {
591        let server = TestServer::new().unwrap();
592        let resource = server.create_resource("/something-else");
593        let resource2 = server.create_resource("/something-else");
594
595        resource.method(Method::GET).status(Status::OK).body("<some body GET>");
596        resource2.method(Method::POST).status(Status::OK).body("<some body POST>");
597
598        let stream_get = make_request(server.port(), "/something-else");
599        let stream_post = make_post_request(server.port(), "/something-else");
600
601        let mut reader = BufReader::new(stream_get);
602        let mut line = String::new();
603        reader.read_to_string(&mut line).unwrap();
604
605        let mut reader = BufReader::new(stream_post);
606        let mut line2 = String::new();
607        reader.read_to_string(&mut line2).unwrap();
608
609        assert_eq!(line, "HTTP/1.1 200 Ok\r\n\r\n<some body GET>");
610        assert_eq!(line2, "HTTP/1.1 200 Ok\r\n\r\n<some body POST>");
611    }
612
613    #[test]
614    fn should_return_405_when_method_not_defined() {
615        let server = TestServer::new().unwrap();
616        let resource = server.create_resource("/something-else");
617
618        resource.method(Method::POST).status(Status::OK).body("<some body>");
619
620        let stream = make_request(server.port(), "/something-else");
621
622        let mut reader = BufReader::new(stream);
623        let mut line = String::new();
624        reader.read_to_string(&mut line).unwrap();
625
626        assert_eq!(line, "HTTP/1.1 405 Method Not Allowed\r\n\r\n");
627    }
628
629    #[test]
630    fn should_increment_request_count() {
631        let server = TestServer::new().unwrap();
632        let resource = server.create_resource("/something-else");
633
634        resource.status(Status::OK).body("<some body>");
635
636        assert_eq!(resource.request_count(), 0);
637
638        let _ = make_request(server.port(), "/something-else");
639        let _ = make_request(server.port(), "/something-else");
640
641        thread::sleep(Duration::from_millis(200));
642
643        assert_eq!(resource.request_count(), 2);
644
645    }
646
647    #[test]
648    fn should_expose_stream() {
649        let server = TestServer::new().unwrap();
650        let resource = server.create_resource("/something-else");
651        resource.stream();
652
653        let (tx, rx) = mpsc::channel();
654
655        let port = server.port();
656
657        thread::spawn(move || {
658            let stream = make_request(port, "/something-else");
659            let reader = BufReader::new(stream);
660
661            for line in reader.lines() {
662                let line = line.unwrap();
663                tx.send(line).unwrap();
664            }
665        });
666
667        thread::sleep(Duration::from_millis(200));
668
669        resource.send_line("hello!");
670        resource.send("it's me");
671        resource.send("\n");
672
673        rx.recv().unwrap();
674        rx.recv().unwrap();
675        assert_eq!(rx.recv().unwrap(), "hello!");
676        assert_eq!(rx.recv().unwrap(), "it's me");
677    }
678
679    #[test]
680    fn should_close_client_connections() {
681        let server = TestServer::new().unwrap();
682        let resource = server.create_resource("/something-else");
683        let (tx, rx) = mpsc::channel();
684        let port = server.port();
685
686        resource.stream();
687
688        thread::spawn(move || {
689            let stream = make_request(port, "/something-else");
690            let reader = BufReader::new(stream);
691
692            for _line in reader.lines() {}
693
694            tx.send("connection closed").unwrap();
695            thread::sleep(Duration::from_millis(200));
696        });
697
698        thread::sleep(Duration::from_millis(100));
699        resource.close_open_connections();
700
701        assert_eq!(rx.recv().unwrap(), "connection closed");
702    }
703
704    #[test]
705    fn should_return_requests_metadata() {
706        let server = TestServer::new().unwrap();
707        let (tx, rx) = mpsc::channel();
708        let port = server.port();
709
710        thread::spawn(move || {
711            for req in server.requests().iter() {
712                tx.send(req).unwrap();
713                thread::sleep(Duration::from_millis(400));
714                break;
715            }
716        });
717
718        thread::sleep(Duration::from_millis(100));
719        let _req = make_request(port, "/something-else");
720
721        let mut request_headers = HashMap::new();
722        request_headers.insert(String::from("Content-Type"), String::from("text"));
723
724        let expected_request = Request {
725            url: String::from("/something-else"),
726            method: String::from("GET"),
727            headers: request_headers
728        };
729
730        assert_eq!(rx.recv().unwrap(), expected_request);
731    }
732
733    #[test]
734    fn should_delay_response() {
735        let server = TestServer::new().unwrap();
736        let resource = server.create_resource("/something-else");
737        resource.delay(Duration::from_millis(300));
738
739        let (tx, rx) = mpsc::channel();
740
741        let port = server.port();
742
743        thread::spawn(move || {
744            let stream = make_request(port, "/something-else");
745            let reader = BufReader::new(stream);
746
747            for line in reader.lines() {
748                let line = line.unwrap();
749                tx.send(line).unwrap();
750            }
751        });
752
753        thread::sleep(Duration::from_millis(200));
754
755        assert!(rx.try_recv().is_err());
756        thread::sleep(Duration::from_millis(200));
757        assert_eq!(rx.try_recv().unwrap(), "HTTP/1.1 200 Ok");
758    }
759
760    #[test]
761    fn server_should_close_connection_when_dropped() {
762        let port;
763        {
764            let server = TestServer::new().unwrap();
765            port = server.port();
766            thread::sleep(Duration::from_millis(200));
767        }
768
769        let host = format!("localhost:{}", port);
770        let stream = TcpStream::connect(host);
771
772        if let Err(e) = stream {
773            assert_eq!(e.kind(), ErrorKind::ConnectionRefused);
774        } else {
775            panic!("connection should be closed");
776        }
777    }
778}