tlsd 2.11.1 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tlsd.js +134 -201
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlsd",
3
- "version": "2.11.1",
3
+ "version": "2.12.0",
4
4
  "description": "A server for web app prototyping with HTTPS and Websockets",
5
5
  "main": "tlsd.js",
6
6
  "bin": {
package/tlsd.js CHANGED
@@ -8,6 +8,11 @@ const connect = require( "connect" );
8
8
  const websocket = require( "websocket" );
9
9
  const serveStatic = require( "serve-static" )
10
10
 
11
+ const body_parser = require( "body-parser" ).json( { limit: "10mb" } );
12
+ const compression = require( "compression" )();
13
+ const cors = require( "cors" )();
14
+ const queryString = require( "querystring" );
15
+
11
16
  require( "sleepless" ).globalize();
12
17
 
13
18
  const L = log5.mkLog( "TLSD: " );
@@ -31,7 +36,7 @@ function usage() {
31
36
  }
32
37
 
33
38
  let seq = 0;
34
- const next_seq = function() {
39
+ function next_seq() {
35
40
  seq += 1;
36
41
  return seq;
37
42
  }
@@ -39,7 +44,11 @@ const next_seq = function() {
39
44
 
40
45
  // Handles incoming RPC msgs.
41
46
  // Tries to load the rpc handler module from root and if successful, passes the msg to it
42
- const rpc_handler = function( root, msg, xport, _okay, _fail ) {
47
+ // xport:
48
+ // { type: "WS", transport: { ... } }
49
+ // { type: "GET", transport: { req, res } }
50
+ // { type: "POST", transport: { req, res } }
51
+ function rpc_handler( root, msg, transport, _okay, _fail ) {
43
52
 
44
53
  let ll = toInt( msg.log_level );
45
54
  if( ll > 0 ) {
@@ -55,15 +64,15 @@ const rpc_handler = function( root, msg, xport, _okay, _fail ) {
55
64
  }
56
65
 
57
66
  V( "RPC " + o2j( msg ).abbr( 70 ) );
58
- D( "----------->>> "+xport+" "+o2j( msg, null, 2 ) );
67
+ D( "----------->>> "+transport.type+" "+o2j( msg, null, 2 ) );
59
68
 
60
69
  const okay = data => {
61
- D( "<<<=========== "+xport+" OKAY "+o2j( data, null, 2 ) );
70
+ D( "<<<=========== "+transport.type+" OKAY "+o2j( data, null, 2 ) );
62
71
  _okay( data );
63
72
  };
64
73
 
65
74
  const fail = error => {
66
- D( "<<<*********** "+xport+" ERROR "+o2j( error, null, 2 ) );
75
+ D( "<<<*********** "+transport.type+" ERROR "+o2j( error, null, 2 ) );
67
76
  _fail( error );
68
77
  };
69
78
 
@@ -75,7 +84,7 @@ const rpc_handler = function( root, msg, xport, _okay, _fail ) {
75
84
  try {
76
85
  const mod = require( root + "/rpc" );
77
86
  try {
78
- mod( msg, okay, ouch );
87
+ mod( msg, okay, ouch, transport );
79
88
  } catch( err ) {
80
89
  E( (err instanceof Error ) ? err.stack : err );
81
90
  ouch( "RPC handler exception" );
@@ -89,8 +98,8 @@ const rpc_handler = function( root, msg, xport, _okay, _fail ) {
89
98
 
90
99
  // Glue function to call rpc handler module and then return response
91
100
  // via the websockets msg object
92
- const ws_msg_handler = function( root, msg ) {
93
- rpc_handler( root, msg.msg, "WS", data => {
101
+ function ws_msg_handler( root, msg, connection ) {
102
+ rpc_handler( root, msg.msg, { type: "WS", transport: connection }, data => {
94
103
  msg.reply( data );
95
104
  }, error => {
96
105
  msg.error( { error } );
@@ -99,115 +108,113 @@ const ws_msg_handler = function( root, msg ) {
99
108
  };
100
109
 
101
110
 
102
- // Return a handler function that implements default (basic) functionality.
103
- // XXX This has got to be inefficient at best, as it's creating a
104
- // connect app on every request. No idea how much of this gets optimized
105
- // out by V8.
106
- const basic_handler = function( root ) {
107
-
108
- const app = connect();
109
-
110
- app.use( require( "body-parser" ).json( { limit: "10mb" } ) );
111
- app.use( require( "compression" )() );
112
- app.use( require( "cors" )() ); // allow requests from other domains
113
-
114
- // populate req.query
115
- app.use( function( req, res, next ) {
116
- req.query = require( "querystring" ).parse( req._parsedUrl.query );
117
- next();
118
- } );
119
-
120
- // logger
121
- app.use( function( req, res, next ) {
122
- const host = req.headers[ "host" ];
123
- let { method, url, query, body } = req;
124
- url = url.split( "?" ).shift();
125
- I( host + ": " + method + " " + o2j(url) + " " + o2j( query, null, 2 ) + "/" + o2j( body, null, 2 ) );
126
- next();
127
- } );
128
-
129
- // Intercept and service POST requests for "/rpc"
130
- app.use( function( req, res, next ) {
131
-
132
- let { method, url, query, body } = req;
133
-
134
- url = url.split( "?" ).shift();
135
-
136
- // if not an rpc request, pass on to static
137
- if( url != "/rpc" && url != "/rpc/" ) {
138
- next();
139
- return
140
- }
141
-
142
- // Only allow POSTS to /rpc in tlsd mode.
143
- // In dev mode, GET is allowed as well with query args.
144
- let input = body;
145
- if( method != "POST" ) {
146
- if( ! ( method == "GET" && dev_mode ) ) {
147
- W( "GET /rpc is disallowed in tlsd mode" );
148
- res.writeHead( 405 ); // Method Not Allowed
149
- res.end();
150
- return;
151
- }
152
- input = query;
153
- }
154
-
155
- // Set up callbacks
156
- const done = ( error, data ) => {
157
- res.writeHead( 200, {
158
- "Content-Type": "application/json",
159
- "Cache-Control": "no-store",
160
- });
161
- res.write( o2j( data ) );
162
- res.end();
163
- };
164
- const okay = ( data ) => { done( null, data ); };
165
- // XXX wrong
166
- const fail = ( error, body ) => { done( error, body ); };
111
+ // -----------------------
167
112
 
168
- // Summon the rpc handler for the domain root.
169
- rpc_handler( root, input, "REST", okay, fail );
170
113
 
171
- } );
114
+ // Handler that populates the req.query var with what's in query args
115
+ function populate_query( req, res, next ) {
116
+ req.query = queryString.parse( req._parsedUrl.query );
117
+ next();
118
+ }
172
119
 
120
+ // Simple logging handler
121
+ function logger( req, res, next ) {
122
+ const host = req.headers[ "host" ];
123
+ let { method, url, query, body } = req;
124
+ url = url.split( "?" ).shift();
125
+ I( host + ": " + method + " " + o2j( url ) + " " + o2j( query, null, 2 ) + "/" + o2j( body, null, 2 ) );
126
+ next();
127
+ }
173
128
 
174
- // Serve my (tlsd's) own static files
175
- // This is primarily so that GET /rpc/rpc.js can return the client-side code for
176
- // sending WS RPC messages (similar to how socket.io works) and providing the
177
- // browser hot-reload feature (which requires WS RPC)
178
- app.use( serveStatic( __dirname + "/rpc_static" ) );
129
+ // Creates and returns a handler function that intercepts and services
130
+ // POST requests to "/rpc" endpoint.
131
+ function rpc_post( root ) {
132
+ return function( req, res, next ) {
133
+ let { method, url, query, body } = req;
179
134
 
135
+ url = url.split( "?" ).shift();
136
+ if( url != "/rpc" && url != "/rpc/" ) {
137
+ next();
138
+ return
139
+ }
180
140
 
181
- // Serve static files for the domain
182
- app.use( serveStatic( root + "/static" ) );
141
+ // Only allow POSTS in TLS mode.
142
+ // In dev mode, GET is allowed as well with query args.
143
+ let input = body;
144
+ if( method != "POST" ) {
145
+ if( ! ( method == "GET" && dev_mode ) ) {
146
+ W( "GET /rpc is disallowed in TLS mode" );
147
+ res.writeHead( 405 ); // Method Not Allowed
148
+ res.end();
149
+ return;
150
+ }
151
+ input = query;
152
+ }
183
153
 
154
+ // Set up callbacks
155
+ const done = ( error, data ) => {
156
+ res.writeHead( 200, {
157
+ "Content-Type": "application/json",
158
+ "Cache-Control": "no-store",
159
+ });
160
+ res.write( o2j( data ) );
161
+ res.end();
162
+ };
163
+ const okay = ( data ) => { done( null, data ); };
164
+ // XXX wrong
165
+ const fail = ( error, body ) => { done( error, body ); };
166
+
167
+ // Summon the rpc handler for the domain root.
168
+ rpc_handler( root, input, { type, method, transport: { req, res, } } , okay, fail );
169
+ };
170
+ }
184
171
 
185
- // finally, if serveStatic can't service the request,
186
- // look for a 404/ dir and redirect to that if present
187
- app.use( function( req, res, next ) {
188
- // I can't just return a redirect here because if the /404 doesn't exist,
189
- // I will just go into a redirect loop, so I have to test to see if the
190
- // dir exists, and then if so, redirect to it, otherwise, just return 404.
191
- let { url, } = req;
192
- let p = root + "/static/404";
193
- is_dir( p, so => {
194
- if( so ) {
195
- let p404 = "/404/?original_path=" + encodeURIComponent( url );
196
- res.writeHead( 302, { "Location": p404, } );
172
+ // Handler for tlsd's own static files.
173
+ // This is so that "GET /rpc/rpc.js" can return the client-side code for
174
+ // sending WS RPC messages (similar to how socket.io works).
175
+ const rpc_static = serveStatic( __dirname + "/rpc_static" );
176
+
177
+
178
+ // Creates and returns a handler that checks for "/404" at root.
179
+ // If found, it redirects to it, otherwise it responds with 404.
180
+ function not_found( root ) {
181
+ return function( req, res, next ) {
182
+ // Can't just do a redirect here because if /404 doesn't exist, it will
183
+ // just go into a redirect loop. So test to see if the dir exists first.
184
+ let path = root + "/static/404";
185
+ is_dir( path, so => {
186
+ if( so ) {
187
+ let p404 = "/404/?original_path=" + encodeURIComponent( req.url );
188
+ res.writeHead( 302, { "Location": p404, } ); // redirect to path
197
189
  } else {
198
- res.writeHead( 404 );
190
+ res.writeHead( 404 );
199
191
  }
200
- res.end();
201
- } );
202
- } );
192
+ res.end();
193
+ } );
194
+ }
195
+ }
203
196
 
197
+ // Create and return a connect app/function that implements
198
+ // default (basic) functionality.
199
+ function basic_handler( root ) {
200
+ const app = connect();
201
+ app.use( body_parser );
202
+ app.use( compression );
203
+ app.use( cors ); // allow requests from other domains
204
+ app.use( populate_query );
205
+ app.use( logger );
206
+ app.use( rpc_post( root ) );
207
+ app.use( rpc_static );
208
+ app.use( serveStatic( root + "/static" ) ); // static files for domain
209
+ app.use( not_found( root ) );
204
210
  return app;
205
-
206
211
  }
207
212
 
208
213
 
214
+ const cached_basic_handlers = {};
215
+
209
216
  // Handle REST calls (as opposed to websocket messages)
210
- const rest_handler = function( root, req, rsp ) {
217
+ function rest_handler( root, req, rsp ) {
211
218
 
212
219
  I( req.headers[ "host" ] + ": " + req.method + " " + req.url );
213
220
  D( "rest_handler root: " + root );
@@ -231,21 +238,31 @@ const rest_handler = function( root, req, rsp ) {
231
238
  D( "Using custom REST handler loaded from "+root );
232
239
  call( handler );
233
240
  } catch( err ) {
234
- // custom handler load attempt threw an exception
235
- const handler = basic_handler( root );
241
+ // attempt to load custom handler threw an exception
242
+
243
+ let handler = cached_basic_handlers[ root ];
244
+ if( ! handler ) {
245
+ handler = basic_handler( root );
246
+ cached_basic_handlers[ root ] = handler;
247
+ }
248
+
236
249
  D( "Using basic REST handler" );
237
250
  call( handler );
238
251
  }
239
252
  };
240
253
 
241
254
 
255
+ // -----------------------
256
+
257
+
242
258
  // tracked websocket connections
243
- const ws_connections = {};
259
+ //const ws_connections = {};
244
260
 
245
261
 
246
- const ws_attach = function( httpServer, msg_handler ) {
262
+ // Associate a websocket message handler (msg_handler) to a webserver instance (server)
263
+ function ws_attach( server, msg_handler ) {
247
264
 
248
- const wsd = new websocket.server( { httpServer, autoAcceptConnections: false, } );
265
+ const wsd = new websocket.server( { httpServer: server, autoAcceptConnections: false, } );
249
266
 
250
267
  wsd.on( "request", function( wsreq ) {
251
268
  V( "WS: connection request from "+wsreq.remoteAddress+" "+wsreq.resource )
@@ -254,7 +271,7 @@ const ws_attach = function( httpServer, msg_handler ) {
254
271
 
255
272
  const socket = wsreq.accept( null, wsreq.origin );
256
273
 
257
- const name = "ws-client-"+next_seq(); // XXX just use the websocket id
274
+ const name = "ws-conn-"+next_seq(); // XXX just use the websocket id
258
275
 
259
276
  // send msg to connected client
260
277
  const send = function( msg , cb ) {
@@ -273,7 +290,7 @@ const ws_attach = function( httpServer, msg_handler ) {
273
290
 
274
291
  socket.on("close", function() {
275
292
  D( "WS: disconnect" );
276
- delete ws_connections[ name ]; // remove from tracked connections
293
+ //delete ws_connections[ name ]; // remove from tracked connections
277
294
  });
278
295
 
279
296
  // incoming msgs from client come through here
@@ -305,7 +322,7 @@ const ws_attach = function( httpServer, msg_handler ) {
305
322
 
306
323
  D( "WS: connected: "+name );
307
324
 
308
- ws_connections[ name ] = conn; // add to tracked connections
325
+ //ws_connections[ name ] = conn; // add to tracked connections
309
326
 
310
327
  } );
311
328
 
@@ -316,84 +333,6 @@ const ws_attach = function( httpServer, msg_handler ) {
316
333
  };
317
334
 
318
335
 
319
-
320
- /*
321
- const build_sass = function( path ) {
322
- const sass = require( "sass" );
323
- const result = sass.compile( path );
324
- fs.writeFileSync( path + ".css", result.css, "utf8" );
325
- };
326
-
327
- const auto_builders = [
328
- [ /\.(sass|scss)$/, build_sass ],
329
- ];
330
-
331
- // Do auto-build processes (dev mode only)
332
- const auto_build = function( paths ) {
333
-
334
- for( let path of paths ) {
335
- for( const bldr of auto_builders ) {
336
- if( bldr[ 0 ].test( path ) ) {
337
- bldr[ 1 ]( path );
338
- }
339
- }
340
-
341
- }
342
-
343
- };
344
-
345
-
346
- // Engage the filesystem watcher (dev mode only)
347
- const enable_fs_watch = function( root ) {
348
-
349
- let paths_noted = {};
350
-
351
- let hot_reload_tid = null;
352
- let hot_reload_count = 0;
353
-
354
- function hot_reload_tick() {
355
- hot_reload_count -= 1; // reduce count by 1
356
- if( hot_reload_count <= 0 ) {
357
- // counter reached 0
358
- clearInterval( hot_reload_tid ); // turn off the ticker
359
- hot_reload_tid = null;
360
-
361
- auto_build( Object.keys( paths_noted ) );
362
- paths_noted = {};
363
-
364
- // send RELOAD msg to all client that have an active ws connection
365
- for( let conn of Object.values( ws_connections ) ) {
366
- conn.send( { msg: "RELOAD" } ); // send RELOAD msg to browser
367
- }
368
- }
369
- }
370
-
371
- function note( evt, path ) {
372
- // A change of some sort was seen
373
- // Hundreds of these can happen very rapidly, so the timer stuff
374
- // above is used to delay sending the RELOAD command until
375
- // a second or so has passed without any more calls to this function
376
-
377
- paths_noted[ path ] = evt; // XXX
378
- //D( "noted fs change: [" + evt + "] " + path );
379
-
380
- hot_reload_count = 5;
381
- if( hot_reload_tid === null ) {
382
- // ticker isn't running, so start it
383
- hot_reload_tid = setInterval( hot_reload_tick, 200 );
384
- }
385
-
386
- };
387
-
388
- // tell OS to start watching for any changes in root
389
- fs.watch( root, { recursive: true, }, function( evt, path ) {
390
- note( evt, root + "/" + path );
391
- } );
392
-
393
- };
394
- */
395
-
396
-
397
336
  // -----------------------
398
337
 
399
338
  const argv = process.argv;
@@ -415,14 +354,13 @@ if( argv.length == 2 ) {
415
354
  maintainerEmail: MAINTAINER_EMAIL,
416
355
  configDir: "./greenlock.d",
417
356
  cluster: false,
418
- //notify
419
357
  } ).ready( glx => {
420
358
 
421
- var httpd = glx.httpsServer();
359
+ var server = glx.httpsServer();
422
360
 
423
- ws_attach( httpd, ( msg, conn, domain ) => {
361
+ ws_attach( server, ( msg, connection, domain ) => {
424
362
  const root = path.resolve( DOMAINS_ROOT + "/" + domain );
425
- ws_msg_handler( root, msg );
363
+ ws_msg_handler( root, msg, connection );
426
364
  } );
427
365
 
428
366
  glx.serveApp( ( req, res ) => {
@@ -454,22 +392,17 @@ if( argv.length == 5 ) {
454
392
  V( "SITE_ROOT: " + SITE_ROOT );
455
393
  V( "PORT: " + PORT );
456
394
 
457
- const httpd = http.createServer( ( req, res ) => {
395
+ const server = http.createServer( ( req, res ) => {
458
396
  rest_handler( SITE_ROOT, req, res );
459
397
  } );
460
398
 
461
- // XXX call ws_attach *before* calling listen() ?
462
- httpd.listen( toInt( PORT ), () => {
463
-
464
- ws_attach( httpd, ( msg, conn, domain ) => {
465
- ws_msg_handler( SITE_ROOT, msg );
466
- } );
399
+ ws_attach( server, ( msg, conn, domain ) => {
400
+ ws_msg_handler( SITE_ROOT, msg );
401
+ } );
467
402
 
468
- // enable the file system watch, which handles hot-reload and auto-build
469
- // enable_fs_watch( SITE_ROOT );
403
+ server.listen( toInt( PORT ), () => {
470
404
 
471
405
  I( "Listening on " + PORT + " & serving from " + SITE_ROOT );
472
-
473
406
  } );
474
407
 
475
408
  } else {