objective-http 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -1
- package/package.json +1 -1
- package/src/js/client/request/OutputRequest.js +69 -0
- package/src/js/client/response/InputResponse.js +43 -0
- package/src/js/index.js +4 -0
- package/src/js/server/ClusteredServer.js +1 -3
- package/src/js/server/LoggedServer.js +11 -2
- package/src/js/server/Server.js +54 -33
- package/src/js/server/request/InputRequest.js +19 -32
- package/src/js/server/request/JsonInputRequest.js +8 -3
- package/src/js/server/request/LoggedInputRequest.js +4 -0
- package/src/js/server/response/OutputResponse.js +4 -0
- package/.github/workflows/npm-publish.yml +0 -21
package/README.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# objective-http
|
|
2
2
|
Proxy classes for creating a http server
|
|
3
3
|
|
|
4
|
-
##
|
|
4
|
+
## Server
|
|
5
|
+
|
|
6
|
+
There are all ```Server``` classes feature.
|
|
7
|
+
Your endpoints must implement ```Endpoint``` class interface (```route``` and ```handle``` functions).
|
|
5
8
|
|
|
6
9
|
``` javascript
|
|
7
10
|
const http = require('node:http');
|
|
@@ -40,4 +43,40 @@ new ClusteredServer(
|
|
|
40
43
|
cluster,
|
|
41
44
|
{workers: workers_count}
|
|
42
45
|
).start();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Client
|
|
49
|
+
|
|
50
|
+
``` javascript
|
|
51
|
+
const https = require('node:https');
|
|
52
|
+
const {
|
|
53
|
+
OutputRequest,
|
|
54
|
+
InputResponse
|
|
55
|
+
} = require('objective-http').client;
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
const response = await new OutputRequest(
|
|
59
|
+
https,
|
|
60
|
+
new InputResponse(),
|
|
61
|
+
{
|
|
62
|
+
url: 'https://example.com',
|
|
63
|
+
method: 'POST',
|
|
64
|
+
body: 'test body'
|
|
65
|
+
})
|
|
66
|
+
.send();
|
|
67
|
+
|
|
68
|
+
console.log(response.body().toString());
|
|
69
|
+
|
|
70
|
+
//or
|
|
71
|
+
|
|
72
|
+
const request = new OutputRequest(https, new InputResponse());
|
|
73
|
+
|
|
74
|
+
const otherResponse = await (request
|
|
75
|
+
.copy({
|
|
76
|
+
url: 'https://example.com',
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: 'test body'
|
|
79
|
+
}))
|
|
80
|
+
.send()
|
|
81
|
+
|
|
43
82
|
```
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"objective-http","version":"1.0
|
|
1
|
+
{"name":"objective-http","version":"1.1.0","description":"Proxy classes for creating a http server","keywords":["web","web-server","http","http-server","oop"],"author":{"name":"volatilization","email":"volatilization@yandex.ru"},"repository":{"url":"git+https://github.com/volatilization/objective-http.git"},"license":"LGPL-3.0-only","main":"src/js/index.js"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module.exports = class OutputRequest {
|
|
2
|
+
#http;
|
|
3
|
+
#response;
|
|
4
|
+
#options;
|
|
5
|
+
|
|
6
|
+
constructor(http, response, options) {
|
|
7
|
+
this.#http = http;
|
|
8
|
+
this.#response = response;
|
|
9
|
+
this.#options = {method: 'GET', ...options};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
copy(options = this.#options, response = this.#response, http = this.#http) {
|
|
13
|
+
return new OutputRequest(http, response, {method: 'GET', ...options});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
send() {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
try {
|
|
19
|
+
this.#sendRequestOutputStream(
|
|
20
|
+
this.#configureRequestOutputStream(this.#http, this.#response, this.#options, resolve, reject),
|
|
21
|
+
this.#options);
|
|
22
|
+
|
|
23
|
+
} catch (e) {
|
|
24
|
+
throw new Error(e.message, {cause: 'INVALID_REQUEST'});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#configureRequestOutputStream(http, response, options, resolve, reject) {
|
|
30
|
+
if (options.url != null) {
|
|
31
|
+
return http.request(
|
|
32
|
+
options.url,
|
|
33
|
+
options,
|
|
34
|
+
async (responseInputStream) => {
|
|
35
|
+
await this.#flushResponseInputStream(responseInputStream, response, resolve, reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return http.request(
|
|
40
|
+
options,
|
|
41
|
+
async (responseInputStream) => {
|
|
42
|
+
await this.#flushResponseInputStream(responseInputStream, response, resolve, reject);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async #flushResponseInputStream(responseInputStream, response, resolve, reject) {
|
|
47
|
+
try {
|
|
48
|
+
resolve(await response
|
|
49
|
+
.copy(responseInputStream)
|
|
50
|
+
.flush());
|
|
51
|
+
|
|
52
|
+
} catch (e) {
|
|
53
|
+
reject(e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#sendRequestOutputStream(requestOutputStream, options) {
|
|
58
|
+
if (this.#needToByWritten(options)) {
|
|
59
|
+
requestOutputStream.write(options.body);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
requestOutputStream.end();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#needToByWritten(options) {
|
|
66
|
+
return ['POST', 'PUT'].some(method => method === options.method.toString().toUpperCase())
|
|
67
|
+
&& (options.body != null && typeof options.body === 'string');
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module.exports = class InputResponse {
|
|
2
|
+
#inputStream;
|
|
3
|
+
#options;
|
|
4
|
+
|
|
5
|
+
constructor(inputStream, options) {
|
|
6
|
+
this.#inputStream = inputStream;
|
|
7
|
+
this.#options = options;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
copy(inputStream = this.#inputStream, options = this.#options) {
|
|
11
|
+
return new InputResponse(inputStream, options);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
flush() {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
this.#inputStream.once('error', (e) =>
|
|
17
|
+
reject(new Error(e.message, {cause: 'INVALID_RESPONSE'}))
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
let chunks = [];
|
|
21
|
+
this.#inputStream.on('data', (chunk) => chunks.push(chunk));
|
|
22
|
+
this.#inputStream.on('end', () => resolve(new InputResponse(this.#inputStream,
|
|
23
|
+
{
|
|
24
|
+
statusCode: this.#inputStream.statusCode,
|
|
25
|
+
headers: new Headers(this.#inputStream.headers),
|
|
26
|
+
body: Buffer.concat(chunks)
|
|
27
|
+
}
|
|
28
|
+
)));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
statusCode() {
|
|
33
|
+
return this.#options.statusCode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
headers() {
|
|
37
|
+
return this.#options.headers;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
body() {
|
|
41
|
+
return this.#options.body;
|
|
42
|
+
}
|
|
43
|
+
};
|
package/src/js/index.js
CHANGED
|
@@ -12,5 +12,9 @@ module.exports = {
|
|
|
12
12
|
OutputResponse: require('./server/response/OutputResponse'),
|
|
13
13
|
JsonOutputResponse: require('./server/response/JsonOutputResponse'),
|
|
14
14
|
LoggedOutputResponse: require('./server/response/LoggedOutputResponse')
|
|
15
|
+
},
|
|
16
|
+
client: {
|
|
17
|
+
OutputRequest: require('./client/request/OutputRequest'),
|
|
18
|
+
InputResponse: require('./client/response/InputResponse')
|
|
15
19
|
}
|
|
16
20
|
}
|
|
@@ -8,13 +8,22 @@ module.exports = class LoggedServer {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async start() {
|
|
11
|
-
await this.#origin.start();
|
|
11
|
+
const server = await this.#origin.start();
|
|
12
12
|
|
|
13
13
|
this.#logger.debug(`HttpServer is running at port: ${this.#origin.options().port}`);
|
|
14
14
|
|
|
15
|
-
return this;
|
|
15
|
+
return new LoggedServer(server, this.#logger);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
async stop() {
|
|
19
|
+
const server = await this.#origin.stop();
|
|
20
|
+
|
|
21
|
+
this.#logger.debug(`HttpServer at port: ${this.#origin.options().port} is stopped`);
|
|
22
|
+
|
|
23
|
+
return new LoggedServer(server, this.#logger);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
18
27
|
options() {
|
|
19
28
|
return this.#origin.options();
|
|
20
29
|
}
|
package/src/js/server/Server.js
CHANGED
|
@@ -4,53 +4,74 @@ module.exports = class HttpServer {
|
|
|
4
4
|
#response;
|
|
5
5
|
#endpoints;
|
|
6
6
|
#options;
|
|
7
|
+
#server;
|
|
7
8
|
|
|
8
|
-
constructor(http, request, response, endpoints, options) {
|
|
9
|
+
constructor(http, request, response, endpoints, options, server) {
|
|
9
10
|
this.#http = http;
|
|
10
11
|
this.#request = request;
|
|
11
12
|
this.#response = response;
|
|
12
13
|
this.#endpoints = endpoints;
|
|
13
14
|
this.#options = options;
|
|
15
|
+
this.#server = server;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
start() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.copy(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const server = this.#http.createServer(async (requestStream, responseStream) => {
|
|
20
|
+
try {
|
|
21
|
+
return await (this.#response
|
|
22
|
+
.copy(responseStream, await this.#endpoints
|
|
23
|
+
.handle(await (this.#request
|
|
24
|
+
.copy(requestStream))
|
|
25
|
+
.flush())))
|
|
26
|
+
.flush();
|
|
27
|
+
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (e.cause === 'INVALID_REQUEST') {
|
|
30
|
+
return await (this.#response
|
|
31
|
+
.copy(responseStream, {
|
|
32
|
+
statusCode: 400,
|
|
33
|
+
body: e.message
|
|
34
|
+
}))
|
|
35
|
+
.flush();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return await (this.#response
|
|
39
|
+
.copy(responseStream, {
|
|
40
|
+
statusCode: 500,
|
|
41
|
+
body: 'Unexpected server error.'
|
|
42
|
+
}))
|
|
43
|
+
.flush();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
return new Promise(resolve => {
|
|
48
|
+
server.listen(
|
|
49
|
+
this.#options,
|
|
50
|
+
() => resolve(new HttpServer(
|
|
51
|
+
this.#http,
|
|
52
|
+
this.#request,
|
|
53
|
+
this.#response,
|
|
54
|
+
this.#endpoints,
|
|
55
|
+
this.#options,
|
|
56
|
+
server))
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
37
60
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
() => resolve(this)
|
|
49
|
-
);
|
|
61
|
+
stop() {
|
|
62
|
+
return new Promise(resolve => {
|
|
63
|
+
this.#server.close(
|
|
64
|
+
() => resolve(new HttpServer(
|
|
65
|
+
this.#http,
|
|
66
|
+
this.#request,
|
|
67
|
+
this.#response,
|
|
68
|
+
this.#endpoints,
|
|
69
|
+
this.#options))
|
|
70
|
+
);
|
|
50
71
|
});
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
options() {
|
|
54
75
|
return this.#options;
|
|
55
76
|
}
|
|
56
|
-
}
|
|
77
|
+
};
|
|
@@ -13,26 +13,22 @@ module.exports = class InputRequest {
|
|
|
13
13
|
|
|
14
14
|
flush() {
|
|
15
15
|
return new Promise((resolve, reject) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
this.#inputStream.once('error', (e) =>
|
|
17
|
+
reject(new Error(e.message, {cause: 'INVALID_REQUEST'}))
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
let chunks = [];
|
|
21
|
+
this.#inputStream.on('data', (chunk) => chunks.push(chunk));
|
|
22
|
+
this.#inputStream.on('end', () => resolve(new InputRequest(
|
|
23
|
+
this.#inputStream,
|
|
24
|
+
{
|
|
25
|
+
method: this.#inputStream.method,
|
|
26
|
+
path: new URL(this.#inputStream.url, 'http://dummy').pathname,
|
|
27
|
+
query: new URL(this.#inputStream.url, 'http://dummy').searchParams,
|
|
28
|
+
headers: new Headers(this.#inputStream.headers),
|
|
29
|
+
body: Buffer.concat(chunks)
|
|
24
30
|
}
|
|
25
|
-
|
|
26
|
-
let chunks = [];
|
|
27
|
-
this.#inputStream.on('data', (chunk) => chunks.push(chunk));
|
|
28
|
-
this.#inputStream.on('end', () => resolve(new InputRequest(
|
|
29
|
-
this.#inputStream,
|
|
30
|
-
{... this.#extractOptionsFromInputStream(this.#inputStream), body: Buffer.concat(chunks)}
|
|
31
|
-
)));
|
|
32
|
-
|
|
33
|
-
} catch (e) {
|
|
34
|
-
reject(e);
|
|
35
|
-
}
|
|
31
|
+
)));
|
|
36
32
|
});
|
|
37
33
|
}
|
|
38
34
|
|
|
@@ -40,7 +36,7 @@ module.exports = class InputRequest {
|
|
|
40
36
|
return {
|
|
41
37
|
method: this.#options.method.toString().toUpperCase(),
|
|
42
38
|
path: this.#options.path.toString().toLowerCase()
|
|
43
|
-
}
|
|
39
|
+
};
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
query() {
|
|
@@ -51,16 +47,7 @@ module.exports = class InputRequest {
|
|
|
51
47
|
return this.#options.body;
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
#extractOptionsFromInputStream(inputStream) {
|
|
59
|
-
return {
|
|
60
|
-
method: inputStream.method,
|
|
61
|
-
path: new URL(inputStream.url, 'http://dummy').pathname,
|
|
62
|
-
query: new URL(inputStream.url, 'http://dummy').searchParams,
|
|
63
|
-
headers: inputStream.headers
|
|
64
|
-
};
|
|
50
|
+
headers() {
|
|
51
|
+
return this.#options.headers;
|
|
65
52
|
}
|
|
66
|
-
}
|
|
53
|
+
};
|
|
@@ -28,11 +28,16 @@ module.exports = class JsonInputRequest {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
body() {
|
|
31
|
-
|
|
32
|
-
return
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(this.#origin.body().toString());
|
|
33
|
+
|
|
34
|
+
} catch (e) {
|
|
35
|
+
throw new Error('Wrong body format. Only JSON accepted.', {cause: 'INVALID_REQUEST'});
|
|
33
36
|
}
|
|
37
|
+
}
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
headers() {
|
|
40
|
+
return this.#origin.headers();
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
#useChunkMethod(requestMethod) {
|
|
@@ -31,6 +31,10 @@ module.exports = class OutputResponse {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
#mergeOptions(existedOptions, newOptions) {
|
|
34
|
+
if (newOptions == null) {
|
|
35
|
+
return existedOptions;
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
if (newOptions.statusCode != null) {
|
|
35
39
|
existedOptions.statusCode = newOptions.statusCode;
|
|
36
40
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
name: Publish release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
publish-npm:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: actions/checkout@v3
|
|
12
|
-
- uses: actions/setup-node@v3
|
|
13
|
-
with:
|
|
14
|
-
node-version: 21
|
|
15
|
-
registry-url: https://registry.npmjs.org/
|
|
16
|
-
- run: npm run prepareToPublish
|
|
17
|
-
env:
|
|
18
|
-
RELEASE: ${{github.event.release.name}}
|
|
19
|
-
- run: npm publish
|
|
20
|
-
env:
|
|
21
|
-
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|