Skip to content

Commit

Permalink
feat: add http-server package
Browse files Browse the repository at this point in the history
* Decouple the creation of HTTP/HTTPS server from the `rest` package.

* Create new package (`http-server`) to handle the creation of HTTP/HTTPS server.
  • Loading branch information
Hage Yaapa committed Jun 7, 2018
1 parent 4a9c5a8 commit 1f89ee5
Show file tree
Hide file tree
Showing 14 changed files with 411 additions and 29 deletions.
1 change: 1 addition & 0 deletions packages/http-server/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
57 changes: 57 additions & 0 deletions packages/http-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# @loopback/http-server

This package implements the HTTP / HTTPS server endpoint for LoopBack 4 apps.

## Overview

This is an internal package used by LoopBack 4 for creating HTTP / HTTPS server.

## Installation

To use this package, you'll need to install `@loopback/http-server`.

```sh
npm i @loopback/http-server
```

## Usage

`@loopback/http-server` should be instantiated with a request handler function, and an HTTP / HTTPS options object.

```js
const httpServer = new HttpServer((req, res) => { res.end('Hello world')}, {port: 3000, host: ''});
```

Instance methods of `HttpServer`.

| Method | Description |
| ------- | -------------------- |
| `start()` | Starts the server |
| `stop()` | Stops the server |

Instance properties of `HttpServer`.

| Property | Description |
| ----------- | ---------------------- |
| `address` | Address details |
| `host` | host of the server |
| `port` | port of the server |
| `protocol` | protocol of the server |
| `url` | url the server |

## Contributions

- [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing#guidelines)
- [Join the team](https://github.com/strongloop/loopback-next/issues/110)

## Tests

Run `npm test` from the root folder.

## Contributors

See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors).

## License

MIT
1 change: 1 addition & 0 deletions packages/http-server/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist8';
6 changes: 6 additions & 0 deletions packages/http-server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: @loopback/http-server
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

module.exports = require('@loopback/dist-util').loadDist(__dirname);
6 changes: 6 additions & 0 deletions packages/http-server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: @loopback/http-server
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './src';
50 changes: 50 additions & 0 deletions packages/http-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@loopback/http-server",
"version": "0.0.1",
"description": "",
"engines": {
"node": ">=8"
},
"scripts": {
"acceptance": "lb-mocha \"DIST/test/acceptance/**/*.js\"",
"build": "npm run build:dist8 && npm run build:dist10",
"build:apidocs": "lb-apidocs",
"build:current": "lb-tsc",
"build:dist8": "lb-tsc es2017",
"build:dist10": "lb-tsc es2018",
"clean": "lb-clean loopback-http-server*.tgz dist* package api-docs",
"pretest": "npm run build:current",
"integration": "lb-mocha \"DIST/test/integration/**/*.js\"",
"test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/integration/**/*.js\" \"DIST/test/acceptance/**/*.js\"",
"unit": "lb-mocha \"DIST/test/unit/**/*.js\"",
"verify": "npm pack && tar xf loopback-http-server*.tgz && tree package && npm run clean"
},
"author": "IBM",
"copyright.owner": "IBM Corp.",
"license": "MIT",
"dependencies": {
"@loopback/dist-util": "^0.3.1",
"p-event": "^2.0.0"
},
"devDependencies": {
"@loopback/build": "^0.6.5",
"@loopback/core": "^0.8.4",
"@loopback/testlab": "^0.10.4",
"@types/node": "^10.1.2",
"@types/p-event": "^1.3.0",
"@types/request-promise-native": "^1.0.14",
"request-promise-native": "^1.0.5"
},
"files": [
"README.md",
"index.js",
"index.d.ts",
"dist*/src",
"dist*/index*",
"src"
],
"repository": {
"type": "git",
"url": "https://github.com/strongloop/loopback-next.git"
}
}
119 changes: 119 additions & 0 deletions packages/http-server/src/http-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: @loopback/http-server
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {createServer, Server, ServerRequest, ServerResponse} from 'http';
import {AddressInfo} from 'net';
import * as pEvent from 'p-event';

export type HttpRequestListener = (
req: ServerRequest,
res: ServerResponse,
) => void;

/**
* Object for specifyig the HTTP / HTTPS server options
*/
export type HttpServerOptions = {
port?: number;
host?: string;
protocol?: HttpProtocol;
};

export type HttpProtocol = 'http' | 'https'; // Will be extended to `http2` in the future

