SpringBoot实现jsonp跨域通信的方法示例

实现jsonp跨域通信

实现基于jsonp的跨域通信方案

原理

浏览器对非同源ajax请求有限制,不允许发送跨域请求

目前跨域解决方案有两种

  • cros配置
  • jsonp请求

cros为新规范,通过一个head请求询问服务器是否允许跨域,若不允许则被拦截

jsonp则为利用浏览器不限制js脚本的同源性,通过动态创建script请求,服务器传递回一个js函数调用语法,浏览器端按照js函数正常调用回调函数

实现思路

首先确定服务器端应该如何返回数据

一次正确的jsonp请求,服务器端应该返回如下格式数据

jQuery39948237({key:3})

其中, jQuery39948237 为浏览器端要执行的函数名,该函数由ajax库动态创建,并将函数名作为一个请求参数和该次请求的其余参数一并发送,服务器端无需对此参数做过多处理

{key:3} 为此次请求返回的数据,作为函数参数传递

其次,服务器端如何处理?

为了兼容jsonp和cros方案,服务器端应该在请求带有函数名参数时返回函数调用,否则正常返回json数据即可

最后,为了减少代码的侵入,不应该将上述流程放入一个Controller正常逻辑中,应该考虑使用aop实现

实现

前端

前端本次使用jquery库~~(本来想用axios库的,但是axios不支持jsonp)~~

代码如下

$.ajax({
    url:'http://localhost:8999/boot/dto',
    dataType:"jsonp",
    success:(response)=>{
      this.messages.push(response);
    }
  })

Jquery默认jsonp函数名参数name为 callback

后端

本次采用aop实现

具体思路为: 给Controller添加后切点,判断request是否有函数名参数,如果有则修改返回的数据,没有则不做处理

而aop又有两种方案

  1. 常规aop,自己定义切点
  2. ResponseBodyAdvice,Spring提供的可直接用于数据返回的工具类

本次使用第二种方案

首先是Controller的接口实现

@RequestMapping("dto")
public Position dto() {
  return new Position(239, 43);
}

返回一个复杂类型,Spring会自动对其做json序列化操作

然后的 ResponseBodyAdvice 实现

该类全路径为: org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice

/**
 * 处理controller返回值,对于有callback值的使用jsonp格式,其余不处理
 */
@RestControllerAdvice(basePackageClasses = IndexController.class)
public class JsonpAdvice implements ResponseBodyAdvice {

  private Logger logger = LoggerFactory.getLogger(getClass());
  @Autowired
  private ObjectMapper mapper;

  //jquery默认是callback,其余jsonp库可能不一样
  private final String callBackKey = "callback";

  @Override
  public boolean supports(MethodParameter methodParameter, Class aClass) {
    logger.debug("返回的class={}", aClass);
    return true;
  }

  /**
   * 在此处对返回值进行处理,需要特别注意如果是非String类型,会被Json序列化,从而添加了双引号,解决办法见
   *
   * @param body        返回值
   * @param methodParameter  方法参数
   * @param mediaType     当前contentType,非String类型为json
   * @param aClass       convert的class
   * @param serverHttpRequest request,暂时支持是ServletServerHttpRequest类型,其余类型将会原样返回
   * @param serverHttpResponse response
   * @return 如果body是String类型,加上方法头后返回,如果是其他类型,序列化后返回
   * @see com.inkbox.boot.demo.converter.Jackson2HttpMessageConverter
   */
  @Override
  public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {

    if (body == null)
      return null;
    // 如果返回String类型,media是plain,否则是json,将会经过json序列化,在下方返回纯字符串之后依然会被序列化,就会添上多余的双引号
    logger.debug("body={},request={},response={},media={}", body, serverHttpRequest, serverHttpResponse, mediaType.getSubtype());

    if (serverHttpRequest instanceof ServletServerHttpRequest) {
      HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();

      String callback = request.getParameter(callBackKey);

      if (!StringUtils.isEmpty(callback)) {
        //使用了jsonp
        if (body instanceof String) {
          return callback + "(\"" + body + "\")";
        } else {
          try {
            String res = mapper.writeValueAsString(body);
            logger.debug("转化后的返回值={},{}", res, callback + "(" + res + ")");

            return callback + "(" + res + ")";
          } catch (JsonProcessingException e) {
            logger.warn("【jsonp支持】数据body序列化失败", e);
            return body;
          }
        }
      }
    } else {
      logger.warn("【jsonp支持】不支持的request class ={}", serverHttpRequest.getClass());
    }
    return body;
  }
}

