-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcaching-proxy.js
More file actions
133 lines (119 loc) · 3.51 KB
/
Copy pathcaching-proxy.js
File metadata and controls
133 lines (119 loc) · 3.51 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
import express from 'express';
import NodeCache from 'node-cache';
import axios from 'axios';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
// Parse command line arguments
const {
port,
origin: originUrl,
ttl = 3600,
clearCache
} = yargs(hideBin(process.argv))
.option('port', {
alias: 'p',
description: 'Port to run the proxy server on',
type: 'number',
})
.option('origin', {
alias: 'o',
description: 'Origin server URL',
type: 'string',
})
.option('ttl', {
alias: 't',
description: 'Cache TTL in seconds',
type: 'number',
default: 3600,
})
.option('clear-cache', {
description: 'Clear the cache and exit',
type: 'boolean',
})
.help()
.argv;
// Initialize cache with configured TTL
const cache = new NodeCache({ stdTTL: ttl });
const app = express();
// If --clear-cache is used, clear and exit
if (clearCache) {
cache.flushAll();
console.log('Cache cleared successfully');
process.exit(0);
}
// Ensure required arguments are provided
if (!port || !originUrl) {
console.error('Error: Both --port and --origin are required when not using --clear-cache');
process.exit(1);
}
// Remove trailing slash from origin if present
const origin = originUrl.replace(/\/$/, '');
// Attach body-parsing middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.raw({ type: '*/*' }));
// Handle incoming requests
app.use(async (req, res) => {
const targetUrl = `${origin}${req.url}`;
const cacheKey = `${req.method}:${targetUrl}`;
try {
// Check cache
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
console.log(`Cache HIT: ${req.method} ${targetUrl}`);
res.set('X-Cache', 'HIT');
Object.entries(cachedResponse.headers).forEach(([key, value]) => {
if (!['transfer-encoding', 'connection'].includes(key.toLowerCase())) {
res.set(key, value);
}
});
return res.status(cachedResponse.status).send(cachedResponse.data);
}
// If no cache, forward request to origin
const response = await axios({
method: req.method,
url: targetUrl,
headers: {
...req.headers,
host: new URL(origin).host, // Override Host header
},
data: req.body,
responseType: 'arraybuffer', // Support binary data
validateStatus: false, // Prevent Axios from throwing on non-2xx
});
console.log(`Cache MISS: ${req.method} ${targetUrl}`);
// Cache the response
cache.set(cacheKey, {
data: response.data,
status: response.status,
headers: response.headers,
});
// Send response to client
res.set('X-Cache', 'MISS');
Object.entries(response.headers).forEach(([key, value]) => {
if (!['transfer-encoding', 'connection'].includes(key.toLowerCase())) {
res.set(key, value);
}
});
res.status(response.status).send(response.data);
} catch (error) {
console.error(`Proxy error for ${req.method} ${targetUrl}:`, error.message);
res.status(502).json({
error: 'Bad Gateway',
message: error.message || 'Error forwarding request to origin server',
});
}
});
// Start the server
const server = app.listen(port, () => {
console.log(`Proxy server running on port ${port}`);
console.log(`Forwarding requests to ${origin}`);
});
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nShutting down server...');
server.close(() => {
console.log('Server stopped');
process.exit(0);
});
});