summaryrefslogtreecommitdiffstats
path: root/netwerk/docs/http_server_for_testing.rst
blob: dbf6ce75208694fed4fd584dc336f802fc561b45 (plain)
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
HTTP server for unit tests
==========================

This page describes the JavaScript implementation of an
HTTP server located in ``netwerk/test/httpserver/``.

Server functionality
~~~~~~~~~~~~~~~~~~~~

Here are some of the things you can do with the server:

-  map a directory of files onto an HTTP path on the server, for an
   arbitrary number of such directories (including nested directories)
-  define custom error handlers for HTTP error codes
-  serve a given file for requests for a specific path, optionally with
   custom headers and status
-  define custom "CGI" handlers for specific paths using a
   JavaScript-based API to create the response (headers and actual
   content)
-  run multiple servers at once on different ports (8080, 8081, 8082,
   and so on.)

This functionality should be more than enough for you to use it with any
test which requires HTTP-provided behavior.

Where you can use it
~~~~~~~~~~~~~~~~~~~~

The server is written primarily for use from ``xpcshell``-based
tests, and it can be used as an inline script or as an XPCOM component. The
Mochitest framework also uses it to serve its tests, and
`reftests <https://searchfox.org/mozilla-central/source/layout/tools/reftest/README.txt>`__
can optionally use it when their behavior is dependent upon specific
HTTP header values.

Ways you might use it
~~~~~~~~~~~~~~~~~~~~~

-  application update testing
-  cross-"server" security tests
-  cross-domain security tests, in combination with the right proxy
   settings (for example, using `Proxy
   AutoConfig <https://en.wikipedia.org/wiki/Proxy_auto-config>`__)
-  tests where the behavior is dependent on the values of HTTP headers
   (for example, Content-Type)
