Bug 760466 - Make JS Storage Server pass Python functional tests; r=rnewman
authorGregory Szorc <gps@mozilla.com>
Fri, 29 Jun 2012 13:29:53 -0700
changeset 97864 f9ec1c3125181f9fd4cbce639af34af770bbd113
parent 97863 ea5eedb6ff461e954fc7fe03e6cd6add214e4235
child 97865 dbb9abec1fea45b038efc103ea5a668fce91fa97
push id551
push usergszorc@mozilla.com
push dateFri, 29 Jun 2012 20:31:30 +0000
treeherderservices-central@dbb9abec1fea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs760466
milestone16.0a1
Bug 760466 - Make JS Storage Server pass Python functional tests; r=rnewman
services/common/storageserver.js
--- a/services/common/storageserver.js
+++ b/services/common/storageserver.js
@@ -232,17 +232,16 @@ ServerBSO.prototype = {
         default:
           this._log.warn("Unexpected field in BSO record: " + key);
           sendMozSvcError(request, response, "8");
           return true;
       }
     }
 
     this.modified = request.timestamp;
-    response.newModified = request.timestamp;
     response.setHeader("X-Last-Modified", "" + this.modified, false);
 
     response.setStatusLine(request.httpVersion, code, status);
   },
 };
 
 /**
  * Represent a collection on the server.
@@ -257,28 +256,28 @@ ServerBSO.prototype = {
  *        An object mapping BSO IDs to ServerBSOs.
  * @param acceptNew
  *        If true, POSTs to this collection URI will result in new BSOs being
  *        created and wired in on the fly.
  * @param timestamp
  *        An optional timestamp value to initialize the modified time of the
  *        collection. This should be in the format returned by new_timestamp().
  */
-function StorageServerCollection(bsos, acceptNew, timestamp) {
+function StorageServerCollection(bsos, acceptNew, timestamp=new_timestamp()) {
   this._bsos = bsos || {};
   this.acceptNew = acceptNew || false;
 
   /*
    * Track modified timestamp.
    * We can't just use the timestamps of contained BSOs: an empty collection
    * has a modified time.
    */
   CommonUtils.ensureMillisecondsTimestamp(timestamp);
+  this._timestamp = timestamp;
 
-  this.timestamp = timestamp || new_timestamp();
   this._log = Log4Moz.repository.getLogger(STORAGE_HTTP_LOGGER);
 }
 StorageServerCollection.prototype = {
   BATCH_MAX_COUNT: 100,         // # of records.
   BATCH_MAX_SIZE: 1024 * 1024,  // # bytes.
 
   _timestamp: null,
 
@@ -396,23 +395,23 @@ StorageServerCollection.prototype = {
 
     if (options.ids) {
       if (options.ids.indexOf(bso.id) == -1) {
         return false;
       }
     }
 
     if (options.newer) {
-      if (bso.modified < options.newer) {
+      if (bso.modified <= options.newer) {
         return false;
       }
     }
 
     if (options.older) {
-      if (bso.modified > options.older) {
+      if (bso.modified >= options.older) {
         return false;
       }
     }
 
     if (options.index_above) {
       if (bso.sortindex === undefined) {
         return false;
       }
@@ -712,17 +711,16 @@ StorageServerCollection.prototype = {
       response.setHeader("Content-Type", "application/newlines", false);
       let normalized = data.map(function map(d) {
         let result = JSON.stringify(d);
 
         return result.replace("\n", "\\u000a");
       });
 
       body = normalized.join("\n") + "\n";
-      _(body);
     } else {
       response.setHeader("Content-Type", "application/json", false);
       body = JSON.stringify({items: data});
     }
 
     this._log.info("Records: " + data.length);
     response.setHeader("X-Num-Records", "" + data.length, false);
     response.setHeader("X-Last-Modified", "" + this.timestamp, false);
@@ -768,31 +766,39 @@ StorageServerCollection.prototype = {
 
         input.push(record);
       }
     } else {
       this._log.info("Unknown media type: " + inputMediaType);
       throw HTTP_415;
     }
 
+    if (this._ensureUnmodifiedSince(request, response)) {
+      return;
+    }
+
     let res = this.post(input, request.timestamp);
     let body = JSON.stringify(res);
     response.setHeader("Content-Type", "application/json", false);
     this.timestamp = request.timestamp;
     response.setHeader("X-Last-Modified", "" + this.timestamp, false);
 
     response.setStatusLine(request.httpVersion, "200", "OK");
     response.bodyOutputStream.write(body, body.length);
   },
 
   deleteHandler: function deleteHandler(request, response) {
     this._log.debug("Invoking StorageServerCollection.DELETE.");
 
     let options = this.parseOptions(request);
 
+    if (this._ensureUnmodifiedSince(request, response)) {
+      return;
+    }
+
     let deleted = this.delete(options);
     response.deleted = deleted;
     this.timestamp = request.timestamp;
 
     response.setStatusLine(request.httpVersion, 204, "No Content");
   },
 
   handler: function handler() {
@@ -809,17 +815,38 @@ StorageServerCollection.prototype = {
         case "DELETE":
           return self.deleteHandler(request, response);
 
       }
 
       request.setHeader("Allow", "GET,POST,DELETE");
       response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
     };
-  }
+  },
+
+  _ensureUnmodifiedSince: function _ensureUnmodifiedSince(request, response) {
+    if (!request.hasHeader("x-if-unmodified-since")) {
+      return false;
+    }
+
+    let requestModified = parseInt(request.getHeader("x-if-unmodified-since"),
+                                   10);
+    let serverModified = this.timestamp;
+
+    this._log.debug("Request modified time: " + requestModified +
+                    "; Server modified time: " + serverModified);
+    if (serverModified <= requestModified) {
+      return false;
+    }
+
+    this._log.info("Conditional request rejected because client time older " +
+                   "than collection timestamp.");
+    response.setStatusLine(request.httpVersion, 412, "Precondition Failed");
+    return true;
+  },
 };
 
 
 //===========================================================================//
 // httpd.js-based Storage server.                                            //
 //===========================================================================//
 
 /**
@@ -1212,16 +1239,17 @@ StorageServer.prototype = {
   storageRE: /^([-_a-zA-Z0-9]+)(?:\/([-_a-zA-Z0-9]+)\/?)?$/,
 
   defaultHeaders: {},
 
   /**
    * HTTP response utility.
    */
   respond: function respond(req, resp, code, status, body, headers, timestamp) {
+    this._log.info("Response: " + code + " " + status);
     resp.setStatusLine(req.httpVersion, code, status);
     for each (let [header, value] in Iterator(headers || this.defaultHeaders)) {
       resp.setHeader(header, value, false);
     }
 
     if (timestamp) {
       resp.setHeader("X-Timestamp", "" + timestamp, false);
     }
@@ -1496,17 +1524,17 @@ StorageServer.prototype = {
                   bso = coll.insert(bsoID);
                 } catch (ex) {
                   return sendMozSvcError(req, resp, "8");
                 }
               }
 
               bso.putHandler(req, resp);
 
-              coll.timestamp = resp.newModified;
+              coll.timestamp = req.timestamp;
               return resp;
             }
 
             return coll.collectionHandler(req, resp);
           } catch (ex) {
             if (ex instanceof HttpError) {
               if (!collectionExisted) {
                 this.deleteCollection(username, collection);