由于某些原因,Jackson 2.3.0 无法解析 JSONP 响应。
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'my_json_callback':
我已经成功地完成了反序列化过程,而没有使用回调函数。
我尝试使用Jackson JAX-RS包,其中包括一个@JSONP注释,但似乎仅在序列化时使用。
这是我使用ReaderInterceptor
想出的针对空间进行修剪的解决方案。我正在使用Jersey 2.x与Jackson一起与只输出JSONP的Web服务进行交互。
public class CallbackStripInterceptor implements ReaderInterceptor {
private final static byte[] callbackBytes = "callback(".getBytes();
@Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
int howMany = callbackBytes.length;
InputStream x = context.getInputStream();
if( !(x.available() >= howMany) ) {
return context.proceed();
}
x.mark( howMany );
byte[] preamble = new byte[ howMany ];
x.read( preamble );
// In case the first part of our entity doesn't have the callback String, reset the stream so downstream exploiters get the full entity.
if( !Arrays.equals( preamble, callbackBytes ) ) {
x.reset();
}
return context.proceed();
}
使用方法如下:
Client c = ClientBuilder.newBuilder()
.register( new CallbackStripInterceptor() )
.build();
最终,我已经成功移除了JSONP响应的回调部分。
首先,即使JSON以括号结尾,Jackson也能解析它。因此,只需从响应中删除my_json_callback(
即可。
由于我正在使用Apache的HTTP客户端,这样就可以解决问题:
String callback = "my_json_callback(";
InputStreamReader r = new InputStreamReader(response.getEntity().getContent());
r.skip(callback.length());
return mapper.readValue(r, MyObject.class);
这个想法是不需要将阅读器转换为字符串,然后在删除回调部分后解析该字符串。
我还能够使用 json.org
库中的 JSONTokener
对给定的 JSONP 字符串实现相同的结果:
JSONTokener t = new JSONTokener(json);
t.nextValue(); // skip the callback
return mapper.readValue(t.nextValue().toString(), MyObject.class);
import java.io.IOException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import feign.InvocationContext;
import feign.Response;
import feign.ResponseInterceptor;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor(access = AccessLevel.PUBLIC)
public class JsonpResponseInterceptor implements ResponseInterceptor {
private final String identifier;
@Override
public Object aroundDecode(InvocationContext invocationContext) throws IOException {
String resp = IOUtils.toString(invocationContext.response().body().asInputStream(),
invocationContext.response().charset());
LOGGER.debug("response is :"+resp);
if (resp.startsWith(identifier)) {
String substring = StringUtils.substringBetween(resp, "(", ")");
Response modifiedResponse = Response.builder().body(substring, invocationContext.response().charset())
.headers(invocationContext.response().headers())
.protocolVersion(invocationContext.response().protocolVersion())
.reason(invocationContext.response().reason()).status(invocationContext.response().status())
.request(invocationContext.response().request()).build();
return invocationContext.decoder().decode(modifiedResponse, invocationContext.returnType());
} else {
return invocationContext.proceed();
}
}
}