help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
rust-web-server
/
src
RSK World
rust-web-server
Rust Web Server - High-Performance Async Web Server + WebSocket Support + JWT Authentication + File Upload + Memory Safety + Educational Design
src
  • auth.rs15.7 KB
  • config.rs2.9 KB
  • error.rs5.2 KB
  • file_upload.rs19 KB
  • handlers.rs12.8 KB
  • lib.rs1.8 KB
  • main.rs6 KB
  • middleware.rs6.2 KB
  • static_files.rs9.9 KB
  • utils.rs9.6 KB
  • websocket.rs15.3 KB
API.mdmiddleware.rsstatic_files.rs
docs/API.md
Raw Download

API.md

# API Documentation

## Overview

The Rust Web Server provides a RESTful API with endpoints for server information, health checks, statistics, and more.

## Base URL

```
http://localhost:8080
```

## Authentication

Some endpoints may require authentication. When authentication is enabled, include the JWT token in the Authorization header:

```
Authorization: Bearer <your-jwt-token>
```

## Endpoints

### Health Check

Get the current health status of the server.

**Endpoint:** `GET /health`

**Response:**
```json
{
"status": "healthy",
"timestamp": "2026-01-23T10:00:00Z",
"uptime_seconds": 3600,
"active_connections": 5
}
```

### Server Information

Get detailed information about the server.

**Endpoint:** `GET /api/info`

**Response:**
```json
{
"name": "Rust Web Server",
"version": "0.1.0",
"description": "High-performance web server built with Rust",
"author": "RSK World",
"contact": {
"website": "https://rskworld.in",
"email": "hello@rskworld.in",
"phone": "+91 93305 39277",
"address": "Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India, 713147"
},
"features": [
"High performance",
"Async request handling",
"Memory safety",
"Concurrent connections",
"Static file serving",
"Systems programming"
],
"technologies": [
"Rust",
"Tokio",
"Async/Await",
"HTTP Server",
"Memory Safety",
"Concurrency"
]
}
```

### Server Statistics

Get server performance statistics and metrics.

**Endpoint:** `GET /api/stats`

**Response:**
```json
{
"uptime_seconds": 3600,
"request_count": 1250,
"active_connections": 5,
"memory_usage_mb": 42.0,
"cpu_usage_percent": 15.2,
"total_requests": 1250,
"avg_response_time_ms": 12.5
}
```

### Echo Service

Echo back the request data with additional metadata.

**Endpoint:** `POST /api/echo`

**Request Body:**
```json
{
"message": "Hello, World!",
"user": "test-user"
}
```

**Response:**
```json
{
"message": "Hello, World!",
"user": "test-user",
"timestamp": "2026-01-23T10:00:00Z",
"method": "POST",
"path": "/api/echo",
"headers": {
"content-type": "application/json",
"user-agent": "curl/7.68.0"
}
}
```

### File Upload

Upload files to the server.

**Endpoint:** `POST /api/upload`

**Content-Type:** `multipart/form-data`

**Parameters:**
- `file`: The file to upload

**Response:**
```json
{
"filename": "uploaded_file.txt",
"original_filename": "document.txt",
"size": 1024,
"content_type": "text/plain",
"upload_time": "2026-01-23T10:00:00Z",
"url": "/uploads/uploaded_file.txt"
}
```

### Authentication Endpoints

#### Login

**Endpoint:** `POST /api/auth/login`

**Request Body:**
```json
{
"username": "admin",
"password": "password"
}
```

**Response:**
```json
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"user": {
"id": "user-123",
"username": "admin",
"email": "admin@example.com",
"role": "Admin"
},
"expires_at": "2026-01-23T11:00:00Z"
}
```

#### Register

**Endpoint:** `POST /api/auth/register`

**Request Body:**
```json
{
"username": "newuser",
"email": "user@example.com",
"password": "securepassword"
}
```

**Response:**
```json
{
"message": "User registered successfully",
"user_id": "user-456"
}
```

## Error Responses

