HTTP conditional request middleware.
Compliant with RFC 9110, 13. Conditional Requests
For a definition of Universal HTTP middleware, see the http-middleware project.
To evaluate precondition, you need to provide select representation function to retrieve the selected representation.
The following example evaluates the If-None-Match precondition and handle
response.
import{conditionalRequest,typeHandler,}from"https://deno.land/x/conditional_request_middleware@$VERSION/mod.ts";import{assertEquals,assertFalse,}from"https://deno.land/std/testing/asserts.ts";import{assertSpyCalls,spy}from"https://deno.land/std/testing/mock.ts";constselectRepresentation=spy((request: Request)=>{returnnewResponse("<body>",{headers: {etag: "<etag>"}});});constmiddleware=conditionalRequest(selectRepresentation);constrequest=newRequest("<uri>",{headers: {"if-none-match": "<etag>"},});declareconst_handler: Handler;consthandler=spy(_handler);constresponse=awaitmiddleware(request,handler);assertSpyCalls(handler,0);assertSpyCalls(selectRepresentation,1);assertEquals(response.status,304);assertFalse(response.body);The evaluation of the pre-conditions is always done on the selected representation.
You must provide a function to retrieve the representation.
Selecting representation is the following interface:
interfaceSelectRepresentation{(request: Request): Response|Promise<Response>;}It is executed prior to the handler when a request with a precondition header is received.
The Request object passed to select representation has fileted the conditional
header.
It satisfies the following requirement.
A server MUST ignore all received preconditions if its response to the same request without those conditions, prior to processing the request content, would have been a status code other than a 2xx (Successful) or 412 (Precondition Failed).
Middleware supports all preconditions compliant with RFC 9110, 13.1. Preconditions by default.
If you want to adapt only some of the preconditions, give a list of them.
Example of middleware that handles only If-None-Match and If-Modified-Since
headers:
import{conditionalRequest,typeHandler,IfModifiedSince,IfNoneMatch,}from"https://deno.land/x/conditional_request_middleware@$VERSION/mod.ts";declareconstselectRepresentation: Handler;constmiddleware=conditionalRequest(selectRepresentation,{preconditions: [newIfNoneMatch(),newIfModifiedSince()],});Don't worry about the order of preconditions. They will be sorted appropriately based on the 13.2.2. Precedence of Preconditions.
The Middleware factory default values are as follows:
import{BytesRange,conditionalRequest,typeHandler,IfMatch,IfModifiedSince,IfNoneMatch,IfRange,IfUnmodifiedSince,}from"https://deno.land/x/conditional_request_middleware@$VERSION/mod.ts";declareconstselectRepresentation: Handler;constDEFAULT_PRECONDITIONS=[newIfMatch(),newIfNoneMatch(),newIfModifiedSince(),newIfUnmodifiedSince(),newIfRange([newBytesRange()]),];constmiddleware=conditionalRequest(selectRepresentation,{preconditions: DEFAULT_PRECONDITIONS,});Precondition is following structured object.
/** Precondition API. */exportinterfacePrecondition{/** Precondition header field name. */readonlyfield: string;/** Definition of precondition evaluation. * If return value is void, it represents ignore this precondition. */evaluate(request: Request,selectedRepresentation: Response,): boolean|void|Promise<boolean|void>;/** Called after {@link Precondition.evaluate}. * If return response, it must not perform the requested method. * If return value is void, it represents ignore this precondition. */respond(request: Request,selectedRepresentation: Response,result: boolean,): Response|void|Promise<Response|void>;}Precondition abstracts the evaluation of a precondition and its response.
Provide all preconditions compliant with RFC 9110, 13.1. Preconditions
If you implement a Precondition that is not in the specification, make sure
extensibility of preconditions.
If-Match header field precondition.
import{IfMatch}from"https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_match.ts";import{assertEquals}from"https://deno.land/std/testing/asserts.ts";constprecondition=newIfMatch();constrequest=newRequest("<uri>",{headers: {"if-match": "<strong:etag>"},});constselectedRepresentation=newResponse("<content>",{headers: {etag: "<weak:etag>"},});declareconstevalResult: false;assertEquals(precondition.field,"if-match");assertEquals(precondition.evaluate(request,selectedRepresentation),evalResult,);assertEquals(precondition.respond(request,selectedRepresentation,evalResult)?.status,412,);Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- HTTP headers
If-None-Match header field precondition.
import{IfNoneMatch}from"https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_none_match.ts";import{assertEquals}from"https://deno.land/std/testing/asserts.ts";constprecondition=newIfNoneMatch();constrequest=newRequest("<uri>",{headers: {"if-none-match": "<weak:etag>"},});constselectedRepresentation=newResponse("<content>",{headers: {etag: "<weak:etag>"},});declareconstevalResult: false;assertEquals(precondition.field,"if-none-match");assertEquals(precondition.evaluate(request,selectedRepresentation),evalResult,);assertEquals(precondition.respond(request,selectedRepresentation,evalResult)?.status,304,);Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- HTTP headers
If-Modified-Since header field precondition.
import{IfModifiedSince}from"https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_modified_since.ts";import{assertEquals}from"https://deno.land/std/testing/asserts.ts";constprecondition=newIfModifiedSince();constrequest=newRequest("<uri>",{headers: {"if-modified-since": "<after:HTTP-date>"},});constselectedRepresentation=newResponse("<content>",{headers: {"last-modified": "<before:HTTP-date>"},});declareconstevalResult: false;assertEquals(precondition.field,"if-modified-since");assertEquals(precondition.evaluate(request,selectedRepresentation),evalResult,);assertEquals(precondition.respond(request,selectedRepresentation,evalResult)?.status,304,);Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- HTTP headers
- Content-Type
- Content-Encoding
- Content-Length
- Content-Language
If-Unmodified-Since header field precondition.
import{IfUnmodifiedSince}from"https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_unmodified_since.ts";import{assertEquals}from"https://deno.land/std/testing/asserts.ts";constprecondition=newIfUnmodifiedSince();constrequest=newRequest("<uri>",{headers: {"if-unmodified-since": "<before:HTTP-date>"},});constselectedRepresentation=newResponse("<content>",{headers: {"last-modified": "<after:HTTP-date>"},});declareconstevalResult: false;assertEquals(precondition.field,"if-unmodified-since");assertEquals(precondition.evaluate(request,selectedRepresentation),evalResult,);assertEquals(precondition.respond(request,selectedRepresentation,evalResult)?.status,412,);Precondition will effect following:
If evaluation is false:
- HTTP content
- HTTP response status
- HTTP headers
If-Range header field precondition.
import{IfRange}from"https://deno.land/x/conditional_request_middleware@$VERSION/preconditions/if_range.ts";import{assertEquals}from"https://deno.land/std/testing/asserts.ts";constprecondition=newIfRange();constrequest=newRequest("<uri>",{headers: {"if-range": "<strong:etag>",range: "<range-unit>=<range-set>"},});constselectedRepresentation=newResponse("<content>",{headers: {etag: "<strong:etag>"},});declareconstevalResult: false;assertEquals(precondition.field,"if-range");assertEquals(precondition.evaluate(request,selectedRepresentation),evalResult,);assertEquals((awaitprecondition.respond(request,selectedRepresentation,evalResult))?.status,206,);Precondition will effect following:
If evaluation is true:
- HTTP content
- HTTP response status
- HTTP headers
- Content-Range
- Content-Type
Middleware will execute only if the following conditions are met:
- Request is conditional request
- Request method is not
CONNECT,OPTIONSorTRACE - Select representation status is
2xxor412
Copyright © 2023-present httpland.
Released under the MIT license
