servers.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. const fs = require('fs-extra')
  2. const http = require('http')
  3. const https = require('https')
  4. const { ApolloServer } = require('apollo-server-express')
  5. const _ = require('lodash')
  6. const io = require('socket.io')
  7. const { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageProductionDefault } = require('apollo-server-core')
  8. const { graphqlUploadExpress } = require('graphql-upload')
  9. module.exports = {
  10. graph: null,
  11. http: null,
  12. https: null,
  13. ws: null,
  14. connections: new Map(),
  15. le: null,
  16. /**
  17. * Initialize HTTP Server
  18. */
  19. async initHTTP () {
  20. WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`)
  21. this.http = http.createServer(WIKI.app)
  22. this.http.on('error', (error) => {
  23. if (error.syscall !== 'listen') {
  24. throw error
  25. }
  26. switch (error.code) {
  27. case 'EACCES':
  28. WIKI.logger.error('Listening on port ' + WIKI.config.port + ' requires elevated privileges!')
  29. return process.exit(1)
  30. case 'EADDRINUSE':
  31. WIKI.logger.error('Port ' + WIKI.config.port + ' is already in use!')
  32. return process.exit(1)
  33. default:
  34. throw error
  35. }
  36. })
  37. this.http.on('listening', () => {
  38. WIKI.logger.info('HTTP Server: [ RUNNING ]')
  39. })
  40. this.http.on('connection', conn => {
  41. let connKey = `http:${conn.remoteAddress}:${conn.remotePort}`
  42. this.connections.set(connKey, conn)
  43. conn.on('close', () => {
  44. this.connections.delete(connKey)
  45. })
  46. })
  47. },
  48. /**
  49. * Start HTTP Server
  50. */
  51. async startHTTP () {
  52. this.http.listen(WIKI.config.port, WIKI.config.bindIP)
  53. },
  54. /**
  55. * Initialize HTTPS Server
  56. */
  57. async initHTTPS () {
  58. if (WIKI.config.ssl.provider === 'letsencrypt') {
  59. this.le = require('./letsencrypt')
  60. await this.le.init()
  61. }
  62. WIKI.logger.info(`HTTPS Server on port: [ ${WIKI.config.ssl.port} ]`)
  63. const tlsOpts = {}
  64. try {
  65. if (WIKI.config.ssl.format === 'pem') {
  66. tlsOpts.key = WIKI.config.ssl.inline ? WIKI.config.ssl.key : fs.readFileSync(WIKI.config.ssl.key)
  67. tlsOpts.cert = WIKI.config.ssl.inline ? WIKI.config.ssl.cert : fs.readFileSync(WIKI.config.ssl.cert)
  68. } else {
  69. tlsOpts.pfx = WIKI.config.ssl.inline ? WIKI.config.ssl.pfx : fs.readFileSync(WIKI.config.ssl.pfx)
  70. }
  71. if (!_.isEmpty(WIKI.config.ssl.passphrase)) {
  72. tlsOpts.passphrase = WIKI.config.ssl.passphrase
  73. }
  74. if (!_.isEmpty(WIKI.config.ssl.dhparam)) {
  75. tlsOpts.dhparam = WIKI.config.ssl.dhparam
  76. }
  77. } catch (err) {
  78. WIKI.logger.error('Failed to setup HTTPS server parameters:')
  79. WIKI.logger.error(err)
  80. return process.exit(1)
  81. }
  82. this.https = https.createServer(tlsOpts, WIKI.app)
  83. this.https.listen(WIKI.config.ssl.port, WIKI.config.bindIP)
  84. this.https.on('error', (error) => {
  85. if (error.syscall !== 'listen') {
  86. throw error
  87. }
  88. switch (error.code) {
  89. case 'EACCES':
  90. WIKI.logger.error('Listening on port ' + WIKI.config.ssl.port + ' requires elevated privileges!')
  91. return process.exit(1)
  92. case 'EADDRINUSE':
  93. WIKI.logger.error('Port ' + WIKI.config.ssl.port + ' is already in use!')
  94. return process.exit(1)
  95. default:
  96. throw error
  97. }
  98. })
  99. this.https.on('listening', () => {
  100. WIKI.logger.info('HTTPS Server: [ RUNNING ]')
  101. })
  102. this.https.on('connection', conn => {
  103. let connKey = `https:${conn.remoteAddress}:${conn.remotePort}`
  104. this.connections.set(connKey, conn)
  105. conn.on('close', () => {
  106. this.connections.delete(connKey)
  107. })
  108. })
  109. },
  110. /**
  111. * Start HTTPS Server
  112. */
  113. async startHTTPS () {
  114. this.https.listen(WIKI.config.ssl.port, WIKI.config.bindIP)
  115. },
  116. /**
  117. * Start GraphQL Server
  118. */
  119. async startGraphQL () {
  120. const graphqlSchema = require('../graph')
  121. this.graph = new ApolloServer({
  122. schema: graphqlSchema,
  123. csrfPrevention: true,
  124. cache: 'bounded',
  125. context: ({ req, res }) => ({ req, res }),
  126. plugins: [
  127. process.env.NODE_ENV === 'development' ? ApolloServerPluginLandingPageGraphQLPlayground({
  128. footer: false
  129. }) : ApolloServerPluginLandingPageProductionDefault({
  130. footer: false
  131. })
  132. // ApolloServerPluginDrainHttpServer({ httpServer: this.http })
  133. // ...(this.https && ApolloServerPluginDrainHttpServer({ httpServer: this.https }))
  134. ]
  135. })
  136. await this.graph.start()
  137. WIKI.app.use(graphqlUploadExpress({
  138. maxFileSize: WIKI.config.security.uploadMaxFileSize,
  139. maxFiles: WIKI.config.security.uploadMaxFiles
  140. }))
  141. this.graph.applyMiddleware({ app: WIKI.app, cors: false, path: '/_graphql' })
  142. },
  143. /**
  144. * Start Socket.io WebSocket Server
  145. */
  146. async initWebSocket() {
  147. if (this.https) {
  148. this.ws = new io.Server(this.https, {
  149. path: '/_ws/',
  150. serveClient: false
  151. })
  152. WIKI.logger.info(`WebSocket Server attached to HTTPS Server [ OK ]`)
  153. } else {
  154. this.ws = new io.Server(this.http, {
  155. path: '/_ws/',
  156. serveClient: false,
  157. cors: true // TODO: dev only, replace with app settings once stable
  158. })
  159. WIKI.logger.info(`WebSocket Server attached to HTTP Server [ OK ]`)
  160. }
  161. },
  162. /**
  163. * Close all active connections
  164. */
  165. closeConnections (mode = 'all') {
  166. for (const [key, conn] of this.connections) {
  167. if (mode !== `all` && key.indexOf(`${mode}:`) !== 0) {
  168. continue
  169. }
  170. conn.destroy()
  171. this.connections.delete(key)
  172. }
  173. if (mode === 'all') {
  174. this.connections.clear()
  175. }
  176. },
  177. /**
  178. * Stop all servers
  179. */
  180. async stopServers () {
  181. this.closeConnections()
  182. if (this.http) {
  183. await new Promise(resolve => this.http.close(resolve))
  184. this.http = null
  185. }
  186. if (this.https) {
  187. await new Promise(resolve => this.https.close(resolve))
  188. this.https = null
  189. }
  190. this.graph = null
  191. },
  192. /**
  193. * Restart Server
  194. */
  195. async restartServer (srv = 'https') {
  196. this.closeConnections(srv)
  197. switch (srv) {
  198. case 'http':
  199. if (this.http) {
  200. await new Promise(resolve => this.http.close(resolve))
  201. this.http = null
  202. }
  203. this.initHTTP()
  204. this.startHTTP()
  205. break
  206. case 'https':
  207. if (this.https) {
  208. await new Promise(resolve => this.https.close(resolve))
  209. this.https = null
  210. }
  211. this.initHTTPS()
  212. this.startHTTPS()
  213. break
  214. default:
  215. throw new Error('Cannot restart server: Invalid designation')
  216. }
  217. }
  218. }