Server Service
The server
service provides HTTP server functionality for Pinstripe applications. It creates and manages HTTP servers, handles incoming requests, parses request bodies (including file uploads with image processing), and integrates with the framework's call handler system.
Interface
server.start(options = {})
server.extractParams(request, baseUrl, limits)
server.parseBody(request, limits)
server.createHash(data)
Key Features
- HTTP Server Creation: Creates HTTP servers with configurable hostname and port
- Request Parameter Extraction: Extracts URL parameters, headers, and body data from HTTP requests
- File Upload Handling: Processes multipart form data with file uploads
- Image Processing: Automatically resizes and optimizes uploaded images using Sharp
- ETags for Caching: Generates SHA1-based ETags for response caching
- Comprehensive Limits: Enforces configurable limits on body size, file size, field counts, etc.
- Error Handling: Provides structured error responses and logging
- Integration: Seamlessly integrates with the call handler system
Core Methods
start(options = {})
Starts an HTTP server with the specified configuration.
Parameters:
options.hostname
(string): Server hostname (default: '127.0.0.1')options.port
(number): Server port (default: 3000)
Features:
- Creates HTTP server using Node.js
http
module - Handles GET, POST, PUT, PATCH requests
- Generates ETags for response caching (returns 304 if not modified)
- Integrates with
callHandler.handleCall()
for request processing - Provides console logging for requests (disabled in test environment)
extractParams(request, baseUrl, limits)
Extracts and normalizes parameters from HTTP requests.
Parameters:
request
: Node.js HTTP request objectbaseUrl
: Base URL for resolving relative URLslimits
: Configuration object with size/count limits
Returns: Object containing:
- URL query parameters (flattened from URLSearchParams)
- Request body parameters (for POST/PUT/PATCH requests)
_request
: Original request object_method
: HTTP method_url
: Parsed URL object_headers
: Request headers object
parseBody(request, limits)
Parses request bodies with support for multiple content types.
Supported Content Types:
application/json
: Parses JSON datamultipart/form-data
: Handles file uploads and form fieldsapplication/x-www-form-urlencoded
: Processes form data- Raw text content
File Upload Features:
- Automatic image processing with Sharp
- Image resizing within configured limits
- TIFF to WebP conversion
- File metadata extraction (filename, MIME type, encoding)
Error Handling:
- Size limit enforcement
- Field count limits
- Structured error reporting in
_bodyErrors
createHash(data)
Generates SHA1-based ETags for HTTP caching.
Parameters:
data
: Data to hash (typically response body)
Returns: ETag string in format "<base64-encoded-sha1>"
Configuration
The server service uses configuration from config.server.limits
:
// Default limits in config service
config.limits.bodySize = 100 * 1024 * 1024; // 100MB max body size
config.limits.rawBodySize = 1024 * 1024; // 1MB raw body display
config.limits.fieldNameSize = 100; // 100 bytes max field name
config.limits.fieldSize = 1024 * 1024; // 1MB max field value
config.limits.fields = Infinity; // Unlimited field count
config.limits.fileSize = 10 * 1024 * 1024; // 10MB max file size
config.limits.files = Infinity; // Unlimited file count
config.limits.parts = Infinity; // Unlimited multipart parts
config.limits.headerPairs = 2000; // Max header pairs
config.limits.imageWidth = 1024; // Max image width (px)
config.limits.imageHeight = 1024; // Max image height (px)
Examples
Basic Server Startup
// In a command (e.g., start-server command)
export default {
run(){
const { host = '127.0.0.1:3000' } = this.params;
const [hostname, port] = host.split(':');
this.server.start({
hostname: hostname || '127.0.0.1',
port: parseInt(port) || 3000
});
}
}
Custom Server Configuration
// pinstripe.config.js
export default {
server: {
limits: {
bodySize: 50 * 1024 * 1024, // 50MB max body
fileSize: 5 * 1024 * 1024, // 5MB max file
imageWidth: 800, // 800px max width
imageHeight: 600, // 600px max height
fields: 100 // Max 100 form fields
}
}
};
Manual Parameter Extraction
// Custom request handling
export default {
async handleCustomRequest(httpRequest){
const baseUrl = new URL('http://127.0.0.1:3000/');
const limits = await this.config.server.limits;
const params = await this.server.extractParams(
httpRequest,
baseUrl,
limits
);
// params contains:
// - URL query parameters
// - Body data (JSON, form fields, files)
// - _method, _url, _headers, _request
return params;
}
}
File Upload Processing
// View handling file uploads
export default {
async render(){
const { profileImage, username } = this.params;
if(profileImage) {
// profileImage is automatically processed:
// {
// filename: 'avatar.jpg',
// mimeType: 'image/jpeg',
// encoding: '7bit',
// data: Buffer // Resized image data
// }
await this.saveProfileImage(profileImage);
}
return this.renderView('upload-success');
}
}
Custom ETag Generation
export default {
async render(){
const data = await this.fetchData();
const etag = this.server.createHash(data);
// Check client cache
if(this.params._headers['if-none-match'] === etag) {
return [304, { etag }, []]; // Not modified
}
const body = this.renderData(data);
return [200, { etag, 'content-type': 'application/json' }, [body]];
}
}
Error Handling with Body Errors
export default {
async render(){
const { _bodyErrors, ...params } = this.params;
if(_bodyErrors) {
// Handle upload errors
if(_bodyErrors.general) {
return this.renderError(`Upload failed: ${_bodyErrors.general}`);
}
// Handle field-specific errors
for(const [fieldName, error] of Object.entries(_bodyErrors)) {
console.error(`Field ${fieldName}: ${error}`);
}
}
return this.processUpload(params);
}
}
Integration with Static Site Generation
// Static site generator using server extraction
export default {
async generateStaticFiles(){
const paths = await this.getAllPaths();
for(const path of paths) {
// Use server.extractParams-like functionality
const url = new URL(path, 'http://127.0.0.1/');
const params = { _url: url };
const [status, headers, data] = await this.callHandler.handleCall(params);
if(status === 200) {
await this.writeStaticFile(path, data);
}
}
}
}
Integration with Call Handler
The server service works closely with the call handler:
// Inside server.start()
const params = await this.extractParams(request, baseUrl, limits);
const [status, headers, body] = await this.callHandler.handleCall(params);
// Generate ETag for caching
const etag = this.createHash(body);
// Handle 304 Not Modified responses
if(params._headers['if-none-match'] == etag){
response.statusCode = 304;
response.end();
return;
}
Image Processing Features
The server automatically processes uploaded images:
- Format Support: PNG, JPEG, GIF, WebP, AVIF, TIFF
- Automatic Resizing: Respects
imageWidth
andimageHeight
limits - Format Conversion: TIFF files automatically converted to WebP
- Optimization: Uses Sharp for efficient image processing
- Metadata Preservation: Maintains filename and encoding information
Performance Considerations
- Memory Management: Large files are processed in chunks
- Size Limits: Configurable limits prevent memory exhaustion
- ETag Caching: Reduces redundant data transfer
- Image Optimization: Automatic resizing reduces bandwidth
- Error Recovery: Graceful handling of malformed requests
Security Features
- Size Limits: Prevents DoS attacks via large uploads
- Field Limits: Prevents form field abuse
- File Type Validation: Only processes supported image formats
- Error Sanitization: Structured error responses without sensitive data
- Request Validation: Validates content types and structure
Error Handling
The server provides comprehensive error handling:
// Server-level error handling
try {
const [status, headers, body] = await this.callHandler.handleCall(params);
// ... response handling
} catch (e) {
response.statusCode = 500;
response.setHeader('content-type', 'text/plain');
const error = (e.stack || e).toString();
console.error(error);
response.end(error);
}
// Body parsing errors are included in params
const { _bodyErrors } = params;
if(_bodyErrors.general) {
// Handle general errors (size limits, etc.)
}
Related Services
- config: Provides server configuration and limits
- callHandler: Processes extracted parameters into responses
- bot: Often started alongside server for development
- serviceWorker: Uses similar parameter extraction for client-side requests
Technical Implementation
The server service is implemented using:
- Node.js http module: Core HTTP server functionality
- Busboy: Multipart form data parsing
- Sharp: Image processing and optimization
- crypto: SHA1 hash generation for ETags
- URL/URLSearchParams: Request parsing and normalization
Best Practices
- Configure Limits: Set appropriate limits based on your application needs
- Handle Errors: Always check for
_bodyErrors
in upload scenarios - Use ETags: Leverage ETag caching for better performance
- Image Optimization: Configure appropriate image size limits
- Security: Be mindful of file upload security implications
- Testing: Test with various content types and edge cases
- Monitoring: Monitor server logs for errors and performance issues
Common Use Cases
- Web Application Server: Primary HTTP server for web applications
- API Server: RESTful API with JSON request/response handling
- File Upload Server: Handling image and document uploads
- Static Site Generation: Server functionality for build-time rendering
- Development Server: Local development with hot reloading
- Testing: HTTP server for automated testing scenarios