RestService — call activateLastFocusedFrame if need, most of the services are responsibile to call it in appropriate time, generic solution is not suitable here

node-rpc-client — fix connect, but "write" is not yet done
This commit is contained in:
Vladimir Krivosheev
2015-12-31 17:52:32 +01:00
parent 34408fba3d
commit e8bae9fbec
20 changed files with 1942 additions and 980 deletions
@@ -5,6 +5,7 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/client/node-rpc-client/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/client/node-rpc-client/out" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
+3 -1
View File
@@ -1 +1,3 @@
node_modules/
node_modules/
npm-debug.log
out/
@@ -1,42 +0,0 @@
var gulp = require('gulp')
var ts = require('gulp-typescript')
var concat = require('gulp-concat')
var uglify = require('gulp-uglify')
var newer = require('gulp-newer')
var sourcemaps = require('gulp-sourcemaps')
var path = require('path')
var outDir = 'out'
var outFile = 'ij-rpc-client.js'
var sources = "src/*.ts";
var tsProject = ts.createProject({
target: "ES5",
noImplicitAny: true,
removeComments: true,
sortOutput: true,
module: "commonjs"
});
gulp.task("compile", function () {
var tsResult = gulp.src(sources)
.pipe(sourcemaps.init())
//.pipe(newer(outDir + '/' + outFile))
.pipe(ts(tsProject));
tsResult.js.pipe(concat(outFile))
//.pipe(uglify({
// output: {
// beautify: true,
// indent_level: 2
// }
// }))
.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: path.resolve('testData')}))
.pipe(gulp.dest(outDir))
});
gulp.task('watch', function () {
gulp.watch(sources, ['compile']);
});
gulp.task('default', ['compile']);
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -2,16 +2,21 @@
"name": "ij-rpc-client",
"version": "0.0.1",
"description": "IntelliJ Platform RPC client",
"scripts": {
"compile": "rimraf app/out && tsconfig -i 2 && tsc",
"test": "mocha --harmony_default_parameters --harmony_rest_parameters"
},
"repository": {
"type": "git",
"url": "https://github.com/JetBrains/intellij-community.git"
},
"devDependencies": {
"gulp": "^3.8.11",
"gulp-concat": "^2.4.3",
"gulp-newer": "^0.5.0",
"gulp-sourcemaps": "^1.3.0",
"gulp-typescript": "^2.4.2",
"gulp-uglify": "^1.1.0"
"rimraf": "^2.5.0",
"should": "^8.0.2",
"tsconfig-glob": "^0.3.3",
"typescript": "^1.8.0-dev.20151231"
},
"dependencies": {
"bluebird": "^3.1.1"
}
}
@@ -1,82 +1,98 @@
"use strict"
import net = require("net")
import rpc = require("./rpc")
import * as net from "net"
import { JsonRpc, Transport } from "./rpc"
export class RpcClient {
connect(port:Number = 63342) {
var socket = net.connect({port: port}, function () {
console.log('Connected to IJ RPC server localhost:' + port)
});
connect(port: number = 63342) {
const socket = net.connect({port: port}, () => {
console.log("Connected to IJ RPC server localhost: " + port)
})
var jsonRpc = new rpc.JsonRpc(new SocketTransport(socket))
var decoder:MessageDecoder = new MessageDecoder(jsonRpc.messageReceived)
socket.on('data', decoder.messageReceived)
const transport = new SocketTransport(socket)
const jsonRpc = new JsonRpc(transport)
const decoder = new MessageDecoder(jsonRpc.messageReceived)
socket.on("data", decoder.messageReceived)
}
}
const enum State {LENGTH, CONTENT}
class SocketTransport implements rpc.Transport {
export class SocketTransport implements Transport {
private headerBuffer = new Buffer(4)
constructor(private socket:net.Socket) {
opened: () => void
constructor(private socket: net.Socket = new net.Socket()) {
}
send(id:number, domain:string, command:string, params:any[] = null):void {
var encodedParams = JSON.stringify(params)
var header = (id == -1 ? '' : (id + ', ')) + '"' + domain + '", "' + command + '"';
this.headerBuffer.writeUInt32BE(Buffer.byteLength(encodedParams) + header.length, 0)
connect(port: number = 63342) {
this.socket.connect(port, null, ()=> {
const opened = this.opened
if (opened != null) {
opened()
}
})
this.socket.on("error", (e: Error) => {
console.error(e)
})
this.socket.write(new Buffer([67, 72, 105, -107, 126, -21, -81, -72, 64, 54, -87, -88, 0, -46, -48, 34, -7, -67]))
}
send(id: number, domain: string, command: string, params: any[] = null): void {
const encodedParams = JSON.stringify(params)
const header = (id == -1 ? '' : (id + ', ')) + '"' + domain + '", "' + command + '"';
this.headerBuffer.writeUInt32BE(header.length + Buffer.byteLength(encodedParams), 0)
this.socket.write(this.headerBuffer)
this.socket.write(encodedParams, 'utf-8')
this.socket.write(header)
this.socket.write(encodedParams)
}
sendResult(id:number, result:any):void {
sendResult(id: number, result: any): void {
this.sendResultOrError(id, result, false)
}
sendError(id:number, error:any):void {
sendError(id: number, error: any): void {
this.sendResultOrError(id, error, true)
}
private sendResultOrError(id:number, result:any, isError:boolean):void {
private sendResultOrError(id: number, result: any, isError: boolean): void {
var encodedResult = JSON.stringify(result)
var header = id + ', "' + (isError ? 'e': 'r') + '"';
var header = id + ', "' + (isError ? 'e' : 'r') + '"';
this.headerBuffer.writeUInt32BE(Buffer.byteLength(encodedResult) + header.length, 0)
this.socket.write(this.headerBuffer)
this.socket.write(encodedResult, 'utf-8')
this.socket.write(encodedResult)
}
}
class MessageDecoder {
private state:State = State.LENGTH
private contentLength:number = 0
private state: State = State.LENGTH
private contentLength: number = 0
private buffers:Array<Buffer> = []
private totalBufferLength:number = 0
private offset:number = 0
private buffers: Array<Buffer> = []
private totalBufferLength: number = 0
private offset: number = 0
constructor(private messageProcessor:(message:any)=>void) {
constructor(private messageProcessor: (message: any)=>void) {
}
private byteConsumed(count:number) {
private byteConsumed(count: number) {
this.offset += count
this.totalBufferLength -= count
}
messageReceived(buffer:Buffer) {
messageReceived(buffer: Buffer) {
this.totalBufferLength += buffer.length
while (true) {
//noinspection FallThroughInSwitchStatementJS
switch (this.state) {
case State.LENGTH: {
case State.LENGTH:
{
if (this.totalBufferLength < 4) {
this.buffers.push(buffer)
return
}
var totalBuffer:Buffer
var totalBuffer: Buffer
if (this.buffers.length === 0) {
totalBuffer = buffer
}
@@ -92,13 +108,14 @@ class MessageDecoder {
buffer = totalBuffer
}
case State.CONTENT: {
case State.CONTENT:
{
if (this.totalBufferLength < this.contentLength) {
this.buffers.push(buffer)
return
}
var totalBuffer:Buffer
var totalBuffer: Buffer
if (this.buffers.length === 0) {
totalBuffer = buffer
}
@@ -1,39 +1,39 @@
/// <reference path="../typings/node/node.d.ts" />
/// <reference path="../typings/bluebird/bluebird.d.ts" />
"use strict"
import Promise = require("bluebird")
class PromiseCallback {
constructor(public resolve:(value?:any) => void, public reject:(error?:any) => void) {
constructor(public resolve: (value?: any) => void, public reject: (error?: any) => void) {
}
}
export interface Transport {
send(id:number, domain:string, command:string, params:any[]):void
opened?: () => void
sendResult(id:number, result:any):void
connect(port: number): void
sendError(id:number, error:any):void
send(id: number, domain: string, command: string, params: any[]): void
sendResult(id: number, result: any):void
sendError(id: number, error: any):void
}
export class JsonRpc {
private messageIdCounter = 0
private callbacks:Map<number, PromiseCallback> = new Map<number, PromiseCallback>()
private domains:Map<string, any> = new Map<string, any>()
private callbacks: Map<number, PromiseCallback> = new Map<number, PromiseCallback>()
private domains: Map<string, any> = new Map<string, any>()
constructor(private transport:Transport) {
constructor(private transport: Transport) {
}
public call<T>(domain:string, command:string, ...params: any[]):Promise<T> {
return new Promise((resolve:(value:T) => void, reject:(error?:any) => void) => {
public call<T>(domain: string, command: string, ...params: any[]): Promise<T> {
return new Promise((resolve: (value: T) => void, reject: (error?: any) => void) => {
var id = this.messageIdCounter++;
this.callbacks.set(id, new PromiseCallback(resolve, reject))
this.transport.send(id, domain, command, params)
})
}
messageReceived(message:Array<any>) {
messageReceived(message: Array<any>) {
if (message.length === 1 || (message.length === 2 && !(typeof message[1] === 'string'))) {
var promiseCallback = this.callbacks.get(message[0])
var singletonArray = safeGet(message, 1)
@@ -45,8 +45,8 @@ export class JsonRpc {
}
}
else {
var id:number
var offset:number
var id: number
var offset: number
if (typeof message[0] === 'string') {
id = -1
offset = 0
@@ -56,11 +56,11 @@ export class JsonRpc {
offset = 1
}
var onRejected = id === -1 ? null : (error:any) => this.transport.sendError(id, error)
var onRejected = id === -1 ? null : (error: any) => this.transport.sendError(id, error)
try {
var object = this.domains.get(message[offset])
var method = object[message[offset + 1]]
var result:any
var result: any
var args = safeGet(message, offset + 2)
if (args === null) {
result = method.call(object)
@@ -70,7 +70,7 @@ export class JsonRpc {
}
if (id !== -1) {
var onFulfilled = (result:any) => this.transport.sendResult(id, result)
var onFulfilled = (result: any) => this.transport.sendResult(id, result)
if (result instanceof Promise) {
(<Promise<any>>result).done(onFulfilled, onRejected)
}
@@ -89,6 +89,6 @@ export class JsonRpc {
}
}
function safeGet(a:any[], index:number):Array<any> {
function safeGet(a: any[], index: number): Array<any> {
return index < a.length ? a[index] : null
}
@@ -0,0 +1,30 @@
const should = require("should")
const rpcClient = require("../out/rpc-client")
const rpc = require("../out/rpc")
describe("RPC", function () {
it("connect", function (done) {
const transport = new rpcClient.SocketTransport()
transport.opened = function () {
done()
}
transport.connect()
})
it("connect", function (done) {
this.timeout(5000000)
const transport = new rpcClient.SocketTransport()
transport.opened = function () {
}
transport.connect(63343)
const rpcServer = new rpc.JsonRpc(transport)
rpcServer.call("Ide", "about")
.then(function (r) {
console.log(r)
done()
}, function (e) {
throw e
})
})
})
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"removeComments": true,
"outDir": "out",
"newLine": "LF",
"noResolve": true,
"noEmitOnError": true,
"inlineSources": true,
"sourceMap": true
},
"filesGlob": [
"src/*.ts",
"typings/**/*.d.ts"
],
"files": [
"typings/bluebird/bluebird.d.ts",
"typings/node/node.d.ts",
"src/rpc-client.ts",
"src/rpc.ts"
]
}
@@ -3,13 +3,12 @@
"repo": "borisyankov/DefinitelyTyped",
"ref": "master",
"path": "typings",
"bundle": "typings/tsd.d.ts",
"installed": {
"node/node.d.ts": {
"commit": "35fffaa44bff5392741b3022d805fe3563023a3d"
"commit": "dc9dabe74a5be62613b17a3605309783a12ff28a"
},
"bluebird/bluebird.d.ts": {
"commit": "cf7c97b2a68a385c98c75fb6edd81083c97c983c"
"commit": "dc9dabe74a5be62613b17a3605309783a12ff28a"
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1 +0,0 @@
/// <reference path="node/node.d.ts" />
@@ -34,6 +34,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.OutputStream;
/**
* @api {get} /about The application info
@@ -41,6 +42,7 @@ import java.io.IOException;
* @apiGroup Platform
*
* @apiParam {Boolean} [registeredFileTypes=false] Whether to include the list of registered file types.
* @apiParam {Boolean} [more=false] Whether to include the full info.
*
* @apiSuccess {String} name The full application name.
* @apiSuccess {String} productName The product name.
@@ -58,7 +60,7 @@ import java.io.IOException;
* @apiUse SuccessExample
* @apiUse SuccessExampleWithRegisteredFileTypes
*/
class AboutHttpService extends RestService {
public class AboutHttpService extends RestService {
@NotNull
@Override
protected String getServiceName() {
@@ -73,10 +75,17 @@ class AboutHttpService extends RestService {
@Nullable
@Override
public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
BuildNumber build = ApplicationInfo.getInstance().getBuild();
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
BufferExposingByteArrayOutputStream byteOut = new BufferExposingByteArrayOutputStream();
JsonWriter writer = createJsonWriter(byteOut);
getAbout(byteOut, urlDecoder);
send(byteOut, request, context);
return null;
}
public static void getAbout(@NotNull OutputStream out, @Nullable QueryStringDecoder urlDecoder) throws IOException {
BuildNumber build = ApplicationInfo.getInstance().getBuild();
JsonWriter writer = createJsonWriter(out);
writer.beginObject();
ApplicationInfoEx appInfo = ApplicationInfoEx.getInstanceEx();
@@ -94,7 +103,7 @@ class AboutHttpService extends RestService {
writer.name("buildNumber").value(build.getBuildNumber());
}
if (getBooleanParameter("registeredFileTypes", urlDecoder)) {
if (urlDecoder != null && getBooleanParameter("registeredFileTypes", urlDecoder)) {
writer.name("registeredFileTypes").beginArray();
for (FileType fileType : FileTypeRegistry.getInstance().getRegisteredFileTypes()) {
writer.beginObject();
@@ -106,8 +115,7 @@ class AboutHttpService extends RestService {
writer.endArray();
}
String uri = request.getUri();
if (uri != null && uri.endsWith("?more")) {
if (urlDecoder != null && getBooleanParameter("more", urlDecoder)) {
writer.name("vendor").value(appInfo.getCompanyName());
writer.name("isEAP").value(appInfo.isEAP());
writer.name("productCode").value(appInfo.getBuild().getProductCode());
@@ -122,12 +130,5 @@ class AboutHttpService extends RestService {
writer.endObject();
writer.close();
send(byteOut, request, context);
return null;
}
@Override
protected boolean activateToolBeforeExecution() {
return false;
}
}
@@ -38,6 +38,7 @@ public class ActivateApplicationHttpService extends RestService {
if (frame instanceof Window) {
sendOk(request, context);
Runnable runnable = new Runnable() {
@Override
public void run() {
Window window = (Window)frame;
window.toFront();
@@ -40,6 +40,8 @@ public class JetBrainsProtocolHandlerHttpService extends RestService {
final String url = reader.nextString();
reader.endObject();
activateLastFocusedFrame();
if (URL_PARAM_NAME.equals(name) && url != null && url.startsWith(JetBrainsProtocolHandler.PROTOCOL)) {
JetBrainsProtocolHandler.processJetBrainsLauncherParameters(url);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@@ -67,6 +67,7 @@ public class ProjectSetRequestHandler extends RestService {
@Override
public void run() {
new ProjectSetReader().readDescriptor(descriptor, null);
activateLastFocusedFrame();
}
});
sendOk(request, context);
@@ -44,6 +44,7 @@ import org.jetbrains.io.Responses;
import java.awt.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.List;
@@ -103,10 +104,6 @@ public abstract class RestService extends HttpRequestHandler {
return false;
}
protected boolean activateToolBeforeExecution() {
return true;
}
@NotNull
/**
* Use human-readable name or UUID if it is an internal service.
@@ -118,13 +115,6 @@ public abstract class RestService extends HttpRequestHandler {
@Override
public final boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
try {
if (activateToolBeforeExecution()) {
IdeFrame frame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
if (frame instanceof Window) {
((Window)frame).toFront();
}
}
String error = execute(urlDecoder, request, context);
if (error != null) {
Responses.sendStatus(HttpResponseStatus.BAD_REQUEST, context.channel(), error, request);
@@ -147,6 +137,13 @@ public abstract class RestService extends HttpRequestHandler {
return true;
}
protected final void activateLastFocusedFrame() {
IdeFrame frame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
if (frame instanceof Window) {
((Window)frame).toFront();
}
}
@Nullable("error text or null if successful")
/**
* Return error or send response using {@link #sendOk(FullHttpRequest, ChannelHandlerContext)}, {@link #send(BufferExposingByteArrayOutputStream, FullHttpRequest, ChannelHandlerContext)}
@@ -161,7 +158,7 @@ public abstract class RestService extends HttpRequestHandler {
}
@NotNull
protected static JsonWriter createJsonWriter(@NotNull BufferExposingByteArrayOutputStream out) {
protected static JsonWriter createJsonWriter(@NotNull OutputStream out) {
JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, CharsetToolkit.UTF8_CHARSET));
writer.setIndent(" ");
return writer;
@@ -96,6 +96,10 @@ public class JsonRpcServer implements MessageServer {
if (!isBinary) {
reader.beginArray();
}
else {
// to allow top-level member to be not a object or array
reader.setLenient(true);
}
int messageId = reader.peek() == JsonToken.NUMBER ? reader.nextInt() : -1;
String domainName = reader.nextString();
@@ -155,7 +159,22 @@ public class JsonRpcServer implements MessageServer {
method.setAccessible(true);
Object result = method.invoke(isStatic ? null : domain, parameters);
if (messageId != -1) {
client.send(encodeMessage(client.getByteBufAllocator(), messageId, null, null, null, new Object[]{result}));
if (result instanceof ByteBuf) {
ByteBuf buffer = (ByteBuf)result;
boolean success = false;
try {
client.send(encodeMessage(client.getByteBufAllocator(), messageId, null, null, buffer, null));
success = true;
}
finally {
if (!success) {
buffer.release();
}
}
}
else {
client.send(encodeMessage(client.getByteBufAllocator(), messageId, null, null, null, new Object[]{result}));
}
}
return;
}
@@ -74,7 +74,6 @@ class RpcBinaryRequestHandler extends BinaryRequestHandler implements ExceptionH
private class MyDecoder extends MessageDecoder {
private State state = State.LENGTH;
private int contentLength;
private final SocketClient client;