我有两个Spring MVC应用程序;我正在使用Spring Integration HTTP来集成它们。
流程
该过程非常简单:指向“application1”的HTTP GET请求由@Controller
管理,然后“转移”到“application2”通过HTTP POST <int-http:outbound-gateway>
。
在那里,这个POST(中间)请求被管理并转移到“application2”的另一个URI(另一个<int-http:outbound-gateway>
),提供真实的响应< /强>
问题
我注意到“application2”方面的请求管理中有不同的行为(由“application1”的<int-http:outbound-gateway>
调用
如果我使用@Controller
来管理“中间”URI,那么流程执行需要几毫秒才能完成。
但如果我使用<int-http:inbound-gateway>
复制相同的流程,需要20秒才能完成流程:为什么会出现这种差异?
这是@Controller
:
@Controller
@RequestMapping(value="/service")
public class ServiceController {
@RequestMapping(value="/{resource}", method=RequestMethod.POST)
public void resource(@PathVariable(value="resource") String resource, HttpServletRequest request, HttpServletResponse res){
handleRequest(resource,request, res);
}
private void handleRequest(String resource, HttpServletRequest request, HttpServletResponse res) throws Exception {
// READING THE REQUEST PAYLOAD
RequestHandler requestHandler = RequestHandler.getHandler(request);
RequestPayload payload = requestHandler.getPayload();
// PREPARING THE MESSAGE TO THE CHANNEL
MessagingChannel messagingChannel = (MessagingChannel)ApplicationContextResolver.getApplicationContext().getBean("requestChannelBean");
MessageChannel requestChannel = messagingChannel.getRequestChannel();
MessagingTemplate messagingTemplate = new MessagingTemplate();
String url = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getServletContext().getContextPath()+request.getServletPath()+"/"+resource;
Message<?> requestMessage = MessageBuilder.withPayload(url).build();
// SEND THE MESSAGE AND RECEIVE THE RESPONSE
Message<?> response = messagingTemplate.sendAndReceive(requestChannel, requestMessage);
// PRINT THE RESPONSE
res.getWriter().write((String)response.getPayload());
}
}
此解决方案完美运行,需要几毫秒才能返回响应。
以下是我正在尝试使用<int-http:inbound-gateway>
和<int:service-activator>
复制 @Controller
解决方案的实现:
<int:service-activator id="channelServiceActivator"
ref="channelService"
input-channel="requestChannel"
method="manage"/>
<int-http:inbound-gateway id="gateway" request-channel="requestChannel"
path="/service/**"
supported-methods="POST"
header-mapper="headerMapper">
<int-http:header name="requestAttributes" expression="#requestAttributes"/>
</int-http:inbound-gateway>
使用此服务激活器:
public class ChannelService {
public String manage(Message<?> message) throws Throwable{
// RECOVERING THE HttpServletRequest
ServletRequestAttributes sra = (ServletRequestAttributes) message.getHeaders().get("requestAttributes");
HttpServletRequest request = sra.getRequest();
// RETRIEVING THE PAYLOAD
Object payload = message.getPayload();
// PREPARING THE MESSAGE TO THE CHANNEL
MessagingChannel messagingChannel = (MessagingChannel)ApplicationContextResolver.getApplicationContext().getBean("requestChannelBean");
MessageChannel requestChannel = messagingChannel.getRequestChannel();
MessagingTemplate messagingTemplate = new MessagingTemplate();
String url = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getServletContext().getContextPath()+request.getServletPath()+"/"+resource;
Message<?> requestMessage = MessageBuilder.withPayload(url).build();
// SEND THE MESSAGE AND RECEIVE THE RESPONSE
Message<?> response = messagingTemplate.sendAndReceive(requestChannel, requestMessage);
// RETURN THE RESPONSE
return (String) response.getPayload(); // <- since here, the behaviour is normal.
// The framework needs 20 seconds after this instruction
}
}
此实现需要20秒才能完成每个请求,我不明白其原因。 我没有在Spring日志中看到任何错误,我只看到系统似乎“冻结”:它不再记录任何东西20秒。
任何解释都将不胜感激。
更新
这里是日志。 在“HttpMessageConverterExtractor.extractData:101”之后,它需要20秒才能查看其他“org.springframework.integration.http”日志(2016-07-29 15:06:18 - &gt; 2016-07-29 15:06 :38)
2016-07-29 15:06:18 TRACE [] org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.close:217 (http-nio-8080-exec-4) - Logical connection closed
GenericMessage [payload={
"_embedded" : {
"persons" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/application2/api/persons"
},
"profile" : {
"href" : "http://localhost:8080/application2/api/profile/persons"
},
"search" : {
"href" : "http://localhost:8080/application2/api/persons/search"
}
}
}, headers={Transfer-Encoding=chunked, Server=Apache-Coyote/1.1, Accept=application/json, X-Content-Type-Options=nosniff, Pragma=no-cache, http_statusCode=200, Date=1469797578000, X-Frame-Options=DENY, Cache-Control=no-cache, no-store, max-age=0, must-revalidate, id=2b6b0b80-82bc-2ce7-c364-1e84c389bc71, X-XSS-Protection=1; mode=block, contentType=application/json, timestamp=1469797578411}]
2016-07-29 15:06:18 DEBUG [] org.springframework.web.client.RestTemplate.handleResponse:658 (executor-1) - POST request for "http://localhost:8080/application2/api/service/persons" resulted in 200 (OK)
2016-07-29 15:06:18 DEBUG [] org.springframework.web.client.HttpMessageConverterExtractor.extractData:101 (executor-1) - Reading [java.lang.String] as "application/octet-stream" using [org.springframework.http.converter.StringHttpMessageConverter@5397e277]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:436 (executor-1) - inboundHeaderNames=[*]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[server] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[Server], value=[Apache-Coyote/1.1]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[pragma] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[Pragma], value=no-cache
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[accept] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[Accept], value=[application/json]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[host] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[host], value=[localhost:8080]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[connection] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[connection], value=[keep-alive]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[cache-control] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[Cache-Control], value=no-cache
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[user-agent] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[user-agent], value=[Java/1.8.0_65]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[x-xss-protection] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:454 (executor-1) - setting headerName=[X-XSS-Protection], value=[1; mode=block]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[x-frame-options] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:454 (executor-1) - setting headerName=[X-Frame-Options], value=[DENY]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[x-content-type-options] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:454 (executor-1) - setting headerName=[X-Content-Type-Options], value=[nosniff]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[content-type] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[Content-Type], value=application/octet-stream
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[content-length] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[Content-Length], value=968
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.shouldMapHeader:537 (executor-1) - headerName=[date] WILL be mapped, matched pattern=*
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:463 (executor-1) - setting headerName=[Date], value=1.469.797.578.000
answer: {
"_embedded" : {
"persons" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/application2/api/persons"
},
"profile" : {
"href" : "http://localhost:8080/application2/api/profile/persons"
},
"search" : {
"href" : "http://localhost:8080/application2/api/persons/search"
}
}
}
2016-07-29 15:06:38 TRACE [] org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest:138 (http-nio-8080-exec-2) - Method [sendMessageToChannel] returned [null]
2016-07-29 15:06:38 DEBUG [] org.springframework.web.servlet.DispatcherServlet.processDispatchResult:1044 (http-nio-8080-exec-2) - Null ModelAndView returned to DispatcherServlet with name 'dispatcher': assuming HandlerAdapter completed request handling
2016-07-29 15:06:38 TRACE [] org.springframework.web.servlet.FrameworkServlet.resetContextHolders:1062 (http-nio-8080-exec-2) - Cleared thread-bound request context: SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.context.HttpSessionSecurityContextRepository$Servlet3SaveToSessionRequestWrapper@9bacc4f]
2016-07-29 15:06:38 DEBUG [] org.springframework.web.servlet.FrameworkServlet.processRequest:1000 (http-nio-8080-exec-2) - Successfully completed request
2016-07-29 15:06:38 TRACE [] org.springframework.context.support.AbstractApplicationContext.publishEvent:362 (http-nio-8080-exec-2) - Publishing event in WebApplicationContext for namespace 'dispatcher-servlet': ServletRequestHandledEvent: url=[/application1/api/persons/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcher]; session=[null]; user=[null]; time=[21426ms]; status=[OK]
2016-07-29 15:06:38 TRACE [] org.springframework.context.support.AbstractApplicationContext.publishEvent:362 (http-nio-8080-exec-2) - Publishing event in Root WebApplicationContext: ServletRequestHandledEvent: url=[/application1/api/persons/]; client=[0:0:0:0:0:0:0:1]; method=[GET]; servlet=[dispatcher]; session=[null]; user=[null]; time=[21426ms]; status=[OK]
2016-07-29 15:06:38 DEBUG [] org.springframework.security.web.access.ExceptionTranslationFilter.doFilter:117 (http-nio-8080-exec-2) - Chain processed normally
2016-07-29 15:06:38 DEBUG [] org.springframework.security.web.header.writers.HstsHeaderWriter.writeHeaders:130 (http-nio-8080-exec-2) - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@7ffcc692
2016-07-29 15:06:38 DEBUG [] org.springframework.security.web.context.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper.saveContext:352 (http-nio-8080-exec-2) - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2016-07-29 15:06:38 DEBUG [] org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter:119 (http-nio-8080-exec-2) - SecurityContextHolder now cleared, as request processing completed
更新2
我已经调查了更多的过程:
outbound-gateway
(application1)使用“sendAndReceive()
”向远程端点(application2)发送消息;该消息包含标题“application/octet-stream
”,因为我将字节数组作为有效负载传递。inbound-gateway
和服务激活器service activator
(application2)处理请求并返回String。HttpMessageConverterExtractor
(application1)尝试使用已发送到“application2”(application/octet-stream
)的消息的 contentType 读取响应。我认为问题就在这里:我发送了一条消息,其中outbound-gateway
标题为application/octet-stream
,,但作为回应我必须收到一个字符串(使用application/json
),不是应用程序/八位字节流。
这些是类inputMessage
的{{1}}方法使用的readInternal
变量的信息:
org.springframework.http.converter.StringHttpMessageConverter
Content-Type 为inputMessage MessageBodyClientHttpResponseWrapper (id=111)
pushbackInputStream PushbackInputStream (id=146)
response SimpleClientHttpResponse (id=121)
connection HttpURLConnection (id=125)
headers HttpHeaders (id=182) {Server=[Apache-Coyote/1.1], Pragma=[no-cache], Accept=[application/json], host=[localhost:8080], connection=[keep-alive], Cache-Control=[no-cache], user-agent=[Java/1.8.0_65], X-XSS-Protection=[1; mode=block], X-Frame-Options=[DENY], X-Content-Type-Options=[nosniff], Content-Type=[application/octet-stream], Content-Length=[968], Date=[Mon, 01 Aug 2016 12:52:47 GMT]}
responseStream HttpURLConnection$HttpInputStream (id=180)
,但必须仅提及出站中的消息,而不是针对在答案中获得的入站消息!
有什么方法可以配置可以接收出站网关的响应的内容类型吗?
我的应用程序服务激活器2以这种方式处理答案:
application/octet-stream
更新3
问题是public class ChannelService {
public String manage(Message<?> message) throws Throwable{
ServletRequestAttributes sra = (ServletRequestAttributes) message.getHeaders().get("requestAttributes");
HttpServletRequest request = sra.getRequest();
System.out.println(request.getRequestURL());
Object payload = message.getPayload();
return "{ \"test\" : \"test\"}";
}
}
InputStream
获得HttpInputMessage
。
虽然使用“inputMessage.getBody()
”方法处理此InputStream,但“read()
”的结果永远不会为-1,持续20秒。
我已经实现了自定义read()
:
StringHttpMessageConverter
自定义@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
System.out.println("ResponseStringHttpMessageConverter.readInternal()");
int content;
long start = System.currentTimeMillis();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ( ( content = inputMessage.getBody().read()) != -1){
System.out.println(content);
baos.write(content);
}
long end = System.currentTimeMillis();
System.out.println("TIME READING: "+(end-start));
String response = baos.toString();
System.out.println(response);
return response;
}
方法打印每个字符的ASCII代码,在停止打印的最后一个字符之后以及重新启动它时打印:
readInternal
所以我认为消息的InputStream中有错误,因为read()方法在20秒之前没有返回“-1”。
更新4
我解决了转换器方面的问题,但在TIME READING: 20164
类的doExecute()
方法中关闭响应时存在同样的问题。
该方法尝试关闭响应(org.springframework.web.client.RestTemplate
的实例):
org.springframework.http.client.SimpleClientHttpResponse
这是@Override
public void close() {
if (this.responseStream != null) {
try {
StreamUtils.drain(this.responseStream); // <- again the same problem
this.responseStream.close();
}
catch (IOException e) { }
}
}
类的"drain"
实现:
org.springframework.util.StreamUtils
在我的转换器中,我使用具有预期可用缓冲区大小的缓冲区解决了问题:
public static int drain(InputStream in) throws IOException {
Assert.notNull(in, "No InputStream specified");
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead = -1;
int byteCount = 0;
while ((bytesRead = in.read(buffer)) != -1) { // it blocks here !!!
byteCount += bytesRead;
}
return byteCount;
}
我确信我的outbound-gateway / inbound-gateway / service激活器的配置有问题,因为响应在读取EOS的-1字节之前就会产生一个块。 否则会有一个错误,我不相信这个选项。
答案 0 :(得分:1)
根据你的记录:
2016-07-29 15:06:18 DEBUG [] org.springframework.web.client.HttpMessageConverterExtractor.extractData:101 (executor-1) - Reading [java.lang.String] as "application/octet-stream" using [org.springframework.http.converter.StringHttpMessageConverter@5397e277]
2016-07-29 15:06:38 DEBUG [] org.springframework.integration.http.support.DefaultHttpHeaderMapper.toHeaders:436 (executor-1) - inboundHeaderNames=[*]
看起来您的响应非常大,需要花费时间才能转换为String
。
请参阅HttpMessageConverterExtractor.extractData(ClientHttpResponse response)
。
Spring Integration没有做什么。
如果您调试StringHttpMessageConverter.readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage)
并查看您的真实回复会更好。
答案 1 :(得分:0)
我已经解决了问题,但为了解决这个问题,我必须创建一个自定义 RestTemplate
。
在我的自定义RestTemplate中,我覆盖方法&#34; doExecute()
&#34;。
实现与原始RestTemplate.doExecute相同,但finally块除外。
在这个区块中,我没有打电话给#34; response.close()
&#34;但我创建了一个自定义方法,用于关闭响应客户端。
这是自定义的RestTemplate实现:
public class AppRestTemplate extends RestTemplate {
@Override
protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "'url' must not be null");
Assert.notNull(method, "'method' must not be null");
ClientHttpResponse response = null;
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
if (responseExtractor != null) {
return responseExtractor.extractData(response);
}
else {
return null;
}
}
catch (IOException ex) {
String resource = url.toString();
String query = url.getRawQuery();
resource = (query != null ? resource.substring(0, resource.indexOf(query) - 1) : resource);
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
}
finally {
if (response != null) {
// response.close(); // <- commented; this is the cause of the slow process
close(response);
}
}
}
/** This method only provides to close the stream, without draining it */
private void close(ClientHttpResponse response) {
try {
if (response.getBody()!=null){
response.getBody().close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
显然我必须在Application Context配置中注册新的RestTemplate:
<bean id="restTemplate" name="restTemplate" class="mypackage.integration.AppRestTemplate" autowire-candidate="true">
<property name="messageConverters">
<list>
<bean class="mypackage.integration.ResponseStringHttpMessageConverter" />
<!-- ... other converters -->
</list>
</property>
</bean>
最后但并非最不重要,我还必须实施自定义转换器 替换原始StringHtppMessageConverter
(在我的情况下) ,ResponseStringHttpMessageConverter
):
public class ResponseStringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
private Logger log = LoggerFactory.getLogger(ResponseStringHttpMessageConverter.class);
public ResponseStringHttpMessageConverter() {
super(new MediaType("application","json"));
}
@Override
protected boolean supports(Class<?> clazz) {
if (clazz == String.class)
return true;
else return false;
}
@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
int content;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte [inputMessage.getBody().available()];
inputMessage.getBody().read(buffer);
baos.write(buffer);
String response = baos.toString();
baos.close();
return response;
}
@Override
protected void writeInternal(String content, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
}
}
通过这种实施,请求不再受到缓慢问题的影响。
P.S。我使用的是Spring 4.3.1.RELEASE和Spring Integration 4.3.0.RELEASE。
我认为这不是一个绝对的解决方案,因为我必须自定义两个框架组件。