Skip to content

Commit

Permalink
refactor(http): use node-fetch instead of axios in http plugin (#120)
Browse files Browse the repository at this point in the history
* refactor(http): use node-fetch instead of axios in http plugin

Add also response header to result returned by salngroom that now is contains the fields: status, result, headers

* refactor(http): use nock@beta in tests since latest stable nock does not support fetch

nock/nock#2397
  • Loading branch information
matteo-cristino committed Mar 28, 2024
1 parent 6e9965c commit 53a9c23
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 51 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:preview": "vitepress preview docs",
"docs": "pnpm run docs:api && pnpm run docs:build"
"docs": "pnpm run docs:api && pnpm run docs:build"
},
"devDependencies": {
"@lerna-lite/publish": "^2.7.2",
Expand All @@ -33,7 +33,7 @@
"esbuild": "^0.19.9",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"nock": "^13.4.0",
"nock": "14.0.0-beta.5",
"prettier": "^3.1.1",
"ts-node": "^10.9.2",
"tslib": "^2.6.2",
Expand Down
3 changes: 1 addition & 2 deletions pkg/http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"version": "1.27.6",
"dependencies": {
"@slangroom/core": "workspace:*",
"@slangroom/shared": "workspace:*",
"axios": "^1.6.2"
"@slangroom/shared": "workspace:*"
},
"repository": "https://github.com/dyne/slangroom",
"license": "AGPL-3.0-only",
Expand Down
68 changes: 41 additions & 27 deletions pkg/http/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import type { JsonableArray, JsonableObject } from '@slangroom/shared';
import { Plugin, type PluginExecutor } from '@slangroom/core';
import axios, { type AxiosRequestConfig } from 'axios';

export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';

Expand All @@ -14,70 +13,85 @@ const p = new Plugin();
* The default timeout of an HTTP request in milliseconds.
*/
export const DefaultTimeoutMs = 5000;

const { request } = axios.create({
headers: { 'Content-Type': 'application/json' },
validateStatus: null,
export const DefaultHeaders = { 'Content-Type': 'application/json' }
let defaultOptions: Record<string, any> = {
timeout: DefaultTimeoutMs,
});
validateStatus: null,
headers: DefaultHeaders
}

const defaultRequest = (m: HttpMethod): PluginExecutor => {
return async (ctx) => {
const url = ctx.fetchConnect()[0];
// TODO: typecheck headers
const headers = ctx.get('headers') as any;
const object = ctx.get('object');
const conf: AxiosRequestConfig = { url: url, method: m };
if (object) conf.data = object;
if (headers) conf.headers = headers;
const options = defaultOptions;
options['method'] = m;
if (object) options['body'] = JSON.stringify(object);
if (headers) options['headers'] = { ...DefaultHeaders, ...headers };
try {
const req = await request(conf);
return ctx.pass({ status: req.status.toString(), result: req.data });
const response = await fetch(url, options);
let data = await response.text();
try {
data = JSON.parse(data);
} catch(e) {}
const responseHeaders: Record<string, any> = {}
response.headers.forEach((v, k) => {
responseHeaders[k] = v
})
return ctx.pass({ status: response.status.toString(), result: data, headers: responseHeaders });
} catch (e) {
if (axios.isAxiosError(e)) return ctx.pass({ status: e.code ?? '', result: '' });
if (e.isFetchError) return ctx.pass({ status: e.code ?? '', result: e.message, headers: {} });
throw e;
}
};
};

const sameParallelRequest = (m: HttpMethod, isSame: boolean): PluginExecutor => {
return async (ctx) => {
const reqs: ReturnType<typeof request<any>>[] = [];
const reqs: Promise<Response>[] = [];
const urls = ctx.fetchConnect();
const options = defaultOptions;
options['method'] = m;
// TODO: typecheck headers
const headers = ctx.get('headers') as any;

if (headers) options['headers'] = { ...DefaultHeaders, ...headers };
if (isSame) {
// TODO: typecheck object JsonableObject
const object = ctx.get('object') as undefined | JsonableObject;
for (const u of urls) {
const conf: AxiosRequestConfig = { url: u, method: m };
if (headers) conf.headers = headers;
if (object) conf.data = object;
reqs.push(request(conf));
reqs.push(fetch(u, { ...options, body: JSON.stringify(object) }));
}
}
// parallel
else {
// TODO: typecheck object (JsonableArray of JsonableObject)
const objects = ctx.get('object') as undefined | JsonableArray;
for (const [i, u] of urls.entries()) {
const conf: AxiosRequestConfig = { url: u, method: m };
if (headers) conf.headers = headers;
if (objects) conf.data = objects[i];
reqs.push(request(conf));
reqs.push(fetch(u, { ...options, body: JSON.stringify(objects && objects[i]) }));
}
}

const results = (await Promise.allSettled(reqs)).map((x) => {
if (x.status === 'fulfilled')
return { status: x.value.status.toString(), result: x.value.data };
const results = await Promise.all((await Promise.allSettled(reqs)).map(async (x) => {
if (x.status === 'fulfilled') {
let data = await x.value.text();
try {
data = JSON.parse(data);
} catch(e) {}
const responseHeaders: Record<string, any> = {}
x.value.headers.forEach((v, k) => {
responseHeaders[k] = v
})
return { status: x.value.status.toString(), result: data, headers: responseHeaders };
}


const err = x.reason;
if (axios.isAxiosError(err)) return { status: err.code ?? '', result: '' };
if (err.isFetchError) return { status: err.code ?? '', result: err.message, headers: {} };

throw x.reason;
});
}));

return ctx.pass(results);
};
Expand Down
8 changes: 6 additions & 2 deletions pkg/http/test/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Slangroom } from '@slangroom/core';
import { http } from '@slangroom/http';

nock('http://localhost')
.defaultReplyHeaders({'test_header': '@slangroom/http'})
.get('/greeting-es')
.reply(200, { req: 'Hola chico!' })
.get('/greeting-en')
Expand Down Expand Up @@ -56,8 +57,8 @@ Then I connect to 'final_endpoints' and send object 'string_array' and do parall
final_endpoints: ['http://localhost/sendresult', 'http://localhost/sendresult'],
string_array: [{ req: 'Hola chico!' }, { req: 'Hi!' }],
results: [
{ status: '200', result: 'received result' },
{ status: '200', result: 'received result' },
{ status: '200', result: 'received result', headers: { test_header: '@slangroom/http'} },
{ status: '200', result: 'received result', headers: { test_header: '@slangroom/http'} },
],
},
res.logs,
Expand Down Expand Up @@ -88,6 +89,9 @@ Then print data
auth: {
result: 'Yes, you can!',
status: '200',
headers: {
test_header: '@slangroom/http',
}
},
},
res.logs,
Expand Down
17 changes: 10 additions & 7 deletions pkg/http/test/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ test('Simple GET', async (t) => {
2: 123,
},
},
headers: {
'content-type': 'application/json'
}
},
});
});
Expand All @@ -69,7 +72,7 @@ test('single put with data', async (t) => {
const res = await defaults.putObject(ctx);
t.deepEqual(res, {
ok: true,
value: { status: '200', result: 'received result' },
value: { status: '200', result: 'received result', headers: {} },
});
});

Expand All @@ -80,7 +83,7 @@ test('single post with data', async (t) => {
const res = await defaults.postObject(ctx);
t.deepEqual(res, {
ok: true,
value: { status: '200', result: 'received result' },
value: { status: '200', result: 'received result', headers: {} },
});
});

Expand All @@ -93,8 +96,8 @@ test('multiple post with data', async (t) => {
t.deepEqual(res, {
ok: true,
value: [
{ status: '200', result: 'received result' },
{ status: '404', result: "doesn't exist, mate" },
{ status: '200', result: 'received result', headers: {} },
{ status: '404', result: "doesn't exist, mate", headers: {} },
],
});
});
Expand All @@ -112,9 +115,9 @@ test('POSTs with custom different', async (t) => {
t.deepEqual(res, {
ok: true,
value: [
{ status: '200', result: 'received result' },
{ status: '404', result: "doesn't exist, mate" },
{ status: '500', result: 'Did not receive the result' },
{ status: '200', result: 'received result', headers: {} },
{ status: '404', result: "doesn't exist, mate", headers: {} },
{ status: '500', result: 'Did not receive the result', headers: {} },
],
});
});
16 changes: 5 additions & 11 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 53a9c23

Please sign in to comment.