Fixes #2360 Fixes #1138 Fixes #1788 a little less hackishly Probably fixes a few other issues I'm not aware of. This more or less goes with @lhorie's comment here, just with a minor name change from `query` to `params`: https://github.com/MithrilJS/mithril.js/issues/1138#issuecomment-231363395 Specifically, here's what this patch entails: - I changed `data` and `useBody` to `params` and `body` in `m.request`. Migration is trivial: just use `params` or `body` depending on which you intend to send. Most servers do actually care where the data goes, so you can generally pretty easily translate this accordingly. If you *really* need the old behavior, pass the old value in `params` and if `method === "GET"` or `method === "TRACE"`, also in `body`. - I opened up all methods to have request bodies. - I fixed `m.parseQueryString` to prefer later values over earlier values and to ensure that objects and arrays are persisted across both hash and query param parsing. That method also accepts an existing key/value map to append to, to simplify deduplication. - I normalized path interpolation to be identical between routes and requests. - I no longer include interpolated values in query strings. If you need to duplicate values again, rename the interpolation to be a distinct property and pass the value you want to duplicate as it. - I converted `m.route` to use pre-compiled routes instead of its existing system of dynamic runtime checking. This shouldn't have a major effect on performance short-term, but it'll ease the migration to built-in userland components and make it a little easier to reconcile. It'll also come handy for large numbers of routes. - I added support for matching routes like `"/:file.:ext"` or `"/:lang-:region"`, giving each defined semantics. - I added support for matching against routes with static query strings, such as `"/edit?type=image": { ... }`. - I'm throwing a few new informative errors. - And I've updated the docs accordingly. I also made a few drive-by edits: - I fixed a bug in the `Stream.HALT` warning where it warned all but the first usage when the intent was to warn only on first use. - Some of the tests were erroneously using `Stream.HALT` when they should've been using `Stream.SKIP`. I've fixed the tests to only test that `Stream.HALT === Stream.SKIP` and that it only warns on first use. - The `m.request` and `m.jsonp` docs signatures were improved to more clearly explain how `m.request(url, options?)` and `m.jsonp(url, options?)` translate to `m.request(options)` and `m.jsonp(options)` respectively. ----- There is some justification to these changes: - In general, it matters surprisingly more than you would expect how things translate to HTTP requests. So the comment there suggesting a thing that papers over the difference has led to plenty of confusion in both Gitter and in GitHub issues. - A lot of servers expect a GET with a body and no parameters, and leaving `m.request` open to working with that makes it much more flexible. - Sometimes, servers expect a POST with query parameters *instead* of a JSON object. I've seen this quite a bit, even with more popular REST APIs like Stack Overflow's. - I've encountered a few servers that expect both parameters and a body, each with distinct semantic meaning, so the separation makes it much easier to translate into a request. - Most of the time, path segments are treated individually, and URL-escaping the contents is much less error-prone. It also avoids being potentially lossy, and when the variable in question isn't trusted, escaping the path segment enables you to pass it through the URL and not risk being redirected to unexpected locations, avoiding some risks of vulnerabilities and client side crashes. If you really don't care how the template and parameters translate to an eventual URL, just pass the same object for the `params` and `body` and use `:param...` for each segment. Either way, the more explicit nature should help a lot in making the intent clearer, whether you care or not.
4.7 KiB
jsonp(options)
Description
Makes JSON-P requests. Typically, it's useful to interact with servers that allow JSON-P but that don't have CORS enabled.
m.jsonp({
url: "/api/v1/users/:id",
data: {id: 1},
callbackKey: "callback",
})
.then(function(result) {
console.log(result)
})
Signature
promise = m.jsonp(options)
| Argument | Type | Required | Description |
|---|---|---|---|
options |
Object |
Yes | The request options to pass. |
options.url |
String |
Yes | The path name to send the request to, optionally interpolated with values from options.data. |
options.data |
any |
No | The data to be interpolated into the URL and serialized into the querystring. |
options.type |
any = Function(any) |
No | A constructor to be applied to each object in the response. Defaults to the identity function. |
options.callbackName |
String |
No | The name of the function that will be called as the callback. Defaults to a randomized string (e.g. _mithril_6888197422121285_0({a: 1}) |
options.callbackKey |
String |
No | The name of the querystring parameter name that specifies the callback name. Defaults to callback (e.g. /someapi?callback=_mithril_6888197422121285_0) |
options.background |
Boolean |
No | If false, redraws mounted components upon completion of the request. If true, it does not. Defaults to false. |
| returns | Promise |
A promise that resolves to the response data, after it has been piped through type method |
promise = m.jsonp(url, options)
| Argument | Type | Required | Description |
|---|---|---|---|
url |
String |
Yes | The path name to send the request to. options.url overrides this when present. |
options |
Object |
No | The request options to pass. |
| returns | Promise |
A promise that resolves to the response data, after it has been piped through the type method |
This second form is mostly equivalent to m.jsonp(Object.assign({url: url}, options)), just it does not depend on the ES6 global Object.assign internally.
How it works
The m.jsonp utility is useful for third party APIs that can return data in JSON-P format.
In a nutshell, JSON-P consists of creating a script tag whose src attribute points to a script that lives in the server outside of your control. Typically, you are required to define a global function and specify its name in the querystring of the script's URL. The response will return code that calls your global function, passing the server's data as the first parameter.
JSON-P has several limitations: it can only use GET requests, it implicitly trusts that the third party server won't serve malicious code and it requires polluting the global JavaScript scope. Nonetheless, it is sometimes the only available way to retrieve data from a service (for example, if the service doesn't support CORS).
Typical usage
Some services follow the de-facto convention of responding with JSON-P if a callback querystring key is provided, thus making m.jsonp automatically work without any effort:
m.jsonp({url: "https://api.github.com/users/lhorie"}).then(function(response) {
console.log(response.data.login) // logs "lhorie"
})
Some services do not follow conventions and therefore you must specify the callback key that the service expects:
m.jsonp({
url: "https://api.flickr.com/services/feeds/photos_public.gne?tags=kitten&format=json",
callbackKey: "jsoncallback",
})
.then(function(response) {
console.log(response.link) // logs "https://www.flickr.com/photos/tags/kitten/"
})
And sometimes, you just want to take advantage of HTTP caching for GET requests for rarely-modified data:
// this request is always called with the same querystring, and therefore it is cached
m.jsonp({
url: "https://api.github.com/users/lhorie",
callbackName: "__callback",
})
.then(function(response) {
console.log(response.data.login) // logs "lhorie"
})