mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-13 11:46:08 -05:00
initial support for compressing responses
This commit is contained in:
9
src/server/node.d.ts
vendored
9
src/server/node.d.ts
vendored
@@ -397,6 +397,15 @@ declare namespace NodeJS {
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace NodeJS {
|
||||
namespace zlib {
|
||||
export interface GZip {
|
||||
gzipSync(buf: Buffer): Buffer;
|
||||
}
|
||||
export function createGZip(): GZip;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace NodeJS {
|
||||
namespace fs {
|
||||
interface Stats {
|
||||
|
||||
2
src/server/protocol.d.ts
vendored
2
src/server/protocol.d.ts
vendored
@@ -30,6 +30,8 @@ declare namespace ts.server.protocol {
|
||||
* Object containing arguments for the command
|
||||
*/
|
||||
arguments?: any;
|
||||
|
||||
canCompressResponse?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
/* tslint:disable:no-null-keyword */
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
const readline: NodeJS.ReadLine = require("readline");
|
||||
const fs: typeof NodeJS.fs = require("fs");
|
||||
const zlib: typeof NodeJS.zlib = require("zlib");
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -13,6 +15,11 @@ namespace ts.server {
|
||||
terminal: false,
|
||||
});
|
||||
|
||||
function compress(s: string): CompressedData {
|
||||
const gzip = zlib.createGZip();
|
||||
return <CompressedData><any>gzip.gzipSync(new Buffer(s, "utf8"));
|
||||
}
|
||||
|
||||
class Logger implements ts.server.Logger {
|
||||
private fd = -1;
|
||||
private seq = 0;
|
||||
@@ -92,7 +99,7 @@ namespace ts.server {
|
||||
|
||||
class IOSession extends Session {
|
||||
constructor(host: ServerHost, cancellationToken: HostCancellationToken, useSingleInferredProject: boolean, logger: ts.server.Logger) {
|
||||
super(host, cancellationToken, useSingleInferredProject, Buffer.byteLength, process.hrtime, logger);
|
||||
super(host, cancellationToken, useSingleInferredProject, Buffer.byteLength, compress, process.hrtime, logger);
|
||||
}
|
||||
|
||||
exit() {
|
||||
@@ -260,15 +267,16 @@ namespace ts.server {
|
||||
const pollingWatchedFileSet = createPollingWatchedFileSet();
|
||||
const logger = createLoggerFromEnv();
|
||||
|
||||
const pending: string[] = [];
|
||||
const pending: Buffer[] = [];
|
||||
let canWrite = true;
|
||||
function writeMessage(s: string) {
|
||||
|
||||
function writeMessage(buf: Buffer) {
|
||||
if (!canWrite) {
|
||||
pending.push(s);
|
||||
pending.push(buf);
|
||||
}
|
||||
else {
|
||||
canWrite = false;
|
||||
process.stdout.write(new Buffer(s, "utf8"), setCanWriteFlagAndWriteMessageIfNecessary);
|
||||
process.stdout.write(buf, setCanWriteFlagAndWriteMessageIfNecessary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,10 +287,16 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
function writeCompressedData(prefix: string, compressed: CompressedData, suffix: string): void {
|
||||
sys.write(prefix);
|
||||
writeMessage(<Buffer><any>compressed);
|
||||
sys.write(suffix);
|
||||
}
|
||||
|
||||
const sys = <ServerHost>ts.sys;
|
||||
|
||||
// Override sys.write because fs.writeSync is not reliable on Node 4
|
||||
sys.write = (s: string) => writeMessage(s);
|
||||
sys.write = (s: string) => writeMessage(new Buffer(s, "utf8"));
|
||||
sys.watchFile = (fileName, callback) => {
|
||||
const watchedFile = pollingWatchedFileSet.addFile(fileName, callback);
|
||||
return {
|
||||
@@ -294,6 +308,7 @@ namespace ts.server {
|
||||
sys.clearTimeout = clearTimeout;
|
||||
sys.setImmediate = setImmediate;
|
||||
sys.clearImmediate = clearImmediate;
|
||||
sys.writeCompressedData = writeCompressedData;
|
||||
|
||||
let cancellationToken: HostCancellationToken;
|
||||
try {
|
||||
|
||||
@@ -10,6 +10,15 @@ namespace ts.server {
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
export interface CompressedData {
|
||||
__compressedDataTag: any;
|
||||
length: number;
|
||||
}
|
||||
|
||||
export interface ServerHost {
|
||||
writeCompressedData(prefix: string, data: CompressedData, suffix: string): void;
|
||||
}
|
||||
|
||||
export function generateSpaces(n: number): string {
|
||||
if (!spaceCache[n]) {
|
||||
let strBuilder = "";
|
||||
@@ -183,6 +192,7 @@ namespace ts.server {
|
||||
cancellationToken: HostCancellationToken,
|
||||
useSingleInferredProject: boolean,
|
||||
private byteLength: (buf: string, encoding?: string) => number,
|
||||
private compress: (s: string) => CompressedData,
|
||||
private hrtime: (start?: number[]) => number[],
|
||||
private logger: Logger) {
|
||||
this.projectService =
|
||||
@@ -215,13 +225,27 @@ namespace ts.server {
|
||||
this.host.write(line + this.host.newLine);
|
||||
}
|
||||
|
||||
public send(msg: protocol.Message) {
|
||||
private sendCompressedDataToClient(prefix: string, data: CompressedData) {
|
||||
this.host.writeCompressedData(prefix, data, this.host.newLine);
|
||||
}
|
||||
|
||||
public send(msg: protocol.Message, canCompressResponse: boolean) {
|
||||
const json = JSON.stringify(msg);
|
||||
if (this.logger.isVerbose()) {
|
||||
this.logger.info(msg.type + ": " + json);
|
||||
}
|
||||
this.sendLineToClient("Content-Length: " + (1 + this.byteLength(json, "utf8")) +
|
||||
"\r\n\r\n" + json);
|
||||
const len = this.byteLength(json, "utf8");
|
||||
if (len < 84000 || !canCompressResponse) {
|
||||
this.sendLineToClient("Content-Length: " + (1 + this.byteLength(json, "utf8")) + "\r\n\r\n" + json);
|
||||
}
|
||||
else {
|
||||
// TODO: measure time
|
||||
const compressed = this.compress(json);
|
||||
if (this.logger.isVerbose()) {
|
||||
this.logger.info(`compressed message to ${compressed.length}`);
|
||||
}
|
||||
this.sendCompressedDataToClient(`Content-Length: ${compressed.length + 1}`, compressed);
|
||||
}
|
||||
}
|
||||
|
||||
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) {
|
||||
@@ -236,7 +260,7 @@ namespace ts.server {
|
||||
diagnostics: bakedDiags
|
||||
}
|
||||
};
|
||||
this.send(ev);
|
||||
this.send(ev, /*canCompressResponse*/ false);
|
||||
}
|
||||
|
||||
public event(info: any, eventName: string) {
|
||||
@@ -246,10 +270,10 @@ namespace ts.server {
|
||||
event: eventName,
|
||||
body: info,
|
||||
};
|
||||
this.send(ev);
|
||||
this.send(ev, /*canCompressResponse*/ false);
|
||||
}
|
||||
|
||||
private response(info: any, cmdName: string, reqSeq = 0, errorMsg?: string) {
|
||||
private response(info: any, cmdName: string, canCompressResponse: boolean, reqSeq = 0, errorMsg?: string) {
|
||||
const res: protocol.Response = {
|
||||
seq: 0,
|
||||
type: "response",
|
||||
@@ -263,11 +287,11 @@ namespace ts.server {
|
||||
else {
|
||||
res.message = errorMsg;
|
||||
}
|
||||
this.send(res);
|
||||
this.send(res, canCompressResponse);
|
||||
}
|
||||
|
||||
public output(body: any, commandName: string, requestSequence = 0, errorMessage?: string) {
|
||||
this.response(body, commandName, requestSequence, errorMessage);
|
||||
public output(body: any, commandName: string, canCompressResponse: boolean, requestSequence = 0, errorMessage?: string) {
|
||||
this.response(body, commandName, canCompressResponse, requestSequence, errorMessage);
|
||||
}
|
||||
|
||||
private getLocation(position: number, scriptInfo: ScriptInfo): protocol.Location {
|
||||
@@ -1030,7 +1054,7 @@ namespace ts.server {
|
||||
this.changeSeq++;
|
||||
// make sure no changes happen before this one is finished
|
||||
if (project.reloadScript(file)) {
|
||||
this.output(undefined, CommandNames.Reload, reqSeq);
|
||||
this.output(undefined, CommandNames.Reload, /*canCompressResponse*/ false, reqSeq);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1407,7 +1431,7 @@ namespace ts.server {
|
||||
},
|
||||
[CommandNames.Configure]: (request: protocol.ConfigureRequest) => {
|
||||
this.projectService.setHostConfiguration(request.arguments);
|
||||
this.output(undefined, CommandNames.Configure, request.seq);
|
||||
this.output(undefined, CommandNames.Configure, /*canCompressResponse*/ false, request.seq);
|
||||
return this.notRequired();
|
||||
},
|
||||
[CommandNames.Reload]: (request: protocol.ReloadRequest) => {
|
||||
@@ -1473,7 +1497,7 @@ namespace ts.server {
|
||||
}
|
||||
else {
|
||||
this.projectService.log("Unrecognized JSON command: " + JSON.stringify(request));
|
||||
this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command);
|
||||
this.output(undefined, CommandNames.Unknown, /*canCompressResponse*/ false, request.seq, "Unrecognized JSON command: " + request.command);
|
||||
return { responseRequired: false };
|
||||
}
|
||||
}
|
||||
@@ -1501,22 +1525,23 @@ namespace ts.server {
|
||||
this.logger.msg(leader + ": " + elapsedMs.toFixed(4).toString(), "Perf");
|
||||
}
|
||||
if (response) {
|
||||
this.output(response, request.command, request.seq);
|
||||
this.output(response, request.command, request.canCompressResponse, request.seq);
|
||||
}
|
||||
else if (responseRequired) {
|
||||
this.output(undefined, request.command, request.seq, "No content available.");
|
||||
this.output(undefined, request.command, /*canCompressResponse*/ false, request.seq, "No content available.");
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof OperationCanceledException) {
|
||||
// Handle cancellation exceptions
|
||||
this.output({ canceled: true }, request.command, request.seq);
|
||||
this.output({ canceled: true }, request.command, /*canCompressResponse*/ false, request.seq);
|
||||
return;
|
||||
}
|
||||
this.logError(err, message);
|
||||
this.output(
|
||||
undefined,
|
||||
request ? request.command : CommandNames.Unknown,
|
||||
/*canCompressResponse*/ false,
|
||||
request ? request.seq : 0,
|
||||
"Error processing request. " + (<StackTraceError>err).message + "\n" + (<StackTraceError>err).stack);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user