Skip to content

Commit

Permalink
gh-10 Add build script for standalone versions
Browse files Browse the repository at this point in the history
* Add build script to build standalone versions of ws-client.q & ws-server.q
* Update README to include details of standalone versions
* Incorporate necessary reQ functions into ws-client, if reQ not independently loaded
* Update build number in Conda pkg configs
* Make ws-handler capable to be loaded multiple times without side-effects

Resolves gh-10
  • Loading branch information
jonathonmcmurray committed Feb 10, 2021
1 parent e324fc5 commit 26c479d
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 12 deletions.
40 changes: 35 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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`

```
Expand Down
9 changes: 9 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion ws-client/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package:
name: ws-client
version: 0.1.0
version: 0.2.0

build:
number: 0
Expand Down
37 changes: 37 additions & 0 deletions ws-client/ws.q
Original file line number Diff line number Diff line change
@@ -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;"@"]]<first ss[pn _ x;"/"]; //user flag - true if username present
un:pn; //default to no user:pass
u:-1_$[uf;(pn _ x) til (un:1+first ss[x;"@"])-pn;""]; //user:pass
d:x til dn:count[x]^first ss[x:un _ x;"/"]; //domain
a:$[dn=count x;enlist"/";dn _ x]; //absolute path
o:`protocol`auth`host`path!(p;u;d;a); //create URL object
:$[q;@[o;`path`query;:;query o`path];o]; //split path into path & query if flag set, return
};
.url.sturl:{(":"=first x)_x:$[-11=type x;string;]x};
.url.hsurl:{`$":",.url.sturl x};
.req.query:`method`url`hsym`path`headers`body`bodytype!();
.req.proxy:{[u]
p:(^/)`$getenv`$(floor\)("HTTP";"NO"),\:"_PROXY"; //check HTTP_PROXY & NO_PROXY env vars, upper & lower case - fill so p[0] is http_, p[1] is no_
t:max(first ":"vs u[`url]`host)like/:{(("."=first x)#"*"),x}each"," vs string p 1; //check if host is in NO_PROXY env var
t:not null[first p]|t; //check if HTTP_PROXY is defined & host isn't in NO_PROXY
:$[t;@[;`proxy;:;p 0];]u; //add proxy to URL object if required
};
.req.enchd:{[d]
k:2_@[k;where 10<>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
Expand Down
2 changes: 1 addition & 1 deletion ws-handler/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package:
name: ws-handler
version: 0.1.0
version: 0.2.0

build:
number: 0
Expand Down
10 changes: 6 additions & 4 deletions ws-handler/ws-handler.q
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion ws-server/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package:
name: ws-server
version: 0.1.0
version: 0.2.0

build:
number: 0
Expand Down

0 comments on commit 26c479d

Please sign in to comment.