Skip to content

Commit

Permalink
enhance info endpoint and bump to v0.1.13 (#117)
Browse files Browse the repository at this point in the history
In a previous PR some(much desired) version information was added to the
`/info` endpoint, however in certain environments(our docker container)
this blows up.

This PR prevents the endpoint from blowing up, while also providing some
optionality and defaults to fall back on and bumps version to `v0.1.13`

Added the text below to the `README.md` file:

- `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.
  • Loading branch information
LiranCohen authored Mar 5, 2024
1 parent 40e758c commit 3436b21
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 9 deletions.
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;
});
});
});

0 comments on commit 3436b21

Please sign in to comment.