Pengurusan Sambungan HttpClient

1. Gambaran keseluruhan

Dalam artikel ini, kita akan membahas asas-asas pengurusan sambungan dalam HttpClient 4.

Kami akan merangkumi penggunaan BasichttpClientConnectionManager dan PoolingHttpClientConnectionManager untuk menerapkan penggunaan sambungan HTTP yang selamat, patuh protokol dan cekap.

2. BasicHttpClientConnectionManager untuk Sambungan Threaded Tingkatan Rendah, Rendah

The BasicHttpClientConnectionManager boleh didapati sejak HttpClient 4.3.3 pelaksanaan yang paling mudah pengurus sambungan HTTP. Ini digunakan untuk membuat dan mengurus satu sambungan yang hanya dapat digunakan oleh satu utas pada satu masa.

Contoh 2.1. Mendapat Permintaan Sambungan untuk Sambungan Tahap Rendah ( HttpClientConnection )

BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(); HttpRoute route = new HttpRoute(new HttpHost("www.baeldung.com", 80)); ConnectionRequest connRequest = connManager.requestConnection(route, null);

The requestConnection kaedah mendapat daripada pengurus kolam sambungan yang tertentu laluan sambungkan. The laluan parameter menentukan laluan dari "hop proksi" kepada tuan rumah sasaran, atau tuan rumah sasaran itu sendiri.

Adalah mungkin untuk melaksanakan permintaan menggunakan HttpClientConnection secara langsung, tetapi perlu diingat pendekatan tahap rendah ini adalah verbose dan sukar dikendalikan. Sambungan tahap rendah berguna untuk mengakses data soket dan sambungan seperti timeout dan maklumat hos sasaran, tetapi untuk pelaksanaan standard, HttpClient adalah API yang jauh lebih mudah untuk ditangani .

3. Menggunakan PoolingHttpClientConnectionManager untuk Mendapatkan dan Mengurus Kumpulan Sambungan Multithreaded

The PoolingHttpClientConnectionManager akan membuat dan menguruskan kolam sambungan bagi setiap laluan atau sasaran pihak yang kita gunakan. Ukuran lalai kumpulan sambungan serentak yang dapat dibuka oleh pengurus adalah 2 untuk setiap laluan atau host sasaran , dan 20 untuk jumlah sambungan terbuka. Pertama - mari kita lihat cara menyediakan pengurus sambungan ini pada HttpClient yang mudah:

Contoh 3.1. Menetapkan PoolingHttpClientConnectionManager pada HttpClient

HttpClientConnectionManager poolingConnManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom().setConnectionManager(poolingConnManager) .build(); client.execute(new HttpGet("/")); assertTrue(poolingConnManager.getTotalStats().getLeased() == 1);

Seterusnya - mari kita lihat bagaimana pengurus sambungan yang sama dapat digunakan oleh dua Pelanggan Http yang berjalan dalam dua utas yang berbeza:

Contoh 3.2. Menggunakan Dua Pelanggan Http untuk Menyambung ke Satu Sasaran Host Masing-masing

HttpGet get1 = new HttpGet("/"); HttpGet get2 = new HttpGet("//google.com"); PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client1 = HttpClients.custom().setConnectionManager(connManager).build(); CloseableHttpClient client2 = HttpClients.custom().setConnectionManager(connManager).build(); MultiHttpClientConnThread thread1 = new MultiHttpClientConnThread(client1, get1); MultiHttpClientConnThread thread2 = new MultiHttpClientConnThread(client2, get2); thread1.start(); thread2.start(); thread1.join(); thread2.join();

Perhatikan bahawa kami menggunakan implementasi benang khusus yang sangat sederhana - berikut:

Contoh 3.3. Benang Tersuai Melaksanakan Permintaan GET

