Implement JSONP cross – domain communication
Implement cross – domain communication scheme based on JSONP
The principle of
Browsers have restrictions on non-homologous Ajax requests and do not allow cross-domain requests to be sent. There are two cross-domain solutions
- Cros configuration
- The json request
Cros is a new specification, through a head request to ask the server whether to allow cross-domain, if not, will be intercepted jSONP to take advantage of the browser does not limit the homology of JS scripts, through the dynamic creation of script request, the server passes back a JS function call syntax, the browser side according to the JS function to call the callback function normally
Implementation approach
First determine how the server should return the data
For a correct JSONP request, the server should return data in the following format
jQuery39948237({key:3})
Copy the code
JQuery39948237 is the name of the function to be executed on the browser side, which is dynamically created by the Ajax library and sent as a request parameter along with the rest of the request parameters without much processing on the server side
{key:3} is the data returned by this request and is passed as a function parameter
Second, what about the server side?
To be compatible with the JSONP and CROS schemes, the server should return the function call if the request has a function name parameter, otherwise it should return JSON data normally
Finally, to reduce code intrusion, the above process should not be put into a Controller normal logic, and aop implementation should be considered
implementation
The front end
The front-end uses jquery library ~~(originally intended to use axios library, but axios does not support JSONP)~~
The following code
$.ajax({
url:'http://localhost:8999/boot/dto'.dataType:"jsonp".success:(response) = >{
this.messages.push(response); }})Copy the code
Jquery default jSONp function name parameter name is callback
The back-end
The use of AOP implementation
Add a post-cut point to the Controller and check whether the request has a function name parameter. If so, modify the returned data. If not, no processing is performed
Aop, however, has two options
- Regular AOP, define your own pointcuts
ResponseBodyAdvice
, Spring provides utility classes that can be used directly for data return
The second option is used this time
The first is the implementation of the Controller interface
@RequestMapping("dto")
public Position dto(a) {
return new Position(239.43);
}
Copy the code
Return a complex type to which Spring automatically serializes json
Then the ResponseBodyAdvice is implemented
The full path is: org. Springframework. Web. Servlet. MVC) method. The annotation. ResponseBodyAdvice
/** * handles controller return values, jSONP format for callback values, and */ is not handled for others
@RestControllerAdvice(basePackageClasses = IndexController.class)
public class JsonpAdvice implements ResponseBodyAdvice {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper mapper;
//jquery defaults to callback, other JSONp libraries may differ
private final String callBackKey = "callback";
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
logger.debug("Returned class={}", aClass);
return true;
}
/** * if the value is not a String, it will be serialized by Json, so that double quotes are added@paramBody returns the value *@paramMethodParameter methodParameter *@paramMediaType Current contentType, non-string json *@paramAClass convert class *@paramServerHttpRequest Request, ServletServerHttpRequest is currently supported. The rest types will return * as is@param serverHttpResponse response
* @returnIf body is a String, it is returned after the method header. If body is of any other type, it is returned * after serialization@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;
// If media is plain, it will be serialized by JSON. If a plain String is returned, it will still be serialized by JSON, with extra double quotes
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 is used
if (body instanceof String) {
return callback + "(\" " + body + "\")";
} else {
try {
String res = mapper.writeValueAsString(body);
logger.debug("Converted return value ={},{}", res, callback + "(" + res + ")");
return callback + "(" + res + ")";
} catch (JsonProcessingException e) {
logger.warn([JSONP support] failed to serialize data body, e);
returnbody; }}}}else {
logger.warn("[jSONP support] request class ={}", serverHttpRequest.getClass());
}
returnbody; }}Copy the code
Specify the pointcut using @restControllerAdvice
bug
After this step, the JSONP call is theoretically ready to be implemented.
However, the actual test found that due to Spring’s JSON serialization strategy, if a JSONP string is returned, the JSON serialization will be enclosed with a pair of quotes, as follows
Should be returned
Jquery332({"x":239."y":43})
Copy the code
The actual return
"Jquery332({\"x\":239,\"y\":43})"
Copy the code
The browser cannot run functions properly
After searching for information in many ways
Because the actual return type is changed to String in ResponseBodyAdvice, the String type is quoted after Jackson serializes it
The solution is to modify the default JSON serialization MessageConverter processing logic to disregard the actual String
The following code
@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) {
// Bypass the actual returned String type, not serialized
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
> converters)
> {
// MappingJackson2HttpMessageConverter converter = mappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(new LinkedList<MediaType>() {{
add(MediaType.TEXT_HTML);
add(MediaType.APPLICATION_JSON_UTF8);
}});
converters.add(newStringHttpMessageConverter(StandardCharsets.UTF_8)); converters.add(converter); }}Copy the code
todo
Why do you need two classes to work together
code
See Github for details