22
33Chainable and immutable HTTP handler for standard ` Request ` and ` Response ` .
44
5+ [ ![ deno land] ( http://img.shields.io/badge/available%20on-deno.land/x-lightgrey.svg?logo=deno )] ( https://deno.land/x/chain_handler )
6+ [ ![ deno doc] ( https://doc.deno.land/badge.svg )] ( https://doc.deno.land/https/deno.land/x/chain_handler/mod.ts )
7+ [ ![ GitHub release (latest by date)] ( https://img.shields.io/github/v/release/httpland/chain-handler )] ( https://github.com/httpland/chain-handler/releases )
8+ [ ![ codecov] ( https://codecov.io/gh/httpland/chain-handler/branch/main/graph/badge.svg?token=nan4NUrx1V )] ( https://codecov.io/gh/httpland/chain-handler )
9+ [ ![ GitHub] ( https://img.shields.io/github/license/httpland/chain-handler )] ( https://github.com/httpland/chain-handler/blob/main/LICENSE )
10+
11+ [ ![ test] ( https://github.com/httpland/chain-handler/actions/workflows/test.yaml/badge.svg )] ( https://github.com/httpland/chain-handler/actions/workflows/test.yaml )
12+ [ ![ NPM] ( https://nodei.co/npm/@httpland/chain-handler.png?mini=true )] ( https://nodei.co/npm/@httpland/chain-handler/ )
13+
14+ ## What
15+
16+ Defines an API for chainable HTTP handler.
17+
18+ ``` ts
19+ interface Handler {
20+ (request : Request ): Response | Promise <Response >;
21+ }
22+ ```
23+
24+ The declarative, web-standard compliant HTTP handler API is a powerful employed
25+ by ` deno/std ` .
26+
27+ We maintain this API and extend it. What we are adding to the ` Handler ` is a
28+ chaining mechanism.
29+
30+ ``` ts
31+ interface ChainableHandler {
32+ (request : Request , next : OptionalHandler ): Response | Promise <Response >;
33+ }
34+
35+ interface OptionalHandler {
36+ (request ? : Request ): Response | Promise <Response >;
37+ }
38+ ```
39+
40+ ` ChainableHandler ` is a handler that takes the next handler as the second
41+ argument.
42+
43+ ` ChainableHandler ` satisfies the following features:
44+
45+ - It can access to the ` Request ` .
46+ - It can access the next handler.
47+ - It can call the next handler.
48+ - It can choose not to call the next handler.
49+ - It can access the next handler's return value (` Response ` ).
50+ - It can return ` Response ` .
51+
52+ ` OptionalHandler ` is the next handler itself. It is optional to call it, or to
53+ pass a modified ` Request ` object.
54+
55+ However, because of the emphasis on immutable, the ` Request ` object is
56+ propagated only through its arguments.
57+
58+ These features make it the core of ** middleware** .
59+
60+ ## Packages
61+
62+ The package supports multiple platforms.
63+
64+ - deno.land/x - ` https://deno.land/x/chain_handler/mod.ts `
65+ - npm - ` @httpland/chain-handler `
66+
567## Usage
668
69+ ` Chain ` is a stateful constructor. Add a ` ChainableHandler ` from the constructor
70+ or from the ` next ` function.
71+
72+ Handlers are executed asynchronously and recursively in the order of their
73+ declarations. Calling the next handler executes the next handler. ` await ` a call
74+ to the next handler gives access to the ` Response ` of the next handler.
75+
776``` ts
877import { Chain } from " https://deno.land/x/chain_handler@$VERSION/mod.ts" ;
978import { assertEquals } from " https://deno.land/std/testing/asserts.ts" ;
@@ -15,27 +84,155 @@ chain.next(async (request, next) => {
1584 const response = await next ();
1685 console .log (" end" );
1786 return response ;
18- }). next ( (request , next ) => {
87+ }, (request , next ) => {
1988 // request proxy
2089 request .headers .append (" x-proxy" , " chain" );
2190 return next (request );
22- }). next ( async (_ , next ) => {
91+ }, async (_ , next ) => {
2392 // response proxy
2493 const response = await next ();
2594 response .headers .append (" server" , " deno" );
2695 return response ;
27- }). next ( () => {
96+ }, () => {
2897 // cut off chain
2998 return new Response (" hello" );
30- }). next ( () => {
99+ }, () => {
31100 // not call because cut off by previous chain.
32101 return new Response (" goodby" );
33102});
103+
34104const response = await chain .respond (new Request (" http://localhost" ));
35105assertEquals (await response .text (), " hello" );
36106assertEquals (response .headers .get (" server" ), " deno" );
37107```
38108
109+ In the ` respond ` function, apply a ` ChainableHandler ` to convert the ` Request `
110+ into a ` Response ` .
111+
112+ ## Immutability
113+
114+ To reduce unexpected bugs, ` Request ` and ` Response ` are ** NOT** shared among
115+ handlers. To propagate a change, pass the ` Request ` or ` Response ` to the next
116+ handler.
117+
118+ ``` ts
119+ import { Chain } from " https://deno.land/x/chain_handler@$VERSION/mod.ts" ;
120+ import { assertEquals } from " https://deno.land/std/testing/asserts.ts" ;
121+
122+ const chain = new Chain ();
123+
124+ chain .next ((request , next ) => {
125+ request .headers .append (" x-effect" , " no effect" );
126+ return next ();
127+ }).next ((request , next ) => {
128+ assertEquals (request .headers .get (" x-effect" ), null );
129+
130+ return next ();
131+ });
132+ ```
133+
134+ In order to propagate changes, a ` Request ` or ` Response ` must be passed to the
135+ next handler.
136+
137+ ``` ts
138+ import { Chain } from " https://deno.land/x/chain_handler@$VERSION/mod.ts" ;
139+ import { assertEquals } from " https://deno.land/std/testing/asserts.ts" ;
140+
141+ const chain = new Chain ((request , next ) => {
142+ request .headers .append (" x-effect" , " effected" );
143+
144+ return next (request );
145+ }, (request ) => {
146+ assertEquals (request .headers .get (" x-effect" ), " effected" );
147+
148+ return new Response (" ok" );
149+ });
150+ ```
151+
152+ This ensures that there are no destructive changes to the object.
153+
154+ ## Utility
155+
156+ Stateless functions are also available.
157+
158+ ``` ts
159+ import {
160+ chain ,
161+ type ChainableHandler ,
162+ } from " https://deno.land/x/chain_handler@$VERSION/mod.ts" ;
163+ import { assertEquals } from " https://deno.land/std/testing/asserts.ts" ;
164+
165+ const initRequest = new Request (" http://localhost" );
166+ const initResponse = new Response (null , { status: 404 });
167+ const justThrough: ChainableHandler = (_ , next ) => next ();
168+
169+ const response = await chain (
170+ initRequest ,
171+ initResponse ,
172+ justThrough ,
173+ justThrough ,
174+ ... new Array (5 ).fill (justThrough ),
175+ );
176+ assertEquals (response .status , 404 );
177+ ```
178+
179+ ## Tips
180+
181+ Detailed specifications are explained below.
182+
183+ ### Clone is not required
184+
185+ If you make a destructive change, such as reading a body, you do not need to
186+ clone it for the next handler.
187+
188+ ``` ts
189+ import { Chain } from " https://deno.land/x/chain_handler@$VERSION/mod.ts" ;
190+
191+ const chain = new Chain (async (request , next ) => {
192+ // No need request.clone()
193+ const text = await request .text ();
194+ return next ();
195+ }, async (request ) => {
196+ const json = await request .json ();
197+ return new Response (" ok" );
198+ });
199+ ```
200+
201+ A clone of the ** argument** or ** return value** object is always used.
202+
203+ That is, ` clone ` is required in the following cases
204+
205+ ``` ts
206+ import { Chain } from " https://deno.land/x/chain_handler@$VERSION/mod.ts" ;
207+ import { assertEquals } from " https://deno.land/std/testing/asserts.ts" ;
208+
209+ const chain = new Chain (async (request , next ) => {
210+ const response = await next ();
211+ const cloned = response .clone ();
212+
213+ assertEquals (await cloned .text (), " ok" );
214+
215+ return response ;
216+ }, async () => new Response (" ok" ));
217+ ```
218+
219+ ### Default response
220+
221+ Default Response is ` new Response(null, { status: 404 }) ` . This can be
222+ completely changed.
223+
224+ ``` ts
225+ import { Chain } from " https://deno.land/x/chain_handler@$VERSION/mod.ts" ;
226+ import { assertEquals } from " https://deno.land/std/testing/asserts.ts" ;
227+
228+ const response = await new Chain ().respond (
229+ new Request (" http://localhost" ),
230+ new Response (" ok" ),
231+ );
232+
233+ assertEquals (await response .text (), " ok" );
234+ ```
235+
39236## License
40237
41238Copyright © 2023-present [ httpland] ( https://github.com/httpland ) .
0 commit comments