UNPKG

22.8 kBMarkdownView Raw
1---
2title: Kettle Request Handlers and the kettle.app grade
3layout: default
4category: Kettle
5---
6# Kettle Request Handlers and the `kettle.app` grade
7
8A [`kettle.server`](Servers.md) comprises one or more `kettle.app` units, each of which comprises an independently
9mountable application unit. Within a [`kettle.app`](#kettle.app), each
10type of request handled by the application is defined using a
11[`kettle.request`](#how-to-implement-a-request-handler) component.
12
13<a id="kettle.app"></a>
14
15## Registering and implementing a request handler
16
17Request handlers are registered in the `requestHandlers` section of the options of a `kettle.app` – see the
18[sample app](ConfigsAndApplications.md#a-simple-kettle-application) for positioning of this component in the
19containment structure. This consists of a free hash of `handlerName` strings to `handlerRecord` structures.
20
21### Structure of the `requestHandlers` option of a `kettle.app`
22
23```snippet
24{
25<handlerName> : <handlerRecord>,
26<handlerName> : <handlerRecord>,
27...
28}
29```
30
31Note that the `handlerName`s are simply free strings and have no function other than to uniquely name the handler in the
32context of its app. These strings exist to allow easy alignment when
33multiple apps are merged together from different sources to produce combined apps.
34
35### Structure of the `handlerRecord` structure
36
37<table>
38 <thead>
39 <tr>
40 <th colspan="3">Members of an <code>handlerRecord</code> entry within the <code>requestHandlers</code> block
41 of a <code>kettle.app</code> component
42 </th>
43 </tr>
44 <tr>
45 <th>Member</th>
46 <th>Type</th>
47 <th>Description</th>
48 </tr>
49 </thead>
50 <tbody>
51 <tr>
52 <td><code>type</code></td>
53 <td><code>String</code></td>
54 <td>The name of a request handling grade, which must be descended from <code>kettle.request</code>. If you
55 supply the <code>method</code> field, your grade must be descended from <code>kettle.request.http</code>.
56 </td>
57 </tr>
58 <tr>
59 <td><code>route</code></td>
60 <td><code>String</code></td>
61 <td>An express-compatible <a href="http://expressjs.com/guide/routing.html">routing</a> string, expressing
62 the range of HTTP paths to be handled by this handler, together with any named parameters and query
63 parameters that should be captured. The exact syntax for route matching is documented more precisely at
64 <a href="https://github.com/pillarjs/path-to-regexp">pillarjs</a>.</td>
65 </tr>
66 <tr>
67 <td><code>gradeNames</code> (optional)</td>
68 <td><code>String/Array of String</code></td>
69 <td>One or more grade names which will be mixed in to the constructed handler when it is constructed.</td>
70 </tr>
71 <tr>
72 <td><code>prefix</code> (optional)</td>
73 <td><code>String</code></td>
74 <td>A routing prefix to be prepended to this handler's <code>route</code>. The prefix plus the route
75 expression must match the incoming request in order for this handler to be activated –
76 but if it is, it will only see the portion of the URL matched by <code>route</code> in the member
77 <code>request.req.url</code>. The entire incoming URL will remain visible in
78 <code>request.req.originalUrl</code> –
79 this is the same behaviour as express.js <a href="http://expressjs.com/api.html#app.use">routing system</a>.
80 It is primarily useful when using
81 <a href="./Middleware.md#built-in-standard-middleware-bundled-with-kettle">static middleware</a>
82 which will compare the <code>req.url</code> value with the filesystem path relative to its mount point.
83 </tr>
84 <tr>
85 <td><code>method</code> (optional)</td>
86 <td><code>String</code> value – one of the valid
87 <a href="https://github.com/nodejs/node/blob/master/deps/http_parser/http_parser.h#L88">HTTP methods</a>
88 supported by node.js, expressed in lower case, or else a comma-separated sequence of such values.
89 </td>
90 <td>The HTTP request type(s) which this handler will match. <code>method</code> is omitted in the
91 case that the request handling grade is not descended from <code>kettle.request.http</code> – the only
92 currently supported requests of that type are WebSockets requests descended from <code>kettle.request.ws</code>.
93 </td>
94 </tr>
95 </tbody>
96</table>
97
98### How to implement a request handler
99
100A handler for a particular request must have a
101[grade](http://docs.fluidproject.org/infusion/development/ComponentGrades.html)
102registered with Infusion whose name matches the `type` field in the `handlerRecord` structure just described. The parent
103grades of this grade must be consistent with the the request you expect to handle descended from `kettle.request.http`
104in the case of an HTTP request, or `kettle.request.ws` in the case of a WebSockets request. In addition, the grade must
105define (at minimum) an [invoker](http://docs.fluidproject.org/infusion/development/Invokers.html) named `handleRequest`.
106This invoker will be called by Kettle when your route is matched, and be supplied a single argument holding the
107***request object***, an object whose grade is your request handler's grade, which the framework has
108constructed to handle the request.
109
110We duplicate the definitions from the [sample application](ConfigsAndApplications.md#a-simple-kettle-application) in
111order to show a minimal request handler grade and request handler function:
112
113```javascript
114fluid.defaults("examples.simpleConfig.handler", {
115 gradeNames: "kettle.request.http",
116 invokers: {
117 handleRequest: "examples.simpleConfig.handleRequest"
118 }
119});
120
121examples.simpleConfig.handleRequest = function (request) {
122 request.events.onSuccess.fire({
123 message: "GET request received on path /handlerPath"
124 });
125};
126```
127
128In the next section we will talk more about request (handler) objects, the members you can expect on them, and how to
129use them.
130
131<a id="kettle.request"></a>
132
133## Request components
134
135A ***request component*** is constructed by Kettle when it has determined the correct
136[handler record](#registering-and-implementing-a-request-handler) which matches the incoming request. This request
137component will be usefully populated with material drawn from the request and node.js initial process of handling it.
138It also contains various elements supplied by Kettle in order to support you in handling the request. You can add any
139further material that you like to the request object by adding entries to its grade definition, of any of the types
140supported by Infusion's
141[component configuration options](http://docs.fluidproject.org/infusion/development/ComponentConfigurationOptions.html).
142Here we will document the standard members that are placed there by Kettle for the two standard request types which are
143supported, `kettle.request.http` and `kettle.request.ws`. These are derived from a common grade `kettle.request` which
144defines several common members and workflow elements.
145
146### Members defined by the Kettle framework at top-level on an HTTP request component
147
148The following table describes the members defined on `kettle.request.http`. Where these are not listed as "only for
149`kettle.request.http`" they are defined in the
150base grade `kettle.request` and so are also available for WebSockets components of type `kettle.request.ws`.
151
152<table>
153 <thead>
154 <tr>
155 <th colspan="3">Members defined by default at top-level on an HTTP request component of type
156 <code>kettle.request.http</code></th>
157 </tr>
158 <tr>
159 <th>Member</th>
160 <th>Type</th>
161 <th>Description</th>
162 </tr>
163 </thead>
164 <tbody>
165 <tr>
166 <td><code>req</code></td>
167 <td><a href="https://nodejs.org/api/http.html#http_http_incomingmessage"><code>http.IncomingMessage</code></a></td>
168 <td>The request object produced by node.js – this is the value which is commonly referred to as
169 <a href="http://expressjs.com/4x/api.html#req"><code>req</code></a> in the standard express
170 <a href="http://expressjs.com/guide/using-middleware.html">middleware pattern</a></td>
171 </tr>
172 <tr>
173 <td><code>res</code> (only for <code>kettle.request.http</code>)</td>
174 <td><a href="https://nodejs.org/api/http.html#http_class_http_serverresponse"><code>http.ServerResponse</code></a></td>
175 <td>The response object produced by node.js – this is the value which is commonly referred to as
176 <a href="http://expressjs.com/4x/api.html#res"><code>res</code></a> in the standard express
177 <a href="http://expressjs.com/4x/api.html#req">middleware pattern</a>
178 </td>
179 </tr>
180 <tr>
181 <td><code>events.onSuccess</code> (only for <code>kettle.request.http</code>)</td>
182 <td><a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html"><code>Event</code></a></td>
183 <td>A standard <a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html">Infusion
184 Event</a> which should be fired if the request is to produce a response successfully. The event argument
185 will produce the response body – if it is of type <code>Object</code>, it will be JSON-encoded.
186 </td>
187 </tr>
188 <tr>
189 <td><code>events.onError</code></td>
190 <td>
191 <a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html"><code>Event</code></a>
192 </td>
193 <td>A standard <a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html">Infusion
194 Event</a> which should be fired if the request is to send an error response.
195 For a request of type <code>kettle.request.http</code>, the argument to the event must be an
196 <code>Object</code> with at least
197 a field <code>message</code> of type <code>String</code> holding the error message to be returned to the
198 client. The argument can also include a member <code>statusCode</code> of type <code>Number</code>
199 holding the HTTP status code to accompany the error if this is not supplied, it will default to 500.
200 </td>
201 </tr>
202 <tr>
203 <td><code>handlerPromise</code> (only for <code>kettle.request.http</code>)</td>
204 <td><code>Promise</code></td>
205 <td>This promise is a proxy for the two events <code>onSuccess</code> and <code>onError</code>, packaged as
206 a <a href="https://www.promisejs.org/">Promise</a>. This promise exposes methods <code>resolve</code>
207 and <code>reject</code> which forward their arguments to <code>onSuccess</code> and <code>onError</code>
208 respectively. In addition, the promise exposes a <code>then</code> method which accepts two callbacks
209 which can be used to listen to these event firings respectively. Note that this promise is not compliant
210 with any particular specification for promises, including ES6, A+, etc. – in the language of those
211 specifications, it is simply a <code>thenable</code> which also includes the standard resolution methods
212 <code>resolve</code> and <code>reject</code>. Implementation at
213 <a href="https://github.com/fluid-project/infusion/blob/master/src/framework/core/js/FluidPromises.js#L21">FluidPromises.js</a>.
214 </td>
215 </tr>
216 </tbody>
217</table>
218
219Note that, conversely with the `req` property of the Kettle request component, the Kettle request component itself will
220be marked onto the node.js request object so that it can easily be retrieved from standard middleware, etc. – it will
221be available as `req.fluidRequest` where `req` is the request object described in the table above. More details follow
222on middleware in the section [working with middleware](Middleware.md#working-with-middleware).
223
224<a id="kettle.request.ws"></a>
225
226### WebSockets request components of type `kettle.request.ws`
227
228WebSockets communications in a Kettle application are mediated by the [ws](https://github.com/websockets/ws) WebSockets
229library – you should get familiar with the documentation for that library if you intend to use this functionality
230significantly. It is also worth spending some time familiarising yourself with at least some of the `ws` implementation
231code since there are several aspects not properly covered by the documentation.
232
233The request component for a WebSockets request, derived from the grade `kettle.request.ws`, includes the members in the
234above table which are not marked as `kettle.request.http` only via `kettle.request`, as well as several more members
235described in the following table:
236
237<table>
238 <thead>
239 <tr>
240 <th colspan="3">Members defined by default at top-level on a WebSockets request component of type
241 <code>kettle.request.ws</code></th>
242 </tr>
243 <tr>
244 <th>Member</th>
245 <th>Type</th>
246 <th>Description</th>
247 </tr>
248 </thead>
249 <tbody>
250 <tr>
251 <td><code>events.onBindWs</code></td>
252 <td><a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html"><code>Event</code></a></td>
253 <td>A standard <a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html">Infusion
254 Event</a> which is fired by the framework when the original HTTP connection has completed the
255 <a href="https://en.wikipedia.org/wiki/WebSocket#Protocol_handshake">handshake and upgrade sequence</a>
256 and the
257 <a href="https://github.com/websockets/ws/blob/master/doc/ws.md#class-wswebsocket"><code>ws.WebSocket</code></a>
258 object has been allocated. Any listener registered to this event will receive two arguments - firstly,
259 the <code>kettle.request.ws</code> component itself, and secondly the
260 <a href="https://github.com/websockets/ws/blob/master/doc/ws.md#class-wswebsocket"><code>ws.WebSocket</code></a>
261 object.
262 </td>
263 </tr>
264 <tr>
265 <td><code>ws</code></td>
266 <td><a href="https://github.com/websockets/ws/blob/master/doc/ws.md#class-wswebsocket"><code>ws.WebSocket</code></a></td>
267 <td>The <code>ws.WebSocket</code> advertised by the <a href="https://github.com/websockets/ws"><code>ws</code></a>
268 WebSockets library as allocated to handle one end of an established WebSockets connection. This will be
269 of the variety referred to in the <code>ws</code> docs as "a WebSocket constructed by a Server".
270 This member will only be present after the <code>onBindWs</code> event described in the previous row has
271 fired.
272 </td>
273 </tr>
274 <tr>
275 <td><code>sendMessage</code></td>
276 <td><code>Function(message: Any)</code></a></td>
277 <td>The <code>sendMessage</code> method is used to sent a WebSocket message to the client. By default, the
278 <code>sendMessageJSON</code> options described in the table below is set to <code>true</code> and so
279 <code>sendMessage</code> will accept a JavaScript object which will be encoded as JSON.
280 </td>
281 </tr>
282 <tr>
283 <td><code>sendTypedMessage</code></td>
284 <td><code>Function(type: String, payload: Object)</code></a></td>
285 <td>Operates a simple "typed message" system which will fire a payload to <code>sendMessage</code>
286 consisting of <code>{type: type, payload: payload}</code>. This method is only useful together with the
287 <code>sendMessageJSON: true</code> option (its default value).
288 </td>
289 </tr>
290 <tr>
291 <td><code>events.onReceiveMessage</code></td>
292 <td><a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html"><code>Event</code></a></td>
293 <td>A standard <a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html">Infusion
294 Event</a> which is fired by the framework when a message is received from the client at the other end of
295 the WebSockets connection. The arguments to the event are <code>(that, message)</code> where
296 <code>that</code> represents this request component itself, and <code>message</code> represnts
297 the message sent by the client. If the <code>receiveMessageJSON</code> option is set to
298 <code>true</code> for this component (the default), the message will have been decoded as JSON.
299 </td>
300 </tr>
301 <tr>
302 <td><code>events.onSendMessage</code></td>
303 <td><a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html"><code>Event</code></a></td>
304 <td>A standard <a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html">Infusion
305 Event</a> which operates the workflow started by the <code>sendMessage</code> method.
306 This is a <a href="./DataSources.md#transforming-promise-chains">transforming promise chain</a> event
307 for which each listener has the chance to transform the payload before it is sent to the next. More
308 information is available for the
309 <a href="http://docs.fluidproject.org/infusion/development/PromisesAPI.html#fluid-promise-firetransformevent-event-payload-options-">
310 <code>fireTransformEvent</code></a> function from Infusion's Promises API
311 </td>
312 </tr>
313 <tr>
314 <td><code>events.onError</code></td>
315 <td><a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html"><code>Event</code></a></td>
316 <td>A standard <a href="http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html">Infusion
317 Event</a> which should be fired if the request is to send an error response.
318 This event has the same name as the one fired by a <code>kettle.request.http</code> but the behaviour
319 and semantic is different. Rather than sending an HTTP error response, the framework instead
320 emits a WebSockets event of type <code>error</code>. Because of this, the <code>statusCode</code> field
321 of the event argument should not be used. However, it is recommended that the event payload still
322 includes a field <code>message</code> of type <code>String</code> holding the error message to be
323 returned to the client, as well as a boolean member <code>isError</code> with the value
324 <code>true</code>.
325 </td>
326 </tr>
327 </tbody>
328</table>
329
330A `kettle.request.ws` accepts the following configurable options at top level:
331
332<table>
333 <thead>
334 <tr>
335 <th colspan="3">Supported configurable options for a <code>kettle.request.ws</code></th>
336 </tr>
337 <tr>
338 <th>Option</th>
339 <th>Type</th>
340 <th>Description</th>
341 </tr>
342 </thead>
343 <tbody>
344 <tr>
345 <td><code>sendMessageJSON</code></td>
346 <td><code>Boolean</code> (default: <code>true</code>)</td>
347 <td>If this is set to <code>true</code>, the argument supplied to the component's <code>sendMessage</code>
348 method will be encoded as JSON. Otherwise the argument will be sent to <code>websocket.send</code>
349 as is.
350 </td>
351 </tr>
352 <tr>
353 <td><code>receiveMessageJSON</code></td>
354 <td><code>Boolean</code> (default: <code>true</code>)</td>
355 <td>If this is set to <code>true</code>, the argument received by listeners to the component's
356 <code>onReceiveMessage</code> event will be encoded as JSON. Otherwise the value will be transmitted as
357 from the WebSocket's <code>message</code> event unchanged.
358 </td>
359 </tr>
360 </tbody>
361</table>
362
363### Ending a WebSockets conversation
364
365The currently recommended scheme for terminating a WebSockets conversation managed by a `kettle.request.ws` component
366is to call the standard [`close`](https://github.com/websockets/ws/blob/master/doc/ws.md#websocketclosecode-data)
367method on the top-level `ws` member. This accepts two optional arguments - a
368[WebSockets status code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) and a description message. After
369processing the connection termination sequence, the request component will be automatically destroyed by the framework.
370
371### Example mini-application hosting a WebSockets endpoint
372
373The following example shows a minimal Kettle application which hosts a single WebSockets request handler named
374`webSocketsHandler` at the path `/webSocketsPath`. This handler
375simply logs any messages received to the console.
376
377```javascript
378
379fluid.defaults("examples.webSocketsConfig", {
380 gradeNames: "fluid.component",
381 components: {
382 server: {
383 type: "kettle.server",
384 options: {
385 gradeNames: ["kettle.server.ws"],
386 port: 8081,
387 components: {
388 app: {
389 type: "kettle.app",
390 options: {
391 requestHandlers: {
392 webSocketsHandler: {
393 "type": "examples.webSocketsConfig.handler",
394 "route": "/webSocketsPath"
395 }
396 }
397 }
398 }
399 }
400 }
401 }
402 }
403});
404
405fluid.defaults("examples.webSocketsConfig.handler", {
406 gradeNames: "kettle.request.ws",
407 listeners: {
408 onReceiveMessage: "examples.webSocketsConfig.receiveMessage"
409 }
410});
411
412examples.webSocketsConfig.receiveMessage = function (request, message) {
413 console.log("Received WebSockets message " + JSON.stringify(message, null, 2));
414};
415
416// Construct the server using the above config
417examples.webSocketsConfig();
418```