1
1
import http from "http" ;
2
- import { existsSync as exists , readFileSync as read } from "fs" ;
2
+ import { createReadStream } from "fs" ;
3
+ import { createDeflate , createGzip , createBrotliCompress } from "zlib" ;
4
+ import accepts = require( "accepts" ) ;
5
+ import { pipeline } from "stream" ;
3
6
import path from "path" ;
4
7
import engine from "engine.io" ;
5
8
import { Client } from "./client" ;
@@ -16,14 +19,8 @@ import { CorsOptions } from "cors";
16
19
17
20
const debug = debugModule ( "socket.io:server" ) ;
18
21
19
- const clientVersion = require ( "socket.io-client/package.json" ) . version ;
20
-
21
- /**
22
- * Socket.IO client source.
23
- */
24
-
25
- let clientSource = undefined ;
26
- let clientSourceMap = undefined ;
22
+ const clientVersion = require ( "../package.json" ) . version ;
23
+ const dotMapRegex = / \. m a p / ;
27
24
28
25
type Transport = "polling" | "websocket" ;
29
26
@@ -175,6 +172,7 @@ export class Server extends EventEmitter {
175
172
private eio ;
176
173
private engine ;
177
174
private _path : string ;
175
+ private clientPathRegex : RegExp ;
178
176
179
177
/**
180
178
* @private
@@ -220,27 +218,6 @@ export class Server extends EventEmitter {
220
218
public serveClient ( v ?: boolean ) : Server | boolean {
221
219
if ( ! arguments . length ) return this . _serveClient ;
222
220
this . _serveClient = v ;
223
- const resolvePath = function ( file ) {
224
- const filepath = path . resolve ( __dirname , "./../../" , file ) ;
225
- if ( exists ( filepath ) ) {
226
- return filepath ;
227
- }
228
- return require . resolve ( file ) ;
229
- } ;
230
- if ( v && ! clientSource ) {
231
- clientSource = read (
232
- resolvePath ( "socket.io-client/dist/socket.io.js" ) ,
233
- "utf-8"
234
- ) ;
235
- try {
236
- clientSourceMap = read (
237
- resolvePath ( "socket.io-client/dist/socket.io.js.map" ) ,
238
- "utf-8"
239
- ) ;
240
- } catch ( err ) {
241
- debug ( "could not load sourcemap file" ) ;
242
- }
243
- }
244
221
return this ;
245
222
}
246
223
@@ -290,7 +267,13 @@ export class Server extends EventEmitter {
290
267
public path ( ) : string ;
291
268
public path ( v ?: string ) : Server | string {
292
269
if ( ! arguments . length ) return this . _path ;
270
+
293
271
this . _path = v . replace ( / \/ $ / , "" ) ;
272
+
273
+ const escapedPath = this . _path . replace ( / [ - \/ \\ ^ $ * + ? . ( ) | [ \] { } ] / g, "\\$&" ) ;
274
+ this . clientPathRegex = new RegExp (
275
+ "^" + escapedPath + "/socket\\.io(\\.min)?\\.js(\\.map)?$"
276
+ ) ;
294
277
return this ;
295
278
}
296
279
@@ -410,16 +393,12 @@ export class Server extends EventEmitter {
410
393
*/
411
394
private attachServe ( srv ) {
412
395
debug ( "attaching client serving req handler" ) ;
413
- const url = this . _path + "/socket.io.js" ;
414
- const urlMap = this . _path + "/socket.io.js.map" ;
396
+
415
397
const evs = srv . listeners ( "request" ) . slice ( 0 ) ;
416
- const self = this ;
417
398
srv . removeAllListeners ( "request" ) ;
418
- srv . on ( "request" , function ( req , res ) {
419
- if ( 0 === req . url . indexOf ( urlMap ) ) {
420
- self . serveMap ( req , res ) ;
421
- } else if ( 0 === req . url . indexOf ( url ) ) {
422
- self . serve ( req , res ) ;
399
+ srv . on ( "request" , ( req , res ) => {
400
+ if ( this . clientPathRegex . test ( req . url ) ) {
401
+ this . serve ( req , res ) ;
423
402
} else {
424
403
for ( let i = 0 ; i < evs . length ; i ++ ) {
425
404
evs [ i ] . call ( srv , req , res ) ;
@@ -429,62 +408,86 @@ export class Server extends EventEmitter {
429
408
}
430
409
431
410
/**
432
- * Handles a request serving `/socket.io.js`
411
+ * Handles a request serving of client source and map
433
412
*
434
413
* @param {http.IncomingMessage } req
435
414
* @param {http.ServerResponse } res
436
415
* @private
437
416
*/
438
417
private serve ( req : http . IncomingMessage , res : http . ServerResponse ) {
418
+ const filename = req . url . replace ( this . _path , "" ) ;
419
+ const isMap = dotMapRegex . test ( filename ) ;
420
+ const type = isMap ? "map" : "source" ;
421
+
439
422
// Per the standard, ETags must be quoted:
440
423
// https://tools.ietf.org/html/rfc7232#section-2.3
441
424
const expectedEtag = '"' + clientVersion + '"' ;
442
425
443
426
const etag = req . headers [ "if-none-match" ] ;
444
427
if ( etag ) {
445
428
if ( expectedEtag == etag ) {
446
- debug ( "serve client 304" ) ;
429
+ debug ( "serve client %s 304" , type ) ;
447
430
res . writeHead ( 304 ) ;
448
431
res . end ( ) ;
449
432
return ;
450
433
}
451
434
}
452
435
453
- debug ( "serve client source" ) ;
436
+ debug ( "serve client %s" , type ) ;
437
+
454
438
res . setHeader ( "Cache-Control" , "public, max-age=0" ) ;
455
- res . setHeader ( "Content-Type" , "application/javascript" ) ;
439
+ res . setHeader (
440
+ "Content-Type" ,
441
+ "application/" + ( isMap ? "json" : "javascript" )
442
+ ) ;
456
443
res . setHeader ( "ETag" , expectedEtag ) ;
457
- res . writeHead ( 200 ) ;
458
- res . end ( clientSource ) ;
444
+
445
+ if ( ! isMap ) {
446
+ res . setHeader ( "X-SourceMap" , filename . substring ( 1 ) + ".map" ) ;
447
+ }
448
+ Server . sendFile ( filename , req , res ) ;
459
449
}
460
450
461
451
/**
462
- * Handles a request serving `/socket.io.js.map`
463
- *
464
- * @param {http.IncomingMessage } req
465
- * @param {http.ServerResponse } res
452
+ * @param filename
453
+ * @param req
454
+ * @param res
466
455
* @private
467
456
*/
468
- private serveMap ( req : http . IncomingMessage , res : http . ServerResponse ) {
469
- // Per the standard, ETags must be quoted:
470
- // https://tools.ietf.org/html/rfc7232#section-2.3
471
- const expectedEtag = '"' + clientVersion + '"' ;
457
+ private static sendFile (
458
+ filename : string ,
459
+ req : http . IncomingMessage ,
460
+ res : http . ServerResponse
461
+ ) {
462
+ const readStream = createReadStream (
463
+ path . join ( __dirname , "../client-dist/" , filename )
464
+ ) ;
465
+ const encoding = accepts ( req ) . encodings ( [ "br" , "gzip" , "deflate" ] ) ;
472
466
473
- const etag = req . headers [ "if-none-match" ] ;
474
- if ( etag ) {
475
- if ( expectedEtag == etag ) {
476
- debug ( "serve client 304" ) ;
477
- res . writeHead ( 304 ) ;
467
+ const onError = err => {
468
+ if ( err ) {
478
469
res . end ( ) ;
479
- return ;
480
470
}
481
- }
471
+ } ;
482
472
483
- debug ( "serve client sourcemap" ) ;
484
- res . setHeader ( "Content-Type" , "application/json" ) ;
485
- res . setHeader ( "ETag" , expectedEtag ) ;
486
- res . writeHead ( 200 ) ;
487
- res . end ( clientSourceMap ) ;
473
+ switch ( encoding ) {
474
+ case "br" :
475
+ res . writeHead ( 200 , { "content-encoding" : "br" } ) ;
476
+ readStream . pipe ( createBrotliCompress ( ) ) . pipe ( res ) ;
477
+ pipeline ( readStream , createBrotliCompress ( ) , res , onError ) ;
478
+ break ;
479
+ case "gzip" :
480
+ res . writeHead ( 200 , { "content-encoding" : "gzip" } ) ;
481
+ pipeline ( readStream , createGzip ( ) , res , onError ) ;
482
+ break ;
483
+ case "deflate" :
484
+ res . writeHead ( 200 , { "content-encoding" : "deflate" } ) ;
485
+ pipeline ( readStream , createDeflate ( ) , res , onError ) ;
486
+ break ;
487
+ default :
488
+ res . writeHead ( 200 ) ;
489
+ pipeline ( readStream , res , onError ) ;
490
+ }
488
491
}
489
492
490
493
/**
0 commit comments