2017-12-01 21:30:49 +08:00
'use strict'
2014-08-11 16:43:14 +08:00
//manage https servers
2017-12-01 21:30:49 +08:00
const async = require ( 'async' ) ,
https = require ( 'https' ) ,
tls = require ( 'tls' ) ,
crypto = require ( 'crypto' ) ,
color = require ( 'colorful' ) ,
certMgr = require ( './certMgr' ) ,
logUtil = require ( './log' ) ,
util = require ( './util' ) ,
co = require ( 'co' ) ,
constants = require ( 'constants' ) ,
asyncTask = require ( 'async-task-mgr' ) ;
const createSecureContext = tls . createSecureContext || crypto . createSecureContext ;
2015-02-10 15:30:39 +08:00
2015-02-10 12:03:21 +08:00
//using sni to avoid multiple ports
2017-12-01 21:30:49 +08:00
function SNIPrepareCert ( serverName , SNICallback ) {
let keyContent ,
crtContent ,
ctx ;
async . series ( [
( callback ) => {
certMgr . getCertificate ( serverName , ( err , key , crt ) => {
if ( err ) {
callback ( err ) ;
} else {
keyContent = key ;
crtContent = crt ;
callback ( ) ;
2014-08-11 16:43:14 +08:00
}
2017-12-01 21:30:49 +08:00
} ) ;
} ,
( callback ) => {
try {
ctx = createSecureContext ( {
key : keyContent ,
cert : crtContent
} ) ;
callback ( ) ;
} catch ( e ) {
callback ( e ) ;
}
}
] , ( err ) => {
if ( ! err ) {
const tipText = 'proxy server for __NAME established' . replace ( '__NAME' , serverName ) ;
logUtil . printLog ( color . yellow ( color . bold ( '[internal https]' ) ) + color . yellow ( tipText ) ) ;
SNICallback ( null , ctx ) ;
} else {
logUtil . printLog ( 'err occurred when prepare certs for SNI - ' + err , logUtil . T _ERR ) ;
logUtil . printLog ( 'err occurred when prepare certs for SNI - ' + err . stack , logUtil . T _ERR ) ;
}
} ) ;
2015-02-10 12:03:21 +08:00
}
2014-08-11 16:43:14 +08:00
2015-02-10 12:03:21 +08:00
//config.port - port to start https server
//config.handler - request handler
2014-11-28 15:52:17 +08:00
2014-08-11 16:43:14 +08:00
2017-12-01 21:30:49 +08:00
/ * *
* Create an https server
*
* @ param { object } config
* @ param { number } config . port
* @ param { function } config . handler
* /
function createHttpsServer ( config ) {
if ( ! config || ! config . port || ! config . handler ) {
throw ( new Error ( 'please assign a port' ) ) ;
}
return new Promise ( ( resolve ) => {
certMgr . getCertificate ( 'anyproxy_internal_https_server' , ( err , keyContent , crtContent ) => {
const server = https . createServer ( {
secureOptions : constants . SSL _OP _NO _SSLv3 || constants . SSL _OP _NO _TLSv1 ,
SNICallback : SNIPrepareCert ,
key : keyContent ,
cert : crtContent
} , config . handler ) . listen ( config . port ) ;
resolve ( server ) ;
2015-02-10 15:30:39 +08:00
} ) ;
2017-12-01 21:30:49 +08:00
} ) ;
}
/ * *
* create an https server that serving on IP address
* @ param @ required { object } config
* @ param @ required { string } config . ip the IP address of the server
* @ param @ required { number } config . port the port to listen on
* @ param @ required { function } handler the handler of each connect
* /
function createIPHttpsServer ( config ) {
if ( ! config || ! config . port || ! config . handler ) {
throw ( new Error ( 'please assign a port' ) ) ;
}
if ( ! config . ip ) {
throw ( new Error ( 'please assign an IP to create the https server' ) ) ;
}
return new Promise ( ( resolve ) => {
certMgr . getCertificate ( config . ip , ( err , keyContent , crtContent ) => {
const server = https . createServer ( {
secureOptions : constants . SSL _OP _NO _SSLv3 || constants . SSL _OP _NO _TLSv1 ,
key : keyContent ,
cert : crtContent
} , config . handler ) . listen ( config . port ) ;
2015-02-10 15:30:39 +08:00
2017-12-01 21:30:49 +08:00
resolve ( server ) ;
} ) ;
} ) ;
2014-08-11 16:43:14 +08:00
}
2017-12-01 21:30:49 +08:00
/ * *
*
*
* @ class httpsServerMgr
* @ param { object } config
* @ param { function } config . handler handler to deal https request
*
* /
class httpsServerMgr {
constructor ( config ) {
if ( ! config || ! config . handler ) {
throw new Error ( 'handler is required' ) ;
}
this . instanceDefaultHost = '127.0.0.1' ;
this . httpsAsyncTask = new asyncTask ( ) ;
this . handler = config . handler ;
}
getSharedHttpsServer ( hostname ) {
// ip address will have a unique name
const finalHost = util . isIpDomain ( hostname ) ? hostname : this . instanceDefaultHost ;
const self = this ;
function prepareServer ( callback ) {
let instancePort ;
co ( util . getFreePort )
. then ( co . wrap ( function * ( port ) {
instancePort = port ;
let httpsServer = null ;
// if ip address passed in, will create an IP http server
if ( util . isIpDomain ( hostname ) ) {
httpsServer = yield createIPHttpsServer ( {
ip : hostname ,
port ,
handler : self . handler
} ) ;
} else {
httpsServer = yield createHttpsServer ( {
port ,
handler : self . handler
} ) ;
}
httpsServer . on ( 'upgrade' , ( req , socket , head ) => {
const reqHost = req . headers . host || 'unknown host' ;
logUtil . printLog ( ` wss:// is not supported when intercepting https. This request will be closed by AnyProxy. You may either exclude this domain in your rule file, or stop all https intercepting. ( ${ reqHost } ) ` , logUtil . T _ERR ) ;
socket . end ( ) ;
} ) ;
const result = {
host : finalHost ,
port : instancePort ,
} ;
callback ( null , result ) ;
return result ;
} ) )
. catch ( e => {
callback ( e ) ;
} ) ;
}
return new Promise ( ( resolve , reject ) => {
// each ip address will gain a unit task name,
// while the domain address will share a common task name
self . httpsAsyncTask . addTask ( ` createHttpsServer- ${ finalHost } ` , prepareServer , ( error , serverInfo ) => {
if ( error ) {
reject ( error ) ;
} else {
resolve ( serverInfo ) ;
}
} ) ;
} ) ;
}
}
2014-08-11 16:43:14 +08:00
2017-12-01 21:30:49 +08:00
module . exports = httpsServerMgr ;