还在问跨域?本文记录js跨域的多种实现实例
前言
众所周知,受浏览器同源策略的影响,产生了跨域问题,那么我们应该如何实现跨域呢?本文记录几种跨域的简单实现
前期准备
为了方便测试,我们启动两个服务,10086(就是在这篇博客自动生成的项目,请戳:SpringBoot系列——Spring-Data-JPA(究极进化版) 自动生成单表基础增、删、改、查接口)服务提供者,10087服务消费者,消费者有一个页面test.html跟一个后端controller
- <!DOCTYPE html>
- <!--解决idea thymeleaf 表达式模板报红波浪线-->
- <!--suppress ALL -->
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>跨域测试</title>
- <!-- 引入静态资源 -->
- <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
- </head>
- <body>
- <h3>直接ajax请求10086服务的接口</h3>
- <button id="button">发送ajax请求</button>
- <br/>
- <textarea id="text" style="width: 380px; height: 380px;">
- </textarea>
- </body>
- <script th:inline="javascript">
- ctx = [[${#request.getContextPath()}]];//应用路径
- //绑定按钮点击事件
- $("body").on("click","#button",function(e){
- $("#text").text("");
- //发送ajax请求
- $.get("http://localhost:10086/tbUser/get/1",function (data) {
- $("#text").text(data);
- })
- })
- </script>
- </html>
- @RequestMapping("page/test")
- public ModelAndView pageLogin() {
- return new ModelAndView("test.html");
- }
我们先启动10086服务
浏览器访问接口,正常获取数据
启动10087服务
访问test.html页面,直接ajax请求10086服务的接口,直接报错
几种跨域方式
1、后端安全跨域
前端发起请求后端,后端使用httpclient(使用方法参考:httpclient+jsoup实现小说线上采集阅读)或者feign(使用方法参考:SpringCloud系列——Feign 服务调用)安全跨域
我们给10087服务消费者新增两个controller接口,用于后台调用10086跟响应数据,并修改test.html
httpclient
- <!DOCTYPE html>
- <!--解决idea thymeleaf 表达式模板报红波浪线-->
- <!--suppress ALL -->
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>跨域测试</title>
- <!-- 引入静态资源 -->
- <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
- </head>
- <body>
- <h3>后端安全跨域(httpclient)</h3>
- <button id="button">发送ajax请求</button>
- <br/>
- <textarea id="text" style="width: 380px; height: 380px;">
- </textarea>
- </body>
- <script th:inline="javascript">
- ctx = [[${#request.getContextPath()}]];//应用路径
- //绑定按钮点击事件
- $("body").on("click","#button",function(e){
- $("#text").text("");
- //发送ajax请求
- $.get(ctx + "/test/httpclient",{id:"1"},function (data) {
- $("#text").text(data);
- })
- })
- </script>
- </html>
- @RequestMapping("/test/httpclient")
- public Object httpclient(String id) {
- String result = null;
- try {
- //创建httpclient对象 (这里设置成全局变量,相对于同一个请求session、cookie会跟着携带过去)
- CloseableHttpClient httpClient = HttpClients.createDefault();
- //创建get方式请求对象
- HttpGet httpGet = new HttpGet("http://localhost:10086/tbUser/get/"+id);
- httpGet.addHeader("Content-type", "application/json");
- //包装一下
- httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
- httpGet.addHeader("Connection", "keep-alive");
- //通过请求对象获取响应对象
- CloseableHttpResponse response = httpClient.execute(httpGet);
- //获取结果实体
- if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
- result = EntityUtils.toString(response.getEntity(), "GBK");
- }
- //释放链接
- response.close();
- }
- //这里还可以捕获超时异常,重新连接抓取
- catch (Exception e) {
- result = null;
- e.printStackTrace();
- }
- return result;
- }
feign
我们先在10087服务消费者修改test.html,新增controller接口,maven引入feign,创建TestFeign,值得注意的是消费者的返回值必须与提供者的返回值一致,参数对象也要一致
- <!DOCTYPE html>
- <!--解决idea thymeleaf 表达式模板报红波浪线-->
- <!--suppress ALL -->
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>跨域测试</title>
- <!-- 引入静态资源 -->
- <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
- </head>
- <body>
- <h3>后端安全跨域(feign)</h3>
- <button id="button">发送ajax请求</button>
- <br/>
- <textarea id="text" style="width: 380px; height: 380px;">
- </textarea>
- </body>
- <script th:inline="javascript">
- ctx = [[${#request.getContextPath()}]];//应用路径
- //绑定按钮点击事件
- $("body").on("click","#button",function(e){
- $("#text").text("");
- //发送ajax请求
- $.get(ctx + "/test/feign",{id:"1"},function (data) {
- $("#text").text(data);
- })
- })
- </script>
- </html>
- @FeignClient(name = "http://localhost:10086", path = "/tbUser/")
- public interface TestFeign {
- @RequestMapping(value = "get/{id}")
- Result<UserVo> get(@PathVariable("id") Integer id);
- }
- @Autowired
- private TestFeign testFeign;
- @RequestMapping("/test/feign")
- public Object feign(Integer id){
- return testFeign.get(id);
- }
直接使用url会报这个错,因为我们没有注册这两个服务,eureka也没起...,所以想使用feign调用,两个服务都需要在eureka上注册,负载均衡器才能找到它
我们将两个服务注册到eureka上(参考springcloud系列博客之SpringCloud系列——Eureka 服务注册与发现),将@FeignClient(name = "http://localhost:10086", path = "/tbUser/")的name的值改成10086服务提供者的 spring.application.name的值即可
2、jsonp
由于同源策略,浏览器禁止向不同源的服务器发起请求,但是 HTML 的<script> 元素是一个例外,我们可以利用标签的src发起请求,服务提供者的后端响应消费者期望的数据格式(必须是符合js对象的格式,否则会在回调函数解析的时候报错)
我们先重写10086服务提供者的get接口,使其返回值符合消费者期待的格式
- @RestController
- @RequestMapping("/tbUser/")
- public class TbUserController extends CommonController<TbUserVo, TbUser, Integer> {
- @Autowired
- private TbUserService tbUserService;
- @RequestMapping("get")
- public Object get(Integer id, String callback) {
- Result<TbUserVo> result = super.get(id);
- TbUserVo tbUserVo = result.getData();
- StringBuffer re = new StringBuffer();
- re.append("{");
- re.append("'flag':'" + result.isFlag() + "',");
- re.append("'msg':'" + result.getMsg() + "',");
- re.append("'data':{");
- re.append("'id':'" + tbUserVo.getId() + "',");
- re.append("'username':'" + tbUserVo.getUsername() + "',");
- re.append("'password':'" + tbUserVo.getPassword() + "',");
- re.append("'created':'" + tbUserVo.getCreated() + "',");
- re.append("'descriptionId':'" + tbUserVo.getDescriptionId()+"'");
- re.append("}}");
- return callback + "(" + re.toString() + ")";
- }
- }
修改10087服务消费者的test.html
- <!DOCTYPE html>
- <!--解决idea thymeleaf 表达式模板报红波浪线-->
- <!--suppress ALL -->
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>跨域测试</title>
- <!-- 引入静态资源 -->
- <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
- </head>
- <body>
- <h3>jsonp</h3>
- <button id="button">发送ajax请求</button>
- <br/>
- <textarea id="text" style="width: 380px; height: 380px;">
- </textarea>
- </body>
- <script th:inline="javascript">
- ctx = [[${#request.getContextPath()}]];//应用路径
- //绑定按钮点击事件
- $("body").on("click","#button",function(e){
- //构造一个<srcipt>标签
- $("body").append("<script id='temporaryScript' src=\"http://localhost:10086/tbUser/get?id=1&callback=callback\"><\/script>");
- })
- //回调
- function callback(data) {
- $("#text").text("");
- $("#text").text(JSON.stringify(data));
- //过河拆桥
- $("#temporaryScript").remove();
- }
- </script>
- </html>
3、cors
详情介绍可以看这里:CORS通信 ,代码实现过程参考:Java实现CORS跨域请求
参考大佬博客,我们在10086服务提供者新建一个CorsFilter过滤器,
- @Component
- @ServletComponentScan
- @WebFilter(filterName = "corsFilter", //过滤器名称
- urlPatterns = "/tbUser/*",//拦截路径
- initParams = {@WebInitParam(name = "allowOrigin", value = "http://localhost:10087"),//允许来源
- @WebInitParam(name = "allowMethods", value = "GET,POST,PUT,DELETE,OPTIONS"),//允许请求方法
- @WebInitParam(name = "allowCredentials", value = "true"),
- @WebInitParam(name = "allowHeaders", value = "Content-Type,X-Token")})
- public class CorsFilter implements Filter {
- private String allowOrigin;
- private String allowMethods;
- private String allowCredentials;
- private String allowHeaders;
- private String exposeHeaders;
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- allowOrigin = filterConfig.getInitParameter("allowOrigin");
- allowMethods = filterConfig.getInitParameter("allowMethods");
- allowCredentials = filterConfig.getInitParameter("allowCredentials");
- allowHeaders = filterConfig.getInitParameter("allowHeaders");
- exposeHeaders = filterConfig.getInitParameter("exposeHeaders");
- }
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
- if (!StringUtils.isEmpty(allowOrigin)) {
- if(allowOrigin.equals("*")){
- response.setHeader("Access-Control-Allow-Origin", allowOrigin);
- }else{
- List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
- if (allowOriginList != null && allowOriginList.size() > 0) {
- String currentOrigin = request.getHeader("Origin");
- if (allowOriginList.contains(currentOrigin)) {
- response.setHeader("Access-Control-Allow-Origin", currentOrigin);
- }
- }
- }
- }
- if (!StringUtils.isEmpty(allowMethods)) {
- response.setHeader("Access-Control-Allow-Methods", allowMethods);
- }
- if (!StringUtils.isEmpty(allowCredentials)) {
- response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
- }
- if (!StringUtils.isEmpty(allowHeaders)) {
- response.setHeader("Access-Control-Allow-Headers", allowHeaders);
- }
- if (!StringUtils.isEmpty(exposeHeaders)) {
- response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
- }
- filterChain.doFilter(servletRequest, servletResponse);
- }
- @Override
- public void destroy() {
- }
- }
然后修改10087服务消费者调用方式,改成普通的ajax请求即可
- <!DOCTYPE html>
- <!--解决idea thymeleaf 表达式模板报红波浪线-->
- <!--suppress ALL -->
- <html xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="UTF-8">
- <title>跨域测试</title>
- <!-- 引入静态资源 -->
- <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
- </head>
- <body>
- <h3>cors</h3>
- <button id="button">发送ajax请求</button>
- <br/>
- <textarea id="text" style="width: 380px; height: 380px;">
- </textarea>
- </body>
- <script th:inline="javascript">
- ctx = [[${#request.getContextPath()}]];//应用路径
- //绑定按钮点击事件
- $("body").on("click","#button",function(e){
- //发送ajax请求
- $.get("http://localhost:10086/tbUser/get/1",function (data) {
- $("#text").text("");
- $("#text").text(JSON.stringify(data));
- })
- })
- </script>
- </html>
在启动10086服务提供者时出现一个启动报错,说corsFilter已经存在,叫我们改名或者启用覆盖,我们启用一下覆盖 PS:最好是重命名我们的bean
启动成功后我们进行测试
测试下list请求,修改这一部分
- //发送ajax请求
- $.post("http://localhost:10086/tbUser/list",{username:"张三"},function (data) {
- $("#text").text("");
- $("#text").text(JSON.stringify(data));
- })
没有问题!
以上是CORS是基于Filter过滤器的实现,事实上,springboot通过@CrossOrigin注解优雅的实现CORS跨域,我们在10086服务提供者的controller层那里加入@CrossOrigin注解:
- @RestController
- @RequestMapping("/tbUser/")
- @CrossOrigin(origins = "http://localhost:10087", methods = "GET,POST,PUT,DELETE,OPTIONS", allowedHeaders = "Content-Type,X-Token",allowCredentials = "true")
- public class TbUserController extends CommonController<TbUserVo, TbUser, Integer> {
- @Autowired
- private TbUserService tbUserService;
- }
@CrossOrigin注解,官方文档:https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
1、Spring 4.2之后提供了跨域注解 @CrossOrigin;
2、可以用在方法或Controller上;
3、Controller和方法上都有时,Spring会合并两个注解的属性一起使用;
注解属性有以下7个:
- String[] value() default {}
- String[] origins() default {} //允许来源
- String[] allowedHeaders() default {}
- String[] exposedHeaders() default {}
- RequestMethod[] methods() default {} //允许调用方法
- String allowCredentials() default {}
- long maxAge() default -1L
如果是SpringBoot项目,还有更简洁的配置,我们看一下官网介绍:CORS Support
- management.endpoints.web.cors.allowed-origins=http://localhost:10087
management.endpoints.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
后记
跨域暂时先记录到这里,如果大家发现有什么错误,还望指正
还在问跨域?本文记录js跨域的多种实现实例的更多相关文章
- [记录]js跨域调用mvc ActionResult扩展
背景 最近2个项目中都用到了js跨域访问的知识,2个项目都需要主站与各个分站之间进行数据交互.状态同步等相关操作.浏览器本身是不允许进行跨域访问,在MVC中我们可以扩展一个方法来实现这个功能.在此大家 ...
- js获取IP地址多种方法实例教程
js获取IP地址方法总结 js代码获取IP地址的方法,如何在js中取得客户端的IP地址.原文地址:js获取IP地址的三种方法 http://www.jbxue.com/article/11338. ...
- 利用JS跨域做一个简单的页面訪问统计系统
事实上在大部分互联网web产品中,我们一般会用百度统计或者谷歌统计分析系统,通过在程序中引入特定的JS脚本,然后便能够在这些统计系统中看到自己站点页面详细的訪问情况.可是有些时候,因为一些特殊情况,我 ...
- 如何解决js跨域问题
Js跨域问题是web开发人员最常碰到的一个问题之一.所谓js跨域问题,是指在一个域下的页面中通过js访问另一个不同域下的数据对象,出于安全性考 虑,几乎所有浏览器都不允许这种跨域访问,这就导致在一些a ...
- 5种处理js跨域问题方法汇总(转载)
1.JSONP跨域GET请求 ajax请求,dataType为jsonp.这种形式需要请求在服务端调整为返回callback([json-object])的形式.如果服务端返回的是普通json对象.那 ...
- 利用JS跨域做一个简单的页面访问统计系统
其实在大部分互联网web产品中,我们通常会用百度统计或者谷歌统计分析系统,通过在程序中引入特定的JS脚本,然后便可以在这些统计系统中看到自己网站页面具体的访问情况.但是有些时候,由于一些特殊情况,我们 ...
- 解决js跨域问题
如何解决js跨域问题 Js跨域问题是web开发人员最常碰到的一个问题之一.所谓js跨域问题,是指在一个域下的页面中通过js访问另一个不同域下 的数据对象,出于安全性考 虑,几乎所有浏览器都不允许这种跨 ...
- js 跨域问题 汇总
前言 相信每一个前端er对于跨域这两个字都不会陌生,在实际项目中应用也是比较多的.但跨域方法的多种多样实在让人目不暇接.老规矩,碰到这种情况,就只能自己总结一篇博客,作为记录. 正文 1. 什么是跨域 ...
- js跨域解决方式
什么是跨域? 概念:仅仅要协议.域名.port有不论什么一个不同,都被当作是不同的域.(所谓同源是指,域名.协议,port同样.),对于port和协议的不同,仅仅能通过后台来解决. URL 说明 是否 ...
随机推荐
- LCA 各种神奇的LCA优化方法
LCA(Least Common Ancestors) 树上问题的一种. 朴素lca很简单啦,我就不多说了,时间复杂度n^2 1.倍增LCA 时间复杂度 nlongn+klogn 其实是一种基于朴素l ...
- Django中用户权限模块
Django中用户权限模块 1 auth模块 auth模块是Django提供的标准权限管理系统,可以提供用户身份认证, 用户组和权限管理. auth可以和admin模块配合使用, 快速建立网站的管理系 ...
- ArcGIS API for JavaScript 入门教程[2] 授人以渔
这篇仍然不讲怎么做,但是我要告诉你如何获取资源. 目录:https://www.cnblogs.com/onsummer/p/9080204.html 转载注明出处,博客园/CSDN/B站:秋意正寒. ...
- Tomcat启动失败的几种解决办法
1.重复映射 用Eclipse开发,新建了的servlet会有一个url-pattern声明: 这样就不需要在web.xml中添加映射,如果在web.xml中添加了这样一段: <servlet& ...
- python——报错ImportError:DLL load failed with error code -1073741795的解决方式
python中导入一个包,import cv2总是报错'ImportError:DLL load failed with error code -1073741795',报错形式: 网上找了好久的解决 ...
- 深入理解数据库磁盘存储(Disk Storage)
数据库管理系统将数据存储在磁盘.磁带以及其他的裸设备上,虽然这些设备的访问速度相比内存慢很多,但其非易失性和大容量的特点使他们成为数据存储的不二之选. 本文主要讨论大型数据库产品的磁盘存储内部结构,这 ...
- 关于socket.io的使用
这段时间学习了socket.io,用它写了小项目,在此总结下它的基本使用方式和一些要点. socket.io是基于Node.js和WebSocket协议的实时通信开源框架,它包括客户端的JavaScr ...
- ASP.Net Core Razor+AdminLTE 小试牛刀
AdminLTE 一个基于 bootstrap 的轻量级后台模板,这个前端界面个人感觉很清爽,对于一个大后端的我来说,可以减少较多的时间去承担前端的工作但又必须去独立去完成一个后台系统开发的任务,并且 ...
- Vue 进阶之路(三)
之前的文章我们已经对 vue 有了初步认识,这篇文章我们通过一个例子说一下 vue 的方法 methods,计算属性 computed 和监听器 watch. 现在我们有一个需求,变量 firstNa ...
- 【JVM虚拟机】(9)-- JVM是如何处理异常的
[JVM虚拟机](9)-- JVM是如何处理异常的 上篇博客我们简单说过异常信息是存放在属性表集合中的Code属性表里,那么这篇博客就单独讲Code属性表中的exception_table. 在讲之前 ...