@@ -2,6 +2,8 @@ const express = require('express');
2
2
const { HTTP } = require ( "cloudevents" ) ;
3
3
const jsonata = require ( 'jsonata' ) ;
4
4
const fs = require ( 'node:fs' ) ;
5
+ const http = require ( 'http' ) ;
6
+ const https = require ( 'https' ) ;
5
7
const fsPromises = require ( 'node:fs' ) . promises ;
6
8
const { buffer} = require ( 'node:stream/consumers' ) ;
7
9
@@ -31,10 +33,18 @@ const {ZipkinExporter} = require('@opentelemetry/exporter-zipkin');
31
33
const { W3CTraceContextPropagator, CompositePropagator} = require ( "@opentelemetry/core" ) ;
32
34
const { B3InjectEncoding, B3Propagator} = require ( "@opentelemetry/propagator-b3" ) ;
33
35
34
- const port = process . env . PORT = process . env . PORT || 8080 ;
36
+ const httpPort = process . env . HTTP_PORT || 8080 ;
37
+ const httpsPort = process . env . HTTPS_PORT || 8443 ;
38
+ const httpsCertPath = process . env . HTTPS_CERT_PATH ;
39
+ const httpsKeyPath = process . env . HTTPS_KEY_PATH ;
40
+ const disableHTTPServer = process . env . DISABLE_HTTP_SERVER === 'true' ;
35
41
const k_sink = process . env . K_SINK || undefined ;
36
42
const jsonata_transform_file_name = process . env . JSONATA_TRANSFORM_FILE_NAME || undefined ;
37
43
44
+ if ( disableHTTPServer && ( ! httpsKeyPath || ! fs . existsSync ( httpsKeyPath ) || ! httpsCertPath || ! fs . existsSync ( httpsCertPath ) ) ) {
45
+ throw new Error ( `HTTP and HTTPS server are both disabled, disableHTTPServer='${ disableHTTPServer } ', httpsKeyPath='${ httpsKeyPath } ', httpsCertPath=${ httpsCertPath } ` ) ;
46
+ }
47
+
38
48
// Allow transforming the response received by the endpoint defined by K_SINK
39
49
const jsonata_response_transform_file_name = process . env . JSONATA_RESPONSE_TRANSFORM_FILE_NAME || undefined ;
40
50
@@ -250,7 +260,7 @@ app.post("/", async (req, res) => {
250
260
const k_sink_url = new URL ( k_sink )
251
261
const kSinkSendSpan = tracer . startSpan ( 'k_sink_send' , {
252
262
attributes : {
253
- [ ATTR_URL_SCHEME ] : k_sink_url . protocol . endsWith ( ':' ) ? k_sink_url . protocol . substring ( 0 , k_sink_url . protocol . length - 1 ) : k_sink_url . protocol ,
263
+ [ ATTR_URL_SCHEME ] : k_sink_url . protocol . endsWith ( ':' ) ? k_sink_url . protocol . substring ( 0 , k_sink_url . protocol . length - 1 ) : k_sink_url . protocol ,
254
264
[ ATTR_SERVER_ADDRESS ] : k_sink_url . hostname ,
255
265
[ ATTR_SERVER_PORT ] : k_sink_url . port ,
256
266
}
@@ -267,6 +277,7 @@ app.post("/", async (req, res) => {
267
277
headers : k_sink_request_headers ,
268
278
body : JSON . stringify ( transformed ) ,
269
279
redirect : 'error' ,
280
+ signal : req . signal ,
270
281
} )
271
282
kSinkSendSpan . setAttributes ( {
272
283
'http.status_code' : result . status ,
@@ -360,7 +371,7 @@ app.post("/", async (req, res) => {
360
371
} ) ;
361
372
362
373
// guessTransformedContentType tries to guess the transformed event content type.
363
- // 1. If the transformed event contains a special "contentype " field, it returns it.
374
+ // 1. If the transformed event contains a special "contenttype " field, it returns it.
364
375
// 2. Otherwise, it tries to find CloudEvents "specversion" attribute and, if it's present, returns
365
376
// the CloudEvent structured content type "application/cloudevents+json".
366
377
// 3. Lastly, it falls back to "application/json" if none of the above are specified.
@@ -384,19 +395,71 @@ app.get('/readyz', (req, res) => {
384
395
385
396
app . disable ( 'x-powered-by' ) ;
386
397
387
- const server = app . listen ( port , ( ) => {
388
- console . log ( `Jsonata server listening on port ${ port } ` )
389
- } )
398
+ let httpServer = null
399
+ let httpsServer = null
400
+
401
+ if ( ! disableHTTPServer ) {
402
+ httpServer = http . createServer ( app )
403
+ . listen ( httpPort , ( ) => {
404
+ console . log ( `Jsonata HTTP server listening on port ${ httpPort } ` )
405
+ } )
406
+ }
407
+
408
+ if ( httpsCertPath && httpsKeyPath ) {
409
+ const httpsServerOptions = {
410
+ cert : fs . readFileSync ( httpsCertPath ) ,
411
+ key : fs . readFileSync ( httpsKeyPath ) ,
412
+
413
+ // TLS Versions
414
+ minVersion : 'TLSv1.2' , // Minimum TLS version (avoid older, less secure protocols)
415
+ maxVersion : 'TLSv1.3' , // Maximum TLS version
416
+
417
+ // Cipher Suites
418
+ ciphers : [
419
+ 'ECDHE-ECDSA-AES128-GCM-SHA256' ,
420
+ 'ECDHE-RSA-AES128-GCM-SHA256' ,
421
+ 'ECDHE-ECDSA-AES256-GCM-SHA384' ,
422
+ 'ECDHE-RSA-AES256-GCM-SHA384' ,
423
+ 'ECDHE-ECDSA-CHACHA20-POLY1305' ,
424
+ 'ECDHE-RSA-CHACHA20-POLY1305' ,
425
+ 'DHE-RSA-AES128-GCM-SHA256' ,
426
+ 'DHE-RSA-AES256-GCM-SHA384'
427
+ ] . join ( ':' ) ,
428
+
429
+ // Attempt to use server cipher suite preference instead of clients
430
+ honorCipherOrder : true ,
431
+
432
+ // Additional security options
433
+ secureOptions :
434
+ require ( 'constants' ) . SSL_OP_NO_TLSv1 |
435
+ require ( 'constants' ) . SSL_OP_NO_TLSv1_1 |
436
+ require ( 'constants' ) . SSL_OP_NO_COMPRESSION ,
437
+ }
438
+
439
+ httpsServer = https . createServer ( httpsServerOptions , app )
440
+ . listen ( httpsPort , ( ) => {
441
+ console . log ( `Jsonata HTTPS server listening on port ${ httpsPort } ` )
442
+ } )
443
+ }
390
444
391
445
process . on ( 'SIGINT' , shutDown ) ;
392
446
process . on ( 'SIGTERM' , shutDownNow ) ;
393
447
394
448
let connections = [ ] ;
395
449
396
- server . on ( 'connection' , connection => {
397
- connections . push ( connection ) ;
398
- connection . on ( 'close' , ( ) => connections = connections . filter ( curr => curr !== connection ) ) ;
399
- } ) ;
450
+ if ( httpServer ) {
451
+ httpServer . on ( 'connection' , connection => {
452
+ connections . push ( connection ) ;
453
+ connection . on ( 'close' , ( ) => connections = connections . filter ( curr => curr !== connection ) ) ;
454
+ } ) ;
455
+ }
456
+
457
+ if ( httpsServer ) {
458
+ httpsServer . on ( 'connection' , connection => {
459
+ connections . push ( connection ) ;
460
+ connection . on ( 'close' , ( ) => connections = connections . filter ( curr => curr !== connection ) ) ;
461
+ } ) ;
462
+ }
400
463
401
464
function shutDown ( ) {
402
465
console . log ( 'Received interrupt signal, shutting down gracefully' ) ;
@@ -413,14 +476,30 @@ function shutDown() {
413
476
function shutDownNow ( ) {
414
477
console . log ( 'Shutting down gracefully' ) ;
415
478
416
- server . close ( ( ) => {
417
- console . log ( 'Closed out remaining connections' ) ;
479
+ const closePromises = [ ]
480
+ if ( httpServer ) {
481
+ closePromises . push ( new Promise ( ( resolve , _ ) => {
482
+ httpServer . close ( ( ) => {
483
+ console . log ( 'Closed out remaining HTTP connections' ) ;
484
+ resolve ( )
485
+ } ) ;
486
+ } ) )
487
+ }
488
+ if ( httpsServer ) {
489
+ closePromises . push ( new Promise ( ( resolve , _ ) => {
490
+ httpsServer . close ( ( ) => {
491
+ console . log ( 'Closed out remaining HTTPS connections' ) ;
492
+ resolve ( )
493
+ } ) ;
494
+ } ) )
495
+ }
418
496
497
+ Promise . all ( closePromises ) . then ( ( ) => {
419
498
console . log ( 'Shutting down tracing...' ) ;
420
499
batchSpanProcessor . shutdown ( ) . then ( ( ) => {
421
500
process . exit ( 0 ) ;
422
501
} ) ;
423
- } ) ;
502
+ } )
424
503
425
504
setTimeout ( ( ) => {
426
505
console . error ( 'Could not close connections in time, forcefully shutting down' ) ;
0 commit comments