diff --git a/packages/hook/src/patch/HttpServer.ts b/packages/hook/src/patch/HttpServer.ts index e288128d..e88e014f 100644 --- a/packages/hook/src/patch/HttpServer.ts +++ b/packages/hook/src/patch/HttpServer.ts @@ -162,24 +162,36 @@ export class HttpServerPatcher extends Patcher { tracer.named(`HTTP-${tags['http.method'].value}:${tags['http.url'].value}`); tracer.setCurrentSpan(span); - res.once('finish', () => { + function onFinishedFactory(eventName) { + return function onFinished() { + res.removeListener('finish', onFinished); + req.removeListener('aborted', onFinished); + + if (eventName !== 'aborted' && options.recordPostData && req.method && req.method.toUpperCase() === 'POST') { + const transformer = options.bufferTransformer || self.bufferTransformer; + const postData = transformer(chunks); + + span.log({ + data: postData + }); + // clear cache + chunks = []; + } + + span.setTag('http.aborted', { + type: 'bool', + value: eventName === 'aborted' + }); - if (options.recordPostData && req.method && req.method.toUpperCase() === 'POST') { - const transformer = options.bufferTransformer || self.bufferTransformer; - const postData = transformer(chunks); + self.beforeFinish(span, res); + span.finish(); + tracer.finish(options); + self.afterFinish(span, res); + }; + } - span.log({ - data: postData - }); - // clear cache - chunks = []; - } - - self.beforeFinish(span, res); - span.finish(); - tracer.finish(options); - self.afterFinish(span, res); - }); + res.once('finish', onFinishedFactory('finish')); + req.once('aborted', onFinishedFactory('aborted')); return requestListener(req, res); }); diff --git a/packages/hook/test/fixtures/http-when-request-abort/index.ts b/packages/hook/test/fixtures/http-when-request-abort/index.ts new file mode 100644 index 00000000..1c027468 --- /dev/null +++ b/packages/hook/test/fixtures/http-when-request-abort/index.ts @@ -0,0 +1,42 @@ +import { RunUtil } from '../../RunUtil'; +import * as assert from 'assert'; +import { HttpServerPatcher } from '../../../src/patch/HttpServer'; + +const httpServerPatcher = new HttpServerPatcher(); + +RunUtil.run(function(done) { + httpServerPatcher.run(); + const http = require('http'); + const httpclient = require('urllib').create(); + + process.on( 'PANDORA_PROCESS_MESSAGE_TRACE', (report: any) => { + assert(report.name === 'HTTP-GET:/'); + assert(report.spans.length === 1); + assert(report.duration <= 1000); + const span = report.spans[0]; + const tag = span.tags['http.aborted']; + assert(tag.value); + + done(); + }); + + const server = http.createServer((req, res) => { + + setTimeout(function() { + res.end('hello'); + }, 3000); + + }); + + server.listen(0, () => { + const port = server.address().port; + + const req = httpclient.request(`http://localhost:${port}`, function(err, body) { + console.log('body size: %d', body.length); + }); + + setTimeout(function() { + req.abort(); + }, 1000); + }); +}); diff --git a/packages/hook/test/fixtures/http-when-request-abort/package.json b/packages/hook/test/fixtures/http-when-request-abort/package.json new file mode 100644 index 00000000..9051d5b8 --- /dev/null +++ b/packages/hook/test/fixtures/http-when-request-abort/package.json @@ -0,0 +1,8 @@ +{ + "name": "http-trace-test", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "urllib": "^2.25.1" + } +} diff --git a/packages/hook/test/fixtures/http/index.ts b/packages/hook/test/fixtures/http/index.ts index f85f76fb..d6a7bc25 100644 --- a/packages/hook/test/fixtures/http/index.ts +++ b/packages/hook/test/fixtures/http/index.ts @@ -19,6 +19,9 @@ RunUtil.run(function(done) { assert(report.name === 'HTTP-GET:/'); assert(report.spans.length > 0); assert(report.status === NORMAL_TRACE); + const span = report.spans[0]; + const tag = span.tags['http.aborted']; + assert(!tag.value); done(); }); diff --git a/packages/hook/test/index.test.ts b/packages/hook/test/index.test.ts index 71248705..2a87ea6d 100644 --- a/packages/hook/test/index.test.ts +++ b/packages/hook/test/index.test.ts @@ -78,6 +78,10 @@ describe('unit test', () => { it('should record http post data and query params', done => { fork('http-record-query-and-data', done); }); + + it('should finish span when request aborted', done => { + fork('http-when-request-abort', done); + }); }); describe('http client', () => {