The WebService class provides static file serving capabilities for the SignalWire SDK. It follows the same architectural pattern as SearchService, allowing it to run as a standalone service or alongside your AI agents.
- Overview
- Installation
- Quick Start
- Configuration
- Security Features
- HTTPS/SSL Support
- API Endpoints
- Usage Examples
- Deployment Patterns
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
- 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
WebService is included in the core SignalWire SDK:
pip install signalwire-sdkfrom signalwire 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)WebService can be configured through multiple methods (in order of priority):
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
)# 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"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": ["*"]
}
}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
.env,.git,.gitignore.key,.pem,.crt.pyc,__pycache__.DS_Store,.swp
WebService prevents access outside designated directories:
# These attempts will be blocked:
# GET /docs/../../../etc/passwd
# GET /docs/./././../config.jsonDefault maximum file size is 100MB. Configure with:
service = WebService(max_file_size=50 * 1024 * 1024) # 50MBAutomatically 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)
WebService provides multiple ways to enable HTTPS:
# 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-----"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{
"security": {
"ssl_enabled": true,
"ssl_cert": "/etc/ssl/certs/server.crt",
"ssl_key": "/etc/ssl/private/server.key"
}
}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"Health check endpoint (no authentication required)
Response:
{
"status": "healthy",
"directories": ["/docs", "/assets"],
"ssl_enabled": false,
"auth_required": true,
"directory_browsing": true
}Root endpoint showing available directories
Response: HTML page listing all mounted directories
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
from signalwire 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.jsonservice = WebService(
directories={"/files": "./public"},
enable_directory_browsing=True # Allow browsing directories
)
service.start()
# Browse files at: http://localhost:8002/files/# Only serve web assets
service = WebService(
directories={"/web": "./www"},
allowed_extensions=['.html', '.css', '.js', '.png', '.jpg', '.woff2'],
enable_directory_browsing=False
)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()service = WebService(
directories={"/private": "./sensitive-docs"},
basic_auth=("admin", "super-secret-password")
)
service.start()# 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:8002import 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()Run WebService as a dedicated static file server:
# web_server.py
from signalwire import WebService
if __name__ == "__main__":
service = WebService(
port=8002,
directories={
"/docs": "/var/www/docs",
"/assets": "/var/www/assets",
"/downloads": "/var/www/downloads"
}
)
service.start()Run WebService alongside your AI agents on different ports:
# main.py
from signalwire 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 8002FROM python:3.9-slim
WORKDIR /app
# Install SDK
RUN pip install signalwire-sdk
# 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 import WebService; WebService(config_file='web_config.json').start()"]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 import WebService; WebService(directories={'/': '/var/www/html'}).start()"
Restart=always
[Install]
WantedBy=multi-user.targetFor 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";
}
}
}- 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
- 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
- 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
Issue: "FastAPI not available"
# Install FastAPI and uvicorn
pip install fastapi uvicornIssue: 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 TrueIssue: Permission denied
# Ensure read permissions on directories
chmod -R 755 /path/to/static/filesIssue: Directory not found
# Use absolute paths
import os
service = WebService(
directories={
"/docs": os.path.abspath("./documentation")
}
)Enable debug logging to troubleshoot issues:
import logging
logging.basicConfig(level=logging.DEBUG)
service = WebService(directories={"/test": "./test"})
service.start()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)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)
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.
def add_directory(self, route: str, directory: str) -> NoneAdd a new directory to serve.
def remove_directory(self, route: str) -> NoneRemove a directory from being served.
WebService complements AI agents by providing static file serving:
from signalwire 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 FunctionResult(
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)WebService provides a secure, configurable static file server that integrates with the SignalWire 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.