3
3
const debug = require ( 'debug' )
4
4
const log = debug ( 'ipfs:http-gateway' )
5
5
log . error = debug ( 'ipfs:http-gateway:error' )
6
- const pull = require ( 'pull-stream' )
7
- const pushable = require ( 'pull-pushable' )
8
- const toStream = require ( 'pull-stream-to-stream' )
6
+
9
7
const fileType = require ( 'file-type' )
10
8
const mime = require ( 'mime-types' )
11
9
const { PassThrough } = require ( 'readable-stream' )
12
10
const Boom = require ( 'boom' )
11
+ const peek = require ( 'buffer-peek-stream' )
13
12
14
13
const { resolver } = require ( 'ipfs-http-response' )
15
14
const PathUtils = require ( '../utils/path' )
@@ -30,6 +29,20 @@ function detectContentType (ref, chunk) {
30
29
return mime . contentType ( mimeType )
31
30
}
32
31
32
+ // Enable streaming of compressed payload
33
+ // https://github.com/hapijs/hapi/issues/3599
34
+ class ResponseStream extends PassThrough {
35
+ _read ( size ) {
36
+ super . _read ( size )
37
+ if ( this . _compressor ) {
38
+ this . _compressor . flush ( )
39
+ }
40
+ }
41
+ setCompressor ( compressor ) {
42
+ this . _compressor = compressor
43
+ }
44
+ }
45
+
33
46
module . exports = {
34
47
checkCID ( request , h ) {
35
48
if ( ! request . params . cid ) {
@@ -85,58 +98,46 @@ module.exports = {
85
98
return h . redirect ( PathUtils . removeTrailingSlash ( ref ) ) . permanent ( true )
86
99
}
87
100
88
- return new Promise ( ( resolve , reject ) => {
89
- let pusher
90
- let started = false
91
-
92
- pull (
93
- ipfs . catPullStream ( data . cid ) ,
94
- pull . drain (
95
- chunk => {
96
- if ( ! started ) {
97
- started = true
98
- pusher = pushable ( )
99
- const res = h . response ( toStream . source ( pusher ) . pipe ( new PassThrough ( ) ) )
100
-
101
- // Etag maps directly to an identifier for a specific version of a resource
102
- res . header ( 'Etag' , `"${ data . cid } "` )
101
+ const rawStream = ipfs . catReadableStream ( data . cid )
102
+ const responseStream = new ResponseStream ( )
103
+
104
+ // Pass-through Content-Type sniffing over initial bytes
105
+ const contentType = await new Promise ( ( resolve , reject ) => {
106
+ try {
107
+ const peekBytes = fileType . minimumBytes
108
+ peek ( rawStream , peekBytes , ( err , streamHead , outputStream ) => {
109
+ if ( err ) {
110
+ log . error ( err )
111
+ return reject ( err )
112
+ }
113
+ outputStream . pipe ( responseStream )
114
+ resolve ( detectContentType ( ref , streamHead ) )
115
+ } )
116
+ } catch ( err ) {
117
+ log . error ( err )
118
+ reject ( err )
119
+ }
120
+ } )
103
121
104
- // Set headers specific to the immutable namespace
105
- if ( ref . startsWith ( '/ipfs/' ) ) {
106
- res . header ( 'Cache-Control' , 'public, max-age=29030400, immutable' )
107
- }
122
+ const res = h . response ( responseStream )
108
123
109
- const contentType = detectContentType ( ref , chunk )
124
+ // Etag maps directly to an identifier for a specific version of a resource
125
+ res . header ( 'Etag' , `"${ data . cid } "` )
110
126
111
- log ( 'ref ' , ref )
112
- log ( 'mime-type ' , contentType )
127
+ // Set headers specific to the immutable namespace
128
+ if ( ref . startsWith ( '/ipfs/' ) ) {
129
+ res . header ( 'Cache-Control' , 'public, max-age=29030400, immutable' )
130
+ }
113
131
114
- if ( contentType ) {
115
- log ( 'writing content-type header' )
116
- res . header ( 'Content-Type' , contentType )
117
- }
132
+ log ( 'ref ' , ref )
133
+ log ( 'content-type ' , contentType )
118
134
119
- resolve ( res )
120
- }
121
- pusher . push ( chunk )
122
- } ,
123
- err => {
124
- if ( err ) {
125
- log . error ( err )
126
-
127
- // We already started flowing, abort the stream
128
- if ( started ) {
129
- return pusher . end ( err )
130
- }
131
-
132
- return reject ( err )
133
- }
135
+ if ( contentType ) {
136
+ log ( 'writing content-type header' )
137
+ res . header ( 'Content-Type' , contentType )
138
+ }
134
139
135
- pusher . end ( )
136
- }
137
- )
138
- )
139
- } )
140
+ return res
140
141
} ,
141
142
142
143
afterHandler ( request , h ) {
0 commit comments