public class MultiHttpClientConnThread extends Thread { private CloseableHttpClient client; private HttpGet get; // standard constructors public void run(){ try { HttpResponse response = client.execute(get); EntityUtils.consume(response.getEntity()); } catch (ClientProtocolException ex) { } catch (IOException ex) { } } }

Perhatikan panggilan EntityUtils.consume (Respons.getEntity) - diperlukan untuk menggunakan keseluruhan kandungan respons (entiti) supaya pengurus dapat melepaskan sambungan kembali ke kumpulan .

4. Konfigurasi Pengurus Sambungan

Lalai pengurus sambungan penyatuan dipilih dengan baik tetapi - bergantung pada kes penggunaan anda - mungkin terlalu kecil. Oleh itu - mari kita lihat bagaimana kita dapat mengkonfigurasi:

  • jumlah sambungan
  • bilangan sambungan maksimum bagi setiap (mana-mana) laluan
  • bilangan sambungan maksimum bagi satu laluan tertentu

Contoh 4.1. Meningkatkan Bilangan Sambungan yang Boleh Dibuka dan Diuruskan Melebihi Had lalai

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); connManager.setMaxTotal(5); connManager.setDefaultMaxPerRoute(4); HttpHost host = new HttpHost("www.baeldung.com", 80); connManager.setMaxPerRoute(new HttpRoute(host), 5);

Mari buat semula API:

  • setMaxTotal (int max) : Tetapkan jumlah maksimum sambungan terbuka.
  • setDefaultMaxPerRoute (int max) : Tetapkan bilangan maksimum sambungan serentak bagi setiap laluan, iaitu 2 secara lalai.
  • setMaxPerRoute (int max) : Tetapkan jumlah keseluruhan sambungan serentak ke laluan tertentu, yang secara default adalah 2.

Oleh itu, tanpa mengubah lalai, kita akan mencapai had pengurus sambungan dengan mudah - mari kita lihat seperti apa:

Contoh 4.2. Menggunakan Threads untuk Melaksanakan Sambungan

HttpGet get = new HttpGet("//www.baeldung.com"); PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom(). setConnectionManager(connManager).build(); MultiHttpClientConnThread thread1 = new MultiHttpClientConnThread(client, get); MultiHttpClientConnThread thread2 = new MultiHttpClientConnThread(client, get); MultiHttpClientConnThread thread3 = new MultiHttpClientConnThread(client, get); thread1.start(); thread2.start(); thread3.start(); thread1.join(); thread2.join(); thread3.join();

Seperti yang telah kita bincangkan, had sambungan per host adalah 2 secara lalai. Oleh itu, dalam contoh ini, kami berusaha agar 3 utas membuat 3 permintaan ke host yang sama , tetapi hanya 2 sambungan yang akan diperuntukkan secara selari.

Mari lihat log - kita mempunyai tiga utas berjalan tetapi hanya 2 sambungan yang disewa:

[Thread-0] INFO o.b.h.c.MultiHttpClientConnThread - Before - Leased Connections = 0 [Thread-1] INFO o.b.h.c.MultiHttpClientConnThread - Before - Leased Connections = 0 [Thread-2] INFO o.b.h.c.MultiHttpClientConnThread - Before - Leased Connections = 0 [Thread-2] INFO o.b.h.c.MultiHttpClientConnThread - After - Leased Connections = 2 [Thread-0] INFO o.b.h.c.MultiHttpClientConnThread - After - Leased Connections = 2

5. Strategi Tetap Hidup Bersambung

Memetik HttpClient 4.3.3. rujukan: "Jika Keep-Aliveheader tidak ada dalam respons, HttpClient menganggap koneksi dapat dijaga selama-lamanya." (Lihat Rujukan Pelanggan Http).

Untuk mengatasi ini, dan dapat menguruskan sambungan mati, kami memerlukan pelaksanaan strategi yang disesuaikan dan membinanya ke dalam HttpClient .

