Properly handling POST bodies in hyco-https plus test coverage (#22)

* handling body content on control channel
* unit tests and appveyor setup
This commit is contained in:
Clemens Vasters 2018-08-10 15:25:56 +02:00 коммит произвёл GitHub
Родитель 5f71bb26c9
Коммит c37436a51f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 8810 добавлений и 27 удалений

5
hyco-https/examples/simple/package-lock.json сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,5 @@
{
"name": "simple",
"version": "0.0.0",
"lockfileVersion": 1
}

Просмотреть файл

@ -117,7 +117,7 @@ util.inherits(ServerResponse, OutgoingMessage);
ServerResponse.prototype._finish = function _finish() {
DTRACE_HTTP_SERVER_RESPONSE(this.connection);
COUNTER_HTTP_SERVER_RESPONSE();
//COUNTER_HTTP_SERVER_RESPONSE();
OutgoingMessage.prototype._finish.call(this);
};
@ -286,6 +286,7 @@ function Server(options, requestListener) {
this.listenUri = listenUri + '&id=' + options.id;
}
this.pendingRequest = null;
this.closeRequested = false;
this.options = options;
this.path = options.path;
@ -399,6 +400,13 @@ function connectControlChannel(server) {
}
server.controlChannel.onmessage = function(event) {
if ( server.pendingRequest != null ) {
server.pendingRequest.handleBody(event.data);
server.pendingRequest = null;
return;
}
var message = JSON.parse(event.data);
if (isDefinedAndNonNull(message, 'accept')) {
@ -565,6 +573,13 @@ function controlChannelRequest(server, message) {
// handler to call when the connection sequence completes
var self = server;
if ( message.request.method) {
req = new IncomingMessage(message, server.controlChannel);
if ( message.request.body == true) {
self.pendingRequest = req;
}
}
// execute the web socket rendezvous with the server
try {
var client = new WebSocket(address, {
@ -586,14 +601,11 @@ function controlChannelRequest(server, message) {
// do we have a request or is this just rendezvous?
if ( message.request.method) {
var req = new IncomingMessage(message, server.controlChannel);
var res = new ServerResponse(req);
res.requestId = message.request.id;
res.assignSocket(client);
server.emit('request', req, res);
}
});
client.on('error', function(event) {
@ -616,6 +628,9 @@ function requestChannelRequest(channel, message) {
var res = null;
// do we have a request or is this just rendezvous?
var req = new IncomingMessage(message, channel);
if ( message.request.body == true) {
channel.pendingRequest = req;
}
res = new ServerResponse(req);
res.requestId = message.request.id;
res.assignSocket(channel);
@ -626,7 +641,15 @@ function requestChannelRequest(channel, message) {
}
function requestChannelListener(server, requestChannel) {
requestChannel.onmessage = function(event) {
if ( requestChannel.pendingRequest != null )
{
requestChannel.pendingRequest.handleBody(event.data);
requestChannel.pendingRequest = null;
return;
}
var message = JSON.parse(event.data);
if (isDefinedAndNonNull(message, 'request')) {

Просмотреть файл

@ -26,7 +26,7 @@ const Stream = require('stream');
/* Abstract base class for ServerRequest and ClientResponse. */
function IncomingMessage(relayRequestMessage, relayWebSocket) {
Stream.Readable.call(this);
this.socket = relayWebSocket;
this.connection = relayWebSocket;
@ -56,7 +56,7 @@ function IncomingMessage(relayRequestMessage, relayWebSocket) {
this._dumped = false;
for (var header in relayRequestMessage.request.requestHeaders) {
this._addHeaderLine(header, relayRequestMessage.request.requestHeaders[header], this.headers);
this._addHeaderLine(header, relayRequestMessage.request.requestHeaders[header], this.headers);
}
}
util.inherits(IncomingMessage, Stream.Readable);
@ -71,6 +71,12 @@ IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
IncomingMessage.prototype._read = function _read(n) {
};
IncomingMessage.prototype.handleBody = function handleBody(buf) {
this.push(buf);
this.push(null);
};

5198
hyco-https/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -2,7 +2,7 @@
"author": "Microsoft Corporation",
"name": "hyco-https",
"description": "HTTP with Azure Relay Hybrid Connections",
"version": "1.0.7",
"version": "1.1.0",
"license": "MIT",
"main": "index.js",
"homepage": "https://docs.microsoft.com/en-us/azure/service-bus-relay/",
@ -17,9 +17,15 @@
"crypto": "latest",
"events": "latest",
"https": "latest",
"moment": "^2.22.1",
"moment": "^2.22.2",
"util": "latest",
"ws": "^5.1.1"
"ws": "^6.0.0"
},
"gypfile": true
"gypfile": true,
"devDependencies": {
"jest": "^23.4.2"
},
"scripts": {
"test": "jest"
}
}

Просмотреть файл

@ -0,0 +1,64 @@
var https = require('..')
jest.setTimeout(30000); // 30 seconds
test('HTTPS GET', (done) => {
var ns = process.env.SB_HC_NAMESPACE ? process.env.SB_HC_NAMESPACE.replace(/^"(.*)"$/, '$1') : null;
var path = "a1";
var keyrule = process.env.SB_HC_KEYRULE ? process.env.SB_HC_KEYRULE.replace(/^"(.*)"$/, '$1') : null;
var key = process.env.SB_HC_KEY ? process.env.SB_HC_KEY.replace(/^"(.*)"$/, '$1') : null;
expect(ns).toBeDefined();
expect(path).toBeDefined();
expect(keyrule).toBeDefined();
expect(key).toBeDefined();
/* set up the listener */
var uri = https.createRelayListenUri(ns, path);
var server = https.createRelayedServer({
server: uri,
token: () => https.createRelayToken(uri, keyrule, key)
},
(req, res) => {
expect(req.method).toBe('GET');
expect(req.headers.custom).toBe('Hello');
res.end('Hello');
});
// fail we get an error
server.listen((err) => {
expect(err).toBeUndefined();
});
// fail if we get an error (we'll always get one if this triggers)
server.on('error', (err) => {
expect(err).toBeUndefined();
});
// client is being run with 5 second delay to allow the server to settle
setTimeout(() => {
/* set up the client */
var clientUri = https.createRelayHttpsUri(ns, path);
var token = https.createRelayToken(clientUri, keyrule, key);
https.get({
hostname: ns,
path: ((!path || path.length == 0 || path[0] !== '/') ? '/' : '') + path,
port: 443,
headers: {
'ServiceBusAuthorization': token,
'Custom': 'Hello'
}
}, (res) => {
expect(res.statusCode).toBe(200);
res.setEncoding('utf8');
res.on('data', (chunk) => {
expect(chunk).toBe('Hello');
});
res.on('end', () => {
server.close();
done();
});
}).on('error', (e) => {
expect(e).toBeUndefined();
});
}, 5000);
});

Просмотреть файл

@ -0,0 +1,78 @@
var https = require('..')
jest.setTimeout(30000); // 30 seconds
test('HTTP POST', (done) => {
var ns = process.env.SB_HC_NAMESPACE?process.env.SB_HC_NAMESPACE.replace(/^"(.*)"$/, '$1'):null;
var path = "a2";
var keyrule = process.env.SB_HC_KEYRULE?process.env.SB_HC_KEYRULE.replace(/^"(.*)"$/, '$1'):null;
var key = process.env.SB_HC_KEY?process.env.SB_HC_KEY.replace(/^"(.*)"$/, '$1'):null;
expect(ns).toBeDefined();
expect(path).toBeDefined();
expect(keyrule).toBeDefined();
expect(key).toBeDefined();
/* set up the listener */
var uri = https.createRelayListenUri(ns, path);
var server = https.createRelayedServer({
server: uri,
token: () => https.createRelayToken(uri, keyrule, key)
},
(req, res) => {
expect(req.method).toBe("POST");
expect(req.headers.custom).toBe("Hello");
req.setEncoding('utf-8');
req.on('data', (chunk) => {
expect(chunk).toBe('Hello');
});
req.on('end', () => {
res.end('Hello');
});
});
// fail we get an error
server.listen((err) => {
expect(err).toBeUndefined();
});
// fail if we get an error (we'll always get one if this triggers)
server.on('error', (err) => {
expect(err).toBeUndefined();
});
// client is being run with 5 second delay to allow the server to settle
setTimeout(()=>{
/* set up the client */
var clientUri = https.createRelayHttpsUri(ns, path);
var token = https.createRelayToken(clientUri, keyrule, key);
const postData = 'Hello';
var req = https.request({
hostname: ns,
path: ((!path || path.length == 0 || path[0] !== '/') ? '/' : '') + path,
port: 443,
method : "POST",
headers: {
'ServiceBusAuthorization': token,
'Custom' : 'Hello',
'Content-Type': 'text/plain',
'Content-Length': Buffer.byteLength(postData)
}
}, (res) => {
expect(res.statusCode).toBe(200);
res.setEncoding('utf8');
res.on('data', (chunk) => {
expect(chunk).toBe('Hello');
});
res.on('end', () => {
server.close();
done();
});
}).on('error', (e) => {
expect(e).toBeUndefined();
});
req.write(postData);
req.end();
}, 5000);
});

180
jest.config.js Normal file
Просмотреть файл

@ -0,0 +1,180 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after the first failure
// bail: false,
// Respect "browser" field in package.json when resolving modules
browser: false,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "C:\\Users\\clemensv\\AppData\\Local\\Temp\\jest",
// Automatically clear mock calls and instances between every test
// clearMocks: false,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: null,
// The directory where Jest should output its coverage files
// coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: null,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files usin a array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: null,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: null,
// A set of global variables that need to be available in all test environments
// globals: {},
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "node"
// ],
// A map from regular expressions to module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "always",
// A preset that is used as a base for Jest's configuration
// preset: null,
// Run tests from one or more projects
// projects: null,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: null,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: null,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// The path to a module that runs some code to configure or set up the testing framework before each test
// setupTestFrameworkScriptFile: null,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.js?(x)",
// "**/?(*.)+(spec|test).js?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// The regexp pattern Jest uses to detect test files
// testRegex: "",
// This option allows the use of a custom results processor
// testResultsProcessor: null,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "about:blank",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: null,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: null,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
watchman: false,
};

3257
yarn.lock Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу