forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path17_http.txt
896 lines (737 loc) · 33.6 KB
/
17_http.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
:chap_num: 17
:prev_link: 16_canvas
:next_link: 18_forms
:load_files: ["js/17_http.js", "js/promise.js"]
= HTTP =
[quote,Tim Berners-Lee,The World Wide Web: A very short personal history]
____
The dream behind the Web is of a common information space in which we
communicate by sharing information. Its universality is essential: the
fact that a hypertext link can point to anything, be it personal,
local or global, be it draft or highly polished.
____
The _Hyper-Text Transfer Protocol_, already mentioned in Chapter 12,
is the mechanism through which data is requested and provided on the
World Wide Web. This chapter describes this protocol in more detail,
and explains the way browser JavaScript has access to it.
== The protocol ==
As an example, imagine you type
_http://eloquentjavascript.net/index.html_ into your browser's address
bar. First, the browser looks up the address of the server associated
with `eloquentjavascript.net`, and tries to open a TCP connection to
it on port 80, the default port for HTTP traffic. If the server
exists, and accepts the connection, the browser sends something like
this:
[source,http]
----
GET /index.html HTTP/1.1
Host: eloquentjavascript.net
User-Agent: Your browser's name
----
After which the server responds, through that same connection:
[source,http]
----
HTTP/1.1 200 OK
Content-Length: 3122
Content-Type: text/html
Last-Modified: Wed, 09 Apr 2014 10:48:09 GMT
<!doctype html>
... the rest of the document
----
The browser then takes the part of the response below the blank line,
and displays it as an HTML document.
The information sent by the client is called the _request_. It starts
with this line:
[source,http]
----
GET /index.html HTTP/1.1
----
The first word is the _method_ of the request. `GET` means that we
want to _get_ the specified resource. Other common methods are
`DELETE` to delete a resource, `PUT` to replace it, and `POST` to send
information to it. Note that the server is not obliged to carry out
every request it gets. If you walk up to a random website and tell it
to `DELETE` its main page, it'll probably refuse.
The part after the method name is the path of the resource the request
applies to. In the simplest case, a resource is simply a file on the
server. But the protocol doesn't require it to be: It may be anything
that can be transferred _as if_ it is a file. Many servers generate
the responses they produce on the fly. For example, if you open
_http://twitter.com/marijnjh_, the server looks in its database for a
user named “marijnjh”, and if it finds one, it will generate a profile
page for that user.
After the resource path, the first line of the request mentions
`HTTP/1.1`, to indicate the version of the HTTP protocol it is using.
The server's response will start with a version as well, followed by
the status of the response, first as a three-digit code, and then as a
human-readable string.
[source,http]
----
HTTP/1.1 200 OK
----
Codes starting with a 2 indicate that the request succeeded. Codes
starting with a 4 mean there was something wrong with the request. 404
is probably the most famous HTTP status code—it means that the
resource that was requested could not be found. Codes that start with
5 mean an error occurred that the server, rather than the request, is
to blame for.
The first line of a request or response may be followed by any number
of _headers_. These are lines in the form “name: value” that specify
extra information about the request or response. These headers were
part of the example response:
----
Content-Length: 3122
Content-Type: text/html
Last-Modified: Wed, 09 Apr 2014 10:48:09 GMT
----
This tells us the size and type of the document that is being
returned. In this case, an HTML document of 3122 bytes. It also tells
us when that document was last modified.
Which headers to include in a request or a response is mostly up to
the client or server sending it, though a few are required. For
example, the `Host` header in a request, which specifies the host name,
should be included, because a server might be serving multiple host
names on a single IP address, and without that header it won't know
which one the client is trying to talk to.
After the headers, both requests and responses may include a blank
line followed by a _body_, which contains the data being sent. `GET`
or `DELETE` request don't send along any data, but `PUT` or `POST`
requests are expected to. Similarly, some response types, such as
error responses, do not require a body.
== Browsers and HTTP ==
As we saw in the example, a browser will make a request when we enter
a URL in its address bar. When the resulting HTML page references
other files, such as images and JavaScript files those are also
fetched.
A moderately complicated website can easily include anywhere from ten
to two hundred resources. To be able to fetch those quickly, browsers
will open several requests simultaneously, rather than waiting for the
responses one at a time.
Fetching such documents is always done using `GET` requests.
Generating `POST` requests in a browser is also a common thing to do.
HTML pages may include _forms_, which allow the user to fill out
information and send it to the server. This is an example of a form:
[source,text/html]
----
<form method="GET" action="example/message.html">
<p>Name: <input type="text" name="name"></p>
<p>Message:<br><textarea name="message"></textarea></p>
<p><button type="submit">Send</button></p>
</form>
----
That will show two fields, a small one asking for a name, and a larger
one to write a message in. When you press the “Send” button, the
information in the fields will be encoded into a _query string_. When
the `<form>` element's `method` attribute is `GET` (or is omitted),
that query string is tacked onto the `action` URL, and the browser
makes a `GET` request to that URL.
[source,text/html]
----
GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1
----
The start of a query string is indicated by a question mark. After
that follow pairs of names and values, corresponding to the `name`
attribute on the form field elements, and the content of those
elements. The ampersand (“&”) is used to separate the pairs.
The actual message encoded in the URL above is “Yes?”. Some characters
in query strings must be escaped. The question mark is one of those,
and is represented as `%3F`. There seems to be an unwritten rule that
every format needs its own different way of escaping characters. This
one, called “URL encoding”, uses a percent sign followed by two
hexadecimal digits which encode the character code. In this case 3F,
which is 63 in decimal notation, is the code of a question mark
character.
If we change the `method` attribute of the form above to `POST`, the
HTTP request made to submit the form will use the `POST` method, and
put the query string in body of the request, rather than adding it to
the URL.
[source,http]
----
POST /example/message.html HTTP/1.1
Content-length: 24
Content-type: application/x-www-form-urlencoded
name=Jean&message=Yes%3F
----
The next chapter will come back to forms, and talk about the way we
can script them with JavaScript.
== Historical accident ==
The interface through which browser JavaScript can make HTTP requests
is called `XMLHttpRequest` (note the inconsisten capitalization). It
was designed by Microsoft, for their Internet Explorer browser, in the
late 1990s. During this time, in the world of business software (a
world which Microsoft has always been at home in) the XML file format
was _very_ popular. So popular that the word “XML” was tacked onto the
front of the name of an interface for HTTP, which is in no way tied to
XML.
The name isn't completely nonsensical. There is functionality for
parsing a response as an XML file built into the interface. Confusing
two distinct concepts (making a request and parsing the response) into
a single thing is terrible design, of course, but so it goes.
When the `XMLHttpRequest` interface was added to Internet Explorer, it
allowed people to do things with JavaScript that had been very hard
before. One thing that made ripples at the time was the ability to
show suggestions when the user is typing something into a text field.
The script would send the text typed to the server over HTTP, and the
server, which had some database of things that the user might mean,
would match those against the partial input, and send back possible
completions. These were then shown to the user. This was mind-blowing,
at a time where people were used to every interaction with a website
requiring a full page reload.
The other significant browser of the time, Mozilla (later Firefox) did
not want to be left behind. To allow people to do similarly neat
things in _their_ browser, they copied the interface, including the
bogus name. The next generation of browsers followed this example, and
today `XMLHttpRequest` is a de facto standard interface.
== Sending a request ==
To make a simple request, we create a request object with the
`XMLHttpRequest` constructor, and call its `open` and `send` methods.
// test: trim
[source,javascript]
----
var req = new XMLHttpRequest();
req.open("GET", "example/data.txt", false);
req.send(null);
console.log(req.responseText);
// → This is the content of data.txt
----
The `open` method configures the request. In this case, we choose to
make a `GET` request for the _example/data.txt_ file. URLs that don't
start with _http://_ are called relative, which means that they are
interpreted relative to the current document. When they start with a
slash (“/”), they replace the current path (the part after the server
name). When they do not, the part of the current path up to and
including its last slash character is put in front of the relative
URL.
After opening the request, we can send it with the `send` method. The
argument to send is the request body. For `GET` requests, we can pass
null. If the third argument to `open` was `false`, `send` will only
return after the response to our request was received. We can read the
request object's `responseText` property to get the response body.
The other information included in the response can also be extracted
from this object. The status code is accessible through the `status`
property, and the status text through `statusText`. Headers can be
read with `getResponseHeader` (which expects a header name as
argument, and returns the header's value) and `getAllResponseHeaders`
(which returns a string containing all headers).
// test: no
[source,javascript]
----
var req = new XMLHttpRequest();
req.open("GET", "example/data.txt", false);
req.send(null);
console.log(req.status, req.statusText);
// → 200 OK
console.log(req.getResponseHeader("content-type"));
// → text/plain
----
Headers names are case-insensitive. They are usually written with a
capital letter at the start of each word, such as “Content-Type”, but
“content-type” or “cOnTeNt-TyPe” refer to the same header.
The browser will automatically add some request headers, such as
“Host” and those needed for the server to figure out the size of the
body. But you can add your own headers with the `setRequestHeader`
method. This is only needed for advanced uses, and requires the
cooperation of the server you are talking to—a server is free to
ignore headers it does not know how to handle.
== Asynchronous Requests ==
In the examples we saw, the call to `send` does not return until the
request has finished. This is convenient, because it means properties
like `responseText` are available immediately. But it does mean that
our program is suspended as long as the browser and server are
communicating. When the connection is bad, the server is slow, or the
file is big, that might take quite a while. Worse, because no event
handlers can fire while our program is suspended, the whole document
will become unresponsive. This is bad.
If we pass `true` as the third argument to `open`, the request is
_asynchronous_. That means that when we call `send`, the only thing
that happens right away is that the request gets scheduled to be sent.
Our program can continue, and the browser will take care of the
sending and receiving of data in the background.
But as long as the request is running, we won't be able to access the
response. We need a mechanism that will notify us when the data is
available.
For this, we must listen for the `"load"` event on the request object.
[source,javascript]
----
var req = new XMLHttpRequest();
req.open("GET", "example/data.txt", true);
req.addEventListener("load", function() {
console.log("Done:", req.status);
});
req.send(null);
----
This again forces us to use an asynchronous style of programming,
wrapping the things that have to be done after the request in a
function, and arranging for that to be called at the appropriate time.
We will come back to this later.
== Fetching XML Data ==
When the file retrieved by an `XMLHttpRequest` object is an XML
document, the object's `responseXML` property will hold a parsed
representation of this document. This representation works much like
the DOM discussed in Chapter 13, except that it doesn't have
HTML-specific functionality like the `style` property. The object that
`responseXML` holds corresponds to the `document` object. Its
`documentElement` property refers to the outer tag of the XML
document. In the document below (_example/fruit.xml_), that would
would be the `<fruits>` tag.
[source,application/xml]
----
<fruits>
<fruit name="banana" color="yellow"/>
<fruit name="lemon" color="yellow"/>
<fruit name="cherry" color="red"/>
</fruits>
----
We can retrieve it like this:
// test: no
[source,javascript]
----
var req = new XMLHttpRequest();
req.open("GET", "example/fruit.xml", false);
req.send(null);
console.log(req.responseXML.querySelectorAll("fruit").length);
// → 3
----
XML documents can be used to exchange structured information with the
server. Their form—tags nested inside other tags—lends itself well to
storing most types of data, or at least better than flat text files.
The DOM interface is rather clumsy for extracting information, though,
and XML documents tend to be verbose. It is often a better idea to
communicate using JSON data.
[source,javascript]
----
var req = new XMLHttpRequest();
req.open("GET", "example/fruit.json", false);
req.send(null);
console.log(JSON.parse(req.responseText));
// → {banana: "yellow", lemon: "yellow", cherry: "red"}
----
== HTTP sandboxing ==
Making HTTP requests in web page scripts once again raises concerns
about security. The person who controls the script might not have the
same interests as the person on whose computer it is running. More
specifically, if I visit themafia.org, I do not want its scripts to be
able to make a request to mybank.com, using identifying information
from my browser, with instructions to transfer all my money to some
random mafia account.
It is not too hard for websites to protect themselves against such
events, but it requires effort, and many websites fail to do it. For
this reason, browsers protect us by disallowing scripts to make HTTP
requests to other domains (names like themafia.org and mybank.com).
This can be an annoying problem when building systems that want to
access other domains for legitimate reasons. It is possible for
servers to include a header like this in their response to explicitly
indicate to browsers that it is okay for the request to come from a
different domain:
----
Access-Control-Allow-Origin: *
----
== Abstracting requests ==
In Chapter 10, in our implementation of the AMD module system, we used
a hypothetical function called `backgroundReadFile`. It took a
file name and a function, and called that function with the contents of
the file when it had finished fetching it. Here's a simple
implementation of that function:
// include_code
[source,javascript]
----
function backgroundReadFile(url, callback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
callback(req.responseText);
});
req.send(null);
}
----
This simple abstraction makes it easier to use `XMLHttpRequest` for
simple `GET` requests. If you are writing a program that has to make
HTTP requests, it is a good idea to use a helper function, so that you
don't end up repeating the same pattern over and over.
The function argument's name, `callback`, is a term that is often used
to describe functions like this. A callback function is given to other
code to provide that code with a way to “call us back” later.
It is not hard to write your own, tailored to what your application is
doing. The above one only does `GET` requests, and doesn't give us
control over the headers or the request body. You could write another
variant for `POST` requests, or a more generic one that supports
various kinds of requests. Many JavaScript libraries also provide with
wrappers for `XMLHttpRequest`.
The main problem with the wrapper above is its handling of failure.
When the request returns a status code that indicates an error (400
and up), it just logs something to the console and bails out.
Sometimes, this is okay, but imagine, for example, we put a “loading”
indicator on the page to indicate that we are fetching information. If
the request fails, because the server crashed or the connection is
briefly interrupted, the page will just sit there, misleadingly
looking like it is doing something. The user will wait for a while,
get impatient, and hate us.
We should also have an option to be notified when the request fails,
so that we can take appropriate action—for example, remove the
“loading” message and inform the user that something went wrong.
Error handling in asynchronous code is even more tricky than error
handling in synchronous code. Because we often need to defer part of
our work, putting it in a callback function, the scope of a `try`
block becomes meaningless. In the code below, the exception will _not_
be caught, because the call to `backgroundReadFile` returns
immediately. Control then leaves the `try` block, and the function it
was given won't be called until later.
// test: no
[source,javascript]
----
try {
backgroundReadFile("example/data.txt", function(text) {
if (text != "expected")
throw new Error("That was unexpected");
});
} catch (e) {
console.log("Hello from the catch block");
}
----
To handle failing requests, we have to allow an additional function to
be passed to our wrapper, and call that when a request goes wrong. Or
alternatively, we can use the convention that if the request fails, an
additional argument describing the problem is passed to the regular
callback function. For example:
// include_code
[source,javascript]
----
function getURL(url, callback) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
callback(req.responseText);
else
callback(null, new Error("Request failed: " +
req.statusText));
});
req.addEventListener("error", function() {
callback(null, new Error("Network error"));
});
req.send(null);
}
----
We have added a handler for the `"error"` event, which will be
signaled when the request fails entirely. We also call the callback
function with an error argument when the request completes with a
status code that indicates an error.
Code using `getURL` must then check whether an error was given, and if
it finds one, handle it.
[source,javascript]
----
getURL("data/nonsense.txt", function(content, error) {
if (error != null)
console.log("Failed to fetch nonsense.txt: " + error);
else
console.log("nonsense.txt: " + content);
});
----
This does not help when it comes to exceptions. When chaining several
asynchronous actions together, an exception at any point of the chain
will still, unless you wrap each handling function in its own
`try`/`catch` block, land at the top level and abort your chain of
actions.
== Promises ==
For complicated projects, writing asynchronous code in plain callback
back is hard to do correctly. It is easy to forget to check for an
error, or to allow an unexpected exception to cut the program short in
a crude way. Additionally, arranging for correct error handling when
the error has to flow through multiple callback functions and `catch`
blocks is tedious.
There have been a lot of attempts to solve this with extra
abstractions. One of the most successful ones is called _promises_.
Promises wrap an asynchronous action in an object, which can be passed
around and told to do certain things when the action finishes or
fails. They are set to become a part of the next version of the
JavaScript language, but can already be used as a library.
The interface for promises is somewhat non-obvious, but very powerful.
This chapter will only roughly describe it. A more thorough treatment
can be found at
http://www.html5rocks.com/en/tutorials/es6/promises[_www.html5rocks.com/en/tutorials/es6/promises_].
To create a promise object, we call the `Promise` constructor, giving
it a function that will initialize the asynchronous action. The
constructor calls that function, passing it two arguments, which are
themselves functions. The first should be called when the action
finishes successfully, and the second when it fails.
Here is once again our wrapper for `GET` requests, this time returning
a promise. We'll simply call it `get`, this time.
// include_code
[source,javascript]
----
function get(url) {
return new Promise(function(succeed, fail) {
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.addEventListener("load", function() {
if (req.status < 400)
succeed(req.responseText);
else
fail(new Error("Request failed: " + req.statusText));
});
req.addEventListener("error", function() {
fail(new Error("Network error"));
});
req.send(null);
});
}
----
Note that the interface to the function itself is now a lot simpler.
You give it a URL, and it returns a promise. That promise is a
_handle_ to the request's outcome. It has a `then` method that you can
call with two functions, one to handle success, and one to handle
failure.
[source,javascript]
----
get("example/data.txt").then(function(text) {
console.log("data.txt: " + text);
}, function(error) {
console.log("Failed to fetch data.txt: " + error);
});
----
So far, this is just a different way to express the same thing we
already expressed before. It is only when you need to chain actions
together that they make a significant difference.
Calling `then` produces a new promise, whose result (the value passed
to success handlers) depends on the return value of the first function
we passed to `then`. If this function returns another promise, that
promise is “connected” to the one returned by ++then++—its success or
failure is passed on to the `then` promise. If the handler returns a
normal, non-promise value, the output promise immediately succeeds,
with that value as its result.
This means you can transform the result of a promise. For example,
this returns a promise whose result is the content of the given URL,
parsed as JSON.
// include_code
[source,javascript]
----
function getJSON(url) {
return get(url).then(JSON.parse);
}
----
That last call to `then` did not specify a failure handler. This is
allowed. The error will be passed on to the promise returned by
`then`, which is exactly what we want—`getJSON` does not know what to
do when something goes wrong, but its caller hopefully does.
As an example of a suitable use of promises, we will build a program
that fetches a number of JSON files from the server, and, while it is
doing that, shows the word “loading”. The JSON files contain
information about people, with links to files that represent other
people in properties like `father`, `mother`, or `spouse`.
We want to get the name of the mother of the spouse of
_example/bert.json_. And if something goes wrong, we want to remove
the “loading” text and show an error message instead. Here is how that
might be done with promises:
[source,text/html]
----
<script>
function showMessage(msg) {
var elt = document.createElement("div");
elt.textContent = msg;
return document.body.appendChild(elt);
}
var loading = showMessage("Loading...");
getJSON("example/bert.json").then(function(bert) {
return getJSON(bert.spouse);
}).then(function(spouse) {
return getJSON(spouse.mother);
}).then(function(mother) {
showMessage("The name is " + mother.name);
}).catch(function(error) {
showMessage(String(error));
}).then(function() {
document.body.removeChild(loading);
});
</script>
----
The resulting program is relatively compact and readable. The `catch`
method is similar to `then`, except that it only expects a failure
handler, and will pass through the result unmodified in case of
success. Much like with the `catch` clause for the `try` statement,
control will continue as normal after the failure is caught, so that
the final `then`, which removes the loading message, is always
executed, even if something went wrong.
You can think of the promise interface as implementing its own
language for asynchronous control flow. All the method calls and
function expressions needed to achieve this make the code look
somewhat awkward, but not remotely as awkward as it would look if we
took care of all the error handing ourselves.
== Appreciating HTTP ==
When building a system that requires communication between a
JavaScript program running in the browser (client-side) and a program
on a server (server-side), there several different ways to model this
communication.
A commonly used model is that of _remote procedure calls_. In this
model, communication follows the patterns of normal function calls,
except that the function is actually running on another machine.
Calling it involves making a request to the server that includes the
function's name and arguments. The response to that request contains
the returned value.
When thinking in terms of remote procedure calls, HTTP is just a
vehicle for communication, and you will most likely write an
abstraction layer that hides it entirely.
Another approach is to build your communication around the concept of
resources and HTTP methods. Instead of a remote procedure called
`addUser`, you use a `PUT` request to `/users/larry`. Instead of
encoding that user's properties in function arguments, you define a
document format (or use an existing one) that represents the user, and
use it as the body of this `PUT` request. Fetching a resource is done
by making `GET` request to the resource's URL, for example
`/user/larry`, which returns the document representing that resource.
This second approach makes it easier to use some of the features that
HTTP provides, such as support for caching resources (keeping a copy
on the client side). It can also help the coherence of your interface,
since resources are easier to reason about than a jumble of functions.
== Security and HTTPS ==
Data traveling over the internet tends to follow a long, dangerous
road. In order to get to its destination it must hop through anything
from open coffee shop wireless networks, to networks controlled by
various companies and states. At any point, it may be inspected, or
even modified.
If it is important that something remain secret, such as the password
to your email account, or that it arrive at its destination
unmodified, such as the account number you transfer money to from your
bank's website, plain HTTP is not good enough.
The secure HTTP protocol, whose URLs start with `https:`, wraps HTTP
traffic in a way that makes it harder to read and tamper with. First,
the client verifies that the server is who it claims to be, by
requiring it to prove that is has a cryptographic certificate issued
by one of the certificate authorities that the browser recognizes.
Next, all data going over the connection is encrypted in a way that
should prevent eavesdropping and tampering.
Thus, when it works right, HTTPS prevents both the situation where
someone impersonates the website you were trying to talk to, and the
situation where someone is snooping on your communication. It is not
perfect, and there have been various incidents where HTTPS failed due
to forged or stolen certificates and broken software. Still, plain
HTTP is trivial to mess with, whereas breaking HTTPS requires the kind
of effort that only states or sophisticated criminal organizations can
hope to make.
== Summary ==
In this chapter, we saw that HTTP is a protocol for accessing
resources over the Internet. A _client_ sends a request, which
contains a method (usually `GET`) and a path that identifies a
resource. The _server_ then decides what to do with the request, and
responds with a status code and a response body. Both requests and
responses may contain headers, providing additional information.
Browsers make `GET` requests to fetch the resources needed to display
a web page. A web page may also contain forms, which allow information
entered by the user to be sent along in the request made when the form
is submitted. More on that in the next chapter.
The interface through which browser JavaScript can make HTTP requests
is called `XMLHttpRequest`. You can usually ignore the “XML” part of
that name (but you still have to type it). There are two ways in which
it can be used—synchronous, which blocks everything until the request
finishes, and asynchronous, which requires an event handler to notice
that the response came in. In almost all cases, asynchronous is
preferable. Making a request looks like this:
[source,javascript]
----
var req = new XMLHttpRequest();
req.open("GET", "example/data.txt", true);
req.addEventListener("load", function() {
console.log(req.statusCode);
});
req.send(null);
----
Asynchronous programming is tricky. _Promises_ are an interface that
makes it slightly easier, by helping route error conditions and
exceptions to the right handler, and abstracting away some of the more
repetetive and error-prone elements in this style of programming.
== Exercises ==
=== Content negotiation ===
One of the things that HTTP can do, but which we have not discussed in
this chapter yet, is called _content negotiation_. The `Accept` header
in a request can be used to tell the server what type of document the
client would like to get. Many servers ignore this header, but when a
server knows of various way to encode a resource, it can look at this
header and send the one that the client prefers.
The URL _http://eloquentjavascript.net/author_ is configured to
respond with either plain text, HTML, or JSON, depending on what the
client asks for. These formats are identified by the standardized
_media types_ `text/plain`, `text/html`, and `application/json`.
Send requests to fetch all three formats of this resource. Use the
`setRequestHeader` method of your `XMLHttpRequest` object set the
header named `Accept` to one of the media types given above. Make sure
you set the header _after_ calling `open`, but before calling `send`.
Finally, try asking for the media type `application/rainbows+unicorns`
and see what happens.
ifdef::html_target[]
// test: no
[source,javascript]
----
// Your code here.
----
endif::html_target[]
!!solution!!
See the various examples of using an `XMLHttpRequest` in this chapter
for an example of the method calls involved in making a request. You
can use a synchronous request (by setting the third parameter to
`open` to `false`) if you want.
Asking for a bogus media type will return a response with code 406
“Not acceptable”, which is the code a server should return when it can
not fulfill the `Accept` header.
!!solution!!
=== Waiting for multiple promises ===
The `Promise` constructor has an `all` method which, given an array of
promises, returns a promise that waits for all of them to finish, and
then succeeds itself, yielding an array of result values. If any of
the promises in the array fail, the promise returned by `all` fails
too (with the failure value from the failing promise).
Try to implement something like this yourself, as a regular function
called `all`.
Note that after a promise has succeeded or failed, it can not succeed
or fail again, and further calls to the functions that resolve it are
ignored. This can simplify the way you handle failure of your promise.
ifdef::html_target[]
// test: no
[source,javascript]
----
function all(promises) {
return new Promise(function(success, fail) {
// Your code here.
});
}
// Test code.
all([], function(array) {
console.log("This should be []:", array);
});
function soon(val) {
return new Promise(function(success) {
setTimeout(function() { success(val); },
Math.random() * 500);
});
}
all([soon(1), soon(2), soon(3)]).then(function(array) {
console.log("This should be [1, 2, 3]:", array);
});
function fail() {
return new Promise(function(success, fail) {
fail(new Error("boom"));
});
}
all([soon(1), fail(), soon(3)]).then(function(array) {
console.log("We should not get here");
}, function(error) {
console.log("Failed with:", error);
});
----
endif::html_target[]
!!solution!!
The function passed to the `Promise` constructor will have to call
`then` on each of the promises in the given array. When one of them
succeeds, two things need to happen—the resulting value needs to be
stored in the correct position of a result array, and we must check
whether this was the last pending promise, and finish our own promise
when it was.
The latter can be done with a counter, which is initialized to the
length of the input array, and which we subtract one for every time a
promise succeeds. When it reaches zero, we are done. Make sure you
take the situation where the input array is empty (and thus no promise
will ever resolve) into account.
Handling failure requires some though, but turns out to be extremely
simple. Just pass the failure function of the wrapping promise to each
of the promises in the array, so that a failure in one of them
triggers failure of the whole wrapper.
!!solution!!