Skip to content

Commit

Permalink
core: add BackendApplicationServer
Browse files Browse the repository at this point in the history
The BackendApplicationServer is an optional backend contribution that
serves frontend files. When not bound, the generators may bind one.

This component is useful when your application is packaged in such a way
that you need to customize how the frontend is served.
  • Loading branch information
paul-marechal committed Jun 7, 2021
1 parent 9b9cc2b commit 2880525
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 40 deletions.
50 changes: 26 additions & 24 deletions dev-packages/application-manager/src/generator/backend-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ export class BackendGenerator extends AbstractGenerator {

protected compileServer(backendModules: Map<string, string>): string {
return `// @ts-check
require('reflect-metadata');
require('reflect-metadata');${this.ifElectron(`
// Patch electron version if missing, see https://github.com/eclipse-theia/theia/pull/7361#pullrequestreview-377065146
if (typeof process.versions.electron === 'undefined' && typeof process.env.THEIA_ELECTRON_VERSION === 'string') {
process.versions.electron = process.env.THEIA_ELECTRON_VERSION;
}
}`)}
const path = require('path');
const express = require('express');
const { Container } = require('inversify');
const { BackendApplication, CliManager } = require('@theia/core/lib/node');
const { BackendApplication, BackendApplicationServer, CliManager } = require('@theia/core/lib/node');
const { backendApplicationModule } = require('@theia/core/lib/node/backend-application-module');
const { messagingBackendModule } = require('@theia/core/lib/node/messaging/messaging-backend-module');
const { loggerBackendModule } = require('@theia/core/lib/node/logger-backend-module');
Expand All @@ -46,49 +46,51 @@ container.load(backendApplicationModule);
container.load(messagingBackendModule);
container.load(loggerBackendModule);
function defaultServeStatic(app) {
app.use(express.static(path.resolve(__dirname, '../../lib')))
}
function load(raw) {
return Promise.resolve(raw.default).then(module =>
container.load(module)
)
return Promise.resolve(raw.default).then(
module => container.load(module)
);
}
function start(port, host, argv) {
if (argv === undefined) {
argv = process.argv;
function start(port, host, argv = process.argv) {
if (!container.isBound(BackendApplicationServer)) {
container.bind(BackendApplicationServer).toConstantValue({ configure: defaultServeStatic });
}
const cliManager = container.get(CliManager);
return cliManager.initializeCli(argv).then(function () {
const application = container.get(BackendApplication);
application.use(express.static(path.join(__dirname, '../../lib')));
application.use(express.static(path.join(__dirname, '../../lib/index.html')));
return application.start(port, host);
return container.get(CliManager).initializeCli(argv).then(() => {
return container.get(BackendApplication).start(port, host);
});
}
module.exports = (port, host, argv) => Promise.resolve()${this.compileBackendModuleImports(backendModules)}
.then(() => start(port, host, argv)).catch(reason => {
console.error('Failed to start the backend application.');
if (reason) {
console.error(reason);
}
throw reason;
});`;
.then(() => start(port, host, argv)).catch(error => {
console.error('Failed to start the backend application:');
console.error(error);
process.exitCode = 1;
throw error;
});
`;
}

protected compileMain(backendModules: Map<string, string>): string {
return `// @ts-check
const { BackendApplicationConfigProvider } = require('@theia/core/lib/node/backend-application-config-provider');
const main = require('@theia/core/lib/node/main');
BackendApplicationConfigProvider.set(${this.prettyStringify(this.pck.props.backend.config)});
const serverModule = require('./server');
const serverAddress = main.start(serverModule());
serverAddress.then(function ({ port, address }) {
serverAddress.then(({ port, address }) => {
if (process && process.send) {
process.send({ port, address });
}
});
module.exports = serverAddress;
`;
}
Expand Down
3 changes: 2 additions & 1 deletion examples/api-samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
},
"theiaExtensions": [
{
"frontend": "lib/browser/api-samples-frontend-module"
"frontend": "lib/browser/api-samples-frontend-module",
"backend": "lib/node/api-samples-backend-module"
},
{
"frontend": "lib/browser/menu/sample-browser-menu-module",
Expand Down
25 changes: 25 additions & 0 deletions examples/api-samples/src/node/api-samples-backend-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/********************************************************************************
* Copyright (C) 2021 Ericsson 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 { ContainerModule } from '@theia/core/shared/inversify';
import { BackendApplicationServer } from '@theia/core/lib/node';
import { SampleBackendApplicationServer } from './sample-backend-application-server';

export default new ContainerModule(bind => {
if (process.env.SAMPLE_BACKEND_APPLICATION_SERVER) {
bind(BackendApplicationServer).to(SampleBackendApplicationServer).inSingletonScope();
}
});
29 changes: 29 additions & 0 deletions examples/api-samples/src/node/sample-backend-application-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/********************************************************************************
* Copyright (C) 2021 Ericsson 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 { injectable } from '@theia/core/shared/inversify';
import { BackendApplicationServer } from '@theia/core/lib/node';
import express = require('@theia/core/shared/express');

@injectable()
export class SampleBackendApplicationServer implements BackendApplicationServer {

configure(app: express.Application): void {
app.get('*', (req, res) => {
res.status(200).send('SampleBackendApplicationServer OK');
});
}
}
12 changes: 11 additions & 1 deletion packages/core/src/node/backend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
bindContributionProvider, MessageService, MessageClient, ConnectionHandler, JsonRpcConnectionHandler,
CommandService, commandServicePath, messageServicePath
} from '../common';
import { BackendApplication, BackendApplicationContribution, BackendApplicationCliContribution } from './backend-application';
import { BackendApplication, BackendApplicationContribution, BackendApplicationCliContribution, BackendApplicationServer } from './backend-application';
import { CliManager, CliContribution } from './cli';
import { IPCConnectionProvider } from './messaging';
import { ApplicationServerImpl } from './application-server';
Expand Down Expand Up @@ -59,6 +59,16 @@ export const backendApplicationModule = new ContainerModule(bind => {

bind(BackendApplication).toSelf().inSingletonScope();
bindContributionProvider(bind, BackendApplicationContribution);
// Bind the BackendApplicationServer as a BackendApplicationContribution
// and fallback to an empty contribution if never bound.
bind(BackendApplicationContribution).toDynamicValue(ctx => {
if (ctx.container.isBound(BackendApplicationServer)) {
return ctx.container.get(BackendApplicationServer);
} else {
console.warn('no BackendApplicationServer is set, frontend might not be available');
return {};
}
}).inSingletonScope();

bind(IPCConnectionProvider).toSelf().inSingletonScope();

Expand Down
35 changes: 21 additions & 14 deletions packages/core/src/node/backend-application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,23 @@ import { environment } from '../common/index';
import { AddressInfo } from 'net';
import { ApplicationPackage } from '@theia/application-package';

export const BackendApplicationContribution = Symbol('BackendApplicationContribution');
const APP_PROJECT_PATH = 'app-project-path';

const TIMER_WARNING_THRESHOLD = 50;

const DEFAULT_PORT = environment.electron.is() ? 0 : 3000;
const DEFAULT_HOST = 'localhost';
const DEFAULT_SSL = false;

export const BackendApplicationServer = Symbol('BackendApplicationServer');
/**
* This service is responsible for serving the frontend files.
*
* When not bound, `@theia/cli` generators will bind it on the fly to serve files according to its own layout.
*/
export interface BackendApplicationServer extends BackendApplicationContribution { }

export const BackendApplicationContribution = Symbol('BackendApplicationContribution');
/**
* Contribution for hooking into the backend lifecycle.
*/
Expand Down Expand Up @@ -82,14 +97,6 @@ export interface BackendApplicationContribution {
onStop?(app?: express.Application): void;
}

const defaultPort = environment.electron.is() ? 0 : 3000;
const defaultHost = 'localhost';
const defaultSSL = false;

const appProjectPath = 'app-project-path';

const TIMER_WARNING_THRESHOLD = 50;

@injectable()
export class BackendApplicationCliContribution implements CliContribution {

Expand All @@ -101,12 +108,12 @@ export class BackendApplicationCliContribution implements CliContribution {
projectPath: string;

configure(conf: yargs.Argv): void {
conf.option('port', { alias: 'p', description: 'The port the backend server listens on.', type: 'number', default: defaultPort });
conf.option('hostname', { alias: 'h', description: 'The allowed hostname for connections.', type: 'string', default: defaultHost });
conf.option('ssl', { description: 'Use SSL (HTTPS), cert and certkey must also be set', type: 'boolean', default: defaultSSL });
conf.option('port', { alias: 'p', description: 'The port the backend server listens on.', type: 'number', default: DEFAULT_PORT });
conf.option('hostname', { alias: 'h', description: 'The allowed hostname for connections.', type: 'string', default: DEFAULT_HOST });
conf.option('ssl', { description: 'Use SSL (HTTPS), cert and certkey must also be set', type: 'boolean', default: DEFAULT_SSL });
conf.option('cert', { description: 'Path to SSL certificate.', type: 'string' });
conf.option('certkey', { description: 'Path to SSL certificate key.', type: 'string' });
conf.option(appProjectPath, { description: 'Sets the application project directory', default: this.appProjectPath() });
conf.option(APP_PROJECT_PATH, { description: 'Sets the application project directory', default: this.appProjectPath() });
}

setArguments(args: yargs.Arguments): void {
Expand All @@ -115,7 +122,7 @@ export class BackendApplicationCliContribution implements CliContribution {
this.ssl = args.ssl as boolean;
this.cert = args.cert as string;
this.certkey = args.certkey as string;
this.projectPath = args[appProjectPath] as string;
this.projectPath = args[APP_PROJECT_PATH] as string;
}

protected appProjectPath(): string {
Expand Down

0 comments on commit 2880525

Please sign in to comment.