diff --git a/CHANGES.md b/CHANGES.md
index 0eea3f775bc5..31522154fcbc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,12 +3,20 @@ Change Log
 
 ### 1.43 - 2018-03-01
 
+##### Deprecated :hourglass_flowing_sand:
+* In the `Resource` class, `addQueryParameters` and `addTemplateValues` have been deprecated and will be removed in Cesium 1.45. Please use `setQueryParameters` and `setTemplateValues` instead.
+
 ##### Additions :tada:
 * Added support for a promise to a resource for `CesiumTerrainProvider`, `createTileMapServiceImageryProvider` and `Cesium3DTileset` [#6204](https://github.com/AnalyticalGraphicsInc/cesium/pull/6204)
+* `Resource` class [#6205](https://github.com/AnalyticalGraphicsInc/cesium/issues/6205)
+  * Added `put`, `patch`, `delete`, `options` and `head` methods, so it can be used for all XHR requests.
+  * Added `preserveQueryParameters` parameter to `getDerivedResource`, to allow us to append query parameters instead of always replacing them.
+  * Added `setQueryParameters` and `appendQueryParameters` to allow for better handling of query strings.
 
 ##### Fixes :wrench:
 * Fixed bug where AxisAlignedBoundingBox did not copy over center value when cloning an undefined result. [#6183](https://github.com/AnalyticalGraphicsInc/cesium/pull/6183)
 * Fixed `Resource.fetch` when called with no arguments [#6206](https://github.com/AnalyticalGraphicsInc/cesium/issues/6206)
+* Fixed `Resource.clone` to clone the `Request` object, so resource can be used in parallel. [#6208](https://github.com/AnalyticalGraphicsInc/cesium/issues/6208)
 * Fixed bug where 3D Tiles Point Clouds would fail in Internet Explorer. [#6220](https://github.com/AnalyticalGraphicsInc/cesium/pull/6220)
 
 ##### Additions :tada:
diff --git a/Source/Core/Resource.js b/Source/Core/Resource.js
index f620576d6fea..1ae03c402f6b 100644
--- a/Source/Core/Resource.js
+++ b/Source/Core/Resource.js
@@ -65,9 +65,16 @@ define([
     })();
 
     /**
+     * Parses a query string and returns the object equivalent.
+     *
+     * @param {Uri} uri The Uri with a query object.
+     * @param {Resource} resource The Resource that will be assigned queryParameters.
+     * @param {Boolean} merge If true, we'll merge with the resource's existing queryParameters. Otherwise they will be replaced.
+     * @param {Boolean} preserveQueryParameters If true duplicate parameters will be concatenated into an array. If false, keys in uri will take precedence.
+     *
      * @private
      */
-    function parseQuery(uri, resource) {
+    function parseQuery(uri, resource, merge, preserveQueryParameters) {
         var queryString = uri.query;
         if (!defined(queryString) || (queryString.length === 0)) {
             return {};
@@ -83,11 +90,20 @@ define([
             query = queryToObject(queryString);
         }
 
-        resource._queryParameters = combine(resource._queryParameters, query);
+        if (merge) {
+            resource._queryParameters = combineQueryParameters(query, resource._queryParameters, preserveQueryParameters);
+        } else {
+            resource._queryParameters = query;
+        }
         uri.query = undefined;
     }
 
     /**
+     * Converts a query object into a string.
+     *
+     * @param {Uri} uri The Uri object that will have the query object set.
+     * @param {Resource} resource The resource that has queryParameters
+     *
      * @private
      */
     function stringifyQuery(uri, resource) {
@@ -104,17 +120,28 @@ define([
     }
 
     /**
+     * Clones a value if it is defined, otherwise returns the default value
+     *
+     * @param {*} [val] The value to clone.
+     * @param {*} [defaultVal] The default value.
+     *
+     * @returns {*} A clone of val or the defaultVal.
+     *
      * @private
      */
-    function defaultClone(obj, defaultVal) {
-        if (!defined(obj)) {
+    function defaultClone(val, defaultVal) {
+        if (!defined(val)) {
             return defaultVal;
         }
 
-        return defined(obj.clone) ? obj.clone() : clone(obj);
+        return defined(val.clone) ? val.clone() : clone(val);
     }
 
     /**
+     * Checks to make sure the Resource isn't already being requested.
+     *
+     * @param {Request} request The request to check.
+     *
      * @private
      */
     function checkAndResetRequest(request) {
@@ -126,6 +153,88 @@ define([
         request.deferred = undefined;
     }
 
+    /**
+     * This combines a map of query parameters.
+     *
+     * @param {Object} q1 The first map of query parameters. Values in this map will take precedence if preserveQueryParameters is false.
+     * @param {Object} q2 The second map of query parameters.
+     * @param {Boolean} preserveQueryParameters If true duplicate parameters will be concatenated into an array. If false, keys in q1 will take precedence.
+     *
+     * @returns {Object} The combined map of query parameters.
+     *
+     * @example
+     * var q1 = {
+     *   a: 1,
+     *   b: 2
+     * };
+     * var q2 = {
+     *   a: 3,
+     *   c: 4
+     * };
+     * var q3 = {
+     *   b: [5, 6],
+     *   d: 7
+     * }
+     *
+     * // Returns
+     * // {
+     * //   a: [1, 3],
+     * //   b: 2,
+     * //   c: 4
+     * // };
+     * combineQueryParameters(q1, q2, true);
+     *
+     * // Returns
+     * // {
+     * //   a: 1,
+     * //   b: 2,
+     * //   c: 4
+     * // };
+     * combineQueryParameters(q1, q2, false);
+     *
+     * // Returns
+     * // {
+     * //   a: 1,
+     * //   b: [2, 5, 6],
+     * //   d: 7
+     * // };
+     * combineQueryParameters(q1, q3, true);
+     *
+     * // Returns
+     * // {
+     * //   a: 1,
+     * //   b: 2,
+     * //   d: 7
+     * // };
+     * combineQueryParameters(q1, q3, false);
+     *
+     * @private
+     */
+    function combineQueryParameters(q1, q2, preserveQueryParameters) {
+        if (!preserveQueryParameters) {
+            return combine(q1, q2);
+        }
+
+        var result = clone(q1, true);
+        for (var param in q2) {
+            if (q2.hasOwnProperty(param)) {
+                var value = result[param];
+                var q2Value = q2[param];
+                if (defined(value)) {
+                    if (!Array.isArray(value)) {
+                        value = result[param] = [value];
+                    }
+
+                    result[param] = value.concat(q2Value);
+                } else {
+                    result[param] = Array.isArray(q2Value) ? q2Value.slice() : q2Value;
+                }
+            }
+        }
+
+        return result;
+    }
+
     /**
      * A resource that includes the location and any other parameters we need to retrieve it or create derived resources. It also provides the ability to retry requests.
      *
@@ -224,7 +333,14 @@ define([
         this.retryAttempts = defaultValue(options.retryAttempts, 0);
         this._retryCount = 0;
 
-        this.url = options.url;
+
+        var uri = new Uri(options.url);
+        parseQuery(uri, this, true, true);
+
+        // Remove the fragment as it's not sent with a request
+        uri.fragment = undefined;
+
+        this._url = uri.toString();
     }
 
     /**
@@ -239,7 +355,13 @@ define([
      */
     Resource.createIfNeeded = function(resource, options) {
         if (resource instanceof Resource) {
-            return resource.clone();
+            // Keep existing request object. This function is used internally to duplicate a Resource, so that it can't
+            //  be modified outside of a class that holds it (eg. an imagery or terrain provider). Since the Request objects
+            //  are managed outside of the providers, by the tile loading code, we want to keep the request property the same so if it is changed
+            //  in the underlying tiling code the requests for this resource will use it.
+            return  resource.getDerivedResource({
+                request: resource.request
+            });
         }
 
         if (typeof resource !== 'string') {
@@ -309,7 +431,7 @@ define([
             set: function(value) {
                 var uri = new Uri(value);
 
-                parseQuery(uri, this);
+                parseQuery(uri, this, false);
 
                 // Remove the fragment as it's not sent with a request
                 uri.fragment = undefined;
@@ -420,27 +542,52 @@ define([
 
     /**
      * Combines the specified object and the existing query parameters. This allows you to add many parameters at once,
-     *  as opposed to adding them one at a time to the queryParameters property.
+     *  as opposed to adding them one at a time to the queryParameters property. If a value is already set, it will be replaced with the new value.
      *
      * @param {Object} params The query parameters
      * @param {Boolean} [useAsDefault=false] If true the params will be used as the default values, so they will only be set if they are undefined.
      */
-    Resource.prototype.addQueryParameters = function(params, useAsDefault) {
+    Resource.prototype.setQueryParameters = function(params, useAsDefault) {
         if (useAsDefault) {
-            this._queryParameters = combine(this._queryParameters, params);
+            this._queryParameters = combineQueryParameters(this._queryParameters, params, false);
         } else {
-            this._queryParameters = combine(params, this._queryParameters);
+            this._queryParameters = combineQueryParameters(params, this._queryParameters, false);
         }
     };
 
+    /**
+     * Combines the specified object and the existing query parameters. This allows you to add many parameters at once,
+     *  as opposed to adding them one at a time to the queryParameters property. If a value is already set, it will be replaced with the new value.
+     *
+     * @param {Object} params The query parameters
+     * @param {Boolean} [useAsDefault=false] If true the params will be used as the default values, so they will only be set if they are undefined.
+     *
+     * @deprecated
+     */
+    Resource.prototype.addQueryParameters = function(params, useAsDefault) {
+        deprecationWarning('Resource.addQueryParameters', 'addQueryParameters has been deprecated and will be removed 1.45. Use setQueryParameters or appendQueryParameters instead.');
+
+        return this.setQueryParameters(params, useAsDefault);
+    };
+
+    /**
+     * Combines the specified object and the existing query parameters. This allows you to add many parameters at once,
+     *  as opposed to adding them one at a time to the queryParameters property.
+     *
+     * @param {Object} params The query parameters
+     */
+    Resource.prototype.appendQueryParameters = function(params) {
+        this._queryParameters = combineQueryParameters(params, this._queryParameters, true);
+    };
+
     /**
      * Combines the specified object and the existing template values. This allows you to add many values at once,
-     *  as opposed to adding them one at a time to the templateValues property.
+     *  as opposed to adding them one at a time to the templateValues property. If a value is already set, it will become an array and the new value will be appended.
      *
-     * @param {Object} params The template values
+     * @param {Object} template The template values
      * @param {Boolean} [useAsDefault=false] If true the values will be used as the default values, so they will only be set if they are undefined.
      */
-    Resource.prototype.addTemplateValues = function(template, useAsDefault) {
+    Resource.prototype.setTemplateValues = function(template, useAsDefault) {
         if (useAsDefault) {
             this._templateValues = combine(this._templateValues, template);
         } else {
@@ -448,6 +595,21 @@ define([
         }
     };
 
+    /**
+     * Combines the specified object and the existing template values. This allows you to add many values at once,
+     *  as opposed to adding them one at a time to the templateValues property. If a value is already set, it will become an array and the new value will be appended.
+     *
+     * @param {Object} template The template values
+     * @param {Boolean} [useAsDefault=false] If true the values will be used as the default values, so they will only be set if they are undefined.
+     *
+     * @deprecated
+     */
+    Resource.prototype.addTemplateValues = function(template, useAsDefault) {
+        deprecationWarning('Resource.addTemplateValues', 'addTemplateValues has been deprecated and will be removed 1.45. Use setTemplateValues.');
+
+        return this.setTemplateValues(template, useAsDefault);
+    };
+
     /**
      * Returns a resource relative to the current instance. All properties remain the same as the current instance unless overridden in options.
      *
@@ -460,6 +622,7 @@ define([
      * @param {Resource~RetryCallback} [options.retryCallback] The function to call when loading the resource fails.
      * @param {Number} [options.retryAttempts] The number of times the retryCallback should be called before giving up.
      * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
+     * @param {Boolean} [options.preserveQueryParameters=false] If true, this will keep all query parameters from the current resource and derived resource. If false, derived parameters will replace those of the current resource.
      *
      * @returns {Resource} The resource derived from the current one.
      */
@@ -470,7 +633,8 @@ define([
         if (defined(options.url)) {
             var uri = new Uri(options.url);
 
-            parseQuery(uri, resource);
+            var preserveQueryParameters = defaultValue(options.preserveQueryParameters, false);
+            parseQuery(uri, resource, true, preserveQueryParameters);
 
             // Remove the fragment as it's not sent with a request
             uri.fragment = undefined;
@@ -492,9 +656,6 @@ define([
         }
         if (defined(options.request)) {
             resource.request = options.request;
-        } else {
-            // Clone the request so we keep all the throttle settings
-            resource.request = this.request.clone();
         }
         if (defined(options.retryCallback)) {
             resource.retryCallback = options.retryCallback;
@@ -512,6 +673,8 @@ define([
      * @param {Error} [error] The error that was encountered.
      *
      * @returns {Promise<Boolean>} A promise to a boolean, that if true will cause the resource request to be retried.
+     *
+     * @private
      */
     Resource.prototype.retryOnError = function(error) {
         var retryCallback = this.retryCallback;
@@ -550,10 +713,7 @@ define([
         result.retryCallback = this.retryCallback;
         result.retryAttempts = this.retryAttempts;
         result._retryCount = 0;
-
-        // In practice, we don't want this cloned. It usually not set, unless we purposely set it internally and not
-        //  using the request will break the request scheduler.
-        result.request = this.request;
+        result.request = this.request.clone();
 
         return result;
     };
@@ -1078,38 +1238,9 @@ define([
     };
 
     /**
-     * Asynchronously loads the given resource.  Returns a promise that will resolve to
-     * the result once loaded, or reject if the resource failed to load.  The data is loaded
-     * using XMLHttpRequest, which means that in order to make requests to another origin,
-     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
-     *
-     * @param {Object} [options] Object with the following properties:
-     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
-     * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
-     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
-     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
-     *
-     *
-     * @example
-     * // Load a single resource asynchronously. In real code, you should use loadBlob instead.
-     * resource.fetch()
-     *   .then(function(blob) {
-     *       // use the data
-     *   }).otherwise(function(error) {
-     *       // an error occurred
-     *   });
-     *
-     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
-     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
+     * @private
      */
-    Resource.prototype.fetch = function(options) {
-        options = defaultClone(options, {});
-        options.method = 'GET';
-
-        return makeRequest(this, options);
-    };
-
-    function makeRequest(resource, options) {
+    Resource._makeRequest = function(resource, options) {
         checkAndResetRequest(resource.request);
 
         var request = resource.request;
@@ -1158,7 +1289,7 @@ define([
                         return when.reject(e);
                     });
             });
-    }
+    };
 
     var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
 
@@ -1209,6 +1340,38 @@ define([
         }
     }
 
+    /**
+     * Asynchronously loads the given resource.  Returns a promise that will resolve to
+     * the result once loaded, or reject if the resource failed to load.  The data is loaded
+     * using XMLHttpRequest, which means that in order to make requests to another origin,
+     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. It's recommended that you use
+     * the more specific functions eg. fetchJson, fetchBlob, etc.
+     *
+     * @param {Object} [options] Object with the following properties:
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     *
+     *
+     * @example
+     * resource.fetch()
+     *   .then(function(body) {
+     *       // use the data
+     *   }).otherwise(function(error) {
+     *       // an error occurred
+     *   });
+     *
+     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
+     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
+     */
+    Resource.prototype.fetch = function(options) {
+        options = defaultClone(options, {});
+        options.method = 'GET';
+
+        return Resource._makeRequest(this, options);
+    };
+
     /**
      * Creates a Resource from a URL and calls fetch() on it.
      *
@@ -1235,7 +1398,175 @@ define([
     };
 
     /**
-     * Asynchronously posts data the given resource.  Returns a promise that will resolve to
+     * Asynchronously deletes the given resource.  Returns a promise that will resolve to
+     * the result once loaded, or reject if the resource failed to load.  The data is loaded
+     * using XMLHttpRequest, which means that in order to make requests to another origin,
+     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
+     *
+     * @param {Object} [options] Object with the following properties:
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     *
+     *
+     * @example
+     * resource.delete()
+     *   .then(function(body) {
+     *       // use the data
+     *   }).otherwise(function(error) {
+     *       // an error occurred
+     *   });
+     *
+     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
+     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
+     */
+    Resource.prototype.delete = function(options) {
+        options = defaultClone(options, {});
+        options.method = 'DELETE';
+
+        return Resource._makeRequest(this, options);
+    };
+
+    /**
+     * Creates a Resource from a URL and calls delete() on it.
+     *
+     * @param {String|Object} options A url or an object with the following properties
+     * @param {String} options.url The url of the resource.
+     * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
+     * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
+     * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
+     * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
+     * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
+     * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
+     * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     */
+    Resource.delete = function (options) {
+        var resource = new Resource(options);
+        return resource.delete({
+            // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
+            responseType: options.responseType,
+            overrideMimeType: options.overrideMimeType
+        });
+    };
+
+    /**
+     * Asynchronously gets headers the given resource.  Returns a promise that will resolve to
+     * the result once loaded, or reject if the resource failed to load.  The data is loaded
+     * using XMLHttpRequest, which means that in order to make requests to another origin,
+     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
+     *
+     * @param {Object} [options] Object with the following properties:
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     *
+     *
+     * @example
+     * resource.head()
+     *   .then(function(headers) {
+     *       // use the data
+     *   }).otherwise(function(error) {
+     *       // an error occurred
+     *   });
+     *
+     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
+     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
+     */
+    Resource.prototype.head = function(options) {
+        options = defaultClone(options, {});
+        options.method = 'HEAD';
+
+        return Resource._makeRequest(this, options);
+    };
+
+    /**
+     * Creates a Resource from a URL and calls head() on it.
+     *
+     * @param {String|Object} options A url or an object with the following properties
+     * @param {String} options.url The url of the resource.
+     * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
+     * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
+     * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
+     * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
+     * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
+     * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
+     * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     */
+    Resource.head = function (options) {
+        var resource = new Resource(options);
+        return resource.head({
+            // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
+            responseType: options.responseType,
+            overrideMimeType: options.overrideMimeType
+        });
+    };
+
+    /**
+     * Asynchronously gets options the given resource.  Returns a promise that will resolve to
+     * the result once loaded, or reject if the resource failed to load.  The data is loaded
+     * using XMLHttpRequest, which means that in order to make requests to another origin,
+     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
+     *
+     * @param {Object} [options] Object with the following properties:
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     *
+     *
+     * @example
+     * resource.options()
+     *   .then(function(headers) {
+     *       // use the data
+     *   }).otherwise(function(error) {
+     *       // an error occurred
+     *   });
+     *
+     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
+     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
+     */
+    Resource.prototype.options = function(options) {
+        options = defaultClone(options, {});
+        options.method = 'OPTIONS';
+
+        return Resource._makeRequest(this, options);
+    };
+
+    /**
+     * Creates a Resource from a URL and calls options() on it.
+     *
+     * @param {String|Object} options A url or an object with the following properties
+     * @param {String} options.url The url of the resource.
+     * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
+     * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
+     * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
+     * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
+     * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
+     * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
+     * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     */
+    Resource.options = function (options) {
+        var resource = new Resource(options);
+        return resource.options({
+            // Make copy of just the needed fields because headers can be passed to both the constructor and to fetch
+            responseType: options.responseType,
+            overrideMimeType: options.overrideMimeType
+        });
+    };
+
+    /**
+     * Asynchronously posts data to the given resource.  Returns a promise that will resolve to
      * the result once loaded, or reject if the resource failed to load.  The data is loaded
      * using XMLHttpRequest, which means that in order to make requests to another origin,
      * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
@@ -1249,7 +1580,6 @@ define([
      *
      *
      * @example
-     * // Load a single resource asynchronously. In real code, you should use loadBlob instead.
      * resource.post(data)
      *   .then(function(result) {
      *       // use the result
@@ -1267,13 +1597,13 @@ define([
         options.method = 'POST';
         options.data = data;
 
-        return makeRequest(this, options);
+        return Resource._makeRequest(this, options);
     };
 
     /**
-     * Creates a Resource from a URL and calls fetch() on it.
+     * Creates a Resource from a URL and calls post() on it.
      *
-     * @param {String|Object} options A url or an object with the following properties
+     * @param {Object} options A url or an object with the following properties
      * @param {String} options.url The url of the resource.
      * @param {Object} options.data Data that is posted with the resource.
      * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
@@ -1296,6 +1626,128 @@ define([
         });
     };
 
+    /**
+     * Asynchronously puts data to the given resource.  Returns a promise that will resolve to
+     * the result once loaded, or reject if the resource failed to load.  The data is loaded
+     * using XMLHttpRequest, which means that in order to make requests to another origin,
+     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
+     *
+     * @param {Object} data Data that is posted with the resource.
+     * @param {Object} [options] Object with the following properties:
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     *
+     *
+     * @example
+     * resource.put(data)
+     *   .then(function(result) {
+     *       // use the result
+     *   }).otherwise(function(error) {
+     *       // an error occurred
+     *   });
+     *
+     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
+     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
+     */
+    Resource.prototype.put = function(data, options) {
+        Check.defined('data', data);
+
+        options = defaultClone(options, {});
+        options.method = 'PUT';
+        options.data = data;
+
+        return Resource._makeRequest(this, options);
+    };
+
+    /**
+     * Creates a Resource from a URL and calls put() on it.
+     *
+     * @param {Object} options A url or an object with the following properties
+     * @param {String} options.url The url of the resource.
+     * @param {Object} options.data Data that is posted with the resource.
+     * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
+     * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
+     * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
+     * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
+     * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
+     * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
+     * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     */
+    Resource.put = function (options) {
+        var resource = new Resource(options);
+        return resource.put(options.data, {
+            // Make copy of just the needed fields because headers can be passed to both the constructor and to post
+            responseType: options.responseType,
+            overrideMimeType: options.overrideMimeType
+        });
+    };
+
+    /**
+     * Asynchronously patches data to the given resource.  Returns a promise that will resolve to
+     * the result once loaded, or reject if the resource failed to load.  The data is loaded
+     * using XMLHttpRequest, which means that in order to make requests to another origin,
+     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
+     *
+     * @param {Object} data Data that is posted with the resource.
+     * @param {Object} [options] Object with the following properties:
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {Object} [options.headers] Additional HTTP headers to send with the request, if any.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     *
+     *
+     * @example
+     * resource.patch(data)
+     *   .then(function(result) {
+     *       // use the result
+     *   }).otherwise(function(error) {
+     *       // an error occurred
+     *   });
+     *
+     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
+     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
+     */
+    Resource.prototype.patch = function(data, options) {
+        Check.defined('data', data);
+
+        options = defaultClone(options, {});
+        options.method = 'PATCH';
+        options.data = data;
+
+        return Resource._makeRequest(this, options);
+    };
+
+    /**
+     * Creates a Resource from a URL and calls patch() on it.
+     *
+     * @param {Object} options A url or an object with the following properties
+     * @param {String} options.url The url of the resource.
+     * @param {Object} options.data Data that is posted with the resource.
+     * @param {Object} [options.queryParameters] An object containing query parameters that will be sent when retrieving the resource.
+     * @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
+     * @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
+     * @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
+     * @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
+     * @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
+     * @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
+     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
+     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
+     * @returns {Promise.<Object>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
+     */
+    Resource.patch = function (options) {
+        var resource = new Resource(options);
+        return resource.patch(options.data, {
+            // Make copy of just the needed fields because headers can be passed to both the constructor and to post
+            responseType: options.responseType,
+            overrideMimeType: options.overrideMimeType
+        });
+    };
+
     /**
      * Contains implementations of functions that can be replaced for testing
      *
@@ -1371,6 +1823,21 @@ define([
             var response = xhr.response;
             var browserResponseType = xhr.responseType;
 
+            if (method === 'HEAD' || method === 'OPTIONS') {
+                var responseHeaderString = xhr.getAllResponseHeaders();
+                var splitHeaders = responseHeaderString.trim().split(/[\r\n]+/);
+
+                var responseHeaders = {};
+                splitHeaders.forEach(function (line) {
+                    var parts = line.split(': ');
+                    var header = parts.shift();
+                    responseHeaders[header] = parts.join(': ');
+                });
+
+                deferred.resolve(responseHeaders);
+                return;
+            }
+
             //All modern browsers will go into either the first or second if block or last else block.
             //Other code paths support older browsers that either do not support the supplied responseType
             //or do not support the xhr.response property.
diff --git a/Source/Core/loadWithXhr.js b/Source/Core/loadWithXhr.js
index c7cd6f2491fe..ad3c6a17ffdf 100644
--- a/Source/Core/loadWithXhr.js
+++ b/Source/Core/loadWithXhr.js
@@ -1,12 +1,14 @@
 define([
         '../ThirdParty/when',
         './Check',
+        './defaultValue',
         './defineProperties',
         './deprecationWarning',
         './Resource'
     ], function(
         when,
         Check,
+        defaultValue,
         defineProperties,
         deprecationWarning,
         Resource) {
@@ -61,9 +63,11 @@ define([
         // Take advantage that most parameters are the same
         var resource = new Resource(options);
 
-        return resource.fetch({
+        return Resource._makeRequest(resource, {
             responseType: options.responseType,
-            overrideMimeType: options.overrideMimeType
+            overrideMimeType: options.overrideMimeType,
+            method: defaultValue(options.method, 'GET'),
+            data: options.data
         });
     }
 
diff --git a/Source/DataSources/KmlDataSource.js b/Source/DataSources/KmlDataSource.js
index 203fb879a002..4e57b55dd85f 100644
--- a/Source/DataSources/KmlDataSource.js
+++ b/Source/DataSources/KmlDataSource.js
@@ -728,10 +728,10 @@ define([
             var viewFormat = defaultValue(queryStringValue(iconNode, 'viewFormat', namespaces.kml), defaultViewFormat);
             var httpQuery = queryStringValue(iconNode, 'httpQuery', namespaces.kml);
             if (defined(viewFormat)) {
-                hrefResource.addQueryParameters(queryToObject(cleanupString(viewFormat)));
+                hrefResource.setQueryParameters(queryToObject(cleanupString(viewFormat)));
             }
             if (defined(httpQuery)) {
-                hrefResource.addQueryParameters(queryToObject(cleanupString(httpQuery)));
+                hrefResource.setQueryParameters(queryToObject(cleanupString(httpQuery)));
             }
 
             processNetworkLinkQueryString(hrefResource, dataSource._camera, dataSource._canvas, viewBoundScale, dataSource._lastCameraView.bbox);
@@ -2108,7 +2108,7 @@ define([
         queryString = queryString.replace('[clientName]', 'Cesium');
         queryString = queryString.replace('[language]', 'English');
 
-        resource.addQueryParameters(queryToObject(queryString));
+        resource.setQueryParameters(queryToObject(queryString));
     }
 
     function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceResource, uriResolver, promises, context) {
@@ -2145,10 +2145,10 @@ define([
                     var viewFormat = defaultValue(queryStringValue(link, 'viewFormat', namespaces.kml), defaultViewFormat);
                     var httpQuery = queryStringValue(link, 'httpQuery', namespaces.kml);
                     if (defined(viewFormat)) {
-                        href.addQueryParameters(queryToObject(cleanupString(viewFormat)));
+                        href.setQueryParameters(queryToObject(cleanupString(viewFormat)));
                     }
                     if (defined(httpQuery)) {
-                        href.addQueryParameters(queryToObject(cleanupString(httpQuery)));
+                        href.setQueryParameters(queryToObject(cleanupString(httpQuery)));
                     }
 
                     processNetworkLinkQueryString(href, dataSource._camera, dataSource._canvas, viewBoundScale, dataSource._lastCameraView.bbox);
@@ -2391,7 +2391,7 @@ define([
         }
 
         if (defined(query)) {
-            sourceUri.addQueryParameters(query);
+            sourceUri.setQueryParameters(query);
         }
 
         return when(promise)
@@ -2525,7 +2525,7 @@ define([
     /**
      * Creates a Promise to a new instance loaded with the provided KML data.
      *
-     * @param {String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
+     * @param {Resource|String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
      * @param {Object} options An object with the following properties:
      * @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
      * @param {Canvas} options.canvas The canvas that is used for sending viewer properties to network links.
@@ -2681,9 +2681,9 @@ define([
      * @param {Resource|String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
      * @param {Object} [options] An object with the following properties:
      * @param {Resource|String} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
-     * @returns {Promise.<KmlDataSource>} A promise that will resolve to this instances once the KML is loaded.
      * @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
-     * @param {Object} [options.query] Key-value pairs which are appended to all URIs in the CZML.
+     *
+     * @returns {Promise.<KmlDataSource>} A promise that will resolve to this instances once the KML is loaded.
      */
     KmlDataSource.prototype.load = function(data, options) {
         //>>includeStart('debug', pragmas.debug);
@@ -2976,7 +2976,7 @@ define([
                     networkLink.updating = true;
                     var newEntityCollection = new EntityCollection();
                     var href = networkLink.href.clone();
-                    href.addQueryParameters(networkLink.cookie);
+                    href.setQueryParameters(networkLink.cookie);
                     processNetworkLinkQueryString(href, that._camera, that._canvas, networkLink.viewBoundScale, lastCameraView.bbox);
                     load(that, newEntityCollection, href, {context : entity.id})
                         .then(getNetworkLinkUpdateCallback(that, networkLink, newEntityCollection, newNetworkLinks, href))
diff --git a/Source/Scene/ArcGisMapServerImageryProvider.js b/Source/Scene/ArcGisMapServerImageryProvider.js
index c6a6cf8d06b5..2b727c69a20b 100644
--- a/Source/Scene/ArcGisMapServerImageryProvider.js
+++ b/Source/Scene/ArcGisMapServerImageryProvider.js
@@ -125,7 +125,7 @@ define([
         resource.appendForwardSlash();
 
         if (defined(options.token)) {
-            resource.addQueryParameters({
+            resource.setQueryParameters({
                 token: options.token
             });
         }
diff --git a/Source/Scene/BingMapsImageryProvider.js b/Source/Scene/BingMapsImageryProvider.js
index 14a284d1645e..c3c43838b70d 100644
--- a/Source/Scene/BingMapsImageryProvider.js
+++ b/Source/Scene/BingMapsImageryProvider.js
@@ -113,7 +113,7 @@ define([
             proxy: options.proxy
         });
 
-        urlResource.addQueryParameters({
+        urlResource.setQueryParameters({
             key: this._key
         });
 
diff --git a/Source/Scene/Cesium3DTile.js b/Source/Scene/Cesium3DTile.js
index be37ce87d547..5ffb0114e887 100644
--- a/Source/Scene/Cesium3DTile.js
+++ b/Source/Scene/Cesium3DTile.js
@@ -619,7 +619,7 @@ define([
         var expired = this.contentExpired;
         if (expired) {
             // Append a query parameter of the tile expiration date to prevent caching
-            resource.addQueryParameters({
+            resource.setQueryParameters({
                 expired: this.expireDate.toString()
             });
         }
diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js
index 200e480cc7f1..237e51888708 100644
--- a/Source/Scene/Cesium3DTileset.js
+++ b/Source/Scene/Cesium3DTileset.js
@@ -1261,7 +1261,7 @@ define([
                 v: defaultValue(asset.tilesetVersion, '0.0')
             };
             this._basePath += '?v=' + versionQuery.v;
-            tilesetResource.addQueryParameters(versionQuery);
+            tilesetResource.setQueryParameters(versionQuery);
         }
 
         // A tileset.json referenced from a tile may exist in a different directory than the root tileset.
diff --git a/Source/Scene/GoogleEarthEnterpriseMapsProvider.js b/Source/Scene/GoogleEarthEnterpriseMapsProvider.js
index a1dbe9a1bd35..7e48643fae7b 100644
--- a/Source/Scene/GoogleEarthEnterpriseMapsProvider.js
+++ b/Source/Scene/GoogleEarthEnterpriseMapsProvider.js
@@ -118,9 +118,14 @@ define([
 
         var url = options.url;
         var path = defaultValue(options.path, '/default_map');
-        var resource = Resource.createIfNeeded(url + path, {
-            proxy: options.proxy
+
+        var resource = Resource.createIfNeeded(url, {
+            proxy : options.proxy
+        }).getDerivedResource({
+            // We used to just append path to url, so now that we do proper URI resolution, removed the /
+            url : (path[0] === '/') ? path.substring(1) : path
         });
+
         resource.appendForwardSlash();
 
         this._resource = resource;
diff --git a/Source/Scene/MapboxImageryProvider.js b/Source/Scene/MapboxImageryProvider.js
index f6c62254e47e..ac46c2693cd6 100644
--- a/Source/Scene/MapboxImageryProvider.js
+++ b/Source/Scene/MapboxImageryProvider.js
@@ -100,7 +100,7 @@ define([
         templateUrl += mapId + '/{z}/{x}/{y}' + this._format;
         resource.url = templateUrl;
 
-        resource.addQueryParameters({
+        resource.setQueryParameters({
             access_token: accessToken
         });
 
diff --git a/Source/Scene/WebMapServiceImageryProvider.js b/Source/Scene/WebMapServiceImageryProvider.js
index 6e9865cf3a8c..e62309b3443a 100644
--- a/Source/Scene/WebMapServiceImageryProvider.js
+++ b/Source/Scene/WebMapServiceImageryProvider.js
@@ -111,15 +111,15 @@ define([
 
         var pickFeatureResource = resource.clone();
 
-        resource.addQueryParameters(WebMapServiceImageryProvider.DefaultParameters, true);
-        pickFeatureResource.addQueryParameters(WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters, true);
+        resource.setQueryParameters(WebMapServiceImageryProvider.DefaultParameters, true);
+        pickFeatureResource.setQueryParameters(WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters, true);
 
         if (defined(options.parameters)) {
-            resource.addQueryParameters(objectToLowercase(options.parameters));
+            resource.setQueryParameters(objectToLowercase(options.parameters));
         }
 
         if (defined(options.getFeatureInfoParameters)) {
-            pickFeatureResource.addQueryParameters(objectToLowercase(options.getFeatureInfoParameters));
+            pickFeatureResource.setQueryParameters(objectToLowercase(options.getFeatureInfoParameters));
         }
 
         var parameters = {};
@@ -139,8 +139,8 @@ define([
             parameters.srs = options.tilingScheme instanceof WebMercatorTilingScheme ? 'EPSG:3857' : 'EPSG:4326';
         }
 
-        resource.addQueryParameters(parameters, true);
-        pickFeatureResource.addQueryParameters(parameters, true);
+        resource.setQueryParameters(parameters, true);
+        pickFeatureResource.setQueryParameters(parameters, true);
 
         var pickFeatureParams = {
             query_layers: options.layers,
@@ -148,7 +148,7 @@ define([
             y: '{j}',
             info_format: '{format}'
         };
-        pickFeatureResource.addQueryParameters(pickFeatureParams, true);
+        pickFeatureResource.setQueryParameters(pickFeatureParams, true);
 
         this._resource = resource;
         this._pickFeaturesResource = pickFeatureResource;
diff --git a/Source/Scene/WebMapTileServiceImageryProvider.js b/Source/Scene/WebMapTileServiceImageryProvider.js
index 997fd2ed4bb8..0ec60896e918 100644
--- a/Source/Scene/WebMapTileServiceImageryProvider.js
+++ b/Source/Scene/WebMapTileServiceImageryProvider.js
@@ -175,10 +175,10 @@ define([
                 TileMatrixSet : tileMatrixSetID
             };
 
-            resource.addTemplateValues(templateValues);
+            resource.setTemplateValues(templateValues);
             this._useKvp = false;
         } else {
-            resource.addQueryParameters(defaultParameters);
+            resource.setQueryParameters(defaultParameters);
             this._useKvp = true;
         }
 
@@ -265,14 +265,14 @@ define([
             resource = imageryProvider._resource.getDerivedResource({
                 request: request
             });
-            resource.addTemplateValues(templateValues);
+            resource.setTemplateValues(templateValues);
 
             if (defined(staticDimensions)) {
-                resource.addTemplateValues(staticDimensions);
+                resource.setTemplateValues(staticDimensions);
             }
 
             if (defined(dynamicIntervalData)) {
-                resource.addTemplateValues(dynamicIntervalData);
+                resource.setTemplateValues(dynamicIntervalData);
             }
         } else {
             // build KVP request
diff --git a/Specs/Core/ResourceSpec.js b/Specs/Core/ResourceSpec.js
index c947355d786f..4e8cab60a41f 100644
--- a/Specs/Core/ResourceSpec.js
+++ b/Specs/Core/ResourceSpec.js
@@ -108,6 +108,49 @@ defineSuite([
         expect(resource.url).toEqual('http://test.com/tileset');
     });
 
+    it('multiple values for query parameters are allowed', function() {
+        var resource = new Resource('http://test.com/tileset/endpoint?a=1&a=2&b=3&a=4');
+        expect(resource.queryParameters.a).toEqual(['1', '2', '4']);
+        expect(resource.queryParameters.b).toEqual('3');
+
+        expect(resource.url).toEqual('http://test.com/tileset/endpoint?a=1&a=2&a=4&b=3');
+    });
+
+    it('multiple values for query parameters works with getDerivedResource without preserverQueryParameters', function() {
+        var resource = new Resource('http://test.com/tileset/endpoint?a=1&a=2&b=3&a=4');
+        expect(resource.queryParameters.a).toEqual(['1', '2', '4']);
+        expect(resource.queryParameters.b).toEqual('3');
+
+        expect(resource.url).toEqual('http://test.com/tileset/endpoint?a=1&a=2&a=4&b=3');
+
+        var derived = resource.getDerivedResource({
+            url: 'other_endpoint?a=5&b=6&a=7'
+        });
+
+        expect(derived.queryParameters.a).toEqual(['5', '7']);
+        expect(derived.queryParameters.b).toEqual('6');
+
+        expect(derived.url).toEqual('http://test.com/tileset/other_endpoint?a=5&a=7&b=6');
+    });
+
+    it('multiple values for query parameters works with getDerivedResource with preserveQueryParameters', function() {
+        var resource = new Resource('http://test.com/tileset/endpoint?a=1&a=2&b=3&a=4');
+        expect(resource.queryParameters.a).toEqual(['1', '2', '4']);
+        expect(resource.queryParameters.b).toEqual('3');
+
+        expect(resource.url).toEqual('http://test.com/tileset/endpoint?a=1&a=2&a=4&b=3');
+
+        var derived = resource.getDerivedResource({
+            url: 'other_endpoint?a=5&b=6&a=7',
+            preserveQueryParameters: true
+        });
+
+        expect(derived.queryParameters.a).toEqual(['5', '7', '1', '2', '4']);
+        expect(derived.queryParameters.b).toEqual(['6', '3']);
+
+        expect(derived.url).toEqual('http://test.com/tileset/other_endpoint?a=5&a=7&a=1&a=2&a=4&b=6&b=3');
+    });
+
     it('templateValues are respected', function() {
         var resource = new Resource({
             url: 'http://test.com/tileset/{foo}/{bar}',
@@ -158,10 +201,10 @@ defineSuite([
         });
 
         expect(resource.getUrlComponent(false, false)).toEqual('http://test.com/tileset/tileset.json');
-        expect(resource.getUrlComponent(true, false)).toEqual('http://test.com/tileset/tileset.json?key1=value1&key2=value2&foo=bar&key=value');
+        expect(resource.getUrlComponent(true, false)).toEqual('http://test.com/tileset/tileset.json?key1=value1&key2=value2&key=value&foo=bar');
         expect(resource.getUrlComponent(false, true)).toEqual(proxy.getURL('http://test.com/tileset/tileset.json'));
-        expect(resource.getUrlComponent(true, true)).toEqual(proxy.getURL('http://test.com/tileset/tileset.json?key1=value1&key2=value2&foo=bar&key=value'));
-        expect(resource.url).toEqual(proxy.getURL('http://test.com/tileset/tileset.json?key1=value1&key2=value2&foo=bar&key=value'));
+        expect(resource.getUrlComponent(true, true)).toEqual(proxy.getURL('http://test.com/tileset/tileset.json?key1=value1&key2=value2&key=value&foo=bar'));
+        expect(resource.url).toEqual(proxy.getURL('http://test.com/tileset/tileset.json?key1=value1&key2=value2&key=value&foo=bar'));
         expect(resource.queryParameters).toEqual({
             foo: 'bar',
             key: 'value',
@@ -248,7 +291,7 @@ defineSuite([
         expect(resource.url).toEqual('http://test.com/terrain?x=1&y=2&z=0');
     });
 
-    it('addQueryParameters with useAsDefault set to true', function() {
+    it('setQueryParameters with useAsDefault set to true', function() {
         var resource = new Resource({
             url: 'http://test.com/terrain',
             queryParameters: {
@@ -262,7 +305,7 @@ defineSuite([
             y: 2
         });
 
-        resource.addQueryParameters({
+        resource.setQueryParameters({
             x: 3,
             y: 4,
             z: 0
@@ -275,7 +318,7 @@ defineSuite([
         });
     });
 
-    it('addQueryParameters with useAsDefault set to false', function() {
+    it('setQueryParameters with useAsDefault set to false', function() {
         var resource = new Resource({
             url: 'http://test.com/terrain',
             queryParameters: {
@@ -289,7 +332,7 @@ defineSuite([
             y: 2
         });
 
-        resource.addQueryParameters({
+        resource.setQueryParameters({
             x: 3,
             y: 4,
             z: 0
@@ -302,7 +345,64 @@ defineSuite([
         });
     });
 
-    it('addTemplateValues with useAsDefault set to true', function() {
+    it('appendQueryParameters works with non-arrays', function() {
+        var resource = new Resource({
+            url: 'http://test.com/terrain',
+            queryParameters: {
+                x: 1,
+                y: 2
+            }
+        });
+
+        expect(resource.queryParameters).toEqual({
+            x: 1,
+            y: 2
+        });
+
+        resource.appendQueryParameters({
+            x: 3,
+            y: 4,
+            z: 0
+        });
+
+        expect(resource.queryParameters).toEqual({
+            x: [3, 1],
+            y: [4, 2],
+            z: 0
+        });
+    });
+
+    it('appendQueryParameters works with arrays/non-arrays', function() {
+        var resource = new Resource({
+            url: 'http://test.com/terrain',
+            queryParameters: {
+                x: [1, 2],
+                y: 2,
+                z: [-1, -2]
+            }
+        });
+
+        expect(resource.queryParameters).toEqual({
+            x: [1, 2],
+            y: 2,
+            z: [-1, -2]
+        });
+
+        resource.appendQueryParameters({
+            x: 3,
+            y: [4, 5],
+            z: [-3, -4]
+        });
+
+        expect(resource.queryParameters).toEqual({
+            x: [3, 1, 2],
+            y: [4, 5, 2],
+            z: [-3, -4, -1, -2]
+        });
+    });
+
+
+    it('setTemplateValues with useAsDefault set to true', function() {
         var resource = new Resource({
             url: 'http://test.com/terrain/{z}/{x}/{y}.terrain',
             templateValues: {
@@ -318,7 +418,7 @@ defineSuite([
             map: 'my map'
         });
 
-        resource.addTemplateValues({
+        resource.setTemplateValues({
             x: 3,
             y: 4,
             z: 0,
@@ -334,7 +434,7 @@ defineSuite([
         });
     });
 
-    it('addTemplateValues with useAsDefault set to false', function() {
+    it('setTemplateValues with useAsDefault set to false', function() {
         var resource = new Resource({
             url: 'http://test.com/terrain/{z}/{x}/{y}.terrain',
             templateValues: {
@@ -350,7 +450,7 @@ defineSuite([
             map: 'my map'
         });
 
-        resource.addTemplateValues({
+        resource.setTemplateValues({
             x: 3,
             y: 4,
             z: 0,
@@ -518,6 +618,160 @@ defineSuite([
             });
     });
 
+    it('put calls with correct method', function() {
+        var expectedUrl = 'http://test.com/endpoint';
+        var expectedResponseType = 'json';
+        var expectedData = {
+            stuff: 'myStuff'
+        };
+        var expectedHeaders = {
+            'X-My-Header': 'My-Value'
+        };
+        var expectedResult = {
+            status: 'success'
+        };
+        var expectedMimeType = 'application/test-data';
+        var resource = new Resource({
+            url: expectedUrl,
+            headers: expectedHeaders
+        });
+
+        spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) {
+            expect(url).toEqual(expectedUrl);
+            expect(responseType).toEqual(expectedResponseType);
+            expect(method).toEqual('PUT');
+            expect(data).toEqual(expectedData);
+            expect(headers['X-My-Header']).toEqual('My-Value');
+            expect(headers['X-My-Other-Header']).toEqual('My-Other-Value');
+            expect(overrideMimeType).toBe(expectedMimeType);
+            deferred.resolve(expectedResult);
+        });
+
+        return resource.put(expectedData, {
+            responseType: expectedResponseType,
+            headers: {
+                'X-My-Other-Header': 'My-Other-Value'
+            },
+            overrideMimeType: expectedMimeType
+        })
+            .then(function(result) {
+                expect(result).toEqual(expectedResult);
+            });
+    });
+
+    it('static put calls with correct method', function() {
+        var expectedUrl = 'http://test.com/endpoint';
+        var expectedResponseType = 'json';
+        var expectedData = {
+            stuff: 'myStuff'
+        };
+        var expectedHeaders = {
+            'X-My-Header': 'My-Value'
+        };
+        var expectedResult = {
+            status: 'success'
+        };
+        var expectedMimeType = 'application/test-data';
+
+        spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) {
+            expect(url).toEqual(expectedUrl);
+            expect(responseType).toEqual(expectedResponseType);
+            expect(method).toEqual('PUT');
+            expect(data).toEqual(expectedData);
+            expect(headers).toEqual(expectedHeaders);
+            expect(overrideMimeType).toBe(expectedMimeType);
+            deferred.resolve(expectedResult);
+        });
+
+        return Resource.put({
+            url: expectedUrl,
+            data: expectedData,
+            responseType: expectedResponseType,
+            headers: expectedHeaders,
+            overrideMimeType: expectedMimeType
+        })
+            .then(function(result) {
+                expect(result).toEqual(expectedResult);
+            });
+    });
+
+    it('patch calls with correct method', function() {
+        var expectedUrl = 'http://test.com/endpoint';
+        var expectedResponseType = 'json';
+        var expectedData = {
+            stuff: 'myStuff'
+        };
+        var expectedHeaders = {
+            'X-My-Header': 'My-Value'
+        };
+        var expectedResult = {
+            status: 'success'
+        };
+        var expectedMimeType = 'application/test-data';
+        var resource = new Resource({
+            url: expectedUrl,
+            headers: expectedHeaders
+        });
+
+        spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) {
+            expect(url).toEqual(expectedUrl);
+            expect(responseType).toEqual(expectedResponseType);
+            expect(method).toEqual('PATCH');
+            expect(data).toEqual(expectedData);
+            expect(headers['X-My-Header']).toEqual('My-Value');
+            expect(headers['X-My-Other-Header']).toEqual('My-Other-Value');
+            expect(overrideMimeType).toBe(expectedMimeType);
+            deferred.resolve(expectedResult);
+        });
+
+        return resource.patch(expectedData, {
+            responseType: expectedResponseType,
+            headers: {
+                'X-My-Other-Header': 'My-Other-Value'
+            },
+            overrideMimeType: expectedMimeType
+        })
+            .then(function(result) {
+                expect(result).toEqual(expectedResult);
+            });
+    });
+
+    it('static patch calls with correct method', function() {
+        var expectedUrl = 'http://test.com/endpoint';
+        var expectedResponseType = 'json';
+        var expectedData = {
+            stuff: 'myStuff'
+        };
+        var expectedHeaders = {
+            'X-My-Header': 'My-Value'
+        };
+        var expectedResult = {
+            status: 'success'
+        };
+        var expectedMimeType = 'application/test-data';
+
+        spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) {
+            expect(url).toEqual(expectedUrl);
+            expect(responseType).toEqual(expectedResponseType);
+            expect(method).toEqual('PATCH');
+            expect(data).toEqual(expectedData);
+            expect(headers).toEqual(expectedHeaders);
+            expect(overrideMimeType).toBe(expectedMimeType);
+            deferred.resolve(expectedResult);
+        });
+
+        return Resource.patch({
+            url: expectedUrl,
+            data: expectedData,
+            responseType: expectedResponseType,
+            headers: expectedHeaders,
+            overrideMimeType: expectedMimeType
+        })
+            .then(function(result) {
+                expect(result).toEqual(expectedResult);
+            });
+    });
+
     it('static fetchArrayBuffer calls correct method', function() {
         var url = 'http://test.com/data';
         spyOn(Resource.prototype, 'fetchArrayBuffer').and.returnValue(when.resolve());
@@ -608,4 +862,157 @@ defineSuite([
                 expect(result).toEqual(expectedResult);
             });
     });
+
+    it('static delete calls correct method', function() {
+        var url = 'http://test.com/data';
+        spyOn(Resource.prototype, 'delete').and.returnValue(when.resolve());
+        return Resource.delete(url)
+            .then(function() {
+                expect(Resource.prototype.delete).toHaveBeenCalled();
+            });
+    });
+
+    it('delete calls correct method', function() {
+        var expectedUrl = 'http://test.com/endpoint';
+        var expectedResult = {
+            status: 'success'
+        };
+
+        spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) {
+            expect(url).toEqual(expectedUrl);
+            expect(method).toEqual('DELETE');
+            deferred.resolve(expectedResult);
+        });
+
+        var resource = new Resource({url: expectedUrl});
+        return resource.delete()
+            .then(function(result) {
+                expect(result).toEqual(expectedResult);
+            });
+    });
+
+    it('static head calls correct method', function() {
+        var url = 'http://test.com/data';
+        spyOn(Resource.prototype, 'head').and.returnValue(when.resolve({}));
+        return Resource.head(url)
+            .then(function() {
+                expect(Resource.prototype.head).toHaveBeenCalled();
+            });
+    });
+
+    it('head calls correct method', function() {
+        var expectedUrl = 'http://test.com/endpoint';
+        var expectedResult = {
+            'accept-ranges': 'bytes',
+            'access-control-allow-headers' : 'Origin, X-Requested-With, Content-Type, Accept',
+            'access-control-allow-origin' : '*',
+            'cache-control': 'public, max-age=0',
+            'connection' : 'keep-alive',
+            'content-length' : '883',
+            'content-type' : 'image/png',
+            'date' : 'Tue, 13 Feb 2018 03:38:55 GMT',
+            'etag' : 'W/"373-15e34d146a1"',
+            'vary' : 'Accept-Encoding',
+            'x-powered-vy' : 'Express'
+        };
+        var headerString = '';
+        for (var key in expectedResult) {
+            if (expectedResult.hasOwnProperty(key)) {
+                headerString += key + ': ' + expectedResult[key] + '\r\n';
+            }
+        }
+        var fakeXHR = {
+            status: 200,
+            send : function() {
+                this.onload();
+            },
+            open : function() {},
+            getAllResponseHeaders : function() {
+                return headerString;
+            }
+        };
+        spyOn(window, 'XMLHttpRequest').and.returnValue(fakeXHR);
+
+        spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) {
+            expect(url).toEqual(expectedUrl);
+            expect(method).toEqual('HEAD');
+            Resource._DefaultImplementations.loadWithXhr(url, responseType, method, data, headers, deferred, overrideMimeType);
+        });
+
+        var resource = new Resource({url: expectedUrl});
+        return resource.head()
+            .then(function(result) {
+                expect(result.date).toEqual(expectedResult.date);
+                expect(result['last-modified']).toEqual(expectedResult['last-modified']);
+                expect(result['x-powered-by']).toEqual(expectedResult['x-powered-by']);
+                expect(result.etag).toEqual(expectedResult.etag);
+                expect(result['content-type']).toEqual(expectedResult['content-type']);
+                expect(result['access-control-allow-origin']).toEqual(expectedResult['access-control-allow-origin']);
+                expect(result['cache-control']).toEqual(expectedResult['cache-control']);
+                expect(result['accept-ranges']).toEqual(expectedResult['accept-ranges']);
+                expect(result['access-control-allow-headers']).toEqual(expectedResult['access-control-allow-headers']);
+                expect(result['content-length']).toEqual(expectedResult['content-length']);
+            });
+    });
+
+    it('static options calls correct method', function() {
+        var url = 'http://test.com/data';
+        spyOn(Resource.prototype, 'options').and.returnValue(when.resolve({}));
+        return Resource.options(url)
+            .then(function() {
+                expect(Resource.prototype.options).toHaveBeenCalled();
+            });
+    });
+
+    it('options calls correct method', function() {
+        var expectedUrl = 'http://test.com/endpoint';
+        var expectedResult = {
+            'access-control-allow-headers' : 'Origin, X-Requested-With, Content-Type, Accept',
+            'access-control-allow-methods' : 'GET, PUT, POST, DELETE, OPTIONS',
+            'access-control-allow-origin' : '*',
+            'connection' : 'keep-alive',
+            'content-length' : '2',
+            'content-type' : 'text/plain; charset=utf-8',
+            'date' : 'Tue, 13 Feb 2018 03:38:55 GMT',
+            'etag' : 'W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc"',
+            'vary' : 'Accept-Encoding',
+            'x-powered-vy' : 'Express'
+        };
+        var headerString = '';
+        for (var key in expectedResult) {
+            if (expectedResult.hasOwnProperty(key)) {
+                headerString += key + ': ' + expectedResult[key] + '\r\n';
+            }
+        }
+        var fakeXHR = {
+            status: 200,
+            send : function() {
+                this.onload();
+            },
+            open : function() {},
+            getAllResponseHeaders : function() {
+                return headerString;
+            }
+        };
+        spyOn(window, 'XMLHttpRequest').and.returnValue(fakeXHR);
+
+        spyOn(Resource._Implementations, 'loadWithXhr').and.callFake(function(url, responseType, method, data, headers, deferred, overrideMimeType) {
+            expect(url).toEqual(expectedUrl);
+            expect(method).toEqual('OPTIONS');
+            Resource._DefaultImplementations.loadWithXhr(url, responseType, method, data, headers, deferred, overrideMimeType);
+        });
+
+        var resource = new Resource({url: expectedUrl});
+        return resource.options()
+            .then(function(result) {
+                expect(result.date).toEqual(expectedResult.date);
+                expect(result['x-powered-by']).toEqual(expectedResult['x-powered-by']);
+                expect(result.etag).toEqual(expectedResult.etag);
+                expect(result['content-type']).toEqual(expectedResult['content-type']);
+                expect(result['access-control-allow-origin']).toEqual(expectedResult['access-control-allow-origin']);
+                expect(result['access-control-allow-methods']).toEqual(expectedResult['access-control-allow-methods']);
+                expect(result['access-control-allow-headers']).toEqual(expectedResult['access-control-allow-headers']);
+                expect(result['content-length']).toEqual(expectedResult['content-length']);
+            });
+    });
 });
diff --git a/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js b/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js
index 693a2c27a511..705c57df5011 100644
--- a/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js
+++ b/Specs/Scene/GoogleEarthEnterpriseMapsProviderSpec.js
@@ -102,7 +102,7 @@ defineSuite([
     });
 
     it('rejects readyPromise on error', function() {
-        var url = 'invalid.localhost';
+        var url = 'http://invalid.localhost';
         var provider = new GoogleEarthEnterpriseMapsProvider({
             url : url,
             channel : 1234
@@ -292,7 +292,7 @@ defineSuite([
     });
 
     it('raises error on invalid url', function() {
-        var url = 'invalid.localhost';
+        var url = 'http://invalid.localhost';
         var provider = new GoogleEarthEnterpriseMapsProvider({
             url : url,
             channel : 1234