Skip to content

Add flag for toggling permessage-deflate #3286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import * as path from "path"
import { Args as VsArgs } from "../../typings/ipc"
import { canConnect, generateCertificate, generatePassword, humanPath, paths } from "./util"

export enum Feature {
/** Web socket compression. */
PermessageDeflate = "permessage-deflate",
}

export enum AuthType {
Password = "password",
None = "none",
Expand Down Expand Up @@ -35,6 +40,7 @@ export interface Args extends VsArgs {
"cert-key"?: string
"disable-telemetry"?: boolean
"disable-update-check"?: boolean
enable?: string[]
help?: boolean
host?: string
json?: boolean
Expand Down Expand Up @@ -128,6 +134,9 @@ const options: Options<Required<Args>> = {
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
"then notifies you once every week that a new release is available.",
},
// --enable can be used to enable experimental features. These features
// provide no guarantees.
enable: { type: "string[]" },
help: { type: "boolean", short: "h", description: "Show this output." },
json: { type: "boolean" },
open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." },
Expand Down
1 change: 1 addition & 0 deletions src/node/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export const version = pkg.version || "development"
export const commit = pkg.commit || "development"
export const rootPath = path.resolve(__dirname, "../..")
export const tmpdir = path.join(os.tmpdir(), "code-server")
export const isDevMode = commit === "development"
153 changes: 4 additions & 149 deletions src/node/entry.ts
Original file line number Diff line number Diff line change
@@ -1,163 +1,17 @@
import { field, logger } from "@coder/logger"
import * as cp from "child_process"
import http from "http"
import * as path from "path"
import { CliMessage, OpenCommandPipeArgs } from "../../typings/ipc"
import { plural } from "../common/util"
import { createApp, ensureAddress } from "./app"
import { logger } from "@coder/logger"
import {
AuthType,
DefaultedArgs,
optionDescriptions,
parse,
readConfigFile,
setDefaults,
shouldOpenInExistingInstance,
shouldRunVsCodeCli,
} from "./cli"
import { coderCloudBind } from "./coder_cloud"
import { commit, version } from "./constants"
import { openInExistingInstance, runCodeServer, runVsCodeCli } from "./main"
import * as proxyAgent from "./proxy_agent"
import { register } from "./routes"
import { humanPath, isFile, open } from "./util"
import { isChild, wrapper } from "./wrapper"

export const runVsCodeCli = (args: DefaultedArgs): void => {
logger.debug("forking vs code cli...")
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
env: {
...process.env,
CODE_SERVER_PARENT_PID: process.pid.toString(),
},
})
vscode.once("message", (message: any) => {
logger.debug("got message from VS Code", field("message", message))
if (message.type !== "ready") {
logger.error("Unexpected response waiting for ready response", field("type", message.type))
process.exit(1)
}
const send: CliMessage = { type: "cli", args }
vscode.send(send)
})
vscode.once("error", (error) => {
logger.error("Got error from VS Code", field("error", error))
process.exit(1)
})
vscode.on("exit", (code) => process.exit(code || 0))
}

export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
type: "open",
folderURIs: [],
fileURIs: [],
forceReuseWindow: args["reuse-window"],
forceNewWindow: args["new-window"],
}

for (let i = 0; i < args._.length; i++) {
const fp = path.resolve(args._[i])
if (await isFile(fp)) {
pipeArgs.fileURIs.push(fp)
} else {
pipeArgs.folderURIs.push(fp)
}
}

if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
logger.error("--new-window can only be used with folder paths")
process.exit(1)
}

if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
logger.error("Please specify at least one file or folder")
process.exit(1)
}

const vscode = http.request(
{
path: "/",
method: "POST",
socketPath,
},
(response) => {
response.on("data", (message) => {
logger.debug("got message from VS Code", field("message", message.toString()))
})
},
)
vscode.on("error", (error: unknown) => {
logger.error("got error from VS Code", field("error", error))
})
vscode.write(JSON.stringify(pipeArgs))
vscode.end()
}

const main = async (args: DefaultedArgs): Promise<void> => {
logger.info(`code-server ${version} ${commit}`)

logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)

if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
throw new Error(
"Please pass in a password via the config file or environment variable ($PASSWORD or $HASHED_PASSWORD)",
)
}

const [app, wsApp, server] = await createApp(args)
const serverAddress = ensureAddress(server)
await register(app, wsApp, server, args)

logger.info(`Using config file ${humanPath(args.config)}`)
logger.info(`HTTP server listening on ${serverAddress} ${args.link ? "(randomized by --link)" : ""}`)

if (args.auth === AuthType.Password) {
logger.info(" - Authentication is enabled")
if (args.usingEnvPassword) {
logger.info(" - Using password from $PASSWORD")
} else if (args.usingEnvHashedPassword) {
logger.info(" - Using password from $HASHED_PASSWORD")
} else {
logger.info(` - Using password from ${humanPath(args.config)}`)
}
} else {
logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
}

if (args.cert) {
logger.info(` - Using certificate for HTTPS: ${humanPath(args.cert.value)}`)
} else {
logger.info(` - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`)
}

if (args["proxy-domain"].length > 0) {
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
args["proxy-domain"].forEach((domain) => logger.info(` - *.${domain}`))
}