All API endpoints return appropriate HTTP status codes and error messages:

### 400 Bad Request
```json
{
"error": "Bad Request",
"message": "Invalid request parameters",
"details": "The 'username' field is required"
}
```

### 401 Unauthorized
```json
{
"error": "Unauthorized",
"message": "Authentication required"
}
```

### 403 Forbidden
```json
{
"error": "Forbidden",
"message": "Access denied"
}
```

### 404 Not Found
```json
{
"error": "Not Found",
"message": "Endpoint not found"
}
```

### 500 Internal Server Error
```json
{
"error": "Internal Server Error",
"message": "An unexpected error occurred"
}
```

## Rate Limiting

The API implements rate limiting to prevent abuse:

- **General endpoints:** 100 requests per minute per IP
- **Authentication endpoints:** 10 requests per minute per IP
- **File upload:** 5 uploads per minute per user

Rate limit headers are included in responses:
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1642934460
```

## CORS

Cross-Origin Resource Sharing is supported. By default, the server allows requests from:
- `http://localhost:3000`
- `http://localhost:8080`

## Content Types

The API supports the following content types:
- `application/json`
- `multipart/form-data` (for file uploads)
- `text/plain`

## Versioning

The API uses URL-based versioning. The current version is v1 (no version prefix in URLs).

---

**© 2026 RSK World. All rights reserved.**
src/middleware.rs
Raw Download
Find: Go to:
/*
 * Middleware Module - Rust Web Server
 * 
 * Created by RSK World (https://rskworld.in)
 * Founder: Molla Samser
 * Designer & Tester: Rima Khatun
 * 
 * Contact:
 * - Email: hello@rskworld.in, support@rskworld.in
 * - Phone: +91 93305 39277
 * - Address: Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India, 713147
 * 
 * © 2026 RSK World. All rights reserved.
 * Content used for educational purposes only.
 */

use hyper::{Body, Request, Response};
use std::time::Instant;
use tracing::{info, warn};

/// Request logging middleware
pub async fn log_request(req: Request<Body>) -> Request<Body> {
    let method = req.method().clone();
    let uri = req.uri().clone();
    let user_agent = req.headers()
        .get("user-agent")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("Unknown");

    info!("Incoming request: {} {} from {}", method, uri, user_agent);
    
    req
}

/// Response logging middleware
pub async fn log_response(
    req: &Request<Body>,
    response: &Response<Body>,
    start_time: Instant,
) {
    let method = req.method();
    let uri = req.uri();
    let status = response.status();
    let duration = start_time.elapsed();

    let status_level = if status.is_success() {
        "INFO"
    } else if status.is_client_error() {
        "WARN"
    } else if status.is_server_error() {
        "ERROR"
    } else {
        "INFO"
    };

    match status_level {
        "INFO" => info!("{} {} {} {}ms", method, uri, status, duration.as_millis()),
        "WARN" => warn!("{} {} {} {}ms", method, uri, status, duration.as_millis()),
        "ERROR" => tracing::error!("{} {} {} {}ms", method, uri, status, duration.as_millis()),
        _ => {}
    }
}

/// CORS middleware
pub fn add_cors_headers(mut response: Response<Body>) -> Response<Body> {
    let headers = response.headers_mut();
    
    headers.insert("Access-Control-Allow-Origin", "*".parse().unwrap());
    headers.insert("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS".parse().unwrap());
    headers.insert("Access-Control-Allow-Headers", "Content-Type, Authorization".parse().unwrap());
    headers.insert("Access-Control-Max-Age", "86400".parse().unwrap());
    
    response
}

/// Security headers middleware
pub fn add_security_headers(mut response: Response<Body>) -> Response<Body> {
    let headers = response.headers_mut();
    
    headers.insert("X-Content-Type-Options", "nosniff".parse().unwrap());
    headers.insert("X-Frame-Options", "DENY".parse().unwrap());
    headers.insert("X-XSS-Protection", "1; mode=block".parse().unwrap());
    headers.insert("Strict-Transport-Security", "max-age=31536000; includeSubDomains".parse().unwrap());
    headers.insert("Referrer-Policy", "strict-origin-when-cross-origin".parse().unwrap());
    
    response
}

