1 | ---
|
2 | title: Kettle Request Handlers and the kettle.app grade
|
3 | layout: default
|
4 | category: Kettle
|
5 | ---
|
6 | # Kettle Request Handlers and the `kettle.app` grade
|
7 |
|
8 | A [`kettle.server`](Servers.md) comprises one or more `kettle.app` units, each of which comprises an independently
|
9 | mountable application unit. Within a [`kettle.app`](#kettle.app), each
|
10 | type 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 |
|
17 | Request 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
|
19 | containment 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 |
|
31 | Note that the `handlerName`s are simply free strings and have no function other than to uniquely name the handler in the
|
32 | context of its app. These strings exist to allow easy alignment when
|
33 | multiple 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 |
|
100 | A handler for a particular request must have a
|
101 | [grade](http://docs.fluidproject.org/infusion/development/ComponentGrades.html)
|
102 | registered with Infusion whose name matches the `type` field in the `handlerRecord` structure just described. The parent
|
103 | grades of this grade must be consistent with the the request you expect to handle descended from `kettle.request.http`
|
104 | in the case of an HTTP request, or `kettle.request.ws` in the case of a WebSockets request. In addition, the grade must
|
105 | define (at minimum) an [invoker](http://docs.fluidproject.org/infusion/development/Invokers.html) named `handleRequest`.
|
106 | This 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
|
108 | constructed to handle the request.
|
109 |
|
110 | We duplicate the definitions from the [sample application](ConfigsAndApplications.md#a-simple-kettle-application) in
|
111 | order to show a minimal request handler grade and request handler function:
|
112 |
|
113 | ```javascript
|
114 | fluid.defaults("examples.simpleConfig.handler", {
|
115 | gradeNames: "kettle.request.http",
|
116 | invokers: {
|
117 | handleRequest: "examples.simpleConfig.handleRequest"
|
118 | }
|
119 | });
|
120 |
|
121 | examples.simpleConfig.handleRequest = function (request) {
|
122 | request.events.onSuccess.fire({
|
123 | message: "GET request received on path /handlerPath"
|
124 | });
|
125 | };
|
126 | ```
|
127 |
|
128 | In the next section we will talk more about request (handler) objects, the members you can expect on them, and how to
|
129 | use them.
|
130 |
|
131 | <a id="kettle.request"></a>
|
132 |
|
133 | ## Request components
|
134 |
|
135 | A ***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
|
137 | component will be usefully populated with material drawn from the request and node.js initial process of handling it.
|
138 | It also contains various elements supplied by Kettle in order to support you in handling the request. You can add any
|
139 | further material that you like to the request object by adding entries to its grade definition, of any of the types
|
140 | supported by Infusion's
|
141 | [component configuration options](http://docs.fluidproject.org/infusion/development/ComponentConfigurationOptions.html).
|
142 | Here we will document the standard members that are placed there by Kettle for the two standard request types which are
|
143 | supported, `kettle.request.http` and `kettle.request.ws`. These are derived from a common grade `kettle.request` which
|
144 | defines several common members and workflow elements.
|
145 |
|
146 | ### Members defined by the Kettle framework at top-level on an HTTP request component
|
147 |
|
148 | The 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
|
150 | base 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 |
|
219 | Note that, conversely with the `req` property of the Kettle request component, the Kettle request component itself will
|
220 | be marked onto the node.js request object so that it can easily be retrieved from standard middleware, etc. – it will
|
221 | be available as `req.fluidRequest` where `req` is the request object described in the table above. More details follow
|
222 | on 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 |
|
228 | WebSockets communications in a Kettle application are mediated by the [ws](https://github.com/websockets/ws) WebSockets
|
229 | library – you should get familiar with the documentation for that library if you intend to use this functionality
|
230 | significantly. It is also worth spending some time familiarising yourself with at least some of the `ws` implementation
|
231 | code since there are several aspects not properly covered by the documentation.
|
232 |
|
233 | The request component for a WebSockets request, derived from the grade `kettle.request.ws`, includes the members in the
|
234 | above table which are not marked as `kettle.request.http` only via `kettle.request`, as well as several more members
|
235 | described 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 |
|
330 | A `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 |
|
365 | The currently recommended scheme for terminating a WebSockets conversation managed by a `kettle.request.ws` component
|
366 | is to call the standard [`close`](https://github.com/websockets/ws/blob/master/doc/ws.md#websocketclosecode-data)
|
367 | method 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
|
369 | processing the connection termination sequence, the request component will be automatically destroyed by the framework.
|
370 |
|
371 | ### Example mini-application hosting a WebSockets endpoint
|
372 |
|
373 | The following example shows a minimal Kettle application which hosts a single WebSockets request handler named
|
374 | `webSocketsHandler` at the path `/webSocketsPath`. This handler
|
375 | simply logs any messages received to the console.
|
376 |
|
377 | ```javascript
|
378 |
|
379 | fluid.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 |
|
405 | fluid.defaults("examples.webSocketsConfig.handler", {
|
406 | gradeNames: "kettle.request.ws",
|
407 | listeners: {
|
408 | onReceiveMessage: "examples.webSocketsConfig.receiveMessage"
|
409 | }
|
410 | });
|
411 |
|
412 | examples.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
|
417 | examples.webSocketsConfig();
|
418 | ```
|