if (args.link) {
try {
await coderCloudBind(serverAddress.replace(/^https?:\/\//, ""), args.link.value)
logger.info(" - Connected to cloud agent")
} catch (err) {
logger.error(err.message)
wrapper.exit(1)
}
}

if (!args.socket && args.open) {
// The web socket doesn't seem to work if browsing with 0.0.0.0.
const openAddress = serverAddress.replace("://0.0.0.0", "://localhost")
try {
await open(openAddress)
logger.info(`Opened ${openAddress}`)
} catch (error) {
logger.error("Failed to open", field("address", openAddress), field("error", error))
}
}
}

async function entry(): Promise<void> {
proxyAgent.monkeyPatch(false)

Expand All @@ -170,7 +24,8 @@ async function entry(): Promise<void> {
if (isChild(wrapper)) {
const args = await wrapper.handshake()
wrapper.preventExit()
return main(args)
await runCodeServer(args)
return
}

const cliArgs = parse(process.argv.slice(2))
Expand Down
159 changes: 159 additions & 0 deletions src/node/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { field, logger } from "@coder/logger"
import * as cp from "child_process"
import http from "http"
import * as path from "path"
import { CliMessage, OpenCommandPipeArgs } from "../../typings/ipc"
import { plural } from "../common/util"
import { createApp, ensureAddress } from "./app"
import { AuthType, DefaultedArgs, Feature } from "./cli"
import { coderCloudBind } from "./coder_cloud"
import { commit, version } from "./constants"
import { register } from "./routes"
import { humanPath, isFile, open } from "./util"

export const runVsCodeCli = (args: DefaultedArgs): void => {
logger.debug("forking vs code cli...")
const vscode = cp.fork(path.resolve(__dirname, "../../lib/vscode/out/vs/server/fork"), [], {
env: {
...process.env,
CODE_SERVER_PARENT_PID: process.pid.toString(),
},
})
vscode.once("message", (message: any) => {
logger.debug("got message from VS Code", field("message", message))
if (message.type !== "ready") {
logger.error("Unexpected response waiting for ready response", field("type", message.type))
process.exit(1)
}
const send: CliMessage = { type: "cli", args }
vscode.send(send)
})
vscode.once("error", (error) => {
logger.error("Got error from VS Code", field("error", error))
process.exit(1)
})
vscode.on("exit", (code) => process.exit(code || 0))
}

export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
type: "open",
folderURIs: [],
fileURIs: [],
forceReuseWindow: args["reuse-window"],
forceNewWindow: args["new-window"],
}

for (let i = 0; i < args._.length; i++) {
const fp = path.resolve(args._[i])
if (await isFile(fp)) {
pipeArgs.fileURIs.push(fp)
} else {
pipeArgs.folderURIs.push(fp)
}
}

if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
logger.error("--new-window can only be used with folder paths")
process.exit(1)
}

if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
logger.error("Please specify at least one file or folder")
process.exit(1)
}

const vscode = http.request(
{
path: "/",
method: "POST",
socketPath,
},
(response) => {
response.on("data", (message) => {
logger.debug("got message from VS Code", field("message", message.toString()))
})
},
)
vscode.on("error", (error: unknown) => {
logger.error("got error from VS Code", field("error", error))
})
vscode.write(JSON.stringify(pipeArgs))
vscode.end()
}

export const runCodeServer = async (args: DefaultedArgs): Promise<http.Server> => {
logger.info(`code-server ${version} ${commit}`)

logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)

if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
throw new Error(
"Please pass in a password via the config file or environment variable ($PASSWORD or $HASHED_PASSWORD)",
)
}

const [app, wsApp, server] = await createApp(args)
const serverAddress = ensureAddress(server)
await register(app, wsApp, server, args)

logger.info(`Using config file ${humanPath(args.config)}`)
logger.info(`HTTP server listening on ${serverAddress} ${args.link ? "(randomized by --link)" : ""}`)
if (args.auth === AuthType.Password) {
logger.info(" - Authentication is enabled")
if (args.usingEnvPassword) {
logger.info(" - Using password from $PASSWORD")
} else if (args.usingEnvHashedPassword) {
logger.info(" - Using password from $HASHED_PASSWORD")
} else {
logger.info(` - Using password from ${humanPath(args.config)}`)
}
} else {
logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
}

if (args.cert) {
logger.info(` - Using certificate for HTTPS: ${humanPath(args.cert.value)}`)
} else {
logger.info(` - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`)
}

if (args["proxy-domain"].length > 0) {
logger.info(` - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
args["proxy-domain"].forEach((domain) => logger.info(` - *.${domain}`))
}

if (args.link) {
await coderCloudBind(serverAddress.replace(/^https?:\/\//, ""), args.link.value)
logger.info(" - Connected to cloud agent")
}

if (args.enable && args.enable.length > 0) {
logger.info("Enabling the following experimental features:")
args.enable.forEach((feature) => {
if (Object.values(Feature).includes(feature as Feature)) {
logger.info(` - "${feature}"`)
} else {
logger.error(` X "${feature}" (unknown feature)`)
}
})
// TODO: Could be nice to add wrapping to the logger?
logger.info(
" The code-server project does not provide stability guarantees or commit to fixing bugs relating to these experimental features. When filing bug reports, please ensure that you can reproduce the bug with all experimental features turned off.",
)
}

if (!args.socket && args.open) {
// The web socket doesn't seem to work if browsing with 0.0.0.0.
const openAddress = serverAddress.replace("://0.0.0.0", "://localhost")
try {
await open(openAddress)
logger.info(`Opened ${openAddress}`)
} catch (error) {
logger.error("Failed to open", field("address", openAddress), field("error", error))
}
}

return server
}
Loading