文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

Spring bootでjsonpの先頭に謎の/**/が混入する問題とその対応

なんでデフォルトの挙動がこんな事になってるんですかね・・・
f:id:treeapps:20180802010416p:plain

現象

以下はチュートリアルのjsonを返すコントローラーです。

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.music.anime.tree.json.Greeting;

@RestController
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping(value = "/greeting", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}

しかし、jsonではクロスドメインの壁を超えられないので、jsonpを返したいところです。

そこで以下のようなクラスとメソッドを用意すると、親クラスのjsonpQueryParamNamesに「callback」というパラメータが設定されます。この値が設定されると「callback」というパラメータがある場合はjsonpになるよ、という処理がされます。

import java.io.IOException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }
}

しかしこの状態でjsonpを出力してみると、何故か以下のようなjsonpが返ってしまいます。
http://localhost:8080/greeting?callback=test

/**/test({"id":1,"content":"Hello, World!"});

何故か先頭に「/**/」というコメントが挿入されてしまいます。

正直これではカッコ悪過ぎますね。

対応

何故先頭に謎ものコメントが挿入されるのかあまり解ってないのですが、以下のようなメソッドを定義しておくと、謎の「/**/」を削除する事ができます。

import java.io.IOException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {

    public JsonpAdvice() {
        super("callback");
    }

    @Bean
    public MappingJackson2HttpMessageConverter MappingJackson2HttpMessageConverter(ApplicationContext ctx) {
        ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().applicationContext(ctx).build();
        return new MappingJackson2HttpMessageConverter(mapper) {
            @Override
            protected void writePrefix(JsonGenerator generator, Object obj) throws IOException {
                if (!(obj instanceof MappingJacksonValue))
                    return;
                String funcName = ((MappingJacksonValue) obj).getJsonpFunction();
                if (funcName != null)
                    generator.writeRaw(funcName + "(");
            }
        };
    }
}

MappingJackson2HttpMessageConverterメソッドがあると、
http://localhost:8080/greeting?callback=test
のURLで以下の正常なjsonpが返るようになります。

test({"id":1,"content":"Hello, World!"});

これでようやくまともなjsonpを生成する事ができます。

www.bunkei-programmer.net