Skip to content

Commit

Permalink
feat: add 400 response for broken client request to instead of empty …
Browse files Browse the repository at this point in the history
…response (#1829)
  • Loading branch information
XadillaX authored and dead-horse committed Dec 15, 2017
1 parent d0ee9f2 commit 40df153
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ docs/plugins.png
package-lock.json
yarn.lock
!test/fixtures/apps/loader-plugin/node_modules
.editorconfig
30 changes: 29 additions & 1 deletion lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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 = `<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>❤</center>
</body>
</html>`;
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
Expand Down Expand Up @@ -55,6 +69,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({
Expand All @@ -66,6 +92,8 @@ class Application extends EggApplication {
this.coreLogger.error(err);
},
});

server.on('clientError', (err, socket) => this.onClientError(err, socket));
}

/**
Expand Down Expand Up @@ -247,7 +275,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));
}

/**
Expand Down
43 changes: 43 additions & 0 deletions test/lib/cluster/app_worker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>❤</center>
</body>
</html>`;

await Promise.all([
test1.expect(html).expect(400),
test2.expect(html).expect(400),
]);
});

describe('listen hostname', () => {
let app;
before(() => {
Expand Down

0 comments on commit 40df153

Please sign in to comment.