Skip to content

Commit

Permalink
Add websocket server launcher (#41)
Browse files Browse the repository at this point in the history
Requires eclipse-glsp/glsp-client#215
Fixes eclipse-glsp/glsp#945

Co-authored-by: Martin Fleck <[email protected]>
  • Loading branch information
tortmayr and martin-fleck-at authored Mar 6, 2023
1 parent 385269d commit 5c40594
Show file tree
Hide file tree
Showing 14 changed files with 1,089 additions and 1,267 deletions.
30 changes: 24 additions & 6 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,30 @@
"${workspaceRoot}/packages/**/*/lib/**/*.js"
],
"sourceMapPathOverrides": {
"webpack:///./~/*": "${workspaceFolder}/node_modules/*",
"webpack:////*": "/*",
"webpack://?:*/*": "${workspaceFolder}/*",
"webpack:///([a-z]):/(.+)": "$1:/$2",
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1",
"meteor://💻app/*": "${workspaceFolder}/*"
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1"
},
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Debug Workflow Example Server (Websocket)",
"program": "${workspaceFolder}/examples/workflow-server/bundle/wf-glsp-server-node.js",
"args": ["--webSocket", "--port", "8081"],
"env": {
"NODE_ENV": "development"
},
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/examples/workflow-server/bundle/**/*.js",
"${workspaceRoot}/node_modules/@eclipse-glsp/*/lib/**/*.js",
"${workspaceRoot}/examples/workflow-server/*/lib/**/*.js",
"${workspaceRoot}/packages/**/*/lib/**/*.js"
],
"sourceMapPathOverrides": {
"webpack://@eclipse-glsp-examples/workflow-server/(.+)": "${workspaceFolder}/examples/workflow-server/$1"
},
"smartStep": true,
"internalConsoleOptions": "openOnSessionStart",
Expand Down
61 changes: 24 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,6 @@ The server consists of three components:
The main target environment is node, nevertheless, all components are implemented in an ismorphic fashion and also provide
an entrypoint to target browser environments (e.g. running the server in a web worker)

## Integration & Features

Currently the Typescript-based API does not yet support the full feature set of the Java GLSP Server API.
Below is a list of features that are currently supported.

| Integration | Node Server | Java Server |
| ------------------------------------------------- | :---------: | :---------: |
| JSON-RPC over Socket (Theia, VSCode) |||
| JSON-RPC over Websocket (Standalone, Eclipse IDE) | ||

| Feature | Node Server | Java Server |
| ----------------------------------------------------------------- | :-------------: | :-------------: |
| Model Saving |||
| Model Dirty State |||
| Model SVG Export |||
| Model Layout |||
| Model Edit Modes<br>- Edit<br>- Read-only | <br>✓<br>&nbsp; | <br>✓<br>✓ |
| Client View Port<br>- Center<br>- Fit to Screen | <br>✓<br>✓ | <br>✓<br>✓ |
| Client Status Notification |||
| Client Message Notification |||
| Element Selection |||
| Element Hover |||
| Element Validation |||
| Element Navigation |||
| Element Type Hints |||
| Element Creation and Deletion |||
| Node Change Bounds<br>- Move<br>- Resize | <br>✓<br>✓ | <br>✓<br>✓ |
| Node Change Container |||
| Edge Reconnect |||
| Edge Routing Points |||
| Element Text Editing |||
| Clipboard (Cut, Copy, Paste) |||
| Undo / Redo |||
| Contexts<br>- Context Menu<br>- Command Palette<br>- Tool Palette | <br><br>✓<br>✓ | <br>✓<br>✓<br>✓ |

## Build

