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 CHANGED
@@ -1,7 +1,10 @@
1
1
  # objective-http
2
2
  Proxy classes for creating a http server
3
3
 
4
- ## Using
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.3","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"}
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
  }
@@ -16,10 +16,8 @@ module.exports = class ClusteredServer {
16
16
  }
17
17
 
18
18
  } else {
19
- await this.#origin.start();
19
+ return new ClusteredServer(await this.#origin.start(), this.#cluster, this.#options);
20
20
  }
21
-
22
- return this;
23
21
  }
24
22
 
25
23
  options() {
@@ -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
  }
@@ -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
- return new Promise(resolve => {
18
- this.#http
19
- .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();
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
- } catch (e) {
29
- if (e.cause === 'INVALID_REQUEST') {
30
- return this.#response
31
- .copy(responseStream, {
32
- statusCode: 400,
33
- body: e.message
34
- })
35
- .flush();
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
- return this.#response
39
- .copy(responseStream, {
40
- statusCode: 500,
41
- body: 'Unexpected server error.'
42
- })
43
- .flush();
44
- }
45
- })
46
- .listen(
47
- {port: this.#options.port},
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
- try {
17
- this.#inputStream.on('error', (e) => reject(e));
18
-
19
- if (!this.#isChunkedInputStream(this.#inputStream)) {
20
- resolve(new InputRequest(
21
- this.#inputStream,
22
- this.#extractOptionsFromInputStream(this.#inputStream)
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
- #isChunkedInputStream(inputStream) {
55
- return ['POST', 'PUT'].some(method => method === inputStream.method.toString().toUpperCase());
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
- if (this.#origin.body() == null) {
32
- return null;
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
- return JSON.parse(this.#origin.body().toString());
39
+ headers() {
40
+ return this.#origin.headers();
36
41
  }
37
42
 
38
43
  #useChunkMethod(requestMethod) {
@@ -30,4 +30,8 @@ module.exports = class LoggedInputRequest {
30
30
  body() {
31
31
  return this.#origin.body();
32
32
  }
33
+
34
+ headers() {
35
+ return this.#origin.headers();
36
+ }
33
37
  }
@@ -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}}