안녕하세요, 하마연구소 입니다.
SpringFramework에서 JSONP 처리를 기본 기능으로 제공하였지만, 스프링부트 2.1(스프링프레임워크 5.1)부터 없어졌습니다.
이미 Deprecated 처리되어 언제가는 없어질 것을 예상했지만, 담당하고 있는 시스템에서는 아직 JSONP가 필요한 상황입니다.
없어진 AbstractJsonpResponseBodyAdvice.java
소스
https://github.com/spring-projects/spring-framework/blob/5.0.x/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractJsonpResponseBodyAdvice.java
Spring에서는 기본 기능으로 빠졌어도 3rd 파트 라이브러리가 있겠지 싶어 폭풍검색을 했지만 찾을 수 없었습니다.
그래서 그냥 직접 구현하려고 하였지만, 은근히 스프링 코어쪽 소스를 이곳저곳 건드려야하는 복잡함을 느꼈습니다.
그래서 최소한의 소스로 최대한 직관적으로 JSONP를 처리할 수 있도록 구현했습니다.
JsonpAdvice.java
ControllerAdvice
로 request 파라미터에 jsonp 또는 callback이 있으면 JSONP로 처리하기
import org.apache.commons.lang3.StringUtils;
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.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMappingJacksonResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Pattern;
/**
* @see "AbstractJsonpResponseBodyAdvice" in SpringFramework 5.X (https://github.com/spring-projects/spring-framework/blob/5.0.x/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractJsonpResponseBodyAdvice.java)
*/
@ControllerAdvice
public class JsonpAdvice extends AbstractMappingJacksonResponseBodyAdvice {
/**
* Pattern for validating jsonp callback parameter values.
*/
public static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*");
public static final MediaType JSONP_MEDIA_TYPE = new MediaType("application", "javascript");
public static final String[] JSONP_QUERY_PARAMETER_NAMES = {"jsonp", "callback"};
@Override
protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
for (String jsonpQueryParameterName : JSONP_QUERY_PARAMETER_NAMES) {
String jsonpFunctionName = servletRequest.getParameter(jsonpQueryParameterName);
if (StringUtils.isNotBlank(jsonpFunctionName) == true) {
if (CALLBACK_PARAM_PATTERN.matcher(jsonpFunctionName).matches() == true) {
// JSONP용 MediaType 설정
response.getHeaders().setContentType(JSONP_MEDIA_TYPE);
// Jackson에 의해서 JSONP로 처리할 수 있는 JsonpWrappingObject 인스턴스 생성
JsonpWrappingObject jsonpWrappingObject = JsonpWrappingObject.builder()
.jsonpFunctionName(jsonpFunctionName)
.value(bodyContainer.getValue())
.build();
// MappingJacksonValue에 JsonpWrappingObject 값 설정
bodyContainer.setValue(jsonpWrappingObject);
break;
}
}
}
}
}
JsonpWrappingObject.java
- 기존
MappingJacksonValue.java
에jsonpFunction
필드 변수가 없어졌기 때문에 이제 더이상HttpMessageConverter
에게 응답을 JSONP로 처리하도록 알려줄 수 없음 - 따라서 응답 객체를 wrapping하여 JSONP로 처리할 수 있도록 함
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@JsonSerialize(using = JsonpWrappingObjectJsonSerializer.class)
public class JsonpWrappingObject {
private String jsonpFunctionName;
private Object value;
}
JsonpWrappingObjectJsonSerializer.java
- JSONP 형식으로 응답하기 위한 커스텀
JsonSerializer
를 구현
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class JsonpWrappingObjectJsonSerializer extends JsonSerializer<JsonpWrappingObject> {
@Override
public Class<JsonpWrappingObject> handledType() {
return JsonpWrappingObject.class;
}
@Override
public void serialize(JsonpWrappingObject value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value != null) {
// JSONP의 prefix 부분 write
gen.writeRaw("/**/");
gen.writeRaw(value.getJsonpFunctionName());
gen.writeRaw("(");
// 값 write
gen.writeObject(value.getValue());
// JSONP의 postfix 부분 write
gen.writeRaw(");");
}
}
}
SpringFramework 5.1 이전 버전에서는 AbstractJackson2HttpMessageConverter
나 JsonView
등 이곳저곳에서 JSONP 처리를 위한 코딩이 되어있습니다.
위 코드는 다소 억지스럽게 강제로 JSONP 처리가 되도록 한 것이며, 다른 환경에서는 수행되지 않을 수도 있는 점 참고부탁드립니다.
감사합니다.