mirror of
https://github.com/buger/goreplay.git
synced 2024-04-21 12:32:02 +00:00
If the http body is empty. The httpBody function will return HTTP header content from index 3. This is unexpected. Function caller also cannot tell whether the httpbody is empty or not.
791 lines
23 KiB
JavaScript
Executable File
791 lines
23 KiB
JavaScript
Executable File
// ======= GoReplay Middleware helper =============
|
|
// Created by Leonid Bugaev in 2017
|
|
//
|
|
// For questions use GitHub or support@goreplay.org
|
|
//
|
|
// GoReplay: https://github.com/buger/goreplay
|
|
// Middleware package: https://github.com/buger/goreplay/middleware
|
|
|
|
var middleware;
|
|
|
|
function init() {
|
|
var proxy = {
|
|
ch: {},
|
|
on: function(chan, id, cb) {
|
|
if (!cb && id) {
|
|
cb = id;
|
|
} else if (cb && id) {
|
|
chan = chan + "#" + id;
|
|
}
|
|
|
|
if (!proxy.ch[chan]) {
|
|
proxy.ch[chan] = [];
|
|
}
|
|
|
|
proxy.ch[chan].push({
|
|
created: new Date(),
|
|
cb: cb
|
|
});
|
|
|
|
return proxy;
|
|
},
|
|
|
|
emit: function(msg, raw) {
|
|
var chanPrefix;
|
|
|
|
switch(msg.type) {
|
|
case "1": chanPrefix = "request"; break;
|
|
case "2": chanPrefix = "response"; break;
|
|
case "3": chanPrefix = "replay"; break;
|
|
}
|
|
|
|
let resp = msg;
|
|
|
|
["message", chanPrefix, chanPrefix + "#" + msg.ID].forEach(function(chanID, idx){
|
|
if (proxy.ch[chanID]) {
|
|
proxy.ch[chanID].forEach(function(ch){
|
|
let r = ch.cb(msg);
|
|
if (resp) resp = r; // If one of callback decided not to send response back, do not override it in global callbacks
|
|
})
|
|
|
|
// Cleanup Individual message channels to avoid memory leaks
|
|
if (idx == 2) {
|
|
delete proxy.ch[chanID]
|
|
}
|
|
}
|
|
})
|
|
|
|
if (resp) {
|
|
process.stdout.write(`${resp.rawMeta.toString('hex')}${Buffer.from("\n").toString("hex")}${resp.http.toString('hex')}\n`)
|
|
}
|
|
|
|
return resp
|
|
}
|
|
}
|
|
|
|
// Clean up old messaged ID specific channels if they are older then 60s
|
|
let gc = function(gcTime){
|
|
let now = new Date();
|
|
for (k in proxy.ch) {
|
|
if (k.indexOf("#") == -1) continue;
|
|
|
|
proxy.ch[k] = proxy.ch[k].filter(function(ch){
|
|
return (now - ch.created) < gcTime
|
|
})
|
|
|
|
if (proxy.ch[k].length == 0) {
|
|
delete proxy.ch[k]
|
|
}
|
|
}
|
|
}
|
|
proxy.gc = gc
|
|
|
|
setInterval(function(){
|
|
gc(10 * 1000)
|
|
}, 1000);
|
|
|
|
const readline = require('readline');
|
|
const rl = readline.createInterface({
|
|
input: process.stdin
|
|
});
|
|
|
|
rl.on('line', function(line) {
|
|
let msg = parseMessage(line)
|
|
if (msg) {
|
|
proxy.emit(msg, line)
|
|
}
|
|
});
|
|
|
|
middleware = proxy;
|
|
|
|
return proxy;
|
|
}
|
|
|
|
|
|
function parseMessage(msg) {
|
|
try {
|
|
let payload = Buffer.from(msg, "hex");
|
|
let metaPos = payload.indexOf("\n");
|
|
let meta = payload.slice(0, metaPos);
|
|
let metaArr = meta.toString("ascii").split(" ");
|
|
let pType = metaArr[0];
|
|
let pID = metaArr[1];
|
|
let raw = payload.slice(metaPos + 1, payload.length);
|
|
|
|
return {
|
|
type: pType,
|
|
ID: pID,
|
|
rawMeta: meta,
|
|
meta: metaArr,
|
|
http: raw
|
|
}
|
|
} catch(e) {
|
|
fail(`Error while parsing incoming request: ${msg}`)
|
|
}
|
|
}
|
|
|
|
// Used to compare values from original and replayed responses
|
|
// Accepts request id, regexp pattern for searching the compared value (should include capture group), and callback which returns both original and replayed matched value.
|
|
// Example:
|
|
//
|
|
// // Compare HTTP headers for response and replayed response, and map values
|
|
// let tokMap = {};
|
|
//
|
|
// gor.on("request", function(req) {
|
|
// let tok = gor.httpHeader(req.http, "Auth-Token");
|
|
// if (tok && tokMap[tok]) {
|
|
// req.http = gor.setHttpHeader(req.http, "Auth-Token", tokMap[tok])
|
|
// }
|
|
//
|
|
// gor.searchResponses(req.ID, "X-Set-Token: (\w+)$", function(respTok, replTok) {
|
|
// tokMap[respTok] = replTok;
|
|
// })
|
|
//
|
|
// return req;
|
|
// })
|
|
//
|
|
function searchResponses(id, searchPattern, callback) {
|
|
let re = new RegExp(searchPattern);
|
|
|
|
// Using regexp require converting buffer to string
|
|
// Before converting to string we can use initial `Buffer.indexOf` check
|
|
let indexPattern = searchPattern.split("(")[0];
|
|
|
|
if (!indexPattern) {
|
|
console.error("Search regexp should include capture group, pointing to the value: `prefix-(.*)`")
|
|
return
|
|
}
|
|
|
|
middleware.on("response", id, function(resp){
|
|
if (resp.http.indexOf(indexPattern) == -1) {
|
|
callback()
|
|
return resp
|
|
}
|
|
|
|
let respMatch = resp.http.toString('utf-8').match(re);
|
|
if (!respMatch) {
|
|
callback()
|
|
return resp
|
|
}
|
|
|
|
middleware.on("replay", id, function(repl) {
|
|
if (repl.http.indexOf(indexPattern) == -1) {
|
|
callback(respMatch[1]);
|
|
return repl;
|
|
}
|
|
|
|
let replMatch = repl.http.toString('utf-8').match(re);
|
|
|
|
if (!replMatch) {
|
|
callback(respMatch[1]);
|
|
return repl;
|
|
}
|
|
|
|
callback(respMatch[1], replMatch[1]);
|
|
|
|
return repl;
|
|
})
|
|
|
|
return resp;
|
|
})
|
|
}
|
|
|
|
|
|
// =========== HTTP parsing =================
|
|
|
|
// Example HTTP payload record (including hidden characters):
|
|
//
|
|
// POST / HTTP/1.1\r\n
|
|
// User-Agent: Node\r\n
|
|
// Content-Length: 5\r\n
|
|
// \r\n
|
|
// hello
|
|
|
|
function httpMethod(payload) {
|
|
var pEnd = payload.indexOf(' ');
|
|
return payload.slice(0, pEnd).toString("ascii");
|
|
}
|
|
|
|
function httpPath(payload) {
|
|
var pStart = payload.indexOf(' ') + 1;
|
|
var pEnd = payload.indexOf(' ', pStart);
|
|
return payload.slice(pStart, pEnd).toString("ascii");
|
|
}
|
|
|
|
function setHttpPath(payload, newPath) {
|
|
var pStart = payload.indexOf(' ') + 1;
|
|
var pEnd = payload.indexOf(' ', pStart);
|
|
|
|
return Buffer.concat([payload.slice(0, pStart), Buffer.from(newPath), payload.slice(pEnd, payload.length)])
|
|
}
|
|
|
|
function httpPathParam(payload, name) {
|
|
let path = httpPath(payload);
|
|
let re = new RegExp(name + "=([^&$]+)");
|
|
let match = path.match(re);
|
|
|
|
if (match) return decodeURI(match[1]);
|
|
}
|
|
|
|
function setHttpPathParam(payload, name, value) {
|
|
let path = httpPath(payload);
|
|
let re = new RegExp(name + "=([^&$]+)");
|
|
let newPath = path.replace(re, name + "=" + encodeURI(value));
|
|
|
|
// If we should add new param instead
|
|
if (newPath == path) {
|
|
if (newPath.indexOf("?") == -1) {
|
|
newPath += "?"
|
|
} else {
|
|
newPath += "&"
|
|
}
|
|
|
|
newPath += name + "=" + encodeURI(value);
|
|
}
|
|
|
|
return setHttpPath(payload, newPath)
|
|
}
|
|
|
|
// HTTP response have status code in same position as `path` for requests
|
|
function httpStatus(payload) {
|
|
return httpPath(payload);
|
|
}
|
|
|
|
function setHttpStatus(payload, newStatus) {
|
|
return setHttpPath(payload, newStatus);
|
|
}
|
|
|
|
function httpHeaders(payload) {
|
|
var httpHeaderString = payload.slice(0,payload.indexOf("\r\n\r\n") + 4).toString().split("\n").slice(1);
|
|
var headers = {};
|
|
|
|
for (var item in httpHeaderString) {
|
|
var parts = httpHeaderString[item].split(":");
|
|
|
|
if (parts.length > 1) {
|
|
headers[parts[0]] = parts.slice(1).join(":").trim();
|
|
}
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
function httpHeader(payload, name) {
|
|
var currentLine = 0;
|
|
var i = 0;
|
|
var header = { start: -1, end: -1, valueStart: -1 }
|
|
var nameBuf = Buffer.from(name);
|
|
var nameBufLower = Buffer.from(name.toLowerCase());
|
|
|
|
while(c = payload[i]) {
|
|
if (c == 13) { // new line "\n"
|
|
currentLine++;
|
|
i++
|
|
header.end = i
|
|
|
|
if (currentLine > 0 && header.start > 0 && header.valueStart > 0) {
|
|
if (nameBuf.compare(payload, header.start, header.valueStart - 1) == 0 ||
|
|
nameBufLower.compare(payload, header.start, header.valueStart - 1) == 0) { // ensure that headers are not case sensitive
|
|
header.value = payload.slice(header.valueStart, header.end - 1).toString("utf-8").trim();
|
|
header.name = payload.slice(header.start, header.valueStart - 1).toString("utf-8");
|
|
return header
|
|
}
|
|
}
|
|
|
|
header.start = -1
|
|
header.valueStart = -1
|
|
continue;
|
|
} else if (c == 10) { // "\r"
|
|
i++
|
|
continue;
|
|
} else if (c == 58) { // ":" Header/value separator symbol
|
|
if (header.valueStart == -1) {
|
|
header.valueStart = i + 1;
|
|
i++
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (header.start == -1) header.start = i;
|
|
|
|
i++
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
function setHttpHeader(payload, name, value) {
|
|
let header = httpHeader(payload, name);
|
|
if (!header) {
|
|
let headerStart = payload.indexOf(13) + 1;
|
|
return Buffer.concat([payload.slice(0, headerStart + 1), Buffer.from(name + ": " + value + "\r\n"), payload.slice(headerStart + 1, payload.length)])
|
|
} else {
|
|
return Buffer.concat([payload.slice(0, header.valueStart), Buffer.from(" " + value + "\r\n"), payload.slice(header.end + 1, payload.length)])
|
|
}
|
|
}
|
|
|
|
function deleteHttpHeader(payload, name) {
|
|
let header = httpHeader(payload, name);
|
|
|
|
if (header) {
|
|
return Buffer.concat([payload.slice(0, header.start), payload.slice(header.end+1, payload.length)])
|
|
}
|
|
|
|
return payload
|
|
}
|
|
|
|
function httpBody(payload) {
|
|
let bodyIndex = payload.indexOf("\r\n\r\n");
|
|
if (-1 != bodyIndex){
|
|
return payload.slice(bodyIndex + 4, payload.length);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function setHttpBody(payload, newBody) {
|
|
let p = setHttpHeader(payload, "Content-Length", newBody.length)
|
|
let headerEnd = p.indexOf("\r\n\r\n") + 4;
|
|
return Buffer.concat([p.slice(0, headerEnd), newBody])
|
|
}
|
|
|
|
function httpBodyParam(payload, name) {
|
|
let body = httpBody(payload);
|
|
let re = new RegExp(name + "=([^&$]+)");
|
|
if (body.indexOf(name + "=") != -1) {
|
|
let param = body.toString('utf-8').match(re);
|
|
if (param) {
|
|
return decodeURI(param[1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function setHttpBodyParam(payload, name, value) {
|
|
let body = httpBody(payload);
|
|
let re = new RegExp(name + "=([^&$]+)");
|
|
|
|
let newBody = body.toString('utf-8');
|
|
|
|
if (newBody.indexOf(name + "=") != -1 ) {
|
|
newBody = newBody.replace(re, name + "=" + encodeURI(value));
|
|
} else {
|
|
if (newBody.indexOf("=") != -1) {
|
|
newBody += "&";
|
|
}
|
|
newBody += name + "=" + value;
|
|
}
|
|
|
|
return setHttpBody(payload, Buffer.from(newBody));
|
|
}
|
|
|
|
function setHttpCookie(payload, name, value) {
|
|
let h = httpHeader(payload, "Cookie");
|
|
let cookie = h ? h.value : "";
|
|
let cookies = cookie.split("; ").filter(function(v){ return v.indexOf(name + "=") != 0 })
|
|
cookies.push(name + "=" + value)
|
|
return setHttpHeader(payload, "Cookie", cookies.join("; "))
|
|
}
|
|
|
|
function deleteHttpCookie(payload, name) {
|
|
let h = httpHeader(payload, "Cookie");
|
|
let cookie = h ? h.value : "";
|
|
let cookies = cookie.split("; ").filter(function(v){ return v.indexOf(name + "=") != 0 })
|
|
return setHttpHeader(payload, "Cookie", cookies.join("; "))
|
|
}
|
|
|
|
function httpCookie(payload, name) {
|
|
let h = httpHeader(payload, "Cookie");
|
|
let cookie = h ? h.value : "";
|
|
let value;
|
|
let cookies = cookie.split("; ").forEach(function(v){
|
|
if (v.indexOf(name + "=") == 0) {
|
|
value = v.substr(name.length + 1);
|
|
}
|
|
})
|
|
return value;
|
|
}
|
|
|
|
module.exports = {
|
|
init: init,
|
|
on: function(){ return middleware.on.apply(this, arguments) },
|
|
parseMessage: parseMessage,
|
|
searchResponses: searchResponses,
|
|
httpPath: httpPath,
|
|
httpMethod: httpMethod,
|
|
setHttpPath: setHttpPath,
|
|
httpPathParam: httpPathParam,
|
|
setHttpPathParam: setHttpPathParam,
|
|
httpStatus: httpStatus,
|
|
setHttpStatus: setHttpStatus,
|
|
httpHeader: httpHeader,
|
|
setHttpHeader: setHttpHeader,
|
|
deleteHttpHeader: deleteHttpHeader,
|
|
httpBody: httpBody,
|
|
setHttpBody: setHttpBody,
|
|
httpBodyParam: httpBodyParam,
|
|
setHttpBodyParam: setHttpBodyParam,
|
|
httpCookie: httpCookie,
|
|
setHttpCookie: setHttpCookie,
|
|
deleteHttpCookie: deleteHttpCookie,
|
|
test: testRunner,
|
|
benchmark: testBenchmark,
|
|
httpHeaders: httpHeaders
|
|
}
|
|
|
|
|
|
// =========== Tests ==============
|
|
|
|
function testRunner(){
|
|
["init", "filter", "parseMessage", "httpMethod", "httpPath", "setHttpHeader", "deleteHttpHeader", "httpPathParam", "httpHeader", "httpBody", "setHttpBody", "httpBodyParam", "httpCookie", "setHttpCookie", "deleteHttpCookie", "httpHeaders"].forEach(function(t){
|
|
console.log(`====== Start ${t} =======`)
|
|
eval(`TEST_${t}()`)
|
|
console.log(`====== End ${t} =======`)
|
|
})
|
|
}
|
|
|
|
function testBenchmark(){
|
|
const child_process = require('child_process');
|
|
|
|
let gor = init();
|
|
gor.on("message", function(){
|
|
});
|
|
|
|
gor.on("request", function(){
|
|
});
|
|
|
|
for (var i = 0; i<256; i++) {
|
|
let req = parseMessage(Buffer.from("1 2 3\nGET / HTTP/1.1\r\n\r\n").toString('hex'));
|
|
req.ID = +Date.now()
|
|
gor.emit(req);
|
|
|
|
gor.on("request", req.ID+"", function(){
|
|
gor.on("response", req.ID+"", function(){
|
|
})
|
|
})
|
|
|
|
if ( i % 3 == 0 ) {
|
|
let resp = parseMessage(Buffer.from("2 2 3\nHTTP/1.1 200 OK\r\n\r\n").toString('hex'));
|
|
resp.ID = req.ID
|
|
gor.emit(resp);
|
|
}
|
|
}
|
|
|
|
child_process.execSync("sleep 0.01");
|
|
|
|
gor.gc(1)
|
|
|
|
fail(JSON.stringify(gor.ch))
|
|
}
|
|
|
|
// Just print in red color
|
|
function fail(message) {
|
|
console.error("\x1b[31m[MIDDLEWARE] %s\x1b[0m", message)
|
|
}
|
|
|
|
function log(message) {
|
|
console.error(message)
|
|
}
|
|
|
|
function TEST_init() {
|
|
const child_process = require('child_process');
|
|
|
|
let received = 0;
|
|
let gor = init();
|
|
gor.on("message", function(){
|
|
received++; // should be called 3 times for for every request
|
|
});
|
|
|
|
gor.on("request", function(){
|
|
received++; // should be called 1 time only for request
|
|
});
|
|
|
|
gor.on("response", "2", function(){
|
|
received++; // should be called 1 time only for specific response
|
|
})
|
|
|
|
if (Object.keys(gor.ch).length != 3) {
|
|
return fail("Should create 3 channels");
|
|
}
|
|
|
|
let req = parseMessage(Buffer.from("1 2 3\nGET / HTTP/1.1\r\n\r\n").toString('hex'));
|
|
let resp = parseMessage(Buffer.from("2 2 3\nHTTP/1.1 200 OK\r\n\r\n").toString('hex'));
|
|
let resp2 = parseMessage(Buffer.from("2 3 3\nHTTP/1.1 200 OK\r\n\r\n").toString('hex'));
|
|
|
|
gor.emit(req);
|
|
gor.emit(resp);
|
|
gor.emit(resp2);
|
|
|
|
child_process.execSync("sleep 0.01");
|
|
|
|
if (received != 5) {
|
|
fail(`Should receive 5 messages: ${received}`);
|
|
}
|
|
}
|
|
|
|
function TEST_filter() {
|
|
const child_process = require('child_process');
|
|
|
|
let gor = init();
|
|
gor.on("request", function(req){
|
|
if (httpPath(req.http) != "/filter") {
|
|
return req
|
|
}
|
|
});
|
|
|
|
gor.on("request", function(req){
|
|
return req
|
|
});
|
|
|
|
|
|
let reqPass = parseMessage(Buffer.from("1 2 3\nGET / HTTP/1.1\r\n\r\n").toString('hex'));
|
|
let reqFilter = parseMessage(Buffer.from("1 2 3\nGET /filter HTTP/1.1\r\n\r\n").toString('hex'));
|
|
|
|
if (!gor.emit(reqPass)) {
|
|
return fail("Should not filter request")
|
|
}
|
|
|
|
if (gor.emit(reqFilter)) {
|
|
return fail("Should filter request even if one middleware rejected it")
|
|
}
|
|
|
|
}
|
|
|
|
function TEST_parseMessage() {
|
|
const exampleMessage = Buffer.from("1 2 3\nGET / HTTP/1.1\r\n\r\n").toString('hex')
|
|
let msg = parseMessage(exampleMessage)
|
|
let expected = { type: '1', ID: '2', meta: ["1", "2", "3"], http: Buffer.from("GET / HTTP/1.1\r\n\r\n") }
|
|
|
|
Object.keys(expected).forEach(function(k){
|
|
if (msg[k].toString() != expected[k].toString()) {
|
|
fail(`${k}: '${expected[k]}' != '${msg[k]}'`)
|
|
}
|
|
})
|
|
}
|
|
|
|
function TEST_httpPath() {
|
|
const examplePayload = "GET /test HTTP/1.1\r\n\r\n";
|
|
|
|
let payload = Buffer.from(examplePayload);
|
|
let path = httpPath(payload);
|
|
|
|
if (path != "/test") {
|
|
return fail(`Path '${patj}' != '/test'`)
|
|
}
|
|
|
|
let newPayload = setHttpPath(payload, '/')
|
|
if (newPayload.toString() != "GET / HTTP/1.1\r\n\r\n") {
|
|
return fail(`Malformed payload '${newPayload}'`)
|
|
}
|
|
|
|
newPayload = setHttpPath(payload, '/bigger')
|
|
if (newPayload.toString() != "GET /bigger HTTP/1.1\r\n\r\n") {
|
|
return fail(`Malformed payload '${newPayload}'`)
|
|
}
|
|
}
|
|
|
|
function TEST_httpMethod() {
|
|
const examplePayload = "GET /test HTTP/1.1\r\n\r\n";
|
|
|
|
let payload = Buffer.from(examplePayload);
|
|
let method = httpMethod(payload);
|
|
|
|
if (method != "GET") {
|
|
return fail(`Path '${method}' != 'GET'`)
|
|
}
|
|
}
|
|
|
|
|
|
function TEST_httpPathParam() {
|
|
let p = Buffer.from("GET / HTTP/1.1\r\n\r\n");
|
|
|
|
if (httpPathParam(p, "test")) {
|
|
return fail("Should not found param")
|
|
}
|
|
|
|
p = setHttpPathParam(p, "test", "123");
|
|
if (httpPath(p) != "/?test=123") {
|
|
return fail("Should set first param: " + httpPath(p));
|
|
}
|
|
|
|
if (httpPathParam(p, "test") != "123") {
|
|
return fail("Should get first param: " + httpPathParam(p, "test"));
|
|
}
|
|
|
|
p = setHttpPathParam(p, "qwer", "ty");
|
|
if (httpPath(p) != "/?test=123&qwer=ty") {
|
|
return fail("Should set second param: " + httpPath(p));
|
|
}
|
|
|
|
p = setHttpPathParam(p, "test", "4321");
|
|
if (httpPath(p) != "/?test=4321&qwer=ty") {
|
|
return fail("Should update first param: " + httpPath(p));
|
|
}
|
|
|
|
if (httpPathParam(p, "test") != "4321") {
|
|
return fail("Should update first param: " + httpPath(p));
|
|
}
|
|
}
|
|
|
|
function TEST_httpBodyParam() {
|
|
let p = Buffer.from("POST / HTTP/1.1\r\n\r\n");
|
|
|
|
if (httpBodyParam(p, "test")) {
|
|
return fail("Should not found param")
|
|
}
|
|
|
|
p = setHttpBodyParam(p, "test", "123");
|
|
if (httpBody(p).toString() != "test=123") {
|
|
return fail("Should set first param: " + httpBody(p).toString());
|
|
}
|
|
|
|
if (httpBodyParam(p, "test") != "123") {
|
|
return fail("Should get first param: " + httpBodyParam(p, "test"));
|
|
}
|
|
|
|
p = setHttpBodyParam(p, "qwer", "ty");
|
|
if (httpBody(p).toString() != "test=123&qwer=ty") {
|
|
return fail("Should set second param: " + httpBody(p).toString());
|
|
}
|
|
|
|
p = setHttpBodyParam(p, "test", "4321");
|
|
if (httpBody(p).toString() != "test=4321&qwer=ty") {
|
|
return fail("Should update first param: " + httpBody(p).toString());
|
|
}
|
|
|
|
if (httpBodyParam(p, "test") != "4321") {
|
|
return fail("Should update first param: " + httpBody(p).toString());
|
|
}
|
|
}
|
|
|
|
function TEST_httpHeader() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nHost: localhost:3000\r\nUser-Agent: Node\r\nContent-Length:5\r\n\r\nhello";
|
|
|
|
let expected = {"Host": "localhost:3000", "User-Agent": "Node", "Content-Length": "5"}
|
|
|
|
Object.keys(expected).forEach(function(name){
|
|
let payload = Buffer.from(examplePayload);
|
|
let header = httpHeader(payload, name);
|
|
if (!header) {
|
|
fail(`Header not found. Was looking for: ${name}`)
|
|
}
|
|
if (header && header.value != expected[name]) {
|
|
fail(`${name}: '${expected[name]}' != '${header.value}'`)
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
function TEST_setHttpHeader() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nUser-Agent: Node\r\nContent-Length: 5\r\n\r\nhello";
|
|
|
|
// Modify existing header
|
|
["", "1", "Long test header"].forEach(function(ua){
|
|
let expected = `GET / HTTP/1.1\r\nUser-Agent: ${ua}\r\nContent-Length: 5\r\n\r\nhello`;
|
|
let p = Buffer.from(examplePayload);
|
|
p = setHttpHeader(p, "User-Agent", ua);
|
|
if (p != expected) {
|
|
console.error(`setHeader failed, expected User-Agent value: ${ua}.\n${p}`)
|
|
}
|
|
})
|
|
|
|
// Adding new header
|
|
let expected = `GET / HTTP/1.1\r\nX-Test: test\r\nUser-Agent: Node\r\nContent-Length: 5\r\n\r\nhello`;
|
|
let p = Buffer.from(examplePayload);
|
|
p = setHttpHeader(p, "X-Test", "test");
|
|
if (p != expected) {
|
|
console.error(`setHeader failed, expected new header 'X-Test' header: ${p}`)
|
|
}
|
|
}
|
|
|
|
function TEST_deleteHttpHeader() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nUser-Agent: Node\r\nContent-Length: 5\r\n\r\nhello";
|
|
|
|
// Adding new header
|
|
let expected = `GET / HTTP/1.1\r\nContent-Length: 5\r\n\r\nhello`;
|
|
let p = Buffer.from(examplePayload);
|
|
p = deleteHttpHeader(p, "User-Agent", "test");
|
|
if (p != expected) {
|
|
console.error(`setHeader failed, expected delete header 'User-Agent' header: ${p}`)
|
|
}
|
|
}
|
|
|
|
function TEST_httpBody() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nUser-Agent: Node\r\nContent-Length: 5\r\n\r\nhello";
|
|
let body = httpBody(Buffer.from(examplePayload));
|
|
if (body != "hello") {
|
|
fail(`'${body}' != 'hello'`)
|
|
}
|
|
|
|
const exampleInvalidPayload = "Invalid HTTP Response by Network issue";
|
|
let invalidBody = httpBody(Buffer.from(exampleInvalidPayload));
|
|
if (invalidBody != null) {
|
|
fail(`'${invalidBody}' != 'null'`)
|
|
}
|
|
}
|
|
|
|
function TEST_setHttpBody() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nUser-Agent: Node\r\nContent-Length: 5\r\n\r\nhello";
|
|
let p = setHttpBody(Buffer.from(examplePayload), Buffer.from("hello, world!"));
|
|
|
|
if (p != "GET / HTTP/1.1\r\nUser-Agent: Node\r\nContent-Length: 13\r\n\r\nhello, world!") {
|
|
fail(`Wrong body: '${p}'`)
|
|
}
|
|
}
|
|
|
|
function TEST_httpCookie() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nCookie: a=b; test=zxc\r\n\r\n";
|
|
let c = httpCookie(Buffer.from(examplePayload), "test");
|
|
if (c != "zxc") {
|
|
return fail(`Should get cookie: ${c}`);
|
|
}
|
|
|
|
c = httpCookie(Buffer.from(examplePayload), "nope");
|
|
if (c != null) {
|
|
return fail(`Should not find cookie: ${c}`);
|
|
}
|
|
}
|
|
|
|
function TEST_setHttpCookie() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nCookie: a=b; test=zxc\r\n\r\n";
|
|
let p = setHttpCookie(Buffer.from(examplePayload), "test", "1");
|
|
if (p != "GET / HTTP/1.1\r\nCookie: a=b; test=1\r\n\r\n") {
|
|
return fail(`Should update cookie: ${p}`)
|
|
}
|
|
|
|
p = setHttpCookie(Buffer.from(examplePayload), "new", "one");
|
|
if (p != "GET / HTTP/1.1\r\nCookie: a=b; test=zxc; new=one\r\n\r\n") {
|
|
return fail(`Should add new cookie: ${p}`)
|
|
}
|
|
}
|
|
|
|
function TEST_deleteHttpCookie() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nCookie: a=b; test=zxc\r\n\r\n";
|
|
let p = deleteHttpCookie(Buffer.from(examplePayload), "a");
|
|
if (p != "GET / HTTP/1.1\r\nCookie: test=zxc\r\n\r\n") {
|
|
return fail(`Should delete cookie: ${p}`)
|
|
}
|
|
}
|
|
|
|
|
|
function TEST_httpHeaders() {
|
|
const examplePayload = "GET / HTTP/1.1\r\nHost: localhost:3000\r\nUser-Agent: Node\r\nContent-Length:5\r\n\r\nhello";
|
|
|
|
let expectedHeaders = {"Host": "localhost:3000", "User-Agent": "Node", "Content-Length": "5"}
|
|
let payload = Buffer.from(examplePayload);
|
|
let headers = httpHeaders(payload);
|
|
|
|
["Host", "User-Agent", "Content-Length"].forEach(function(header){
|
|
let actual = headers[header];
|
|
let expected = expectedHeaders[header];
|
|
|
|
if (!actual) {
|
|
fail(`${header} Header was not found`);
|
|
}
|
|
|
|
if (actual != expected) {
|
|
fail(`${header} Header not Equal to Expected: ${expected} was ${actual}`);
|
|
}
|
|
|
|
})
|
|
}
|