-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.js
186 lines (161 loc) · 5.77 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
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
import { EventEmitter } from 'events'
import util from './lib/util.js'
import url from 'url'
import _ from 'koa-route'
import HttpsProxyAgent from 'https-proxy-agent'
import HttpProxyAgent from 'http-proxy-agent'
import http from 'http'
import https from 'https'
class Rewrite extends EventEmitter {
description () {
return 'URL Rewriting. Use to re-route requests to local or remote resources.'
}
optionDefinitions () {
return [
{
name: 'rewrite',
alias: 'r',
type: String,
multiple: true,
typeLabel: '{underline expression} ...',
description: "A list of URL rewrite rules. For each rule, separate the 'from' and 'to' routes with '->'. Whitespace surrounding the routes is ignored. E.g. '/from -> /to'."
},
{
name: 'rewrite.keep-secure-attr',
type: Boolean,
description: 'When local-web-server is running in plain, insecure HTTP mode (not HTTPS or HTTP2), stripping the `secure` attribute from remote, rewrite-target cookies is the default behaviour. Set this flag to leave remote `secure` cookies untouched - this may prevent cookies functioning correctly when your server is plain HTTP.'
}
]
}
middleware (options, lws) {
const rules = util.parseRewriteRules(options.rewrite)
if (rules.length) {
this.emit('verbose', 'middleware.rewrite.config', { rewrite: rules })
/* return one middleware per defined rewrite rule */
return rules.map(rule => {
if (rule.to) {
/* `to` address is remote if the url specifies a host */
if (url.parse(rule.to).host) {
return _.all(rule.from, proxyRequest(rule, this, lws))
} else {
const rmw = rewrite(rule.from, rule.to, this)
return rmw
}
}
})
}
}
}
function proxyRequest (route, mw, lws) {
let id = 1
let httpProxyAgent, httpsProxyAgent
const httpProxy = process.env.http_proxy
if (httpProxy) {
httpsProxyAgent = new HttpsProxyAgent(httpProxy)
httpProxyAgent = new HttpProxyAgent(httpProxy)
}
return function proxyMiddleware (ctx) {
return new Promise((resolve, reject) => {
const isHttp2 = ctx.req.httpVersion === '2.0'
ctx.state.id = id++
/* disable Koa response mechanism, create and send response manually */
ctx.respond = false
/* get remote URL */
const remoteUrl = util.getTargetUrl(route.from, route.to, ctx.url)
/* info about this rewrite */
const rewrite = {
id: ctx.state.id,
from: ctx.url,
to: remoteUrl
}
/* if lws-request-monitor added a `requestId`, include that in the verbose output */
if (typeof ctx.req.requestId === 'number') {
rewrite.requestId = ctx.req.requestId
}
const reqInfo = {
rewrite,
method: ctx.request.method,
headers: ctx.request.headers
}
/* ensure host header is set */
reqInfo.headers.host = url.parse(reqInfo.rewrite.to).host
/* remove HTTP2 request headers */
if (isHttp2) {
for (const prop of Object.keys(ctx.request.headers)) {
if (prop.substr(0, 1) === ':') {
delete reqInfo.headers[prop]
}
}
}
reqInfo.headers.via = '1.1 lws-rewrite'
util.removeHopSpecificHeaders(reqInfo.headers)
let transport
const remoteReqOptions = url.parse(reqInfo.rewrite.to)
remoteReqOptions.method = reqInfo.method
remoteReqOptions.headers = reqInfo.headers
remoteReqOptions.rejectUnauthorized = false
/* emit verbose info */
mw.emit('verbose', 'middleware.rewrite.remote.request', reqInfo)
const protocol = remoteReqOptions.protocol
if (protocol === 'http:') {
transport = http
remoteReqOptions.agent = httpProxyAgent
} else if (protocol === 'https:') {
transport = https
remoteReqOptions.agent = httpsProxyAgent
} else {
return reject(new Error('Protocol missing from request: ' + reqInfo.rewrite.to))
}
const remoteReq = transport.request(remoteReqOptions, (remoteRes) => {
remoteRes.headers.via = remoteRes.headers.via
? `${remoteRes.headers.via}, 1.1 lws-rewrite`
: '1.1 lws-rewrite'
mw.emit('verbose', 'middleware.rewrite.remote.response', {
rewrite,
status: remoteRes.statusCode,
headers: remoteRes.headers
})
util.removeHopSpecificHeaders(remoteRes.headers)
/* On insecure connections, remove `secure` attribute from remote cookies */
const setCookies = remoteRes.headers['set-cookie']
if (!ctx.req.socket.encrypted && !lws.config.rewriteKeepSecureAttr && setCookies && setCookies.length) {
const cookies = setCookies.map(c => {
let result = util.removeCookieAttribute(c, 'secure')
if (/samesite=none/.test(result)) {
result = util.removeCookieAttribute(result, 'samesite=none')
}
return result
})
remoteRes.headers['set-cookie'] = cookies
}
ctx.res.writeHead(remoteRes.statusCode, remoteRes.headers)
remoteRes.pipe(ctx.res)
resolve()
})
if (ctx.request.rawBody) {
remoteReq.end(ctx.request.rawBody)
} else {
ctx.req.pipe(remoteReq)
}
remoteReq.on('error', reject)
})
}
}
function rewrite (from, to, mw) {
return async function (ctx, next) {
const targetUrl = util.getTargetUrl(from, to, ctx.url)
if (ctx.url !== targetUrl) {
const initialUrl = ctx.url
ctx.url = targetUrl
mw.emit('verbose', 'middleware.rewrite.local', {
from: initialUrl,
to: ctx.url
})
await next()
ctx.url = initialUrl
} else {
await next()
}
}
}
export default Rewrite