Skip to content

Commit

Permalink
Addressed various initialization issues and patterns (#143)
Browse files Browse the repository at this point in the history
There are more to be done, but this is a good start.

- Fixed confusing patterns of existing async method take callbacks, and yet return immediately without invoking and awaiting on the callback.
- Introduced  `DwnServer.stop()` for clarity and consistency.
- Introduced  `HttpApi.close()` for clarity and consistency.
- Added missing `removeProcessHandlers` as counterpart to
`setProcessHandlers` for proper clean up.
- Added `didResolver` to `DwnServerOptions` for overriding DID Resolver cache LevelDB implementation in tests to prevent DB locking issues.
- Used Poller to make some tests more stable.
  • Loading branch information
thehenrytsai authored Jul 8, 2024
1 parent 704cbb4 commit 24199fa
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 172 deletions.
79 changes: 71 additions & 8 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"url": "https://github.com/TBD54566975/dwn-server/issues"
},
"dependencies": {
"@tbd54566975/dwn-sdk-js": "0.4.0",
"@tbd54566975/dwn-sdk-js": "0.4.2",
"@tbd54566975/dwn-sql-store": "0.6.1",
"better-sqlite3": "^8.5.0",
"body-parser": "^1.20.2",
Expand All @@ -45,7 +45,7 @@
"readable-stream": "4.4.2",
"response-time": "2.3.2",
"uuid": "9.0.0",
"ws": "8.17.1"
"ws": "8.18.0"
},
"devDependencies": {
"@types/bytes": "3.1.1",
Expand Down
95 changes: 78 additions & 17 deletions src/dwn-server.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@
import type { DidResolver } from '@web5/dids';
import type { EventStream } from '@tbd54566975/dwn-sdk-js';
import { Dwn, EventEmitterStream } from '@tbd54566975/dwn-sdk-js';

import type { ProcessHandlers } from './process-handlers.js';
import type { Server } from 'http';
import type { WebSocketServer } from 'ws';
import type { DwnServerConfig } from './config.js';

import log from 'loglevel';
import prefix from 'loglevel-plugin-prefix';
import { type WebSocketServer } from 'ws';

import { config as defaultConfig } from './config.js';
import { getDWNConfig } from './storage.js';
import { HttpServerShutdownHandler } from './lib/http-server-shutdown-handler.js';

import { type DwnServerConfig, config as defaultConfig } from './config.js';
import { HttpApi } from './http-api.js';
import { setProcessHandlers } from './process-handlers.js';
import { getDWNConfig } from './storage.js';
import { WsApi } from './ws-api.js';
import { RegistrationManager } from './registration/registration-manager.js';
import { WsApi } from './ws-api.js';
import { Dwn, EventEmitterStream } from '@tbd54566975/dwn-sdk-js';
import { removeProcessHandlers, setProcessHandlers } from './process-handlers.js';

/**
* Options for the DwnServer constructor.
* This is different to DwnServerConfig in that the DwnServerConfig defines configuration that come from environment variables so (more) user facing.
* Where as DwnServerOptions wraps DwnServerConfig with additional overrides that can be used for testing.
*/
export type DwnServerOptions = {
/**
* A custom DID resolver to use in the DWN.
* Mainly for testing purposes. Ignored if `dwn` is provided.
*/
didResolver?: DidResolver;
dwn?: Dwn;
config?: DwnServerConfig;
};

/**
* State of the DwnServer, either Stopped or Started, to help short-circuit start and stop logic.
*/
enum DwnServerState {
Stopped,
Started
}

export class DwnServer {
serverState = DwnServerState.Stopped;
processHandlers: ProcessHandlers;

/**
* A custom DID resolver to use in the DWN.
* Mainly for testing purposes. Ignored if `dwn` is provided.
*/
didResolver?: DidResolver;
dwn?: Dwn;
config: DwnServerConfig;
#httpServerShutdownHandler: HttpServerShutdownHandler;
Expand All @@ -32,6 +59,8 @@ export class DwnServer {
*/
constructor(options: DwnServerOptions = {}) {
this.config = options.config ?? defaultConfig;

this.didResolver = options.didResolver;
this.dwn = options.dwn;

log.setLevel(this.config.logLevel as log.LogLevelDesc);
Expand All @@ -40,9 +69,17 @@ export class DwnServer {
prefix.apply(log);
}

/**
* Starts the DWN server.
*/
async start(): Promise<void> {
if (this.serverState === DwnServerState.Started) {
return;
}

await this.#setupServer();
setProcessHandlers(this);
this.processHandlers = setProcessHandlers(this);
this.serverState = DwnServerState.Started;
}

/**
Expand All @@ -68,17 +105,18 @@ export class DwnServer {
eventStream = new EventEmitterStream();
}

this.dwn = await Dwn.create(getDWNConfig(this.config, {
const dwnConfig = getDWNConfig(this.config, {
didResolver: this.didResolver,
tenantGate: registrationManager,
eventStream,
}));
})
this.dwn = await Dwn.create(dwnConfig);
}

this.#httpApi = await HttpApi.create(this.config, this.dwn, registrationManager);

await this.#httpApi.start(this.config.port, () => {
log.info(`HttpServer listening on port ${this.config.port}`);
});
await this.#httpApi.start(this.config.port);
log.info(`HttpServer listening on port ${this.config.port}`);

this.#httpServerShutdownHandler = new HttpServerShutdownHandler(
this.#httpApi.server,
Expand All @@ -91,8 +129,31 @@ export class DwnServer {
}
}

stop(callback: () => void): void {
this.#httpServerShutdownHandler.stop(callback);
/**
* Stops the DWN server.
*/
async stop(): Promise<void> {
if (this.serverState === DwnServerState.Stopped) {
return;
}

await this.dwn.close();
await this.#httpApi.close();

// close WebSocket server if it was initialized
if (this.#wsApi !== undefined) {
await this.#wsApi.close();
}

await new Promise<void>((resolve) => {
this.#httpServerShutdownHandler.stop(() => {
resolve();
});
});

removeProcessHandlers(this.processHandlers);

this.serverState = DwnServerState.Stopped;
}

get httpServer(): Server {
Expand Down
36 changes: 29 additions & 7 deletions src/http-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,6 @@ export class HttpApi {
this.#setupWeb5ConnectServerRoutes();
}

#listen(port: number, callback?: () => void): void {
this.#server.listen(port, callback);
}

#setupRegistrationRoutes(): void {
if (this.#config.registrationProofOfWorkEnabled) {
this.#api.get('/registration/proof-of-work', async (_req: Request, res: Response) => {
Expand Down Expand Up @@ -458,8 +454,34 @@ export class HttpApi {
});
}

async start(port: number, callback?: () => void): Promise<http.Server> {
this.#listen(port, callback);
return this.#server;
/**
* Starts the HTTP API endpoint on the given port.
* @returns The HTTP server instance.
*/
async start(port: number): Promise<void> {
// promisify http.Server.listen() and await on it
await new Promise<void>((resolve) => {
this.#server.listen(port, () => {
resolve();
});
});
}

/**
* Stops the HTTP API endpoint.
*/
async close(): Promise<void> {
// promisify http.Server.close() and await on it
await new Promise<void>((resolve, reject) => {
this.#server.close((err?: Error) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});

this.server.closeAllConnections();
}
}
Loading

0 comments on commit 24199fa

Please sign in to comment.