Skip to content

Commit bac8d8c

Browse files
author
Hage Yaapa
committed
feat: add http-server package
* Decouple the creation of HTTP/HTTPS server from the `rest` package. * Create new package (`http-server`) to handle the creation of HTTP/HTTPS server.
1 parent 4a9c5a8 commit bac8d8c

File tree

14 files changed

+411
-29
lines changed

14 files changed

+411
-29
lines changed

packages/http-server/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

packages/http-server/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# @loopback/http-server
2+
3+
This package implements the HTTP / HTTPS server endpoint for LoopBack 4 apps.
4+
5+
## Overview
6+
7+
This is an internal package used by LoopBack 4 for creating HTTP / HTTPS server.
8+
9+
## Installation
10+
11+
To use this package, you'll need to install `@loopback/http-server`.
12+
13+
```sh
14+
npm i @loopback/http-server
15+
```
16+
17+
## Usage
18+
19+
`@loopback/http-server` should be instantiated with a request handler function, and an HTTP / HTTPS options object.
20+
21+
```js
22+
const httpServer = new HttpServer((req, res) => { res.end('Hello world')}, {port: 3000, host: ''});
23+
```
24+
25+
Instance methods of `HttpServer`.
26+
27+
| Method | Description |
28+
| ------- | -------------------- |
29+
| `start()` | Starts the server |
30+
| `stop()` | Stops the server |
31+
32+
Instance properties of `HttpServer`.
33+
34+
| Property | Description |
35+
| ----------- | ---------------------- |
36+
| `address` | Address details |
37+
| `host` | host of the server |
38+
| `port` | port of the server |
39+
| `protocol` | protocol of the server |
40+
| `url` | url the server |
41+
42+
## Contributions
43+
44+
- [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing#guidelines)
45+
- [Join the team](https://github.com/strongloop/loopback-next/issues/110)
46+
47+
## Tests
48+
49+
Run `npm test` from the root folder.
50+
51+
## Contributors
52+
53+
See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors).
54+
55+
## License
56+
57+
MIT

packages/http-server/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './dist8';

packages/http-server/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright IBM Corp. 2017. All Rights Reserved.
2+
// Node module: @loopback/http-server
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
module.exports = require('@loopback/dist-util').loadDist(__dirname);

packages/http-server/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
2+
// Node module: @loopback/http-server
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
export * from './src';

packages/http-server/package.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "@loopback/http-server",
3+
"version": "0.0.1",
4+
"description": "",
5+
"engines": {
6+
"node": ">=8"
7+
},
8+
"scripts": {
9+
"acceptance": "lb-mocha \"DIST/test/acceptance/**/*.js\"",
10+
"build": "npm run build:dist8 && npm run build:dist10",
11+
"build:apidocs": "lb-apidocs",
12+
"build:current": "lb-tsc",
13+
"build:dist8": "lb-tsc es2017",
14+
"build:dist10": "lb-tsc es2018",
15+
"clean": "lb-clean loopback-http-server*.tgz dist* package api-docs",
16+
"pretest": "npm run build:current",
17+
"integration": "lb-mocha \"DIST/test/integration/**/*.js\"",
18+
"test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/integration/**/*.js\" \"DIST/test/acceptance/**/*.js\"",
19+
"unit": "lb-mocha \"DIST/test/unit/**/*.js\"",
20+
"verify": "npm pack && tar xf loopback-http-server*.tgz && tree package && npm run clean"
21+
},
22+
"author": "IBM",
23+
"copyright.owner": "IBM Corp.",
24+
"license": "MIT",
25+
"dependencies": {
26+
"@loopback/dist-util": "^0.3.1",
27+
"p-event": "^2.0.0"
28+
},
29+
"devDependencies": {
30+
"@loopback/build": "^0.6.5",
31+
"@loopback/core": "^0.8.4",
32+
"@loopback/testlab": "^0.10.4",
33+
"@types/node": "^10.1.2",
34+
"@types/p-event": "^1.3.0",
35+
"@types/request-promise-native": "^1.0.14",
36+
"request-promise-native": "^1.0.5"
37+
},
38+
"files": [
39+
"README.md",
40+
"index.js",
41+
"index.d.ts",
42+
"dist*/src",
43+
"dist*/index*",
44+
"src"
45+
],
46+
"repository": {
47+
"type": "git",
48+
"url": "https://github.com/strongloop/loopback-next.git"
49+
}
50+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
2+
// Node module: @loopback/http-server
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {createServer, Server, ServerRequest, ServerResponse} from 'http';
7+
import {AddressInfo} from 'net';
8+
import * as pEvent from 'p-event';
9+
10+
export type HttpRequestListener = (
11+
req: ServerRequest,
12+
res: ServerResponse,
13+
) => void;
14+
15+
/**
16+
* Object for specifyig the HTTP / HTTPS server options
17+
*/
18+
export type HttpServerOptions = {
19+
port?: number;
20+
host?: string;
21+
protocol?: HttpProtocol;
22+
};
23+
24+
export type HttpProtocol = 'http' | 'https'; // Will be extended to `http2` in the future
25+
26+
/**
27+
* HTTP / HTTPS server used by LoopBack's RestServer
28+
*
29+
* @export
30+
* @class HttpServer
31+
*/
32+
export class HttpServer {
33+
private _port: number;
34+
private _host?: string;
35+
private _started: Boolean;
36+
private _protocol: HttpProtocol;
37+
private _address: AddressInfo;
38+
private httpRequestListener: HttpRequestListener;
39+
private httpServer: Server;
40+
41+
/**
42+
* @param httpServerOptions
43+
* @param httpRequestListener
44+
*/
45+
constructor(
46+
httpRequestListener: HttpRequestListener,
47+
httpServerOptions?: HttpServerOptions,
48+
) {
49+
this.httpRequestListener = httpRequestListener;
50+
if (!httpServerOptions) httpServerOptions = {};
51+
this._port = httpServerOptions.port || 0;
52+
this._host = httpServerOptions.host || undefined;
53+
this._protocol = httpServerOptions.protocol || 'http';
54+
}
55+
56+
/**
57+
* Starts the HTTP / HTTPS server
58+
*/
59+
public async start() {
60+
this.httpServer = createServer(this.httpRequestListener);
61+
this.httpServer.listen(this._port, this._host);
62+
await pEvent(this.httpServer, 'listening');
63+
this._started = true;
64+
this._address = this.httpServer.address() as AddressInfo;
65+
}
66+
67+
/**
68+
* Stops the HTTP / HTTPS server
69+
*/
70+
public async stop() {
71+
if (this.httpServer) {
72+
this.httpServer.close();
73+
await pEvent(this.httpServer, 'close');
74+
this._started = false;
75+
}
76+
}
77+
78+
/**
79+
* Protocol of the HTTP / HTTPS server
80+
*/
81+
public get protocol(): HttpProtocol {
82+
return this._protocol;
83+
}
84+
85+
/**
86+
* Port number of the HTTP / HTTPS server
87+
*/
88+
public get port(): number {
89+
return (this._address && this._address.port) || this._port;
90+
}
91+
92+
/**
93+
* Host of the HTTP / HTTPS server
94+
*/
95+
public get host(): string | undefined {
96+
return (this._address && this._address.address) || this._host;
97+
}
98+
99+
/**
100+
* URL of the HTTP / HTTPS server
101+
*/
102+
public get url(): string {
103+
return `${this._protocol}://${this.host}:${this.port}`;
104+
}
105+
106+
/**
107+
* State of the HTTP / HTTPS server
108+
*/
109+
public get started(): Boolean {
110+
return this._started;
111+
}
112+
113+
/**
114+
* Address of the HTTP / HTTPS server
115+
*/
116+
public get address(): AddressInfo {
117+
return this._address;
118+
}
119+
}

packages/http-server/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './http-server';
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/http-server
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
import {HttpServer} from '../../';
6+
import {supertest, expect} from '@loopback/testlab';
7+
import * as makeRequest from 'request-promise-native';
8+
import {ServerRequest, ServerResponse} from 'http';
9+
10+
describe('HttpServer (integration)', () => {
11+
it('starts server', async () => {
12+
const server = new HttpServer(dummyRequestHandler);
13+
await server.start();
14+
supertest(server.url)
15+
.get('/')
16+
.expect(200);
17+
await server.stop();
18+
});
19+
20+
it('stops server', async () => {
21+
const server = new HttpServer(dummyRequestHandler);
22+
await server.start();
23+
await server.stop();
24+
await expect(
25+
makeRequest({
26+
uri: server.url,
27+
}),
28+
).to.be.rejectedWith(/ECONNREFUSED/);
29+
});
30+
31+
it('exports original port', async () => {
32+
const server = new HttpServer(dummyRequestHandler, {port: 0});
33+
expect(server)
34+
.to.have.property('port')
35+
.which.is.equal(0);
36+
});
37+
38+
it('exports reported port', async () => {
39+
const server = new HttpServer(dummyRequestHandler);
40+
await server.start();
41+
expect(server)
42+
.to.have.property('port')
43+
.which.is.a.Number()
44+
.which.is.greaterThan(0);
45+
await server.stop();
46+
});
47+
48+
it('does not permanently bind to the initial port', async () => {
49+
const server = new HttpServer(dummyRequestHandler);
50+
await server.start();
51+
const port = server.port;
52+
await server.stop();
53+
await server.start();
54+
expect(server)
55+
.to.have.property('port')
56+
.which.is.a.Number()
57+
.which.is.not.equal(port);
58+
await server.stop();
59+
});
60+
61+
it('exports original host', async () => {
62+
const server = new HttpServer(dummyRequestHandler);
63+
expect(server)
64+
.to.have.property('host')
65+
.which.is.equal(undefined);
66+
});
67+
68+
it('exports reported host', async () => {
69+
const server = new HttpServer(dummyRequestHandler);
70+
await server.start();
71+
expect(server)
72+
.to.have.property('host')
73+
.which.is.a.String();
74+
await server.stop();
75+
});
76+
77+
it('exports protocol', async () => {
78+
const server = new HttpServer(dummyRequestHandler);
79+
await server.start();
80+
expect(server)
81+
.to.have.property('protocol')
82+
.which.is.a.String()
83+
.match(/http|https/);
84+
await server.stop();
85+
});
86+
87+
it('exports url', async () => {
88+
const server = new HttpServer(dummyRequestHandler);
89+
await server.start();
90+
expect(server)
91+
.to.have.property('url')
92+
.which.is.a.String()
93+
.match(/http|https\:\/\//);
94+
await server.stop();
95+
});
96+
97+
it('exports address', async () => {
98+
const server = new HttpServer(dummyRequestHandler);
99+
await server.start();
100+
expect(server)
101+
.to.have.property('address')
102+
.which.is.an.Object();
103+
await server.stop();
104+
});
105+
106+
it('exports started', async () => {
107+
const server = new HttpServer(dummyRequestHandler);
108+
await server.start();
109+
expect(server.started).to.be.true();
110+
await server.stop();
111+
expect(server.started).to.be.false();
112+
});
113+
114+
it('start() returns a rejected promise', async () => {
115+
const serverA = new HttpServer(dummyRequestHandler);
116+
await serverA.start();
117+
const port = serverA.port;
118+
const serverB = new HttpServer(dummyRequestHandler, {port: port});
119+
expect(serverB.start()).to.be.rejectedWith(/EADDRINUSE/);
120+
});
121+
122+
function dummyRequestHandler(req: ServerRequest, res: ServerResponse): void {
123+
res.end();
124+
}
125+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "http://json.schemastore.org/tsconfig",
3+
"extends": "../build/config/tsconfig.common.json",
4+
"compilerOptions": {
5+
"rootDir": "."
6+
},
7+
"include": ["index.ts", "src", "test"]
8+
}

0 commit comments

Comments
 (0)