使用 @RestControllerAdvice 指明切点

bug

经过此步骤,理论上即可实现jsonp调用了。

然而实际测试发现,由于Spring json序列化策略的问题,如果返回jsonp字符串,json序列化之后,将会添上一对引号,如下

应该返回

Jquery332({"x":239,"y":43})

实际返回

"Jquery332({\"x\":239,\"y\":43})"

导致浏览器端无法正常运行函数

经多方查找资料后得知

由于在 ResponseBodyAdvice 中修改了实际的返回值类型为 String ,而字符串类型经过 Jackson 序列化后就会加上引号

解决办法为:修改默认的json序列化 MessageConverter 处理逻辑,对于实际是 String 的不做处理

代码如下

@Component
public class Jackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

  private Logger logger = LoggerFactory.getLogger(getClass());

  @Override
  protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    if (object instanceof String) {
      //绕开实际上返回的String类型,不序列化
      Charset charset = this.getDefaultCharset();
      StreamUtils.copy((String) object, charset, outputMessage.getBody());
    } else {
      super.writeInternal(object, type, outputMessage);
    }
  }
}

@Configuration
public class MvcConfig implements WebMvcConfigurer {

  private Logger logger = LoggerFactory.getLogger(getClass());

  @Autowired
  private MappingJackson2HttpMessageConverter converter;

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//    MappingJackson2HttpMessageConverter converter = mappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(new LinkedList<MediaType>() {{
      add(MediaType.TEXT_HTML);
      add(MediaType.APPLICATION_JSON_UTF8);
    }});
    converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
    converters.add(converter);
  }
}

todo

暂时不明白为什么需要两个类搭配使用

代码