/// Rate limiting middleware (simplified implementation)
pub struct RateLimiter {
    requests: std::collections::HashMap<String, u32>,
    window_start: std::time::Instant,
    window_duration: std::time::Duration,
    max_requests: u32,
}

impl RateLimiter {
    pub fn new(max_requests: u32, window_seconds: u64) -> Self {
        Self {
            requests: std::collections::HashMap::new(),
            window_start: std::time::Instant::now(),
            window_duration: std::time::Duration::from_secs(window_seconds),
            max_requests,
        }
    }

    pub fn is_allowed(&mut self, client_ip: &str) -> bool {
        // Reset window if expired
        if self.window_start.elapsed() > self.window_duration {
            self.requests.clear();
            self.window_start = std::time::Instant::now();
        }

        let count = self.requests.entry(client_ip.to_string()).or_insert(0);
        *count += 1;

        *count <= self.max_requests
    }
}

/// Request size limiting middleware
pub fn check_request_size(req: &Request<Body>, max_size: usize) -> bool {
    if let Some(content_length) = req.headers().get("content-length") {
        if let Ok(length_str) = content_length.to_str() {
            if let Ok(length) = length_str.parse::<usize>() {
                return length <= max_size;
            }
        }
    }
    true // Default to allowing if we can't determine size
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_rate_limiter() {
        let mut limiter = RateLimiter::new(5, 60); // 5 requests per minute
        
        // Should allow first 5 requests
        for i in 0..5 {
            assert!(limiter.is_allowed("127.0.0.1"), "Request {} should be allowed", i + 1);
        }
        
        // Should deny 6th request
        assert!(!limiter.is_allowed("127.0.0.1"), "6th request should be denied");
    }

    #[test]
    fn test_request_size_check() {
        let mut builder = Request::builder()
            .method("POST")
            .uri("/test")
            .header("content-length", "1000");
        
        let request = builder.body(Body::empty()).unwrap();
        
        assert!(check_request_size(&request, 2000)); // Should allow
        assert!(!check_request_size(&request, 500));  // Should deny
    }

    #[test]
    fn test_cors_headers() {
        let response = Response::builder()
            .status(200)
            .body(Body::empty())
            .unwrap();
        
        let response_with_cors = add_cors_headers(response);
        
        assert_eq!(
            response_with_cors.headers().get("Access-Control-Allow-Origin").unwrap(),
            "*"
        );
        assert_eq!(
            response_with_cors.headers().get("Access-Control-Allow-Methods").unwrap(),
            "GET, POST, PUT, DELETE, OPTIONS"
        );
    }

    #[test]
    fn test_security_headers() {
        let response = Response::builder()
            .status(200)
            .body(Body::empty())
            .unwrap();
        
        let response_with_security = add_security_headers(response);
        
        assert_eq!(
            response_with_security.headers().get("X-Content-Type-Options").unwrap(),
            "nosniff"
        );
        assert_eq!(
            response_with_security.headers().get("X-Frame-Options").unwrap(),
            "DENY"
        );
    }
}
201 lines•6.2 KB
rust
src/static_files.rs
Raw Download
Find: Go to:
/*
 * Static File Serving Module - Rust Web Server
 * 
 * Created by RSK World (https://rskworld.in)
 * Founder: Molla Samser
 * Designer & Tester: Rima Khatun
 * 
 * Contact:
 * - Email: hello@rskworld.in, support@rskworld.in
 * - Phone: +91 93305 39277
 * - Address: Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India, 713147
 * 
 * © 2026 RSK World. All rights reserved.
 * Content used for educational purposes only.
 */

use hyper::{Body, Response, StatusCode};
use mime_guess::from_path;
use std::path::{Path, PathBuf};
use tokio::fs;
use tracing::{debug, error, warn};

use crate::error::{ServerError, ServerResult};

