Remove JSONP support
CORS is now widely supported and should be used instead for cross-domain requests. Issue: SPR-16914
This commit is contained in:
parent
19dc981685
commit
ac37b678a3
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -95,21 +95,6 @@ public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMes
|
||||||
if (this.jsonPrefix != null) {
|
if (this.jsonPrefix != null) {
|
||||||
generator.writeRaw(this.jsonPrefix);
|
generator.writeRaw(this.jsonPrefix);
|
||||||
}
|
}
|
||||||
String jsonpFunction =
|
|
||||||
(object instanceof MappingJacksonValue ? ((MappingJacksonValue) object).getJsonpFunction() : null);
|
|
||||||
if (jsonpFunction != null) {
|
|
||||||
generator.writeRaw("/**/");
|
|
||||||
generator.writeRaw(jsonpFunction + "(");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void writeSuffix(JsonGenerator generator, Object object) throws IOException {
|
|
||||||
String jsonpFunction =
|
|
||||||
(object instanceof MappingJacksonValue ? ((MappingJacksonValue) object).getJsonpFunction() : null);
|
|
||||||
if (jsonpFunction != null) {
|
|
||||||
generator.writeRaw(");");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -45,9 +45,6 @@ public class MappingJacksonValue {
|
||||||
@Nullable
|
@Nullable
|
||||||
private FilterProvider filters;
|
private FilterProvider filters;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String jsonpFunction;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance wrapping the given POJO to be serialized.
|
* Create a new instance wrapping the given POJO to be serialized.
|
||||||
|
|
@ -113,19 +110,4 @@ public class MappingJacksonValue {
|
||||||
return this.filters;
|
return this.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the name of the JSONP function name.
|
|
||||||
*/
|
|
||||||
public void setJsonpFunction(@Nullable String functionName) {
|
|
||||||
this.jsonpFunction = functionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the configured JSONP function name.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public String getJsonpFunction() {
|
|
||||||
return this.jsonpFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -394,39 +394,6 @@ public class MappingJackson2HttpMessageConverterTests {
|
||||||
assertThat(result, not(containsString("\"property2\":\"value\"")));
|
assertThat(result, not(containsString("\"property2\":\"value\"")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void jsonp() throws Exception {
|
|
||||||
MappingJacksonValue jacksonValue = new MappingJacksonValue("foo");
|
|
||||||
jacksonValue.setSerializationView(MyJacksonView1.class);
|
|
||||||
jacksonValue.setJsonpFunction("callback");
|
|
||||||
|
|
||||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
|
||||||
this.converter.writeInternal(jacksonValue, null, outputMessage);
|
|
||||||
|
|
||||||
assertEquals("/**/callback(\"foo\");", outputMessage.getBodyAsString(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void jsonpAndJsonView() throws Exception {
|
|
||||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
|
||||||
JacksonViewBean bean = new JacksonViewBean();
|
|
||||||
bean.setWithView1("with");
|
|
||||||
bean.setWithView2("with");
|
|
||||||
bean.setWithoutView("without");
|
|
||||||
|
|
||||||
MappingJacksonValue jacksonValue = new MappingJacksonValue(bean);
|
|
||||||
jacksonValue.setSerializationView(MyJacksonView1.class);
|
|
||||||
jacksonValue.setJsonpFunction("callback");
|
|
||||||
this.converter.writeInternal(jacksonValue, null, outputMessage);
|
|
||||||
|
|
||||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
|
||||||
assertThat(result, startsWith("/**/callback("));
|
|
||||||
assertThat(result, endsWith(");"));
|
|
||||||
assertThat(result, containsString("\"withView1\":\"with\""));
|
|
||||||
assertThat(result, not(containsString("\"withView2\":\"with\"")));
|
|
||||||
assertThat(result, not(containsString("\"withoutView\":\"without\"")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test // SPR-13318
|
@Test // SPR-13318
|
||||||
public void writeSubType() throws Exception {
|
public void writeSubType() throws Exception {
|
||||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2015 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.web.servlet.mvc.method.annotation;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
|
||||||
import org.springframework.http.server.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.ServerHttpResponse;
|
|
||||||
import org.springframework.http.server.ServletServerHttpRequest;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.ObjectUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenient base class for a {@code ResponseBodyAdvice} to instruct the
|
|
||||||
* {@link org.springframework.http.converter.json.MappingJackson2HttpMessageConverter}
|
|
||||||
* to serialize with JSONP formatting.
|
|
||||||
*
|
|
||||||
* <p>Sub-classes must specify the query parameter name(s) to check for the name
|
|
||||||
* of the JSONP callback function.
|
|
||||||
*
|
|
||||||
* <p>Sub-classes are likely to be annotated with the {@code @ControllerAdvice}
|
|
||||||
* annotation and auto-detected or otherwise must be registered directly with the
|
|
||||||
* {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver}.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
* @since 4.1
|
|
||||||
*/
|
|
||||||
public abstract class AbstractJsonpResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pattern for validating jsonp callback parameter values.
|
|
||||||
*/
|
|
||||||
private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");
|
|
||||||
|
|
||||||
|
|
||||||
private final Log logger = LogFactory.getLog(getClass());
|
|
||||||
|
|
||||||
private final String[] jsonpQueryParamNames;
|
|
||||||
|
|
||||||
|
|
||||||
protected AbstractJsonpResponseBodyAdvice(String... queryParamNames) {
|
|
||||||
Assert.isTrue(!ObjectUtils.isEmpty(queryParamNames), "At least one query param name is required");
|
|
||||||
this.jsonpQueryParamNames = queryParamNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
|
|
||||||
MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
|
|
||||||
|
|
||||||
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
|
|
||||||
|
|
||||||
for (String name : this.jsonpQueryParamNames) {
|
|
||||||
String value = servletRequest.getParameter(name);
|
|
||||||
if (value != null) {
|
|
||||||
if (!isValidJsonpQueryParam(value)) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Ignoring invalid jsonp parameter value: " + value);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
MediaType contentTypeToUse = getContentType(contentType, request, response);
|
|
||||||
response.getHeaders().setContentType(contentTypeToUse);
|
|
||||||
bodyContainer.setJsonpFunction(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the jsonp query parameter value. The default implementation
|
|
||||||
* returns true if it consists of digits, letters, or "_" and ".".
|
|
||||||
* Invalid parameter values are ignored.
|
|
||||||
* @param value the query param value, never {@code null}
|
|
||||||
* @since 4.1.8
|
|
||||||
*/
|
|
||||||
protected boolean isValidJsonpQueryParam(String value) {
|
|
||||||
return CALLBACK_PARAM_PATTERN.matcher(value).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the content type to set the response to.
|
|
||||||
* This implementation always returns "application/javascript".
|
|
||||||
* @param contentType the content type selected through content negotiation
|
|
||||||
* @param request the current request
|
|
||||||
* @param response the current response
|
|
||||||
* @return the content type to set the response to
|
|
||||||
*/
|
|
||||||
protected MediaType getContentType(MediaType contentType, ServerHttpRequest request, ServerHttpResponse response) {
|
|
||||||
return new MediaType("application", "javascript");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -17,15 +17,10 @@
|
||||||
package org.springframework.web.servlet.view.json;
|
package org.springframework.web.servlet.view.json;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonView;
|
import com.fasterxml.jackson.annotation.JsonView;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
|
@ -33,10 +28,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.ser.FilterProvider;
|
import com.fasterxml.jackson.databind.ser.FilterProvider;
|
||||||
|
|
||||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.servlet.View;
|
import org.springframework.web.servlet.View;
|
||||||
|
|
||||||
|
|
@ -67,17 +60,6 @@ public class MappingJackson2JsonView extends AbstractJackson2View {
|
||||||
*/
|
*/
|
||||||
public static final String DEFAULT_CONTENT_TYPE = "application/json";
|
public static final String DEFAULT_CONTENT_TYPE = "application/json";
|
||||||
|
|
||||||
/**
|
|
||||||
* Default content type for JSONP: "application/javascript".
|
|
||||||
*/
|
|
||||||
public static final String DEFAULT_JSONP_CONTENT_TYPE = "application/javascript";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pattern for validating jsonp callback parameter values.
|
|
||||||
*/
|
|
||||||
private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");
|
|
||||||
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String jsonPrefix;
|
private String jsonPrefix;
|
||||||
|
|
||||||
|
|
@ -86,9 +68,6 @@ public class MappingJackson2JsonView extends AbstractJackson2View {
|
||||||
|
|
||||||
private boolean extractValueFromSingleKeyModel = false;
|
private boolean extractValueFromSingleKeyModel = false;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private Set<String> jsonpParameterNames = new LinkedHashSet<>(Arrays.asList("jsonp", "callback"));
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new {@code MappingJackson2JsonView} using default configuration
|
* Construct a new {@code MappingJackson2JsonView} using default configuration
|
||||||
|
|
@ -166,49 +145,6 @@ public class MappingJackson2JsonView extends AbstractJackson2View {
|
||||||
this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel;
|
this.extractValueFromSingleKeyModel = extractValueFromSingleKeyModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set JSONP request parameter names. Each time a request has one of those
|
|
||||||
* parameters, the resulting JSON will be wrapped into a function named as
|
|
||||||
* specified by the JSONP request parameter value.
|
|
||||||
* <p>The parameter names configured by default are "jsonp" and "callback".
|
|
||||||
* @since 4.1
|
|
||||||
* @see <a href="http://en.wikipedia.org/wiki/JSONP">JSONP Wikipedia article</a>
|
|
||||||
*/
|
|
||||||
public void setJsonpParameterNames(Set<String> jsonpParameterNames) {
|
|
||||||
this.jsonpParameterNames = jsonpParameterNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String getJsonpParameterValue(HttpServletRequest request) {
|
|
||||||
if (this.jsonpParameterNames != null) {
|
|
||||||
for (String name : this.jsonpParameterNames) {
|
|
||||||
String value = request.getParameter(name);
|
|
||||||
if (StringUtils.isEmpty(value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!isValidJsonpQueryParam(value)) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Ignoring invalid jsonp parameter value: " + value);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the jsonp query parameter value. The default implementation
|
|
||||||
* returns true if it consists of digits, letters, or "_" and ".".
|
|
||||||
* Invalid parameter values are ignored.
|
|
||||||
* @param value the query param value, never {@code null}
|
|
||||||
* @since 4.1.8
|
|
||||||
*/
|
|
||||||
protected boolean isValidJsonpQueryParam(String value) {
|
|
||||||
return CALLBACK_PARAM_PATTERN.matcher(value).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter out undesired attributes from the given model.
|
* Filter out undesired attributes from the given model.
|
||||||
* The return value can be either another {@link Map} or a single value object.
|
* The return value can be either another {@link Map} or a single value object.
|
||||||
|
|
@ -231,58 +167,11 @@ public class MappingJackson2JsonView extends AbstractJackson2View {
|
||||||
return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
|
return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object filterAndWrapModel(Map<String, Object> model, HttpServletRequest request) {
|
|
||||||
Object value = super.filterAndWrapModel(model, request);
|
|
||||||
String jsonpParameterValue = getJsonpParameterValue(request);
|
|
||||||
if (jsonpParameterValue != null) {
|
|
||||||
if (value instanceof MappingJacksonValue) {
|
|
||||||
((MappingJacksonValue) value).setJsonpFunction(jsonpParameterValue);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
MappingJacksonValue container = new MappingJacksonValue(value);
|
|
||||||
container.setJsonpFunction(jsonpParameterValue);
|
|
||||||
value = container;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
|
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
|
||||||
if (this.jsonPrefix != null) {
|
if (this.jsonPrefix != null) {
|
||||||
generator.writeRaw(this.jsonPrefix);
|
generator.writeRaw(this.jsonPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
String jsonpFunction = null;
|
|
||||||
if (object instanceof MappingJacksonValue) {
|
|
||||||
jsonpFunction = ((MappingJacksonValue) object).getJsonpFunction();
|
|
||||||
}
|
|
||||||
if (jsonpFunction != null) {
|
|
||||||
generator.writeRaw("/**/");
|
|
||||||
generator.writeRaw(jsonpFunction + "(");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void writeSuffix(JsonGenerator generator, Object object) throws IOException {
|
|
||||||
String jsonpFunction = null;
|
|
||||||
if (object instanceof MappingJacksonValue) {
|
|
||||||
jsonpFunction = ((MappingJacksonValue) object).getJsonpFunction();
|
|
||||||
}
|
|
||||||
if (jsonpFunction != null) {
|
|
||||||
generator.writeRaw(");");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setResponseContentType(HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
if (getJsonpParameterValue(request) != null) {
|
|
||||||
response.setContentType(DEFAULT_JSONP_CONTENT_TYPE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
super.setResponseContentType(request, response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -255,25 +255,6 @@ public class RequestMappingHandlerAdapterTests {
|
||||||
assertEquals("{\"status\":400,\"message\":\"body\"}", this.response.getContentAsString());
|
assertEquals("{\"status\":400,\"message\":\"body\"}", this.response.getContentAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void jsonpResponseBodyAdvice() throws Exception {
|
|
||||||
|
|
||||||
List<HttpMessageConverter<?>> converters = new ArrayList<>();
|
|
||||||
converters.add(new MappingJackson2HttpMessageConverter());
|
|
||||||
this.handlerAdapter.setMessageConverters(converters);
|
|
||||||
|
|
||||||
this.webAppContext.registerSingleton("jsonpAdvice", JsonpAdvice.class);
|
|
||||||
this.webAppContext.refresh();
|
|
||||||
|
|
||||||
testJsonp("callback", true);
|
|
||||||
testJsonp("_callback", true);
|
|
||||||
testJsonp("_Call.bAcK", true);
|
|
||||||
testJsonp("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.", true);
|
|
||||||
|
|
||||||
testJsonp("<script>", false);
|
|
||||||
testJsonp("!foo!bar", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
|
private HandlerMethod handlerMethod(Object handler, String methodName, Class<?>... paramTypes) throws Exception {
|
||||||
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
|
Method method = handler.getClass().getDeclaredMethod(methodName, paramTypes);
|
||||||
return new InvocableHandlerMethod(handler, method);
|
return new InvocableHandlerMethod(handler, method);
|
||||||
|
|
@ -399,12 +380,4 @@ public class RequestMappingHandlerAdapterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAdvice
|
|
||||||
private static class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
|
|
||||||
|
|
||||||
public JsonpAdvice() {
|
|
||||||
super("c");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -322,17 +322,6 @@ public class MappingJackson2JsonViewTests {
|
||||||
assertFalse(content.contains(FilterProvider.class.getName()));
|
assertFalse(content.contains(FilterProvider.class.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void renderWithJsonp() throws Exception {
|
|
||||||
testJsonp("jsonp", "callback", true);
|
|
||||||
testJsonp("jsonp", "_callback", true);
|
|
||||||
testJsonp("jsonp", "_Call.bAcK", true);
|
|
||||||
testJsonp("jsonp", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.", true);
|
|
||||||
|
|
||||||
testJsonp("jsonp", "<script>", false);
|
|
||||||
testJsonp("jsonp", "!foo!bar", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateResult() throws Exception {
|
private void validateResult() throws Exception {
|
||||||
String json = response.getContentAsString();
|
String json = response.getContentAsString();
|
||||||
DirectFieldAccessor viewAccessor = new DirectFieldAccessor(view);
|
DirectFieldAccessor viewAccessor = new DirectFieldAccessor(view);
|
||||||
|
|
@ -346,26 +335,6 @@ public class MappingJackson2JsonViewTests {
|
||||||
assertEquals("application/json", response.getContentType());
|
assertEquals("application/json", response.getContentType());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testJsonp(String paramName, String paramValue, boolean validValue) throws Exception {
|
|
||||||
Map<String, Object> model = new HashMap<>();
|
|
||||||
model.put("foo", "bar");
|
|
||||||
|
|
||||||
this.request = new MockHttpServletRequest();
|
|
||||||
this.request.addParameter("otherparam", "value");
|
|
||||||
this.request.addParameter(paramName, paramValue);
|
|
||||||
this.response = new MockHttpServletResponse();
|
|
||||||
|
|
||||||
this.view.render(model, this.request, this.response);
|
|
||||||
|
|
||||||
String content = this.response.getContentAsString();
|
|
||||||
if (validValue) {
|
|
||||||
assertEquals("/**/" + paramValue + "({\"foo\":\"bar\"});", content);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
assertEquals("{\"foo\":\"bar\"}", content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public interface MyJacksonView1 {
|
public interface MyJacksonView1 {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -48,7 +48,7 @@ public interface StompWebSocketEndpointRegistration {
|
||||||
* {@code Origin} header value.
|
* {@code Origin} header value.
|
||||||
*
|
*
|
||||||
* <p>When SockJS is enabled and origins are restricted, transport types that do not
|
* <p>When SockJS is enabled and origins are restricted, transport types that do not
|
||||||
* allow to check request origin (JSONP and Iframe based transports) are disabled.
|
* allow to check request origin (Iframe based transports) are disabled.
|
||||||
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
|
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
|
||||||
*
|
*
|
||||||
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
|
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -50,7 +50,7 @@ public interface WebSocketHandlerRegistration {
|
||||||
* {@code Origin} header value.
|
* {@code Origin} header value.
|
||||||
*
|
*
|
||||||
* <p>When SockJS is enabled and origins are restricted, transport types that do not
|
* <p>When SockJS is enabled and origins are restricted, transport types that do not
|
||||||
* allow to check request origin (JSONP and Iframe based transports) are disabled.
|
* allow to check request origin (Iframe based transports) are disabled.
|
||||||
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
|
* As a consequence, IE 6 to 9 are not supported when origins are restricted.
|
||||||
*
|
*
|
||||||
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
|
* <p>Each provided allowed origin must start by "http://", "https://" or be "*"
|
||||||
|
|
|
||||||
|
|
@ -298,9 +298,9 @@ public abstract class AbstractSockJsService implements SockJsService, CorsConfig
|
||||||
* designed for browsers. There is nothing preventing other types of client
|
* designed for browsers. There is nothing preventing other types of client
|
||||||
* to modify the {@code Origin} header value.
|
* to modify the {@code Origin} header value.
|
||||||
* <p>When SockJS is enabled and origins are restricted, transport types
|
* <p>When SockJS is enabled and origins are restricted, transport types
|
||||||
* that do not allow to check request origin (JSONP and Iframe based
|
* that do not allow to check request origin (Iframe based transports)
|
||||||
* transports) are disabled. As a consequence, IE 6 to 9 are not supported
|
* are disabled. As a consequence, IE 6 to 9 are not supported when origins
|
||||||
* when origins are restricted.
|
* are restricted.
|
||||||
* <p>Each provided allowed origin must have a scheme, and optionally a port
|
* <p>Each provided allowed origin must have a scheme, and optionally a port
|
||||||
* (e.g. "http://example.org", "http://example.org:9090"). An allowed origin
|
* (e.g. "http://example.org", "http://example.org:9090"). An allowed origin
|
||||||
* string may also be "*" in which case all origins are allowed.
|
* string may also be "*" in which case all origins are allowed.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -40,10 +40,6 @@ public enum TransportType {
|
||||||
|
|
||||||
XHR_SEND("xhr_send", HttpMethod.POST, "cors", "jsessionid", "no_cache"),
|
XHR_SEND("xhr_send", HttpMethod.POST, "cors", "jsessionid", "no_cache"),
|
||||||
|
|
||||||
JSONP("jsonp", HttpMethod.GET, "jsessionid", "no_cache"),
|
|
||||||
|
|
||||||
JSONP_SEND("jsonp_send", HttpMethod.POST, "jsessionid", "no_cache"),
|
|
||||||
|
|
||||||
XHR_STREAMING("xhr_streaming", HttpMethod.POST, "cors", "jsessionid", "no_cache"),
|
XHR_STREAMING("xhr_streaming", HttpMethod.POST, "cors", "jsessionid", "no_cache"),
|
||||||
|
|
||||||
EVENT_SOURCE("eventsource", HttpMethod.GET, "origin", "jsessionid", "no_cache"),
|
EVENT_SOURCE("eventsource", HttpMethod.GET, "origin", "jsessionid", "no_cache"),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -46,7 +46,7 @@ public abstract class AbstractHttpSendingTransportHandler extends AbstractTransp
|
||||||
implements SockJsSessionFactory {
|
implements SockJsSessionFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pattern for validating jsonp callback parameter values.
|
* Pattern for validating callback parameter values.
|
||||||
*/
|
*/
|
||||||
private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");
|
private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -84,8 +84,6 @@ public class DefaultSockJsService extends TransportHandlingSockJsService impleme
|
||||||
result.add(new XhrPollingTransportHandler());
|
result.add(new XhrPollingTransportHandler());
|
||||||
result.add(new XhrReceivingTransportHandler());
|
result.add(new XhrReceivingTransportHandler());
|
||||||
result.add(new XhrStreamingTransportHandler());
|
result.add(new XhrStreamingTransportHandler());
|
||||||
result.add(new JsonpPollingTransportHandler());
|
|
||||||
result.add(new JsonpReceivingTransportHandler());
|
|
||||||
result.add(new EventSourceTransportHandler());
|
result.add(new EventSourceTransportHandler());
|
||||||
result.add(new HtmlFileTransportHandler());
|
result.add(new HtmlFileTransportHandler());
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.web.socket.sockjs.transport.handler;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.server.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.ServerHttpResponse;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.socket.CloseStatus;
|
|
||||||
import org.springframework.web.socket.WebSocketHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.SockJsException;
|
|
||||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
|
||||||
import org.springframework.web.socket.sockjs.frame.DefaultSockJsFrameFormat;
|
|
||||||
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.SockJsSession;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.TransportType;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
|
|
||||||
import org.springframework.web.util.JavaScriptUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A TransportHandler that sends messages via JSONP polling.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
* @since 4.0
|
|
||||||
*/
|
|
||||||
public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportType getTransportType() {
|
|
||||||
return TransportType.JSONP;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected MediaType getContentType() {
|
|
||||||
return new MediaType("application", "javascript", StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkSessionType(SockJsSession session) {
|
|
||||||
return session instanceof PollingSockJsSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PollingSockJsSession createSession(
|
|
||||||
String sessionId, WebSocketHandler handler, Map<String, Object> attributes) {
|
|
||||||
|
|
||||||
return new PollingSockJsSession(sessionId, getServiceConfig(), handler, attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
|
|
||||||
AbstractHttpSockJsSession sockJsSession) throws SockJsException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
String callback = getCallbackParam(request);
|
|
||||||
if (!StringUtils.hasText(callback)) {
|
|
||||||
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
|
||||||
response.getBody().write("\"callback\" parameter required".getBytes(StandardCharsets.UTF_8));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable ex) {
|
|
||||||
sockJsSession.tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
|
|
||||||
throw new SockJsTransportFailureException("Failed to send error", sockJsSession.getId(), ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.handleRequestInternal(request, response, sockJsSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SockJsFrameFormat getFrameFormat(ServerHttpRequest request) {
|
|
||||||
// We already validated the parameter above...
|
|
||||||
String callback = getCallbackParam(request);
|
|
||||||
|
|
||||||
return new DefaultSockJsFrameFormat("/**/" + callback + "(\"%s\");\r\n") {
|
|
||||||
@Override
|
|
||||||
protected String preProcessContent(String content) {
|
|
||||||
return JavaScriptUtils.javaScriptEscape(content);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2016 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.web.socket.sockjs.transport.handler;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
|
||||||
import org.springframework.http.server.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.ServerHttpResponse;
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.socket.WebSocketHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.SockJsException;
|
|
||||||
import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.TransportHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.TransportType;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link TransportHandler} that receives messages over HTTP.
|
|
||||||
*
|
|
||||||
* @author Rossen Stoyanchev
|
|
||||||
*/
|
|
||||||
public class JsonpReceivingTransportHandler extends AbstractHttpReceivingTransportHandler {
|
|
||||||
|
|
||||||
private final FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportType getTransportType() {
|
|
||||||
return TransportType.JSONP_SEND;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
|
|
||||||
WebSocketHandler wsHandler, AbstractHttpSockJsSession sockJsSession) throws SockJsException {
|
|
||||||
|
|
||||||
super.handleRequestInternal(request, response, wsHandler, sockJsSession);
|
|
||||||
try {
|
|
||||||
response.getBody().write("ok".getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
throw new SockJsException("Failed to write to the response body", sockJsSession.getId(), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
protected String[] readMessages(ServerHttpRequest request) throws IOException {
|
|
||||||
SockJsMessageCodec messageCodec = getServiceConfig().getMessageCodec();
|
|
||||||
MediaType contentType = request.getHeaders().getContentType();
|
|
||||||
if (contentType != null && MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
|
|
||||||
MultiValueMap<String, String> map = this.formConverter.read(null, request);
|
|
||||||
String d = map.getFirst("d");
|
|
||||||
return (StringUtils.hasText(d) ? messageCodec.decode(d) : null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return messageCodec.decodeInputStream(request.getBody());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected HttpStatus getResponseStatus() {
|
|
||||||
return HttpStatus.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -55,8 +55,6 @@ import org.springframework.web.socket.sockjs.transport.TransportType;
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
|
import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.EventSourceTransportHandler;
|
import org.springframework.web.socket.sockjs.transport.handler.EventSourceTransportHandler;
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.HtmlFileTransportHandler;
|
import org.springframework.web.socket.sockjs.transport.handler.HtmlFileTransportHandler;
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.JsonpPollingTransportHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.JsonpReceivingTransportHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;
|
import org.springframework.web.socket.sockjs.transport.handler.WebSocketTransportHandler;
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.XhrPollingTransportHandler;
|
import org.springframework.web.socket.sockjs.transport.handler.XhrPollingTransportHandler;
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.XhrReceivingTransportHandler;
|
import org.springframework.web.socket.sockjs.transport.handler.XhrReceivingTransportHandler;
|
||||||
|
|
@ -178,8 +176,6 @@ public class HandlersBeanDefinitionParserTests {
|
||||||
containsInAnyOrder(
|
containsInAnyOrder(
|
||||||
instanceOf(XhrPollingTransportHandler.class),
|
instanceOf(XhrPollingTransportHandler.class),
|
||||||
instanceOf(XhrReceivingTransportHandler.class),
|
instanceOf(XhrReceivingTransportHandler.class),
|
||||||
instanceOf(JsonpPollingTransportHandler.class),
|
|
||||||
instanceOf(JsonpReceivingTransportHandler.class),
|
|
||||||
instanceOf(XhrStreamingTransportHandler.class),
|
instanceOf(XhrStreamingTransportHandler.class),
|
||||||
instanceOf(EventSourceTransportHandler.class),
|
instanceOf(EventSourceTransportHandler.class),
|
||||||
instanceOf(HtmlFileTransportHandler.class),
|
instanceOf(HtmlFileTransportHandler.class),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2013 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -30,8 +30,6 @@ public class TransportTypeTests {
|
||||||
assertEquals(TransportType.WEBSOCKET, TransportType.fromValue("websocket"));
|
assertEquals(TransportType.WEBSOCKET, TransportType.fromValue("websocket"));
|
||||||
assertEquals(TransportType.XHR, TransportType.fromValue("xhr"));
|
assertEquals(TransportType.XHR, TransportType.fromValue("xhr"));
|
||||||
assertEquals(TransportType.XHR_SEND, TransportType.fromValue("xhr_send"));
|
assertEquals(TransportType.XHR_SEND, TransportType.fromValue("xhr_send"));
|
||||||
assertEquals(TransportType.JSONP, TransportType.fromValue("jsonp"));
|
|
||||||
assertEquals(TransportType.JSONP_SEND, TransportType.fromValue("jsonp_send"));
|
|
||||||
assertEquals(TransportType.XHR_STREAMING, TransportType.fromValue("xhr_streaming"));
|
assertEquals(TransportType.XHR_STREAMING, TransportType.fromValue("xhr_streaming"));
|
||||||
assertEquals(TransportType.EVENT_SOURCE, TransportType.fromValue("eventsource"));
|
assertEquals(TransportType.EVENT_SOURCE, TransportType.fromValue("eventsource"));
|
||||||
assertEquals(TransportType.HTML_FILE, TransportType.fromValue("htmlfile"));
|
assertEquals(TransportType.HTML_FILE, TransportType.fromValue("htmlfile"));
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
|
||||||
|
|
||||||
@Mock private TransportHandler xhrSendHandler;
|
@Mock private TransportHandler xhrSendHandler;
|
||||||
|
|
||||||
@Mock private SessionCreatingTransportHandler jsonpHandler;
|
|
||||||
|
|
||||||
@Mock private TransportHandler jsonpSendHandler;
|
|
||||||
|
|
||||||
@Mock private HandshakeTransportHandler wsTransportHandler;
|
@Mock private HandshakeTransportHandler wsTransportHandler;
|
||||||
|
|
||||||
@Mock private WebSocketHandler wsHandler;
|
@Mock private WebSocketHandler wsHandler;
|
||||||
|
|
@ -89,9 +85,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
|
||||||
given(this.xhrHandler.getTransportType()).willReturn(TransportType.XHR);
|
given(this.xhrHandler.getTransportType()).willReturn(TransportType.XHR);
|
||||||
given(this.xhrHandler.createSession(sessionId, this.wsHandler, attributes)).willReturn(this.session);
|
given(this.xhrHandler.createSession(sessionId, this.wsHandler, attributes)).willReturn(this.session);
|
||||||
given(this.xhrSendHandler.getTransportType()).willReturn(TransportType.XHR_SEND);
|
given(this.xhrSendHandler.getTransportType()).willReturn(TransportType.XHR_SEND);
|
||||||
given(this.jsonpHandler.getTransportType()).willReturn(TransportType.JSONP);
|
|
||||||
given(this.jsonpHandler.createSession(sessionId, this.wsHandler, attributes)).willReturn(this.session);
|
|
||||||
given(this.jsonpSendHandler.getTransportType()).willReturn(TransportType.JSONP_SEND);
|
|
||||||
given(this.wsTransportHandler.getTransportType()).willReturn(TransportType.WEBSOCKET);
|
given(this.wsTransportHandler.getTransportType()).willReturn(TransportType.WEBSOCKET);
|
||||||
|
|
||||||
this.service = new TransportHandlingSockJsService(this.taskScheduler, this.xhrHandler, this.xhrSendHandler);
|
this.service = new TransportHandlingSockJsService(this.taskScheduler, this.xhrHandler, this.xhrSendHandler);
|
||||||
|
|
@ -103,13 +96,11 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
|
||||||
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class));
|
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class));
|
||||||
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
|
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
|
||||||
|
|
||||||
assertEquals(8, handlers.size());
|
assertEquals(6, handlers.size());
|
||||||
assertNotNull(handlers.get(TransportType.WEBSOCKET));
|
assertNotNull(handlers.get(TransportType.WEBSOCKET));
|
||||||
assertNotNull(handlers.get(TransportType.XHR));
|
assertNotNull(handlers.get(TransportType.XHR));
|
||||||
assertNotNull(handlers.get(TransportType.XHR_SEND));
|
assertNotNull(handlers.get(TransportType.XHR_SEND));
|
||||||
assertNotNull(handlers.get(TransportType.XHR_STREAMING));
|
assertNotNull(handlers.get(TransportType.XHR_STREAMING));
|
||||||
assertNotNull(handlers.get(TransportType.JSONP));
|
|
||||||
assertNotNull(handlers.get(TransportType.JSONP_SEND));
|
|
||||||
assertNotNull(handlers.get(TransportType.HTML_FILE));
|
assertNotNull(handlers.get(TransportType.HTML_FILE));
|
||||||
assertNotNull(handlers.get(TransportType.EVENT_SOURCE));
|
assertNotNull(handlers.get(TransportType.EVENT_SOURCE));
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +112,7 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
|
||||||
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), xhrHandler);
|
DefaultSockJsService service = new DefaultSockJsService(mock(TaskScheduler.class), xhrHandler);
|
||||||
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
|
Map<TransportType, TransportHandler> handlers = service.getTransportHandlers();
|
||||||
|
|
||||||
assertEquals(8, handlers.size());
|
assertEquals(6, handlers.size());
|
||||||
assertSame(xhrHandler, handlers.get(xhrHandler.getTransportType()));
|
assertSame(xhrHandler, handlers.get(xhrHandler.getTransportType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,28 +260,6 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
|
||||||
verifyNoMoreInteractions(this.xhrSendHandler);
|
verifyNoMoreInteractions(this.xhrSendHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleTransportRequestJsonp() throws Exception {
|
|
||||||
TransportHandlingSockJsService jsonpService = new TransportHandlingSockJsService(
|
|
||||||
this.taskScheduler, this.jsonpHandler, this.jsonpSendHandler);
|
|
||||||
String sockJsPath = sessionUrlPrefix+ "jsonp";
|
|
||||||
setRequest("GET", sockJsPrefix + sockJsPath);
|
|
||||||
jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
|
|
||||||
assertEquals(404, this.servletResponse.getStatus());
|
|
||||||
|
|
||||||
resetRequestAndResponse();
|
|
||||||
jsonpService.setAllowedOrigins(Collections.singletonList("http://mydomain1.com"));
|
|
||||||
setRequest("GET", sockJsPrefix + sockJsPath);
|
|
||||||
jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
|
|
||||||
assertEquals(404, this.servletResponse.getStatus());
|
|
||||||
|
|
||||||
resetRequestAndResponse();
|
|
||||||
jsonpService.setAllowedOrigins(Collections.singletonList("*"));
|
|
||||||
setRequest("GET", sockJsPrefix + sockJsPath);
|
|
||||||
jsonpService.handleRequest(this.request, this.response, sockJsPath, this.wsHandler);
|
|
||||||
assertNotEquals(404, this.servletResponse.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleTransportRequestWebsocket() throws Exception {
|
public void handleTransportRequestWebsocket() throws Exception {
|
||||||
TransportHandlingSockJsService wsService = new TransportHandlingSockJsService(
|
TransportHandlingSockJsService wsService = new TransportHandlingSockJsService(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -32,7 +32,7 @@ import static org.mockito.BDDMockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test fixture for {@link AbstractHttpReceivingTransportHandler} and sub-classes
|
* Test fixture for {@link AbstractHttpReceivingTransportHandler} and sub-classes
|
||||||
* {@link XhrReceivingTransportHandler} and {@link JsonpReceivingTransportHandler}.
|
* {@link XhrReceivingTransportHandler}.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
*/
|
*/
|
||||||
|
|
@ -46,35 +46,6 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTests
|
||||||
assertEquals(204, this.servletResponse.getStatus());
|
assertEquals(204, this.servletResponse.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readMessagesJsonp() throws Exception {
|
|
||||||
this.servletRequest.setContent("[\"x\"]".getBytes("UTF-8"));
|
|
||||||
handleRequest(new JsonpReceivingTransportHandler());
|
|
||||||
|
|
||||||
assertEquals(200, this.servletResponse.getStatus());
|
|
||||||
assertEquals("ok", this.servletResponse.getContentAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readMessagesJsonpFormEncoded() throws Exception {
|
|
||||||
this.servletRequest.setContent("d=[\"x\"]".getBytes("UTF-8"));
|
|
||||||
this.servletRequest.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
|
||||||
handleRequest(new JsonpReceivingTransportHandler());
|
|
||||||
|
|
||||||
assertEquals(200, this.servletResponse.getStatus());
|
|
||||||
assertEquals("ok", this.servletResponse.getContentAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test // SPR-10621
|
|
||||||
public void readMessagesJsonpFormEncodedWithEncoding() throws Exception {
|
|
||||||
this.servletRequest.setContent("d=[\"x\"]".getBytes("UTF-8"));
|
|
||||||
this.servletRequest.setContentType("application/x-www-form-urlencoded;charset=UTF-8");
|
|
||||||
handleRequest(new JsonpReceivingTransportHandler());
|
|
||||||
|
|
||||||
assertEquals(200, this.servletResponse.getStatus());
|
|
||||||
assertEquals("ok", this.servletResponse.getContentAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readMessagesBadContent() throws Exception {
|
public void readMessagesBadContent() throws Exception {
|
||||||
this.servletRequest.setContent("".getBytes("UTF-8"));
|
this.servletRequest.setContent("".getBytes("UTF-8"));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -24,14 +24,11 @@ import org.junit.Test;
|
||||||
import org.springframework.scheduling.TaskScheduler;
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
import org.springframework.web.socket.AbstractHttpRequestTests;
|
import org.springframework.web.socket.AbstractHttpRequestTests;
|
||||||
import org.springframework.web.socket.WebSocketHandler;
|
import org.springframework.web.socket.WebSocketHandler;
|
||||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
|
||||||
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
|
import org.springframework.web.socket.sockjs.frame.SockJsFrame;
|
||||||
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
|
import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat;
|
||||||
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
|
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
|
||||||
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession;
|
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession;
|
||||||
import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
|
import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
|
||||||
import org.springframework.web.util.UriUtils;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
@ -91,50 +88,6 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
|
||||||
assertEquals("c[2010,\"Another connection still open\"]\n", this.servletResponse.getContentAsString());
|
assertEquals("c[2010,\"Another connection still open\"]\n", this.servletResponse.getContentAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void jsonpTransport() throws Exception {
|
|
||||||
testJsonpTransport(null, false);
|
|
||||||
testJsonpTransport("_jp123xYz", true);
|
|
||||||
testJsonpTransport("A..B__3..4", true);
|
|
||||||
testJsonpTransport("!jp!abc", false);
|
|
||||||
testJsonpTransport("<script>", false);
|
|
||||||
testJsonpTransport("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testJsonpTransport(String callbackValue, boolean expectSuccess) throws Exception {
|
|
||||||
JsonpPollingTransportHandler transportHandler = new JsonpPollingTransportHandler();
|
|
||||||
transportHandler.initialize(this.sockJsConfig);
|
|
||||||
PollingSockJsSession session = transportHandler.createSession("1", this.webSocketHandler, null);
|
|
||||||
|
|
||||||
resetRequestAndResponse();
|
|
||||||
setRequest("POST", "/");
|
|
||||||
|
|
||||||
if (callbackValue != null) {
|
|
||||||
// need to encode the query parameter
|
|
||||||
this.servletRequest.setQueryString("c=" + UriUtils.encodeQueryParam(callbackValue, "UTF-8"));
|
|
||||||
this.servletRequest.addParameter("c", callbackValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
transportHandler.handleRequest(this.request, this.response, this.webSocketHandler, session);
|
|
||||||
}
|
|
||||||
catch (SockJsTransportFailureException ex) {
|
|
||||||
if (expectSuccess) {
|
|
||||||
throw new AssertionError("Unexpected transport failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectSuccess) {
|
|
||||||
assertEquals(200, this.servletResponse.getStatus());
|
|
||||||
assertEquals("application/javascript;charset=UTF-8", this.response.getHeaders().getContentType().toString());
|
|
||||||
verify(this.webSocketHandler).afterConnectionEstablished(session);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
assertEquals(500, this.servletResponse.getStatus());
|
|
||||||
verifyNoMoreInteractions(this.webSocketHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleRequestXhrStreaming() throws Exception {
|
public void handleRequestXhrStreaming() throws Exception {
|
||||||
XhrStreamingTransportHandler transportHandler = new XhrStreamingTransportHandler();
|
XhrStreamingTransportHandler transportHandler = new XhrStreamingTransportHandler();
|
||||||
|
|
@ -205,10 +158,6 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
|
||||||
format = new EventSourceTransportHandler().getFrameFormat(this.request);
|
format = new EventSourceTransportHandler().getFrameFormat(this.request);
|
||||||
formatted = format.format(frame);
|
formatted = format.format(frame);
|
||||||
assertEquals("data: " + frame.getContent() + "\r\n\r\n", formatted);
|
assertEquals("data: " + frame.getContent() + "\r\n\r\n", formatted);
|
||||||
|
|
||||||
format = new JsonpPollingTransportHandler().getFrameFormat(this.request);
|
|
||||||
formatted = format.format(frame);
|
|
||||||
assertEquals("/**/callback(\"" + frame.getContent() + "\");\r\n", formatted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2030,11 +2030,6 @@ annotations. When further control is needed, a custom `ObjectMapper` can be inje
|
||||||
through the `ObjectMapper` property for cases where custom JSON
|
through the `ObjectMapper` property for cases where custom JSON
|
||||||
serializers/deserializers need to be provided for specific types.
|
serializers/deserializers need to be provided for specific types.
|
||||||
|
|
||||||
http://en.wikipedia.org/wiki/JSONP[JSONP] is supported and automatically enabled when
|
|
||||||
the request has a query parameter named `jsonp` or `callback`. The JSONP query parameter
|
|
||||||
name(s) could be customized through the `jsonpParameterNames` property.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[mvc-view-xml-mapping]]
|
[[mvc-view-xml-mapping]]
|
||||||
=== XML
|
=== XML
|
||||||
|
|
|
||||||
|
|
@ -2645,30 +2645,6 @@ to the model:
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
[[mvc-ann-jsonp]]
|
|
||||||
===== Jackson JSONP
|
|
||||||
|
|
||||||
In order to enable http://en.wikipedia.org/wiki/JSONP[JSONP] support for `@ResponseBody`
|
|
||||||
and `ResponseEntity` methods, declare an `@ControllerAdvice` bean that extends
|
|
||||||
`AbstractJsonpResponseBodyAdvice` as shown below where the constructor argument indicates
|
|
||||||
the JSONP query parameter name(s):
|
|
||||||
|
|
||||||
[source,java,indent=0]
|
|
||||||
[subs="verbatim,quotes"]
|
|
||||||
----
|
|
||||||
@ControllerAdvice
|
|
||||||
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
|
|
||||||
|
|
||||||
public JsonpAdvice() {
|
|
||||||
super("callback");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
For controllers relying on view resolution, JSONP is automatically enabled when the
|
|
||||||
request has a query parameter named `jsonp` or `callback`. Those names can be
|
|
||||||
customized through `jsonpParameterNames` property.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[mvc-ann-modelattrib-methods]]
|
[[mvc-ann-modelattrib-methods]]
|
||||||
|
|
|
||||||
|
|
@ -401,8 +401,8 @@ The 3 possible behaviors are:
|
||||||
transport is disabled since it does not allow to check the origin of a request.
|
transport is disabled since it does not allow to check the origin of a request.
|
||||||
As a consequence, IE6 and IE7 are not supported when this mode is enabled.
|
As a consequence, IE6 and IE7 are not supported when this mode is enabled.
|
||||||
* Allow a specified list of origins: each provided _allowed origin_ must start with `http://`
|
* Allow a specified list of origins: each provided _allowed origin_ must start with `http://`
|
||||||
or `https://`. In this mode, when SockJS is enabled, both IFrame and JSONP based
|
or `https://`. In this mode, when SockJS is enabled, IFrame transport is disabled.
|
||||||
transports are disabled. As a consequence, IE6 through IE9 are not supported when this
|
As a consequence, IE6 through IE9 are not supported when this
|
||||||
mode is enabled.
|
mode is enabled.
|
||||||
* Allow all origins: to enable this mode, you should provide `{asterisk}` as the allowed origin
|
* Allow all origins: to enable this mode, you should provide `{asterisk}` as the allowed origin
|
||||||
value. In this mode, all transports are available.
|
value. In this mode, all transports are available.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue