mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-11 01:10:24 -06:00
184 lines
4.3 KiB
CoffeeScript
184 lines
4.3 KiB
CoffeeScript
_ = require("lodash")
|
|
fs = require("fs-extra")
|
|
net = require("net")
|
|
url = require("url")
|
|
https = require("https")
|
|
Promise = require("bluebird")
|
|
semaphore = require("semaphore")
|
|
allowDestroy = require("server-destroy-vvo")
|
|
parse = require("./util/parse")
|
|
|
|
fs = Promise.promisifyAll(fs)
|
|
|
|
sslServers = {}
|
|
sslSemaphores = {}
|
|
|
|
class Server
|
|
constructor: (@_ca, @_port) ->
|
|
@_onError = null
|
|
|
|
connect: (req, socket, head, options = {}) ->
|
|
if not head or head.length is 0
|
|
socket.once "data", (data) =>
|
|
@connect(req, socket, data, options)
|
|
|
|
socket.write "HTTP/1.1 200 OK\r\n"
|
|
|
|
if req.headers["proxy-connection"] is "keep-alive"
|
|
socket.write("Proxy-Connection: keep-alive\r\n")
|
|
socket.write("Connection: keep-alive\r\n")
|
|
|
|
return socket.write("\r\n")
|
|
|
|
else
|
|
if odc = options.onDirectConnection
|
|
## if onDirectConnection return true
|
|
## then dont proxy, just pass this through
|
|
if odc.call(@, req, socket, head) is true
|
|
return @_makeDirectConnection(req, socket, head)
|
|
|
|
socket.pause()
|
|
|
|
@_onServerConnectData(req, socket, head)
|
|
|
|
_onUpgrade: (fn, req, socket, head) ->
|
|
if fn
|
|
fn.call(@, req, socket, head)
|
|
|
|
_onRequest: (fn, req, res) ->
|
|
hostPort = parse.hostAndPort(req.url, req.headers, 443)
|
|
|
|
req.url = url.format({
|
|
protocol: "https:"
|
|
hostname: hostPort.host
|
|
port: hostPort.port
|
|
}) + req.url
|
|
|
|
if fn
|
|
return fn.call(@, req, res)
|
|
|
|
req.pipe(request(req.url))
|
|
.on "error", ->
|
|
res.statusCode = 500
|
|
res.end()
|
|
.pipe(res)
|
|
|
|
_makeDirectConnection: (req, socket, head) ->
|
|
directUrl = url.parse("http://#{req.url}")
|
|
|
|
@_makeConnection(socket, head, directUrl.port, directUrl.hostname)
|
|
|
|
_makeConnection: (socket, head, port, hostname) ->
|
|
cb = ->
|
|
socket.pipe(conn)
|
|
conn.pipe(socket)
|
|
socket.emit("data", head)
|
|
|
|
socket.resume()
|
|
|
|
## compact out hostname when undefined
|
|
args = _.compact([port, hostname, cb])
|
|
|
|
conn = net.connect.apply(net, args)
|
|
|
|
conn.on "error", (err) =>
|
|
if @_onError
|
|
@_onError(err, socket, head, port)
|
|
|
|
_onServerConnectData: (req, socket, head) ->
|
|
firstBytes = head[0]
|
|
|
|
makeConnection = (port) =>
|
|
@_makeConnection(socket, head, port)
|
|
|
|
if firstBytes is 0x16 or firstBytes is 0x80 or firstBytes is 0x00
|
|
{hostname} = url.parse("http://#{req.url}")
|
|
|
|
if sslServer = sslServers[hostname]
|
|
return makeConnection(sslServer.port)
|
|
|
|
if not sem = sslSemaphores[hostname]
|
|
sem = sslSemaphores[hostname] = semaphore(1)
|
|
|
|
sem.take =>
|
|
leave = ->
|
|
process.nextTick ->
|
|
sem.leave()
|
|
|
|
if sslServer = sslServers[hostname]
|
|
leave()
|
|
|
|
return makeConnection(sslServer.port)
|
|
|
|
@_getPortFor(hostname)
|
|
.then (port) ->
|
|
sslServers[hostname] = { port: port }
|
|
|
|
leave()
|
|
|
|
makeConnection(port)
|
|
|
|
else
|
|
makeConnection(@_port)
|
|
|
|
_normalizeKeyAndCert: (certPem, privateKeyPem) ->
|
|
return {
|
|
key: privateKeyPem
|
|
cert: certPem
|
|
}
|
|
|
|
_getCertificatePathsFor: (hostname) ->
|
|
@_ca.getCertificateKeysForHostname(hostname)
|
|
.spread(@_normalizeKeyAndCert)
|
|
|
|
_generateMissingCertificates: (hostname) ->
|
|
@_ca.generateServerCertificateKeys(hostname)
|
|
.spread(@_normalizeKeyAndCert)
|
|
|
|
_getPortFor: (hostname) ->
|
|
@_getCertificatePathsFor(hostname)
|
|
|
|
.catch (err) =>
|
|
@_generateMissingCertificates(hostname)
|
|
|
|
.then (data = {}) =>
|
|
@_sniServer.addContext(hostname, data)
|
|
|
|
return @_sniPort
|
|
|
|
listen: (options = {}) ->
|
|
new Promise (resolve) =>
|
|
@_onError = options.onError
|
|
|
|
@_sniServer = https.createServer({})
|
|
|
|
allowDestroy(@_sniServer)
|
|
|
|
@_sniServer.on "upgrade", @_onUpgrade.bind(@, options.onUpgrade)
|
|
@_sniServer.on "request", @_onRequest.bind(@, options.onRequest)
|
|
@_sniServer.listen =>
|
|
## store the port of our current sniServer
|
|
@_sniPort = @_sniServer.address().port
|
|
|
|
resolve()
|
|
|
|
close: ->
|
|
close = =>
|
|
new Promise (resolve) =>
|
|
@_sniServer.destroy(resolve)
|
|
|
|
close()
|
|
.finally ->
|
|
sslServers = {}
|
|
|
|
module.exports = {
|
|
reset: ->
|
|
sslServers = {}
|
|
|
|
create: (ca, port, options = {}) ->
|
|
srv = new Server(ca, port)
|
|
|
|
srv
|
|
.listen(options)
|
|
.return(srv)
|
|
} |