-  anything which requires use of files not stored locally
-  open-id : the users could provide their own open id server (they only
   need it when they're using their browser)
-  micro-blogging : users could host their own micro blog based on
   standards like RSS/Atom
-  rest APIs : web application could interact with REST or SOAP APIs for
   many purposes like : file/data storage, social sharing and so on
-  download testing

Using the server
~~~~~~~~~~~~~~~~

The best and first place you should look for documentation is
``netwerk/test/httpserver/nsIHttpServer.idl``. It's extremely
comprehensive and detailed, and it should be enough to figure out how to
make the server do what you want. I also suggest taking a look at the
less-comprehensive server
`README <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/README>`__,
although the IDL should usually be sufficient.

Running the server
^^^^^^^^^^^^^^^^^^

From test suites, the server should be importable as a testing-only JS
module:

.. code:: javascript

   ChromeUtils.import("resource://testing-common/httpd.js");

Once you've done that, you can create a new server as follows:

.. code:: javascript

   let server = new HttpServer(); // Or nsHttpServer() if you don't use ChromeUtils.import.

   server.registerDirectory("/", nsILocalFileForBasePath);

   server.start(-1); // uses a random available port, allows us to run tests concurrently
   const SERVER_PORT = server.identity.primaryPort; // you can use this further on

   // and when the tests are done, most likely from a callback...
   server.stop(function() { /* continue execution here */ });

You can also pass in a numeric port argument to the ``start()`` method,
but we strongly suggest you don't do it. Using a dynamic port allow us
to run your test in parallel with other tests which reduces wait times
and makes everybody happy.  If you really have to use a hardcoded port,
you will have to annotate your test in the xpcshell manifest file with
``run-sequentially = REASON``.
However, this should only be used as the last possible option.

.. note::

   Note: You **must** make sure to stop the server (the last line above)
   before your test completes. Failure to do so will result in the
   "XPConnect is being called on a scope without a Components property"
   assertion, which will cause your test to fail in debug builds, and
   you'll make people running tests grumbly because you've broken the
   tests.

Debugging errors
^^^^^^^^^^^^^^^^

The server's default error pages don't give much information, partly
because the error-dispatch mechanism doesn't currently accommodate doing
so and partly because exposing errors in a real server could make it
easier to exploit them. If you don't know why the server is acting a
particular way, edit
`httpd.js <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/httpd.js>`__
and change the value of ``DEBUG`` to ``true``. This will cause the
server to print information about the processing of requests (and errors
encountered doing so) to the console, and it's usually not difficult to
determine why problems exist from that output. ``DEBUG`` is ``false`` by
default because the information printed with it set to ``true``
unnecessarily obscures tinderbox output.

Header modification for files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The server supports modifying the headers of the files (not request
handlers) it serves. To modify the headers for a file, create a sibling
file with the first file's name followed by ``^headers^``. Here's an
example of how such a file might look:

.. code::

   HTTP 404 I want a cool HTTP description!
   Content-Type: text/plain

The status line is optional; all other lines specify HTTP headers in the
standard HTTP format. Any line ending style is accepted, and the file
may optionally end with a single newline character, to play nice with
Unix text tools like ``diff`` and ``hg``.

Hidden files
^^^^^^^^^^^^

Any file which ends with a single ``^`` is inaccessible when querying
the web server; if you try to access such a file you'll get a
``404 File Not Found`` page instead. If for some reason you need to
serve a file ending with a ``^``, just tack another ``^`` onto the end
of the file name and the file will then become available at the
single-``^`` location.

At the moment this feature is basically a way to smuggle header
modification for files into the file system without making those files
accessible to clients; it remains to be seen whether and how hidden-file
capabilities will otherwise be used.

SJS: server-side scripts
^^^^^^^^^^^^^^^^^^^^^^^^

Support for server-side scripts is provided through the SJS mechanism.
Essentially an SJS is a file with a particular extension, chosen by the
creator of the server, which contains a function with the name
``handleRequest`` which is called to determine the response the server
will generate. That function acts exactly like the ``handle`` function
on the ``nsIHttpRequestHandler`` interface. First, tell the server what
extension you're using:

.. code:: javascript

   const SJS_EXTENSION = "cgi";
   server.registerContentType(SJS_EXTENSION, "sjs");

Now just create an SJS with the extension ``cgi`` and write whatever you
want. For example:

.. code:: javascript

   function handleRequest(request, response)
   {
     response.setStatusLine(request.httpVersion, 200, "OK");
     response.write("Hello world!  This request was dynamically " +
                    "generated at " + new Date().toUTCString());
   }

Further examples may be found `in the Mozilla source
tree <https://searchfox.org/mozilla-central/search?q=&path=.sjs>`__
in existing tests. The request object is an instance of
``nsIHttpRequest`` and the response is a ``nsIHttpResponse``.
Please refer to the `IDL
documentation <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/nsIHttpServer.idl>`
for more details.

Storing information across requests
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

HTTP is basically a stateless protocol, and the httpd.js server API is
for the most part similarly stateless. If you're using the server
through the XPCOM interface you can simply store whatever state you want
in enclosing environments or global variables. However, if you're using
it through an SJS your request is processed in a near-empty environment
every time processing occurs. To support stateful SJS behavior, the
following functions have been added to the global scope in which a SJS
handler executes, providing a simple key-value state storage mechanism:

.. code::

   /*
    * v : T means v is of type T
    * function A() : T means A() has type T
    */

   function getState(key : string) : string
   function setState(key : string, value : string)
   function getSharedState(key : string) : string
   function setSharedState(key : string, value : string)
   function getObjectState(key : string, callback : function(value : object) : void) // SJS API, XPCOM differs, see below
   function setObjectState(key : string, value : object)

A key is a string with arbitrary contents. The corresponding value is
also a string, for the non-object-saving functions. For the
object-saving functions, it is (wait for it) an object, or also
``null``. Initially all keys are associated with the empty string or
with ``null``, depending on whether the function accesses string- or
object-valued storage. A stored value persists across requests and
across server shutdowns and restarts. The state methods are available
both in SJS and, for convenience when working with the server both via
XPCOM and via SJS, XPCOM through the ``nsIHttpServer`` interface. The
variants are designed to support different needs.

.. warning::

   **Warning:** Be careful using state: you, the user, are responsible
   for synchronizing all uses of state through any of the available
   methods. (This includes the methods that act only on per-path state:
   you might still run into trouble there if your request handler
   generates responses asynchronously. Further, any code with access to
   the server XPCOM component could modify it between requests even if
   you only ever used or modified that state while generating
   synchronous responses.) JavaScript's run-to-completion behavior will
   save you in simple cases, but with anything moderately complex you
   are playing with fire, and if you do it wrong you will get burned.

``getState`` and ``setState``
'''''''''''''''''''''''''''''

``getState`` and ``setState`` are designed for the case where a single
request handler needs to store information from a first request of it
for use in processing a second request of it — say, for example, if you
wanted to implement a request handler implementing a counter:

.. code:: javascript

   /**
    * Generates a response whose body is "0", "1", "2", and so on. each time a
    * request is made.  (Note that browser caching might make it appear
    * to not quite have that behavior; a Cache-Control header would fix
    * that issue if desired.)
    */
   function handleRequest(request, response)
   {
     var counter = +getState("counter"); // convert to number; +"" === 0
     response.write("" + counter);
     setState("counter", "" + ++counter);
   }

The useful feature of these two methods is that this state doesn't bleed
outside the single path at which it resides. For example, if the above
SJS were at ``/counter``, the value returned by ``getState("counter")``
at some other path would be completely distinct from the counter
implemented above. This makes it much simpler to write stateful handlers
without state accidentally bleeding between unrelated handlers.

.. note::

   **Note:** State saved by this method is specific to the HTTP path,
   excluding query string and hash reference. ``/counter``,
   ``/counter?foo``, and ``/counter?bar#baz`` all share the same state
   for the purposes of these methods. (Indeed, non-shared state would be
   significantly less useful if it changed when the query string
   changed!)

.. note::

   **Note:** The predefined ``__LOCATION__`` state
   contains the native path of the SJS file itself. You can pass the
   result directly to the ``nsILocalFile.initWithPath()``. Example:
   ``thisSJSfile.initWithPath(getState('__LOCATION__'));``

``getSharedState`` and ``setSharedState``
'''''''''''''''''''''''''''''''''''''''''

``getSharedState`` and ``setSharedState`` make up the functionality
intentionally not supported by ``getState`` and set\ ``State``: state
that exists between different paths. If you used the above handler at
the paths ``/sharedCounters/1`` and ``/sharedCounters/2`` (changing the
state-calls to use shared state, of course), the first load of either
handler would return "0", a second load of either handler would return
"1", a third load either handler would return "2", and so on. This more
powerful functionality allows you to write cooperative handlers that
expose and manipulate a piece of shared state. Be careful! One test can
screw up another test pretty easily if it's not careful what it does
with this functionality.

``getObjectState`` and ``setObjectState``
'''''''''''''''''''''''''''''''''''''''''

``getObjectState`` and ``setObjectState`` support the remaining
functionality not provided by the above methods: storing non-string
values (object values or ``null``). These two methods are the same as
``getSharedState`` and ``setSharedState``\ in that state is visible
across paths; ``setObjectState`` in one handler will expose that value
in another handler that uses ``getObjectState`` with the same key. (This
choice was intentional, because object values already expose mutable
state that you have to be careful about using.) This functionality is
particularly useful for cooperative request handlers where one request
*suspends* another, and that second request must then be *resumed* at a
later time by a third request. Without object-valued storage you'd need
to resort to polling on a string value using either of the previous
state APIs; with this, however, you can make precise callbacks exactly
when a particular event occurs.

``getObjectState`` in an SJS differs in one important way from
``getObjectState`` accessed via XPCOM. In XPCOM the method takes a
single string argument and returns the object or ``null`` directly. In
SJS, however, the process to return the value is slightly different:

.. code:: javascript

   function handleRequest(request, response)
   {
     var key = request.hasHeader("key")
             ? request.getHeader("key")
             : "unspecified";
     var obj = null;
     getObjectState(key, function(objval)
     {
       // This function is called synchronously with the object value
       // associated with key.
       obj = objval;
     });
     response.write("Keyed object " +
                    (obj && Object.prototype.hasOwnProperty.call(obj, "doStuff")
                    ? "has "
                    : "does not have ") +
                    "a doStuff method.");
   }

This idiosyncratic API is a restriction imposed by how sandboxes
currently work: external functions added to the sandbox can't return
object values when called within the sandbox. However, such functions
can accept and call callback functions, so we simply use a callback
function here to return the object value associated with the key.

Advanced dynamic response creation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The default behavior of request handlers is to fully construct the
response, return, and only then send the generated data. For certain use
cases, however, this is infeasible. For example, a handler which wanted
to return an extremely large amount of data (say, over 4GB on a 32-bit
system) might run out of memory doing so. Alternatively, precise control
over the timing of data transmission might be required so that, for
example, one request is received, "paused" while another request is
received and completes, and then finished. httpd.js solves this problem
by defining a ``processAsync()`` method which indicates to the server
that the response will be written and finished by the handler. Here's an
example of an SJS file which writes some data, waits five seconds, and
then writes some more data and finishes the response:

.. code:: javascript

   var timer = null;

   function handleRequest(request, response)
   {
     response.processAsync();
     response.setHeader("Content-Type", "text/plain", false);
     response.write("hello...");

     timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
     timer.initWithCallback(function()
     {
       response.write("world!");
       response.finish();
     }, 5 * 1000 /* milliseconds */, Ci.nsITimer.TYPE_ONE_SHOT);
   }

The basic flow is simple: call ``processAsync`` to mark the response as
being sent asynchronously, write data to the response body as desired,
and when complete call ``finish()``. At the moment if you drop such a
response on the floor, nothing will ever terminate the connection, and
the server cannot be stopped (the stop API is asynchronous and
callback-based); in the future a default connection timeout will likely
apply, but for now, "don't do that".

Full documentation for ``processAsync()`` and its interactions with
other methods may, as always, be found in
``netwerk/test/httpserver/nsIHttpServer.idl``.

Manual, arbitrary response creation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The standard mode of response creation is fully synchronous and is
guaranteed to produce syntactically correct responses (excluding
headers, which for the most part may be set to arbitrary values).
Asynchronous processing enables the introduction of response handling
coordinated with external events, but again, for the most part only
syntactically correct responses may be generated. The third method of
processing removes the correct-syntax property by allowing a response to
contain completely arbitrary data through the ``seizePower()`` method.
After this method is called, any data subsequently written to the
response is written directly to the network as the response, skipping
headers and making no attempt whatsoever to ensure any formatting of the
transmitted data. As with asynchronous processing, the response is
generated asynchronously and must be finished manually for the
connection to be closed. (Again, nothing will terminate the connection
for a response dropped on the floor, so again, "don't do that".) This
mode of processing is useful for testing particular data formats that
are either not HTTP or which do not match the precise, canonical
representation that httpd.js generates. Here's an example of an SJS file
which writes an apparent HTTP response whose status text contains a null
byte (not allowed by HTTP/1.1, and attempting to set such status text
through httpd.js would throw an exception) and which has a header that
spans multiple lines (httpd.js responses otherwise generate only
single-line headers):

.. code:: javascript

   function handleRequest(request, response)
   {
     response.seizePower();
     response.write("HTTP/1.1 200 OK Null byte \u0000 makes this response malformed\r\n" +
                    "X-Underpants-Gnomes-Strategy:\r\n" +
                    " Phase 1: Collect underpants.\r\n" +
                    " Phase 2: ...\r\n" +
                    " Phase 3: Profit!\r\n" +
                    "\r\n" +
                    "FAIL");
     response.finish();
   }

While the asynchronous mode is capable of producing certain forms of
invalid responses (through setting a bogus Content-Length header prior
to the start of body transmission, among others), it must not be used in
this manner. No effort will be made to preserve such implementation
quirks (indeed, some are even likely to be removed over time): if you
want to send malformed data, use ``seizePower()`` instead.

Full documentation for ``seizePower()`` and its interactions with other
methods may, as always, be found in
``netwerk/test/httpserver/nsIHttpServer.idl``.

Example uses of the server
~~~~~~~~~~~~~~~~~~~~~~~~~~

Shorter examples (for tests which only do one test):

-  ``netwerk/test/unit/test_bug331825.js``
-  ``netwerk/test/unit/test_httpcancel.js``
-  ``netwerk/test/unit/test_cookie_header.js``

Longer tests (where you'd need to do multiple async server requests):

-  ``netwerk/test/httpserver/test/test_setstatusline.js``
-  ``netwerk/test/unit/test_content_sniffer.js``
-  ``netwerk/test/unit/test_authentication.js``
-  ``netwerk/test/unit/test_event_sink.js``
-  ``netwerk/test/httpserver/test/``

Examples of modifying HTTP headers in files may be found at
``netwerk/test/httpserver/test/data/cern_meta/``.

Future directions
~~~~~~~~~~~~~~~~~

The server, while very functional, is not yet complete. There are a
number of things to fix and features to add, among them support for
pipelining, support for incrementally-received requests (rather than
buffering the entire body before invoking a request handler), and better
conformance to the MUSTs and SHOULDs of HTTP/1.1. If you have
suggestions for functionality or find bugs, file them in
`Testing-httpd.js <https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=General>`__
.