2016-08-15 17:48:47 +08:00
const Koa = require ( 'koa' ) ;
const KoaRouter = require ( 'koa-router' ) ;
const koaBody = require ( 'koa-body' ) ;
const send = require ( 'koa-send' ) ;
const path = require ( 'path' ) ;
const https = require ( 'https' ) ;
2017-12-01 21:30:49 +08:00
const certMgr = require ( '../../lib/certMgr' ) ;
2016-08-15 17:48:47 +08:00
const fs = require ( 'fs' ) ;
2017-12-01 21:30:49 +08:00
const nurl = require ( 'url' ) ;
2016-08-15 17:48:47 +08:00
const color = require ( 'colorful' ) ;
const WebSocketServer = require ( 'ws' ) . Server ;
2017-12-01 21:30:49 +08:00
const tls = require ( 'tls' ) ;
const crypto = require ( 'crypto' ) ;
2019-03-21 18:18:20 +08:00
const stream = require ( 'stream' ) ;
const brotli = require ( 'brotli' ) ;
const zlib = require ( 'zlib' ) ;
2017-12-01 21:30:49 +08:00
const createSecureContext = tls . createSecureContext || crypto . createSecureContext ;
2016-08-15 17:48:47 +08:00
const DEFAULT _PORT = 3000 ;
const HTTPS _PORT = 3001 ;
2017-12-01 21:30:49 +08:00
const HTTPS _PORT2 = 3002 ; // start multiple https server
const UPLOAD _DIR = path . resolve ( _ _dirname , '../temp' ) ;
2016-08-15 17:48:47 +08:00
const PROXY _KEY _PREFIX = 'proxy-' ;
2017-12-01 21:30:49 +08:00
function SNICertCallback ( serverName , SNICallback ) {
certMgr . getCertificate ( serverName , ( err , key , crt ) => {
if ( err ) {
console . error ( 'error happend in sni callback' , err ) ;
return ;
}
const ctx = createSecureContext ( {
key ,
cert : crt
2016-08-15 17:48:47 +08:00
} ) ;
2017-12-01 21:30:49 +08:00
SNICallback ( null , ctx ) ;
} ) ;
}
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
function KoaServer ( ) {
this . httpServer = null ;
this . httpsServer = null ;
this . requestRecordMap = { } ; // store all request data to the map
const self = this ;
/ * *
* log the request info , write as
* /
this . logRequest = function * ( next ) {
const headers = this . request . headers ;
let key = this . request . protocol + '://' + this . request . host + nurl . parse ( this . request . url ) . pathname ; // remove param to get clean key
// take proxy data with 'proxy-' + url
if ( headers [ 'via-proxy' ] === 'true' ) {
key = PROXY _KEY _PREFIX + key ;
}
printLog ( 'log request with key :' + key ) ;
let body = this . request . body ;
body = typeof body === 'object' ? JSON . stringify ( body ) : body ;
self . requestRecordMap [ key ] = {
headers ,
body
2016-08-15 17:48:47 +08:00
} ;
2017-12-01 21:30:49 +08:00
yield next ;
} ;
2016-08-15 17:48:47 +08:00
2018-03-23 15:42:38 +08:00
this . logWsRequest = function ( wsReq ) {
const headers = wsReq . headers ;
const host = headers . host ;
2019-03-13 21:26:40 +08:00
const isEncript = wsReq . connection && wsReq . connection . encrypted ;
2018-03-23 15:42:38 +08:00
const protocol = isEncript ? 'wss' : 'ws' ;
let key = ` ${ protocol } :// ${ host } ${ wsReq . url } ` ;
// take proxy data with 'proxy-' + url
if ( headers [ 'via-proxy' ] === 'true' ) {
key = PROXY _KEY _PREFIX + key ;
}
self . requestRecordMap [ key ] = {
headers : wsReq . headers ,
body : ''
}
} ;
2017-12-01 21:30:49 +08:00
this . start ( ) ;
}
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
KoaServer . prototype . constructRouter = function ( ) {
const router = KoaRouter ( ) ;
router . post ( '/test/getuser' , koaBody ( ) , this . logRequest , function * ( next ) {
printLog ( 'requesting post /test/getuser' ) ;
this . response . set ( 'reqbody' , JSON . stringify ( this . request . body ) ) ;
this . response . body = 'body_post_getuser' ;
} ) ;
router . get ( '/test' , this . logRequest , function * ( next ) {
printLog ( 'request in get: ' + JSON . stringify ( this . request ) ) ;
this . cookies . set ( 'a1' , 'a1value' ) ;
this . cookies . set ( 'a2' , 'a2value' ) ;
this . cookies . set ( 'a3' , 'a3value' ) ;
this . response . set ( 'header1' , 'cookie2=headervalue2' ) ;
this . response . body = 'something' ;
this . response . _ _req = this . request ;
printLog ( 'response in get:' + JSON . stringify ( this . response ) ) ;
} ) ;
router . get ( '/test/uselocal' , this . logRequest , function * ( next ) {
printLog ( 'request in get local:' + JSON . stringify ( this . request ) ) ;
this . response . body = 'something should be in local' ;
// this.response.__req = this.request;
printLog ( 'response in get:' + JSON . stringify ( this . response ) ) ;
} ) ;
[ 'png' , 'webp' , 'json' , 'js' , 'css' , 'ttf' , 'eot' , 'svg' , 'woff' , 'woff2' ] . forEach ( item => {
router . get ( ` /test/download/ ${ item } ` , this . logRequest , function * ( next ) {
yield send ( this , ` ./data/test. ${ item } ` , {
root : path . resolve ( _ _dirname , '../' )
} ) ;
2016-08-15 17:48:47 +08:00
} ) ;
2017-12-01 21:30:49 +08:00
} ) ;
2017-12-14 23:58:52 +08:00
router . get ( '/test/response/304' , this . logRequest , function * ( next ) {
this . response . set ( 'Content-Encoding' , 'gzip' ) ;
this . status = 304 ;
} ) ;
2017-12-01 21:30:49 +08:00
router . get ( '/test/response/303' , function * ( next ) {
printLog ( 'now to redirect 303' ) ;
this . redirect ( '/test' ) ;
this . status = 303 ;
} ) ;
router . get ( '/test/response/302' , function * ( next ) {
printLog ( 'now to redirect 302' ) ;
this . redirect ( '/test' ) ;
} ) ;
router . get ( '/test/response/301' , function * ( next ) {
printLog ( 'now to redirect permanently' ) ;
this . redirect ( '/test' ) ;
this . status = 301 ;
} ) ;
const onFileBegin = function ( name , file ) {
if ( ! fs . existsSync ( UPLOAD _DIR ) ) {
try {
fs . mkdirSync ( UPLOAD _DIR , '0777' ) ;
} catch ( e ) {
console . log ( e ) ;
return null ;
}
}
file . name = 'test_upload_' + Date . now ( ) + '.png' ;
const folder = path . dirname ( file . path ) ;
file . path = path . join ( folder , file . name ) ;
} ;
router . post ( '/test/upload/png' ,
this . logRequest ,
koaBody ( {
multipart : true ,
formidable : {
uploadDir : UPLOAD _DIR ,
onFileBegin
}
} ) ,
function * ( next ) {
const file = this . request . body . files . file ;
this . response . set ( 'reqbody' , JSON . stringify ( this . request . body . fields ) ) ;
this . response . body = file . path ;
}
) ;
router . put ( '/test/upload/putpng' ,
this . logRequest ,
koaBody ( {
multipart : true ,
formidable : {
uploadDir : UPLOAD _DIR ,
onFileBegin
}
} ) ,
function * ( next ) {
const file = this . request . body . files . file ;
this . response . body = file . path ;
}
) ;
router . put ( '/test/put' , koaBody ( ) , this . logRequest , function * ( next ) {
printLog ( 'requesting put /test/put' + JSON . stringify ( this . request ) ) ;
this . response . body = 'something in put' ;
} ) ;
router . delete ( '/test/delete/:id' , this . logRequest , function * ( next ) {
printLog ( 'requesting delete /test/delete/:id' + JSON . stringify ( this . params ) ) ;
this . response . body = 'something in delete' ;
} ) ;
router . head ( '/test/head' , this . logRequest , function * ( next ) {
printLog ( 'requesting head /test/head' ) ;
this . response . body = '' ; // the body will not be passed to response, in HEAD request
this . response . set ( 'reqBody' , 'head_request_contains_no_resbody' ) ;
} ) ;
router . options ( '/test/options' , this . logRequest , function * ( next ) {
printLog ( 'requesting options /test/options' ) ;
this . response . body = 'could_be_empty' ;
this . response . set ( 'Allow' , 'GET, HEAD, POST, OPTIONS' ) ;
} ) ;
router . get ( '/test/should_not_replace_option' , this . logRequest , function * ( next ) {
this . response . body = 'the_option_that_not_be_replaced' ;
} ) ;
router . get ( '/test/should_replace_option' , this . logRequest , function * ( next ) {
this . response . body = 'the_request_that_has_not_be_replaced' ;
} ) ;
router . get ( '/test/new_replace_option' , this . logRequest , function * ( next ) {
this . response . body = 'the_new_replaced_option_page_content' ;
} ) ;
router . get ( '/test/normal_request1' , this . logRequest , koaBody ( ) , function * ( next ) {
printLog ( 'requesting get /test/normal_request1' ) ;
this . response . body = 'body_normal_request1' ;
} ) ;
router . get ( '/test/normal_request2' , this . logRequest , koaBody ( ) , function * ( next ) {
printLog ( 'requesting get /test/normal_request2' ) ;
this . response . body = 'body_normal_request2' ;
} ) ;
router . post ( '/test/normal_post_request1' , koaBody ( ) , this . logRequest , function * ( next ) {
printLog ( 'requesting post /test/normal_post_request1' ) ;
this . response . body = 'body_normal_post_request1' ;
} ) ;
router . get ( '/big_response' , this . logRequest , function * ( next ) {
const buf = new Buffer ( 1 * 1024 * 1024 * 1024 ) ; // 1GB
buf . fill ( 1 ) ;
printLog ( 'request in get big response of 1GB' ) ;
this . response . type = 'application/octet-stream' ;
this . response . body = buf ;
} ) ;
2019-03-21 18:18:20 +08:00
router . get ( '/test/brotli' , this . logRequest , function * ( next ) {
this . status = 200 ;
this . response . set ( 'Content-Encoding' , 'br' ) ;
this . response . set ( 'Content-Type' , 'application/json' ) ;
const buf = new Buffer ( '{"type":"brotli","message":"This is a brotli encoding response, but it need to be a long string or the brotli module\'s compress result will be null"}' ) ;
this . response . body = Buffer . from ( brotli . compress ( buf ) ) ;
} ) ;
router . get ( '/test/gzip' , this . logRequest , function * ( next ) {
this . status = 200 ;
this . response . set ( 'Content-Encoding' , 'gzip' ) ;
this . response . set ( 'Content-Type' , 'application/json' ) ;
const bufStream = new stream . PassThrough ( ) ;
bufStream . end ( new Buffer ( '{"type":"gzip","message":"This is a gzip encoding response"}' ) ) ;
this . response . body = bufStream . pipe ( zlib . createGzip ( ) ) ;
} ) ;
2017-12-01 21:30:49 +08:00
return router ;
2016-08-15 17:48:47 +08:00
} ;
2018-03-23 15:42:38 +08:00
KoaServer . prototype . createWsServer = function ( httpServer ) {
const wsServer = new WebSocketServer ( {
server : httpServer ,
path : '/test/socket'
} ) ;
wsServer . on ( 'connection' , ( ws , wsReq ) => {
const self = this ;
self . logWsRequest ( wsReq ) ;
2017-12-01 21:30:49 +08:00
const messageObj = {
type : 'initial' ,
content : 'default message'
} ;
ws . send ( JSON . stringify ( messageObj ) ) ;
ws . on ( 'message' , message => {
printLog ( 'message from request socket: ' + message ) ;
self . handleRecievedMessage ( ws , message ) ;
2016-08-15 17:48:47 +08:00
} ) ;
2018-03-23 15:42:38 +08:00
} )
2016-08-15 17:48:47 +08:00
} ;
KoaServer . prototype . getRequestRecord = function ( key ) {
2017-12-01 21:30:49 +08:00
return this . requestRecordMap [ key ] || null ;
2016-08-15 17:48:47 +08:00
} ;
KoaServer . prototype . getProxyRequestRecord = function ( key ) {
2017-12-01 21:30:49 +08:00
key = PROXY _KEY _PREFIX + key ;
return this . requestRecordMap [ key ] || null ;
2016-08-15 17:48:47 +08:00
} ;
2017-12-01 21:30:49 +08:00
KoaServer . prototype . handleRecievedMessage = function ( ws , message ) {
const newMessage = {
type : 'onMessage' ,
content : message
} ;
ws . send ( JSON . stringify ( newMessage ) ) ;
2016-08-15 17:48:47 +08:00
} ;
2017-12-01 21:30:49 +08:00
KoaServer . prototype . start = function ( ) {
printLog ( 'Starting the server...' ) ;
const router = this . constructRouter ( ) ;
const self = this ;
const app = Koa ( ) ;
app . use ( router . routes ( ) ) ;
this . httpServer = app . listen ( DEFAULT _PORT ) ;
2018-03-23 15:42:38 +08:00
this . createWsServer ( this . httpServer ) ;
2017-12-01 21:30:49 +08:00
printLog ( 'HTTP is now listening on port :' + DEFAULT _PORT ) ;
certMgr . getCertificate ( 'localhost' , ( error , keyContent , crtContent ) => {
if ( error ) {
console . error ( 'failed to create https server:' , error ) ;
} else {
self . httpsServer = https . createServer ( {
SNICallback : SNICertCallback ,
key : keyContent ,
cert : crtContent
} , app . callback ( ) ) ;
// create wss server
const wss = new WebSocketServer ( {
server : self . httpsServer
} ) ;
2018-03-23 15:42:38 +08:00
wss . on ( 'connection' , ( ws , wsReq ) => {
self . logWsRequest ( wsReq ) ;
2017-12-01 21:30:49 +08:00
ws . on ( 'message' , ( message ) => {
printLog ( 'received in wss: ' + message ) ;
self . handleRecievedMessage ( ws , message ) ;
} ) ;
} ) ;
2016-08-15 17:48:47 +08:00
2017-12-12 20:07:06 +08:00
wss . on ( 'error' , e => console . error ( 'error happened in wss:%s' , e ) ) ;
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
self . httpsServer . listen ( HTTPS _PORT ) ;
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
self . httpsServer2 = https . createServer ( {
key : keyContent ,
cert : crtContent
} , app . callback ( ) ) ;
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
self . httpsServer2 . listen ( HTTPS _PORT2 ) ;
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
printLog ( 'HTTPS is now listening on port :' + HTTPS _PORT ) ;
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
printLog ( 'Server started successfully' ) ;
}
} ) ;
2016-08-15 17:48:47 +08:00
2017-12-01 21:30:49 +08:00
return this ;
2016-08-15 17:48:47 +08:00
} ;
2017-12-01 21:30:49 +08:00
KoaServer . prototype . close = function ( ) {
printLog ( 'Closing server now...' ) ;
this . httpServer && this . httpServer . close ( ) ;
this . httpsServer && this . httpsServer . close ( ) ;
this . httpsServer2 && this . httpsServer2 . close ( ) ;
this . requestRecordMap = { } ;
printLog ( 'Server closed successfully' ) ;
2016-08-15 17:48:47 +08:00
} ;
function printLog ( content ) {
2017-12-01 21:30:49 +08:00
console . log ( color . cyan ( '[SERVER LOG]: ' + content ) ) ;
2016-08-15 17:48:47 +08:00
}
2017-12-01 21:30:49 +08:00
module . exports = KoaServer ;