├── .gitignore ├── tsconfig.json ├── README.md ├── package.json └── src └── server.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | .vscode 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "outDir": "dist", 7 | "lib": ["es2016", "dom"], 8 | "declaration": true 9 | }, 10 | "include": [ 11 | "src" 12 | ] 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sets up a websocket proxy for any number of language servers. 2 | 3 | Each server is run as a subprocess which is connected to by sending the client 4 | to the URL / based on a configuration file defined locally. For example, 5 | with the following defined as `servers.yml`: 6 | 7 | ``` 8 | langservers: 9 | python: 10 | - python 11 | - python-langserver.py 12 | - --stdio 13 | go: 14 | - /usr/local/bin/go 15 | - langserver.go 16 | ``` 17 | 18 | The client would connect to `ws://localhost/python` to get a python language server 19 | 20 | Usage: 21 | 22 | ``` 23 | npm install 24 | npm run prepare 25 | node dist/server.js --port 3000 --languageServers servers.yml 26 | ``` 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonrpc-ws-proxy", 3 | "version": "0.0.5", 4 | "author": "Wylie Conlon ", 5 | "description": "Creates a web socket proxy for any number of language server processes", 6 | "keywords": [ 7 | "jsonrpc", 8 | "json-rpc", 9 | "languageserver", 10 | "websocket" 11 | ], 12 | "homepage": "https://github.com/wylieconlon/jsonrpc-ws-proxy", 13 | "repository": "github:wylieconlon/jsonrpc-ws-proxy", 14 | "bin": "dist/server.js", 15 | "main": "dist/server.js", 16 | "scripts": { 17 | "prepare": "npm run clean && npm run build", 18 | "watch": "tsc -w", 19 | "clean": "rimraf dist", 20 | "build": "tsc" 21 | }, 22 | "dependencies": { 23 | "@sourcegraph/vscode-ws-jsonrpc": "0.0.3-fork", 24 | "js-yaml": "^3.13.1", 25 | "minimist": "^1.2.0", 26 | "ws": "^6.2.1" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^10.14.8", 30 | "@types/ws": "^6.0.1", 31 | "javascript-typescript-langserver": "^2.11.3", 32 | "rimraf": "^2.6.3", 33 | "typescript": "^3.5.1", 34 | "vscode-css-languageserver-bin": "^1.4.0", 35 | "vscode-html-languageserver-bin": "^1.4.0" 36 | }, 37 | "license": "ISC" 38 | } 39 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as http from 'http'; 4 | import * as fs from 'fs'; 5 | 6 | import * as parseArgs from 'minimist'; 7 | import * as yaml from 'js-yaml'; 8 | import * as ws from 'ws'; 9 | import * as rpc from '@sourcegraph/vscode-ws-jsonrpc'; 10 | import * as rpcServer from '@sourcegraph/vscode-ws-jsonrpc/lib/server'; 11 | 12 | let argv = parseArgs(process.argv.slice(2)); 13 | 14 | if (argv.help || !argv.languageServers) { 15 | console.log(`Usage: server.js --port 3000 --languageServers config.yml`); 16 | process.exit(1); 17 | } 18 | 19 | let serverPort : number = parseInt(argv.port) || 3000; 20 | 21 | let languageServers; 22 | try { 23 | let parsed = yaml.safeLoad(fs.readFileSync(argv.languageServers), 'utf8'); 24 | if (!parsed.langservers) { 25 | console.log('Your langservers file is not a valid format, see README.md'); 26 | process.exit(1); 27 | } 28 | languageServers = parsed.langservers; 29 | } catch (e) { 30 | console.error(e); 31 | process.exit(1); 32 | } 33 | 34 | const wss : ws.Server = new ws.Server({ 35 | port: serverPort, 36 | perMessageDeflate: false 37 | }, () => { 38 | console.log(`Listening to http and ws requests on ${serverPort}`); 39 | }); 40 | 41 | function toSocket(webSocket: ws): rpc.IWebSocket { 42 | return { 43 | send: content => webSocket.send(content), 44 | onMessage: cb => webSocket.onmessage = event => cb(event.data), 45 | onError: cb => webSocket.onerror = event => { 46 | if ('message' in event) { 47 | cb((event as any).message) 48 | } 49 | }, 50 | onClose: cb => webSocket.onclose = event => cb(event.code, event.reason), 51 | dispose: () => webSocket.close() 52 | } 53 | } 54 | 55 | wss.on('connection', (client : ws, request : http.IncomingMessage) => { 56 | let langServer : string[]; 57 | 58 | Object.keys(languageServers).forEach((key) => { 59 | if (request.url === '/' + key) { 60 | langServer = languageServers[key]; 61 | } 62 | }); 63 | if (!langServer || !langServer.length) { 64 | console.error('Invalid language server', request.url); 65 | client.close(); 66 | return; 67 | } 68 | 69 | let localConnection = rpcServer.createServerProcess('Example', langServer[0], langServer.slice(1)); 70 | let socket : rpc.IWebSocket = toSocket(client); 71 | let connection = rpcServer.createWebSocketConnection(socket); 72 | rpcServer.forward(connection, localConnection); 73 | console.log(`Forwarding new client`); 74 | socket.onClose((code, reason) => { 75 | console.log('Client closed', reason); 76 | localConnection.dispose(); 77 | }); 78 | }); 79 | --------------------------------------------------------------------------------