diff --git a/docs/change-log.md b/docs/change-log.md index c0bb1199..39747dd2 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -40,6 +40,9 @@ - route, request: Interpolated arguments are *not* appended to the query string. This means `m.request({url: "/api/user/:id/get", params: {id: user.id}})` would result in a request like `GET /api/user/1/get`, not one like `GET /api/user/1/get?id=1`. If you really need it in both places, pass the same value via two separate parameters with the non-query-string parameter renamed, like in `m.request({url: "/api/user/:urlID/get", params: {id: user.id, urlID: user.id}})`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) - route, request: `m.route.set`, `m.request`, and `m.jsonp` all use the same path template syntax now, and vary only in how they receive their parameters. Furthermore, declared routes in `m.route` shares the same syntax and semantics, but acts in reverse as if via pattern matching. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361)) - request: `options.responseType` now defaults to `"json"` if `extract` is absent, and `deserialize` receives the parsed response, not the raw string. If you want the old behavior, [use `responseType: "text"`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType). ([#2335](https://github.com/MithrilJS/mithril.js/pull/2335)) +- request: set `Content-Type: application/json; charset=utf-8` for all XHR methods by default, provided they have a body that's `!= null` ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361), [#2421](https://github.com/MithrilJS/mithril.js/pull/2421)) + - This can cause CORS issues when issuing `GET` with bodies, but you can address them through configuring CORS appropriately. + - Previously, it was only set for all non-`GET` methods and only when `useBody: true` was passed (the default), and it was always set for them. Now it's automatically omitted when no body is present, so the hole is slightly broadened. #### News diff --git a/request/request.js b/request/request.js index cc08d8d8..64af3123 100644 --- a/request/request.js +++ b/request/request.js @@ -71,7 +71,7 @@ module.exports = function($window, Promise) { xhr.open(method, url, args.async !== false, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) - if (assumeJSON && !hasHeader(args, /^content-type$/i)) { + if (assumeJSON && body != null && !hasHeader(args, /^content-type$/i)) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { diff --git a/request/tests/test-request.js b/request/tests/test-request.js index 57aaa513..b6556d0e 100644 --- a/request/tests/test-request.js +++ b/request/tests/test-request.js @@ -434,19 +434,6 @@ o.spec("xhr", function() { o(xhr.getRequestHeader("Content-Type")).equals("Value") } }) - o("json headers are set to the correct default value", function(done) { - mock.$defineRoutes({ - "POST /item": function() { - 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("doesn't fail on abort", function(done) { mock.$defineRoutes({ "GET /item": function() { @@ -640,4 +627,72 @@ o.spec("xhr", function() { }) }) }) + o.spec("json header", function() { + function checkUnset(method) { + o("doesn't set header on " + method + " without body", function(done) { + var routes = {} + routes[method + " /item"] = function() { + return {status: 200, responseText: JSON.stringify({a: 1})} + } + mock.$defineRoutes(routes) + xhr({ + method: method, url: "/item", + config: function(xhr) { + var header = xhr.getRequestHeader("Content-Type") + o(header).equals(undefined) + header = xhr.getRequestHeader("Accept") + o(header).equals("application/json, text/*") + } + }).then(function(result) { + o(result).deepEquals({a: 1}) + done() + }).catch(function(e) { + done(e) + }) + }) + } + + function checkSet(method, body) { + o("sets header on " + method + " with body", function(done) { + var routes = {} + routes[method + " /item"] = function(response) { + return { + status: 200, + responseText: JSON.stringify({body: JSON.parse(response.body)}), + } + } + mock.$defineRoutes(routes) + xhr({ + method: method, url: "/item", body: body, + config: function(xhr) { + var header = xhr.getRequestHeader("Content-Type") + o(header).equals("application/json; charset=utf-8") + header = xhr.getRequestHeader("Accept") + o(header).equals("application/json, text/*") + } + }).then(function(result) { + o(result).deepEquals({body: body}) + done() + }).catch(function(e) { + done(e) + }) + }) + } + + checkUnset("GET") + checkUnset("HEAD") + checkUnset("OPTIONS") + checkUnset("POST") + checkUnset("PUT") + checkUnset("DELETE") + checkUnset("PATCH") + + checkSet("GET", {foo: "bar"}) + checkSet("HEAD", {foo: "bar"}) + checkSet("OPTIONS", {foo: "bar"}) + checkSet("POST", {foo: "bar"}) + checkSet("PUT", {foo: "bar"}) + checkSet("DELETE", {foo: "bar"}) + checkSet("PATCH", {foo: "bar"}) + }) })