Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance info endpoint and bump to v0.1.13 #117

Merged
merged 9 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ the server exposes information about itself via the `/info` endpoint, which retu
"maxFileSize": 1073741824,
"registrationRequirements": ["proof-of-work-sha256-v0", "terms-of-service"],
"version": "0.1.5",
"sdkVersion": "0.2.6"
"sdkVersion": "0.2.6",
"webSocketSupport": "true"
}
```

- `server` is read from the `process.env.npm_package_name` variable that `npm` provides. If that does not exist, it will check for a `DWN_SERVER_PACKAGE_NAME` environment variable set by the user, or otherwise it will default to `@web5/dwn-server`.
- `version` and `sdkVersion` are read from the `package.json` file. It will locate the file's path either from the `process.env.npm_package_json` variable that `npm` provides. If that does not exist, it will check for a `DWN_SERVER_PACKAGE_JSON` environment variable set by the user, or otherwise it will default to `/dwn-server/package.json` which is the path within the default Docker container build.
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@web5/dwn-server",
"type": "module",
"version": "0.1.12",
"version": "0.1.13",
"files": [
"dist",
"src"
Expand Down
16 changes: 16 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ import bytes from 'bytes';
export type DwnServerConfig = typeof config;

export const config = {
/**
* Used to populate the `server` property returned by the `/info` endpoint.
*
* If running using `npm` the `process.env.npm_package_name` variable exists and we use that,
* otherwise we fall back on the use defined `DWN_SERVER_PACKAGE_NAME` or `@web5/dwn-server`.
*/
serverName: process.env.npm_package_name || process.env.DWN_SERVER_PACKAGE_NAME || '@web5/dwn-server',
/**
* Used to populate the `version` and `sdkVersion` properties returned by the `/info` endpoint.
*
* The `version` and `sdkVersion` are pulled from `package.json` at runtime.
* If running using `npm` the `process.env.npm_package_json` variable exists as the filepath, so we use that.
* Otherwise we check to see if a specific `DWN_SERVER_PACKAGE_JSON` exists, if it does we use that.
* Finally if both of those options don't exist we resort to the path within the docker server image, located at `/dwn-server/package.json`
*/
packageJsonPath: process.env.npm_package_json || process.env.DWN_SERVER_PACKAGE_JSON || '/dwn-server/package.json',
// max size of data that can be provided with a RecordsWrite
maxRecordDataSize: bytes(process.env.MAX_RECORD_DATA_SIZE || '1gb'),
// port that server listens on
Expand Down
21 changes: 17 additions & 4 deletions src/http-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import { jsonRpcRouter } from './json-rpc-api.js';
import { requestCounter, responseHistogram } from './metrics.js';
import type { RegistrationManager } from './registration/registration-manager.js';

const packageJson = process.env.npm_package_json ? JSON.parse(readFileSync(process.env.npm_package_json).toString()) : {};

export class HttpApi {
#config: DwnServerConfig;
#packageInfo: { version?: string, sdkVersion?: string, server: string };
#api: Express;
#server: http.Server;
registrationManager: RegistrationManager;
Expand All @@ -33,6 +33,19 @@ export class HttpApi {
constructor(config: DwnServerConfig, dwn: Dwn, registrationManager?: RegistrationManager) {
console.log(config);

this.#packageInfo = {
server: config.serverName,
};

try {
// We populate the `version` and `sdkVersion` properties from the `package.json` file.
const packageJson = JSON.parse(readFileSync(config.packageJsonPath).toString());
this.#packageInfo.version = packageJson.version;
this.#packageInfo.sdkVersion = packageJson.dependencies ? packageJson.dependencies['@tbd54566975/dwn-sdk-js'] : undefined;
} catch (error: any) {
log.error('could not read `package.json` for version info', error);
}

this.#config = config;
this.#api = express();
this.#server = http.createServer(this.#api);
Expand Down Expand Up @@ -185,11 +198,11 @@ export class HttpApi {
}

res.json({
server : process.env.npm_package_name,
server : this.#packageInfo.server,
maxFileSize : config.maxRecordDataSize,
registrationRequirements : registrationRequirements,
version : packageJson.version,
sdkVersion : packageJson.dependencies['@tbd54566975/dwn-sdk-js'],
version : this.#packageInfo.version,
sdkVersion : this.#packageInfo.sdkVersion,
webSocketSupport : config.webSocketSupport,
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/json-rpc-handlers/dwn/process-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const handleDwnProcessMessage: JsonRpcHandler = async (
);

// log the unhandled error response
log.error('handleDwnProcessMessage error', jsonRpcResponse, e);
log.error('handleDwnProcessMessage error', jsonRpcResponse, dwnRequest, e);
return { jsonRpcResponse } as HandlerResponse;
}
};
64 changes: 64 additions & 0 deletions tests/http-api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// node.js 18 and earlier, needs globalThis.crypto polyfill
import sinon from 'sinon';
import {
Cid,
DataStream,
Expand All @@ -18,6 +19,7 @@ import request from 'supertest';
import { v4 as uuidv4 } from 'uuid';

import { config } from '../src/config.js';
import log from 'loglevel';
import { HttpApi } from '../src/http-api.js';
import type {
JsonRpcErrorResponse,
Expand Down Expand Up @@ -71,6 +73,7 @@ describe('http api', function () {
});

beforeEach(async function () {
sinon.restore();
server = await httpApi.start(3000);
});

Expand All @@ -80,6 +83,7 @@ describe('http api', function () {
});

after(function () {
sinon.restore();
clock.restore();
});

Expand Down Expand Up @@ -561,6 +565,66 @@ describe('http api', function () {
info = await resp.json();
expect(info['server']).to.equal('@web5/dwn-server');
expect(info['webSocketSupport']).to.equal(false);

// restore old config value
config.webSocketSupport = true;
});

it('verify /info still returns when package.json file does not exist', async function () {
server.close();
server.closeAllConnections();

// set up spy to check for an error log by the server
const logSpy = sinon.spy(log, 'error');

// set the config to an invalid file path
const packageJsonConfig = config.packageJsonPath;
config.packageJsonPath = '/some/invalid/file.json';
httpApi = new HttpApi(config, dwn, registrationManager);
server = await httpApi.start(3000);

const resp = await fetch(`http://localhost:3000/info`);
const info = await resp.json();
expect(resp.status).to.equal(200);

// check that server name exists in the info object
expect(info['server']).to.equal('@web5/dwn-server');

// check that `sdkVersion` and `version` are undefined as they were not abel to be retrieved from the invalid file.
expect(info['sdkVersion']).to.be.undefined;
expect(info['version']).to.be.undefined;

// check the logSpy was called
expect(logSpy.callCount).to.equal(1);
expect(logSpy.args[0][0]).to.contain('could not read `package.json` for version info');

// restore old config path
config.packageJsonPath = packageJsonConfig;
});

it('verify /info returns server name from config', async function () {
server.close();
server.closeAllConnections();

// set a custom name for the `serverName`
const serverName = config.serverName;
config.serverName = '@web5/dwn-server-2'
httpApi = new HttpApi(config, dwn, registrationManager);
server = await httpApi.start(3000);

const resp = await fetch(`http://localhost:3000/info`);
const info = await resp.json();
expect(resp.status).to.equal(200);

// verify that the custom server name was passed to the info endpoint
expect(info['server']).to.equal('@web5/dwn-server-2');

// verify that `sdkVersion` and `version` exist.
expect(info['sdkVersion']).to.not.be.undefined;
expect(info['version']).to.not.be.undefined;

// restore server name config
config.serverName = serverName;
});
});
});
Loading