diff --git a/.gitignore b/.gitignore index bf11da8c85..f149bb21f9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ docs/plugins.png package-lock.json yarn.lock !test/fixtures/apps/loader-plugin/node_modules +.editorconfig diff --git a/lib/application.js b/lib/application.js index 387e0f9e0f..d261986c07 100644 --- a/lib/application.js +++ b/lib/application.js @@ -18,6 +18,20 @@ const EGG_LOADER = Symbol.for('egg#loader'); const EGG_PATH = Symbol.for('egg#eggPath'); const CLUSTER_CLIENTS = Symbol.for('egg#clusterClients'); +// client error => 400 Bad Request +// Refs: https://nodejs.org/dist/latest-v8.x/docs/api/http.html#http_event_clienterror +const DEFAULT_BAD_REQUEST_HTML = ` + 400 Bad Request + +

400 Bad Request

+
+ + `; +const DEFAULT_BAD_REQUEST_HTML_LENGTH = Buffer.byteLength(DEFAULT_BAD_REQUEST_HTML); +const DEFAULT_BAD_REQUEST_RESPONSE = + `HTTP/1.1 400 Bad Request\r\nContent-Length: ${DEFAULT_BAD_REQUEST_HTML_LENGTH}` + + `\r\n\r\n${DEFAULT_BAD_REQUEST_HTML}`; + /** * Singleton instance in App Worker, extend {@link EggApplication} * @extends EggApplication @@ -54,6 +68,18 @@ class Application extends EggApplication { return path.join(__dirname, '..'); } + onClientError(err, socket) { + this.logger.error('A client (%s:%d) error [%s] occurred: %s', + socket.remoteAddress, + socket.remotePort, + err.code, + err.message); + + // because it's a raw socket object, we should return the raw HTTP response + // packet. + socket.end(DEFAULT_BAD_REQUEST_RESPONSE); + } + onServer(server) { /* istanbul ignore next */ graceful({ @@ -65,6 +91,8 @@ class Application extends EggApplication { this.coreLogger.error(err); }, }); + + server.on('clientError', (err, socket) => this.onClientError(err, socket)); } /** @@ -245,7 +273,7 @@ class Application extends EggApplication { ctx.coreLogger.error(err); }); // expose server to support websocket - this.on('server', server => this.onServer(server)); + this.once('server', server => this.onServer(server)); } /** diff --git a/test/lib/cluster/app_worker.test.js b/test/lib/cluster/app_worker.test.js index 2624833516..711a1aa8b5 100644 --- a/test/lib/cluster/app_worker.test.js +++ b/test/lib/cluster/app_worker.test.js @@ -19,6 +19,49 @@ describe('test/lib/cluster/app_worker.test.js', () => { .expect('true'); }); + it('should response 400 bad request when HTTP request packet broken', async () => { + const test1 = app.httpRequest() + // Node.js (http-parser) will occur an error while the raw URI in HTTP + // request packet containing space. + // + // Refs: https://zhuanlan.zhihu.com/p/31966196 + .get('/foo bar'); + const test2 = app.httpRequest().get('/foo baz'); + + // app.httpRequest().expect() will encode the uri so that we cannot + // request the server with raw `/foo bar` to emit 400 status code. + // + // So we generate `test.req` via `test.request()` first and override the + // encoded uri. + // + // `test.req` will only generated once: + // + // ``` + // function Request::request() { + // if (this.req) return this.req; + // + // // code to generate this.req + // + // return this.req; + // } + // ``` + test1.request().path = '/foo bar'; + test2.request().path = '/foo baz'; + + const html = ` + 400 Bad Request + +

400 Bad Request

+
+ + `; + + await Promise.all([ + test1.expect(html).expect(400), + test2.expect(html).expect(400), + ]); + }); + describe('listen hostname', () => { let app; before(() => {