http_server_for_testing.rst (20940B)
1 HTTP Server for Unit Tests 2 ========================== 3 4 This page describes the JavaScript implementation of an 5 HTTP server located in ``netwerk/test/httpserver/``. 6 7 Server Functionality 8 ~~~~~~~~~~~~~~~~~~~~ 9 10 Here are some of the things you can do with the server: 11 12 - map a directory of files onto an HTTP path on the server, for an 13 arbitrary number of such directories (including nested directories) 14 - define custom error handlers for HTTP error codes 15 - serve a given file for requests for a specific path, optionally with 16 custom headers and status 17 - define custom "CGI" handlers for specific paths using a 18 JavaScript-based API to create the response (headers and actual 19 content) 20 - run multiple servers at once on different ports (8080, 8081, 8082, 21 and so on.) 22 23 This functionality should be more than enough for you to use it with any 24 test which requires HTTP-provided behavior. 25 26 Where You Can Use It 27 ~~~~~~~~~~~~~~~~~~~~ 28 29 The server is written primarily for use from ``xpcshell``-based 30 tests, and it can be used as an inline script or as an XPCOM component. The 31 Mochitest framework also uses it to serve its tests, and 32 `reftests <https://searchfox.org/mozilla-central/source/layout/tools/reftest/README.txt>`__ 33 can optionally use it when their behavior is dependent upon specific 34 HTTP header values. 35 36 Ways You Might Use It 37 ~~~~~~~~~~~~~~~~~~~~~ 38 39 - application update testing 40 - cross-"server" security tests 41 - cross-domain security tests, in combination with the right proxy 42 settings (for example, using `Proxy 43 AutoConfig <https://en.wikipedia.org/wiki/Proxy_auto-config>`__) 44 - tests where the behavior is dependent on the values of HTTP headers 45 (for example, Content-Type) 46 - anything which requires use of files not stored locally 47 - open-id : the users could provide their own open id server (they only 48 need it when they're using their browser) 49 - micro-blogging : users could host their own micro blog based on 50 standards like RSS/Atom 51 - rest APIs : web application could interact with REST or SOAP APIs for 52 many purposes like : file/data storage, social sharing and so on 53 - download testing 54 55 Using the Server 56 ~~~~~~~~~~~~~~~~ 57 58 The best and first place you should look for documentation is 59 ``netwerk/test/httpserver/nsIHttpServer.idl``. It's extremely 60 comprehensive and detailed, and it should be enough to figure out how to 61 make the server do what you want. I also suggest taking a look at the 62 less-comprehensive server 63 `README <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/README>`__, 64 although the IDL should usually be sufficient. 65 66 Running the Server 67 ^^^^^^^^^^^^^^^^^^ 68 69 From test suites, the server should be importable as a testing-only JS 70 module: 71 72 .. code:: JavaScript 73 74 ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs"); 75 76 Once you've done that, you can create a new server as follows: 77 78 .. code:: JavaScript 79 80 let server = new HttpServer(); // Or nsHttpServer() if you don't use ChromeUtils.importESModule. 81 82 server.registerDirectory("/", nsILocalFileForBasePath); 83 84 server.start(-1); // uses a random available port, allows us to run tests concurrently 85 const SERVER_PORT = server.identity.primaryPort; // you can use this further on 86 87 // and when the tests are done, most likely from a callback... 88 server.stop(function() { /* continue execution here */ }); 89 90 You can also pass in a numeric port argument to the ``start()`` method, 91 but we strongly suggest you don't do it. Using a dynamic port allow us 92 to run your test in parallel with other tests which reduces wait times 93 and makes everybody happy. If you really have to use a hardcoded port, 94 you will have to annotate your test in the xpcshell manifest file with 95 ``run-sequentially = REASON``. 96 However, this should only be used as the last possible option. 97 98 .. note:: 99 100 You **must** make sure to stop the server (the last line above) 101 before your test completes. Failure to do so will result in the 102 "XPConnect is being called on a scope without a Components property" 103 assertion, which will cause your test to fail in debug builds, and 104 you'll make people running tests grumbly because you've broken the 105 tests. 106 107 Debugging Errors 108 ^^^^^^^^^^^^^^^^ 109 110 The server's default error pages don't give much information, partly 111 because the error-dispatch mechanism doesn't currently accommodate doing 112 so and partly because exposing errors in a real server could make it 113 easier to exploit them. If you don't know why the server is acting a 114 particular way, edit 115 `httpd.sys.mjs <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/httpd.sys.mjs>`__ 116 and change the value of ``DEBUG`` to ``true``. This will cause the 117 server to print information about the processing of requests (and errors 118 encountered doing so) to the console, and it's usually not difficult to 119 determine why problems exist from that output. ``DEBUG`` is ``false`` by 120 default because the information printed with it set to ``true`` 121 unnecessarily obscures tinderbox output. 122 123 Header Modification for Files 124 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 125 126 The server supports modifying the headers of the files (not request 127 handlers) it serves. To modify the headers for a file, create a sibling 128 file with the first file's name followed by ``^headers^``. Here's an 129 example of how such a file might look: 130 131 .. code:: text 132 133 HTTP 404 I want a cool HTTP description! 134 Content-Type: text/plain 135 136 The status line is optional; all other lines specify HTTP headers in the 137 standard HTTP format. Any line ending style is accepted, and the file 138 may optionally end with a single newline character, to play nice with 139 Unix text tools like ``diff`` and ``hg``. 140 141 Hidden Files 142 ^^^^^^^^^^^^ 143 144 Any file which ends with a single ``^`` is inaccessible when querying 145 the web server; if you try to access such a file you'll get a 146 ``404 File Not Found`` page instead. If for some reason you need to 147 serve a file ending with a ``^``, just tack another ``^`` onto the end 148 of the file name and the file will then become available at the 149 single-``^`` location. 150 151 At the moment this feature is basically a way to smuggle header 152 modification for files into the file system without making those files 153 accessible to clients; it remains to be seen whether and how hidden-file 154 capabilities will otherwise be used. 155 156 SJS: Server-Side Scripts 157 ^^^^^^^^^^^^^^^^^^^^^^^^ 158 159 Support for server-side scripts is provided through the SJS mechanism. 160 Essentially an SJS is a file with a particular extension, chosen by the 161 creator of the server, which contains a function with the name 162 ``handleRequest`` which is called to determine the response the server 163 will generate. That function acts exactly like the ``handle`` function 164 on the ``nsIHttpRequestHandler`` interface. First, tell the server what 165 extension you're using: 166 167 .. code:: JavaScript 168 169 const SJS_EXTENSION = "cgi"; 170 server.registerContentType(SJS_EXTENSION, "sjs"); 171 172 Now just create an SJS with the extension ``cgi`` and write whatever you 173 want. For example: 174 175 .. code:: JavaScript 176 177 function handleRequest(request, response) 178 { 179 response.setStatusLine(request.httpVersion, 200, "OK"); 180 response.write("Hello world! This request was dynamically " + 181 "generated at " + new Date().toUTCString()); 182 } 183 184 Further examples may be found `in the Mozilla source 185 tree <https://searchfox.org/mozilla-central/search?q=&path=.sjs>`__ 186 in existing tests. The request object is an instance of 187 ``nsIHttpRequest`` and the response is a ``nsIHttpResponse``. 188 Please refer to the `IDL 189 documentation <https://searchfox.org/mozilla-central/source/netwerk/test/httpserver/nsIHttpServer.idl>` 190 for more details. 191 192 Storing Information Across Requests 193 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 194 195 HTTP is basically a stateless protocol, and the httpd.js server API is 196 for the most part similarly stateless. If you're using the server 197 through the XPCOM interface you can simply store whatever state you want 198 in enclosing environments or global variables. However, if you're using 199 it through an SJS your request is processed in a near-empty environment 200 every time processing occurs. To support stateful SJS behavior, the 201 following functions have been added to the global scope in which a SJS 202 handler executes, providing a simple key-value state storage mechanism: 203 204 .. code:: JavaScript 205 206 /* 207 * v : T means v is of type T 208 * function A() : T means A() has type T 209 */ 210 211 function getState(key : string) : string 212 function setState(key : string, value : string) 213 function getSharedState(key : string) : string 214 function setSharedState(key : string, value : string) 215 function getObjectState(key : string, callback : function(value : object) : void) // SJS API, XPCOM differs, see below 216 function setObjectState(key : string, value : object) 217 218 A key is a string with arbitrary contents. The corresponding value is 219 also a string, for the non-object-saving functions. For the 220 object-saving functions, it is (wait for it) an object, or also 221 ``null``. Initially all keys are associated with the empty string or 222 with ``null``, depending on whether the function accesses string- or 223 object-valued storage. A stored value persists across requests and 224 across server shutdowns and restarts. The state methods are available 225 both in SJS and, for convenience when working with the server both via 226 XPCOM and via SJS, XPCOM through the ``nsIHttpServer`` interface. The 227 variants are designed to support different needs. 228 229 .. warning:: 230 231 **Warning:** Be careful using state: you, the user, are responsible 232 for synchronizing all uses of state through any of the available 233 methods. (This includes the methods that act only on per-path state: 234 you might still run into trouble there if your request handler 235 generates responses asynchronously. Further, any code with access to 236 the server XPCOM component could modify it between requests even if 237 you only ever used or modified that state while generating 238 synchronous responses.) JavaScript's run-to-completion behavior will 239 save you in simple cases, but with anything moderately complex you 240 are playing with fire, and if you do it wrong you will get burned. 241 242 ``getState`` and ``setState`` 243 ''''''''''''''''''''''''''''' 244 245 ``getState`` and ``setState`` are designed for the case where a single 246 request handler needs to store information from a first request of it 247 for use in processing a second request of it — say, for example, if you 248 wanted to implement a request handler implementing a counter: 249 250 .. code:: JavaScript 251 252 /** 253 * Generates a response whose body is "0", "1", "2", and so on. each time a 254 * request is made. (Note that browser caching might make it appear 255 * to not quite have that behavior; a Cache-Control header would fix 256 * that issue if desired.) 257 */ 258 function handleRequest(request, response) 259 { 260 var counter = +getState("counter"); // convert to number; +"" === 0 261 response.write("" + counter); 262 setState("counter", "" + ++counter); 263 } 264 265 The useful feature of these two methods is that this state doesn't bleed 266 outside the single path at which it resides. For example, if the above 267 SJS were at ``/counter``, the value returned by ``getState("counter")`` 268 at some other path would be completely distinct from the counter 269 implemented above. This makes it much simpler to write stateful handlers 270 without state accidentally bleeding between unrelated handlers. 271 272 .. note:: 273 274 State saved by this method is specific to the HTTP path, 275 excluding query string and hash reference. ``/counter``, 276 ``/counter?foo``, and ``/counter?bar#baz`` all share the same state 277 for the purposes of these methods. (Indeed, non-shared state would be 278 significantly less useful if it changed when the query string 279 changed!) 280 281 .. note:: 282 283 The predefined ``__LOCATION__`` state 284 contains the native path of the SJS file itself. You can pass the 285 result directly to the ``nsILocalFile.initWithPath()``. Example: 286 ``thisSJSfile.initWithPath(getState('__LOCATION__'));`` 287 288 ``getSharedState`` and ``setSharedState`` 289 ''''''''''''''''''''''''''''''''''''''''' 290 291 ``getSharedState`` and ``setSharedState`` make up the functionality 292 intentionally not supported by ``getState`` and set\ ``State``: state 293 that exists between different paths. If you used the above handler at 294 the paths ``/sharedCounters/1`` and ``/sharedCounters/2`` (changing the 295 state-calls to use shared state, of course), the first load of either 296 handler would return "0", a second load of either handler would return 297 "1", a third load either handler would return "2", and so on. This more 298 powerful functionality allows you to write cooperative handlers that 299 expose and manipulate a piece of shared state. Be careful! One test can 300 screw up another test pretty easily if it's not careful what it does 301 with this functionality. 302 303 ``getObjectState`` and ``setObjectState`` 304 ''''''''''''''''''''''''''''''''''''''''' 305 306 ``getObjectState`` and ``setObjectState`` support the remaining 307 functionality not provided by the above methods: storing non-string 308 values (object values or ``null``). These two methods are the same as 309 ``getSharedState`` and ``setSharedState``\ in that state is visible 310 across paths; ``setObjectState`` in one handler will expose that value 311 in another handler that uses ``getObjectState`` with the same key. (This 312 choice was intentional, because object values already expose mutable 313 state that you have to be careful about using.) This functionality is 314 particularly useful for cooperative request handlers where one request 315 *suspends* another, and that second request must then be *resumed* at a 316 later time by a third request. Without object-valued storage you'd need 317 to resort to polling on a string value using either of the previous 318 state APIs; with this, however, you can make precise callbacks exactly 319 when a particular event occurs. 320 321 ``getObjectState`` in an SJS differs in one important way from 322 ``getObjectState`` accessed via XPCOM. In XPCOM the method takes a 323 single string argument and returns the object or ``null`` directly. In 324 SJS, however, the process to return the value is slightly different: 325 326 .. code:: JavaScript 327 328 function handleRequest(request, response) 329 { 330 var key = request.hasHeader("key") 331 ? request.getHeader("key") 332 : "unspecified"; 333 var obj = null; 334 getObjectState(key, function(objval) 335 { 336 // This function is called synchronously with the object value 337 // associated with key. 338 obj = objval; 339 }); 340 response.write("Keyed object " + 341 (obj && Object.prototype.hasOwnProperty.call(obj, "doStuff") 342 ? "has " 343 : "does not have ") + 344 "a doStuff method."); 345 } 346 347 This idiosyncratic API is a restriction imposed by how sandboxes 348 currently work: external functions added to the sandbox can't return 349 object values when called within the sandbox. However, such functions 350 can accept and call callback functions, so we simply use a callback 351 function here to return the object value associated with the key. 352 353 Advanced Dynamic Response Creation 354 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 355 356 The default behavior of request handlers is to fully construct the 357 response, return, and only then send the generated data. For certain use 358 cases, however, this is infeasible. For example, a handler which wanted 359 to return an extremely large amount of data (say, over 4GB on a 32-bit 360 system) might run out of memory doing so. Alternatively, precise control 361 over the timing of data transmission might be required so that, for 362 example, one request is received, "paused" while another request is 363 received and completes, and then finished. httpd.js solves this problem 364 by defining a ``processAsync()`` method which indicates to the server 365 that the response will be written and finished by the handler. Here's an 366 example of an SJS file which writes some data, waits five seconds, and 367 then writes some more data and finishes the response: 368 369 .. code:: JavaScript 370 371 var timer = null; 372 373 function handleRequest(request, response) 374 { 375 response.processAsync(); 376 response.setHeader("Content-Type", "text/plain", false); 377 response.write("hello..."); 378 379 timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 380 timer.initWithCallback(function() 381 { 382 response.write("world!"); 383 response.finish(); 384 }, 5 * 1000 /* milliseconds */, Ci.nsITimer.TYPE_ONE_SHOT); 385 } 386 387 The basic flow is simple: call ``processAsync`` to mark the response as 388 being sent asynchronously, write data to the response body as desired, 389 and when complete call ``finish()``. At the moment if you drop such a 390 response on the floor, nothing will ever terminate the connection, and 391 the server cannot be stopped (the stop API is asynchronous and 392 callback-based); in the future a default connection timeout will likely 393 apply, but for now, "don't do that". 394 395 Full documentation for ``processAsync()`` and its interactions with 396 other methods may, as always, be found in 397 ``netwerk/test/httpserver/nsIHttpServer.idl``. 398 399 Manual, Arbitrary Response Creation 400 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 401 402 The standard mode of response creation is fully synchronous and is 403 guaranteed to produce syntactically correct responses (excluding 404 headers, which for the most part may be set to arbitrary values). 405 Asynchronous processing enables the introduction of response handling 406 coordinated with external events, but again, for the most part only 407 syntactically correct responses may be generated. The third method of 408 processing removes the correct-syntax property by allowing a response to 409 contain completely arbitrary data through the ``seizePower()`` method. 410 After this method is called, any data subsequently written to the 411 response is written directly to the network as the response, skipping 412 headers and making no attempt whatsoever to ensure any formatting of the 413 transmitted data. As with asynchronous processing, the response is 414 generated asynchronously and must be finished manually for the 415 connection to be closed. (Again, nothing will terminate the connection 416 for a response dropped on the floor, so again, "don't do that".) This 417 mode of processing is useful for testing particular data formats that 418 are either not HTTP or which do not match the precise, canonical 419 representation that httpd.js generates. Here's an example of an SJS file 420 which writes an apparent HTTP response whose status text contains a null 421 byte (not allowed by HTTP/1.1, and attempting to set such status text 422 through httpd.js would throw an exception) and which has a header that 423 spans multiple lines (httpd.js responses otherwise generate only 424 single-line headers): 425 426 .. code:: JavaScript 427 428 function handleRequest(request, response) 429 { 430 response.seizePower(); 431 response.write("HTTP/1.1 200 OK Null byte \u0000 makes this response malformed\r\n" + 432 "X-Underpants-Gnomes-Strategy:\r\n" + 433 " Phase 1: Collect underpants.\r\n" + 434 " Phase 2: ...\r\n" + 435 " Phase 3: Profit!\r\n" + 436 "\r\n" + 437 "FAIL"); 438 response.finish(); 439 } 440 441 While the asynchronous mode is capable of producing certain forms of 442 invalid responses (through setting a bogus Content-Length header prior 443 to the start of body transmission, among others), it must not be used in 444 this manner. No effort will be made to preserve such implementation 445 quirks (indeed, some are even likely to be removed over time): if you 446 want to send malformed data, use ``seizePower()`` instead. 447 448 Full documentation for ``seizePower()`` and its interactions with other 449 methods may, as always, be found in 450 ``netwerk/test/httpserver/nsIHttpServer.idl``. 451 452 Example Uses of the Server 453 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 454 455 Shorter examples (for tests which only do one test): 456 457 - ``netwerk/test/unit/test_bug331825.js`` 458 - ``netwerk/test/unit/test_httpcancel.js`` 459 - ``netwerk/test/unit/test_cookie_header.js`` 460 461 Longer tests (where you'd need to do multiple async server requests): 462 463 - ``netwerk/test/httpserver/test/test_setstatusline.js`` 464 - ``netwerk/test/unit/test_content_sniffer.js`` 465 - ``netwerk/test/unit/test_authentication.js`` 466 - ``netwerk/test/unit/test_event_sink.js`` 467 - ``netwerk/test/httpserver/test/`` 468 469 Examples of modifying HTTP headers in files may be found at 470 ``netwerk/test/httpserver/test/data/cern_meta/``. 471 472 Future Directions 473 ~~~~~~~~~~~~~~~~~ 474 475 The server, while very functional, is not yet complete. There are a 476 number of things to fix and features to add, among them support for 477 pipelining, support for incrementally-received requests (rather than 478 buffering the entire body before invoking a request handler), and better 479 conformance to the MUSTs and SHOULDs of HTTP/1.1. If you have 480 suggestions for functionality or find bugs, file them in 481 `Testing-httpd.js <https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=General>`__ 482 .