diff --git a/web-ide/proxy/src/config.json b/web-ide/proxy/src/config.json index e7a0f19837..f1e82df41b 100644 --- a/web-ide/proxy/src/config.json +++ b/web-ide/proxy/src/config.json @@ -4,6 +4,7 @@ "port" : 3000, "managementPort" : 3001, "httpToHttpsPort" : 3002, + "hostname" : "localhost", "secureHeaders" :true }, "session" : { diff --git a/web-ide/proxy/src/docker.ts b/web-ide/proxy/src/docker.ts index 7b36c52e90..96e0ac33a2 100644 --- a/web-ide/proxy/src/docker.ts +++ b/web-ide/proxy/src/docker.ts @@ -77,6 +77,7 @@ export default class Docker { const createOptions = { HostConfig: config.docker.hostConfig } if (!this.onInternalNetwork && config.devMode) createOptions.HostConfig.PublishAllPorts=true + debug("sending run api command") const runner = this.api.run(imageId, ["code-server", "--no-auth", "--allow-http", "--disable-telemetry"], [], createOptions, (err :any, result :any) => { if (err) reject(err) }) @@ -86,7 +87,7 @@ export default class Docker { cResolve(container) }) }) - runner.on('container', (container :Container) => debug(`created container ${container.id}`)) + runner.on('container', (container :Container) => { debug(`created container ${container.id}`) }) runner.on('stream', (stream :Stream) => { let started = false stream.pipe(process.stdout) //TODO should we create context aware log messages per container (including the user session info)?? diff --git a/web-ide/proxy/src/proxy.ts b/web-ide/proxy/src/proxy.ts index 9f041276b7..3e24cdc04a 100644 --- a/web-ide/proxy/src/proxy.ts +++ b/web-ide/proxy/src/proxy.ts @@ -48,7 +48,8 @@ http.createServer(httpToHttpsApp).listen(conf.http.httpToHttpsPort, () => { if (!conf.http.port) throw new Error("MUST configure port for webide: 'conf.http.port'") const webideServer = createWebIdeServer() -if (conf.secureHeaders || conf.secureHeaders === undefined) { +if (conf.http.secureHeaders || conf.http.secureHeaders === undefined) { + console.log("INFO adding security headers") webIdeApp.use((req, res, next) => { addSecureHeaders(res, conf) next() @@ -143,5 +144,5 @@ function addSecureHeaders(res :Response, config :any) { res.setHeader("X-Frame-Options", "sameorigin") res.setHeader("X-XSS-Protection", "1; mode=block") res.setHeader("Referrer-Policy", "no-referrer-when-downgrade") - res.setHeader("Content-Security-Policy", `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;`) + res.setHeader("Content-Security-Policy", `default-src 'self'; connect-src 'self' ws://${config.http.hostname}:${config.http.port}/; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;`) } diff --git a/web-ide/proxy/src/routes/webide.ts b/web-ide/proxy/src/routes/webide.ts index db084ba4b5..ac24403f90 100644 --- a/web-ide/proxy/src/routes/webide.ts +++ b/web-ide/proxy/src/routes/webide.ts @@ -13,6 +13,7 @@ import { ImageInspectInfo } from "dockerode"; import request, { HttpArchiveRequest } from "request" import zlib from "zlib" import { Readable } from "stream" +import NodeCache from "node-cache" const conf = require('../config').read() const debug = require('debug')('webide:route') @@ -80,7 +81,7 @@ export default class WebIdeRoute { const route = this debug("requesting %s", req.url) Session.session(req, res, (err :any, state :any, sessionId :string, saveSession :any) => { - route.getImage() + route.getDockerImage() .then(image => route.ensureDockerContainer(req, state, saveSession, sessionId, image)) .then(containerInfo => { const url = route.docker.getContainerUrl(containerInfo, 'http') @@ -88,15 +89,11 @@ export default class WebIdeRoute { }) .catch(err => { console.error(`could not initiate connection to web-ide: ${err}`) - if (err instanceof ProxyError) res.statusCode = err.status - else res.statusCode = 500 - res.end() + route.sendErrorResponse(err, req, res) }) }) } catch (error) { - console.error(error) - res.statusCode = 500 - res.end() + this.sendErrorResponse(error, req, res) } } @@ -125,7 +122,7 @@ export default class WebIdeRoute { //doing nothing we let the proxy handle the response } - private getImage() :Promise { + private getDockerImage() :Promise { return this.docker.getImage(conf.docker.webIdeReference) } @@ -169,7 +166,9 @@ export default class WebIdeRoute { private ensureDockerContainer(req :Request, state :any, saveSession :Session.SaveSession, sessionId :string, image :ImageInspectInfo) { if (!state.docker) { - if (!state.initializing) { + //double check current state whether it is initializing or not + const currentState :any = Session.getStateSync(sessionId) || {} + if (!currentState.initializing) { state.initializing = true; saveSession(state); return this.docker.api.listContainers({all: false, filters: { ancestor: [`${conf.docker.webIdeReference}`] }}) @@ -177,7 +176,7 @@ export default class WebIdeRoute { if (containers.length >= conf.docker.maxInstances) { state.initializing = false; saveSession(state); - return Promise.reject(new ProxyError(`Breach max instances ${conf.docker.maxInstances}`, 503)) + return Promise.reject(new ProxyError(`Breach max instances ${conf.docker.maxInstances}`, 503, "There is unusually high server load. Please try again in a couple of minutes.")) } return this.docker.startContainer(image.Id).then(c => { console.log("INFO attaching container %s to session %s", c.Id, sessionId) @@ -188,16 +187,16 @@ export default class WebIdeRoute { }); }); } else { - //this occurs sporadically (perhaps when developer tools is open) sending another request - //TODO create better promise handling without timeout console.log("INFO request sent during initialization...waiting for docker to come up") return new Promise((resolve, reject) => { - Session.readSession(req, (err, state, sessionId) => { - const wait = setTimeout(() => { - clearTimeout(wait); - resolve(state.docker) - }, 10000) - }) + const interval = setInterval(() => { + Session.readSession(req, (err, state, sessionId) => { + if (state.docker && !state.initializing) { + clearInterval(interval); + resolve(state.docker); + } + }) + }, 1000) }) } } diff --git a/web-ide/proxy/src/session.ts b/web-ide/proxy/src/session.ts index 1e81389651..30d8b68b17 100644 --- a/web-ide/proxy/src/session.ts +++ b/web-ide/proxy/src/session.ts @@ -28,8 +28,12 @@ export function onTimeout(callback: Callback) { }); } -function remove(sessionId :string) { - store.del(sessionId) +/** + * explicit synchronous getter in case we need state current state + * @param sessionId + */ +export function getStateSync(sessionId :string) :any { + return store.get(sessionId) } /** @@ -122,7 +126,7 @@ function save(sessionId :string, state :any) :boolean { store.del(sessionId) }, delay) } - return store.set(sessionId, state) + return store.set(sessionId, state, 0) } function generateSessionId() {