@@ -8,9 +8,10 @@ import highland from 'highland';
8
8
9
9
import WebSocketGremlinConnection from './WebSocketGremlinConnection' ;
10
10
import MessageStream from './MessageStream' ;
11
- import executeHandler from './executeHandler' ;
12
11
import * as Utils from './utils' ;
13
12
13
+ import Rx from 'rx' ;
14
+
14
15
15
16
class GremlinClient extends EventEmitter {
16
17
constructor ( port = 8182 , host = 'localhost' , options = { } ) {
@@ -27,7 +28,6 @@ class GremlinClient extends EventEmitter {
27
28
op : 'eval' ,
28
29
processor : '' ,
29
30
accept : 'application/json' ,
30
- executeHandler,
31
31
...options ,
32
32
path : path . length && ! path . startsWith ( '/' ) ? `/${ path } ` : path
33
33
}
@@ -43,22 +43,59 @@ class GremlinClient extends EventEmitter {
43
43
44
44
this . commands = { } ;
45
45
46
- this . connection = this . createConnection ( {
46
+ const connection = this . createConnection ( {
47
47
port,
48
48
host,
49
49
path : this . options . path
50
50
} ) ;
51
+
52
+ this . commands$ = new Rx . Subject ( ) ;
53
+ this . commands$ . subscribe ( ( command ) => {
54
+ const { message : { requestId } } = command ;
55
+ this . commands [ requestId ] = command
56
+ } ) ;
57
+
58
+ this . registerConnection ( connection ) ;
51
59
}
52
60
53
61
createConnection ( { port, host, path } ) {
54
- const connection = new WebSocketGremlinConnection ( { port, host, path } ) ;
62
+ return new WebSocketGremlinConnection ( { port, host, path } ) ;
63
+ }
64
+
65
+ registerConnection ( connection ) {
66
+ this . connection = connection ;
67
+
68
+ const open$ = Rx . Observable . fromEvent ( connection , 'open' ) ;
69
+ const error$ = Rx . Observable . fromEvent ( connection , 'error' ) ;
70
+ const incomingMessages$ = Rx . Observable . fromEvent ( connection , 'message' )
71
+ . map ( ( { data } ) => {
72
+ const buffer = new Buffer ( data , 'binary' ) ;
73
+ const rawMessage = JSON . parse ( buffer . toString ( 'utf-8' ) ) ;
74
+
75
+ return rawMessage ;
76
+ } ) ;
77
+ const close$ = Rx . Observable . fromEvent ( connection , 'close' ) ;
78
+
79
+ const canSend$ = Rx . Observable . merge (
80
+ open$ . map ( true ) ,
81
+ error$ . map ( false ) ,
82
+ close$ . map ( false )
83
+ )
84
+
85
+ open$ . subscribe ( ( connection ) => this . onConnectionOpen ( ) ) ;
86
+ error$ . subscribe ( ( error ) => this . handleError ( error ) ) ;
55
87
56
- connection . on ( 'open' , ( ) => this . onConnectionOpen ( ) ) ;
57
- connection . on ( 'error' , ( error ) => this . handleError ( error ) ) ;
58
- connection . on ( 'message' , ( message ) => this . handleProtocolMessage ( message ) ) ;
59
- connection . on ( 'close' , ( event ) => this . handleDisconnection ( event ) )
60
88
61
- return connection ;
89
+ this . incomingMessages$ = incomingMessages$ ;
90
+
91
+ close$ . subscribe ( ( event ) => this . handleDisconnection ( event ) ) ;
92
+
93
+ const outgoingMessages$ = this . commands$
94
+ . map ( ( { message } ) => message )
95
+ . pausableBuffered ( canSend$ ) ;
96
+
97
+ outgoingMessages$
98
+ . subscribe ( ( message ) => this . sendMessage ( message ) ) ;
62
99
}
63
100
64
101
handleError ( err ) {
@@ -73,35 +110,32 @@ class GremlinClient extends EventEmitter {
73
110
* @param {MessageEvent } event
74
111
*/
75
112
handleProtocolMessage ( message ) {
76
- const { data } = message ;
77
- const buffer = new Buffer ( data , 'binary' ) ;
78
- const rawMessage = JSON . parse ( buffer . toString ( 'utf-8' ) ) ;
79
113
const {
80
114
requestId,
81
115
status : {
82
116
code : statusCode ,
83
117
message : statusMessage
84
118
}
85
- } = rawMessage ;
119
+ } = message ;
86
120
87
- const { messageStream } = this . commands [ requestId ] ;
121
+ const { observable } = this . commands [ requestId ] ;
88
122
89
123
switch ( statusCode ) {
90
124
case 200 : // SUCCESS
91
125
delete this . commands [ requestId ] ; // TODO: optimize performance
92
- messageStream . push ( rawMessage ) ;
93
- messageStream . push ( null ) ;
126
+ observable . onNext ( message ) ;
127
+ observable . push ( null ) ;
94
128
break ;
95
129
case 204 : // NO_CONTENT
96
130
delete this . commands [ requestId ] ;
97
- messageStream . push ( null ) ;
131
+ observable . push ( null ) ;
98
132
break ;
99
133
case 206 : // PARTIAL_CONTENT
100
- messageStream . push ( rawMessage ) ;
134
+ observable . push ( message ) ;
101
135
break ;
102
136
default :
103
137
delete this . commands [ requestId ] ;
104
- messageStream . emit ( 'error' , new Error ( statusMessage + ' (Error ' + statusCode + ')' ) ) ;
138
+ observable . emit ( 'error' , new Error ( statusMessage + ' (Error ' + statusCode + ')' ) ) ;
105
139
break ;
106
140
}
107
141
}
@@ -113,8 +147,6 @@ class GremlinClient extends EventEmitter {
113
147
onConnectionOpen ( ) {
114
148
this . connected = true ;
115
149
this . emit ( 'connect' ) ;
116
-
117
- this . executeQueue ( ) ;
118
150
} ;
119
151
120
152
/**
@@ -127,17 +159,6 @@ class GremlinClient extends EventEmitter {
127
159
} ) ;
128
160
} ;
129
161
130
- /**
131
- * Process the current command queue, sending commands to Gremlin Server
132
- * (First In, First Out).
133
- */
134
- executeQueue ( ) {
135
- while ( this . queue . length > 0 ) {
136
- let { message } = this . queue . shift ( ) ;
137
- this . sendMessage ( message ) ;
138
- }
139
- } ;
140
-
141
162
/**
142
163
* @param {Object } reason
143
164
*/
@@ -228,14 +249,13 @@ class GremlinClient extends EventEmitter {
228
249
message = { } ;
229
250
}
230
251
231
- const messageStream = this . messageStream ( script , bindings , message ) ;
232
-
233
- // TO CHECK: errors handling could be improved
234
- // See https://groups.google.com/d/msg/nodejs/lJYT9hZxFu0/L59CFbqWGyYJ
235
- // for an example using domains
236
- const { executeHandler } = this . options ;
237
-
238
- executeHandler ( messageStream , callback ) ;
252
+ this . observable ( script , bindings , message )
253
+ . flatMap ( ( { result : { data } } ) => data )
254
+ . toArray ( )
255
+ . subscribe (
256
+ ( results ) => callback ( null , results ) ,
257
+ ( err ) => callback ( err )
258
+ )
239
259
}
240
260
241
261
/**
@@ -294,33 +314,57 @@ class GremlinClient extends EventEmitter {
294
314
messageStream : stream
295
315
} ;
296
316
297
- this . sendCommand ( command ) ; //todo improve for streams
317
+ this . commands$ . onNext ( command ) ;
298
318
299
319
return stream ;
300
320
} ;
301
321
302
- /**
303
- * Send a command to Gremlin Server, or add it to queue if the connection
304
- * is not established.
305
- *
306
- * @param {Object } command
307
- */
308
- sendCommand ( command ) {
309
- const {
310
- message,
311
- message : {
312
- requestId
313
- }
314
- } = command ;
322
+ observable ( script , bindings , rawMessage ) {
323
+ const command = {
324
+ message : this . buildMessage ( script , bindings , rawMessage ) ,
325
+ }
315
326
316
- this . commands [ requestId ] = command ;
327
+ this . commands$ . onNext ( command ) ;
317
328
318
- if ( this . connected ) {
319
- this . sendMessage ( message ) ;
320
- } else {
321
- this . queue . push ( command ) ;
322
- }
323
- } ;
329
+ const commandMessages$ = this . incomingMessages$
330
+ . filter ( ( { requestId } ) => requestId === command . message . requestId ) ;
331
+
332
+ const successMessage$ = commandMessages$
333
+ . filter ( ( { status : { code } } ) => code === 200 ) ;
334
+
335
+ const continuationMessages$ = commandMessages$
336
+ . filter ( ( { status : { code } } ) => code === 206 ) ;
337
+
338
+ const noContentMessage$ = commandMessages$
339
+ . filter ( ( { status : { code } } ) => code === 204 )
340
+ // Rewrite these in order to ensure the callback is always fired with an
341
+ // Empty Array rather than a null value.
342
+ // Mutating is perfectly fine here.
343
+ . map ( ( message ) => {
344
+ message . result . data = [ ]
345
+ return message ;
346
+ } ) ;
347
+
348
+ const terminationMessages$ = Rx . Observable . merge (
349
+ successMessage$ , noContentMessage$
350
+ ) ;
351
+
352
+ const errorMessages$ = commandMessages$
353
+ . filter ( ( { status : { code } } ) => [ 200 , 204 , 206 ] . indexOf ( code ) === - 1 )
354
+ . flatMap ( ( { status : { code, message } } ) => {
355
+ return Rx . Observable . throw ( new Error ( message + ' (Error ' + code + ')' ) )
356
+ } ) ;
357
+
358
+ const results$ = Rx . Observable . merge (
359
+ successMessage$ ,
360
+ continuationMessages$ ,
361
+ noContentMessage$ ,
362
+ errorMessages$
363
+ )
364
+ . takeUntil ( terminationMessages$ ) ;
365
+
366
+ return results$ ;
367
+ }
324
368
}
325
369
326
370
export default GremlinClient ;
0 commit comments