buddy-tunnel
Advanced tools
Comparing version 1.0.18-dev to 1.0.19-dev
{ | ||
"name": "buddy-tunnel", | ||
"preferGlobal": false, | ||
"version": "1.0.18-dev", | ||
"version": "1.0.19-dev", | ||
"type": "module", | ||
@@ -6,0 +6,0 @@ "license": "MIT", |
@@ -146,3 +146,4 @@ import EventEmitter from 'events'; | ||
currentConnections: Object.keys(t.connections).length, | ||
httpIdentified: t.identify ? t.identify.type : '' | ||
httpIdentified: t.identify ? t.identify.type : '', | ||
httpRequests: t.httpLog ? t.httpLog.getRequestsToSync() : [] | ||
}); | ||
@@ -149,0 +150,0 @@ }); |
@@ -139,4 +139,12 @@ import { | ||
httpVersion: logRequest.httpVersion, | ||
reqBodyTooLarge: logRequest.reqBodyTooLarge, | ||
reqBody: logRequest.reqBody.toString('base64'), | ||
requestBody: { | ||
data: logRequest.requestBody.data.toString('base64'), | ||
tooLarge: logRequest.requestBody.tooLarge, | ||
realLength: logRequest.requestBody.realLength | ||
}, | ||
responseBody: { | ||
data: logRequest.responseBody.data.toString('base64'), | ||
tooLarge: logRequest.responseBody.tooLarge, | ||
realLength: logRequest.responseBody.realLength | ||
}, | ||
headers: logRequest.headers, | ||
@@ -143,0 +151,0 @@ status: logRequest.status, |
@@ -56,3 +56,12 @@ import { ERR_TUNNEL_NOT_FOUND } from '../../texts.js'; | ||
requests: data.requests.map((r) => { | ||
r.reqBody = Buffer.from(r.reqBody, 'base64'); | ||
r.requestBody = { | ||
data: Buffer.from(r.requestBody.data, 'base64'), | ||
tooLarge: r.requestBody.tooLarge, | ||
realLength: r.requestBody.realLength | ||
}; | ||
r.responseBody = { | ||
data: Buffer.from(r.responseBody.data, 'base64'), | ||
tooLarge: r.responseBody.tooLarge, | ||
realLength: r.responseBody.realLength | ||
}; | ||
return r; | ||
@@ -85,5 +94,17 @@ }) | ||
if (!this.httpLog || !this.httpLog.requests || !this.log) return; | ||
/** | ||
* @type {object} | ||
*/ | ||
const r = this.httpLog.requests.find((r) => r.id === data.id); | ||
if (!r) { | ||
data.reqBody = Buffer.from(data.reqBody, 'base64'); | ||
data.requestBody = { | ||
data: Buffer.from(data.requestBody.data, 'base64'), | ||
tooLarge: data.requestBody.tooLarge, | ||
realLength: data.requestBody.realLength | ||
}; | ||
data.responseBody = { | ||
data: Buffer.from(data.responseBody.data, 'base64'), | ||
tooLarge: data.responseBody.tooLarge, | ||
realLength: data.responseBody.realLength | ||
}; | ||
this.httpLog.requests.unshift(data); | ||
@@ -94,4 +115,12 @@ } else { | ||
r.httpVersion = data.httpVersion; | ||
r.reqBodyTooLarge = data.reqBodyTooLarge; | ||
r.reqBody = Buffer.from(data.reqBody, 'base64'); | ||
r.requestBody = { | ||
data: Buffer.from(data.requestBody.data, 'base64'), | ||
tooLarge: data.requestBody.tooLarge, | ||
realLength: data.requestBody.realLength | ||
}; | ||
r.responseBody = { | ||
data: Buffer.from(data.responseBody.data, 'base64'), | ||
tooLarge: data.responseBody.tooLarge, | ||
realLength: data.responseBody.realLength | ||
}; | ||
r.headers = data.headers; | ||
@@ -98,0 +127,0 @@ r.status = data.status; |
@@ -11,3 +11,3 @@ import logger from '../logger.js'; | ||
const TIMEOUT = 10000; | ||
const TIMEOUT = 30000; | ||
@@ -14,0 +14,0 @@ const openSocket = (path) => { |
@@ -8,2 +8,3 @@ import { getBasicCommandTcp } from '../../tcp.js'; | ||
ERR_AGENT_NOT_ENABLED, | ||
OPTION_FOLLOW, | ||
OPTION_TARGET, | ||
@@ -14,5 +15,7 @@ TXT_OPENING_TUNNEL, | ||
import ApiAgent from '../../../api/agent.js'; | ||
import AgentSocketTunnel from '../../../agent/socket/tunnel.js'; | ||
const commandAgentTunnelTcp = getBasicCommandTcp(); | ||
commandAgentTunnelTcp.description(DESC_COMMAND_AGENT_TUNNEL_TCP); | ||
commandAgentTunnelTcp.option('-f, --follow', OPTION_FOLLOW); | ||
commandAgentTunnelTcp.argument('[host:port]', OPTION_TARGET); | ||
@@ -27,3 +30,10 @@ commandAgentTunnelTcp.action(async (target, options) => { | ||
const data = await ApiAgent.addTunnel(prepared); | ||
Output.exitSuccess(TXT_TUNNEL_STARTED(data.type)); | ||
if (options.follow) { | ||
const ws = await ApiAgent.socketTunnel(data.id); | ||
const tunnel = new AgentSocketTunnel(ws, data.id); | ||
await tunnel.waitForReady(); | ||
Output.tunnel(tunnel); | ||
} else { | ||
Output.exitSuccess(TXT_TUNNEL_STARTED(data.type)); | ||
} | ||
} catch (err) { | ||
@@ -30,0 +40,0 @@ Output.exitError(err); |
@@ -14,3 +14,5 @@ import { Command } from 'commander'; | ||
import AgentManager from './agent/manager.js'; | ||
import stream from 'stream'; | ||
stream.setDefaultHighWaterMark(false, 67108864); | ||
process.title = 'buddy-tunnel'; | ||
@@ -17,0 +19,0 @@ process.on('uncaughtException', (err) => { |
@@ -34,2 +34,12 @@ import { | ||
const formatBytes = (len) => { | ||
if (len < 1024) return `${len}B`; | ||
len = Math.round(len / 1024); | ||
if (len < 1024) return `${len}KB`; | ||
len = Math.round(len / 1024); | ||
if (len < 1024) return `${len}MB`; | ||
len = Math.round(len / 1024); | ||
return `${len}GB`; | ||
}; | ||
class OutputInteractiveTunnel { | ||
@@ -45,3 +55,3 @@ constructor(terminal, tunnel) { | ||
retry() { | ||
if (this.selectedRequest && !this.selectedRequest.reqBodyTooLarge) { | ||
if (this.selectedRequest && !this.selectedRequest.requestBody.tooLarge) { | ||
this.tunnel.retryHttpLogRequest(this.selectedRequest); | ||
@@ -66,2 +76,3 @@ } | ||
y += this.updateSpriteRequestInfo(y); | ||
this.updateSpriteRequestScrollable(); | ||
if (y < this.terminal.height) { | ||
@@ -111,49 +122,45 @@ this.viewPort.fill({ | ||
createSpriteRequestInfo() { | ||
if (this.tunnel.log) { | ||
this.sprites.requestInfo = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: this.terminal.height | ||
}); | ||
this.sprites.requestInfoScroll = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: REQUEST_INFO_START_Y, | ||
width: REQUEST_INFO_MAX_LENGTH, | ||
height: this.terminal.height | ||
}); | ||
} | ||
this.sprites.requestInfo = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: this.terminal.height | ||
}); | ||
this.sprites.requestInfoScroll = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: REQUEST_INFO_START_Y, | ||
width: REQUEST_INFO_MAX_LENGTH, | ||
height: this.terminal.height | ||
}); | ||
} | ||
createSpriteRequests() { | ||
if (this.tunnel.log) { | ||
let header = fillString('', ROW_LENGTH); | ||
header += '\n'; | ||
header += fillString('Requests (use arrows ↑↓ to inspect & `enter` to retry)', ROW_LENGTH); | ||
header += '\n'; | ||
header += fillString('-------------------------------------------------------------------------------------------------------', ROW_LENGTH); | ||
this.sprites.requestsHeader = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, header); | ||
this.sprites.requests = new TerminalKit.ScreenBuffer({ | ||
x: 10, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: REQUESTS_MAX | ||
}); | ||
this.sprites.requestsSelected = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: 10, | ||
height: REQUESTS_MAX | ||
}); | ||
this.sprites.requestsSelectedLast = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: 1 | ||
}); | ||
} | ||
let header = fillString('', ROW_LENGTH); | ||
header += '\n'; | ||
header += fillString('Requests (use arrows ↑↓ to inspect & `enter` to retry)', ROW_LENGTH); | ||
header += '\n'; | ||
header += fillString('-------------------------------------------------------------------------------------------------------', ROW_LENGTH); | ||
this.sprites.requestsHeader = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, header); | ||
this.sprites.requests = new TerminalKit.ScreenBuffer({ | ||
x: 10, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: REQUESTS_MAX | ||
}); | ||
this.sprites.requestsSelected = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: 10, | ||
height: REQUESTS_MAX | ||
}); | ||
this.sprites.requestsSelectedLast = new TerminalKit.ScreenBuffer({ | ||
x: 0, | ||
y: 0, | ||
width: ROW_LENGTH, | ||
height: 1 | ||
}); | ||
} | ||
@@ -200,3 +207,3 @@ | ||
if (this.selectedRequest) { | ||
this.sprites.requestInfoScroll.y = REQUEST_INFO_START_Y; | ||
this.sprites.requestInfoScroll.y = this.sprites.requestInfoScrollY; | ||
this.sprites.requestInfoScroll.fill({ | ||
@@ -210,6 +217,6 @@ char: ' ', | ||
const { query, path } = this.getPathAndQuery(this.selectedRequest.url); | ||
let msg = '^KHTTP version:^ '; | ||
msg += `^W${this.selectedRequest.httpVersion}^\n`; | ||
let msg = '^KStatus:^ '; | ||
msg += `^W${this.selectedRequest.status}^\n`; | ||
msg += '\n^KMethod:^ '; | ||
msg += `^W${this.selectedRequest.method}^\n`; | ||
msg += `^WHTTP ${this.selectedRequest.httpVersion} ${this.selectedRequest.method}^\n`; | ||
msg += '\n^KPath:^ '; | ||
@@ -224,12 +231,12 @@ msg += `^W${path}^\n`; | ||
} | ||
if (this.selectedRequest.reqBody.length > 0) { | ||
if (this.selectedRequest.requestBody.realLength > 0) { | ||
msg += '\n^WBody:^\n'; | ||
if (this.selectedRequest.reqBodyTooLarge || this.selectedRequest.reqBody.length > 20 * REQUEST_INFO_MAX_LENGTH) { | ||
msg += '^K<Too large>^\n'; | ||
if (this.selectedRequest.requestBody.tooLarge || this.selectedRequest.requestBody.realLength > 20 * REQUEST_INFO_MAX_LENGTH) { | ||
msg += `^K<Too large (${formatBytes(this.selectedRequest.requestBody.realLength)})>^\n`; | ||
} else { | ||
const isBinary = isBinaryFileSync(this.selectedRequest.reqBody); | ||
const isBinary = isBinaryFileSync(this.selectedRequest.requestBody.data); | ||
if (isBinary) { | ||
msg += '^K<Binary>^\n'; | ||
} else { | ||
msg += `^K${this.selectedRequest.reqBody.toString('utf8')}^\n`; | ||
msg += `^K${this.selectedRequest.requestBody.data.toString('utf8')}^\n`; | ||
} | ||
@@ -243,2 +250,22 @@ } | ||
}); | ||
if (this.selectedRequest.finished){ | ||
if (this.selectedRequest.responseBody.realLength > 0) { | ||
msg += '\n^WResponse body:^\n'; | ||
if (this.selectedRequest.responseBody.tooLarge || this.selectedRequest.responseBody.realLength > 20 * REQUEST_INFO_MAX_LENGTH) { | ||
msg += `^K<Too large (${formatBytes(this.selectedRequest.responseBody.realLength)})>^\n`; | ||
} else { | ||
const isBinary = isBinaryFileSync(this.selectedRequest.responseBody.data); | ||
if (isBinary) { | ||
msg += '^K<Binary>^\n'; | ||
} else { | ||
msg += `^K${this.selectedRequest.responseBody.data.toString('utf8')}^\n`; | ||
} | ||
} | ||
} | ||
msg += '\n^WResponse headers:^\n'; | ||
const headers = this.selectedRequest.responseHeaders || {}; | ||
Object.keys(headers).forEach((name) => { | ||
msg += `^K${name}:^ ^W${headers[name]}^\n`; | ||
}); | ||
} | ||
this.sprites.requestInfoScroll.put({ | ||
@@ -262,3 +289,3 @@ x: 0, | ||
width: ROW_LENGTH, | ||
height: this.terminal.height - y | ||
height: this.terminal.height | ||
} | ||
@@ -352,3 +379,3 @@ }); | ||
let selcol; | ||
if (this.selectedRequest.reqBodyTooLarge) { | ||
if (this.selectedRequest.requestBody.tooLarge) { | ||
last = '^R[ LARGE ]^ '; | ||
@@ -420,2 +447,3 @@ seltxt = '[ LARGE ]'; | ||
} | ||
this.sprites.requestInfoScrollY = REQUEST_INFO_START_Y; | ||
this.updateSpriteRequestScrollable(); | ||
@@ -435,3 +463,3 @@ } | ||
if (this.selectedRequest && this.sprites.requestInfoScroll) { | ||
this.sprites.requestInfoScroll.y -= 1; | ||
this.sprites.requestInfoScrollY -= 1; | ||
} | ||
@@ -536,9 +564,7 @@ } | ||
createSpriteIdentify() { | ||
if (this.tunnel.type === TUNNEL_HTTP) { | ||
this.sprites.identify = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('HTTP:', ROW_LENGTH)); | ||
} | ||
this.sprites.identify = TerminalKit.ScreenBuffer.createFromString({ | ||
attr: { | ||
color: COLOR_LIGHT_GRAY | ||
} | ||
}, fillString('HTTP:', ROW_LENGTH)); | ||
} | ||
@@ -545,0 +571,0 @@ |
@@ -51,3 +51,3 @@ import EventEmitter from 'events'; | ||
}); | ||
if (logRequest.reqBody.length > 0) req.write(logRequest.reqBody); | ||
if (logRequest.requestBody.data.length > 0) req.write(logRequest.requestBody.data); | ||
req.on('response', (res) => { | ||
@@ -54,0 +54,0 @@ res.resume(); |
@@ -46,3 +46,3 @@ import EventEmitter from 'events'; | ||
let req = client.request(logRequest.headers); | ||
if (logRequest.reqBody.length > 0) req.write(logRequest.reqBody); | ||
if (logRequest.requestBody.data.length > 0) req.write(logRequest.requestBody.data); | ||
req.on('response', () => { | ||
@@ -49,0 +49,0 @@ req.resume(); |
@@ -129,2 +129,3 @@ const ERR_AGENT_NOT_REGISTERED = 'Agent not registered. Exiting...'; | ||
const OPTION_SUBDOMAIN = 'provide custom subdomain'; | ||
const OPTION_FOLLOW = 'follow tunnel details'; | ||
const OPTION_DOMAIN = 'provide custom domain'; | ||
@@ -251,2 +252,3 @@ const OPTION_TIMEOUT = 'enforce connection timeout in seconds'; | ||
OPTION_DOMAIN, | ||
OPTION_FOLLOW, | ||
OPTION_SUBDOMAIN, | ||
@@ -253,0 +255,0 @@ OPTION_SERVE, |
@@ -350,3 +350,3 @@ import basicAuth from 'basic-auth'; | ||
httpEndFast(req, res, statusCode, msg, headers = {}) { | ||
const log = this.httpLog.newRequest(req); | ||
const log = this.httpLog.newRequest(req.method, req.headers, req.url, req.httpVersion, req); | ||
if (!log) { | ||
@@ -360,5 +360,4 @@ this.httpLog.newResponse(req, statusCode, headers); | ||
} else { | ||
req.pipe(log); | ||
log.resume(); | ||
log.once('end', () => { | ||
req.pipe(log.requestBody); | ||
log.requestBody.pipeToNothing(() => { | ||
this.httpLog.newResponse(req, statusCode, headers, log); | ||
@@ -432,3 +431,3 @@ res.statusCode = statusCode; | ||
retryHttpLogRequest(logRequest) { | ||
if (logRequest.reqBodyTooLarge) return; | ||
if (logRequest.requestBody.tooLarge) return; | ||
if (logRequest.httpVersion === '2.0') return this.http2server.retryRequest(logRequest); | ||
@@ -435,0 +434,0 @@ return this.http1server.retryRequest(logRequest); |
import zlib from 'zlib'; | ||
import mimeDB from 'mime-db'; | ||
import { negotiate } from '@fastify/accept-negotiator'; | ||
import stream from 'stream'; | ||
@@ -21,3 +22,3 @@ const TUNNEL_COMPRESSION_METHOD_IDENTITY = 'identity'; | ||
static compress(method, stream) { | ||
static compress(method) { | ||
let outStream; | ||
@@ -27,4 +28,4 @@ if (method === TUNNEL_COMPRESSION_METHOD_BR) outStream = zlib.createBrotliCompress(); | ||
else if (method === TUNNEL_COMPRESSION_METHOD_DEFLATE) outStream = zlib.createDeflate(); | ||
if (!outStream) return stream; | ||
return stream.pipe(outStream); | ||
else outStream = new stream.PassThrough(); | ||
return outStream; | ||
} | ||
@@ -31,0 +32,0 @@ } |
import EventEmitter from 'events'; | ||
import { v4 } from 'uuid'; | ||
import { TUNNEL_IDENTIFIED_HTTP1 } from './identification.js'; | ||
import { | ||
TUNNEL_IDENTIFIED_HTTP1, | ||
TUNNEL_IDENTIFIED_HTTP2 | ||
} from './identification.js'; | ||
import http from 'http'; | ||
@@ -17,2 +20,3 @@ import { | ||
import { LOG_ERROR } from '../texts.js'; | ||
import stream from 'stream'; | ||
@@ -64,3 +68,3 @@ class TunnelHttp extends EventEmitter { | ||
this.httpLog = httpLog; | ||
this.logRequest = httpLog.newRequest(req); | ||
this.logRequest = null; | ||
} | ||
@@ -70,2 +74,6 @@ | ||
try { | ||
const s = new stream.Readable(); | ||
s.push(html); | ||
s.push(null); | ||
s.pipe(this.logRequest.responseBody); | ||
this.httpLog.newResponse(this.req, code, {}, this.logRequest); | ||
@@ -181,2 +189,9 @@ this.res.statusCode = code; | ||
const reqHeaders = this.req.headers; | ||
const method = this.getMethod(); | ||
const path = this.getPath(); | ||
const headers = { | ||
...this.getClearedHeaders(reqHeaders), | ||
...this.headers, | ||
...this.getRequestHeaders(false) | ||
}; | ||
const r = http.request({ | ||
@@ -186,12 +201,9 @@ agent: TunnelAgent.getHttp1(), | ||
port: this.port, | ||
method: this.getMethod(), | ||
method, | ||
protocol: 'http:', | ||
setHost: false, | ||
path: this.getPath(), | ||
headers: { | ||
...this.getClearedHeaders(reqHeaders), | ||
...this.headers, | ||
...this.getRequestHeaders(false) | ||
} | ||
path, | ||
headers | ||
}); | ||
this.logRequest = this.httpLog.newRequest(method, headers, path, TUNNEL_IDENTIFIED_HTTP1, this.req); | ||
r.on('response', (proxyRes) => { | ||
@@ -203,3 +215,3 @@ this.proxyRes = proxyRes; | ||
this.outputHeaders(statusCode, this.getClearedHeaders(resHeaders, compressionMethod)); | ||
TunnelCompression.compress(compressionMethod, this.proxyRes).pipe(this.res); | ||
this.proxyRes.pipe(this.logRequest.responseBody).pipe(TunnelCompression.compress(compressionMethod)).pipe(this.res); | ||
this.httpLog.newResponse(this.req, statusCode, resHeaders, this.logRequest); | ||
@@ -212,2 +224,9 @@ }); | ||
const reqHeaders = this.req.headers; | ||
const method = this.getMethod(); | ||
const headers = { | ||
...this.getClearedHeaders(reqHeaders), | ||
...this.headers, | ||
...this.getRequestHeaders(false) | ||
}; | ||
const path = this.getPath(); | ||
const r = https.request({ | ||
@@ -217,13 +236,10 @@ agent: TunnelAgent.getHttps1(), | ||
port: this.port, | ||
method: this.getMethod(), | ||
method, | ||
protocol: 'https:', | ||
setHost: false, | ||
path: this.getPath(), | ||
path, | ||
rejectUnauthorized: this.verify, | ||
headers: { | ||
...this.getClearedHeaders(reqHeaders), | ||
...this.headers, | ||
...this.getRequestHeaders(false) | ||
} | ||
headers | ||
}); | ||
this.logRequest = this.httpLog.newRequest(method, headers, path, TUNNEL_IDENTIFIED_HTTP1, this.req); | ||
r.on('response', (proxyRes) => { | ||
@@ -235,3 +251,3 @@ this.proxyRes = proxyRes; | ||
this.outputHeaders(statusCode, this.getClearedHeaders(resHeaders, compressionMethod)); | ||
TunnelCompression.compress(compressionMethod, this.proxyRes).pipe(this.res); | ||
this.proxyRes.pipe(this.logRequest.responseBody).pipe(TunnelCompression.compress(compressionMethod)).pipe(this.res); | ||
this.httpLog.newResponse(this.req, statusCode, resHeaders, this.logRequest); | ||
@@ -245,2 +261,5 @@ }); | ||
const r = new TunnelHttpServe(this.serve); | ||
const method = this.getMethod(); | ||
const path = this.getPath(); | ||
this.logRequest = this.httpLog.newRequest(method, reqHeaders, path, TUNNEL_IDENTIFIED_HTTP1, this.req); | ||
r.on('response', (statusCode, resHeaders, stream) => { | ||
@@ -253,3 +272,3 @@ if (!stream) { | ||
this.outputHeaders(statusCode, this.getClearedHeaders(resHeaders, compressionMethod)); | ||
TunnelCompression.compress(compressionMethod, stream).pipe(this.res); | ||
stream.pipe(this.logRequest.responseBody).pipe(TunnelCompression.compress(compressionMethod)).pipe(this.res); | ||
} | ||
@@ -265,7 +284,11 @@ this.httpLog.newResponse(this.req, statusCode, resHeaders, this.logRequest); | ||
const client = await TunnelAgent.getHttp2Client(authority, this.host, this.verify); | ||
const r = client.request({ | ||
const method = this.getMethod(); | ||
const path = this.getPath(); | ||
const headers = { | ||
...this.getClearedHeaders(reqHeaders), | ||
...this.headers, | ||
...this.getRequestHeaders(true) | ||
}); | ||
}; | ||
const r = client.request(headers); | ||
this.logRequest = this.httpLog.newRequest(method, headers, path, TUNNEL_IDENTIFIED_HTTP2, this.req); | ||
r.on('response', (resHeaders) => { | ||
@@ -275,3 +298,3 @@ const status = resHeaders[':status'] || 200; | ||
this.outputHeaders(status, this.getClearedHeaders(resHeaders, compressionMethod)); | ||
TunnelCompression.compress(compressionMethod, r).pipe(this.res); | ||
r.pipe(this.logRequest.responseBody).pipe(TunnelCompression.compress(compressionMethod)).pipe(this.res); | ||
this.httpLog.newResponse(this.req, status, resHeaders, this.logRequest); | ||
@@ -342,9 +365,8 @@ }); | ||
if (this.logRequest) { | ||
this.req.pipe(this.logRequest); | ||
this.logRequest.once('end', () => { | ||
this.req.pipe(this.logRequest.requestBody); | ||
// no real request waiting for this data -> push it into the void | ||
this.logRequest.requestBody.pipeToNothing(() => { | ||
// wait for piping request | ||
this.output503(); | ||
}); | ||
// no real request waiting for this data -> push it into the void | ||
this.logRequest.resume(); | ||
return; | ||
@@ -355,3 +377,3 @@ } | ||
} | ||
this.proxyReq.setTimeout(this.timeout); | ||
this.proxyReq.setTimeout(this.timeout * 1000); | ||
this.proxyReq.on('timeout', () => { | ||
@@ -371,3 +393,3 @@ this.output503(); | ||
if (this.logRequest) { | ||
this.req.pipe(this.logRequest).pipe(this.proxyReq); | ||
this.req.pipe(this.logRequest.requestBody).pipe(this.proxyReq); | ||
} else { | ||
@@ -374,0 +396,0 @@ this.req.pipe(this.proxyReq); |
import EventEmitter from 'events'; | ||
import { Transform } from 'stream'; | ||
import { v4 } from 'uuid'; | ||
import TunnelHttpStream from './stream.js'; | ||
@@ -9,2 +10,3 @@ const TUNNEL_HTTP_RATE_LIMIT = 2000; | ||
const TUNNEL_HTTP_LOG_MAX_REQUESTS = 20; | ||
const TUNNEL_MAX_REQUEST_SIZE_TO_SYNC = 1048576; // 1MB | ||
const TUNNEL_HTTP_CB_WINDOW = 10000; | ||
@@ -16,10 +18,8 @@ const TUNNEL_HTTP_CB_MIN_REQUESTS = 20; | ||
class TunnelHttpLogRequest extends Transform { | ||
constructor(req) { | ||
constructor(method, headers, url, httpVersion) { | ||
super(); | ||
this.method = req.method; | ||
this.headers = req.headers; | ||
this.url = req.url; | ||
this.httpVersion = req.httpVersion; | ||
this.reqBody = Buffer.alloc(0); | ||
this.reqBodyTooLarge = false; | ||
this.method = method; | ||
this.headers = headers; | ||
this.url = url; | ||
this.httpVersion = httpVersion; | ||
this.status = 'NEW'; | ||
@@ -29,16 +29,6 @@ this.start = process.hrtime(); | ||
this.id = v4(); | ||
this.requestBody = new TunnelHttpStream(TUNNEL_HTTP_LOG_MAX_BODY); | ||
this.responseBody = new TunnelHttpStream(TUNNEL_HTTP_LOG_MAX_BODY); | ||
} | ||
_transform(chunk, encoding, cb) { | ||
if (!this.reqBodyTooLarge) { | ||
if (this.reqBody.length + chunk.length > TUNNEL_HTTP_LOG_MAX_BODY) { | ||
this.reqBodyTooLarge = true; | ||
} else { | ||
this.reqBody = Buffer.concat([this.reqBody, chunk]); | ||
} | ||
} | ||
this.push(chunk); | ||
cb(); | ||
} | ||
response(statusCode, headers) { | ||
@@ -127,6 +117,6 @@ this.status = statusCode; | ||
newRequest(req) { | ||
this.increaseRateLimit(req); | ||
newRequest(method, headers, url, httpVersion, originalReq) { | ||
this.increaseRateLimit(originalReq); | ||
if (this.log) { | ||
const r = new TunnelHttpLogRequest(req, this.log); | ||
const r = new TunnelHttpLogRequest(method, headers, url, httpVersion); | ||
this.requests.unshift(r); | ||
@@ -154,2 +144,46 @@ if (this.requests.length > TUNNEL_HTTP_LOG_MAX_REQUESTS) this.requests.pop(); | ||
getRequestsToSync() { | ||
const list = []; | ||
this.requests.forEach((r) => { | ||
let requestBody = null; | ||
let requestBodyLength = 0; | ||
let requestBodyTooLarge = false; | ||
let responseBody = null; | ||
let responseBodyLength = 0; | ||
let responseBodyTooLarge = false; | ||
if (r.requestBody.realLength > 0) { | ||
requestBodyLength = r.requestBody.realLength; | ||
requestBodyTooLarge = r.requestBody.tooLarge; | ||
if (r.requestBody.data.length <= TUNNEL_MAX_REQUEST_SIZE_TO_SYNC) { | ||
requestBody = r.requestBody.data.toString('base64'); | ||
} | ||
} | ||
if (r.responseBody.realLength > 0) { | ||
responseBodyLength = r.responseBody.realLength; | ||
responseBodyTooLarge = r.responseBody.tooLarge; | ||
if (r.responseBody.data.length <= TUNNEL_MAX_REQUEST_SIZE_TO_SYNC) { | ||
responseBody = r.responseBody.data.toString('base64'); | ||
} | ||
} | ||
list.push({ | ||
method: r.method, | ||
headers: r.headers, | ||
url: r.url, | ||
httpVersion: r.httpVersion, | ||
status: r.status, | ||
finished: r.finished, | ||
id: r.id, | ||
responseHeaders: r.responseHeaders, | ||
time: r.time, | ||
requestBody, | ||
requestBodyTooLarge, | ||
requestBodyLength, | ||
responseBody, | ||
responseBodyTooLarge, | ||
responseBodyLength | ||
}); | ||
}); | ||
return list; | ||
} | ||
newResponse(req, statusCode, headers, logRequest) { | ||
@@ -156,0 +190,0 @@ this.increaseCircuitBreaker(statusCode); |
@@ -7,4 +7,4 @@ import EventEmitter from 'events'; | ||
const EVENT_TUNNEL_IDENTIFIED = 'tunnel identified'; | ||
const TUNNEL_IDENTIFIED_HTTP1 = 'http1'; | ||
const TUNNEL_IDENTIFIED_HTTP2 = 'http2'; | ||
const TUNNEL_IDENTIFIED_HTTP1 = '1.1'; | ||
const TUNNEL_IDENTIFIED_HTTP2 = '2.0'; | ||
const CHECK_INTERVAL = 300000; | ||
@@ -11,0 +11,0 @@ const CHECK_TIMEOUT = 5000; |
299929
88
6859