我想从http调用的回复中获得报头值,将其存储在缓存中,并通过将其传递给http请求的报头来在下一个请求中使用此值,以实现粘性会话。我的项目是在Java 8。
我有一个假的http客户端构建器像这样使用JacksonDecoder解码。
public <T> T buildClient(Class<T> type, String url) {
return Feign.builder()
.contract(new JAXRSContract())
.encoder(new JacksonEncoder(mapper))
.decoder(new CustomDecoder(mapper))
.logger(new Slf4jLogger(Constants.FFA_LOGGER_NAME))
.logLevel(Level.FULL)
.options(new Options(connectTimeout, readTimeout))
.retryer(Retryer.NEVER_RETRY)
.requestInterceptor(auth2FeignRequestInterceptor)
.invocationHandlerFactory(factory)
.target(type, url);
}
jackson decode的默认解码器只有body而不是header,所以我正在实现我自己的CustomDecoder。
我想要实现的是得到响应的值。
从映射器获取值后,将其映射到body或动态地在对象responseBodyObject中添加属性。public final class CustomDecoder extends JacksonDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
//Here the default decoder only decoding body.
Reader reader = response.body().asReader();
Object responseBodyObject = mapper.readValue(reader, mapper.constructType(type));
如果我理解正确的话,这是一个构造类型具有应该从header填充的附加属性的情况。这里有几种方法。首先,我更喜欢的是,有一个特殊的接口类型,你的解码器知道,被编码的对象可以实现接收值:
public interface SessionTokenAware {
void setSessionToken(String value);
}
然后在你的解码器中:
@Override
public Object decode(Response response, Type type) throws IOException {
Object parsedResponse = mapper.readValue(
response.body().asReader(),
mapper.constructType(type)
);
if (parsedResponse instanceof SessionTokenAware) {
SessionTokenAware sessionAware = (SessionTokenAware) parsedResponse;
Collection<String> sessionHeader = response.headers().get("sessionId");
if (sessionHeader != null && !sessionHeader.isEmpty()) {
sessionAware.setSessionToken(sessionHeader.iterator().next());
}
}
return parsedResponse;
}
当您的响应实现接口时,它将获得由解码器设置的会话令牌值,而无需破解body流并向其添加任何内容:
public class MyResponseBody implements SesionTokenAware {
private String token;
@Override
public void setSessionToken(String value) {
token = value;
}
public String getSessionToken() {
return token;
}
}
或者,如果你不喜欢为你想要接收的每一种可能的头文件都有单独的接口,你可以创建一个捕获所有的接口,让每个响应实现自己对头文件进行分类:
public interface HeadersAware {
void onHeaders(Map<String, Collection<String>> headerValues);
}
public Object decode(Response response, Type type) throws IOException {
Object parsedResponse = ....
if (parsedResponse instanceof HeadersAware) {
HeaderAware headerAware = (HeaderAware) parsedResponse;
headerAware.onHeaders(response.headers());
}
return parsedResponse;
}
public class MyResponse implements HeaderAware {
private String token;
public void onHeaders(Map<String, Collection<String>> headers) {
Collection<String> sessionHeader = headers.getOrDefault("sessionId", emptySet());
if (!sessionHeader.isEmpty()) {
token = sessionHeader.iterator().next();
}
}
}
我不喜欢后一种方法,原因有二:
- 响应可能会看到它没有业务看到的标头值,因为它接收所有的值,而不仅仅是实现特定响应POJO所需的值。
- 如果你有多个不同层次的会话感知响应(所以没有共同的超类是可能的)-然后你将不得不复制粘贴的sessionId处理代码到每个他们,而不是有代码整齐地位于
CustomDecoder
类。
下面是我如何用不同的方法解决我自己的问题。为了将报头值复制到主体,我最终将响应转换为JSON树,然后将sessionid/token的报头值添加到主体,然后将其转换回对象。
@Override
public Object decode(Response response, Type type) throws IOException {
Map<String, Collection<String>> headers = response.headers();
Collection<String> values = headers.get("sessionid");
String value = null;
if (values != null && values.isEmpty() == false) {
String[] array = new String[values.size()];
values.toArray(array);
value = array[0];
}
if (response.status() == 404)
return Util.emptyValueOf(type);
if (response.body() == null)
return null;
ByteArrayOutputStream output = new ByteArrayOutputStream();
try{
StreamUtils.copy(response.body().asInputStream(), output);
} catch (FileNotFoundException ex) {
}
JsonNode actualObj = mapper.readTree(output.toByteArray());
JsonNode node1=actualObj.get("header");
ObjectNode parentObjectNode = (ObjectNode) node1;
parentObjectNode.put("token", value);
Object returnObject = mapper.treeToValue(actualObj, mapper.constructType(type).getClass());
return returnObject;
}
}