mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Simple web server component (#9648)
* Simple web server component * More testing
This commit is contained in:
@@ -198,6 +198,7 @@
|
|||||||
"@azure/arm-resourcegraph": "^2.0.0",
|
"@azure/arm-resourcegraph": "^2.0.0",
|
||||||
"@azure/arm-subscriptions": "1.0.0",
|
"@azure/arm-subscriptions": "1.0.0",
|
||||||
"adal-node": "^0.2.1",
|
"adal-node": "^0.2.1",
|
||||||
|
"axios": "^0.19.2",
|
||||||
"request": "2.88.0",
|
"request": "2.88.0",
|
||||||
"vscode-nls": "^4.0.0"
|
"vscode-nls": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import * as http from 'http';
|
||||||
|
import * as url from 'url';
|
||||||
|
import { AddressInfo } from 'net';
|
||||||
|
|
||||||
|
export type WebHandler = (req: http.IncomingMessage, reqUrl: url.UrlWithParsedQuery, res: http.ServerResponse) => void;
|
||||||
|
|
||||||
|
export class AlreadyRunningError extends Error { }
|
||||||
|
|
||||||
|
export class SimpleWebServer {
|
||||||
|
private hasStarted: boolean;
|
||||||
|
|
||||||
|
private readonly pathMappings = new Map<string, WebHandler>();
|
||||||
|
private readonly server: http.Server;
|
||||||
|
private lastUsed: number;
|
||||||
|
private shutoffInterval: NodeJS.Timer;
|
||||||
|
|
||||||
|
constructor(private readonly autoShutoffTimer = 5 * 60 * 1000) { // Default to five minutes.
|
||||||
|
this.bumpLastUsed();
|
||||||
|
this.autoShutoff();
|
||||||
|
this.server = http.createServer((req, res) => {
|
||||||
|
this.bumpLastUsed();
|
||||||
|
const reqUrl = url.parse(req.url!, /* parseQueryString */ true);
|
||||||
|
|
||||||
|
const handler = this.pathMappings.get(reqUrl.pathname);
|
||||||
|
if (handler) {
|
||||||
|
return handler(req, reqUrl, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bumpLastUsed(): void {
|
||||||
|
this.lastUsed = new Date().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async shutdown(): Promise<void> {
|
||||||
|
clearInterval(this.shutoffInterval);
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
this.server.close((error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async startup(): Promise<string> {
|
||||||
|
if (this.hasStarted) {
|
||||||
|
throw new AlreadyRunningError();
|
||||||
|
}
|
||||||
|
this.hasStarted = true;
|
||||||
|
let portTimeout: NodeJS.Timer;
|
||||||
|
const portPromise = new Promise<string>((resolve, reject) => {
|
||||||
|
portTimeout = setTimeout(() => {
|
||||||
|
reject(new Error('Timed out waiting for the server to start'));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
this.server.on('listening', () => {
|
||||||
|
// TODO: What are string addresses?
|
||||||
|
const address = this.server.address() as AddressInfo;
|
||||||
|
if (address!.port === undefined) {
|
||||||
|
reject(new Error('Port was not defined'));
|
||||||
|
}
|
||||||
|
resolve(address.port.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.on('error', () => {
|
||||||
|
reject(new Error('Server error'));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.on('close', () => {
|
||||||
|
reject(new Error('Server closed'));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.listen(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const clearPortTimeout = () => {
|
||||||
|
clearTimeout(portTimeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
portPromise.finally(clearPortTimeout);
|
||||||
|
|
||||||
|
return portPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(pathMapping: string, handler: WebHandler) {
|
||||||
|
this.pathMappings.set(pathMapping, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private autoShutoff(): void {
|
||||||
|
this.shutoffInterval = setInterval(() => {
|
||||||
|
const time = new Date().getTime();
|
||||||
|
|
||||||
|
if (time - this.lastUsed > this.autoShutoffTimer) {
|
||||||
|
console.log('Shutting off webserver...');
|
||||||
|
this.shutdown().catch(console.error);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import 'mocha';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import { SimpleWebServer } from '../../../account-provider/utils/simpleWebServer';
|
||||||
|
|
||||||
|
let server: SimpleWebServer;
|
||||||
|
|
||||||
|
// These tests don't work on Linux systems because gnome-keyring doesn't like running on headless machines.
|
||||||
|
describe('AccountProvider.SimpleWebServer', function (): void {
|
||||||
|
beforeEach(async function (): Promise<void> {
|
||||||
|
server = new SimpleWebServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Starts up and returns a port', async function (): Promise<void> {
|
||||||
|
const port = await server.startup();
|
||||||
|
should(port).be.any.String().and.not.be.undefined().and.not.be.null();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Double startup should fail', async function (): Promise<void> {
|
||||||
|
await server.startup();
|
||||||
|
server.startup().should.be.rejected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('404 on unknown requests', async function (): Promise<void> {
|
||||||
|
const status = 404;
|
||||||
|
|
||||||
|
const server = new SimpleWebServer();
|
||||||
|
const port = await server.startup();
|
||||||
|
try {
|
||||||
|
const result = await axios.get(`http://localhost:${port}/hello`);
|
||||||
|
should(result).be.undefined();
|
||||||
|
} catch (ex) {
|
||||||
|
should(ex.response.status).equal(status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Responds to GET requests', async function (): Promise<void> {
|
||||||
|
const msg = 'Hello World';
|
||||||
|
const status = 200;
|
||||||
|
|
||||||
|
const server = new SimpleWebServer();
|
||||||
|
const port = await server.startup();
|
||||||
|
server.on('/hello', (req, reqUrl, res) => {
|
||||||
|
res.writeHead(status);
|
||||||
|
res.write(msg);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await axios.get(`http://localhost:${port}/hello`);
|
||||||
|
should(response.status).equal(status);
|
||||||
|
should(response.data).equal(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Server shuts off', async function (): Promise<void> {
|
||||||
|
await server.startup();
|
||||||
|
server.shutdown().should.not.be.rejected();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -237,6 +237,13 @@ axios@^0.19.0:
|
|||||||
follow-redirects "1.5.10"
|
follow-redirects "1.5.10"
|
||||||
is-buffer "^2.0.2"
|
is-buffer "^2.0.2"
|
||||||
|
|
||||||
|
axios@^0.19.2:
|
||||||
|
version "0.19.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
||||||
|
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "1.5.10"
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
|||||||
Reference in New Issue
Block a user