實現基於jsonp的跨域通訊方案javascript
瀏覽器對非同源ajax請求有限制,不容許發送跨域請求
目前跨域解決方案有兩種前端
- cros配置
- jsonp請求
cros爲新規範,經過一個head請求詢問服務器是否容許跨域,若不容許則被攔截
jsonp則爲利用瀏覽器不限制js腳本的同源性,經過動態建立script請求,服務器傳遞迴一個js函數調用語法,瀏覽器端按照js函數正常調用回調函數java
首先肯定服務器端應該如何返回數據jquery
一次正確的jsonp請求,服務器端應該返回以下格式數據ios
jQuery39948237({key:3})
複製代碼
其中,jQuery39948237
爲瀏覽器端要執行的函數名,該函數由ajax庫動態建立,並將函數名做爲一個請求參數和該次請求的其他參數一併發送,服務器端無需對此參數作過多處理git
{key:3}
爲這次請求返回的數據,做爲函數參數傳遞github
其次,服務器端如何處理?web
爲了兼容jsonp和cros方案,服務器端應該在請求帶有函數名參數時返回函數調用,不然正常返回json數據便可ajax
最後,爲了減小代碼的侵入,不該該將上述流程放入一個Controller正常邏輯中,應該考慮使用aop實現spring
前端本次使用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又有兩種方案
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
指明切點
通過此步驟,理論上便可實現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);
}
}
複製代碼
暫時不明白爲何須要兩個類搭配使用
具體實現可查閱github