gremlin-secure
Version:
Private version of the gremlin websocket client module: https://github.com/jbmusso/gremlin-javascript. This private version supports SSL and SASL authentication, and is undergoing a PR with the main module. This package is created as a private, temporary
363 lines (254 loc) • 13 kB
Markdown
[](https://travis-ci.org/jbmusso/gremlin-javascript) [](https://coveralls.io/github/jbmusso/gremlin-javascript?branch=master) [](https://www.npmjs.com/package/gremlin)
gremlin-javascript
==================
A WebSocket JavaScript client for TinkerPop3 Gremlin Server. Works in Node.js and modern browsers.
NOTE:
This module is a private version of https://www.npmjs.com/package/gremlin created to provide SSL and SASL auth support.
## UPDATE
This module will be deprecated soon. please use the official version of the gremlin-javascript module, which now supports SSL and SASL.
https://www.npmjs.com/package/gremlin
## Installation
```
npm install gremlin-secure --save
```
## Quick start
```javascript
import { createClient } from 'gremlin';
const client = createClient();
client.execute('g.V().has("name", name)', { name: 'Alice' }, (err, results) => {
if (err) {
return console.error(err)
}
console.log(results);
});
```
### Using ES2015/2016
```javascript
import { createClient, makeTemplateTag } from 'gremlin';
const client = createClient();
const gremlin = makeTemplateTag(client);
const fetchByName = async (name) => {
const users = await gremlin`g.V().has('name', ${name})`;
console.log(users);
}
fetchByName('Alice');
```
### Experimental: JavaScript Gremlin language variant
This library has partial support for [Gremlin-JavaScript language variant](http://tinkerpop.apache.org/docs/3.2.4/reference/#_on_gremlin_language_variants). It currently sends Groovy strings (rather than bytecode) and automatically escapes primitives. However, it does not support sending anonymous functions. Under the hood, it serializes `Traversal` to Groovy using an early version of [zer](https://github.com/jbmusso/zer).
The following works with a recent version of Node.js (tested with v7.6.0):
```javascript
import { createClient, statics } from 'gremlin';
const client = createClient();
const g = client.traversalSource();
const { both } = statics;
// And then, within any async function:
const results = await g.V().repeat(both('created')).times(2).toPromise();
// results.length === 16;
```
## Usage
### Creating a new client
```javascript
// Assuming Node.js or Browser environment with browserify:
import Gremlin from 'gremlin';
// Will open a WebSocket to ws://localhost:8182 by default
const client = Gremlin.createClient();
```
This is a shorthand for:
```javascript
const client = Gremlin.createClient(8182, 'localhost');
```
If you want to use Gremlin Server sessions, you can set the `session` argument as true in the `options` object:
```javascript
const client = Gremlin.createClient(8182, 'localhost', { session: true });
```
The `options` object currently allows you to set the following options:
* `session`: whether to use sessions or not (default: `false`)
* `language`: the script engine to use on the server, see your gremlin-server.yaml file (default: `"gremlin-groovy"`)
* `op` (advanced usage): The name of the "operation" to execute based on the available OpProcessor (default: `"eval"`)
* `processor` (advanced usage): The name of the OpProcessor to utilize (default: `""`)
* `accept` (advanced usage): mime type of returned responses, depending on the serializer (default: `"application/json"`)
* `path`: a custom URL connection path if connecting to a Gremlin server behind a WebSocket proxy
* `ssl`: whether to use secure WebSockets or not (default: `false`)
$ `rejectUnauthorized`: when using ssl, whether to reject self-signed certificates or not (default: `true`). Useful in development mode when using gremlin-server self signed certificates. Do NOT use self-signed certificates with this option in production.
* `user` : username to use for SASL authentication
* `password` : password to use for SASL authentication
## Using SASL Authentication
If you want to use [SASL Authentication] (http://tinkerpop.apache.org/docs/3.2.5/dev/provider/#_authentication) with your gremlin server:
```javascript
import { createClient } from 'gremlin';
const client = Gremlin.createClient(8182, 'localhost', { ssl:true, user:'user', password:'password' });
client.execute('g.V()', { }, (err, results) => {
if (err) {
return console.error(err)
}
console.log(results);
});
```
### Executing Gremlin queries
The client currently supports three modes:
* callback mode (with internal buffer)
* promise mode
* streaming moderesults
* streaming protocol messages (low level API, for advanced usages)
#### Callback mode: client.execute(script, bindings, message, callback)
Will execute the provided callback when all results are actually returned from the server.
```javascript
client.execute('g.V()', (err, results) => {
if (!err) {
console.log(results) // notice how results is *always* an array
}
});
```
The client will internally concatenate all partial results returned over different messages (depending on the total number of results and the value of `resultIterationBatchSize` set in your .yaml file).
When the client receives the final `statusCode: 299` message, the callback will be executed.
#### Promise/template mode: Gremlin.makeTemplateTag(client);
The EcmaScript2015 specification added support for Promise and tagged template literals to JavaScript. Gremlin client leverages these features and offers an alternative way to execute Gremlin queries.
`makeTemplateTag(client)` will return a template function, or 'tag', bound to a given Gremlin client instance. Calling that template will return a `Promise` of execution of the given script using the registered client, while simultaneously escaping all parameters for performance and security concerns.
```javascript
import { createClient, makeTemplateTag } from 'gremlin';
const client = createClient();
const gremlin = makeTemplateTag(client);
gremlin`g.V().has('name', ${name})` // template tag that returns a Promise
.then((vertices) => {
console.log(vertices)
})
.catch((err) => {
// Something went wrong
})
```
For easier debugging, you can also preview the raw query sent to Gremlin server:
```javascript
const name = 'Bob';
const { query } = gremlin`g.V().has('name', ${name})`;
console.log(query);
// output:
// { gremlin: 'g.V().has(\'name\', p1)', bindings: { p1: 'Bob' } }
```
Because the `gremlin` template literal returns a `Promise`, it can be used in conjunction with the async function proposal from ES2016 to execute Gremlin queries with a shortened syntax:
```javascript
const fetchByName = async (name) => {
const users = await gremlin`g.V().has('name', ${name})`;
console.log(users);
}
fetchByName('Alice');
```
#### Stream mode
##### client.stream(script, bindings, message)
Return a Node.js ReadableStream set in Object mode. The stream emits a distinct `data` event per query result returned by Gremlin Server.
Internally, a 1-level flatten is performed on all raw protocol messages returned. If you do not wish this behavior and prefer handling raw protocol messages with batched results, prefer using `client.messageStream()`.
The order in which results are returned is guaranteed, allowing you to effectively use `order` steps and the like in your Gremlin traversal.
The stream emits an `end` event when the client receives the last `statusCode: 299` message returned by Gremlin Server.
```javascript
const query = client.stream('g.V()');
// If playing with classic TinkerPop graph, will emit 6 data events
query.on('data', (result) => {
// Handle first vertex
console.log(result);
});
query.on('end', () => {
console.log('All results fetched');
});
```
This allows you to effectively `.pipe()` the stream to any other Node.js WritableStream/TransformStream.
##### client.messageStream(script, bindings, message)
A lower level method that returns a `ReadableStream` which emits the raw protocol messages returned by Gremlin Server as distinct `data` events.
If you wish a higher-level stream of `results` rather than protocol messages, please use `client.stream()`.
Although a public method, this is recommended for advanced usages only.
```javascript
const client = Gremlin.createClient();
const stream = client.messageStream('g.V()');
// Will emit 3 events with a resultIterationBatchSize set to 2 and classic graph defined in gremlin-server.yaml
stream.on('data', (message) => {
console.log(message.result); // Array of 2 vertices
});
```
### Adding bound parameters to your scripts
For better performance and security concerns (script injection), you must send bound parameters (`bindings`) with your scripts.
`client.execute()`, `client.stream()` and `client.messageStream()` share the same function signature: `(script, bindings, querySettings)`.
Notes/Gotchas:
- Any bindings set to `undefined` will be automatically escaped with `null` values (first-level only) in order to generate a valid JSON string sent to Gremlin Server.
- You cannot use bindings whose names collide with Gremlin reserved keywords (statically imported variables), such as `id`, `label` and `key` (see [https://github.com/jbmusso/gremlin-javascript/issues/23](issue#23)). This is a TinkerPop3 Gremlin Server limitation. Workarounds: `vid`, `eid`, `userId`, etc.
#### (String, Object) signature
```javascript
const client = Gremlin.createClient();
client.execute('g.v(vid)', { vid: 1 }, (err, results) => {
console.log(results[0]) // notice how results is always an array
});
```
#### (Object) signature
Expects an `Object` as first argument with a `gremlin` property holding a `String` and a `bindings` property holding an `Object` of bound parameters.
```javascript
const client = Gremlin.createClient();
const query = {
gremlin: 'g.V(vid)',
bindings: {
vid: 1
}
}
client.execute(query, (err, results) => {
console.log(results[0])
});
```
### Overriding low level settings on a per request basis
For advanced usage, for example if you wish to set the `op` or `processor` values for a given request only, you may wish to override the client level settings in the raw message sent to Gremlin Server:
```javascript
client.execute('g.v(1)', null, { args: { language: 'nashorn' }}, (err, results) => {
// Handle result
});
```
Basically, all you have to do is provide an Object as third parameter to any `client.stream()`, `client.execute()` or `client.streamMessage()` methods.
Because we're not sending any bound parameters (`bindings`) in this example, notice how the second argument **must** be set to `null` so the low level message object is not mistaken with bound arguments.
If you wish to also send bound parameters while overriding the low level message, you can do the following:
```javascript
client.execute('g.v(vid)', { vid: 1 }, { args: { language: 'nashorn' }}, (err, results) => {
// Handle err and results
});
```
Or in stream mode:
```javascript
client.stream('g.v(vid)', { vid: 1 }, { args: { language: 'nashorn' }})
.pipe(/* ... */);
```
### Gremlin.bindForClient()
Given a map of functions returning query `Object`s (`{ gremlin, bindings }`), returns a map of function promising execution of these queries with the given Gremlin client.
This function is especially useful when used with [gremlin-loader](https://github.com/jbmusso/gremlin-loader), a Webpack loader which imports functions from `.groovy` files as `Object<String, Functions>` where each functions returns query `Object`s that need to be executed with a client.
```javascript
import { bindForClient, createClient } from 'gremlin';
// A function returning a Gremlin query object { gremlin, bindings }
const getByName = (name) => ({
gremlin: 'g.V().has("name", name)',
bindings: { name }
});
const client = createClient();
const queries = bindForClient(client, { getByName });
// Then, within an async function:
const users = await queries.getByName('Alice');
```
### Using Gremlin-JavaScript syntax with Nashorn
Please see [/docs/UsingNashorn.md](Using Nashorn).
## Running the Examples
Start your own Gremlin Server with the default TinkerPop graph loaded by using `scripts: [scripts/generate-classic.groovy]` in your `gremlin-server.yaml` config file.
### Node.js
To run the command line example:
```shell
npm run examples:node
```
### Browser
Build library:
```shell
npm run build:umd
```
Start the example server (listens on port 3000):
```
npm run examples:browser
```
Open [http://localhost:3000/examples/gremlin.html](http://localhost:3000/examples/gremlin.html) for an example on how a list of six vertices is being populated as the vertices are being streamed down from Gremlin Server.
## To do list
* better error handling
* emit more client events
* reconnect WebSocket if connection is lost
* add option for secure WebSocket
* more tests
* performance optimization
## License
MIT(LICENSE)