Skip to content

Commit 06b8ae0

Browse files
Merge pull request #14 from volatilization/feature/client-side
Feature/client side
2 parents 78f48ff + c828e4f commit 06b8ae0

File tree

13 files changed

+991
-94
lines changed

13 files changed

+991
-94
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
};

src/js/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,9 @@ module.exports = {
1212
OutputResponse: require('./server/response/OutputResponse'),
1313
JsonOutputResponse: require('./server/response/JsonOutputResponse'),
1414
LoggedOutputResponse: require('./server/response/LoggedOutputResponse')
15+
},
16+
client: {
17+
OutputRequest: require('./client/request/OutputRequest'),
18+
InputResponse: require('./client/response/InputResponse')
1519
}
1620
}

src/js/server/request/InputRequest.js

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,22 @@ module.exports = class InputRequest {
1212
}
1313

1414
flush() {
15-
return new Promise((resolve) => {
16-
this.#inputStream.once('error', (e) => {
17-
throw new Error(e.message, {cause: 'INVALID_REQUEST'});
18-
});
19-
20-
if (!this.#isChunkedInputStream(this.#inputStream)) {
21-
return resolve(new InputRequest(
22-
this.#inputStream,
23-
this.#extractOptionsFromInputStream(this.#inputStream)
24-
));
25-
}
15+
return new Promise((resolve, reject) => {
16+
this.#inputStream.once('error', (e) =>
17+
reject(new Error(e.message, {cause: 'INVALID_REQUEST'}))
18+
);
2619

2720
let chunks = [];
2821
this.#inputStream.on('data', (chunk) => chunks.push(chunk));
2922
this.#inputStream.on('end', () => resolve(new InputRequest(
3023
this.#inputStream,
31-
{... this.#extractOptionsFromInputStream(this.#inputStream), body: Buffer.concat(chunks)}
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)
30+
}
3231
)));
3332
});
3433
}
@@ -37,7 +36,7 @@ module.exports = class InputRequest {
3736
return {
3837
method: this.#options.method.toString().toUpperCase(),
3938
path: this.#options.path.toString().toLowerCase()
40-
}
39+
};
4140
}
4241

4342
query() {
@@ -51,17 +50,4 @@ module.exports = class InputRequest {
5150
headers() {
5251
return this.#options.headers;
5352
}
54-
55-
#isChunkedInputStream(inputStream) {
56-
return ['POST', 'PUT'].some(method => method === inputStream.method.toString().toUpperCase());
57-
}
58-
59-
#extractOptionsFromInputStream(inputStream) {
60-
return {
61-
method: inputStream.method,
62-
path: new URL(inputStream.url, 'http://dummy').pathname,
63-
query: new URL(inputStream.url, 'http://dummy').searchParams,
64-
headers: inputStream.headers
65-
};
66-
}
67-
}
53+
};

src/js/server/request/JsonInputRequest.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ module.exports = class JsonInputRequest {
2828
}
2929

3030
body() {
31-
if (this.#origin.body() == null) {
32-
return null;
33-
}
34-
3531
try {
3632
return JSON.parse(this.#origin.body().toString());
3733

src/test/e2e/client/client.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/* node:coverage disable */
2+
3+
const {describe, it, before, after} = require('node:test');
4+
const assert = require('node:assert');
5+
const http = require('node:http');
6+
7+
const {
8+
OutputRequest,
9+
InputResponse
10+
} = require('../../../js/index').client;
11+
12+
const {
13+
Server,
14+
InputRequest,
15+
OutputResponse,
16+
Endpoints
17+
} = require('../../../js').server;
18+
19+
const serverConfig = new Server(
20+
http,
21+
new InputRequest(),
22+
new OutputResponse(),
23+
new Endpoints([
24+
{
25+
route() {
26+
return {
27+
method: 'GET',
28+
path: '/test'
29+
};
30+
},
31+
handle() {
32+
return {
33+
statusCode: 200
34+
};
35+
}
36+
},
37+
{
38+
route() {
39+
return {
40+
method: 'POST',
41+
path: '/test'
42+
};
43+
},
44+
handle() {
45+
return {
46+
statusCode: 201,
47+
body: 'test body'
48+
};
49+
}
50+
},
51+
]),
52+
{port: 8090}
53+
);
54+
55+
describe('client', async () => {
56+
let serverInstance;
57+
before(async () => {
58+
serverInstance = await serverConfig.start();
59+
});
60+
after(async () => await serverInstance.stop());
61+
62+
await it('should be started', async () => {
63+
await assert.doesNotReject(() =>
64+
new OutputRequest(http, new InputResponse(), {
65+
url: 'http://localhost', method: 'GET', port: '8090'
66+
}).send(),
67+
{message: 'fetch failed'});
68+
69+
await assert.doesNotReject(() =>
70+
new OutputRequest(http, new InputResponse(), {
71+
port: '8090', method: 'GET', host: 'localhost'
72+
}).send(),
73+
{message: 'fetch failed'});
74+
});
75+
76+
await it('should return 501', async () => {
77+
const response = await new OutputRequest(http, new InputResponse(), {
78+
url: 'http://localhost:8090/no_test', method: 'GET'
79+
}).send();
80+
81+
assert.strictEqual(response.statusCode(), 501);
82+
assert.strictEqual(response.body().toString(), 'There are no handler for request.');
83+
});
84+
85+
await it('should return 200 and no body', async () => {
86+
const response = await new OutputRequest(http, new InputResponse(), {
87+
url: 'http://localhost:8090/test', method: 'GET'
88+
}).send();
89+
90+
assert.strictEqual(response.statusCode(), 200);
91+
assert.strictEqual(response.body().length, 0);
92+
});
93+
94+
await it('should return 201 and test body', async () => {
95+
const response = await new OutputRequest(http, new InputResponse(), {
96+
url: 'http://localhost:8090/test', method: 'POST', body: 'test body'
97+
}).send();
98+
99+
assert.strictEqual(response.statusCode(), 201);
100+
assert.strictEqual(response.body().toString(), 'test body');
101+
});
102+
103+
await it('should not fall, but body is not a string', async () => {
104+
const response = await new OutputRequest(http, new InputResponse(), {
105+
url: 'http://localhost:8090/test', method: 'POST', body: {}
106+
}).send();
107+
108+
assert.strictEqual(response.statusCode(), 201);
109+
assert.strictEqual(response.body().toString(), 'test body');
110+
});
111+
});

src/test/e2e/json.server.js renamed to src/test/e2e/server/json.server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const {
77
OutputResponse,
88
JsonOutputResponse,
99
Endpoints
10-
} = require('../../js').server;
10+
} = require('../../../js').server;
1111
const {describe, it, before, after} = require('node:test');
1212
const assert = require('node:assert');
1313
const http = require('node:http');

src/test/e2e/server.js renamed to src/test/e2e/server/server.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
/* node:coverage disable */
22

3+
const {describe, it, before, after} = require('node:test');
4+
const assert = require('node:assert');
5+
6+
const http = require('node:http');
37
const {
48
Server,
59
InputRequest,
610
OutputResponse,
711
Endpoints
8-
} = require('../../js').server;
9-
const {describe, it, before, after} = require('node:test');
10-
const assert = require('node:assert');
11-
const http = require('node:http');
12+
} = require('../../../js').server;
13+
1214

1315
const testBody = {value: 'value', queryValue: 'otherQueryValue'};
1416

0 commit comments

Comments
 (0)