You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 21_skillsharing.md
+24-34Lines changed: 24 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,10 +14,6 @@ quote}}
14
14
15
15
A _((skill-sharing))_ meeting is an event where people with a shared interest come together and give small, informal presentations about things they know. At a ((gardening)) skill-sharing meeting, someone might explain how to cultivate ((celery)). Or in a programming skill-sharing group, you could drop by and tell people about Node.js.
16
16
17
-
{{index learning, "users' group"}}
18
-
19
-
Such meetups—also often called _users' groups_ when they are about computers—can be a great way to learn things, or simply meet people with similar interests. Many larger cities have JavaScript meetups. They are typically free to attend, and I've found the ones I've visited to be friendly and welcoming.
20
-
21
17
In this final project chapter, our goal is to set up a ((website)) for managing ((talk))s given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about ((unicycling)). The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves, without an active organizer.
22
18
23
19
[Just like in the [previous chapter](node), some of the code in this chapter is written for Node.js, and running it directly in the HTML page that you are looking at is unlikely to work.]{if interactive} The full code for the project can be ((download))ed from [_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip).
@@ -52,7 +48,7 @@ We can arrange for the client to open the connection and keep it around so that
52
48
53
49
{{index socket}}
54
50
55
-
But an ((HTTP)) request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called _((WebSockets))_, supported by modern browsers, that makes it possible to open ((connection))s for arbitrary data exchange. But using them properly is somewhat tricky.
51
+
But an ((HTTP)) request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called _((WebSockets))_ that makes it possible to open ((connection))s for arbitrary data exchange. But using them properly is somewhat tricky.
56
52
57
53
In this chapter, we use a simpler technique—((long polling))—where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report.
58
54
@@ -193,41 +189,33 @@ export class Router {
193
189
add(method, url, handler) {
194
190
this.routes.push({method, url, handler});
195
191
}
196
-
}
197
-
198
-
async function resolveRequest(router, context, request) {
199
-
let path = parse(request.url).pathname;
200
-
201
-
for (let {method, url, handler} of this.routes) {
202
-
let match = url.exec(path);
203
-
if (!match || request.method != method) continue;
204
-
let urlParts = match.slice(1).map(decodeURIComponent);
205
-
let response =
206
-
await handler(context, ...urlParts, request);
207
-
if (response) return response;
192
+
async resolve(request, context) {
193
+
let path = parse(request.url).pathname;
194
+
for (let {method, url, handler} of this.routes) {
195
+
let match = url.exec(path);
196
+
if (!match || request.method != method) continue;
197
+
let parts = match.slice(1).map(decodeURIComponent);
198
+
return handler(context, ...parts, request);
199
+
}
208
200
}
209
201
}
210
202
```
211
203
212
204
{{index "Router class"}}
213
205
214
-
The module exports the `Router` class. A router object allows new handlers to be registered with the `add` method and can resolve requests with its `resolve` method.
215
-
216
-
{{index "some method"}}
217
-
218
-
The latter will return a response when a handler was found, and `null` otherwise. It tries the routes one at a time (in the order in which they were defined) until a matching one is found.
206
+
The module exports the `Router` class. A router object allows you to register handlers for specific methods and URL patterns with its `add` method. When a request is resolved with the `resolve` method, the router calls the handler whose method and URL match the request and return its result.
219
207
220
208
{{index "capture group", "decodeURIComponent function", [escaping, "in URLs"]}}
221
209
222
-
The handler functions are called with the `context` value (which will be the server instance in our case), match strings for any groups they defined in their ((regular expression)), and the request object. The strings have to be URL-decoded since the raw URL may contain `%20`-style codes.
210
+
Handler functions are called with the `context` value given to `resolve`. We will use this to give them access to our server state. Additionally, they receive the match strings for any groups they defined in their ((regular expression)), and the request object. The strings have to be URL-decoded since the raw URL may contain `%20`-style codes.
223
211
224
212
### Serving files
225
213
226
214
When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the `public` directory. It would be possible to use the file server defined in [Chapter ?](node#file_server) to serve such files, but we neither need nor want to support `PUT` and `DELETE` requests on files, and we would like to have advanced features such as support for caching. So let's use a solid, well-tested ((static file)) server from ((NPM)) instead.
I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests that we should handle specially, as defined in the router, so we wrap it in another function.
218
+
I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server from `"node:http"`, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests that we should handle specially, as defined in the router, so we wrap it in another function.
The `serveFromRouter` function has the same interface as `fileServer`, taking `(request, response, next)` arguments. This allows us to “chain” several request handlers, allowing each to either handle the request, or pass responsibility for that on to the next handler. The final handler, `notFound`, simply responds with a “not found” error.
264
252
265
-
Our `serveFromRouter` function uses a similar convention as the file server from the [previous chapter](node) for responses—handler in the router return promises that resolve to objects describing the response.
253
+
Our `serveFromRouter` function uses a similar convention as the file server from the [previous chapter](node) for responses—handlers in the router return promises that resolve to objects describing the response.
let {body, status = 200, headers = defaultHeaders} =
275
-
await resolved.catch(error => {
261
+
async function serveFromRouter(server, request,
262
+
response, next) {
263
+
let resolved = await router.resolve(request, server)
264
+
.catch(error => {
276
265
if (error.status != null) return error;
277
-
return {body: String(error), status: 500};
266
+
return {body: String(err), status: 500};
278
267
});
268
+
if (!resolved) return next();
269
+
let {body, status = 200, headers = defaultHeaders} =
270
+
await resolved;
279
271
response.writeHead(status, headers);
280
272
response.end(body);
281
273
}
282
274
```
283
275
284
276
### Talks as resources
285
277
286
-
The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. These will be exposed as HTTP ((resource))s under `/talks/[title]`, so we need to add handlers to our router that implement the various methods that clients can use to work with them.
278
+
The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. We will add some handlers to our router that expose these as HTTP ((resource))s under `/talks/[title]`.
287
279
288
280
{{index "GET method", "404 (HTTP status code)" "hasOwn function"}}
289
281
@@ -507,9 +499,7 @@ Thus, if we want a page to show up when a browser is pointed at our server, we s
507
499
508
500
{{index CSS}}
509
501
510
-
It defines the document ((title)) and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks.
511
-
512
-
At the bottom, it adds a heading at the top of the page and loads the script that contains the ((client))-side application.
502
+
It defines the document ((title)) and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks. Then it adds a heading at the top of the page and loads the script that contains the ((client))-side application.
513
503
514
504
### Actions
515
505
@@ -830,7 +820,7 @@ hint}}
830
820
831
821
The wholesale redrawing of talks works pretty well because you usually can't tell the difference between a DOM node and its identical replacement. But there are exceptions. If you start typing something in the comment ((field)) for a talk in one browser window and then, in another, add a comment to that talk, the field in the first window will be redrawn, removing both its content and its ((focus)).
832
822
833
-
In a heated discussion, where multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it?
823
+
When multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it?
0 commit comments