-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.js
195 lines (169 loc) · 6.21 KB
/
server.js
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
185
186
187
188
189
190
191
192
193
194
195
require('dotenv').config();
const express = require('express');
const MBTiles = require('@mapbox/mbtiles');
const path = require('path');
const cors = require('cors');
const helmet = require('helmet');
const winston = require('winston');
const winstonDailyRotateFile = require('winston-daily-rotate-file'); // Import the log rotation transport
const fs = require('fs');
const app = express();
const port = process.env.PORT || 8000;
// Set up CORS
const allowedOrigins = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(',') : ['*'];
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.includes('*') || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORS not allowed'), false);
}
},
methods: 'GET,HEAD,OPTIONS',
optionsSuccessStatus: 204
};
app.use(cors(corsOptions));
app.use(helmet());
// Configure winston for logging
const logDir = process.env.LOG_DIR ? path.resolve(process.env.LOG_DIR) : path.join(__dirname, 'logs'); // Use .env value or default to './logs'
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir); // Create log directory if it doesn't exist
}
const logger = winston.createLogger({
level: 'warn', // show only warn, error or crtitical in command line
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp(),
winston.format.simple()
),
transports: [
// Console transport
new winston.transports.Console(),
// Daily rotate file transport
new winstonDailyRotateFile({
dirname: logDir,
filename: 'server-%DATE%.log',
datePattern: 'YYYY-MM-DD', // Rotate daily
maxSize: '20m', // Max size of log file before rotation
maxFiles: '14d', // Keep logs for 14 days
zippedArchive: true, // Compress older logs
level: 'info' // log everything in the log file
})
]
});
// Directory where MBTiles files are stored
const mbtilesDir = process.env.MBTILES_DIR ? path.resolve(process.env.MBTILES_DIR) : path.join(__dirname, 'tiles'); // Use .env value or default to './tiles'
let tileServers = {};
// Dynamically load all MBTiles files in the directory
fs.readdir(mbtilesDir, (err, files) => {
if (err) {
logger.error('Error reading MBTiles directory:', err);
process.exit(1);
}
files.forEach((file) => {
if (file.endsWith('.mbtiles')) {
const mbtilesPath = path.join(mbtilesDir, file);
new MBTiles(`${mbtilesPath}?mode=ro`, (err, mbtiles) => {
if (err) {
logger.error(`Error loading MBTiles file ${file}:`, err);
return;
}
tileServers[file] = mbtiles;
logger.info(`MBTiles file ${file} loaded successfully`);
});
}
});
});
// Health check endpoint
app.get('/health', (req, res) => {
if (Object.keys(tileServers).length > 0) {
logger.info('Health check passed');
res.status(200).send('OK');
} else {
logger.warn('Health check failed: Tile server not initialized');
res.status(503).send('Tile server not initialized');
}
});
// Serve tiles from the dynamically loaded MBTiles files
app.get('/tiles/:file/:z/:x/:y.mvt', (req, res) => {
const { file, z, x, y } = req.params;
const tileServer = tileServers[`${file}.mbtiles`];
if (!tileServer) {
logger.warn(`Tile file ${file} not found for z:${z}, x:${x}, y:${y}`);
return res.status(404).send('Tile file not found');
}
logger.info(`Serving tile from ${file} at z:${z}, x:${x}, y:${y}`);
tileServer.getTile(z, x, y, (err, tile, headers) => {
if (err) {
if (err.message.includes('Tile does not exist')) {
logger.info(`Tile does not exist: ${file} at z:${z}, x:${x}, y:${y}`);
res.status(204).end();
} else {
logger.error('Error serving tile:', err);
res.status(500).send('Error loading tile');
}
return;
}
// Set comprehensive response headers
res.set({
'Content-Type': 'application/x-protobuf',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'public, max-age=3600',
'X-Content-Type-Options': 'nosniff'
});
if (headers) {
for (const [key, value] of Object.entries(headers)) {
res.set(key, value);
}
}
res.send(tile);
});
});
// Serve tile metadata from the dynamically loaded MBTiles files
app.get('/metadata/:file', (req, res) => {
const { file } = req.params;
const tileServer = tileServers[`${file}.mbtiles`];
if (!tileServer) {
logger.warn(`Tile file ${file} not found for metadata request`);
return res.status(404).send('Tile file not found');
}
logger.info(`Serving metadata for ${file}`);
tileServer.getInfo((err, info) => {
if (err) {
logger.error('Error loading metadata:', err);
res.status(500).send('Error loading metadata');
return;
}
res.json(info);
});
});
// Error handling middleware for async errors
app.use((err, req, res, next) => {
logger.error('Server error:', err);
res.status(500).send('Internal Server Error');
});
// Graceful shutdown handling
const gracefulShutdown = () => {
logger.info('Received shutdown signal. Closing server...');
server.close(() => {
logger.info('HTTP server closed');
Object.values(tileServers).forEach((tileServer) => {
tileServer.close(() => {
logger.info('Closed tile server connection');
});
});
process.exit(0);
});
// Force close if graceful shutdown fails
setTimeout(() => {
logger.error('Could not close connections in time, forcefully shutting down');
process.exit(1);
}, 10000);
};
// Attach shutdown handlers
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
// Start the server
const server = app.listen(port, () => {
logger.info(`Tile server running at http://localhost:${port}`);
});