/// Static file handler
pub struct StaticFileHandler {
    base_dir: PathBuf,
}

impl StaticFileHandler {
    /// Create a new static file handler
    pub fn new(base_dir: PathBuf) -> Self {
        Self { base_dir }
    }

    /// Serve a static file
    pub async fn serve_file(&self, file_path: &str) -> ServerResult<Response<Body>> {
        let full_path = self.get_full_path(file_path)?;
        
        // Security check: ensure the path is within the base directory
        if !self.is_safe_path(&full_path) {
            warn!("Attempted to access file outside base directory: {:?}", full_path);
            return Err(ServerError::Forbidden("Access denied".to_string()));
        }

        debug!("Serving static file: {:?}", full_path);

        // Check if file exists
        if !full_path.exists() {
            return Err(ServerError::NotFound(format!("File not found: {}", file_path)));
        }

        // Check if it's a directory
        if full_path.is_dir() {
            // Try to serve index.html
            let index_path = full_path.join("index.html");
            if index_path.exists() {
                return self.serve_file_content(&index_path).await;
            } else {
                return Err(ServerError::NotFound("Directory listing not allowed".to_string()));
            }
        }

        // Serve the file
        self.serve_file_content(&full_path).await
    }

    /// Serve file content
    async fn serve_file_content(&self, file_path: &Path) -> ServerResult<Response<Body>> {
        // Read file content
        let content = fs::read(file_path).await
            .map_err(|e| ServerError::StaticFile(format!("Failed to read file: {}", e)))?;

        // Determine MIME type
        let mime_type = from_path(file_path)
            .first_or_octet_stream()
            .to_string();

        // Create response
        let mut response = Response::builder()
            .status(StatusCode::OK)
            .header("content-type", mime_type)
            .body(Body::from(content))
            .map_err(|e| ServerError::Internal(format!("Failed to create response: {}", e)))?;

        // Add cache headers for static files
        let headers = response.headers_mut();
        match headers.insert("cache-control", "public, max-age=3600".parse()) {
            Ok(_) => {},
            Err(_) => warn!("Failed to insert cache-control header"),
        }
        match headers.insert("etag", format!("\"{}\"", self.calculate_etag(&content)).parse()) {
            Ok(_) => {},
            Err(_) => warn!("Failed to insert etag header"),
        }

        Ok(response)
    }

    /// Get full path for a relative file path
    fn get_full_path(&self, file_path: &str) -> ServerResult<PathBuf> {
        let path = PathBuf::from(file_path);
        
        // Remove leading slash if present
        let relative_path = if path.starts_with("/") {
            path.strip_prefix("/").unwrap()
        } else {
            path.as_path()
        };

        let full_path = self.base_dir.join(relative_path);
        
        // Convert to absolute path
        match full_path.canonicalize() {
            Ok(path) => Ok(path),
            Err(_) => Ok(full_path), // Return original path if canonicalization fails
        }
    }

    /// Check if a path is safe (within base directory)
    fn is_safe_path(&self, full_path: &Path) -> bool {
        match self.base_dir.canonicalize() {
            Ok(base_canonical) => {
                match full_path.canonicalize() {
                    Ok(path_canonical) => {
                        path_canonical.starts_with(&base_canonical)
                    }
                    Err(_) => {
                        // If we can't canonicalize, do a simple prefix check
                        full_path.starts_with(&self.base_dir)
                    }
                }
            }
            Err(_) => true, // If base dir can't be canonicalized, assume safe
        }
    }

    /// Calculate ETag for file content
    fn calculate_etag(&self, content: &[u8]) -> String {
        use std::hash::{Hash, Hasher};
        use std::collections::hash_map::DefaultHasher;
        
        let mut hasher = DefaultHasher::new();
        content.hash(&mut hasher);
        format!("{:x}", hasher.finish())
    }

    /// Check if file has been modified based on If-None-Match header
    pub fn is_not_modified(&self, etag: &str, if_none_match: Option<&str>) -> bool {
        match if_none_match {
            Some(header_etag) => header_etag == etag,
            None => false,
        }
    }

