1 | 'use strict';
|
2 |
|
3 | const crypto = require("crypto");
|
4 | const _ = require('lodash');
|
5 | const domain = require('domain');
|
6 | const cookie = require('cookie');
|
7 | const urlParser = require('url');
|
8 | const onFinished = require('on-finished');
|
9 |
|
10 | const Service = require('./service');
|
11 |
|
12 | class ExpressHandler extends Service {
|
13 | register(server) {
|
14 | this.server = server;
|
15 | }
|
16 |
|
17 | get defaultOptions() {
|
18 | return {
|
19 | defaultLogFields: ['method', 'host', 'protocol', 'url', 'ip'],
|
20 | logFields: []
|
21 | };
|
22 | }
|
23 |
|
24 | get preHandler() {
|
25 | return (req, res, next) => {
|
26 |
|
27 | const reqDomain = domain.create();
|
28 | reqDomain.on('error', next);
|
29 |
|
30 |
|
31 | req.connection.ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
|
32 |
|
33 |
|
34 | req.id = req.headers['x-request-id'] || crypto.randomBytes(16).toString("hex");
|
35 |
|
36 | const http = _.cloneDeep(this._filterFields(
|
37 | this._parseRequest(req),
|
38 | this.options.logFields.concat(this.options.defaultLogFields)
|
39 | ));
|
40 |
|
41 |
|
42 | req.logger = this.context.logger;
|
43 |
|
44 | let start;
|
45 | onFinished(res, (err, res) => {
|
46 | const end = new Date() - start;
|
47 | this.context.logger.info(
|
48 | `${http.method} ${http.url} ${res.statusCode} ${end}ms`,
|
49 | _.extend(http, {time: end, status: res.statusCode})
|
50 | );
|
51 | });
|
52 |
|
53 | this.context.logger.debug(`${http.method} ${http.url}`, http);
|
54 | start = new Date();
|
55 | return reqDomain.run(next);
|
56 | };
|
57 | }
|
58 |
|
59 | get postHandler() {
|
60 | return (err, req, res, next) => {
|
61 | const status = err.status || err.statusCode || err.status_code || 500;
|
62 |
|
63 |
|
64 | if (status < 500) {
|
65 | return next(err);
|
66 | }
|
67 |
|
68 | const info = this._parseRequest(req);
|
69 |
|
70 | this.context.error.capture(err, info).finally(() => next(err, req, res));
|
71 | };
|
72 | }
|
73 |
|
74 | _filterFields(obj, fields) {
|
75 | const result = {};
|
76 | _.forEach(fields, field => {
|
77 | _.set(result, field, _.get(obj, field));
|
78 | });
|
79 | return result;
|
80 | }
|
81 |
|
82 | _parseRequest(req) {
|
83 | var http = {};
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | http.headers = req.headers || req.header || {};
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | http.method = req.method;
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | http.host = req.hostname || req.host || http.headers.host || '<no host>';
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | http.protocol = 'https' === req.protocol || true === req.secure || true === (req.socket || {}).encrypted ? 'https' : 'http';
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 | http.originalUrl = req.originalUrl || req.url;
|
124 |
|
125 |
|
126 | http.url = http.protocol + '://' + http.host + http.originalUrl;
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | http.query = req.query || urlParser.parse(req.originalUrl || '', true).query;
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | http.cookies = cookie.parse(req.headers.cookie || '');
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 | http.data = req.body;
|
151 | if (['GET', 'HEAD'].indexOf(req.method) === -1) {
|
152 | if (typeof http.data === 'undefined') {
|
153 | http.data = '<unavailable>';
|
154 | }
|
155 | }
|
156 |
|
157 | if (http.data && {}.toString.call(http.data) !== '[object String]') {
|
158 |
|
159 | http.data = JSON.stringify(http.data);
|
160 | }
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | http.ip = req.ip || (req.connection || {}).remoteAddress;
|
169 |
|
170 | return http;
|
171 | }
|
172 |
|
173 | dispose() {
|
174 | if (this.server) {
|
175 | return new Promise(res => this.server.close(() => res()));
|
176 | }
|
177 |
|
178 | return Promise.resolve();
|
179 | }
|
180 | }
|
181 |
|
182 | module.exports = ExpressHandler;
|