-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
184 lines (159 loc) · 5 KB
/
Copy pathserver.js
File metadata and controls
184 lines (159 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import express from 'express';
import compression from 'compression';
import path from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
import { syntheticMarkerMiddleware } from '@swantron/otel-bootstrap';
// Get __dirname equivalent for ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Simple logger for the server
const logger = {
info: (message, context = {}) => {
console.log(`[${new Date().toISOString()}] [INFO] ${message}`, context);
},
warn: (message, context = {}) => {
console.warn(`[${new Date().toISOString()}] [WARN] ${message}`, context);
},
error: (message, context = {}) => {
console.error(`[${new Date().toISOString()}] [ERROR] ${message}`, context);
},
};
const app = express();
const PORT = process.env.PORT || 8080;
app.use(compression());
// Stamp synthetic run ids from watchtron probes onto the active server span so
// the control plane can confirm probe traffic reached this instrumented origin.
app.use(syntheticMarkerMiddleware());
// Security middleware
app.use((req, res, next) => {
// Log suspicious requests
const suspiciousPatterns = [
/\.(tar|gz|zip|rar|bak|backup|sql|db|dat|log)$/i,
/\.(php|asp|jsp|cgi)$/i,
/\.(env|config|ini)$/i,
/admin|wp-|xmlrpc|phpmyadmin/i,
/\.\./,
/\/etc\/|\/proc\/|\/sys\//i,
];
const isSuspicious = suspiciousPatterns.some(pattern =>
pattern.test(req.path)
);
if (isSuspicious) {
logger.warn('Suspicious request detected', {
ip: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
path: req.path,
method: req.method,
referer: req.get('Referer'),
timestamp: new Date().toISOString(),
});
}
next();
});
// Request logging middleware
app.use((req, res, next) => {
const start = Date.now();
// Log the request
logger.info('Incoming request', {
method: req.method,
path: req.path,
ip: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
referer: req.get('Referer'),
query: req.query,
});
// Override res.end to log response
const originalEnd = res.end;
res.end = function (chunk, encoding) {
const duration = Date.now() - start;
logger.info('Request completed', {
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`,
ip: req.ip || req.connection.remoteAddress,
contentLength: res.get('Content-Length') || 0,
});
originalEnd.call(this, chunk, encoding);
};
next();
});
// Health check endpoints for DigitalOcean and monitoring
// Use /api/health to avoid conflict with React Router
app.get('/api/health', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Check if build directory exists
const buildDir = path.join(__dirname, 'build');
if (!existsSync(buildDir)) {
logger.error('Build directory not found', { buildDir });
process.exit(1);
}
// Serve static files. Files under /assets/ are content-hashed by Vite, so
// they can be cached forever; everything else (index.html, manifest, etc.)
// must revalidate so clients always pick up the latest asset hashes.
app.use(
express.static(buildDir, {
setHeaders: (res, filePath) => {
if (filePath.startsWith(path.join(buildDir, 'assets') + path.sep)) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
} else {
res.setHeader('Cache-Control', 'no-cache');
}
},
})
);
// Handle client-side routing (SPA) - catch all non-API routes
app.get(/^(?!\/api).*$/, (req, res) => {
const indexPath = path.join(buildDir, 'index.html');
if (!existsSync(indexPath)) {
logger.error('index.html not found in build directory', { indexPath });
res.status(500).send('Application not built correctly');
return;
}
res.sendFile(indexPath);
});
// Error handling middleware
app.use((err, req, res, next) => {
logger.error('Unhandled error', {
error: err.message,
stack: err.stack,
path: req.path,
method: req.method,
ip: req.ip || req.connection.remoteAddress,
});
res.status(500).send('Internal Server Error');
});
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM received, shutting down gracefully');
process.exit(0);
});
process.on('SIGINT', () => {
logger.info('SIGINT received, shutting down gracefully');
process.exit(0);
});
app
.listen(PORT, () => {
logger.info('Server started', {
port: PORT,
nodeEnv: process.env.NODE_ENV,
timestamp: new Date().toISOString(),
platform: 'Digital Ocean App Platform',
buildpack: 'Node.js',
});
// Log that we're ready to serve requests
logger.info('Application ready to serve requests', {
staticFilesPath: path.join(__dirname, 'build'),
spaFallback: true,
});
})
.on('error', err => {
logger.error('Server failed to start', {
error: err.message,
port: PORT,
code: err.code,
});
process.exit(1);
});