Spring集成:入站网关管理慢20秒而不是@Controller管理

时间:2016-07-29 11:41:54

标签: spring spring-mvc spring-integration

我有两个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”,因为我将字节数组作为有效负载传递。
  • 远程端点(application2)具有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字节之前就会产生一个块。 否则会有一个错误,我不相信这个选项。

2 个答案:

答案 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。

我认为这不是一个绝对的解决方案,因为我必须自定义两个框架组件。