Skip to content

Repository files navigation

conditional-request-middleware

deno landdeno docGitHub release (latest by date)codecovGitHub

testNPM

HTTP conditional request middleware.

Compliant with RFC 9110, 13. Conditional Requests

Middleware

For a definition of Universal HTTP middleware, see the http-middleware project.

Usage

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);

Select representation

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).

Preconditions

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.

Middleware default

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

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.

IfMatch

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,);

Effects

Precondition will effect following:

If evaluation is false:

IfNoneMatch

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,);

Effects

Precondition will effect following:

If evaluation is false:

IfModifiedSince

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,);

Effects

Precondition will effect following:

If evaluation is false:

  • HTTP content
  • HTTP response status
  • HTTP headers
    • Content-Type
    • Content-Encoding
    • Content-Length
    • Content-Language

IfUnmodifiedSince

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,);

Effects

Precondition will effect following:

If evaluation is false:

IfRange

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,);

Effects

Precondition will effect following:

If evaluation is true:

Conditions

Middleware will execute only if the following conditions are met:

  • Request is conditional request
  • Request method is not CONNECT, OPTIONS or TRACE
  • Select representation status is 2xx or 412

License

Copyright © 2023-present httpland.

Released under the MIT license