具体实现可查阅github

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Springboot 实现跨域访问无需使用jsonp的实现代码

    Springboot 实现跨域访问 无需使用jsonp 在springboot的拦截器中添加respone的头信息即可 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //String origin = (String) request.getRemoteHost()+":"+re

  • springboot+jsonp解决前端跨域问题小结

    现在咱们一起来讨论浏览器跨域请求数据的相关问题.说这样可能不是很标准,因为拒绝跨域请求数据并不是浏览器所独有的,之所以会出现跨域请求不了数据,是因为浏览器基本都实现了一个叫"同源策略"的安全规范.该规范具体是什么呢?我们在MDN上找到了一份资料,地址如下: 浏览器同源策略讲解 总的来说,当A网址和B网址在 协议 . 端口 . 域名 方面存在不同时,浏览器就会启动同源策略,拒绝A.B服务器之间进行数据请求. 说了同源策略,纸上得来终觉浅,绝知此事要躬行,到底同源策略是怎么体现的呢?下面我

  • 详解SpringBoot多跨域请求的支持(JSONP)

    在我们做项目的过程中,有可能会遇到跨域请求,所以需要我们自己组装支持跨域请求的JSONP数据,而在4.1版本以后的SpringMVC中,为我们提供了一个AbstractJsonpResponseBodyAdvice的类用来支持jsonp的数据(SpringBoot接收解析web请求是依赖于SpringMVC实现的).下面我们就看一下怎么用AbstractJsonpResponseBodyAdvice来支持跨域请求. 使用AbstractJsonpResponseBodyAdvice来支持跨域请求

  • SpringBoot跨域Jsonp和Cors的方法

    有一次我的项目中采用了前后端分离的模式,引起了跨域问题,本文将介绍我所采用的跨域解决方法. 首先要了解产生跨域的本质,也就是同源策略的限制,源是指域名.端口号.协议,有一者不相同将被浏览器拒绝接受响应信息,(请求可以发送出去,但是浏览器不接受响应). 解决方法: 1. jsonp jsonp的原理的 src="" 属性不受同源策略的限制,动态创建一个callback回调函数,服务器调用回调函数把数据放进去,具体的细节打算以后做一个专门讲解. 这里给一个模版: $.ajax({ type

  • SpringBoot实现jsonp跨域通信的方法示例

    实现jsonp跨域通信 实现基于jsonp的跨域通信方案 原理 浏览器对非同源ajax请求有限制,不允许发送跨域请求 目前跨域解决方案有两种 cros配置 jsonp请求 cros为新规范,通过一个head请求询问服务器是否允许跨域,若不允许则被拦截 jsonp则为利用浏览器不限制js脚本的同源性,通过动态创建script请求,服务器传递回一个js函数调用语法,浏览器端按照js函数正常调用回调函数 实现思路 首先确定服务器端应该如何返回数据 一次正确的jsonp请求,服务器端应该返回如下格式数据

  • Ajax jsonp跨域请求实现方法

    什么是跨域? 简单的来说,出于安全方面的考虑,页面中的JavaScript无法访问其他服务器上的数据,即"同源策略".而跨域就是通过某些手段来绕过同源策略限制,实现不同服务器之间通信的效果. 具体策略限制情况可看下表: URL 说明 允许通信 http://www.a.com/a.js http://www.a.com/b.js 同一域名下 允许 http://www.a.com/lab/a.js http://www.a.com/script/b.js 同一域名下不同文件夹 允许 h

  • SpringBoot解决ajax跨域问题的方法

    SpringBoot解决ajax跨域,供大家参考,具体内容如下 一.第一种方式 1.编写一个支持跨域请求的 Configuration import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.anno

  • jquery下利用jsonp跨域访问实现方法

    复制代码 代码如下: $.ajax({ async:false, url: '', // 跨域URL type: 'GET', dataType: 'jsonp', jsonp: 'jsoncallback', //默认callback data: mydata, //请求数据 timeout: 5000, beforeSend: function(){ //jsonp 方式此方法不被触发.原因可能是dataType如果指定为jsonp的话,就已经不是ajax事件了 }, success: fu

  • Vue 使用postMessage 实现父子跨域通信

    目录 一.跨域通信 二.示例 三.拓展阅读 vue项目中postMessage的使用总结 postMessage简介 项目搭建 一.跨域通信 1.子向父通信parent.html // 页面销毁前,务必去除监听器,否则会造成资源泄露! beforeDestory () { window.removeEventListener('message', this.listenerFun) } mounted() { window.addEventListener('message',this.list

  • 常见的javascript跨域通信方法

    本文主要介绍几种常见的javascript跨域通信方法.首先讲解一下JSONP. 1.JSONP JSONP(JSON with Padding)是JSON的一种"使用模式",可用于解决主流浏览器的跨域数据访问的问题.由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外.利用 <script> 元素的这个开放策略,网页可以得到从其

  • Springboot处理CORS跨域请求的三种方法

    前言 Springboot跨域问题,是当前主流web开发人员都绕不开的难题.但我们首先要明确以下几点 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了. 之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议.域名.端口号都完全一致. 浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的H

  • jsonp跨域及实现百度首页联想功能的方法

    什么是跨域呢? 比如说ajax必须在自己的域(名)之下,才能进行异步的请求,如果不是在同一个域之下就不能进行请求,会报错.比如说我们用ajax去请求腾讯的API如果我们的这个域不在腾讯那个接口的'白名单'里面,腾讯的接口就会拒绝给我返回数据.但是大多数情况下,我们不可能每次都去去相应的网站申请一个'白名单',这个时候我们就要用到跨域这种方法,跨域有很多方式比如说用iframe但是这种的可操作性太差了,不能操作frame里面的DOM元素.当先最流行的一种的方式是使用jsonp这种方式. 什么是JS

  • jsonp跨域获取百度联想词的方法分析

    本文实例讲述了jsonp跨域获取百度联想词的方法.分享给大家供大家参考,具体如下: jsonp原理: 1.Web页面上用<script> 引入 js文件时则不受是否跨域的影响 (不仅如此,我们还发现凡是拥有"src"这个属性的标签都拥有跨域的能力,比如<script>.<img>.<iframe>) 2.于是我们把数据放到服务器上,并且数据为json形式(因为js可以轻松处理json数据) 3.因为我们无法监控通过<script&g

  • Vue项目中使用jsonp抓取跨域数据的方法

    下载jsonp npm install jsonp 在js文件夹下新增一个jsonp.js,来封装一个jsonp() 如何封装一个jsonp() 在下载的jsopn中,jsonp(url,options,callback)这个是原生jsonp方法中的参数: 引入下载的jsonp import originJsonp from 'jsonp': 导出自己定义的jsonp函数 //这个jsonp函数是我们自己定义的,与上面的originJsonp不是同一个,originJsonp是一个可以直接引用的

随机推荐