Contoh 5.1. Strategi Custom Keep Alive

ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { HeaderElementIterator it = new BasicHeaderElementIterator (response.headerIterator(HTTP.CONN_KEEP_ALIVE)); while (it.hasNext()) { HeaderElement he = it.nextElement(); String param = he.getName(); String value = he.getValue(); if (value != null && param.equalsIgnoreCase ("timeout")) { return Long.parseLong(value) * 1000; } } return 5 * 1000; } };

Strategi ini akan mula-mula menerapkan kebijakan Keep-Alive host yang dinyatakan dalam tajuk. Sekiranya maklumat itu tidak terdapat di tajuk respons, maklumat tersebut akan terus kekal selama 5 saat.

Sekarang - mari buat pelanggan dengan strategi tersuai ini :

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom() .setKeepAliveStrategy(myStrategy) .setConnectionManager(connManager) .build();

6. Ketekunan Sambungan / Penggunaan Semula

Spesifikasi HTTP / 1.1 menyatakan bahawa sambungan boleh digunakan semula jika belum ditutup - ini dikenali sebagai ketekunan sambungan.

Setelah sambungan dilepaskan oleh pengurus, sambungan akan tetap terbuka untuk digunakan semula. Semasa menggunakan BasicHttpClientConnectionManager, yang hanya dapat menjarakkan satu sambungan, sambungan mesti dilepaskan sebelum disewakan kembali:

Contoh 6.1. Penggunaan semula sambungan BasicHttpClientConnectionManager

BasicHttpClientConnectionManager basicConnManager = new BasicHttpClientConnectionManager(); HttpClientContext context = HttpClientContext.create(); // low level HttpRoute route = new HttpRoute(new HttpHost("www.baeldung.com", 80)); ConnectionRequest connRequest = basicConnManager.requestConnection(route, null); HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS); basicConnManager.connect(conn, route, 1000, context); basicConnManager.routeComplete(conn, route, context); HttpRequestExecutor exeRequest = new HttpRequestExecutor(); context.setTargetHost((new HttpHost("www.baeldung.com", 80))); HttpGet get = new HttpGet("//www.baeldung.com"); exeRequest.execute(get, conn, context); basicConnManager.releaseConnection(conn, null, 1, TimeUnit.SECONDS); // high level CloseableHttpClient client = HttpClients.custom() .setConnectionManager(basicConnManager) .build(); client.execute(get);

Mari kita lihat apa yang berlaku.

Pertama - perhatikan bahawa kita menggunakan sambungan tahap rendah terlebih dahulu, supaya kita mempunyai kawalan penuh apabila sambungan dilepaskan, kemudian sambungan tingkat tinggi yang normal dengan HttpClient. Logik tahap rendah yang kompleks tidak begitu relevan di sini - satu-satunya perkara yang kita pedulikan ialah panggilan ReleaseConnection . Itu melepaskan satu-satunya sambungan yang tersedia dan membolehkannya digunakan semula.

Kemudian, pelanggan melaksanakan permintaan GET sekali lagi dengan jayanya. Sekiranya kita melepaskan sambungan, kita akan mendapat IllegalStateException dari HttpClient:

java.lang.IllegalStateException: Connection is still allocated at o.a.h.u.Asserts.check(Asserts.java:34) at o.a.h.i.c.BasicHttpClientConnectionManager.getConnection (BasicHttpClientConnectionManager.java:248)

Perhatikan bahawa sambungan yang ada tidak ditutup, baru dilepaskan dan kemudian digunakan kembali oleh permintaan kedua.

In contrast to the above example, The PoolingHttpClientConnectionManager allows connection re-use transparently without the need to release a connection implicitly:

Example 6.2.PoolingHttpClientConnectionManager: Re-Using Connections with Threads

