From 95d738bc71b149f3279f0b23b0fa3ff407d21189 Mon Sep 17 00:00:00 2001 From: impinball Date: Fri, 30 Dec 2016 02:52:00 -0500 Subject: [PATCH 1/2] Add support for `options.headers` in `m.request` + tests/docs I also had to edit the mocks accordingly, so I could inspect the headers set. --- docs/request.md | 18 +++++++++++++++-- request/request.js | 8 ++++++-- request/tests/test-request.js | 37 +++++++++++++++++++++++++++++++++++ test-utils/xhrMock.js | 8 +++++++- 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/docs/request.md b/docs/request.md index 75be0de9..3671f957 100644 --- a/docs/request.md +++ b/docs/request.md @@ -50,6 +50,7 @@ Argument | Type | Required | Descr `options.password` | `String` | No | A password for HTTP authorization. Defaults to `undefined`. This option is provided for `XMLHttpRequest` compatibility, but you should avoid using it because it sends the password in plain text over the network. `options.withCredentials` | `Boolean` | No | Whether to send cookies to 3rd party domains. Defaults to `false` `options.config` | `xhr = Function(xhr)` | No | Exposes the underlying XMLHttpRequest object for low-level configuration. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). +`options.headers` | `Object` | No | Headers to append to the request before sending it (applied right before `options.config`). `options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function). `options.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`). `options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the response. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses. @@ -404,7 +405,21 @@ function parseCSV(data) { } ``` -Ignoring the fact that the parseCSV function above doesn't handle a lot of cases that a proper CSV parser would, the code above logs an array of arrays +Ignoring the fact that the parseCSV function above doesn't handle a lot of cases that a proper CSV parser would, the code above logs an array of arrays. + +Custom headers may also be helpful in this regard. For example, if you're requesting an SVG, you probably want to set the content type accordingly. To override the default JSON request type, set `options.headers` to an object of key-value pairs corresponding to request header names and values. + +```javascript +m.request({ + method: "GET", + url: "/files/image.svg", + headers: { + "Content-Type": "image/svg+xml; charset=utf-8", + "Accept": "image/svg, text/*" + }, + deserialize: function(value) {return value} +}) +``` --- @@ -485,4 +500,3 @@ m.request("/api/v1/users").then(function(users) { console.log("list of users:", users) }) ``` - diff --git a/request/request.js b/request/request.js index 2a9597ba..11e2e956 100644 --- a/request/request.js +++ b/request/request.js @@ -33,7 +33,7 @@ module.exports = function($window, Promise) { } return args } - + function request(args, extra) { var finalize = finalizer() args = normalize(args, extra) @@ -63,6 +63,10 @@ module.exports = function($window, Promise) { } if (args.withCredentials) xhr.withCredentials = args.withCredentials + for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) { + xhr.setRequestHeader(key, args.headers[key]) + } + if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr xhr.onreadystatechange = function() { @@ -93,7 +97,7 @@ module.exports = function($window, Promise) { function jsonp(args, extra) { var finalize = finalizer() args = normalize(args, extra) - + var promise = new Promise(function(resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") diff --git a/request/tests/test-request.js b/request/tests/test-request.js index 86643e95..a9fce1dd 100644 --- a/request/tests/test-request.js +++ b/request/tests/test-request.js @@ -343,6 +343,43 @@ o.spec("xhr", function() { done() }, 20) }) + o("headers are set when header arg passed", function(done) { + mock.$defineRoutes({ + "POST /item": function(request) { + return {status: 200, responseText: ""} + } + }) + xhr({method: "POST", url: "/item", config: config, headers: {"Custom-Header": "Value"}}).then(done) + + function config(xhr) { + o(xhr.getRequestHeader("Custom-Header")).equals("Value") + } + }) + o("headers are with higher precedence than default headers", function(done) { + mock.$defineRoutes({ + "POST /item": function(request) { + return {status: 200, responseText: ""} + } + }) + xhr({method: "POST", url: "/item", config: config, headers: {"Content-Type": "Value"}}).then(done) + + function config(xhr) { + o(xhr.getRequestHeader("Content-Type")).equals("Value") + } + }) + o("json headers are set to the correct default value", function(done) { + mock.$defineRoutes({ + "POST /item": function(request) { + return {status: 200, responseText: ""} + } + }) + xhr({method: "POST", url: "/item", config: config}).then(done) + + function config(xhr) { + o(xhr.getRequestHeader("Content-Type")).equals("application/json; charset=utf-8") + o(xhr.getRequestHeader("Accept")).equals("application/json, text/*") + } + }) }) o.spec("failure", function() { o("rejects on server error", function(done) { diff --git a/test-utils/xhrMock.js b/test-utils/xhrMock.js index ccf78331..0a7b8f8d 100644 --- a/test-utils/xhrMock.js +++ b/test-utils/xhrMock.js @@ -14,7 +14,13 @@ module.exports = function() { var $window = { XMLHttpRequest: function XMLHttpRequest() { var args = {} - this.setRequestHeader = function(header, value) {} + var headers = {} + this.setRequestHeader = function(header, value) { + headers[header] = value + } + this.getRequestHeader = function(header) { + return headers[header] + } this.open = function(method, url, async, user, password) { var urlData = parseURL(url, {protocol: "http:", hostname: "localhost", port: "", pathname: "/"}) args.method = method From 81a540ae410686846b81b249699a8117b014f235 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Fri, 30 Dec 2016 12:21:08 +0000 Subject: [PATCH 2/2] Bundled output for commit ad8f7b6f2ccc90fe8055a318de0ae55abd179838 [skip ci] --- README.md | 2 +- mithril.js | 5 +-- mithril.min.js | 84 +++++++++++++++++++++++++------------------------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 913fdfe3..b6e99098 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,6 @@ There are over 4000 assertions in the test suite, and tests cover even difficult ## Modularity -Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at 7.55 KB min+gzip +Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at 7.59 KB min+gzip In addition, Mithril is now completely modular: you can import only the modules that you need and easily integrate 3rd party modules if you wish to use a different library for routing, ajax, and even rendering diff --git a/mithril.js b/mithril.js index 074077db..55810181 100644 --- a/mithril.js +++ b/mithril.js @@ -232,7 +232,6 @@ var _8 = function($window, Promise) { } return args } - function request(args, extra) { var finalize = finalizer() args = normalize(args, extra) @@ -255,6 +254,9 @@ var _8 = function($window, Promise) { xhr.setRequestHeader("Accept", "application/json, text/*") } if (args.withCredentials) xhr.withCredentials = args.withCredentials + for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) { + xhr.setRequestHeader(key, args.headers[key]) + } if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr xhr.onreadystatechange = function() { if (xhr.readyState === 4) { @@ -282,7 +284,6 @@ var _8 = function($window, Promise) { function jsonp(args, extra) { var finalize = finalizer() args = normalize(args, extra) - var promise0 = new Promise(function(resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") diff --git a/mithril.min.js b/mithril.min.js index fe499750..ddf9415f 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,42 +1,42 @@ -new function(){function w(a,c,g,d,f,l){return{tag:a,key:c,attrs:g,children:d,text:f,dom:l,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function A(a){if(null==a||"string"!==typeof a&&null==a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===G[a]){for(var c,g,d=[],f={};c=N.exec(a);){var l=c[1],k=c[2];""===l&&""!==k?g=k:"#"===l?f.id=k:"."===l?d.push(k):"["===c[3][0]&&((l=c[6])&&(l=l.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")), -"class"===c[4]?d.push(l):f[c[4]]=l||!0)}0a.indexOf("?")?"?":"&";a+=d+b}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(C){throw Error(a); -}}function r(a){return a.responseText}function q(a,c){if("function"===typeof a)if(c instanceof Array)for(var b=0;bm.status||304===m.status)c(q(b.type,a));else{var f=Error(m.responseText),g;for(g in a)f[g]=a[g];d(f)}}catch(e){d(e)}};g&&null!=b.data?m.send(b.data):m.send()});return!0===b.background?z:u(z)}, -jsonp:function(b,k){var r=g();b=d(b,k);var u=new c(function(c,d){var g=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=a.document.createElement("script");a[g]=function(d){k.parentNode.removeChild(k);c(q(b.type,d));delete a[g]};k.onerror=function(){k.parentNode.removeChild(k);d(Error("JSONP request failed"));delete a[g]};null==b.data&&(b.data={});b.url=f(b.url,b.data);b.data[b.callbackKey||"callback"]=g;k.src=l(b.url,b.data);a.document.documentElement.appendChild(k)});return!0=== -b.background?u:r(u)},setCompletionCallback:function(a){u=a}}}(window,x),M=function(a){function c(e,h,a,b,c,d,f){for(;a=t&&z>=u;){var y=a[t],n=p[u];if(y!==n||h)if(null==y)t++;else if(null==n)u++;else if(y.key===n.key)t++,u++,k(e,y,n,d,q(a,t,f),h,l),h&&y.tag===n.tag&&m(e,r(y),f);else if(y=a[v],y!==n||h)if(null==y)v--;else if(null==n)u++;else if(y.key===n.key)k(e, -y,n,d,q(a,v+1,f),h,l),(h||u=t&&z>=u;){y=a[v];n=p[z];if(y!==n||h)if(null==y)v--;else{if(null!=n)if(y.key===n.key)k(e,y,n,d,q(a,v+1,f),h,l),h&&y.tag===n.tag&&m(e,r(y),f),null!=y.dom&&(f=y.dom),v--;else{if(!C){C=a;var y=v,E={},w;for(w=0;wa.indexOf("?")?"?":"&";a+=d+c}return a}function m(b){try{return""!==b?JSON.parse(b):null}catch(C){throw Error(b); +}}function q(b){return b.responseText}function n(b,c){if("function"===typeof b)if(c instanceof Array)for(var a=0;ak.status||304===k.status)c(n(a.type,b));else{var g=Error(k.responseText),e;for(e in b)g[e]=b[e]; +d(g)}}catch(f){d(f)}};h&&null!=a.data?k.send(a.data):k.send()});return!0===a.background?z:t(z)},jsonp:function(a,m){var t=h();a=d(a,m);var q=new c(function(c,d){var h=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+k++,m=b.document.createElement("script");b[h]=function(d){m.parentNode.removeChild(m);c(n(a.type,d));delete b[h]};m.onerror=function(){m.parentNode.removeChild(m);d(Error("JSONP request failed"));delete b[h]};null==a.data&&(a.data={});a.url=g(a.url,a.data);a.data[a.callbackKey|| +"callback"]=h;m.src=l(a.url,a.data);b.document.documentElement.appendChild(m)});return!0===a.background?q:t(q)},setCompletionCallback:function(b){t=b}}}(window,x),M=function(b){function c(e,f,b,a,c,d,g){for(;b=u&&z>=t;){var y=f[u],p=b[t];if(y!==p||r)if(null==y)u++;else if(null==p)t++;else if(y.key===p.key)u++,t++,m(e,y,p,d,n(f,u,g),r,l),r&&y.tag=== +p.tag&&k(e,q(y),g);else if(y=f[v],y!==p||r)if(null==y)v--;else if(null==p)t++;else if(y.key===p.key)m(e,y,p,d,n(f,v+1,g),r,l),(r||t=u&&z>=t;){y=f[v];p=b[z];if(y!==p||r)if(null==y)v--;else{if(null!=p)if(y.key===p.key)m(e,y,p,d,n(f,v+1,g),r,l),r&&y.tag===p.tag&&k(e,q(y),g),null!=y.dom&&(g=y.dom),v--;else{if(!C){C=f;var y=v,E={},w;for(w=0;w