import * as bodyParser from 'body-parser' ; import cors from 'cors' ; import express , { NextFunction } from 'express' ; import * as openapi from 'express-openapi' ; import * as fs from 'fs' ; import * as http from 'http' ; import * as https from 'https' ; import { inject , injectable } from 'inversify' ; import * as yaml from 'js-yaml' ; import * as log4js from 'log4js' ; import mkdirp from 'mkdirp' ; import multer from 'multer' ; import { OpenAPIV3 } from 'openapi-types' ; import * as path from 'path' ; import urljoin from 'url-join' ; import FileUtil from '../../util/FileUtil' ; import IConfigFile from '../IConfigFile' ; import IConfiguration from '../IConfiguration' ; import ILogger from '../ILogger' ; import ILoggerModel from '../ILoggerModel' ; import IServiceServer from './IServiceServer' ; import ISocketIOManageModel from './socketio/ISocketIOManageModel' ; @ injectable ( ) class ServiceServer implements IServiceServer { private log : ILogger ; private config : IConfigFile ; private socketIoManageModel : ISocketIOManageModel ; private app = express ( ) ; constructor ( @ inject ( 'ILoggerModel' ) logger , @ inject ( 'IConfiguration' ) configuration , @ inject ( 'ISocketIOManageModel' ) socketIoManageModel ) { this . log = logger . getLogger ( ) ; this . config = configuration . getConfig ( ) ; this . socketIoManageModel = socketIoManageModel ; this . init ( ) ; } /**
     * 初期化処理
     */ private init ( ) { this . setLog ( ) ; const api = this . getApiDocument ( ServiceServer . API_YML ) ; if ( this . config . isAllowAllCORS === true ) { this . app . use ( cors ( ) ) ; } this . setSwaggerUI ( ) ; this . createUploadDir ( ) ; this . initOpenApi ( api ) ; this . setMime ( ) ; this . setStaticFiles ( ) ; } /**
     * log の設定
     */ private setLog ( ) { this . app . use ( log4js . connectLogger ( this . log . access , { level : 'info' } ) ) ; } param ymlPath return private getApiDocument ( ymlPath ) { const api = < OpenAPIV3 . Document > yaml . load ( fs . readFileSync ( ymlPath , 'utf-8' ) ) ; api . servers = this . config . apiServers . map ( url => { return { url : urljoin ( url , this . createUrl ( '/api' ) ) , } ; } ) ; const pkg = < any > JSON . parse ( fs . readFileSync ( ServiceServer . PACKAGE_JSON , 'utf-8' ) ) ; api . info . title = pkg . name ; api . info . version = pkg . version ; return api ; } param api private initOpenApi ( api ) { openapi . initialize ( { apiDoc : api , app : this . app , docsPath : '/docs' , consumesMiddleware : { 'application/json' : bodyParser . json ( ) as any , 'text/text' : bodyParser . text ( ) as any , 'multipart/form-data' : ( req , res , next ) => { this . uploadFile ( req as any , res as any , next ) ; } , } , errorMiddleware : ( err , _req , res , _next ) => { this . log . system . error ( err ) ; res . status ( 400 ) ; res . json ( err ) ; } , errorTransformer : openApi => { this . log . system . error ( < any > openApi ) ; return { message : ( < any > openApi ) . message , } ; } , exposeApiDocs : true , paths : ServiceServer . API_DIR , } ) ; } /**
     * mime 設定
     */ private setMime ( ) { express . static . mime . define ( { 'text/css' : [ 'css' , 'min.css' ] } ) ; express . static . mime . define ( { 'text/javascript' : [ 'js' , 'min.js' ] } ) ; express . static . mime . define ( { 'application/vnd.ms-fontobject' : [ 'eot' ] , } ) ; express . static . mime . define ( { 'application/font-ttf' : [ 'ttf' ] } ) ; express . static . mime . define ( { 'application/font-woff' : [ 'woff' ] } ) ; express . static . mime . define ( { 'application/font-woff2' : [ 'woff2' ] } ) ; express . static . mime . define ( { 'magnus-internal/imagemap' : [ 'map' ] } ) ; express . static . mime . define ( { 'image/png' : [ 'png' ] } ) ; express . static . mime . define ( { 'image/jpg' : [ 'jpg' ] } ) ; express . static . mime . define ( { 'video/mpeg' : [ 'ts' ] } ) ; express . static . mime . define ( { 'application/octet-stream' : [ 'm4s' ] } ) ; express . static . mime . define ( { 'video/MP2T' : [ 'm3u8' ] } ) ; express . static . mime . define ( { 'text/plain' : [ 'log' ] } ) ; } /**
     * ファイル読み込み url 設定
     */ private setStaticFiles ( ) { this . app . use ( this . createUrl ( '/img' ) , express . static ( path . join ( __dirname , '..' , '..' , '..' , 'img' ) ) ) ; this . app . use ( this . createUrl ( '/thumbnail' ) , express . static ( this . config . thumbnail ) ) ; this . app . use ( this . createUrl ( '/streamfiles' ) , express . static ( this . config . streamFilePath ) ) ; this . app . use ( this . createUrl ( '/' ) , express . static ( ServiceServer . CLIENT_DIR ) ) ; } /**
     * SwaggerUI の設定
     */ private setSwaggerUI ( ) { if ( fs . existsSync ( ServiceServer . SWAGGER_UI_DIST ) === false ) { return ; } this . app . use ( this . createUrl ( '/api-docs' ) , express . static ( ServiceServer . SWAGGER_UI_DIST ) ) ; this . app . get ( this . createUrl ( '/api/debug' ) , ( _req , res ) => { return res . redirect ( this . createUrl ( '/api-docs/?url=' + this . createUrl ( '/api/docs' ) ) ) ; } ) ; } /**
     * upload 用のディレクトリを生成する
     */ private createUploadDir ( ) { try { fs . statSync ( this . config . uploadTempDir ) ; } catch ( e ) { this . log . system . info ( `mkdirp: ${ this . config . uploadTempDir }` ) ; mkdirp . sync ( this . config . uploadTempDir ) ; } } param req param res param next private uploadFile ( req , res , next ) { let fileName = '' ; const storage = multer . diskStorage ( { destination : this . config . uploadTempDir , filename : ( _req , file , cb ) => { fileName = file . fieldname + '-' + new Date ( ) . getTime ( ) . toString ( 16 ) + Math . floor ( 100000 * Math . random ( ) ) . toString ( 16 ) ; cb ( null , fileName ) ; } , } ) ; multer ( { storage : storage } ) . single ( 'file' ) ( req as any , res as any , async ( err ) => { if ( err ) { const filePath = path . join ( this . config . uploadTempDir , fileName ) ; try { await FileUtil . unlink ( filePath ) ; this . log . access . info ( `delete upload file: ${ filePath }` ) ; } catch ( err ) { this . log . access . error ( `upload file delete error: ${ filePath }` ) ; this . log . access . error ( err . message ) ; } return next ( err . message ) ; } if ( typeof req . body . recordedId === 'string' ) { req . body . recordedId = parseInt ( req . body . recordedId , 10 ) ; } if ( typeof req . file !== 'undefined' && typeof req . file . fieldname !== 'undefined' ) { req . body . file = req . file . filename ; } return next ( ) ; } ) ; } param url private createUrl ( urlStr ) { return typeof this . config . subDirectory === 'undefined' ? urlStr : urljoin ( this . config . subDirectory , urlStr ) ; } /**
     * http server 起動
     */ public start ( ) { const sokcetioServers = [ ] ; if ( typeof this . config . port !== 'undefined' ) { const socketioPort = typeof this . config . socketioPort !== 'undefined' ? this . config . socketioPort : this . config . port ; const server = this . app . listen ( this . config . port , ( ) => { this . log . system . info ( `http server listening on ${ this . config . port }` ) ; } ) ; if ( socketioPort === this . config . port ) { sokcetioServers . push ( server ) ; } else { const socketIOServer = http . createServer ( ) ; socketIOServer . listen ( this . config . socketioPort , ( ) => { this . log . system . info ( `http SocketIO listening on ${ this . config . socketioPort }` ) ; } ) ; sokcetioServers . push ( socketIOServer ) ; } } if ( typeof this . config . https !== 'undefined' ) { const option = { key : fs . readFileSync ( this . config . https . key ) , cert : fs . readFileSync ( this . config . https . cert ) , } ; if ( typeof this . config . https . ca !== 'undefined' ) { if ( typeof this . config . https . ca === 'string' ) { option . ca = fs . readFileSync ( this . config . https . ca ) ; } else { option . ca = this . config . https . ca . map ( f => { return fs . readFileSync ( f ) ; } ) ; } option . requestCert = true ; option . rejectUnauthorized = true ; } const httpsServer = https . createServer ( option , this . app ) ; httpsServer . listen ( this . config . https . port , ( ) => { if ( typeof this . config . https !== 'undefined' ) { this . log . system . info ( `https server listening on ${ this . config . https . port }` ) ; } } ) ; if ( typeof this . config . https . socketioPort === 'undefined' ) { sokcetioServers . push ( httpsServer ) ; } else { const socketIOServer = https . createServer ( option ) ; sokcetioServers . push ( socketIOServer ) ; socketIOServer . listen ( this . config . https . socketioPort , ( ) => { this . log . system . info ( `https SocketIO listening on ${ this . config . socketioPort }` ) ; } ) ; } } this . socketIoManageModel . initialize ( sokcetioServers ) ; } } namespace ServiceServer { export const ROOT_DIR = path . join ( __dirname , '..' , '..' , '..' ) ; export const API_YML = path . join ( ServiceServer . ROOT_DIR , 'api.yml' ) ; export const PACKAGE_JSON = path . join ( ServiceServer . ROOT_DIR , 'package.json' ) ; export const SWAGGER_UI_DIST = path . join ( ServiceServer . ROOT_DIR , 'node_modules' , 'swagger-ui-dist' ) ; export const API_DIR = path . join ( __dirname , 'api' ) ; export const CLIENT_DIR = path . join ( ROOT_DIR , 'client' , 'dist' ) ; } export default ServiceServer ;