From 0a76efdc64d6461656241e15f0672f015b88e21f Mon Sep 17 00:00:00 2001 From: landrigun Date: Thu, 15 Sep 2022 18:10:10 +0100 Subject: [PATCH 1/4] improv: #5 use tokio crate to handle incoming conn async --- Cargo.lock | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 49 ++++++-------- 3 files changed, 210 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9df2a7a..9eb98ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "blocking" version = "1.2.0" @@ -155,6 +161,12 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + [[package]] name = "cache-padded" version = "1.2.0" @@ -265,6 +277,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -310,6 +331,16 @@ version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +[[package]] +name = "lock_api" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -326,6 +357,28 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.14.0" @@ -338,6 +391,29 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -382,6 +458,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.6.0" @@ -399,6 +484,21 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "simple-auth" version = "0.1.0" @@ -407,6 +507,7 @@ dependencies = [ "json", "lazy_static", "regex", + "tokio", ] [[package]] @@ -418,6 +519,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + [[package]] name = "socket2" version = "0.4.7" @@ -439,6 +546,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tokio" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.3" @@ -467,6 +606,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.82" @@ -573,3 +718,46 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml index 20e5bef..841e835 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" json = "0.12.4" lazy_static = "1.4.0" regex = "1" +tokio = { version = "1.21.1", features = ["full"] } [dependencies.async-std] version = "1.6" diff --git a/src/main.rs b/src/main.rs index fb4b02a..f8dd27c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,45 +1,38 @@ mod handlers; use std::io::prelude::*; -use std::net::TcpListener; -use std::net::TcpStream; + +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream}, +}; use handlers::handle_request; -// TODO: must be set in a conf file const SERVER_URL: &str = "127.0.0.1:9000"; -// switch to an asynchronous main function -#[async_std::main] +#[tokio::main] async fn main() { - let listener = TcpListener::bind(SERVER_URL).unwrap(); - println!("server is listening at {}", SERVER_URL); - for stream in listener.incoming() { - let stream = stream.unwrap(); + let listener = TcpListener::bind(SERVER_URL).await.unwrap(); + + loop { + let (stream, _) = listener.accept().await.unwrap(); handle_connection(stream).await; } } async fn handle_connection(mut stream: TcpStream) { - let mut buffer = [0; 1024]; - stream.read(&mut buffer).unwrap(); + let mut buffer: [u8; 1024] = [0; 1024]; + let n = stream.read(&mut buffer).await.unwrap(); - // transform buffer bytes array into `String` - let buffer_string = String::from_utf8_lossy(&buffer); - let request = handle_request(&buffer_string); + println!("buffer : {:?}", std::str::from_utf8(&buffer[0..n])); - // TODO: `Response` struct must be implemented - let status_line = if request.start_line.target == "/".to_string() { - "HTTP/1.1 200 OK\r\n" - } else { - "HTTP/1.1 404 NOT FOUND\r\n" - }; - - let content_type = "Content-Type: application/json\r\n\r\n"; - let json = r#"{"access_token":"AAAAAAAAAAAA.BBBBBBBBBB.CCCCCCCCCC","refresh_token": "DDDDDDDDDDD.EEEEEEEEEEE.FFFFF"}"#; - let response = format!("{status_line}{content_type}{json}\n"); - - println!("return status code : {}", status_line); - stream.write(response.as_bytes()).unwrap(); - stream.flush().unwrap(); + let contents = "{\"balance\": 0.00}"; + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", + contents.len(), + contents + ); + stream.write(response.as_bytes()).await.unwrap(); + stream.flush().await.unwrap(); } From 617c0847823af50d43260c3d3186a712dcfe29af Mon Sep 17 00:00:00 2001 From: landrigun Date: Mon, 19 Sep 2022 10:51:30 +0100 Subject: [PATCH 2/4] improv: #5 handle the request and return a response (useful for tests) --- src/handlers/request.rs | 4 ++-- src/main.rs | 23 ++++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/handlers/request.rs b/src/handlers/request.rs index b6bcb65..323ba07 100644 --- a/src/handlers/request.rs +++ b/src/handlers/request.rs @@ -122,7 +122,7 @@ impl HTTPStartLine { HTTP_VERSION_REGEX.is_match(version) } - fn is_valid(&self) -> bool { + pub fn is_valid(&self) -> bool { return self.method != "" && self.target != ""; } } @@ -234,7 +234,7 @@ impl HTTPRequest { } } - fn is_valid(&self) -> bool { + pub fn is_valid(&self) -> bool { return self.start_line.is_valid(); } } diff --git a/src/main.rs b/src/main.rs index f8dd27c..baa6e5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,14 +25,23 @@ async fn handle_connection(mut stream: TcpStream) { let mut buffer: [u8; 1024] = [0; 1024]; let n = stream.read(&mut buffer).await.unwrap(); - println!("buffer : {:?}", std::str::from_utf8(&buffer[0..n])); + let request_string = std::str::from_utf8(&buffer[0..n]).unwrap(); + let request = handle_request(request_string); - let contents = "{\"balance\": 0.00}"; - let response = format!( - "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", - contents.len(), - contents - ); + if request.is_valid() { + let contents = "{\"status\": \"ok\"}"; + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", + contents.len(), + contents + ); + + stream.write(response.as_bytes()).await.unwrap(); + stream.flush().await.unwrap(); + return; + } + + let response = "HTTP/1.1 400 OK\r\n\r\n".to_string(); stream.write(response.as_bytes()).await.unwrap(); stream.flush().await.unwrap(); } From 65e188bcf83b080e7a0cbba99d9d350e89d62e37 Mon Sep 17 00:00:00 2001 From: landrigun Date: Mon, 19 Sep 2022 14:10:28 +0100 Subject: [PATCH 3/4] improv: #5 accept http 1.0 request version --- src/handlers/request.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/handlers/request.rs b/src/handlers/request.rs index 323ba07..77bc465 100644 --- a/src/handlers/request.rs +++ b/src/handlers/request.rs @@ -18,12 +18,13 @@ const HTTP_METHODS: [&'static str; 1] = ["POST"]; const HTTP_TARGETS: [&'static str; 3] = ["/validate/", "/get/", "/refresh/"]; lazy_static! { - static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|2)$").unwrap(); + static ref HTTP_VERSION_REGEX: Regex = Regex::new("^HTTP/(1.1|1.0|2)$").unwrap(); } #[derive(Debug)] pub enum HTTPVersion { - Http1, + Http1_0, + Http1_1, Http2, Unknown, } @@ -31,7 +32,8 @@ pub enum HTTPVersion { impl Into for HTTPVersion { fn into(self) -> String { match self { - Self::Http1 => "HTTP/1.1".to_string(), + Self::Http1_0 => "HTTP/1.0".to_string(), + Self::Http1_1 => "HTTP/1.1".to_string(), Self::Http2 => "HTTP/2".to_string(), Self::Unknown => "UNKNOWN".to_string(), } @@ -42,7 +44,8 @@ impl Into for HTTPVersion { impl From<&String> for HTTPVersion { fn from(http_version: &String) -> Self { match http_version.as_str() { - "HTTP/1.1" => Self::Http1, + "HTTP/1.0" => Self::Http1_0, + "HTTP/1.1" => Self::Http1_1, "HTTP/2" => Self::Http2, _ => Self::Unknown, } @@ -85,7 +88,6 @@ impl HTTPStartLine { if !Self::check_target(&target) { return Err("target validation failed, unvalid target"); } - if !Self::check_version(&version) { return Err("http version validation failed, unknown version"); } @@ -272,7 +274,7 @@ fn test_handle_request() { is_valid: bool, } - let test_cases: [(String, Expect); 10] = [ + let test_cases: [(String, Expect); 11] = [ ( "POST /get/ HTTP/1.1\r\n\r\n".to_string(), Expect { @@ -289,6 +291,14 @@ fn test_handle_request() { is_valid: true, }, ), + ( + "POST /validate/ HTTP/1.0\r\n\r\n".to_string(), + Expect { + start_line: "POST /validate/ HTTP/1.0".to_string(), + body: None, + is_valid: true, + }, + ), ( "GET / HTTP/1.1\r\n\r\n".to_string(), Expect { From 5321c06d24315dd11cabaa7d2bcaa1f42e49e3ca Mon Sep 17 00:00:00 2001 From: landrigun Date: Mon, 19 Sep 2022 14:10:03 +0000 Subject: [PATCH 4/4] improv: #5 add e2e tests --- .gitignore | 3 +++ tests/bash/curling.bash | 36 +++++++++++++++++++++++++++++++++++ tests/python/test_requests.py | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100755 tests/bash/curling.bash create mode 100644 tests/python/test_requests.py diff --git a/.gitignore b/.gitignore index 39ac593..ddf9e12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target simple-auth *.swp + +tests/python/__pycache__ +tests/bash/response.txt diff --git a/tests/bash/curling.bash b/tests/bash/curling.bash new file mode 100755 index 0000000..3e7e1db --- /dev/null +++ b/tests/bash/curling.bash @@ -0,0 +1,36 @@ +#!/bin/bash + +####################################### +# +# A simple curl test on a deployed app +# +####################################### + +URL="https://dev.thegux.fr" + +for i in {0..10} +do + http_response=$(curl -s -o response.txt -w "%{http_code}" ${URL}/get/ -d '{"username":"toto", "password":"tutu"}') + if [ $http_response != "200" ] + then + echo "bad http status code : ${http_response}, expect 200" + exit 1 + fi + + if [ $(cat response.txt | jq -r '.[]') != "ok" ] + then + echo "bad data returned, expect : ok" + exit 1 + fi +done + + +for i in {0..10} +do + http_response=$(curl -s -o response.txt -w "%{http_code}" ${URL}/ge/ -d '{"username":"toto", "password":"tutu"}') + if [ $http_response != "400" ] + then + echo "bad http status code : ${http_response}, expect 400" + exit 1 + fi +done diff --git a/tests/python/test_requests.py b/tests/python/test_requests.py new file mode 100644 index 0000000..66534c5 --- /dev/null +++ b/tests/python/test_requests.py @@ -0,0 +1,34 @@ +import requests + +URL = "https://dev.thegux.fr" + + +def test_get_target(): + resp = requests.post(URL + "/get/", json={"username": "toto", "password": "tata"}) + assert resp.status_code == 200, "bad status code returned" + assert resp.json() is not None, "response data can't be empty" + assert resp.json()["status"] == "ok", "bad status returned" + + +def test_validate_target(): + resp = requests.post( + URL + "/validate/", json={"username": "toto", "password": "tata"} + ) + assert resp.status_code == 200, "bad status code returned" + assert resp.json() is not None, "response data can't be empty" + assert resp.json()["status"] == "ok", "bad status returned" + + +def test_refresh_target(): + resp = requests.post( + URL + "/refresh/", json={"username": "toto", "password": "tata"} + ) + assert resp.status_code == 200, "bad status code returned" + assert resp.json() is not None, "response data can't be empty" + assert resp.json()["status"] == "ok", "bad status returned" + + +def test_bad_target(): + resp = requests.post(URL + "/token/", json={"username": "toto", "password": "tata"}) + assert resp.status_code == 400, "bad status code returned" + assert resp.text == "", "response data must be empty"