1 | 'use strict';
|
2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
6 | return c > 3 && r && Object.defineProperty(target, key, r), r;
|
7 | };
|
8 | var __metadata = (this && this.__metadata) || function (k, v) {
|
9 | if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
10 | };
|
11 | Object.defineProperty(exports, "__esModule", { value: true });
|
12 | const _ = require("lodash");
|
13 | const http = require("http");
|
14 | const compression = require("compression");
|
15 | const express = require("express");
|
16 | const admin_api_1 = require("./admin/api/admin-api");
|
17 | const users_1 = require("./admin/api/users");
|
18 | const typescript_rest_1 = require("typescript-rest");
|
19 | const logger_1 = require("./logger");
|
20 | const express_logger_1 = require("./express-logger");
|
21 | const config_1 = require("./service/config");
|
22 | const configuration_1 = require("./configuration");
|
23 | const fs = require("fs-extra-promise");
|
24 | const typescript_ioc_1 = require("typescript-ioc");
|
25 | const events_1 = require("./config/events");
|
26 | const path = require("path");
|
27 | const time_intervals_1 = require("./utils/time-intervals");
|
28 | const service_discovery_1 = require("./pipeline/servicediscovery/service-discovery");
|
29 | const events_2 = require("events");
|
30 | const request_1 = require("./pipeline/stats/request");
|
31 | const api_1 = require("./pipeline/api");
|
32 | let Gateway = class Gateway extends events_2.EventEmitter {
|
33 | constructor() {
|
34 | super();
|
35 | this.serverRunning = false;
|
36 | this.setMaxListeners(150);
|
37 | }
|
38 | get server() {
|
39 | return this.app;
|
40 | }
|
41 | get apis() {
|
42 | return this.apiPipeline.apis;
|
43 | }
|
44 | get running() {
|
45 | return this.serverRunning;
|
46 | }
|
47 | getApiConfig(apiId) {
|
48 | return this.apiPipeline.getApiConfig(apiId);
|
49 | }
|
50 | start() {
|
51 | return new Promise((resolve, reject) => {
|
52 | this.initialize()
|
53 | .then(() => {
|
54 | this.apiServer = new Map();
|
55 | let started = 0;
|
56 | let expected = 0;
|
57 | if (this.config.gateway.protocol.http) {
|
58 | expected++;
|
59 | const httpServer = http.createServer(this.app);
|
60 | this.apiServer.set('http', httpServer.listen(_.toSafeInteger(this.config.gateway.protocol.http.listenPort), () => {
|
61 | this.logger.info(`Gateway listenning HTTP on port ${this.config.gateway.protocol.http.listenPort}`);
|
62 | started++;
|
63 | if (started === expected) {
|
64 | this.serverRunning = true;
|
65 | this.emit('start', this);
|
66 | resolve();
|
67 | }
|
68 | }));
|
69 | }
|
70 | if (this.config.gateway.protocol.https) {
|
71 | expected++;
|
72 | const httpsServer = this.createHttpsServer(this.app);
|
73 | this.apiServer.set('https', httpsServer.listen(_.toSafeInteger(this.config.gateway.protocol.https.listenPort), () => {
|
74 | this.logger.info(`Gateway listenning HTTPS on port ${this.config.gateway.protocol.https.listenPort}`);
|
75 | started++;
|
76 | if (started === expected) {
|
77 | this.serverRunning = true;
|
78 | this.emit('start', this);
|
79 | resolve();
|
80 | }
|
81 | }));
|
82 | }
|
83 | })
|
84 | .catch((err) => {
|
85 | reject(err);
|
86 | });
|
87 | });
|
88 | }
|
89 | startAdmin() {
|
90 | return new Promise((resolve, reject) => {
|
91 | if (!this.config.gateway.admin) {
|
92 | return resolve();
|
93 | }
|
94 | if (this.adminApp) {
|
95 | this.adminServer = new Map();
|
96 | let started = 0;
|
97 | let expected = 0;
|
98 | if (this.config.gateway.admin.protocol.http) {
|
99 | expected++;
|
100 | const httpServer = http.createServer(this.adminApp);
|
101 | httpServer.timeout = time_intervals_1.getMilisecondsInterval(this.config.gateway.timeout, 60000);
|
102 | this.adminServer.set('http', httpServer.listen(_.toSafeInteger(this.config.gateway.admin.protocol.http.listenPort), () => {
|
103 | this.logger.info(`Gateway Admin Server listenning HTTP on port ${this.config.gateway.admin.protocol.http.listenPort}`);
|
104 | started++;
|
105 | if (started === expected) {
|
106 | this.emit('admin-start', this);
|
107 | resolve();
|
108 | }
|
109 | }));
|
110 | }
|
111 | if (this.config.gateway.admin.protocol.https) {
|
112 | expected++;
|
113 | const httpsServer = this.createHttpsServer(this.adminApp);
|
114 | httpsServer.timeout = time_intervals_1.getMilisecondsInterval(this.config.gateway.timeout, 60000);
|
115 | this.adminServer.set('https', httpsServer.listen(_.toSafeInteger(this.config.gateway.admin.protocol.https.listenPort), () => {
|
116 | this.logger.info(`Gateway Admin Server listenning HTTPS on port ${this.config.gateway.admin.protocol.https.listenPort}`);
|
117 | started++;
|
118 | if (started === expected) {
|
119 | this.emit('admin-start', this);
|
120 | resolve();
|
121 | }
|
122 | }));
|
123 | }
|
124 | }
|
125 | else {
|
126 | reject('You must start the Tree-Gateway before.');
|
127 | }
|
128 | });
|
129 | }
|
130 | stop() {
|
131 | return new Promise((resolve, reject) => {
|
132 | if (this.apiServer) {
|
133 | let toClose = this.apiServer.size;
|
134 | if (toClose === 0) {
|
135 | this.serverRunning = false;
|
136 | this.apiPipeline.clearRoutes();
|
137 | this.emit('stop', this);
|
138 | return resolve();
|
139 | }
|
140 | this.apiServer.forEach(server => {
|
141 | server.close(() => {
|
142 | toClose--;
|
143 | if (toClose === 0) {
|
144 | this.logger.info('Gateway server stopped');
|
145 | this.serverRunning = false;
|
146 | this.apiPipeline.clearRoutes();
|
147 | this.emit('stop', this);
|
148 | resolve();
|
149 | }
|
150 | });
|
151 | });
|
152 | this.apiServer = null;
|
153 | }
|
154 | else {
|
155 | this.serverRunning = false;
|
156 | this.apiPipeline.clearRoutes();
|
157 | this.emit('stop', this);
|
158 | resolve();
|
159 | }
|
160 | });
|
161 | }
|
162 | stopAdmin() {
|
163 | return new Promise((resolve, reject) => {
|
164 | if (this.adminServer) {
|
165 | let toClose = this.adminServer.size;
|
166 | if (toClose === 0) {
|
167 | this.emit('admin-stop', this);
|
168 | return resolve();
|
169 | }
|
170 | this.adminServer.forEach(server => {
|
171 | server.close(() => {
|
172 | toClose--;
|
173 | if (toClose === 0) {
|
174 | this.logger.info('Gateway Admin server stopped');
|
175 | this.emit('admin-stop', this);
|
176 | resolve();
|
177 | }
|
178 | });
|
179 | });
|
180 | this.adminServer = null;
|
181 | }
|
182 | else {
|
183 | resolve();
|
184 | }
|
185 | });
|
186 | }
|
187 | async restart() {
|
188 | this.logger.info(`Gateway is restarting...`);
|
189 | await this.stopAdmin();
|
190 | await this.stop();
|
191 | await this.start();
|
192 | await this.startAdmin();
|
193 | }
|
194 | createHttpsServer(app) {
|
195 | const privateKey = fs.readFileSync(this.config.gateway.protocol.https.privateKey, 'utf8');
|
196 | const certificate = fs.readFileSync(this.config.gateway.protocol.https.certificate, 'utf8');
|
197 | const credentials = { key: privateKey, cert: certificate };
|
198 | const https = require('https');
|
199 | return https.createServer(credentials, app);
|
200 | }
|
201 | async loadApis() {
|
202 | const configs = await this.configService.getAllApiConfig();
|
203 | await this.apiPipeline.loadApis(configs, this.server);
|
204 | }
|
205 | async reloadApis() {
|
206 | this.emit('api-reload', this);
|
207 | await this.loadApis();
|
208 | }
|
209 | updateConfig(packageId, needsReload) {
|
210 | if (needsReload) {
|
211 | this.config.reload()
|
212 | .then(() => {
|
213 | this.logger.info(`Configuration reloaded. Restarting server...`);
|
214 | return this.restart();
|
215 | })
|
216 | .then(() => {
|
217 | this.logger.info(`Server restarted.`);
|
218 | this.logger.info(`Configuration package ${packageId} applied successfuly.`);
|
219 | })
|
220 | .catch((error) => {
|
221 | this.logger.error(`Error updating gateway config.`);
|
222 | this.logger.inspectObject(error);
|
223 | });
|
224 | }
|
225 | else {
|
226 | this.configService.installAllMiddlewares()
|
227 | .then(() => this.reloadApis())
|
228 | .then(() => {
|
229 | this.logger.info(`Configuration package ${packageId} applied successfuly.`);
|
230 | })
|
231 | .catch(err => {
|
232 | this.logger.error(`Error applying configuration package ${packageId}.`);
|
233 | this.logger.inspectObject(err);
|
234 | });
|
235 | }
|
236 | }
|
237 | async initialize() {
|
238 | try {
|
239 | this.app = express();
|
240 | await this.configureServer();
|
241 | await this.configService.removeAllListeners()
|
242 | .on(events_1.ConfigEvents.CONFIG_UPDATED, (packageId, needsReload) => this.updateConfig(packageId, needsReload))
|
243 | .on(events_1.ConfigEvents.CIRCUIT_CHANGED, (id, state) => this.apiPipeline.circuitChanged(id, state))
|
244 | .subscribeEvents();
|
245 | await this.configureAdminServer();
|
246 | if (this.requestLogger.isGatewayRequestLogEnabled()) {
|
247 | this.requestLogger.initialize();
|
248 | }
|
249 | }
|
250 | catch (err) {
|
251 | this.logger.error(`Error configuring gateway server. Config File:\n${JSON.stringify(this.config.gateway)}`);
|
252 | this.logger.inspectObject(err);
|
253 | throw err;
|
254 | }
|
255 | }
|
256 | async configureServer() {
|
257 | this.app.disable('x-powered-by');
|
258 | if (!this.config.gateway.disableCompression) {
|
259 | this.app.use(compression());
|
260 | }
|
261 | if (this.config.gateway.underProxy) {
|
262 | this.app.enable('trust proxy');
|
263 | }
|
264 | if (this.config.gateway.accessLogger) {
|
265 | express_logger_1.AccessLogger.configureAccessLoger(this.config.gateway.accessLogger, this.config.rootPath, this.app, './logs');
|
266 | }
|
267 | this.configureHealthcheck();
|
268 | await this.configService.installAllMiddlewares();
|
269 | await this.serviceDiscovery.loadServiceDiscoveryProviders(this.config.gateway);
|
270 | this.apiPipeline.buildGatewayFilters(this.app, this.config.gateway.filter);
|
271 | await this.loadApis();
|
272 | }
|
273 | configureHealthcheck() {
|
274 | if (this.config.gateway.healthcheck) {
|
275 | this.app.get(this.config.gateway.healthcheck, (req, res) => {
|
276 | res.write('OK');
|
277 | res.end();
|
278 | });
|
279 | }
|
280 | }
|
281 | configureAdminServer() {
|
282 | if (this.config.gateway.admin) {
|
283 | this.adminApp = express();
|
284 | this.adminApp.disable('x-powered-by');
|
285 | if (!this.config.gateway.disableCompression) {
|
286 | this.adminApp.use(compression());
|
287 | }
|
288 | if (this.config.gateway.admin.accessLogger) {
|
289 | express_logger_1.AccessLogger.configureAccessLoger(this.config.gateway.admin.accessLogger, this.config.rootPath, this.adminApp, './logs/admin');
|
290 | }
|
291 | this.apiPipeline.buildGatewayFilters(this.adminApp, this.config.gateway.admin.filter);
|
292 | this.configureAdminCors();
|
293 | this.configureApiDocs();
|
294 | users_1.UsersRest.configureAuthMiddleware(this.adminApp);
|
295 | typescript_rest_1.Server.buildServices(this.adminApp, ...admin_api_1.default);
|
296 | this.adminApp.use((err, req, res, next) => {
|
297 | if (err instanceof typescript_rest_1.HttpError) {
|
298 | if (res.headersSent) {
|
299 | return next(err);
|
300 | }
|
301 | res.status(err.statusCode);
|
302 | res.json({ error: err.message, code: err.statusCode });
|
303 | }
|
304 | else {
|
305 | next(err);
|
306 | }
|
307 | });
|
308 | }
|
309 | }
|
310 | configureAdminCors() {
|
311 | if (this.config.gateway.admin.cors) {
|
312 | this.adminApp.use(this.apiPipeline.configureCors(this.config.gateway.admin.cors));
|
313 | }
|
314 | }
|
315 | configureApiDocs() {
|
316 | if (this.config.gateway.admin.apiDocs) {
|
317 | const isTest = process.env.NODE_ENV === 'test';
|
318 | const schemes = (this.config.gateway.admin.protocol.https ? ['https'] : ['http']);
|
319 | const swaggerFile = isTest ?
|
320 | './dist/admin/api/swagger.json' :
|
321 | path.join(__dirname, './admin/api/swagger.json');
|
322 | const apiPath = this.config.gateway.admin.apiDocs.path;
|
323 | const apiHost = this.config.gateway.admin.apiDocs.host;
|
324 | typescript_rest_1.Server.swagger(this.adminApp, swaggerFile, apiPath, apiHost, schemes);
|
325 | }
|
326 | }
|
327 | };
|
328 | __decorate([
|
329 | typescript_ioc_1.Inject,
|
330 | __metadata("design:type", configuration_1.Configuration)
|
331 | ], Gateway.prototype, "config", void 0);
|
332 | __decorate([
|
333 | typescript_ioc_1.Inject,
|
334 | __metadata("design:type", logger_1.Logger)
|
335 | ], Gateway.prototype, "logger", void 0);
|
336 | __decorate([
|
337 | typescript_ioc_1.Inject,
|
338 | __metadata("design:type", request_1.RequestLogger)
|
339 | ], Gateway.prototype, "requestLogger", void 0);
|
340 | __decorate([
|
341 | typescript_ioc_1.Inject,
|
342 | __metadata("design:type", config_1.ConfigService)
|
343 | ], Gateway.prototype, "configService", void 0);
|
344 | __decorate([
|
345 | typescript_ioc_1.Inject,
|
346 | __metadata("design:type", service_discovery_1.ServiceDiscovery)
|
347 | ], Gateway.prototype, "serviceDiscovery", void 0);
|
348 | __decorate([
|
349 | typescript_ioc_1.Inject,
|
350 | __metadata("design:type", api_1.ApiPileline)
|
351 | ], Gateway.prototype, "apiPipeline", void 0);
|
352 | Gateway = __decorate([
|
353 | typescript_ioc_1.Singleton,
|
354 | typescript_ioc_1.AutoWired,
|
355 | __metadata("design:paramtypes", [])
|
356 | ], Gateway);
|
357 | exports.Gateway = Gateway;
|
358 |
|
\ | No newline at end of file |