【问题标题】:Resteasy Client keeping connection allocated after a method throws an ExceptionResteasy客户端在方法抛出异常后保持连接分配
【发布时间】:2016-10-31 13:18:19
【问题描述】:

我目前正在使用 TJWSEmbeddedJaxrsServer 来帮助我进行 RESTful API 测试(使用 Resteasy 创建),它运行良好。 但是当任何被调用的方法抛出异常时就会出现问题:Reasteasy 客户端“丢失”并仍然保持连接,不允许其他测试方法调用 RESTful 服务。即使您实例化一个可以解包异常并在嵌入式服务器中使用它的提供程序,也会发生这种情况。

有人可以帮帮我吗?

要模拟问题,其实很简单:

  1. 下载 Mark Paluch 在https://github.com/mp911de/rest-api-test 中提供的示例
  2. 把测试类改成这样:

公共类 InMemoryRestTest {

@Path("/myresource")
public static class MyResource {

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_XML)
    public MyModel createMyModel(int number) throws Exception {
        // supose this is a Business exception
        throw new Exception("Test");
    }

}

public static MyResource sut = new MyResource();
public static InMemoryRestServer server;

@BeforeClass
public static void beforeClass() throws Exception {
    server = InMemoryRestServer.create(sut);
}

@AfterClass
public static void afterClass() throws Exception {
    server.close();
}

@Test
public void postSimpleBody() throws Exception {
    // This one throws the "Exception" exception and passes     
    Response response = server.newRequest("/myresource").request().buildPost(Entity.text("42")).invoke();
    assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
}

@Test
public void postAnother() throws Exception {
    // This one fails with "Invalid use of BasicClientConnManager: connection still allocated."
    Response response = server.newRequest("/myresource").request().buildPost(Entity.text("20")).invoke();
    assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
}

}

瞧!当“postAnother()”的测试运行时,会出现以下错误:

javax.ws.rs.ProcessingException: Unable to invoke request
    at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:287)
    at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:407)
    at biz.paluch.rest.test.InMemoryRestTest.postSimpleBody(InMemoryRestTest.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.IllegalStateException: Invalid use of BasicClientConnManager: connection still allocated.
Make sure to release the connection before allocating another one.
    at org.apache.http.impl.conn.BasicClientConnectionManager.getConnection(BasicClientConnectionManager.java:162)
    at org.apache.http.impl.conn.BasicClientConnectionManager$1.getConnection(BasicClientConnectionManager.java:139)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:456)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
    at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:283)
    ... 27 more

【问题讨论】:

    标签: java web-services resteasy


    【解决方案1】:

    默认的 ResteasyClientBuilder 不使用连接池,这意味着您一次只能有一个并行连接。 因此,您必须确保关闭您的响应(即 response.close())以释放(唯一的)连接。

    在此处查看响应实现: https://github.com/resteasy/Resteasy/blob/master/resteasy-client/src/main/java/org/jboss/resteasy/client/jaxrs/internal/ClientResponse.java

    public void close()
       {
          if (isClosed()) return;
          try {
             isClosed = true;
             releaseConnection(); // <= HERE!
          }
          catch (Exception e) {
             throw new ProcessingException(e);
          }
       }
    

    所以以下应该可以解决您的问题:

    @Test
    public void postBody() throws Exception {          
        Response response = null;
        try {
            response = server.newRequest("...").request().buildPost(Entity.text("...")).invoke();           
        } finally {
            response.close();
        }   
    }
    

    问候,

    伯纳德。

    【讨论】:

    • 是的,这解决了问题。必须更改一些代码,因为在关闭响应后,调用“response.readEntity()”会抛出异常,但我设法在关闭响应之前捕获了实体。谢谢!
    • 非常感谢,我试图关闭连接但是当我尝试它时,我无法使用我的其他方法。但是当我阅读您的回复时,我意识到我需要关闭我的回复。
    【解决方案2】:

    您需要配置 ResteasyClientBuilder 以使用连接池。修改InMemoryRestServer类的withDefaults()方法如下

    //this.resteasyClient = new ResteasyrestEasyClientBuilder().build();
    ResteasyrestEasyClientBuilder restEasyClientBuilder = new ResteasyrestEasyClientBuilder();
    restEasyClientBuilder = restEasyClientBuilder.connectionPoolSize(20);
    this.resteasyClient = restEasyClientBuilder.build();
    

    这样你的两个测试用例都应该毫无例外地运行(虽然两者似乎都抛出了 AssertionError)

    【讨论】:

    • 好的,暂时解决了。这允许在嵌入式服务器停止响应之前发生 20 个异常。但我真正需要的是一种在发生异常后释放连接的方法。你知道我怎样才能做到这一点吗?谢谢!
    • 我刚刚做了“.connectionPoolSize(1)”,这似乎解决了它。我用同一个客户端构建了一个包含 1000 次调用的循环,但问题没有出现。谢谢!
    【解决方案3】:

    在 Java SE 7 及更高版本中,更简洁的方法是对资源使用 try。示例 sn-p 如下:

    try(response = server.newRequest("...").request().buildPost(Entity.text("...")).invoke())
    { 
       /* response handling logic */ 
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-07
      • 2015-08-18
      • 1970-01-01
      • 1970-01-01
      • 2018-04-05
      • 2013-04-16
      相关资源
      最近更新 更多