    /// Handle conditional GET requests
    pub async fn serve_file_conditional(
        &self,
        file_path: &str,
        if_none_match: Option<&str>,
    ) -> ServerResult<Response<Body>> {
        let full_path = self.get_full_path(file_path)?;
        
        if !self.is_safe_path(&full_path) {
            return Err(ServerError::Forbidden("Access denied".to_string()));
        }

        if !full_path.exists() {
            return Err(ServerError::NotFound(format!("File not found: {}", file_path)));
        }

        // Read file content to calculate ETag
        let content = fs::read(&full_path).await
            .map_err(|e| ServerError::StaticFile(format!("Failed to read file: {}", e)))?;

        let etag = format!("\"{}\"", self.calculate_etag(&content));

        // Check if file has not been modified
        if self.is_not_modified(&etag, if_none_match) {
            return Ok(Response::builder()
                .status(StatusCode::NOT_MODIFIED)
                .header("etag", etag)
                .body(Body::empty())
                .map_err(|e| ServerError::Internal(format!("Failed to create response: {}", e)))?);
        }

        // Serve the file with ETag header
        let mime_type = from_path(&full_path)
            .first_or_octet_stream()
            .to_string();

        let mut response = Response::builder()
            .status(StatusCode::OK)
            .header("content-type", mime_type)
            .header("etag", etag)
            .header("cache-control", "public, max-age=3600")
            .body(Body::from(content))
            .map_err(|e| ServerError::Internal(format!("Failed to create response: {}", e)))?;

        Ok(response)
    }

    /// Create default static files directory structure
    pub async fn create_default_structure(&self) -> ServerResult<()> {
        let dirs = vec![
            self.base_dir.join("css"),
            self.base_dir.join("js"),
            self.base_dir.join("images"),
            self.base_dir.join("fonts"),
        ];

        for dir in dirs {
            if !dir.exists() {
                fs::create_dir_all(&dir).await
                    .map_err(|e| ServerError::StaticFile(format!("Failed to create directory: {}", e)))?;
                debug!("Created directory: {:?}", dir);
            }
        }

        // Create a default index.html if it doesn't exist
        let index_path = self.base_dir.join("index.html");
        if !index_path.exists() {
            let default_html = r#"<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome to Rust Web Server</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
        .container { text-align: center; }
        .logo { font-size: 4em; margin: 20px 0; }
    </style>
</head>
<body>
    <div class="container">
        <div class="logo">🦀</div>
        <h1>Welcome to Rust Web Server</h1>
        <p>High-performance web server built with Rust</p>
        <p>Created by <strong>RSK World</strong></p>
    </div>
</body>
</html>"#;

            fs::write(&index_path, default_html).await
                .map_err(|e| ServerError::StaticFile(format!("Failed to create index.html: {}", e)))?;
            debug!("Created default index.html");
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[tokio::test]
    async fn test_static_file_handler() {
        let temp_dir = TempDir::new().unwrap();
        let handler = StaticFileHandler::new(temp_dir.path().to_path_buf());

        // Create a test file
        let test_file = temp_dir.path().join("test.txt");
        fs::write(&test_file, "Hello, World!").await.unwrap();

        // Test serving the file
        let response = handler.serve_file("test.txt").await.unwrap();
        assert_eq!(response.status(), StatusCode::OK);

        // Test non-existent file
        let result = handler.serve_file("nonexistent.txt").await;
        assert!(result.is_err());
    }

    #[test]
    fn test_safe_path() {
        let temp_dir = TempDir::new().unwrap();
        let handler = StaticFileHandler::new(temp_dir.path().to_path_buf());

        // Test safe path
        let safe_path = temp_dir.path().join("safe.txt");
        assert!(handler.is_safe_path(&safe_path));

        // Test unsafe path (would need to be outside base dir in real scenario)
        // This is a simplified test - in practice, you'd need to create an actual unsafe path
    }
}
290 lines•9.9 KB
rust

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer