11import http from "node:http" ;
22import packageJson from "../package.json" with { type : "json" } ;
33import { Request } from "./Request.js" ;
4+ import { EmptyResponse } from "./response/index.js" ;
45import { Response } from "./response/Response.js" ;
56import { RouteRegistry } from "./routing/RouteRegistry.js" ;
67import { ServerErrorRegistry } from "./ServerErrorRegistry.js" ;
@@ -16,6 +17,7 @@ class Server {
1617 public readonly routes = new RouteRegistry ( ) ;
1718 private readonly server : http . Server ;
1819 private readonly copyOrigin : boolean ;
20+ private readonly handleConditionalRequests : boolean ;
1921
2022 /**
2123 * This server's error registry.
@@ -36,6 +38,7 @@ class Server {
3638 this . globalHeaders . set ( "Server" , `cldn/${ packageJson . version } ` ) ;
3739
3840 this . copyOrigin = options . copyOrigin ?? false ;
41+ this . handleConditionalRequests = options . handleConditionalRequests ?? true ;
3942
4043 this . server . listen ( options . port ) ;
4144 }
@@ -80,7 +83,53 @@ class Server {
8083 response = this . errors . _get ( ServerErrorRegistry . ErrorCodes . INTERNAL , apiRequest ) ;
8184 }
8285 }
83- response . _send ( res , this , apiRequest ) ;
86+ await this . sendResponse ( response , res , apiRequest ) ;
87+ }
88+
89+ private async sendResponse ( response : Response , res : http . ServerResponse , req : Request ) : Promise < void > {
90+ conditional: if (
91+ this . handleConditionalRequests
92+ && response . statusCode === 200
93+ && [ Request . Method . GET , Request . Method . HEAD ] . includes ( req . method )
94+ ) {
95+ const responseHeaders = response . allHeaders ( res , this , req ) ;
96+ const etag = responseHeaders . get ( "etag" ) ;
97+ const lastModified = responseHeaders . has ( "last-modified" )
98+ ? new Date ( responseHeaders . get ( "last-modified" ) ! )
99+ : null ;
100+ if ( etag === null && lastModified === null )
101+ break conditional;
102+
103+ if ( req . headers . has ( "if-match" ) ) {
104+ if ( ! this . getETags ( req . headers . get ( "if-match" ) ! )
105+ . filter ( t => ! t . startsWith ( "W/" ) )
106+ . includes ( etag ! ) )
107+ return this . errors . _get ( ServerErrorRegistry . ErrorCodes . PRECONDITION_FAILED , req ) . _send ( res , this , req ) ;
108+ }
109+ else if ( req . headers . has ( "if-unmodified-since" ) ) {
110+ if ( lastModified === null
111+ || lastModified . getTime ( ) > new Date ( req . headers . get ( "if-unmodified-since" ) ! ) . getTime ( ) )
112+ return this . errors . _get ( ServerErrorRegistry . ErrorCodes . PRECONDITION_FAILED , req ) . _send ( res , this , req ) ;
113+ }
114+
115+ if ( req . headers . has ( "if-none-match" ) ) {
116+ if ( this . getETags ( req . headers . get ( "if-none-match" ) ! )
117+ . includes ( etag ! ) )
118+ return new EmptyResponse ( responseHeaders , 304 ) . _send ( res , this , req ) ;
119+ }
120+ else if ( req . headers . has ( "if-modified-since" ) ) {
121+ if ( lastModified !== null
122+ && lastModified . getTime ( ) <= new Date ( req . headers . get ( "if-modified-since" ) ! ) . getTime ( ) )
123+ return new EmptyResponse ( responseHeaders , 304 ) . _send ( res , this , req ) ;
124+ }
125+ }
126+ response . _send ( res , this , req ) ;
127+ }
128+
129+ private getETags ( header : string ) {
130+ return header
131+ . split ( "," )
132+ . map ( t => t . trim ( ) )
84133 }
85134
86135 public close ( ) : Promise < void > {
@@ -119,6 +168,13 @@ namespace Server {
119168 * @default false
120169 */
121170 readonly copyOrigin ?: boolean ;
171+
172+ /**
173+ * Automatically handle conditional requests for GET and HEAD requests that result in a 200 status code.
174+ * `If-Range` headers are ignored.
175+ * @default true
176+ */
177+ readonly handleConditionalRequests ?: boolean ;
122178 }
123179}
124180
0 commit comments