diff --git a/README.md b/README.md index 1faec00..108e7f5 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ A simple set of modules for using WebSockets in KDB+/q -`ws-handler` is a generic wrapper for `.z.ws` in order to allow different handlers to be defined depending on wether the q session is a client or server for the present connection. - `ws-client` provides function `.ws.open` to open a WebSocket as a client, allowing definition of a per- socket callback function, tracked in a keyed table `.ws.w` @@ -12,9 +10,37 @@ implementation in the form of `wschaintick.q`, a chained tickerplant which subscribes to a regular kdb+tick TP & republishes received records via WebSockets. [See below](#ws-handler--wschaintickq) for more details. -## Installation +`ws-handler` is a generic wrapper for `.z.ws` in order to allow different handlers to be defined depending +on wether the q session is a client or server for the present connection. Typically it shouldn't be loaded +directly but will be loaded as a dependency of the client/server libs. + +## Setup + +The simplest way for most people to setup will be using the standalone scripts from the latest release in the +repo's [Releases](https://github.com/jonathonmcmurray/ws.q/releases) tab. These scripts can be loaded directly +into a q session & have no dependencies e.g. (with client library) + +``` + $ q +KDB+ 4.0 2020.05.04 Copyright (C) 1993-2020 Kx Systems +l64/ 12(16)core 16296MB jonny desktop-c4h2kis 127.0.1.1 EXPIRE 2021.06.05 jonathon.mcmurray@aquaq.co.uk KOD #4171328 + +q)\l ws-client_0.2.0.q +q).echo.upd:show +q).echo.h:.ws.open["wss://echo.websocket.org";`.echo.upd] +q).echo.h "hello world" +q)"hello world" +``` + +Note that other examples in this readme use e.g. `.utl.require"ws-client"` to load lib instead of `\l` - you can simply replace +with the relevant `\l` command if using the standalone scripts. -The simplest way to install the modules is via Anaconda. Assuming an Anaconda distribution is installed (e.g. [miniconda](https://conda.io/en/latest/miniconda.html)), installation is as follows: +It should be possible to load both `ws-client_*.q` and `ws-server_*.q` in the same q process without conflict if a single +process needs to act as both a server & client. + +## Installation via Anaconda + +It is also possible to install the modules via Anaconda. Assuming an Anaconda distribution is installed (e.g. [miniconda](https://conda.io/en/latest/miniconda.html)), installation is as follows: ```bash $ conda install -c jmcmurray ws-client ws-server @@ -60,7 +86,7 @@ l64/ 8()core 16048MB jmcmurray homer.aquaq.co.uk 127.0.1.1 EXPIRE 2018.06.30 Aqu q).utl.require"ws-client" q).echo.upd:show -q).echo.h:.ws.open["ws://demos.kaazing.com/echo";`.echo.upd] +q).echo.h:.ws.open["wss://echo.websocket.org";`.echo.upd] q).echo.h "hi" q)"hi" .echo.h "kdb4life" @@ -131,6 +157,10 @@ socket. This object contains three keys, `type`, `tables` and `syms`. `type` is while `tables` & `syms` are lists of tables & syms to subscribe to. Similar to `u.q`, an empty list (including leaving out the key) subscribes to everything available. +Note: if using the standalone version of `ws-server_*.q` from [Releases](https://github.com/jonathonmcmurray/ws.q/releases) +page, please use `wschaintick.q` from there as well (version from main repo uses +qutil to load `ws-server`). + ### q client via `ws-client` ``` diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b0eb929 --- /dev/null +++ b/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Script to build standalone release versions of ws-client.q and ws-server.q + +RELEASE=${1} + +cat ws-handler/ws-handler.q ws-client/ws.q > ws-client_${RELEASE}.q +cat ws-handler/ws-handler.q ws-server/wsu.q > ws-server_${RELEASE}.q +sed "s/.utl.require\"ws-server\"/\\\\l ws-server_${RELEASE}.q/g" wschaintick.q > wschaintick_${RELEASE}.q \ No newline at end of file diff --git a/ws-client/meta.yaml b/ws-client/meta.yaml index 0b79ebf..430931e 100644 --- a/ws-client/meta.yaml +++ b/ws-client/meta.yaml @@ -1,6 +1,6 @@ package: name: ws-client - version: 0.1.0 + version: 0.2.0 build: number: 0 diff --git a/ws-client/ws.q b/ws-client/ws.q index 6a6f072..d04b15c 100644 --- a/ws-client/ws.q +++ b/ws-client/ws.q @@ -1,5 +1,42 @@ \d .ws +/ if reQ not loaded, define necessary components here +if[not `req in key `; + .url.parse0:{[q;x] + if[x~hsym`$255#"a";'"hsym too long - consider using a string"]; //error if URL~`: .. too long + x:.url.sturl x; //ensure string URL + p:x til pn:3+first ss[x;"://"]; //protocol + uf:("@"in x)&first[ss[x;"@"]]type each k:(" ";`),key d;string]; //convert non-string keys to strings + v:2_@[v;where 10<>type each v:(" ";`),value d;string]; //convert non-string values to strings + :("\r\n" sv ": " sv/:flip (k;v)),"\r\n\r\n"; //encode headers dict to HTTP headers + }; + .req.buildquery:{[q] + r:string[q`method]," ",q[`url;`path]," HTTP/1.1\r\n", //method & endpoint TODO: fix q[`path] for proxy use case + "Host: ",q[`url;`host],$[count q`headers;"\r\n";""], //add host string + .req.enchd[q`headers], //add headers + $[count q`body;q`body;""]; //add payload if present + :r; //return complete query string + }; + ]; + VERBOSE:@[value;`.ws.VERBOSE;$[count .z.x;"-verbose" in .z.x;0b]]; //default to non-verbose output w:([h:`int$()] hostname:`$();callback:`$()) //table for recording open websockets diff --git a/ws-handler/meta.yaml b/ws-handler/meta.yaml index 05a5f02..9367ac0 100644 --- a/ws-handler/meta.yaml +++ b/ws-handler/meta.yaml @@ -1,6 +1,6 @@ package: name: ws-handler - version: 0.1.0 + version: 0.2.0 build: number: 0 diff --git a/ws-handler/ws-handler.q b/ws-handler/ws-handler.q index 7214bd0..c6f245d 100644 --- a/ws-handler/ws-handler.q +++ b/ws-handler/ws-handler.q @@ -1,11 +1,13 @@ / WebSockets handler module; create & receive WebSocket connections in a managable way \d .ws -clients:([h:`int$()] hostname:`$()) //clients = incoming connections over ws -servers:([h:`int$()] hostname:`$()) //servers = outgoing connections over ws +/ Set up client/server tables & handlers without overwriting, so this script +/ can be loaded multiple times without issue +clients:@[value;`.ws.clients;([h:`int$()] hostname:`$())]; //clients = incoming connections over ws +servers:@[value;`.ws.servers;([h:`int$()] hostname:`$())]; //servers = outgoing connections over ws -onmessage.client:{x} //default echo -onmessage.server:{x} //default echo +onmessage.client:@[value;`.ws.onmessage.client;{{x}}]; //default echo +onmessage.server:@[value;`.ws.onmessage.server;{{x}}]; //default echo .z.ws:{.ws.onmessage[$[.z.w in key servers;`server;`client]]x} //pass messages to relevant handler func .z.wo:{clients,:(.z.w;.z.h)} //log incoming connections diff --git a/ws-server/meta.yaml b/ws-server/meta.yaml index c8c505c..3977db8 100644 --- a/ws-server/meta.yaml +++ b/ws-server/meta.yaml @@ -1,6 +1,6 @@ package: name: ws-server - version: 0.1.0 + version: 0.2.0 build: number: 0