Parcourir la source

Added WebSocket support

rigwild il y a 5 ans
Parent
commit
c60f4973b2
8 fichiers modifiés avec 124 ajouts et 13 suppressions
  1. 1 0
      back.Dockerfile
  2. 18 2
      config.js
  3. 3 2
      package.json
  4. 8 2
      server/index.js
  5. 3 6
      server/routes/getImageExtracts.js
  6. 62 0
      server/webSocket/index.js
  7. 28 0
      server/webSocket/messageHandler.js
  8. 1 1
      yarn.lock

+ 1 - 0
back.Dockerfile

@@ -5,6 +5,7 @@ COPY . /usr/src/app
 
 WORKDIR /usr/src/app
 
+# Server port
 EXPOSE 5000
 
 RUN yarn install

+ 18 - 2
config.js

@@ -37,8 +37,24 @@ export const logger = winston.createLogger({
   level: 'info',
   format: winston.format.json(),
   transports: [
-    new winston.transports.File({ filename: 'logs/combined.log' }),
-    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
+    new winston.transports.File({ filename: 'logs/server.combined.log' }),
+    new winston.transports.File({ filename: 'logs/server.error.log', level: 'error' }),
+    new winston.transports.Console({
+      level: 'debug',
+      handleExceptions: true,
+      format: winston.format.simple()
+    })
+  ],
+  exitOnError: false
+})
+
+// WebSocket logger configuration
+export const wsLogger = winston.createLogger({
+  level: 'info',
+  format: winston.format.json(),
+  transports: [
+    new winston.transports.File({ filename: 'logs/ws.log' }),
+    new winston.transports.File({ filename: 'logs/ws.error.log', level: 'error' }),
     new winston.transports.Console({
       level: 'debug',
       handleExceptions: true,

+ 3 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "expe-web",
-  "version": "0.1.0",
+  "version": "0.0.8",
   "private": true,
   "scripts": {
     "server:start": "node -r esm server/index.js",
@@ -24,7 +24,8 @@
     "sharp": "^0.22.0",
     "vue": "^2.6.6",
     "vue-router": "^3.0.1",
-    "winston": "^3.2.1"
+    "winston": "^3.2.1",
+    "ws": "^6.2.1"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^3.5.0",

+ 8 - 2
server/index.js

@@ -9,12 +9,15 @@ import cors from 'cors'
 import routes from './routes'
 import { errorHandler } from './functions'
 import { apiPrefix, imageServedUrl, serverPort, serveClient, imagesPath, logger } from '../config'
+import startWebSocketServer from './webSocket'
 const morgan = require('morgan')
 
 const app = express()
 
 // Activating logging
-app.use(morgan('combined', { 'stream': { write: (message, encoding) => logger.info(message) } }))
+app.use(morgan('combined', {
+  stream: { write: message => logger.info(message) }
+}))
 
 // Use gzip compression to improve performance
 app.use(compression())
@@ -43,4 +46,7 @@ else {
 app.use(errorHandler)
 
 // Start the server on the configured port
-app.listen(serverPort, () => logger.info('The server was started on http://localhost:' + serverPort))
+const server = app.listen(serverPort, () => logger.info('The server was started on http://localhost:' + serverPort))
+
+// Start the WebSocket server on top of the started HTTP server
+startWebSocketServer(server)

+ 3 - 6
server/routes/getImageExtracts.js

@@ -7,14 +7,11 @@ import path from 'path'
 import boom from 'boom'
 
 import { asyncMiddleware, checkSceneName, checkRequiredParameters } from '../functions'
-import { getImage } from './getImage'
 import { imageServedUrl, imagesPath, extractsDirName } from '../../config'
+import { getImage, Image } from './getImage' // eslint-disable-line no-unused-vars
 
 const router = express.Router()
 
-/**
- * @typedef {import('./getImage').Image} Image
- */
 /**
  * Cut an image, save its extracts and get the url of these extracts
  *
@@ -33,8 +30,8 @@ const cutImage = async (image, xExtracts, yExtracts) => {
 
   // Check the image is cuttable with the current parameters
   let errorsList = []
-  if (!Number.isInteger(xCropSize)) errorsList.push('Incompatible number of horizontal extracts (width % numberOfExtracts != 0)')
-  if (!Number.isInteger(yCropSize)) errorsList.push('Incompatible number of vertical extracts (height % numberOfExtracts != 0)')
+  if (!Number.isInteger(xCropSize)) errorsList.push('Incompatible number of horizontal extracts (width % numberOfExtracts != 0).')
+  if (!Number.isInteger(yCropSize)) errorsList.push('Incompatible number of vertical extracts (height % numberOfExtracts != 0).')
   if (errorsList.length > 0) throw boom.badRequest('Invalid query parameter(s).', errorsList)
 
   let extracts = []

+ 62 - 0
server/webSocket/index.js

@@ -0,0 +1,62 @@
+'use strict'
+
+import WebSocket from 'ws'
+import { wsLogger } from '../../config'
+import messageHandler from './messageHandler'
+
+/**
+ * @typedef {object} LogObj
+ * @property {Date} date current date
+ * @property {object|string} log anything to log
+ */
+/**
+ * Format a string or object to a log object
+ *
+ * @param {object|string} data any message or object
+ * @param {('info'|'message'|'error')} event the type of event
+ * @returns {LogObj} the log object
+ */
+export const formatLog = (data, event = 'info') => ({
+  event,
+  log: typeof data === 'object' ? JSON.stringify(data) : data,
+  date: new Date()
+})
+
+/**
+ * @typedef {function} ErrorLogger
+ * @param {Error} err an Error object
+ */
+/**
+ * Handle thrown errors
+ *
+ * @param {object} ws a WebSocket connected client
+ * @returns {ErrorLogger} the actual error logger
+ */
+export const errorHandler = ws => err => {
+  ws.send(err.message)
+  wsLogger.error(formatLog({ error: err.message, stack: err.stack }, 'error'))
+}
+
+/**
+ * Create the WebSocket server
+ *
+ * @param {*} httpServer an HTTP node object (provided by Express here)
+ * @returns {void}
+ */
+const createWsServer = httpServer => {
+  const wss = new WebSocket.Server({ server: httpServer })
+
+  wss.on('listening', () => wsLogger.info(formatLog('The WebSocket server was started')))
+  wss.on('error', err => wsLogger.error(formatLog('WebSocket server error - ' + err.message, 'error')))
+
+  wss.on('connection', ws => {
+    wsLogger.info(formatLog('New client connected.'))
+
+    ws.on('message', data => messageHandler(ws)(data).catch(err => errorHandler(ws)(err)))
+
+    ws.on('error', err => errorHandler(ws)(err))
+    ws.on('close', () => wsLogger.info(formatLog('Client disconnected.')))
+  })
+}
+errorHandler()()
+export default createWsServer

+ 28 - 0
server/webSocket/messageHandler.js

@@ -0,0 +1,28 @@
+'use strict'
+
+import { formatLog } from './index'
+import { wsLogger } from '../../config'
+
+/**
+ * @typedef {Function} MessageHandler
+ * @param {string} data a message received from a client
+ */
+/**
+ * Treat received message from a WebSocket client
+ * @param {object} ws a WebSocket connected client
+ * @returns {MessageHandler} the message handler
+ */
+const messageHandler = ws => async data => {
+  let json
+  try {
+    json = JSON.parse(data)
+  }
+  catch (err) {
+    throw new Error('Invalid JSON data.')
+  }
+
+  wsLogger.info(formatLog(json, 'message'))
+  ws.send('ok')
+}
+
+export default messageHandler

+ 1 - 1
yarn.lock

@@ -9553,7 +9553,7 @@ write@^0.2.1:
   dependencies:
     mkdirp "^0.5.1"
 
-ws@^6.0.0:
+ws@^6.0.0, ws@^6.2.1:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
   integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==