WebService Documentation
The WebService class provides static file serving capabilities for the SignalWire AI Agents SDK. It follows the same architectural pattern as SearchService, allowing it to run as a standalone service or alongside your AI agents.
Table of Contents
- Overview
- Installation
- Quick Start
- Configuration
- Security Features
- HTTPS/SSL Support
- API Endpoints
- Usage Examples
- Deployment Patterns
Overview
WebService is designed to serve static files with configurable security features. It's perfect for:
- Serving agent documentation and API specs
- Hosting static assets (images, CSS, JavaScript)
- Serving generated reports and exports
- Providing configuration files and templates
- Hosting agent UI components
Key Features
- Multiple directory mounting - Serve different directories at different URL paths
- Security-first design - Authentication, CORS, security headers, file filtering
- HTTPS support - Full SSL/TLS support with PEM files
- Directory browsing - Optional HTML directory listings
- MIME type handling - Automatic content-type detection
- Path traversal protection - Prevents access outside designated directories
- File filtering - Allow/block specific file extensions
Installation
WebService is included in the core SignalWire AI Agents SDK:
pip install signalwire-agents
Quick Start
from signalwire_agents import WebService
# Create a service to serve files
service = WebService(
port=8002,
directories={
"/docs": "./documentation",
"/assets": "./static/assets"
}
)
# Start the service
service.start()
# Service available at http://localhost:8002
# Basic Auth: dev:w00t (auto-generated)
Configuration
WebService can be configured through multiple methods (in order of priority):
1. Constructor Parameters
service = WebService(
port=8002, # Port to bind to
directories={ # URL path to directory mappings
"/docs": "./documentation",
"/assets": "./static"
},
basic_auth=("admin", "secret"), # Custom authentication
enable_directory_browsing=True, # Allow directory listings
allowed_extensions=['.html', '.css', '.js'], # Whitelist extensions
blocked_extensions=['.env', '.key'], # Blacklist extensions
max_file_size=100 * 1024 * 1024, # Max file size (100MB)
enable_cors=True # Enable CORS headers
)
2. Environment Variables
# Basic authentication
export SWML_BASIC_AUTH_USER="admin"
export SWML_BASIC_AUTH_PASS="secretpassword"
# SSL/HTTPS configuration
export SWML_SSL_ENABLED=true
export SWML_SSL_CERT="/path/to/cert.pem"
export SWML_SSL_KEY="/path/to/key.pem"
# Security settings
export SWML_ALLOWED_HOSTS="example.com,*.example.com"
export SWML_CORS_ORIGINS="https://app.example.com"
3. Configuration File
Create a web.json or swml_web.json file:
{
"service": {
"port": 8002,
"directories": {
"/docs": "./documentation",
"/api": "./api-specs",
"/reports": "./generated/reports"
},
"enable_directory_browsing": true,
"max_file_size": 52428800,
"allowed_extensions": [".html", ".css", ".js", ".json", ".pdf"],
"blocked_extensions": [".env", ".key", ".pem"]
},
"security": {
"basic_auth": {
"username": "admin",
"password": "secure123"
},
"ssl_enabled": true,
"ssl_cert": "/etc/ssl/certs/server.crt",
"ssl_key": "/etc/ssl/private/server.key",
"allowed_hosts": ["*"],
"cors_origins": ["*"]
}
}
Security Features
Basic Authentication
WebService implements HTTP Basic Authentication. Credentials can be set via:
- Constructor:
basic_auth=("username", "password") - Environment:
SWML_BASIC_AUTH_USERandSWML_BASIC_AUTH_PASS - Config file:
security.basic_authsection - Auto-generated: If not specified, generates random credentials
File Security
Default Blocked Extensions/Files
.env,.git,.gitignore.key,.pem,.crt.pyc,__pycache__.DS_Store,.swp
Path Traversal Protection
WebService prevents access outside designated directories:
# These attempts will be blocked:
# GET /docs/../../../etc/passwd
# GET /docs/./././../config.json
File Size Limits
Default maximum file size is 100MB. Configure with:
service = WebService(max_file_size=50 * 1024 * 1024) # 50MB
Security Headers
Automatically adds security headers to all responses:
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockStrict-Transport-Security(when HTTPS is enabled)
HTTPS/SSL Support
WebService provides multiple ways to enable HTTPS:
Method 1: Environment Variables
# Using file paths
export SWML_SSL_CERT="/path/to/cert.pem"
export SWML_SSL_KEY="/path/to/key.pem"
# Or using inline PEM content
export SWML_SSL_CERT_INLINE="-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKLdQVPy...
-----END CERTIFICATE-----"
export SWML_SSL_KEY_INLINE="-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQE...
-----END PRIVATE KEY-----"
Method 2: Direct Parameters
service = WebService(directories={"/docs": "./docs"})
service.start(
ssl_cert="/path/to/cert.pem",
ssl_key="/path/to/key.pem"
)
# Service available at https://localhost:8002
Method 3: Configuration File
{
"security": {
"ssl_enabled": true,
"ssl_cert": "/etc/ssl/certs/server.crt",
"ssl_key": "/etc/ssl/private/server.key"
}
}
Generating Self-Signed Certificates
For development/testing:
# Generate a self-signed certificate
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"
# Use with WebService
export SWML_SSL_CERT="cert.pem"
export SWML_SSL_KEY="key.pem"
API Endpoints
GET /health
Health check endpoint (no authentication required)
Response:
{
"status": "healthy",
"directories": ["/docs", "/assets"],
"ssl_enabled": false,
"auth_required": true,
"directory_browsing": true
}
GET /
Root endpoint showing available directories
Response: HTML page listing all mounted directories
GET /{route}/{file_path}
Serve files from mounted directories
Parameters:
route: The mounted directory route (e.g.,/docs)file_path: Path to file within the directory
Response:
- File content with appropriate MIME type
- 404 if file not found
- 403 if file type blocked or directory browsing disabled
Usage Examples
Basic File Serving
from signalwire_agents import WebService
# Serve documentation
service = WebService(
directories={
"/docs": "./documentation",
"/api": "./api-specs"
}
)
service.start()
# Files accessible at:
# http://localhost:8002/docs/index.html
# http://localhost:8002/api/swagger.json
With Directory Browsing
service = WebService(
directories={"/files": "./public"},
enable_directory_browsing=True # Allow browsing directories
)
service.start()
# Browse files at: http://localhost:8002/files/
Restricted File Types
# Only serve web assets
service = WebService(
directories={"/web": "./www"},
allowed_extensions=['.html', '.css', '.js', '.png', '.jpg', '.woff2'],
enable_directory_browsing=False
)
Dynamic Directory Management
service = WebService()
# Add directories after initialization
service.add_directory("/docs", "./documentation")
service.add_directory("/reports", "./generated/reports")
# Remove a directory
service.remove_directory("/reports")
service.start()
With Custom Authentication
service = WebService(
directories={"/private": "./sensitive-docs"},
basic_auth=("admin", "super-secret-password")
)
service.start()
HTTPS with Let's Encrypt
# Assuming you have Let's Encrypt certificates
service = WebService(
directories={"/secure": "./secure-files"}
)
service.start(
ssl_cert="/etc/letsencrypt/live/example.com/fullchain.pem",
ssl_key="/etc/letsencrypt/live/example.com/privkey.pem"
)
# Service available at https://example.com:8002
Multi-Environment Configuration
import os
# Development vs Production
if os.getenv("ENVIRONMENT") == "production":
service = WebService(
port=443,
directories={"/": "./dist"},
enable_directory_browsing=False
)
service.start(
host="0.0.0.0",
ssl_cert="/etc/ssl/certs/production.crt",
ssl_key="/etc/ssl/private/production.key"
)
else:
service = WebService(
port=8002,
directories={"/": "./src"},
enable_directory_browsing=True
)
service.start()
Deployment Patterns
Standalone Service
Run WebService as a dedicated static file server:
# web_server.py
from signalwire_agents import WebService
if __name__ == "__main__":
service = WebService(
port=8002,
directories={
"/docs": "/var/www/docs",
"/assets": "/var/www/assets",
"/downloads": "/var/www/downloads"
}
)
service.start()
Alongside AI Agents
Run WebService alongside your AI agents on different ports:
# main.py
from signalwire_agents import AgentBase, WebService
import threading
# Start WebService in background
def run_web_service():
web = WebService(
port=8002,
directories={"/docs": "./agent-docs"}
)
web.start()
# Start web service thread
web_thread = threading.Thread(target=run_web_service, daemon=True)
web_thread.start()
# Run your agent
class MyAgent(AgentBase):
def __init__(self):
super().__init__(name="My Agent")
agent = MyAgent()
agent.serve(port=3000) # Agent on port 3000, WebService on 8002
Docker Deployment
FROM python:3.9-slim
WORKDIR /app
# Install SDK
RUN pip install signalwire-agents
# Copy static files
COPY ./static /app/static
COPY ./web_config.json /app/web_config.json
# Expose port
EXPOSE 8002
# Run WebService
CMD ["python", "-c", "from signalwire_agents import WebService; WebService(config_file='web_config.json').start()"]
Systemd Service
Create /etc/systemd/system/signalwire-web.service:
[Unit]
Description=SignalWire Web Service
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/signalwire
Environment="SWML_SSL_CERT=/etc/ssl/certs/server.crt"
Environment="SWML_SSL_KEY=/etc/ssl/private/server.key"
ExecStart=/usr/bin/python3 -c "from signalwire_agents import WebService; WebService(directories={'/': '/var/www/html'}).start()"
Restart=always
[Install]
WantedBy=multi-user.target
Nginx Reverse Proxy
For production, use Nginx as a reverse proxy:
server {
listen 80;
server_name static.example.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name static.example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
location / {
proxy_pass http://localhost:8002;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
proxy_pass http://localhost:8002;
expires 1h;
add_header Cache-Control "public, immutable";
}
}
}
Best Practices
Security
- Always use HTTPS in production - Protect data in transit
- Change default credentials - Never use auto-generated auth in production
- Restrict file types - Use
allowed_extensionsto whitelist safe files - Disable directory browsing - Turn off in production environments
- Use reverse proxy - Put Nginx/Apache in front for additional security
Performance
- Set appropriate cache headers - WebService adds 1-hour cache by default
- Limit file sizes - Adjust
max_file_sizebased on your needs - Use CDN for static assets - Offload traffic for better performance
- Compress large files - Use gzip/brotli at reverse proxy level
Organization
- Separate content types - Use different routes for different file types
- Version your assets - Include version in path (e.g.,
/assets/v1/) - Use index.html - Provide default files for directories
- Document your structure - Maintain clear directory organization
Troubleshooting
Common Issues
Issue: "FastAPI not available"
# Install FastAPI and uvicorn
pip install fastapi uvicorn
Issue: SSL certificate errors
# Check certificate paths
import os
print(os.path.exists("/path/to/cert.pem")) # Should be True
print(os.path.exists("/path/to/key.pem")) # Should be True
Issue: Permission denied
# Ensure read permissions on directories
chmod -R 755 /path/to/static/files
Issue: Directory not found
# Use absolute paths
import os
service = WebService(
directories={
"/docs": os.path.abspath("./documentation")
}
)
Debug Logging
Enable debug logging to troubleshoot issues:
import logging
logging.basicConfig(level=logging.DEBUG)
service = WebService(directories={"/test": "./test"})
service.start()
API Reference
WebService Class
class WebService:
def __init__(self,
port: int = 8002,
directories: Dict[str, str] = None,
basic_auth: Optional[Tuple[str, str]] = None,
config_file: Optional[str] = None,
enable_directory_browsing: bool = False,
allowed_extensions: Optional[list] = None,
blocked_extensions: Optional[list] = None,
max_file_size: int = 100 * 1024 * 1024,
enable_cors: bool = True)
Parameters
port: Port to bind to (default: 8002)directories: Dictionary mapping URL paths to local directoriesbasic_auth: Tuple of (username, password) for authenticationconfig_file: Path to JSON configuration fileenable_directory_browsing: Allow directory listing (default: False)allowed_extensions: List of allowed file extensionsblocked_extensions: List of blocked file extensionsmax_file_size: Maximum file size in bytes (default: 100MB)enable_cors: Enable CORS headers (default: True)
Methods
start()
def start(self,
host: str = "0.0.0.0",
port: Optional[int] = None,
ssl_cert: Optional[str] = None,
ssl_key: Optional[str] = None)
Start the web service.
add_directory()
def add_directory(self, route: str, directory: str) -> None
Add a new directory to serve.
remove_directory()
def remove_directory(self, route: str) -> None
Remove a directory from being served.
Integration with SignalWire Agents
WebService complements AI agents by providing static file serving:
from signalwire_agents import AgentBase, WebService
class DocumentationAgent(AgentBase):
def __init__(self):
super().__init__(name="Documentation Assistant")
# Reference documentation served by WebService
self.prompt_add_section(
"Documentation",
"User documentation is available at https://example.com:8002/docs/"
)
@self.tool(
"get_doc_link",
description="Get link to a documentation page",
parameters={
"doc_name": {"type": "string", "description": "Name of the documentation page"}
}
)
def get_doc_link(self, args, raw_data):
doc_name = args.get('doc_name')
return SwaigFunctionResult(
f"Documentation available at: https://example.com:8002/docs/{doc_name}.html"
)
# Run both services
if __name__ == "__main__":
# Start WebService for documentation
web = WebService(
port=8002,
directories={"/docs": "./documentation"}
)
# Start agent
agent = DocumentationAgent()
# Run in threads or separate processes
import threading
web_thread = threading.Thread(target=web.start, daemon=True)
web_thread.start()
agent.serve(port=3000)
Summary
WebService provides a secure, configurable static file server that integrates with the SignalWire AI Agents SDK. It follows the same architectural patterns as other SDK services, making it familiar and easy to use while providing configurable security features and flexible deployment options.