Install dependencies and build via
Expand Down Expand Up @@ -88,17 +53,39 @@ Simply select a test file (`*.spec.ts`), then go to the `Run & Debug` View (`Ctr

## Start & Debug

The example server can be launched with:
### Socket

To launch the server for TCP sockets use:

```console
yarn start
```

This starts a server that is listening on port 5007 for incoming client requests.

To debug you can use the `Debug workflow example GLSP Server` launch configuration.
This starts the example server in a dedicated process. To test the server you have to connect a workflow GLSP client that supports JSON-RPC via socket.
To test the server you have to connect a workflow GLSP client that supports JSON-RPC via socket.
We recommend to use the client provided by the [`glsp-integration`](https://github.com/eclipse-glsp/glsp-theia-integration#how-to-start-the-workflow-diagram-example-server-from-the-sources).

### Websocket

To launch the server for WebSockets use:

```console
yarn start:websocket
```

This starts a server that is listening on the `ws://localhost:8081/workflow` endpoint for incoming client requests.

To debug you can use the `Debug workflow example GLSP Server (Websocket)` launch configuration.
To test the server you have to connect a workflow GLSP client that supports JSON-RPC via WebSocket.
We recommend to use the standalone example provided by [`glsp-client`](https://github.com/eclipse-glsp/glsp-client/blob/master/README.md#how-to-start-the-workflow-diagram-example).

## More information

For more information, please visit the [Eclipse GLSP Umbrella repository](https://github.com/eclipse-glsp/glsp) and the [Eclipse GLSP Website](https://www.eclipse.org/glsp/).
If you have questions, please raise them in the [discussions](https://github.com/eclipse-glsp/glsp/discussions) and have a look at our [communication and support options](https://www.eclipse.org/glsp/contact/).

```
```
1 change: 1 addition & 0 deletions examples/workflow-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"lint:ci": "yarn lint -o eslint.xml -f checkstyle",
"prepare": "yarn clean && yarn build",
"start": "node --enable-source-maps bundle/wf-glsp-server-node.js",
"start:websocket": "node --enable-source-maps bundle/wf-glsp-server-node.js -w --port 8081",
"watch": "tsc -w"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/workflow-server/src/browser/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function launch(argv?: string[]): Promise<void> {

launcher.configure(serverModule);

await launcher.start();
await launcher.start({});
} catch (error) {
(logger ?? console).error('Error in workflow server launcher:', error);
}
Expand Down
21 changes: 14 additions & 7 deletions examples/workflow-server/src/node/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,41 @@
import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk';
import {
createAppModule,
createSocketCliParser,
GModelStorage,
Logger,
LoggerFactory,
SocketServerLauncher
SocketServerLauncher,
WebSocketServerLauncher
} from '@eclipse-glsp/server/node';
import { Container } from 'inversify';
import { WorkflowLayoutConfigurator } from '../common/layout/workflow-layout-configurator';
import { WorkflowDiagramModule, WorkflowServerModule } from '../common/workflow-diagram-module';
import { createWorkflowCliParser } from './workflow-cli-parser';

export async function launch(argv?: string[]): Promise<void> {
let logger: Logger | undefined;
try {
const options = createSocketCliParser().parse(argv);
const options = createWorkflowCliParser().parse(argv);
const appContainer = new Container();
appContainer.load(createAppModule(options));

logger = appContainer.get<LoggerFactory>(LoggerFactory)('WorkflowServerApp');
const launcher = appContainer.resolve(SocketServerLauncher);

const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator });
const serverModule = new WorkflowServerModule().configureDiagramModule(
new WorkflowDiagramModule(() => GModelStorage),
elkLayoutModule
);

launcher.configure(serverModule);

launcher.start({ port: options.port, host: options.host });
if (options.webSocket) {
const launcher = appContainer.resolve(WebSocketServerLauncher);
launcher.configure(serverModule);
launcher.start({ port: options.port, host: options.host, path: 'workflow' });
} else {
const launcher = appContainer.resolve(SocketServerLauncher);
launcher.configure(serverModule);
launcher.start({ port: options.port, host: options.host });
}
} catch (error) {
(logger ?? console).error('Error in workflow server launcher:', error);
}
Expand Down
29 changes: 29 additions & 0 deletions examples/workflow-server/src/node/workflow-cli-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/********************************************************************************
* Copyright (c) 2023 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { CliParser, createSocketCliParser, defaultSocketLaunchOptions, SocketLaunchOptions } from '@eclipse-glsp/server/node';

export interface WorkflowLaunchOptions extends SocketLaunchOptions {
webSocket: boolean;
}

export function createWorkflowCliParser<O extends WorkflowLaunchOptions = WorkflowLaunchOptions>(
defaultOptions: WorkflowLaunchOptions = { webSocket: false, ...defaultSocketLaunchOptions }
): CliParser<O> {
const parser = createSocketCliParser<O>(defaultOptions);
parser.command.option('-w , --webSocket', 'Flag to use websocket launcher instead of default launcher', false);
return parser;
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
"publish:next": "SHA=$(git rev-parse --short HEAD) && lerna publish preminor --exact --canary --preid next.${SHA} --dist-tag next --no-git-reset --no-git-tag-version --no-push --ignore-scripts --yes --no-verify-access",
"publish:prepare": "lerna version --ignore-scripts --yes --no-push",
"start": "yarn --cwd examples/workflow-server start",
"start:websocket": "yarn --cwd examples/workflow-server start:websocket",
"test": "lerna run test",
"test:ci": "lerna run test:ci",
"test:coverage": "lerna run test:coverage",
"test:coverage:ci": "yarn glsp coverageReport .",
"upgrade:next": "yarn upgrade -p \"@eclipse-glsp.*|sprotty.*\" --next ",
"upgrade:next": "yarn upgrade @eclipse-glsp/protocol@next",
"watch": "lerna run --parallel watch"
},
"devDependencies": {
"@eclipse-glsp/dev": "1.1.0-next.164cf99.124",
"@eclipse-glsp/dev": "1.1.0-next.7026c40.129",
"@types/node": "16.x"
},
"engines": {
Expand Down
6 changes: 4 additions & 2 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@
"commander": "^8.3.0",
"fast-json-patch": "^3.1.0",
"inversify": "^5.1.1",
"winston": "^3.3.3"
"winston": "^3.3.3",
"ws": "^8.12.1"
},
"devDependencies": {
"@types/fs-extra": "^9.0.13"
"@types/fs-extra": "^9.0.13",
"@types/ws": "^8.5.4"
},
"publishConfig": {
"access": "public"
Expand Down
28 changes: 16 additions & 12 deletions packages/server/src/browser/launch/worker-server-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,30 @@

import { Container, injectable } from 'inversify';
import * as jsonrpc from 'vscode-jsonrpc/browser';
import { GLSPServer, GLSPServerLauncher, JsonRpcGLSPServer, MaybePromise, START_UP_COMPLETE_MSG } from '../../common/index';
import { GLSPServer, GLSPServerLauncher, JsonRpcGLSPServer, MaybePromise } from '../../common/index';
export interface WorkerLaunchOptions {
context?: Worker;
}

export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed.';

@injectable()
export class WorkerServerLauncher extends GLSPServerLauncher {
private connection?: jsonrpc.MessageConnection;
export class WorkerServerLauncher extends GLSPServerLauncher<WorkerLaunchOptions> {
protected connection?: jsonrpc.MessageConnection;

protected run(): MaybePromise<void> {
protected run(options: WorkerLaunchOptions): MaybePromise<void> {
if (this.connection) {
throw new Error('Error during launch. Server already has an active client connection');
}
const container = this.createContainer();
this.connection = this.createConnection();
this.connection = this.createConnection(options);
const glspServer = container.get<JsonRpcGLSPServer>(JsonRpcGLSPServer);
glspServer.connect(this.connection);
this.connection.onDispose(() => this.disposeClientConnection(container, glspServer));

this.logger.info('GLSP server worker connection established');
this.connection.listen();
this.toDispose.push(this.connection);
postMessage(START_UP_COMPLETE_MSG);
return new Promise((resolve, rejects) => {
this.connection?.onClose(() => resolve(undefined));
Expand All @@ -47,12 +53,10 @@ export class WorkerServerLauncher extends GLSPServerLauncher {
this.connection = undefined;
}

protected stop(): MaybePromise<void> {
this.logger.info('Shutdown WorkerServerLauncher');
this.connection?.dispose();
}

protected createConnection(): jsonrpc.MessageConnection {
return jsonrpc.createMessageConnection(new jsonrpc.BrowserMessageReader(self), new jsonrpc.BrowserMessageWriter(self));
protected createConnection(options: WorkerLaunchOptions): jsonrpc.MessageConnection {
return jsonrpc.createMessageConnection(
new jsonrpc.BrowserMessageReader(options.context ?? self),
new jsonrpc.BrowserMessageWriter(options.context ?? self)
);
}
}
20 changes: 13 additions & 7 deletions packages/server/src/common/launch/glsp-server-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,19 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { MaybePromise } from '@eclipse-glsp/protocol';
import { Disposable, DisposableCollection, MaybePromise } from '@eclipse-glsp/protocol';
import { Container, ContainerModule, inject, injectable, optional } from 'inversify';
import { ServerModule } from '../di/server-module';
import { InjectionContainer } from '../di/service-identifiers';
import { Logger } from '../utils/logger';

export const START_UP_COMPLETE_MSG = '[GLSP-Server]:Startup completed';

@injectable()
export abstract class GLSPServerLauncher<T = undefined> {
export abstract class GLSPServerLauncher<T> implements Disposable {
@inject(Logger) protected logger: Logger;

protected _modules: ContainerModule[] = [];
protected running: boolean;
protected toDispose = new DisposableCollection();

@inject(InjectionContainer) @optional() protected parentContainer?: Container;

Expand All @@ -40,26 +39,33 @@ export abstract class GLSPServerLauncher<T = undefined> {
return container;
}

start(startParams?: T): MaybePromise<void> {
start(startParams: T): MaybePromise<void> {
if (!this.running) {
this.running = true;
return this.run(startParams);
}
this.logger.warn('Could not start launcher. Launcher is already running!');
}

protected abstract run(startParams?: T): MaybePromise<void>;
protected abstract run(startParams: T): MaybePromise<void>;

shutdown(): MaybePromise<void> {
if (this.running) {
this.logger.info('Shutdown GLSPServerLauncher');
const result = this.stop();
this.running = false;
return result;
}
this.logger.warn('Could not stop launcher. Launcher is not running!');
}

protected abstract stop(): MaybePromise<void>;
protected stop(): MaybePromise<void> {
this.dispose();
}

dispose(): void {
this.toDispose.dispose();
}

get modules(): ContainerModule[] {
if (!this._modules) {
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from './gmodel/gmodel-storage';
export * from './launch/cli-parser';
export * from './launch/socket-cli-parser';
export * from './launch/socket-server-launcher';
export * from './launch/websocket-server-launcher';
Loading

0 comments on commit 5c40594

Please sign in to comment.