[Spring] spring-boot 2.1(SpringFramework 5.1)에서 없어진 기능, JSONP 간단하게 구현하기

JSONP 처리를 기본 기능으로 제공하였지만, 스프링부트 2.1(스프링프레임워크 5.1)부터 없어졌다.
이미 Deprecated 처리되어 언제가는 없어질 것을 예상했지만, 담당하고 있는 시스템에서는 아직 JSONP가 필요하였다.

 

spring-projects/spring-framework

Spring Framework. Contribute to spring-projects/spring-framework development by creating an account on GitHub.

github.com

 

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 처리가 되도록 한 것이며, 다른/특별한 환경에서는 수행되지 않을 수도 있다.

 

댓글(0)

Designed by JB FACTORY