/**
* HTTP / HTTPS server used by LoopBack's RestServer
*
* @export
* @class HttpServer
*/
export class HttpServer {
private _port: number;
private _host?: string;
private _started: Boolean;
private _protocol: HttpProtocol;
private _address: AddressInfo;
private httpRequestListener: HttpRequestListener;
private httpServer: Server;

/**
* @param httpServerOptions
* @param httpRequestListener
*/
constructor(
httpRequestListener: HttpRequestListener,
httpServerOptions?: HttpServerOptions,
) {
this.httpRequestListener = httpRequestListener;
if (!httpServerOptions) httpServerOptions = {};
this._port = httpServerOptions.port || 0;
this._host = httpServerOptions.host || undefined;
this._protocol = httpServerOptions.protocol || 'http';
}

/**
* Starts the HTTP / HTTPS server
*/
public async start() {
this.httpServer = createServer(this.httpRequestListener);
this.httpServer.listen(this._port, this._host);
await pEvent(this.httpServer, 'listening');
this._started = true;
this._address = this.httpServer.address() as AddressInfo;
}

/**
* Stops the HTTP / HTTPS server
*/
public async stop() {
if (this.httpServer) {
this.httpServer.close();
await pEvent(this.httpServer, 'close');
this._started = false;
}
}

/**
* Protocol of the HTTP / HTTPS server
*/
public get protocol(): HttpProtocol {
return this._protocol;
}

/**
* Port number of the HTTP / HTTPS server
*/
public get port(): number {
return (this._address && this._address.port) || this._port;
}

/**
* Host of the HTTP / HTTPS server
*/
public get host(): string | undefined {
return (this._address && this._address.address) || this._host;
}

/**
* URL of the HTTP / HTTPS server
*/
public get url(): string {
return `${this._protocol}://${this.host}:${this.port}`;
}

/**
* State of the HTTP / HTTPS server
*/
public get started(): Boolean {
return this._started;
}

/**
* Address of the HTTP / HTTPS server
*/
public get address(): AddressInfo {
return this._address;
}
}
1 change: 1 addition & 0 deletions packages/http-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './http-server';
125 changes: 125 additions & 0 deletions packages/http-server/test/integration/http-server.integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/http-server
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {HttpServer} from '../../';
import {supertest, expect} from '@loopback/testlab';
import * as makeRequest from 'request-promise-native';
import {ServerRequest, ServerResponse} from 'http';

describe('HttpServer (integration)', () => {
it('starts server', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
supertest(server.url)
.get('/')
.expect(200);
await server.stop();
});

it('stops server', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
await server.stop();
await expect(
makeRequest({
uri: server.url,
}),
).to.be.rejectedWith(/ECONNREFUSED/);
});

it('exports original port', async () => {
const server = new HttpServer(dummyRequestHandler, {port: 0});
expect(server)
.to.have.property('port')
.which.is.equal(0);
});

it('exports reported port', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
expect(server)
.to.have.property('port')
.which.is.a.Number()
.which.is.greaterThan(0);
await server.stop();
});

it('does not permanently bind to the initial port', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
const port = server.port;
await server.stop();
await server.start();
expect(server)
.to.have.property('port')
.which.is.a.Number()
.which.is.not.equal(port);
await server.stop();
});

it('exports original host', async () => {
const server = new HttpServer(dummyRequestHandler);
expect(server)
.to.have.property('host')
.which.is.equal(undefined);
});

it('exports reported host', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
expect(server)
.to.have.property('host')
.which.is.a.String();
await server.stop();
});

it('exports protocol', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
expect(server)
.to.have.property('protocol')
.which.is.a.String()
.match(/http|https/);
await server.stop();
});

it('exports url', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
expect(server)
.to.have.property('url')
.which.is.a.String()
.match(/http|https\:\/\//);
await server.stop();
});

it('exports address', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
expect(server)
.to.have.property('address')
.which.is.an.Object();
await server.stop();
});

it('exports started', async () => {
const server = new HttpServer(dummyRequestHandler);
await server.start();
expect(server.started).to.be.true();
await server.stop();
expect(server.started).to.be.false();
});

it('start() returns a rejected promise', async () => {
const serverA = new HttpServer(dummyRequestHandler);
await serverA.start();
const port = serverA.port;
const serverB = new HttpServer(dummyRequestHandler, {port: port});
expect(serverB.start()).to.be.rejectedWith(/EADDRINUSE/);
});

function dummyRequestHandler(req: ServerRequest, res: ServerResponse): void {
res.end();
}
});
8 changes: 8 additions & 0 deletions packages/http-server/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "../build/config/tsconfig.common.json",
"compilerOptions": {
"rootDir": "."
},
"include": ["index.ts", "src", "test"]
}
1 change: 1 addition & 0 deletions packages/rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@loopback/context": "^0.11.2",
"@loopback/core": "^0.8.4",
"@loopback/openapi-v3": "^0.10.5",
"@loopback/http-server": "^0.0.1",
"@loopback/openapi-v3-types": "^0.7.4",
"@types/cors": "^2.8.3",
"@types/express": "^4.11.1",
Expand Down
Loading

0 comments on commit 1f89ee5

Please sign in to comment.