From cbe565130462661a097f9ae409f7d0c248d4ca18 Mon Sep 17 00:00:00 2001 From: gucheen Date: Fri, 19 Apr 2024 14:07:42 +0800 Subject: [PATCH 1/3] feat: add method to kill traceroute process and fix desitination parsing --- src/process.ts | 63 ++++++++++++++++++++++++++++----------------- test/index.spec.ts | 64 +++++++++++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 50 deletions(-) diff --git a/src/process.ts b/src/process.ts index 9843c83..1ebdc67 100644 --- a/src/process.ts +++ b/src/process.ts @@ -2,7 +2,8 @@ import events from 'events'; import readline from 'readline'; import validator from 'validator'; -import { spawn } from 'child_process'; +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import { Readable } from 'stream'; export interface Hop { hop: number; @@ -13,10 +14,35 @@ export interface Hop { } export abstract class Process extends events.EventEmitter { + process: ChildProcessWithoutNullStreams|null = null + constructor(private command: string, private args: string[]) { super(); } + private readProcessStd(input: Readable) { + let isDestinationCaptured = false + readline.createInterface({ + input, + terminal: false + }) + .on('line', (line) => { + if (!isDestinationCaptured) { + const destination = this.parseDestination(line); + if (destination !== null) { + this.emit('destination', destination); + + isDestinationCaptured = true; + } + } + + const hop = this.parseHop(line); + if (hop !== null) { + this.emit('hop', hop); + } + }); + } + public trace(domainName: string): void { if (!this.isValidDomainName(domainName)) { throw "Invalid domain name or IP address"; @@ -24,34 +50,23 @@ export abstract class Process extends events.EventEmitter { this.args.push(domainName); - const process = spawn(this.command, this.args); - process.on('close', (code) => { + this.process = spawn(this.command, this.args); + this.process.on('close', (code) => { this.emit('close', code); }); - this.emit('pid', process.pid); - - let isDestinationCaptured = false; - if (process.pid) { - readline.createInterface({ - input: process.stdout, - terminal: false - }) - .on('line', (line) => { - if (!isDestinationCaptured) { - const destination = this.parseDestination(line); - if (destination !== null) { - this.emit('destination', destination); + this.emit('pid', this.process.pid); - isDestinationCaptured = true; - } - } + if (this.process.pid) { + this.readProcessStd(this.process.stdout) + this.readProcessStd(this.process.stderr) + } + } - const hop = this.parseHop(line); - if (hop !== null) { - this.emit('hop', hop); - } - }); + public kill(signal?: NodeJS.Signals | number) { + if (this.process) { + this.process.kill(signal) + this.removeAllListeners() } } diff --git a/test/index.spec.ts b/test/index.spec.ts index f6d5286..3c8b2f8 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -3,29 +3,41 @@ import validator from 'validator'; import Traceroute from '../src/index'; describe('Traceroute', () => { - it('should verify pid, destination, hops and close code', (wait) => { - const tracer = new Traceroute(); - - tracer - .on('pid', (pid) => { - expect(Number.isInteger(pid)).toBeTruthy(); - }) - .on('destination', (destination) => { - expect(validator.isIP(destination)).toBeTruthy(); - }) - .on('hop', (hopObj) => { - const { hop, ip, rtt1 } = hopObj; - - expect(Number.isInteger(hop)).toBeTruthy(); - expect(validator.isIP(ip) || ip === '*').toBeTruthy(); - expect(/^\d+\.\d+\sms$/.test(rtt1) || rtt1 === '*').toBeTruthy(); - }) - .on('close', (code) => { - expect(Number.isInteger(code)).toBeTruthy(); - - wait(); - }); - - tracer.trace('github.com'); - }, 60000); -}); \ No newline at end of file + it('should verify pid, destination, hops and close code', (wait) => { + const tracer = new Traceroute(); + + tracer + .on('pid', (pid) => { + expect(Number.isInteger(pid)).toBeTruthy(); + }) + .on('destination', (destination) => { + expect(validator.isIP(destination)).toBeTruthy(); + }) + .on('hop', (hopObj) => { + const { hop, ip, rtt1 } = hopObj; + + expect(Number.isInteger(hop)).toBeTruthy(); + expect(validator.isIP(ip) || ip === '*').toBeTruthy(); + expect(/^\d+\.\d+\sms$/.test(rtt1) || rtt1 === '*').toBeTruthy(); + }) + .on('close', (code) => { + expect(Number.isInteger(code)).toBeTruthy(); + + wait(); + }); + + tracer.trace('github.com'); + }, 60000); + + it('should exit trace by calling kill method', (done) => { + const tracer = new Traceroute(); + + tracer.trace('github.com'); + + setTimeout(() => { + tracer.kill() + expect(tracer.process?.killed).toBeTruthy() + done() + }, 5000) + }, 10000); +}); From fad1994918c1bf1a2a67007067d390b37375f186 Mon Sep 17 00:00:00 2001 From: gucheen Date: Fri, 19 Apr 2024 14:10:38 +0800 Subject: [PATCH 2/3] docs: kill method to terminate traceroute --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d29dd26..0b5abfc 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,11 @@ try { }); tracer.trace('github.com'); + + // and you can terminate traceroute by calling `kill` method + setTimeout(() => { + tracer.kill() + }, 5000) } catch (ex) { console.log(ex); } From e3bcff0ce11c69bd9810453144828d05e36061b7 Mon Sep 17 00:00:00 2001 From: gucheen Date: Wed, 24 Apr 2024 10:15:23 +0800 Subject: [PATCH 3/3] feat: kill method return result --- src/process.ts | 3 ++- test/index.spec.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/process.ts b/src/process.ts index 1ebdc67..85ea4fc 100644 --- a/src/process.ts +++ b/src/process.ts @@ -65,8 +65,9 @@ export abstract class Process extends events.EventEmitter { public kill(signal?: NodeJS.Signals | number) { if (this.process) { - this.process.kill(signal) + const result = this.process.kill(signal) this.removeAllListeners() + return result } } diff --git a/test/index.spec.ts b/test/index.spec.ts index 3c8b2f8..fe52751 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -35,8 +35,9 @@ describe('Traceroute', () => { tracer.trace('github.com'); setTimeout(() => { - tracer.kill() + const killResult = tracer.kill() expect(tracer.process?.killed).toBeTruthy() + expect(killResult).toBeTruthy() done() }, 5000) }, 10000);