11import path from "path" ;
22
33import mime from "mime-types" ;
4+ import parseRange from "range-parser" ;
45
56import getFilenameFromUrl from "./utils/getFilenameFromUrl" ;
6- import handleRangeHeaders from "./utils/handleRangeHeaders" ;
7+ import {
8+ getHeaderNames ,
9+ getHeaderFromRequest ,
10+ getHeaderFromResponse ,
11+ setHeaderForResponse ,
12+ setStatusCode ,
13+ send ,
14+ } from "./utils/compatibleAPI" ;
715import ready from "./utils/ready" ;
816
17+ function getValueContentRangeHeader ( type , size , range ) {
18+ return `${ type } ${ range ? `${ range . start } -${ range . end } ` : "*" } /${ size } ` ;
19+ }
20+
21+ function createHtmlDocument ( title , body ) {
22+ return (
23+ `${
24+ "<!DOCTYPE html>\n" +
25+ '<html lang="en">\n' +
26+ "<head>\n" +
27+ '<meta charset="utf-8">\n' +
28+ "<title>"
29+ } ${ title } </title>\n` +
30+ `</head>\n` +
31+ `<body>\n` +
32+ `<pre>${ body } </pre>\n` +
33+ `</body>\n` +
34+ `</html>\n`
35+ ) ;
36+ }
37+
38+ const BYTES_RANGE_REGEXP = / ^ * b y t e s / i;
39+
940export default function wrapper ( context ) {
1041 return async function middleware ( req , res , next ) {
1142 const acceptedMethods = context . options . methods || [ "GET" , "HEAD" ] ;
@@ -16,6 +47,7 @@ export default function wrapper(context) {
1647
1748 if ( ! acceptedMethods . includes ( req . method ) ) {
1849 await goNext ( ) ;
50+
1951 return ;
2052 }
2153
@@ -42,80 +74,146 @@ export default function wrapper(context) {
4274
4375 async function processRequest ( ) {
4476 const filename = getFilenameFromUrl ( context , req . url ) ;
45- let { headers } = context . options ;
46-
47- if ( typeof headers === "function" ) {
48- headers = headers ( req , res , context ) ;
49- }
50-
51- let content ;
5277
5378 if ( ! filename ) {
5479 await goNext ( ) ;
80+
5581 return ;
5682 }
5783
58- try {
59- content = context . outputFileSystem . readFileSync ( filename ) ;
60- } catch ( _ignoreError ) {
61- await goNext ( ) ;
62- return ;
84+ let { headers } = context . options ;
85+
86+ if ( typeof headers === "function" ) {
87+ headers = headers ( req , res , context ) ;
6388 }
6489
65- const contentTypeHeader = res . get
66- ? res . get ( "Content-Type" )
67- : res . getHeader ( "Content-Type" ) ;
90+ if ( headers ) {
91+ const names = Object . keys ( headers ) ;
92+
93+ for ( const name of names ) {
94+ setHeaderForResponse ( res , name , headers [ name ] ) ;
95+ }
96+ }
6897
69- if ( ! contentTypeHeader ) {
98+ if ( ! getHeaderFromResponse ( res , "Content-Type" ) ) {
7099 // content-type name(like application/javascript; charset=utf-8) or false
71100 const contentType = mime . contentType ( path . extname ( filename ) ) ;
72101
73102 // Only set content-type header if media type is known
74103 // https://tools.ietf.org/html/rfc7231#section-3.1.1.5
75104 if ( contentType ) {
76- // Express API
77- if ( res . set ) {
78- res . set ( "Content-Type" , contentType ) ;
79- }
80- // Node.js API
81- else {
82- res . setHeader ( "Content-Type" , contentType ) ;
83- }
105+ setHeaderForResponse ( res , "Content-Type" , contentType ) ;
84106 }
85107 }
86108
87- if ( headers ) {
88- const names = Object . keys ( headers ) ;
109+ if ( ! getHeaderFromResponse ( res , "Accept-Ranges" ) ) {
110+ setHeaderForResponse ( res , "Accept-Ranges" , "bytes" ) ;
111+ }
89112
90- for ( const name of names ) {
91- // Express API
92- if ( res . set ) {
93- res . set ( name , headers [ name ] ) ;
94- }
95- // Node.js API
96- else {
97- res . setHeader ( name , headers [ name ] ) ;
113+ const rangeHeader = getHeaderFromRequest ( req , "range" ) ;
114+
115+ let start ;
116+ let end ;
117+
118+ if ( rangeHeader && BYTES_RANGE_REGEXP . test ( rangeHeader ) ) {
119+ const size = await new Promise ( ( resolve ) => {
120+ context . outputFileSystem . lstat ( filename , ( error , stats ) => {
121+ if ( error ) {
122+ context . logger . error ( error ) ;
123+
124+ return ;
125+ }
126+
127+ resolve ( stats . size ) ;
128+ } ) ;
129+ } ) ;
130+
131+ const parsedRanges = parseRange ( size , rangeHeader , {
132+ combine : true ,
133+ } ) ;
134+
135+ if ( parsedRanges === - 1 ) {
136+ const message = "Unsatisfiable range for 'Range' header." ;
137+
138+ context . logger . error ( message ) ;
139+
140+ const existingHeaders = getHeaderNames ( res ) ;
141+
142+ for ( let i = 0 ; i < existingHeaders . length ; i ++ ) {
143+ res . removeHeader ( existingHeaders [ i ] ) ;
98144 }
145+
146+ setStatusCode ( res , 416 ) ;
147+ setHeaderForResponse (
148+ res ,
149+ "Content-Range" ,
150+ getValueContentRangeHeader ( "bytes" , size )
151+ ) ;
152+ setHeaderForResponse ( res , "Content-Type" , "text/html; charset=utf-8" ) ;
153+
154+ const document = createHtmlDocument ( 416 , `Error: ${ message } ` ) ;
155+ const byteLength = Buffer . byteLength ( document ) ;
156+
157+ setHeaderForResponse (
158+ res ,
159+ "Content-Length" ,
160+ Buffer . byteLength ( document )
161+ ) ;
162+
163+ send ( req , res , document , byteLength ) ;
164+
165+ return ;
166+ } else if ( parsedRanges === - 2 ) {
167+ context . logger . error (
168+ "A malformed 'Range' header was provided. A regular response will be sent for this request."
169+ ) ;
170+ } else if ( parsedRanges . length > 1 ) {
171+ context . logger . error (
172+ "A 'Range' header with multiple ranges was provided. Multiple ranges are not supported, so a regular response will be sent for this request."
173+ ) ;
99174 }
100- }
101175
102- // Buffer
103- content = handleRangeHeaders ( context , content , req , res ) ;
176+ if ( parsedRanges !== - 2 && parsedRanges . length === 1 ) {
177+ // Content-Range
178+ setStatusCode ( res , 206 ) ;
179+ setHeaderForResponse (
180+ res ,
181+ "Content-Range" ,
182+ getValueContentRangeHeader ( "bytes" , size , parsedRanges [ 0 ] )
183+ ) ;
104184
105- // Express API
106- if ( res . send ) {
107- res . send ( content ) ;
185+ [ { start, end } ] = parsedRanges ;
186+ }
108187 }
109- // Node.js API
110- else {
111- res . setHeader ( "Content-Length" , content . length ) ;
112188
113- if ( req . method === "HEAD" ) {
114- res . end ( ) ;
189+ const isFsSupportsStream =
190+ typeof context . outputFileSystem . createReadStream === "function" ;
191+
192+ let bufferOtStream ;
193+ let byteLength ;
194+
195+ try {
196+ if (
197+ typeof start !== "undefined" &&
198+ typeof end !== "undefined" &&
199+ isFsSupportsStream
200+ ) {
201+ bufferOtStream = context . outputFileSystem . createReadStream ( filename , {
202+ start,
203+ end,
204+ } ) ;
205+ byteLength = end - start + 1 ;
115206 } else {
116- res . end ( content ) ;
207+ bufferOtStream = context . outputFileSystem . readFileSync ( filename ) ;
208+ byteLength = Buffer . byteLength ( bufferOtStream ) ;
117209 }
210+ } catch ( _ignoreError ) {
211+ await goNext ( ) ;
212+
213+ return ;
118214 }
215+
216+ send ( req , res , bufferOtStream , byteLength ) ;
119217 }
120218 } ;
121219}
0 commit comments