而有时候,我们不得不在一个客户端下访问不同域中的资源,于是需要用到一些方法来避开浏览器的同源策略,这些方法被称为跨域。
实现跨域有如下几种方法:
JSONP(JSON with Padding)是数据格式JSON的一种使用模式,可以使网页实现跨域请求。其原理主要利用了 HTML 的 script 标签。由于 script 是采用开放策略,通过设置 src 引入不同域下的资源,所以可以通过 script 实现跨域,该方法需要后端支持。jsonp跨域的实现步骤如下:
下面来做个演示,首先为演示方便,将系统的hosts做如下修改:
以上例子最终实现了由example.a.com到example.b.com的跨域。应注意的是,因为 <script>只能发送GET请求,所以jsonp只能实现GET请求的跨域。如果希望能实现其他请求的跨域,就可以用接下来介绍的一种方法——CORS。
CORS(全称为:Cross-Origin Resouce Sharing)跨域资源共享,是一种通过ajax跨域请求资源的方法。浏览器将CORS请求分为两大类,简单请求(simple request)和非简单请求(not-so-simple request,浏览器对这两种请求的处理方式不一样。如果请求满足以下两个条件,则为简单请求。
简单请求的实现方式即当用XMLHttpRequest发请求时,浏览器如果发现该请求不符合同源策略,会给该请求加上一个请求头origin,origin用来说明本次请求来自哪个源(协议+域名+端口)。如果origin指定的源不在后台允许范围内,后台会返回一个正常的HTTP响应,然后浏览器会发现该响应头部信息不包含Access-Control-Allow-Origin字段,然后抛出一个错误,该错误被XMLHttpRequest的onerror函数捕获,响应被驳回,但因为该错误无法通过状态码识别,所以HTTP回应的状态码还是200。如果origin在后台允许范围内,则服务器返回的响应,会包含Access-Control-Allow-Origin:Origin(指定的源)信息,浏览器此时不会抛错,响应能正常处理。
非简单请求是是请求方法为PUT或DELETE,又或者Content-Type为application/json的对服务器有特殊要求的请求。非简单请求的CORS请求,会在正式通信前增加一次HTTP查询,称为预检(preflight),询问服务器当前网页所在域名是否在服务器的许可名单中,如果在,则发出正式的XMLHttpRequest,之后就与简单请求一样,不在则报错。
依旧用上面的例子。
最终实现的效果与第一个jsonp的例子一样。
还有一种方式,就是通过降域来实现跨域。即通过设置document.domain的方式,将两个域名的domain设置为一个,如对于a.example.com和b.example.com,可以通过js设置 document.domain = "example.com" ,实现跨域。
做个演示,假设在 http://a.example.com:8080 下有一个a.html文件,其中a.html中有一个 iframe ,它的 src 为 http://b.example.com:8080/b.html 。
用降域方法实现跨域操作简单,但是有一些缺点。比如域名只能往下设置,不能回去,比如从example.com回到a.example.com。同时如果一个子域名被攻击,多个被降域的域名都会被连带攻击,有很大的安全风险。
postMessage是一个web API,可以实现跨域通信。 window.postMessage() 被调用时,会在所有页面脚本执行完毕后,向目标窗口派发一个 MessageEvent 消息。语法如下:
MessageEvent 具有如下属性:
用一个与上面降域类似的例子来做演示。同样有两个页面a.html和b.html,a.html中的 iframe 的 src 指向b.html。
最终实现a.html与b.html通信效果如下:
使用postMessage方法应注意的是,如果不希望从其他网站接收message,那么不要为message事件添加任何监听器。如果确实希望接收其他网站的message,那么应该始终使用origin和source属性来验证发件人的身份,以免被恶意的网站攻击。
以上就是几种常见的跨域方法,各有优劣,且各自都有一定的安全问题,在日常应用中,需要有针对性的使用,对可能的安全风险采取相应措施。
非常狭义地说,前端在网络协议层面就不能直接「连接」到数据库。因为 JS 默认发出的请求走的都是应用层的 HTTP 协议,并非与常见数据库交互时所需的长连接。此时的通信协议一般是各家数据库厂商在 TCP/UDP 之上自己约定的(比如默认 MySQL 连 3306 端口走 MySQL 的协议 ,MongoDB 连 27017 端口然后走它的 Wire Protocol )。虽然不同数据库之间有 ODBC 这样的标准能让你兼容地连上它们然后无脑 SQL,但这类协议的实现也并非建立在 HTTP 或 WebSocket 之上,不是前端直接发网络请求就能连上的东西。当然这种理解比较片面。因为你在技术上也完全可以直接在前端生成 SQL,靠一层非常薄的后端服务「代理」转发给数据库执行。这也就是很多其他回答里的「SQL over HTTP」式方案。容易看出,这种做法在安全性和性能上都有先天的不足,不属于现在互联网行业显学的主流(F12 警告,注入警告,大并发警告)。但除了直接在前端拼 SQL 之外,还有没有更广义一点的「前端连接数据库」方式呢?个人觉得其实还是有的。比如为了尽可能易用,数据库本身的 API 都可以直接做成 REST 的形式方便前端一把梭,例子就是很方便做小应用的 CouchDB。再广义一点,如果把 GraphQL 当作「更好的 SQL 格式」,那么只要你能和 GraphQL 服务端通信,是不是也算直接连上了数据库呢?这时候也已经有 Hasura 这样的项目能帮你把 GraphQL 查询直接映射到 PostgreSQL 等数据库上,这时它在概念上就像一种更方便的跨数据库 driver 方便你来增查改删,这个饼感觉还是能画得很大的。毕竟按传统的互联网味技术栈,一个业务概念需要在数据库里、后端业务逻辑和前端业务逻辑里各定义一遍,中途太多胶水可以节约了,肯定是一步到胃最舒服啊。