IT/PROGRAMMING

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

하마연구소장 2019. 6. 9. 00:18
728x90
반응형

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

 

반응형