UNPKG

4.43 kBJavaScriptView Raw
1'use strict';
2
3const crypto = require("crypto");
4const _ = require('lodash');
5const domain = require('domain');
6const cookie = require('cookie');
7const urlParser = require('url');
8const onFinished = require('on-finished');
9
10const Service = require('./service');
11
12class 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 // catch async express errors
27 const reqDomain = domain.create();
28 reqDomain.on('error', next);
29
30 // set real ip
31 req.connection.ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
32
33 // set request id
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 // attach logger to request
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 // skip anything not marked as an internal server error
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 // headers:
86 //
87 // node: req.headers
88 // express: req.headers
89 // koa: req.header
90 //
91 http.headers = req.headers || req.header || {};
92
93 // method:
94 //
95 // node: req.method
96 // express: req.method
97 // koa: req.method
98 //
99 http.method = req.method;
100
101 // host:
102 //
103 // node: req.headers.host
104 // express: req.hostname in > 4 and req.host in < 4
105 // koa: req.host
106 //
107 http.host = req.hostname || req.host || http.headers.host || '<no host>';
108
109 // protocol:
110 //
111 // node: <n/a>
112 // express: req.protocol
113 // koa: req.protocol
114 //
115 http.protocol = 'https' === req.protocol || true === req.secure || true === (req.socket || {}).encrypted ? 'https' : 'http';
116
117 // url (including path and query string):
118 //
119 // node: req.originalUrl
120 // express: req.originalUrl
121 // koa: req.url
122 //
123 http.originalUrl = req.originalUrl || req.url;
124
125 // absolute url
126 http.url = http.protocol + '://' + http.host + http.originalUrl;
127
128 // query string
129 //
130 // node: req.url (raw)
131 // express: req.query
132 // koa: req.query
133 //
134 http.query = req.query || urlParser.parse(req.originalUrl || '', true).query;
135
136 // cookies:
137 //
138 // node: req.headers.cookie
139 // express: req.headers.cookie
140 // koa: req.headers.cookie
141 //
142 http.cookies = cookie.parse(req.headers.cookie || '');
143
144 // body data:
145 //
146 // node: req.body
147 // express: req.body
148 // koa: req.body
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 // Make sure the request body is a string
159 http.data = JSON.stringify(http.data);
160 }
161
162 // client ip:
163 //
164 // node: req.connection.remoteAddress
165 // express: req.ip
166 // koa: req.ip
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
182module.exports = ExpressHandler;