Bug 1134073 - Part 3: Show network request cause and stacktrace in netmonitor - mochitests. r=ochameau
☠☠ backed out by ab5e81678aaa ☠ ☠
authorJarda Snajdr <jsnajdr@gmail.com>
Fri, 03 Jun 2016 16:26:35 +0200
changeset 339367 a43d99734390d98a6430d2e0900222651546456e
parent 339366 572ebec612e811adc0333a3b486bc25b680957bb
child 339368 094a647ca9c259002607cea50b15538f79bda932
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1134073
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1134073 - Part 3: Show network request cause and stacktrace in netmonitor - mochitests. r=ochameau
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_cause.js
devtools/client/netmonitor/test/browser_net_cause_redirect.js
devtools/client/netmonitor/test/browser_net_image-tooltip.js
devtools/client/netmonitor/test/browser_net_service-worker-status.js
devtools/client/netmonitor/test/head.js
devtools/client/netmonitor/test/html_cause-test-page.html
devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js
devtools/client/netmonitor/test/service-workers/status-codes.html
devtools/client/netmonitor/test/sjs_hsts-test-server.sjs
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   dropmarker.svg
   head.js
+  html_cause-test-page.html
   html_content-type-test-page.html
   html_content-type-without-cache-test-page.html
   html_cors-test-page.html
   html_custom-get-page.html
   html_single-get-page.html
   html_cyrillic-test-page.html
   html_filter-test-page.html
   html_infinite-get-page.html
@@ -28,30 +29,33 @@ support-files =
   html_statistics-test-page.html
   html_status-codes-test-page.html
   html_api-calls-test-page.html
   html_copy-as-curl.html
   html_curl-utils.html
   sjs_content-type-test-server.sjs
   sjs_cors-test-server.sjs
   sjs_https-redirect-test-server.sjs
+  sjs_hsts-test-server.sjs
   sjs_simple-test-server.sjs
   sjs_sorting-test-server.sjs
   sjs_status-codes-test-server.sjs
   test-image.png
   service-workers/status-codes.html
   service-workers/status-codes-service-worker.js
 
 [browser_net_aaa_leaktest.js]
 [browser_net_accessibility-01.js]
 [browser_net_accessibility-02.js]
 skip-if = (toolkit == "cocoa" && e10s) # bug 1252254
 [browser_net_api-calls.js]
 [browser_net_autoscroll.js]
 [browser_net_cached-status.js]
