问题来源

期末的一个作业,我和同学打算建一个前后端分离的 Web 项目。他用 java 写了一个 api server,而我则构建前端。
我们的 api 抄了别人的一套设计,但只有自己实现的接口遇到了跨域资源请求被禁止的问题。
经过一番调查,我发现别人的接口获取的数据其实是jsonp格式,只是文档里让你看起来像获取json一样。

jsonp 和 json

我翻开《JSON 必知必会》发现发现其 6.3.2 小节有介绍jsonp
json我就不多讲了,我们看看jsonp是怎么一回事。
我们用jQuery向服务发起一个ajax请求jsonp数据(注意dataType字段):

$.ajax({
  method: 'GET',
  url:"http://localhost:3000/login",
  dataType:"jsonp",
  data: {
    userid: that.userid
  },
  success(data, status, xhr) {
    console.log(data);
  }
})

浏览器实际向服务器发起的请求 url 是这样的(注意callback这个查询参数):

http://localhost:3000/login?userid=h&callback=jsonp

从服务器返回的参数竟然是一段javascript代码:

jsonp({
  "success": false,
  "code": 13
});

原来,jsonp 是这么工作的:它通过动态地向页面插入<script>的方式获取数据。
我们使用的jQuery库的函数做好了很多工作,让它感觉和获取json一样。(js原生写法可以看《JSON 必知必会》6.3.2)
既然知道是怎么一会事了,我该如何补救呢?

解决办法

1. 改 java 服务器端代码

这个方法很现实,不过:

  • 老实说我 java 已经不会写了,其对于我来说是 Readonly。
  • 找同学修改,不太好意思呢。

2. 做个转换的中间层

对于我个准前端来说,还是node.js最友好。
实现思路大致如下:

-> 获得请求
-> 所有请求转发 java 服务器
-> 从 java 服务器获得的 json 并包装成 callback(json),也就是jsonp
-> 回复

像这个样子:

---------------     请求JSONP      ---------------      请求JSON       --------------- 
|             |------------------>|    json      |------------------>|              |
|    浏览器    |                   |     to       |                   |     服务器    |
|             |<------------------|    jsonp     |<------------------|              |
---------------     答复JSONP      ---------------      答复JSON       --------------- 

原来我的想法是通过 node 的原生 api 来做,后来想起原来一直拿来做静态 api server 的 json-server 模块,结果寥寥几行代码就搞定了:

var server = require('json-server')();
var request = require('request');

var serverUrl = 'http://the.url.of.json.server';

server.get('*', function (req, res) {
  request(serverUrl + req.url, function (error, response, body) {
     res.jsonp(JSON.parse(response.body));
  });
});

server.listen(3000);

(同学 api 实现得不太好,错误的参数会返回 tomcat 的 html 页面,所以我是做了错误处理以防崩溃,上面代码简便起见不处理了)
json-server还有提供网页资源的功能,如果页面是这里提供,那么直接转发json也是可以的,不跨域了嘛。

总结

最后再啰嗦点其他东西:

  1. 我觉得别人的 API 是这样设计的:当请求参数有callback时答复jsonp,没有则是json,这样服务器可以多端通用啦!这个参数取名叫callback难道是业界通用?
  2. 其实跨域也不是只有jsonp一种办法,还有更安全的跨域资源共享(cross-origin resource sharing,CORS)技术,也在《JSON 必知必会》6.3 里。
  3. 我为什么要写这篇文章呢?这里其实没什么技术含量,只是我网上一搜全是 Java、PHP 服务器上的方法,那就不是很容易看明白。难道遇到这问题最多的不是前端程序员吗?