File-Handler Plugin

The File-Handler plugin serves static files and handles file operations including GET, PUT, POST, DELETE, and HEAD requests. It provides the core file serving functionality for Rusty Beam with security features and content type detection.

Overview

The file-handler plugin is the primary content-serving plugin in Rusty Beam. It maps URL paths to filesystem paths, serves files with appropriate content types, and supports file uploads and modifications. The plugin includes security features like path traversal protection and respects host-specific document roots.

Key Features

Configuration

The file-handler plugin is configured as part of the plugin pipeline in your host configuration:

<li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
    <span itemprop="library">file://./plugins/file-handler.so</span>
    <meta itemprop="root_dir" content="./public">
</li>

Configuration Parameters

Parameter Type Required Default Description
root_dir String No "." Default document root directory. Can be overridden by host-specific hostRoot
Note: The host-specific hostRoot configuration takes precedence over the plugin's root_dir setting.

HTTP Methods

GET - Retrieve Files

Serves files from the document root:

# Get a specific file
curl http://localhost:3000/index.html

# Get directory (serves index.html if exists)
curl http://localhost:3000/

# Get file from subdirectory
curl http://localhost:3000/assets/style.css

PUT - Create/Replace Files

Creates new files or replaces existing ones:

# Create a new file
curl -X PUT http://localhost:3000/new-file.txt \
  -H "Content-Type: text/plain" \
  -d "File content here"

# Replace existing file
curl -X PUT http://localhost:3000/existing.html \
  -H "Content-Type: text/html" \
  -d "<html><body>Updated content</body></html>"

# Upload binary file
curl -X PUT http://localhost:3000/image.png \
  -H "Content-Type: image/png" \
  --data-binary @local-image.png

Response codes:

POST - Append to Files

Appends content to existing files or creates new ones:

# Append to log file
curl -X POST http://localhost:3000/app.log \
  -H "Content-Type: text/plain" \
  -d "New log entry"

# Create file if doesn't exist
curl -X POST http://localhost:3000/notes.txt \
  -d "First note"

DELETE - Remove Files

Deletes files from the filesystem:

# Delete a file
curl -X DELETE http://localhost:3000/old-file.txt

# Response: 200 OK with "File deleted successfully"
# or 404 Not Found if file doesn't exist

HEAD - Get File Metadata

Returns headers without body content:

# Check if file exists and get metadata
curl -I http://localhost:3000/document.pdf

# Response includes:
# Content-Type: application/pdf
# Content-Length: 1048576

OPTIONS - Get Allowed Methods

Returns supported HTTP methods:

curl -X OPTIONS http://localhost:3000/
# Response header: Allow: GET, PUT, DELETE, OPTIONS, POST, HEAD

Content Type Detection

The plugin automatically sets Content-Type based on file extensions:

Extension Content-Type
.html text/html
.css text/css
.js application/javascript
.json application/json
.txt text/plain
.png image/png
.jpg, .jpeg image/jpeg
.gif image/gif
other application/octet-stream

Security Features

Path Traversal Protection

The plugin prevents directory traversal attacks by:

# These attacks are blocked:
curl http://localhost:3000/../etc/passwd         # 403 Forbidden
curl http://localhost:3000/../../sensitive.txt   # 403 Forbidden
curl http://localhost:3000/./././../../../etc/   # 403 Forbidden

File Permissions

Important: The plugin operates with the permissions of the Rusty Beam process. Ensure proper file system permissions are set.

Plugin Pipeline Placement

The file-handler plugin should typically be placed near the end of the pipeline:

1. basic-auth.so      → Authentication
2. authorization.so   → Access control
3. cors.so           → CORS headers
4. selector-handler.so → Dynamic content
5. file-handler.so    → Static files ✓
6. error-handler.so   → Error pages

Examples

Basic Static File Server

<li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
    <span itemprop="library">file://./plugins/file-handler.so</span>
    <meta itemprop="root_dir" content="./static">
</li>

Multi-Host Configuration

<!-- Host 1 configuration -->
<tr itemscope itemtype="https://rustybeam.net/schema/HostConfig">
    <td itemprop="hostName">example.com</td>
    <td itemprop="hostRoot">./sites/example</td>
    <td>
        <ol>
            <li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
                <span itemprop="library">file://./plugins/file-handler.so</span>
            </li>
        </ol>
    </td>
</tr>

<!-- Host 2 configuration -->
<tr itemscope itemtype="https://rustybeam.net/schema/HostConfig">
    <td itemprop="hostName">api.example.com</td>
    <td itemprop="hostRoot">./sites/api</td>
    <td>
        <ol>
            <li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
                <span itemprop="library">file://./plugins/file-handler.so</span>
            </li>
        </ol>
    </td>
</tr>

Upload Script Example

#!/bin/bash
# upload.sh - Upload files to Rusty Beam

FILE="$1"
REMOTE_PATH="$2"

if [ -z "$FILE" ] || [ -z "$REMOTE_PATH" ]; then
    echo "Usage: ./upload.sh <local-file> <remote-path>"
    exit 1
fi

# Detect content type
CONTENT_TYPE=$(file -b --mime-type "$FILE")

# Upload file
curl -X PUT "http://localhost:3000/$REMOTE_PATH" \
    -H "Content-Type: $CONTENT_TYPE" \
    --data-binary "@$FILE"

JavaScript File Upload

async function uploadFile(file, path) {
    const response = await fetch(`http://localhost:3000/${path}`, {
        method: 'PUT',
        headers: {
            'Content-Type': file.type
        },
        body: file
    });
    
    if (response.status === 201) {
        console.log('File created successfully');
    } else if (response.status === 200) {
        console.log('File updated successfully');
    } else {
        console.error('Upload failed:', response.statusText);
    }
}

// Usage with file input
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    if (file) {
        uploadFile(file, `uploads/${file.name}`);
    }
});

Best Practices

Directory Structure

project/
├── config.html
├── plugins/
│   └── file-handler.so
└── public/              # Document root
    ├── index.html       # Default page
    ├── assets/
    │   ├── css/
    │   ├── js/
    │   └── images/
    └── uploads/         # Writable directory for uploads

Security Recommendations

Performance Considerations

Troubleshooting

Common Issues

Issue Cause Solution
404 for existing files Wrong document root Check hostRoot or root_dir configuration
403 Forbidden Path traversal attempt or permissions Use paths within document root, check file permissions
PUT/POST failing Directory doesn't exist or no write permission Create parent directories, check permissions
Wrong content type Unknown file extension File gets application/octet-stream by default
Method not allowed Using unsupported HTTP method Use GET, PUT, POST, DELETE, HEAD, or OPTIONS

Debug Logging

Run the server with -v flag to see file operations:

./rusty-beam -v config.html

Integration with Other Plugins

Selector-Handler Plugin

The selector-handler plugin can modify HTML files before the file-handler serves them. Place selector-handler before file-handler for this to work.

Authorization Plugin

Use the authorization plugin to control access to files based on user roles and paths.

Compression Plugin

The compression plugin can compress files served by file-handler. Place it after file-handler in the pipeline.

Range Request Support

Note: The file-handler advertises support for selector-based Range requests via the Accept-Ranges: selector header, but actual Range request processing is handled by the selector-handler plugin.

See Also