Skip to content

Commit

Permalink
Merge pull request #37 from comake/version/0.7.0
Browse files Browse the repository at this point in the history
Version/0.7.0
  • Loading branch information
adlerfaulkner authored Jul 18, 2023
2 parents 4d5ad88 + 8f0843d commit fa862ba
Show file tree
Hide file tree
Showing 22 changed files with 738 additions and 260 deletions.
36 changes: 36 additions & 0 deletions config/server/server-factory/configurator/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@comake/solid-on-rails/^0.0.0/components/context.jsonld",
"@graph": [
{
"@id": "urn:solid-on-rails:default:ServerConfigurator",
"@type": "ParallelHandler",
"handlers": [
{
"comment": "Handles all request events from the server.",
"@id": "urn:solid-on-rails:default:HandlerServerConfigurator",
"@type": "HandlerServerConfigurator",
"handler": { "@id": "urn:solid-on-rails:default:HttpHandler" },
"showStackTrace": { "@id": "urn:solid-on-rails:default:variable:showStackTrace" }
},
{
"comment": "Handles all WebSocket connections to the server.",
"@id": "urn:solid-on-rails:default:WebSocketServerConfigurator",
"@type": "WebSocketServerConfigurator",
"handler": {
"@id": "urn:solid-on-rails:default:WebSocketHandler",
"@type": "WaterfallHandler",
"handlers": [
{
"comment": [
"This handler is required to prevent Components.js issues with arrays.",
"This might be fixed in the next Components.js release after which this can be removed."
],
"@type": "UnsupportedAsyncHandler"
}
]
}
}
]
}
]
}
10 changes: 6 additions & 4 deletions config/server/server-factory/default.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@comake/solid-on-rails/^0.0.0/components/context.jsonld",
"import": [
"files-sor:config/server/server-factory/configurator/default.json"
],
"@graph": [
{
"comment": "Creates a server that supports HTTP requests.",
"@id": "urn:solid-on-rails:default:ServerFactory",
"@type": "BaseHttpServerFactory",
"handler": { "@id": "urn:solid-on-rails:default:HttpHandler" },
"options_showStackTrace": { "@id": "urn:solid-on-rails:default:variable:showStackTrace" }
"@type": "BaseServerFactory",
"configurator": { "@id": "urn:solid-on-rails:default:ServerConfigurator" }
}
]
}
}
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,6 +1,6 @@
{
"name": "@comake/solid-on-rails",
"version": "0.6.0",
"version": "0.7.0",
"description": "Runs a Node.js server using componentsjs preset with configurations for working with Solid.",
"repository": {
"type": "git",
Expand Down
13 changes: 13 additions & 0 deletions src/http/handler/WebSocketHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { WebSocket } from 'ws';
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
import type { HttpRequest } from '../HttpRequest';

export interface WebSocketHandlerInput {
webSocket: WebSocket;
upgradeRequest: HttpRequest;
}

/**
* A handler to support requests trying to open a WebSocket connection.
*/
export abstract class WebSocketHandler extends AsyncHandler<WebSocketHandlerInput> {}
8 changes: 7 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Http/Handler
export * from './http/handler/HttpHandler';
export * from './http/handler/ParsingHttpHandler';
export * from './http/handler/WebSocketHandler';

// Http/Body
export * from './http/input/body/BodyParser';
Expand Down Expand Up @@ -107,8 +108,13 @@ export * from './logging/VoidLoggerFactory';
export * from './logging/WinstonLogger';
export * from './logging/WinstonLoggerFactory';

// Server/Configurator
export * from './server/configurator/HandlerServerConfigurator';
export * from './server/configurator/ServerConfigurator';
export * from './server/configurator/WebSocketServerConfigurator';

// Server/Factory
export * from './server/factory/BaseHttpServerFactory';
export * from './server/factory/BaseServerFactory';
export * from './server/factory/HttpServerFactory';

// Server/Middleware
Expand Down
25 changes: 22 additions & 3 deletions src/init/initialize/ServerInitializer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { Server } from 'http';
import { URL } from 'url';
import { promisify } from 'util';
import { getLoggerFor } from '../../logging/LogUtil';
import { isHttpsServer } from '../../server/factory/HttpServerFactory';
import type { HttpServerFactory } from '../../server/factory/HttpServerFactory';
import type { Finalizable } from '../finalize/Finalizable';
import { Initializer } from './Initializer';
Expand All @@ -8,19 +11,35 @@ import { Initializer } from './Initializer';
* Creates and starts an HTTP server.
*/
export class ServerInitializer extends Initializer implements Finalizable {
protected readonly logger = getLoggerFor(this);

private readonly serverFactory: HttpServerFactory;
private readonly port: number;
private readonly port?: number;
private readonly socketPath?: string;

private server?: Server;

public constructor(serverFactory: HttpServerFactory, port: number) {
public constructor(serverFactory: HttpServerFactory, port?: number, socketPath?: string) {
super();
this.serverFactory = serverFactory;
this.port = port;
this.socketPath = socketPath;
if (!port && !socketPath) {
throw new Error('Either Port or Socket arguments must be set');
}
}

public async handle(): Promise<void> {
this.server = this.serverFactory.startServer(this.port);
this.server = await this.serverFactory.createServer();

if (this.socketPath) {
this.logger.info(`Listening to server at ${this.server.address()}`);
this.server.listen(this.socketPath);
} else {
const url = new URL(`http${isHttpsServer(this.server) ? 's' : ''}://localhost:${this.port}/`).href;
this.logger.info(`Listening to server at ${url}`);
this.server.listen(this.port);
}
}

public async finalize(): Promise<void> {
Expand Down
68 changes: 68 additions & 0 deletions src/server/configurator/HandlerServerConfigurator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { Server, IncomingMessage, ServerResponse } from 'http';
import type { HttpHandler } from '../../http/handler/HttpHandler';
import { getLoggerFor } from '../../logging/LogUtil';
import { isError } from '../../util/errors/ErrorUtil';
import { guardStream } from '../../util/GuardedStream';
import { ServerConfigurator } from './ServerConfigurator';

/**
* A {@link ServerConfigurator} that attaches an {@link HttpHandler} to the `request` event of a {@link Server}.
* All incoming requests will be sent to the provided handler.
* Failsafes are added to make sure a valid response is sent in case something goes wrong.
*
* The `showStackTrace` parameter can be used to add stack traces to error outputs.
*/
export class HandlerServerConfigurator extends ServerConfigurator {
protected readonly logger = getLoggerFor(this);
protected readonly errorLogger = (error: Error): void => {
this.logger.error(`Request error: ${error.message}`);
};

/** The main HttpHandler */
private readonly handler: HttpHandler;
private readonly showStackTrace: boolean;

public constructor(handler: HttpHandler, showStackTrace = false) {
super();
this.handler = handler;
this.showStackTrace = showStackTrace;
}

public async handle(server: Server): Promise<void> {
server.on('request',
async(request: IncomingMessage, response: ServerResponse): Promise<void> => {
try {
this.logger.info(`Received ${request.method} request for ${request.url}`);
const guardedRequest = guardStream(request);
guardedRequest.on('error', this.errorLogger);
await this.handler.handleSafe({ request: guardedRequest, response });
} catch (error: unknown) {
const errMsg = this.createErrorMessage(error);
this.logger.error(errMsg);
if (response.headersSent) {
response.end();
} else {
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
response.writeHead(500).end(errMsg);
}
} finally {
if (!response.headersSent) {
response.writeHead(404).end();
}
}
});
}

/**
* Creates a readable error message based on the error and the `showStackTrace` parameter.
*/
private createErrorMessage(error: unknown): string {
if (!isError(error)) {
return `Unknown error: ${error}.\n`;
}
if (this.showStackTrace && error.stack) {
return `${error.stack}\n`;
}
return `${error.name}: ${error.message}\n`;
}
}
7 changes: 7 additions & 0 deletions src/server/configurator/ServerConfigurator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Server } from 'http';
import { AsyncHandler } from '../../util/handlers/AsyncHandler';

/**
* Configures a {@link Server} by attaching listeners for specific events.
*/
export abstract class ServerConfigurator extends AsyncHandler<Server> {}
40 changes: 40 additions & 0 deletions src/server/configurator/WebSocketServerConfigurator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { IncomingMessage, Server } from 'http';
import type { Socket } from 'net';
import type { WebSocket } from 'ws';
import { WebSocketServer } from 'ws';
import type { WebSocketHandler } from '../../http/handler/WebSocketHandler';
import { getLoggerFor } from '../../logging/LogUtil';
import { createErrorMessage } from '../../util/errors/ErrorUtil';
import { guardStream } from '../../util/GuardedStream';
import { ServerConfigurator } from './ServerConfigurator';

/**
* {@link ServerConfigurator} that adds WebSocket functionality to an existing {@link Server}.
*
* Listens for WebSocket requests and sends them to its handler.
*/
export class WebSocketServerConfigurator extends ServerConfigurator {
protected readonly logger = getLoggerFor(this);

private readonly handler: WebSocketHandler;

public constructor(handler: WebSocketHandler) {
super();
this.handler = handler;
}

public async handle(server: Server): Promise<void> {
const webSocketServer = new WebSocketServer({ noServer: true });
server.on('upgrade', (upgradeRequest: IncomingMessage, socket: Socket, head: Buffer): void => {
webSocketServer.handleUpgrade(upgradeRequest, socket, head, async(webSocket: WebSocket): Promise<void> => {
try {
await this.handler.handleSafe({ upgradeRequest: guardStream(upgradeRequest), webSocket });
} catch (error: unknown) {
this.logger.error(`Something went wrong handling a WebSocket connection: ${createErrorMessage(error)}`);
webSocket.send(`There was an error opening this WebSocket: ${createErrorMessage(error)}`);
webSocket.close();
}
});
});
}
}
108 changes: 0 additions & 108 deletions src/server/factory/BaseHttpServerFactory.ts

This file was deleted.

Loading

0 comments on commit fa862ba

Please sign in to comment.