1 |
|
2 | 'use strict';
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | const isGeneratorFunction = require('is-generator-function');
|
9 | const debug = require('debug')('koa:application');
|
10 | const onFinished = require('on-finished');
|
11 | const response = require('./response');
|
12 | const compose = require('koa-compose');
|
13 | const context = require('./context');
|
14 | const request = require('./request');
|
15 | const statuses = require('statuses');
|
16 | const Emitter = require('events');
|
17 | const util = require('util');
|
18 | const Stream = require('stream');
|
19 | const http = require('http');
|
20 | const only = require('only');
|
21 | const convert = require('koa-convert');
|
22 | const deprecate = require('depd')('koa');
|
23 | const { HttpError } = require('http-errors');
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | module.exports = class Application extends Emitter {
|
31 | |
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | |
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | constructor(options) {
|
50 | super();
|
51 | options = options || {};
|
52 | this.proxy = options.proxy || false;
|
53 | this.subdomainOffset = options.subdomainOffset || 2;
|
54 | this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
|
55 | this.maxIpsCount = options.maxIpsCount || 0;
|
56 | this.env = options.env || process.env.NODE_ENV || 'development';
|
57 | if (options.keys) this.keys = options.keys;
|
58 | this.middleware = [];
|
59 | this.context = Object.create(context);
|
60 | this.request = Object.create(request);
|
61 | this.response = Object.create(response);
|
62 | if (util.inspect.custom) {
|
63 | this[util.inspect.custom] = this.inspect;
|
64 | }
|
65 | }
|
66 |
|
67 | |
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | listen(...args) {
|
78 | debug('listen');
|
79 | const server = http.createServer(this.callback());
|
80 | return server.listen(...args);
|
81 | }
|
82 |
|
83 | |
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 | toJSON() {
|
92 | return only(this, [
|
93 | 'subdomainOffset',
|
94 | 'proxy',
|
95 | 'env'
|
96 | ]);
|
97 | }
|
98 |
|
99 | |
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | inspect() {
|
107 | return this.toJSON();
|
108 | }
|
109 |
|
110 | |
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | use(fn) {
|
121 | if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
|
122 | if (isGeneratorFunction(fn)) {
|
123 | deprecate('Support for generators will be removed in v3. ' +
|
124 | 'See the documentation for examples of how to convert old middleware ' +
|
125 | 'https://github.com/koajs/koa/blob/master/docs/migration.md');
|
126 | fn = convert(fn);
|
127 | }
|
128 | debug('use %s', fn._name || fn.name || '-');
|
129 | this.middleware.push(fn);
|
130 | return this;
|
131 | }
|
132 |
|
133 | |
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 | callback() {
|
142 | const fn = compose(this.middleware);
|
143 |
|
144 | if (!this.listenerCount('error')) this.on('error', this.onerror);
|
145 |
|
146 | const handleRequest = (req, res) => {
|
147 | const ctx = this.createContext(req, res);
|
148 | return this.handleRequest(ctx, fn);
|
149 | };
|
150 |
|
151 | return handleRequest;
|
152 | }
|
153 |
|
154 | |
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | handleRequest(ctx, fnMiddleware) {
|
161 | const res = ctx.res;
|
162 | res.statusCode = 404;
|
163 | const onerror = err => ctx.onerror(err);
|
164 | const handleResponse = () => respond(ctx);
|
165 | onFinished(res, onerror);
|
166 | return fnMiddleware(ctx).then(handleResponse).catch(onerror);
|
167 | }
|
168 |
|
169 | |
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | createContext(req, res) {
|
176 | const context = Object.create(this.context);
|
177 | const request = context.request = Object.create(this.request);
|
178 | const response = context.response = Object.create(this.response);
|
179 | context.app = request.app = response.app = this;
|
180 | context.req = request.req = response.req = req;
|
181 | context.res = request.res = response.res = res;
|
182 | request.ctx = response.ctx = context;
|
183 | request.response = response;
|
184 | response.request = request;
|
185 | context.originalUrl = request.originalUrl = req.url;
|
186 | context.state = {};
|
187 | return context;
|
188 | }
|
189 |
|
190 | |
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 | onerror(err) {
|
198 | if (!(err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err));
|
199 |
|
200 | if (404 == err.status || err.expose) return;
|
201 | if (this.silent) return;
|
202 |
|
203 | const msg = err.stack || err.toString();
|
204 | console.error();
|
205 | console.error(msg.replace(/^/gm, ' '));
|
206 | console.error();
|
207 | }
|
208 | };
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 | function respond(ctx) {
|
215 |
|
216 | if (false === ctx.respond) return;
|
217 |
|
218 | if (!ctx.writable) return;
|
219 |
|
220 | const res = ctx.res;
|
221 | let body = ctx.body;
|
222 | const code = ctx.status;
|
223 |
|
224 |
|
225 | if (statuses.empty[code]) {
|
226 |
|
227 | ctx.body = null;
|
228 | return res.end();
|
229 | }
|
230 |
|
231 | if ('HEAD' === ctx.method) {
|
232 | if (!res.headersSent && !ctx.response.has('Content-Length')) {
|
233 | const { length } = ctx.response;
|
234 | if (Number.isInteger(length)) ctx.length = length;
|
235 | }
|
236 | return res.end();
|
237 | }
|
238 |
|
239 |
|
240 | if (null == body) {
|
241 | if (ctx.req.httpVersionMajor >= 2) {
|
242 | body = String(code);
|
243 | } else {
|
244 | body = ctx.message || String(code);
|
245 | }
|
246 | if (!res.headersSent) {
|
247 | ctx.type = 'text';
|
248 | ctx.length = Buffer.byteLength(body);
|
249 | }
|
250 | return res.end(body);
|
251 | }
|
252 |
|
253 |
|
254 | if (Buffer.isBuffer(body)) return res.end(body);
|
255 | if ('string' == typeof body) return res.end(body);
|
256 | if (body instanceof Stream) return body.pipe(res);
|
257 |
|
258 |
|
259 | body = JSON.stringify(body);
|
260 | if (!res.headersSent) {
|
261 | ctx.length = Buffer.byteLength(body);
|
262 | }
|
263 | res.end(body);
|
264 | }
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | module.exports.HttpError = HttpError;
|