forked from jackiealex/nobone
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproxy.coffee
196 lines (170 loc) · 4.77 KB
/
proxy.coffee
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
196
###*
* For test, page injection development.
* A cross platform Fiddler alternative.
* Most time used with SwitchySharp.
* @extends {http-proxy.ProxyServer}
###
Overview = 'proxy'
_ = require 'lodash'
kit = require '../kit'
http = require 'http'
###*
* Create a Proxy instance.
* @param {Object} opts Defaults: `{ }`
* @return {Proxy} For more, see [node-http-proxy][node-http-proxy]
* [node-http-proxy]: https://github.com/nodejitsu/node-http-proxy
###
proxy = (opts = {}) ->
_.defaults opts, {}
self = {}
self.agent = new http.Agent
###*
* Use it to proxy one url to another.
* @param {http.IncomingMessage} req
* @param {http.ServerResponse} res
* @param {String} url The target url forced to. Optional.
* Such as force 'http://test.com/a' to 'http://test.com/b',
* force 'http://test.com/a' to 'http://other.com/a',
* force 'http://test.com' to 'other.com'.
* @param {Object} opts Other options. Default:
* ```coffeescript
* {
* bps: null # Limit the bandwidth byte per second.
* global_bps: false # if the bps is the global bps.
* agent: custom_http_agent
* }
* ```
* @param {Function} err Custom error handler.
* @return {Promise}
###
self.url = (req, res, url, opts = {}, err) ->
if _.isObject url
opts = url
url = undefined
_.defaults opts, {
bps: null
global_bps: false
agent: self.agent
}
if not url
url = req.url
if _.isObject url
url = kit.url.format url
else
sep_index = url.indexOf('/')
switch sep_index
when 0
url = req.headers.host + url
when -1
url = 'http://' + url + req.url
error = err or (e) ->
kit.log e.toString() + ' -> ' + req.url.red
# Normalize the headers
headers = {}
for k, v of req.headers
nk = k.replace(/(\w)(\w*)/g, (m, p1, p2) -> p1.toUpperCase() + p2)
headers[nk] = v
stream = if opts.bps == null
res
else
if opts.global_bps
sock_num = _.keys(opts.agent.sockets).length
bps = opts.bps / (sock_num + 1)
else
bps = opts.bps
throttle = new kit.require('throttle')(bps)
throttle.pipe res
throttle
p = kit.request {
method: req.method
url
headers
req_pipe: req
res_pipe: stream
auto_unzip: false
agent: opts.agent
}
p.req.on 'response', (proxy_res) ->
res.writeHead proxy_res.statusCode, proxy_res.headers
p.catch error
###*
* Http CONNECT method tunneling proxy helper.
* Most times used with https proxing.
* @param {http.IncomingMessage} req
* @param {net.Socket} sock
* @param {Buffer} head
* @param {String} host The host force to. It's optional.
* @param {Int} port The port force to. It's optional.
* @param {Function} err Custom error handler.
* @example
* ```coffeescript
* nobone = require 'nobone'
* { proxy, service } = nobone { proxy:{}, service: {} }
*
* # Directly connect to the original site.
* service.server.on 'connect', proxy.connect
* ```
###
self.connect = (req, sock, head, host, port, err) ->
net = kit.require 'net'
h = host or req.headers.host
p = port or req.url.match(/:(\d+)$/)[1] or 443
psock = new net.Socket
psock.connect p, h, ->
psock.write head
sock.write "
HTTP/#{req.httpVersion} 200 Connection established\r\n\r\n
"
sock.pipe psock
psock.pipe sock
error = err or (err, socket) ->
kit.log err.toString() + ' -> ' + req.url.red
socket.end()
sock.on 'error', (err) ->
error err, sock
psock.on 'error', (err) ->
error err, psock
###*
* A pac helper.
* @param {String} curr_host The current host for proxy server. It's optional.
* @param {Function} rule_handler Your custom pac rules.
* It gives you three helpers.
* ```coffeescript
* url # The current client request url.
* host # The host name derived from the url.
* curr_host = 'PROXY host:port;' # Nobone server host address.
* direct = "DIRECT;"
* match = (pattern) -> # A function use shExpMatch to match your url.
* proxy = (target) -> # return 'PROXY target;'.
* ```
* @return {Function} Express Middleware.
###
self.pac = (curr_host, rule_handler) ->
if _.isFunction curr_host
rule_handler = curr_host
curr_host = null
(req, res, next) ->
addr = req.socket.address()
curr_host ?= "#{addr.address}:#{addr.port}"
url = kit.url.parse(req.url)
url.host ?= req.headers.host
kit.log url
if url.host != curr_host
return next()
pac_str = """
FindProxyForURL = function (url, host) {
var curr_host = "PROXY #{curr_host};";
var direct = "DIRECT;";
var match = function (pattern) {
return shExpMatch(url, pattern);
};
var proxy = function (target) {
return 'PROXY ' + target + ';';
};
return (#{rule_handler.toString()})();
}
"""
res.set 'Content-Type', 'application/x-ns-proxy-autoconfig'
res.send pac_str
return self
module.exports = proxy