rastapi/lib.rs
1#![allow(non_snake_case)]
2//! # RastAPI
3//!
4//! RastAPI is a easy-to-use Rust library for creating low-boilerplate, efficient RESTfull APIs, inspired by FastAPI (Python).
5//! You define route handler (functions) and map them with their corresponding url and you have a functioning route.
6//! RastAPI follows a **Non Blocking IO** structure for efficient and conccurent handling of requests.
7//! We achieve Non blocking IO by following a **Threadpool** architecture. We can set the number of threads in the pool before
8//! starting the application.
9//! RastAPI also has a very efficient LFU-LRU based file caching system.It's a multi threaded cache system, so we used efficient lock
10//! stripping techniques to reduce lock contentions.
11//!
12//! ## Features
13//! - Minimal boilerplate for defining routes and handlers.
14//! - High performance leveraging Rust's coccurency and safety.
15//! - Very efficient file uploads and downloads.
16//!
17//! ## Example
18//! ### Text or Json Response
19//!
20//! ```no_run
21//! use rastapi::RastAPI;
22//! use rastapi::Request::HttpRequest;
23//! use rastapi::Response::{HttpResponse,create_response};
24//! use rastapi::utils::ContentType;
25//! use std::collections::HashMap;
26//!
27//! // Define a Function/Route handler.
28//! // With this exact fucnction signature.
29//! // i.e. fn function_name(request_obj:&HttpRequest,path_params:HashMap<String,String>)->HttpResponse
30//! // request_obj is parameter where all the details of the incoming request is stored.
31//! // path_params is a parameter where url path params are stored. Not confuse it with url query parameters.
32//! // query params are stored in request_obj.params.
33//!
34//! fn json(request_obj:&HttpRequest,path_params:HashMap<String,String>)->HttpResponse{
35//!
36//! let json_content=r#"{
37//! "name" : "Rony",
38//! "batch" : 2024,
39//! "sem" : 3,
40//! "subjects" : ["OS","Networking","Algorithms"],
41//! "grade" : {
42//! "OS" : "C",
43//! "Cryptography" : "C",
44//! "Algorithms" : "C"
45//! }
46//! }"#;
47//!
48//! let resp=create_response(&json_content, 200,ContentType::JSON,true).unwrap(); //create_response(string_content,HTTP_CODE,
49//! return resp; // ContentType,keep_alive_flag)
50//! }
51//! fn main(){
52//! let mut app=RastAPI::new();
53//! // Set number of workers. It indicates total number of simultaneous requests it can serve.
54//! app.set_total_workers(5);
55//! // Map the function/route handler with corresponding url route.
56//! app.register_route("/json",vec!["GET"],json);
57//! app.run("0.0.0.0",5000);
58//! }
59//!```
60//! ### File response
61//!
62//! One of the most powerfull features of rastapi is It's very efficient file uploads downloads. If a file is Not found on system
63//! then the server automatically sends 404 Not Found response.
64//!
65//! ```no_run
66//! use rastapi::RastAPI;
67//! use rastapi::Request::HttpRequest;
68//! use rastapi::Response::{HttpResponse,send_file};
69//! use rastapi::utils::FileType;
70//! use std::collections::HashMap;
71//!
72//! fn file(req:&HttpRequest,path_params:HashMap<String,String>)->HttpResponse{
73//! let mut resp=send_file("file_path/file_name.ext",Some("file_name.ext".to_string()),FileType::MP4,200,true).unwrap(); // send_file0(file_path,file_name
74//! resp.add_header("Header-Name","Header-Value"); // file_type,HTTP_CODE,keep_alive_flag)
75//! return resp;
76//! }
77//! fn main(){
78//! let mut app=RastAPI::new();
79//! app.set_total_workers(5);
80//! app.register_route("/file",vec!["GET"],file);
81//! app.run("127.0.0.1",5000);
82//! }
83//! ```
84//! ### Load local enviornment variables and access them from std::env module.
85//! ```no_run
86//! use rastapi::RastAPI;
87//! use rastapi::Request::HttpRequest;
88//! use rastapi::Response::{HttpResponse,send_file};
89//! use rastapi::utils::FileType;
90//! use rastapi::utils::load_env;
91//! use std::collections::HashMap;
92//! use std::env;
93//! fn file(req:&HttpRequest,path_params:HashMap<String,String>)->HttpResponse{
94//! let mut resp=send_file("file_path/file_name.ext",Some("file_name.ext".to_string()),FileType::MP4,200,true).unwrap(); // send_file0(file_path,file_name
95//! resp.add_header("Header-Name","Header-Value"); // file_type,HTTP_CODE,keep_alive_flag)
96//! return resp;
97//! }
98//! fn main(){
99//! load_env::load_env(); // Loads all local enviornment variables in .env file.
100//! let mut app=RastAPI::new();
101//! app.set_total_workers(5);
102//! app.register_route("/file",vec!["GET"],file);
103//! let port:u16=env::var("PORT").unwrap().parse().unwrap();
104//! app.run(env::var("HOST").unwrap().as_str(),port);
105//! }
106//! ```
107//! ## Cache
108//! RastAPI uses a slightly tweaked version of a standard LFU-LRU (LFU for eviction and LRU when there is a tie between frequencies of two entity) cache. It only caches files for now.
109//! It's a multi threaded cache So for syncronization we use locks (Mutex). We cann't use RwLock as for LRU-LFU cache as every read qyery is a write query.
110//! So there is a issue of Lock contention. To mitigate lock contention we devided the cache into multiple parts (CacheStore) and lock them individually, thus reducing load on
111//! single cache store.
112//! We can set total cache size and total number of cache stores.
113//!
114//! ```no_run
115//! use rastapi::RastAPI;
116//! fn main(){
117//! let mut app=RastAPI::new();
118//! #[cfg(feature="cachung")]
119//! app.set_cache_config(500,10); // Total cache size is set to 500 MB and
120//! // total number of cache stores set to 10
121//! }
122//! ```
123//!
124//! ## Persistent Connections
125//! RastAPI supports persistent connections. If you want a route to be persistent then set the `keep_alive` flag in `create_response` or `send_file` as `true`.
126//! You can tweak the keep alive behabiour like maximum number of keep alive requests i.e. total number of request response cycle on a signle connection can be set,
127//! default value is 10. You can also set the keep alive timeout, i.e. for how much time we wait for next request after sending a response on persistent connection.
128//! If we don't recieve new request for this duration then we drop the connection. Default value is 5 seconds.
129//!
130//! ```no_run
131//! use rastapi::RastAPI;
132//! fn main(){
133//! let mut app=RastAPI::new();
134//! app.set_keep_alive_time_out(3);
135//! app.set_maximum_keep_alive_requests(10);
136//! app.run("127.0.0.1",5000);
137//! }
138//! ```
139//! ## Stop the app
140//! To stop the app gracefully you need to send SIGINT (CTRL + C) or SIGTERM. SIGTSTP(CTRL + Z) is ignored.
141mod App;
142mod File;
143pub mod Request;
144pub mod Response;
145mod cache;
146mod macros;
147pub mod utils;
148use cache::Cache;
149use libc::{
150 sigaction as sigaction_syscall, sigaction as sigaction_struct, sigaddset, SA_RESTART, SIGINT,
151 SIGTERM, SIGTSTP,getsockopt,SOL_SOCKET,SO_SNDBUF
152};
153use std::{
154 ffi::c_void, io, net::{IpAddr, TcpListener, TcpStream, UdpSocket}, os::fd::AsRawFd, path::PathBuf, process, ptr::NonNull, sync::Arc, time::Duration
155};
156use utils::{threadpool::ThreadPool, Method};
157use App::{client::Client, AppEnv, Route, RouteFunction, URLRouter};
158
159// Signal handling ctrl+c & ctrl + z
160static mut SIG_FLAG: bool = false;
161static mut PORT: u16 = 0;
162extern "C" fn handle_shutdown_signal(_: i32) {
163 unsafe {
164 SIG_FLAG = true;
165 let addr = format!("127.0.0.1:{}", PORT);
166 let _dummy_stream = match TcpStream::connect(&addr) {
167 Ok(c) => c,
168 Err(_) => {
169 process::exit(1);
170 }
171 };
172 }
173}
174extern "C" fn handle_sigtstp(_: i32) {
175 log_info!("CTRL + Z is ignored. If want to terminate the server press CTRL + C");
176}
177fn get_send_buffer_len(sock_fd:i32)->usize{
178 let mut buf_sz=1<<14;
179 unsafe {
180 let n_ptr=Box::into_raw(Box::new(0_usize)) as * mut c_void;
181 let m_ptr=Box::into_raw(Box::new(std::mem::size_of::<usize>() as u32));
182 let res=getsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF, n_ptr, m_ptr);
183 if res==0{
184 buf_sz=*(n_ptr as * const usize);
185 }
186 let _=Box::from_raw(n_ptr as * mut usize);
187 let _=Box::from_raw(m_ptr);
188 }
189 if buf_sz>(1<<16){
190 buf_sz=1<<14;
191 }
192 buf_sz as usize
193}
194/// This is the main API struct. Before doing anything we need to initialize it.
195/// ## Example
196/// ```no_run
197/// use rastapi::RastAPI;
198/// use rastapi::Request::HttpRequest;
199/// use rastapi::Response::{HttpResponse,send_file};
200/// use rastapi::utils::FileType;
201/// use rastapi::utils::load_env;
202/// use std::collections::HashMap;
203///
204/// fn route_handler(req:&HttpRequest,path_params:HashMap<String,String>)->HttpResponse{
205/// let mut resp=send_file("file_path/file_name.ext",Some("file_name.ext".to_string()),FileType::MP4,200,true).unwrap(); // send_files(file_path,file_name
206/// resp.add_header("Header-Name","Header-Value"); // file_type,HTTP_CODE,keep_alive_flag)
207/// return resp;
208/// }
209///
210/// fn main(){
211/// let mut app=RastAPI::new();
212/// app.set_total_workers(5);
213/// app.set_maximum_payload_size(100);
214/// app.set_read_time_out(5);
215/// app.set_write_time_out(5);
216/// app.set_keep_alive_time_out(5);
217/// app.set_maximum_keep_alive_requests(10);
218/// app.register_route("/route",vec!["GET"],route_handler);
219/// app.run("localhost",5000);
220/// }
221///
222///
223/// ```
224pub struct RastAPI {
225 /// Routes of our app.
226 pub(crate) routes: NonNull<URLRouter>,
227 /// Total number of workers. i.e. total threads in our threadpool. It's system threads NOT green threads. Default 10 workers.
228 pub total_workers: usize,
229 /// Maximum size of payload to accept on each request. If it exceeds for any request our API will automatically send *413 Payload too larrge*. Default 512 MB.
230 pub payload_maximum_size_in_MB: usize,
231 /// If no packet recieved for this duration then we stop reading from stream and drop the connection. Default 5 secs.
232 pub read_time_out: Duration,
233 /// If we couldn't write on the stream for this duration we stop sending request and drop the connection. Default 5 secs.
234 pub write_time_out: Duration,
235 /// Maximum amount of time we keep a persistent connection alive after sending a response. Default 5 secs.
236 pub keep_alive_time_out: Duration,
237 /// Maximum number of request-response cycle on a persistent connection. Default 10 request-response cycle.
238 pub keep_alive_max_count: u8,
239 /// A LFU-LRU cache for file caching. Default size 400 MB, devided among 10 Cache Stores.
240 pub(crate) cache: NonNull<Cache<PathBuf>>,
241 /// Name of the directory where incoming files are stored. i.e. files coming in request bodies. Default name is `input_files`.
242 pub file_upload_directory_name:String
243}
244impl RastAPI {
245 /// Initializes a RastAPI struct with default configurations.
246 pub fn new() -> Self {
247 let total_workers: usize = 10;
248 let default_read_time_out = Duration::from_secs(5);
249 let deafault_write_time_out = Duration::from_secs(5);
250 let deafault_keep_alive_time_out = Duration::from_secs(5);
251 Self {
252 routes: unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(URLRouter::new()))) },
253 total_workers,
254 payload_maximum_size_in_MB: 512 as usize,
255 read_time_out: default_read_time_out,
256 write_time_out: deafault_write_time_out,
257 keep_alive_time_out: deafault_keep_alive_time_out,
258 keep_alive_max_count: 10,
259 cache: unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(Cache::new(10, 40)))) },
260 file_upload_directory_name:String::from("input_files")
261 }
262 }
263
264 /// Sets the cache configuration.
265 /// ## Parameters
266 /// - `total_cache_size` : Total size of the cache.
267 /// - `total_no_of_cache_stores` : Total number of cache stores. each cache store will contain part of the cache.
268 /// Maximum size of a cache store will be `toatal_cache_size`/`total_no_of_cache_stores`.
269 ///
270 /// Make sure `total_cache_size` is divisible by `total_no_of_cache_stores`.
271 ///
272 #[cfg(feature="caching")]
273 pub fn set_cache_config(&mut self, total_cache_size: usize, total_no_of_cache_stores: usize) {
274 assert!(
275 total_cache_size % total_no_of_cache_stores == 0,
276 "TOTAL CACHE SIZE MUST BE DIVISIBLE BY TOTAL NUMBER OF CACHE STORES"
277 );
278 let _ = unsafe { Box::from_raw(self.cache.as_ptr()) };
279 let eache_cache_store_size = total_cache_size / total_no_of_cache_stores;
280 self.cache = unsafe {
281 NonNull::new_unchecked(Box::into_raw(Box::new(Cache::new(
282 total_no_of_cache_stores,
283 eache_cache_store_size,
284 ))))
285 };
286 }
287
288 /// Register a route i.e. Map a url with it's corresponding handler.
289 /// ## Parameters
290 /// - `url` : The url we want to map.
291 /// - `methods`: Vector of HTTP methods allowed on this route.
292 /// -`func` : Name of the route handler.
293 ///
294 /// Can return error if supplied method is not implimented.
295 ///
296 pub fn register_route(
297 &mut self,
298 url: &str,
299 methods: Vec<&str>,
300 func: RouteFunction,
301 ) -> Result<(), io::Error> {
302 let mut method_list: Vec<Method> = Vec::new();
303 for m in methods {
304 if let Some(_m_) = Method::from_string(m) {
305 method_list.push(_m_);
306 } else {
307 error!(
308 "No http method named {}. Try using only uppercase letters like GET,POST",
309 m
310 );
311 return Err(io::ErrorKind::InvalidInput.into());
312 }
313 }
314
315 let route = Route::new(func, method_list);
316 // Routes is not a NULL pointer
317 unsafe { self.routes.as_mut().add_route(url, route) };
318 Ok(())
319 }
320 // Get the local ipv4 address.
321 fn server_wl01_addr() -> Option<String> {
322 let udp_socket = match UdpSocket::bind("0.0.0.0:0") {
323 Ok(udps) => udps,
324 Err(_) => {
325 return None;
326 }
327 };
328 let _ = match udp_socket.connect("8.8.8.8:80") {
329 Ok(_) => (),
330 Err(_) => {
331 return None;
332 }
333 };
334 if let Ok(local_addr) = udp_socket.local_addr() {
335 if let IpAddr::V4(ipv4_addr) = local_addr.ip() {
336 return Some(ipv4_addr.to_string());
337 } else {
338 return None;
339 }
340 }
341 return None;
342 }
343 /// Set total number of threads in threadpool.
344 pub fn set_total_workers(&mut self, n: usize) -> () {
345 self.total_workers = n;
346 }
347 /// Set maximum payload size. If it exceeded in any request then automatic 413 Payload too large is sent.
348 pub fn set_maximum_payload_size(&mut self, size_in_MB: usize) -> () {
349 self.payload_maximum_size_in_MB = size_in_MB;
350 }
351 /// Set the read time out. If no packet recieved for this duration then we stop reading from stream and drop the connection.
352 pub fn set_read_time_out(&mut self, time_in_secs: u8) -> () {
353 self.read_time_out = Duration::from_secs(time_in_secs as u64);
354 }
355 /// Set the write time out. If we couldn't write on the stream for this duration we stop sending request and drop the connection.
356 pub fn set_write_time_out(&mut self, time_in_secs: u8) -> () {
357 self.write_time_out = Duration::from_secs(time_in_secs as u64);
358 }
359 /// set the maximum amount of time we keep a persistent connection alive after sending a response. Default 5 secs.
360 pub fn set_keep_alive_time_out(&mut self, time_in_secs: u8) {
361 self.keep_alive_time_out = Duration::from_secs(time_in_secs as u64);
362 }
363 /// set the maximum number of request-response cycle on a persistent connection. Default 10 request-response cycle.
364 pub fn set_maximum_keep_alive_requests(&mut self, n_requests: u8) {
365 self.keep_alive_max_count = n_requests;
366 }
367 /// Set the name of the directory where incoming files are stored. i.e. files coming in request bodies. Default name is `input_files`.
368 pub fn set_incoming_files_directory_name(&mut self,directory_name:&str){
369 self.file_upload_directory_name=String::from(directory_name);
370 }
371
372 /// Run the application.
373 ///
374 /// ## Parameters
375 /// - `host` : IP address of the API. Either It can be `127.0.0.1` then it can only be accessed from your local machine
376 /// or `0.0.0.0` then It can be accessed from all devices in your LAN.
377 /// - `port` : Port on which the API listens to. If It's zero then a random available port is assigned.
378 pub fn run(&mut self, host: &str, port: u16) -> () {
379 //termination and interuption signal handling
380 unsafe {
381 let mut sig_int_act: sigaction_struct = std::mem::zeroed();
382 let mut sig_term_act: sigaction_struct = std::mem::zeroed();
383 let mut sig_tstp_act: sigaction_struct = std::mem::zeroed();
384 sig_int_act.sa_sigaction = handle_shutdown_signal as usize;
385 sig_term_act.sa_sigaction = handle_shutdown_signal as usize;
386 sig_tstp_act.sa_sigaction = handle_sigtstp as usize;
387 sigaddset(&mut sig_int_act.sa_mask, SIGTERM);
388 sigaddset(&mut sig_int_act.sa_mask, SIGTSTP);
389 sigaddset(&mut sig_term_act.sa_mask, SIGINT);
390 sigaddset(&mut sig_term_act.sa_mask, SIGTSTP);
391 sigaddset(&mut sig_tstp_act.sa_mask, SIGINT);
392 sigaddset(&mut sig_tstp_act.sa_mask, SIGTERM);
393 sig_int_act.sa_flags = SA_RESTART;
394 sig_term_act.sa_flags = SA_RESTART;
395 sig_tstp_act.sa_flags = SA_RESTART;
396 sigaction_syscall(SIGINT, &sig_int_act, std::ptr::null_mut());
397 sigaction_syscall(SIGTERM, &sig_term_act, std::ptr::null_mut());
398 sigaction_syscall(SIGTSTP, &sig_tstp_act, std::ptr::null_mut());
399 }
400 let mut addr = String::from(host);
401 addr.push(':');
402 addr.push_str(&port.to_string());
403 let listner = match TcpListener::bind(&addr) {
404 Ok(l) => l,
405 Err(e) => {
406 eprintln!("{}", e);
407 return;
408 }
409 };
410 let send_buf_sz=get_send_buffer_len(listner.as_raw_fd());
411 let mut app_env = AppEnv::new(host, port, &self,send_buf_sz);
412 if let Ok(server_addr) = listner.local_addr() {
413 if server_addr.ip().is_unspecified() {
414 if let Some(wl10) = Self::server_wl01_addr() {
415 addr = wl10.clone();
416 addr.push(':');
417 addr.push_str(server_addr.port().to_string().as_str());
418 app_env.host = wl10;
419 app_env.port = server_addr.port();
420 }
421 } else {
422 addr = String::from("127.0.0.1");
423 app_env.host = addr.clone();
424 addr.push(':');
425 addr.push_str(server_addr.port().to_string().as_str());
426 app_env.port = server_addr.port();
427 }
428 }
429 log_info!("SERVER STARTED");
430 log_info!("Listening to {}", &addr);
431 log_info!("Press CTRL + C to stop the server.");
432 log_info!("Process PID : {}", process::id());
433 unsafe {
434 PORT = app_env.port;
435 }
436 let pool = ThreadPool::new(self.total_workers);
437 let max_keep_alive_count = app_env.keep_alive_max_count;
438 let app_env_arc = Arc::new(app_env);
439 for stream in listner.incoming().flatten() {
440 unsafe {
441 if SIG_FLAG {
442 break;
443 }
444 }
445 let stream = Arc::new(stream);
446 let app_env_cloned = Arc::clone(&app_env_arc);
447 let cnt = max_keep_alive_count;
448 pool.execute(move || {
449 let _ = Client(stream, app_env_cloned, cnt, true);
450 });
451 }
452 }
453}
454#[cfg(test)]
455mod apitest {
456 use super::*;
457 use io::Read;
458 use reqwest::{
459 blocking::Client,
460 header::{self, HeaderMap, HeaderValue}
461 };
462 use std::{collections::HashMap, fs};
463 use std::thread;
464 use utils::{ContentType, FileType};
465 use Request::HttpRequest;
466 use Response::{create_response, send_file, HttpResponse};
467 enum TestResult {
468 PASSED,
469 FAILED(String),
470
471 }
472 fn json_header_path_params(
473 _req: &HttpRequest,
474 _path_params: HashMap<String, String>,
475 ) -> HttpResponse {
476 let resp_json = r#"{
477 "Foo" : "Bar",
478 "Dummy" : 5
479 }"#;
480 let mut resp = create_response(&resp_json, 200, ContentType::JSON, false).unwrap();
481 for (key, val) in _req.headers.iter() {
482 resp.add_header(&key, &val);
483 }
484 for (key, val) in _path_params.iter() {
485 resp.add_header(&key, &val);
486 }
487 resp
488 }
489 fn file_download(_req:&HttpRequest,_path_params:HashMap<String,String>)->HttpResponse{
490 let resp=send_file("src/test/test.jpg", None,FileType::JPEG,200,false).unwrap();
491 return resp;
492 }
493 fn file_upload(req:&HttpRequest,_path_params:HashMap<String,String>)->HttpResponse{
494 if let Some(file_path) =&req.body_location{
495 let mut uploaded_file=match fs::File::open(file_path){
496 Ok(f)=>f,
497 Err(e)=>{
498 let mut s=String::from("FAILED TO OPEN UPLOADED FILE.\n");
499 s.push_str(e.to_string().as_str());
500 let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
501 return resp;
502 }
503 };
504 let mut uploaded_file_buf=Vec::<u8>::new();
505 let _=match uploaded_file.read_to_end(&mut uploaded_file_buf){
506 Ok(_)=>(),
507 Err(e)=>{
508 let mut s=String::from("FAILED TO READ UPLOADED FILE.\n");
509 s.push_str(e.to_string().as_str());
510 let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
511 return resp;
512 }
513 };
514 let mut orignal_file=match fs::File::open("src/test/test.jpg"){
515 Ok(f)=>f,
516 Err(e)=>{
517 let mut s=String::from("FAILED TO OPEN ORIGNAL FILE.\n");
518 s.push_str(e.to_string().as_str());
519 let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
520 return resp;
521 }
522 };
523 let mut orignal_file_buf=Vec::<u8>::new();
524 let _=match orignal_file.read_to_end(&mut orignal_file_buf){
525 Ok(_)=>(),
526 Err(e)=>{
527 let mut s=String::from("FAILED TO READ ORIGNAL FILE.\n");
528 s.push_str(e.to_string().as_str());
529 let resp=create_response(&s, 400, ContentType::TEXT, false).unwrap();
530 return resp;
531 }
532 };
533 if uploaded_file_buf.eq(&orignal_file_buf){
534 let resp=create_response("SUCCESS", 200, ContentType::TEXT, false).unwrap();
535 return resp;
536 }
537 else {
538 let resp=create_response("ORIGNAL FILE AND UPLOADED FILE DOESN'T MATCH", 200, ContentType::TEXT, false).unwrap();
539 return resp;
540 }
541 }
542 else {
543
544 let resp=create_response("BODY LOCATION IS NONE.", 400, ContentType::TEXT, false).unwrap();
545 return resp;
546 }
547 }
548 fn run_server(){
549 let mut app = RastAPI::new();
550 let _ = app.register_route("/json/{id}/{name}", vec!["GET"], json_header_path_params).expect("FAILED TO REGISTER 1");
551 let _ = app.register_route("/download", vec!["GET"], file_download).expect("FAILED TO REGISTER 2");
552 let _=app.register_route("/upload", vec!["POST"], file_upload);
553 app.run("127.0.0.1", 5000);
554 }
555 #[test]
556 fn json_header_path_params_test() {
557 let _handle1 = thread::spawn(|| {
558 run_server();
559 });
560 thread::sleep(std::time::Duration::from_secs(1));
561 let handle2 = thread::spawn(|| {
562 let mut headers = header::HeaderMap::new();
563 headers.insert("X-api-key", HeaderValue::from_static("abcdef12"));
564 let response = match Client::new()
565 .get("http://127.0.0.1:5000/json/5/rony")
566 .headers(headers)
567 .send(){
568 Ok(R)=>R,
569 Err(e)=>{
570 let mut s=String::from("FAILED TO SEND REQUEST. REASON :\n");
571 s.push_str(e.to_string().as_str());
572 return TestResult::FAILED(s);
573 }
574 };
575 let resp_json = r#"{
576 "Foo" : "Bar",
577 "Dummy" : 5
578 }"#;
579 let resp_json_vec = resp_json.as_bytes().to_vec();
580 let h = response.headers();
581 if !(h.get("id").map(|hv| hv.to_str().unwrap()).eq(&Some("5"))){
582 return TestResult::FAILED("FAILED PATH PARAM 1".to_string());
583 }
584 if !(h.get("name").map(|hv| hv.to_str().unwrap()).eq(&Some("rony"))){
585 return TestResult::FAILED("FAILED PATH PARAM 2".to_string());
586 }
587 if !(h.get("X-api-key").map(|hv| hv.to_str().unwrap()).eq(&Some("abcdef12"))){
588 return TestResult::FAILED("FAILED HEADER".to_string());
589 }
590 let body = match response.bytes(){
591 Ok(b)=>b.to_vec(),
592 Err(e)=>{
593 let mut s=String::from("FAILED TO RECIEVE BYTES FROM SERVER. REASON :\n");
594 s.push_str(e.to_string().as_str());
595 return TestResult::FAILED(s);
596 }
597 };
598 if !(body.eq(&resp_json_vec)){
599 return TestResult::FAILED(String::from("BODY MISMATCH"));
600 }
601 TestResult::PASSED
602 });
603 let res=handle2.join().expect("FAILED TO JOIN");
604 match res {
605 TestResult::FAILED(S)=>{
606 assert!(false,"{}",S);
607 },
608 TestResult::PASSED=>()
609 };
610 }
611 #[test]
612 fn file_download_test(){
613 let _handle1 = thread::spawn(|| {
614 run_server();
615 });
616 let _=thread::sleep(std::time::Duration::from_secs(1));
617 let handle2=thread::spawn(||{
618 let resp=match Client::new().get("http://127.0.0.1:5000/download").send(){
619 Ok(R)=>R,
620 Err(e)=>{
621 let mut s=String::from("FAILED TO SEND REQUEST. REASON :\n");
622 s.push_str(e.to_string().as_str());
623 return TestResult::FAILED(s);
624 }
625 };
626 let mut file=match fs::File::open("src/test/test.jpg"){
627 Ok(F)=>F,
628 Err(e)=>{
629 let mut s=String::from("FAILED TO OPEN ORIGNAL FILE. REASON :\n");
630 s.push_str(e.to_string().as_str());
631 return TestResult::FAILED(s);
632 }
633 };
634 let mut orignal_buf=Vec::<u8>::new();
635 let _=match file.read_to_end(&mut orignal_buf){
636 Ok(_)=>(),
637 Err(e)=>{
638 let mut s=String::from("FAILED TO LOAD ORIGNAL FILE. REASON :\n");
639 s.push_str(e.to_string().as_str());
640 return TestResult::FAILED(s);
641 }
642 };
643 let downloaded_buf=match resp.bytes(){
644 Ok(b)=>b.to_vec(),
645 Err(e)=>{
646 let mut s=String::from("FAILED TO RECIEVE BYTES FROM SERVER. REASON :\n");
647 s.push_str(e.to_string().as_str());
648 return TestResult::FAILED(s);
649 }
650 };
651 if !(orignal_buf.eq(&downloaded_buf)){
652 return TestResult::FAILED(String::from("ORIGNAL FILE AND DOWNLOADED FILE MISMATCH"));
653 }
654 TestResult::PASSED
655 });
656 let res=handle2.join().expect("FAILED TO JOIN");
657 match res {
658 TestResult::FAILED(S)=>{
659 assert!(false,"{}",S);
660 },
661 TestResult::PASSED=>()
662 };
663 }
664 #[test]
665 fn file_upload_test(){
666 let _handle1=thread::spawn(||{
667 run_server();
668 });
669 let handle2=thread::spawn(||{
670 let test_file=match fs::File::open("src/test/test.jpg"){
671 Ok(f)=>f,
672 Err(e)=>{
673 let mut s=String::from("FAILED TO OPEN TEST FILE. REASON :\n");
674 s.push_str(e.to_string().as_str());
675 return TestResult::FAILED(s);
676 }
677 };
678 let mut headers=HeaderMap::new();
679 headers.insert("Content-Type", HeaderValue::from_static("image/jpeg"));
680 let resp=match Client::new().post("http://127.0.0.1:5000/upload").headers(headers).body(test_file).send(){
681 Ok(R)=>R,
682 Err(e)=>{
683 let mut s=String::from("FAILED TO SEND REQUEST. REASON :\n");
684 s.push_str(e.to_string().as_str());
685 return TestResult::FAILED(s);
686 }
687 };
688 if resp.status().as_u16()!=200_u16{
689 let content=match resp.text(){
690 Ok(s)=>s,
691 Err(e)=>{
692 let mut s=String::from("FAILED TO GET RESPONSE TEXT. REASON :\n");
693 s.push_str(e.to_string().as_str());
694 return TestResult::FAILED(s);
695 }
696 };
697 return TestResult::FAILED(content);
698 }
699 TestResult::PASSED
700 });
701 let res=handle2.join().expect("FAILED TO JOIN");
702 match res {
703 TestResult::FAILED(s)=>{
704 assert!(false,"{}",s);
705 },
706 TestResult::PASSED=>()
707 }
708
709 }
710}