forked from dimapaloskin/micro-proxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
140 lines (115 loc) · 2.96 KB
/
index.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
const micro = require('micro')
const { resolve, parse, URL } = require('url')
const fetch = require('node-fetch')
const lintRules = require('./lib/lint-rules')
const WebSocket = require('ws')
module.exports = (rules) => {
const lintedRules = lintRules(rules).map(({pathname, pathnameRe, method, dest}) => {
const methods = method ? method.reduce((final, c) => {
final[c.toLowerCase()] = true
return final
}, {}) : null
return {
pathname,
pathnameRegexp: new RegExp(pathnameRe || pathname || '.*'),
dest,
methods
}
})
const getDest = (req) => {
for (const { pathnameRegexp, methods, dest } of lintedRules) {
if (pathnameRegexp.test(req.url) && (!methods || methods[req.method.toLowerCase()])) {
return dest
}
}
}
const server = micro(async (req, res) => {
try {
const dest = getDest(req)
if (!dest) {
res.writeHead(404)
res.end('404 - Not Found')
return
}
await proxyRequest(req, res, dest)
} catch (err) {
console.error(err.stack)
res.end()
}
})
const wss = new WebSocket.Server({ server })
wss.on('connection', (ws, req) => {
const dest = getDest(req)
if (!dest) {
ws.close()
return
}
proxyWs(ws, req, dest)
})
return server
}
function proxyWs (ws, req, dest) {
const destUrlObject = parse(dest)
const newUrl = `ws://${destUrlObject.host}${req.url}`
const destWs = new WebSocket(newUrl)
// util functions
const incomingHandler = (message) => {
destWs.send(message)
}
const outgoingHandler = (message) => {
ws.send(message)
}
const onError = (err) => {
console.error(`Error on proxying url: ${newUrl}`)
console.error(err.stack)
}
const removeListeners = () => {
ws.removeListener('message', incomingHandler)
destWs.removeListener('message', outgoingHandler)
}
// events handling
destWs.on('open', () => {
ws.addListener('message', incomingHandler)
destWs.addListener('message', outgoingHandler)
})
ws.on('close', () => {
destWs.close()
removeListeners()
})
destWs.on('close', () => {
ws.close()
removeListeners()
})
ws.on('error', onError)
destWs.on('error', onError)
}
async function proxyRequest (req, res, dest) {
const newUrl = resolve(dest, req.url)
const url = new URL(dest)
const proxyRes = await fetch(newUrl, {
method: req.method,
headers: {
...req.headers,
host: url.host
},
body: req,
compress: false
})
// Forward status code
res.statusCode = proxyRes.status
// Forward headers
const headers = proxyRes.headers.raw()
for (const key of Object.keys(headers)) {
res.setHeader(key, headers[key])
}
// Stream the proxy response
proxyRes.body.pipe(res)
proxyRes.body.on('error', (err) => {
console.error(`Error on proxying url: ${newUrl}`)
console.error(err.stack)
res.end()
})
req.on('abort', () => {
proxyRes.body.destroy()
})
}