+[browser_net_cause.js]
+[browser_net_cause_redirect.js]
 [browser_net_service-worker-status.js]
 [browser_net_charts-01.js]
 [browser_net_charts-02.js]
 [browser_net_charts-03.js]
 [browser_net_charts-04.js]
 [browser_net_charts-05.js]
 [browser_net_charts-06.js]
 [browser_net_charts-07.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cause.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if request cause is reported correctly.
+ */
+
+const CAUSE_FILE_NAME = "html_cause-test-page.html";
+const CAUSE_URL = EXAMPLE_URL + CAUSE_FILE_NAME;
+
+const EXPECTED_REQUESTS = [
+  {
+    method: "GET",
+    url: CAUSE_URL,
+    causeType: "document",
+    causeUri: "",
+    // The document load is from JS function in e10s, native in non-e10s
+    hasStack: !gMultiProcessBrowser
+  },
+  {
+    method: "GET",
+    url: EXAMPLE_URL + "stylesheet_request",
+    causeType: "stylesheet",
+    causeUri: CAUSE_URL,
+    hasStack: false
+  },
+  {
+    method: "GET",
+    url: EXAMPLE_URL + "img_request",
+    causeType: "img",
+    causeUri: CAUSE_URL,
+    hasStack: false
+  },
+  {
+    method: "GET",
+    url: EXAMPLE_URL + "xhr_request",
+    causeType: "xhr",
+    causeUri: CAUSE_URL,
+    hasStack: { fn: "performXhrRequest", file: CAUSE_FILE_NAME, line: 22 }
+  },
+  {
+    method: "POST",
+    url: EXAMPLE_URL + "beacon_request",
+    causeType: "beacon",
+    causeUri: CAUSE_URL,
+    hasStack: { fn: "performBeaconRequest", file: CAUSE_FILE_NAME, line: 26 }
+  },
+];
+
+var test = Task.async(function* () {
+  // the initNetMonitor function clears the network request list after the
+  // page is loaded. That's why we first load a bogus page from SIMPLE_URL,
+  // and only then load the real thing from CAUSE_URL - we want to catch
+  // all the requests the page is making, not only the XHRs.
+  // We can't use about:blank here, because initNetMonitor checks that the
+  // page has actually made at least one request.
+  let [, debuggee, monitor] = yield initNetMonitor(SIMPLE_URL);
+  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  debuggee.location = CAUSE_URL;
+
+  yield waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
+
+  is(RequestsMenu.itemCount, EXPECTED_REQUESTS.length,
+    "All the page events should be recorded.");
+
+  EXPECTED_REQUESTS.forEach((spec, i) => {
+    let { method, url, causeType, causeUri, hasStack } = spec;
+
+    let requestItem = RequestsMenu.getItemAtIndex(i);
+    verifyRequestItemTarget(requestItem,
+      method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
+    );
+
+    let { stacktrace } = requestItem.attachment.cause;
+    let stackLen = stacktrace ? stacktrace.length : 0;
+
+    if (hasStack) {
+      ok(stacktrace, `Request #${i} has a stacktrace`);
+      ok(stackLen > 0,
+        `Request #${i} (${causeType}) has a stacktrace with ${stackLen} items`);
+
+      // if "hasStack" is object, check the details about the top stack frame
+      if (typeof hasStack === "object") {
+        is(stacktrace[0].functionName, hasStack.fn,
+          `Request #${i} has the correct function on top of the JS stack`);
+        is(stacktrace[0].filename.split("/").pop(), hasStack.file,
+          `Request #${i} has the correct file on top of the JS stack`);
+        is(stacktrace[0].lineNumber, hasStack.line,
+          `Request #${i} has the correct line number on top of the JS stack`);
+      }
+    } else {
+      is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`);
+    }
+  });
+
+  yield teardown(monitor);
+  finish();
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cause_redirect.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if request JS stack is property reported if the request is internally
+ * redirected without hitting the network (HSTS is one of such cases)
+ */
+
+var test = Task.async(function* () {
+  const EXPECTED_REQUESTS = [
+    // Request to HTTP URL, redirects to HTTPS, has callstack
+    { status: 302, hasStack: true },
+    // Serves HTTPS, sets the Strict-Transport-Security header, no stack
+    { status: 200, hasStack: false },
+    // Second request to HTTP redirects to HTTPS internally
+    { status: 200, hasStack: true },
+  ];
+
+  let [, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL);
+  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+  RequestsMenu.lazyUpdate = false;
+
+  debuggee.performRequests(2, HSTS_SJS);
+  yield waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
+
+  EXPECTED_REQUESTS.forEach(({status, hasStack}, i) => {
+    let { attachment } = RequestsMenu.getItemAtIndex(i);
+
+    is(attachment.status, status, `Request #${i} has the expected status`);
+
+    let { stacktrace } = attachment.cause;
+    let stackLen = stacktrace ? stacktrace.length : 0;
+
+    if (hasStack) {
+      ok(stacktrace, `Request #${i} has a stacktrace`);
+      ok(stackLen > 0, `Request #${i} has a stacktrace with ${stackLen} items`);
+    } else {
+      is(stackLen, 0, `Request #${i} has an empty stacktrace`);
+    }
+  });
+
+  // Send a request to reset the HSTS policy to state before the test
+  debuggee.performRequests(1, HSTS_SJS + "?reset");
+  yield waitForNetworkEvents(monitor, 1);
+
+  yield teardown(monitor);
+  finish();
+});
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -18,42 +18,39 @@ add_task(function* test() {
   let onEvents = waitForNetworkEvents(monitor, 7);
   let onThumbnail = waitFor(monitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   debuggee.performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a few requests were made...");
-  yield showTooltipAndVerify(RequestsMenu.items[5]);
+  yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[5]);
 
   // 7 XHRs as before + 1 extra document reload
   onEvents = waitForNetworkEvents(monitor, 8);
   onThumbnail = waitFor(monitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   info("Reloading the debuggee and performing all requests again...");
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
   debuggee.performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a reload.");
-  yield showTooltipAndVerify(RequestsMenu.items[6]);
+  yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[6]);
 
   yield teardown(monitor);
   finish();
 
   /**
    * Show a tooltip on the {requestItem} and verify that it was displayed
    * with the expected content.
    */
-  function* showTooltipAndVerify(requestItem) {
-    let { tooltip } = requestItem.attachment;
-    ok(tooltip, "There should be a tooltip instance for the image request.");
-
+  function* showTooltipAndVerify(tooltip, requestItem) {
     let anchor = $(".requests-menu-file", requestItem.target);
     yield showTooltipOn(tooltip, anchor);
 
     info("Tooltip was successfully opened for the image request.");
     is(tooltip.content.querySelector("image").src, TEST_IMAGE_DATA_URI,
       "The tooltip's image content is displayed correctly.");
   }
 
--- a/devtools/client/netmonitor/test/browser_net_service-worker-status.js
+++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
@@ -8,48 +8,64 @@
  */
 
 // Service workers only work on https
 const URL = EXAMPLE_URL.replace("http:", "https:");
 
 const TEST_URL = URL + "service-workers/status-codes.html";
 
 var test = Task.async(function* () {
-  let [tab, debuggee, monitor] = yield initNetMonitor(TEST_URL, null, true);
+  let [, debuggee, monitor] = yield initNetMonitor(TEST_URL, null, true);
   info("Starting test... ");
 
-  let { document, L10N, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
 
   const REQUEST_DATA = [
     {
       method: "GET",
       uri: URL + "service-workers/test/200",
       details: {
         status: 200,
         statusText: "OK (service worker)",
         displayedStatus: "service worker",
         type: "plain",
         fullMimeType: "text/plain; charset=UTF-8"
-      }
+      },
+      stackFunctions: ["doXHR", "performRequests"]
     },
   ];
 
   info("Registering the service worker...");
   yield debuggee.registerServiceWorker();
 
   info("Performing requests...");
   debuggee.performRequests();
   yield waitForNetworkEvents(monitor, REQUEST_DATA.length);
 
   let index = 0;
   for (let request of REQUEST_DATA) {
     let item = RequestsMenu.getItemAtIndex(index);
 
-    info("Verifying request #" + index);
+    info(`Verifying request #${index}`);
     yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
 
+    let { stacktrace } = item.attachment.cause;
+    let stackLen = stacktrace ? stacktrace.length : 0;
+
+    ok(stacktrace, `Request #${index} has a stacktrace`);
+    ok(stackLen >= request.stackFunctions.length,
+      `Request #${index} has a stacktrace with enough (${stackLen}) items`);
+
+    request.stackFunctions.forEach((functionName, j) => {
+      is(stacktrace[j].functionName, functionName,
+      `Request #${index} has the correct function at position #${j} on the stack`);
+    });
+
     index++;
   }
 
+  info("Unregistering the service worker...");
+  yield debuggee.unregisterServiceWorker();
+
   yield teardown(monitor);
   finish();
 });
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -45,16 +45,20 @@ const SEND_BEACON_URL = EXAMPLE_URL + "h
 const CORS_URL = EXAMPLE_URL + "html_cors-test-page.html";
 
 const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
 const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
 const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
 const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";
 const HTTPS_REDIRECT_SJS = EXAMPLE_URL + "sjs_https-redirect-test-server.sjs";
 const CORS_SJS_PATH = "/browser/devtools/client/netmonitor/test/sjs_cors-test-server.sjs";
+const HSTS_SJS = EXAMPLE_URL + "sjs_hsts-test-server.sjs";
+
+const HSTS_BASE_URL = EXAMPLE_URL;
+const HSTS_PAGE_URL = CUSTOM_GET_URL;
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 
 DevToolsUtils.testing = true;
 SimpleTest.registerCleanupFunction(() => {
@@ -279,17 +283,17 @@ function verifyRequestItemTarget(aReques
 
   let requestsMenu = aRequestItem.ownerView;
   let widgetIndex = requestsMenu.indexOfItem(aRequestItem);
   let visibleIndex = requestsMenu.visibleItems.indexOf(aRequestItem);
 
   info("Widget index of item: " + widgetIndex);
   info("Visible index of item: " + visibleIndex);
 
-  let { fuzzyUrl, status, statusText, type, fullMimeType,
+  let { fuzzyUrl, status, statusText, cause, type, fullMimeType,
         transferred, size, time, displayedStatus } = aData;
   let { attachment, target } = aRequestItem;
 
   let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
   let unicodeUrl = NetworkHelper.convertToUnicode(unescape(aUrl));
   let name = NetworkHelper.convertToUnicode(unescape(uri.fileName || uri.filePath || "/"));
   let query = NetworkHelper.convertToUnicode(unescape(uri.query));
   let hostPort = uri.hostPort;
@@ -331,16 +335,25 @@ function verifyRequestItemTarget(aReques
     let tooltip = target.querySelector(".requests-menu-status").getAttribute("tooltiptext");
     info("Displayed status: " + value);
     info("Displayed code: " + codeValue);
     info("Tooltip status: " + tooltip);
     is(value, displayedStatus ? displayedStatus : status, "The displayed status is correct.");
     is(codeValue, status, "The displayed status code is correct.");
     is(tooltip, status + " " + statusText, "The tooltip status is correct.");
   }
+  if (cause !== undefined) {
+    let causeLabel = target.querySelector(".requests-menu-cause-label");
+    let value = causeLabel.getAttribute("value");
+    let tooltip = causeLabel.getAttribute("tooltiptext");
+    info("Displayed cause: " + value);
+    info("Tooltip cause: " + tooltip);
+    is(value, cause.type, "The displayed cause is correct.");
+    is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.")
+  }
   if (type !== undefined) {
     let value = target.querySelector(".requests-menu-type").getAttribute("value");
     let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext");
     info("Displayed type: " + value);
     info("Tooltip type: " + tooltip);
     is(value, type, "The displayed type is correct.");
     is(tooltip, fullMimeType, "The tooltip type is correct.");
   }
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_cause-test-page.html
@@ -0,0 +1,33 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <title>Network Monitor test page</title>
+    <link rel="stylesheet" type="text/css" href="stylesheet_request" />
+  </head>
+
+  <body>
+    <p>Request cause test</p>
+    <img src="img_request" />
+    <script type="text/javascript">
+      function performXhrRequest() {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", "xhr_request", true);
+        xhr.send();
+      }
+
+      function performBeaconRequest() {
+        navigator.sendBeacon("beacon_request");
+      }
+
+      performXhrRequest();
+      performBeaconRequest();
+    </script>
+  </body>
+</html>
--- a/devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js
+++ b/devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js
@@ -1,8 +1,15 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-addEventListener("fetch", function (event) {
+"use strict";
+
+self.addEventListener("activate", event => {
+  // start controlling the already loaded page
+  event.waitUntil(self.clients.claim());
+});
+
+self.addEventListener("fetch", event => {
   let response = new Response("Service worker response");
   event.respondWith(response);
 });
--- a/devtools/client/netmonitor/test/service-workers/status-codes.html
+++ b/devtools/client/netmonitor/test/service-workers/status-codes.html
@@ -10,30 +10,50 @@
     <meta http-equiv="Expires" content="0" />
     <title>Network Monitor test page</title>
   </head>
 
   <body>
     <p>Status codes test</p>
 
     <script type="text/javascript">
-      function get(url) {
-        return new Promise(done => {
-          let iframe = document.createElement("iframe");
-          iframe.setAttribute("src", url);
-          document.documentElement.appendChild(iframe);
-          iframe.contentWindow.onload = done;
-        });
+      let swRegistration;
+
+      function registerServiceWorker() {
+        let sw = navigator.serviceWorker;
+        return sw.register("status-codes-service-worker.js")
+          .then(registration => {
+            swRegistration = registration;
+            console.log("Registered, scope is:", registration.scope);
+            return sw.ready;
+          }).then(() => {
+            // wait until the page is controlled
+            return new Promise(resolve => {
+              if (sw.controller) {
+                resolve();
+              } else {
+                sw.addEventListener('controllerchange', function onControllerChange() {
+                  sw.removeEventListener('controllerchange', onControllerChange);
+                  resolve();
+                });
+              }
+            });
+          }).catch(err => {
+            console.error("Registration failed");
+          });
       }
 
-      function registerServiceWorker() {
-        return navigator.serviceWorker.register("status-codes-service-worker.js")
-                        .then(() => navigator.serviceWorker.ready);
+      function unregisterServiceWorker() {
+        return swRegistration.unregister();
       }
 
       function performRequests() {
-        return get("test/200");
+        return new Promise(function doXHR(done) {
+          let xhr = new XMLHttpRequest();
+          xhr.open("GET", "test/200", true);
+          xhr.onreadystatechange = done;
+          xhr.send(null);
+        });
       }
-
     </script>
   </body>
 
 </html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_hsts-test-server.sjs
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+  response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+  response.setHeader("Pragma", "no-cache");
+  response.setHeader("Expires", "0");
+
+  if (request.queryString === "reset") {
+    // Reset the HSTS policy, prevent influencing other tests
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.setHeader("Strict-Transport-Security", "max-age=0");
+    response.write("Resetting HSTS");
+  } else if (request.scheme === "http") {
+    response.setStatusLine(request.httpVersion, 302, "Found");
+    response.setHeader("Location", "https://" + request.host + request.path);
+  } else {
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.setHeader("Strict-Transport-Security", "max-age=100");
+    response.write("Page was accessed over HTTPS!");
+  }
+}