HttpGet get = new HttpGet("//echo.200please.com"); PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); connManager.setDefaultMaxPerRoute(5); connManager.setMaxTotal(5); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connManager) .build(); MultiHttpClientConnThread[] threads = new MultiHttpClientConnThread[10]; for(int i = 0; i < threads.length; i++){ threads[i] = new MultiHttpClientConnThread(client, get, connManager); } for (MultiHttpClientConnThread thread: threads) { thread.start(); } for (MultiHttpClientConnThread thread: threads) { thread.join(1000); }

The example above has 10 threads, executing 10 requests but only sharing 5 connections.

Of course, this example relies on the server's Keep-Alive timeout. To make sure the connections don't die before being re-used it is recommended to configure the client with a Keep-Alive strategy (See Example 5.1.).

7. Configuring Timeouts – Socket Timeout Using the Connection Manager

The only timeout that can be set at the time when connection manager is configured is the socket timeout:

Example 7.1. Setting Socket Timeout to 5 Seconds

HttpRoute route = new HttpRoute(new HttpHost("www.baeldung.com", 80)); PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); connManager.setSocketConfig(route.getTargetHost(),SocketConfig.custom(). setSoTimeout(5000).build());

For a more in-depth discussion of timeouts in the HttpClient – see this.

8. Connection Eviction

Connection eviction is used to detect idle and expired connections and close them; there are two options to do this.

  1. Relying on the HttpClient to check if the connection is stale before executing a request. This is an expensive option that is not always reliable.
  2. Create a monitor thread to close idle and/or closed connections.

Example 8.1. Setting the HttpClient to Check for Stale Connections

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig( RequestConfig.custom().setStaleConnectionCheckEnabled(true).build() ).setConnectionManager(connManager).build();

Example 8.2. Using a Stale Connection Monitor Thread

PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connManager).build(); IdleConnectionMonitorThread staleMonitor = new IdleConnectionMonitorThread(connManager); staleMonitor.start(); staleMonitor.join(1000);

The IdleConnectionMonitorThreadclass is listed below:

public class IdleConnectionMonitorThread extends Thread { private final HttpClientConnectionManager connMgr; private volatile boolean shutdown; public IdleConnectionMonitorThread( PoolingHttpClientConnectionManager connMgr) { super(); this.connMgr = connMgr; } @Override public void run() { try { while (!shutdown) { synchronized (this) { wait(1000); connMgr.closeExpiredConnections(); connMgr.closeIdleConnections(30, TimeUnit.SECONDS); } } } catch (InterruptedException ex) { shutdown(); } } public void shutdown() { shutdown = true; synchronized (this) { notifyAll(); } } }

9. Connection Closing

A connection can be closed gracefully (an attempt to flush the output buffer prior to closing is made), or forcefully, by calling the shutdown method (the output buffer is not flushed).

To properly close connections we need to do all of the following:

  • consume and close the response (if closeable)
  • close the client
  • close and shut down the connection manager

Example 8.1. Closing Connection and Releasing Resources

connManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connManager).build(); HttpGet get = new HttpGet("//google.com"); CloseableHttpResponse response = client.execute(get); EntityUtils.consume(response.getEntity()); response.close(); client.close(); connManager.close(); 

If the manager is shut down without connections being closed already – all connections will be closed and all resources released.

It's important to keep in mind that this will not flush any data that may have been ongoing for the existing connections.

10. Conclusion

Dalam artikel ini kami membincangkan cara menggunakan HTTP Connection Management API dari HttpClient untuk menangani keseluruhan proses menguruskan sambungan - dari membuka dan memperuntukkannya, melalui menguruskan penggunaan serentak oleh beberapa agen, hingga akhirnya menutupnya.

Kami melihat bagaimana BasicHttpClientConnectionManager adalah penyelesaian mudah untuk menangani sambungan tunggal, dan bagaimana ia dapat menguruskan sambungan tahap rendah. Kami juga melihat bagaimana PoolingHttpClientConnectionManager yang digabungkan dengan HttpClient API menyediakan penggunaan sambungan HTTP yang cekap dan sesuai dengan protokol.