RestEasy客户端:无效使用BasicClientConnManager:仍然分配了连接

时间:2014-03-13 23:12:16

标签: resteasy apache-httpclient-4.x

我正在使用RestEasy客户端从Web服务器检索实体列表。这是我的代码:

@ApplicationScoped
public class RestHttpClient {
    private ResteasyClient client;

    @Inject
    private ObjectMapper mapper;

    @PostConstruct
    public void initialize() {
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(params, 5000);
        HttpConnectionParams.setSoTimeout(params, 5000);
        HttpClient httpClient = new DefaultHttpClient(params);
        this.client = new ResteasyClientBuilder().httpEngine(new ApacheHttpClient4Engine(httpClient)).build();
    }

    public <E> List<E> getList(final Class<E> resultClass, final String path,
            MultivaluedMap<String, Object> queryParams) {
        ResteasyWebTarget target = this.client.target(path);
        Response response = null;
        try {
            response = target.queryParams(queryParams).request().get();
            String jsonString = response.readEntity(String.class);
            TypeFactory typeFactory = TypeFactory.defaultInstance();
            List<E> list = this.mapper.readValue(
                    jsonString, typeFactory.constructCollectionType(ArrayList.class, resultClass));
            return list;
        } catch (Exception e) {
            // Handle exception
        } finally {
            if (response != null)
                response.close();
        }

        return null;
    }
}

它运行正常,但是...如果我快速连续多次调用getList()方法,有时我收到错误“无效使用BasicClientConnManager:连接仍然分配”。我可以一遍又一遍地进行相同的调用序列,并且它至少有90%的时间都有效,因此它似乎是一种竞争条件。我正在关闭finally块中的Response对象,这应该足以释放所有资源,但显然它不是。我还需要做些什么来确保连接被释放?我在网上找到了一些答案,但它们要么太旧,要么不是RestEasy特有的。我正在使用resteasy-client 3.0.4.Final。

1 个答案:

答案 0 :(得分:9)

我猜你只有一个类RestHttpClient的实例,并且所有线程/请求都使用相同的对象。

默认的ResteasyClientBuilder不使用连接池。这意味着您一次只能有一个并行连接。在您第二次使用ResteasyClient之前需要返回一个请求(错误消息“仍然分配连接”)。您可以通过增加连接池大小来避免这种情况:

ResteasyClientBuilder clientBuilder = new ResteasyClientBuilder();
clientBuilder = clientBuilder.connectionPoolSize( 20 );
ResteasyWebTarget target = clientBuilder.build().target( "http://your.host" );

我正在使用以下RestClientFactory来设置新客户端。它为您提供原始响应的调试输出,指定密钥库(客户端ssl证书所需),连接池和使用代理的选项。

public class RestClientFactory {

    public static class Options {
        private final String baseUri;
        private final String proxyHostname;
        private final String proxyPort;
        private final String keystore;
        private final String keystorePassword;
        private final String connectionPoolSize;
        private final String connectionTTL;

        public Options(String baseUri, String proxyHostname, String proxyPort) {
            this.baseUri = baseUri;
            this.proxyHostname = proxyHostname;
            this.proxyPort = proxyPort;
            this.connectionPoolSize = "100";
            this.connectionTTL = "500";
            this.keystore = System.getProperty( "javax.net.ssl.keyStore" );
            this.keystorePassword = System.getProperty( "javax.net.ssl.keyStorePassword" );
        }

        public Options(String baseUri, String proxyHostname, String proxyPort, String keystore, String keystorePassword, String connectionPoolSize, String connectionTTL) {
            this.baseUri = baseUri;
            this.proxyHostname = proxyHostname;
            this.proxyPort = proxyPort;
            this.connectionPoolSize = connectionPoolSize;
            this.connectionTTL = connectionTTL;
            this.keystore = keystore;
            this.keystorePassword = keystorePassword;
        }
    }

    private static Logger log = LoggerFactory.getLogger( RestClientFactory.class );

    public static <T> T createClient(Options options, Class<T> proxyInterface) throws Exception {
        log.info( "creating ClientBuilder using options {}", ReflectionToStringBuilder.toString( options ) );

        ResteasyClientBuilder clientBuilder = new ResteasyClientBuilder();

        ResteasyProviderFactory providerFactory = new ResteasyProviderFactory();
        RegisterBuiltin.register( providerFactory );
        providerFactory.getClientReaderInterceptorRegistry().registerSingleton( new ReaderInterceptor() {
            @Override
            public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
                if (log.isDebugEnabled()) {
                    InputStream is = context.getInputStream();
                    String responseBody = IOUtils.toString( is );

                    log.debug( "received response:\n{}\n\n", responseBody );

                    context.setInputStream( new ByteArrayInputStream( responseBody.getBytes() ) );
                }
                return context.proceed();
            }
        } );
        clientBuilder.providerFactory( providerFactory );

        if (StringUtils.isNotBlank( options.proxyHostname ) && StringUtils.isNotBlank( options.proxyPort )) {
            clientBuilder = clientBuilder.defaultProxy( options.proxyHostname, Integer.parseInt( options.proxyPort ) );
        }

        // why the fuck do you have to specify the keystore with RestEasy?
        // not setting the keystore will result in not using the global one
        if ((StringUtils.isNotBlank( options.keystore )) && (StringUtils.isNotBlank( options.keystorePassword ))) {
            KeyStore ks;
            ks = KeyStore.getInstance( KeyStore.getDefaultType() );
            FileInputStream fis = new FileInputStream( options.keystore );
            ks.load( fis, options.keystorePassword.toCharArray() );
            fis.close();
            clientBuilder = clientBuilder.keyStore( ks, options.keystorePassword );
            // Not catching these exceptions on purpose
        }

        if (StringUtils.isNotBlank( options.connectionPoolSize )) {
            clientBuilder = clientBuilder.connectionPoolSize( Integer.parseInt( options.connectionPoolSize ) );
        }

        if (StringUtils.isNotBlank( options.connectionTTL )) {
            clientBuilder = clientBuilder.connectionTTL( Long.parseLong( options.connectionTTL ), TimeUnit.MILLISECONDS );
        }

        ResteasyWebTarget target = clientBuilder.build().target( options.baseUri );

        return target.proxy( proxyInterface );
    }
}

示例客户端界面:

public interface SimpleClient
{
   @GET
   @Path("basic")
   @Produces("text/plain")
   String getBasic();
}

创建客户端:

SimpleClient client = RestClientFactory.createClient( 
    new RestClientFactory.Options(
        "https://your.service.host",
        "proxyhost",
        "8080",
        "keystore.jks",
        "changeit",
        "20",
        "500"
    )
     ,SimpleClient.class
);

另见: http://docs.jboss.org/resteasy/docs/3.0-beta-3/userguide/html/RESTEasy_Client_Framework.html#d4e2049