|
|
|
const fs = require('fs-extra')
|
|
|
|
const http = require('http')
|
|
|
|
const https = require('https')
|
|
|
|
const { ApolloServer } = require('apollo-server-express')
|
|
|
|
const _ = require('lodash')
|
|
|
|
const io = require('socket.io')
|
|
|
|
const { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageProductionDefault } = require('apollo-server-core')
|
|
|
|
const { graphqlUploadExpress } = require('graphql-upload')
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
graph: null,
|
|
|
|
http: null,
|
|
|
|
https: null,
|
|
|
|
ws: null,
|
|
|
|
connections: new Map(),
|
|
|
|
le: null,
|
|
|
|
/**
|
|
|
|
* Initialize HTTP Server
|
|
|
|
*/
|
|
|
|
async initHTTP () {
|
|
|
|
WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`)
|
|
|
|
this.http = http.createServer(WIKI.app)
|
|
|
|
|
|
|
|
this.http.on('error', (error) => {
|
|
|
|
if (error.syscall !== 'listen') {
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (error.code) {
|
|
|
|
case 'EACCES':
|
|
|
|
WIKI.logger.error('Listening on port ' + WIKI.config.port + ' requires elevated privileges!')
|
|
|
|
return process.exit(1)
|
|
|
|
case 'EADDRINUSE':
|
|
|
|
WIKI.logger.error('Port ' + WIKI.config.port + ' is already in use!')
|
|
|
|
return process.exit(1)
|
|
|
|
default:
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.http.on('listening', () => {
|
|
|
|
WIKI.logger.info('HTTP Server: [ RUNNING ]')
|
|
|
|
})
|
|
|
|
|
|
|
|
this.http.on('connection', conn => {
|
|
|
|
let connKey = `http:${conn.remoteAddress}:${conn.remotePort}`
|
|
|
|
this.connections.set(connKey, conn)
|
|
|
|
conn.on('close', () => {
|
|
|
|
this.connections.delete(connKey)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Start HTTP Server
|
|
|
|
*/
|
|
|
|
async startHTTP () {
|
|
|
|
this.http.listen(WIKI.config.port, WIKI.config.bindIP)
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Initialize HTTPS Server
|
|
|
|
*/
|
|
|
|
async initHTTPS () {
|
|
|
|
if (WIKI.config.ssl.provider === 'letsencrypt') {
|
|
|
|
this.le = require('./letsencrypt')
|
|
|
|
await this.le.init()
|
|
|
|
}
|
|
|
|
|
|
|
|
WIKI.logger.info(`HTTPS Server on port: [ ${WIKI.config.ssl.port} ]`)
|
|
|
|
const tlsOpts = {}
|
|
|
|
try {
|
|
|
|
if (WIKI.config.ssl.format === 'pem') {
|
|
|
|
tlsOpts.key = WIKI.config.ssl.inline ? WIKI.config.ssl.key : fs.readFileSync(WIKI.config.ssl.key)
|
|
|
|
tlsOpts.cert = WIKI.config.ssl.inline ? WIKI.config.ssl.cert : fs.readFileSync(WIKI.config.ssl.cert)
|
|
|
|
} else {
|
|
|
|
tlsOpts.pfx = WIKI.config.ssl.inline ? WIKI.config.ssl.pfx : fs.readFileSync(WIKI.config.ssl.pfx)
|
|
|
|
}
|
|
|
|
if (!_.isEmpty(WIKI.config.ssl.passphrase)) {
|
|
|
|
tlsOpts.passphrase = WIKI.config.ssl.passphrase
|
|
|
|
}
|
|
|
|
if (!_.isEmpty(WIKI.config.ssl.dhparam)) {
|
|
|
|
tlsOpts.dhparam = WIKI.config.ssl.dhparam
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.error('Failed to setup HTTPS server parameters:')
|
|
|
|
WIKI.logger.error(err)
|
|
|
|
return process.exit(1)
|
|
|
|
}
|
|
|
|
this.https = https.createServer(tlsOpts, WIKI.app)
|
|
|
|
|
|
|
|
this.https.listen(WIKI.config.ssl.port, WIKI.config.bindIP)
|
|
|
|
this.https.on('error', (error) => {
|
|
|
|
if (error.syscall !== 'listen') {
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (error.code) {
|
|
|
|
case 'EACCES':
|
|
|
|
WIKI.logger.error('Listening on port ' + WIKI.config.ssl.port + ' requires elevated privileges!')
|
|
|
|
return process.exit(1)
|
|
|
|
case 'EADDRINUSE':
|
|
|
|
WIKI.logger.error('Port ' + WIKI.config.ssl.port + ' is already in use!')
|
|
|
|
return process.exit(1)
|
|
|
|
default:
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.https.on('listening', () => {
|
|
|
|
WIKI.logger.info('HTTPS Server: [ RUNNING ]')
|
|
|
|
})
|
|
|
|
|
|
|
|
this.https.on('connection', conn => {
|
|
|
|
let connKey = `https:${conn.remoteAddress}:${conn.remotePort}`
|
|
|
|
this.connections.set(connKey, conn)
|
|
|
|
conn.on('close', () => {
|
|
|
|
this.connections.delete(connKey)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Start HTTPS Server
|
|
|
|
*/
|
|
|
|
async startHTTPS () {
|
|
|
|
this.https.listen(WIKI.config.ssl.port, WIKI.config.bindIP)
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Start GraphQL Server
|
|
|
|
*/
|
|
|
|
async startGraphQL () {
|
|
|
|
const graphqlSchema = require('../graph')
|
|
|
|
this.graph = new ApolloServer({
|
|
|
|
schema: graphqlSchema,
|
|
|
|
csrfPrevention: true,
|
|
|
|
cache: 'bounded',
|
|
|
|
context: ({ req, res }) => ({ req, res }),
|
|
|
|
plugins: [
|
|
|
|
process.env.NODE_ENV === 'development' ? ApolloServerPluginLandingPageGraphQLPlayground({
|
|
|
|
footer: false
|
|
|
|
}) : ApolloServerPluginLandingPageProductionDefault({
|
|
|
|
footer: false
|
|
|
|
})
|
|
|
|
// ApolloServerPluginDrainHttpServer({ httpServer: this.http })
|
|
|
|
// ...(this.https && ApolloServerPluginDrainHttpServer({ httpServer: this.https }))
|
|
|
|
]
|
|
|
|
})
|
|
|
|
await this.graph.start()
|
|
|
|
WIKI.app.use(graphqlUploadExpress({
|
|
|
|
maxFileSize: WIKI.config.security.uploadMaxFileSize,
|
|
|
|
maxFiles: WIKI.config.security.uploadMaxFiles
|
|
|
|
}))
|
|
|
|
this.graph.applyMiddleware({ app: WIKI.app, cors: false, path: '/_graphql' })
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Start Socket.io WebSocket Server
|
|
|
|
*/
|
|
|
|
async initWebSocket() {
|
|
|
|
if (this.https) {
|
|
|
|
this.ws = new io.Server(this.https, {
|
|
|
|
path: '/_ws/',
|
|
|
|
serveClient: false
|
|
|
|
})
|
|
|
|
WIKI.logger.info(`WebSocket Server attached to HTTPS Server [ OK ]`)
|
|
|
|
} else {
|
|
|
|
this.ws = new io.Server(this.http, {
|
|
|
|
path: '/_ws/',
|
|
|
|
serveClient: false,
|
|
|
|
cors: true // TODO: dev only, replace with app settings once stable
|
|
|
|
})
|
|
|
|
WIKI.logger.info(`WebSocket Server attached to HTTP Server [ OK ]`)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Close all active connections
|
|
|
|
*/
|
|
|
|
closeConnections (mode = 'all') {
|
|
|
|
for (const [key, conn] of this.connections) {
|
|
|
|
if (mode !== `all` && key.indexOf(`${mode}:`) !== 0) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
conn.destroy()
|
|
|
|
this.connections.delete(key)
|
|
|
|
}
|
|
|
|
if (mode === 'all') {
|
|
|
|
this.connections.clear()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Stop all servers
|
|
|
|
*/
|
|
|
|
async stopServers () {
|
|
|
|
this.closeConnections()
|
|
|
|
if (this.http) {
|
|
|
|
await new Promise(resolve => this.http.close(resolve))
|
|
|
|
this.http = null
|
|
|
|
}
|
|
|
|
if (this.https) {
|
|
|
|
await new Promise(resolve => this.https.close(resolve))
|
|
|
|
this.https = null
|
|
|
|
}
|
|
|
|
this.graph = null
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Restart Server
|
|
|
|
*/
|
|
|
|
async restartServer (srv = 'https') {
|
|
|
|
this.closeConnections(srv)
|
|
|
|
switch (srv) {
|
|
|
|
case 'http':
|
|
|
|
if (this.http) {
|
|
|
|
await new Promise(resolve => this.http.close(resolve))
|
|
|
|
this.http = null
|
|
|
|
}
|
|
|
|
this.initHTTP()
|
|
|
|
this.startHTTP()
|
|
|
|
break
|
|
|
|
case 'https':
|
|
|
|
if (this.https) {
|
|
|
|
await new Promise(resolve => this.https.close(resolve))
|
|
|
|
this.https = null
|
|
|
|
}
|
|
|
|
this.initHTTPS()
|
|
|
|
this.startHTTPS()
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
throw new Error('Cannot restart server: Invalid designation')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|