11import EventEmitter from 'node:events'
22
3- import type { ServerOptions } from '../types'
4- import type { DataStore } from '../models'
3+ import type { ServerOptions , WithRequired } from '../types'
4+ import type { DataStore , CancellationContext } from '../models'
55import type http from 'node:http'
6+ import stream from 'node:stream'
7+ import { ERRORS } from '../constants'
68
79const reExtractFileID = / ( [ ^ / ] + ) \/ ? $ /
810const reForwardedHost = / h o s t = " ? ( [ ^ " ; ] + ) /
911const reForwardedProto = / p r o t o = ( h t t p s ? ) /
1012
1113export class BaseHandler extends EventEmitter {
12- options : ServerOptions
14+ options : WithRequired < ServerOptions , 'locker' >
1315 store : DataStore
1416
15- constructor ( store : DataStore , options : ServerOptions ) {
17+ constructor ( store : DataStore , options : WithRequired < ServerOptions , 'locker' > ) {
1618 super ( )
1719 if ( ! store ) {
1820 throw new Error ( 'Store must be defined' )
@@ -27,6 +29,7 @@ export class BaseHandler extends EventEmitter {
2729 // @ts -expect-error not explicitly typed but possible
2830 headers [ 'Content-Length' ] = Buffer . byteLength ( body , 'utf8' )
2931 }
32+
3033 res . writeHead ( status , headers )
3134 res . write ( body )
3235 return res . end ( )
@@ -101,4 +104,61 @@ export class BaseHandler extends EventEmitter {
101104
102105 return { host : host as string , proto}
103106 }
107+
108+ protected async getLocker ( req : http . IncomingMessage ) {
109+ if ( typeof this . options . locker === 'function' ) {
110+ return this . options . locker ( req )
111+ }
112+ return this . options . locker
113+ }
114+
115+ protected async acquireLock (
116+ req : http . IncomingMessage ,
117+ id : string ,
118+ context : CancellationContext
119+ ) {
120+ const locker = await this . getLocker ( req )
121+
122+ const lock = locker . newLock ( id )
123+
124+ await lock . lock ( ( ) => {
125+ context . cancel ( )
126+ } )
127+
128+ return lock
129+ }
130+
131+ protected writeToStore (
132+ req : http . IncomingMessage ,
133+ id : string ,
134+ offset : number ,
135+ context : CancellationContext
136+ ) {
137+ return new Promise < number > ( async ( resolve , reject ) => {
138+ if ( context . signal . aborted ) {
139+ reject ( ERRORS . ABORTED )
140+ return
141+ }
142+
143+ const proxy = new stream . PassThrough ( )
144+ stream . addAbortSignal ( context . signal , proxy )
145+
146+ proxy . on ( 'error' , ( err ) => {
147+ req . unpipe ( proxy )
148+ if ( err . name === 'AbortError' ) {
149+ reject ( ERRORS . ABORTED )
150+ } else {
151+ reject ( err )
152+ }
153+ } )
154+
155+ req . on ( 'error' , ( err ) => {
156+ if ( ! proxy . closed ) {
157+ proxy . destroy ( err )
158+ }
159+ } )
160+
161+ this . store . write ( req . pipe ( proxy ) , id , offset ) . then ( resolve ) . catch ( reject )
162+ } )
163+ }
104164}
0 commit comments