Manajemen Koneksi HttpClient

1. Ikhtisar

Pada artikel ini, kita akan membahas dasar-dasar manajemen koneksi dalam HttpClient 4.

Kami akan membahas penggunaan BasichttpClientConnectionManager dan PoolingHttpClientConnectionManager untuk menerapkan penggunaan koneksi HTTP yang aman, sesuai protokol, dan efisien.

2. BasicHttpClientConnectionManager untuk Tingkat Rendah, Koneksi Berulir Tunggal

The BasicHttpClientConnectionManager tersedia sejak HttpClient 4.3.3 sebagai implementasi sederhana dari seorang manajer koneksi HTTP. Ini digunakan untuk membuat dan mengelola satu koneksi yang hanya dapat digunakan oleh satu utas dalam satu waktu.

Contoh 2.1. Mendapatkan Permintaan Koneksi untuk Koneksi Tingkat Rendah ( HttpClientConnection )

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

The requestConnection Metode mendapat dari manajer kolam koneksi untuk khusus rute untuk terhubung ke. The rute parameter menentukan rute dari “hop proxy” ke host target, atau host target itu sendiri.

Dimungkinkan untuk menjalankan permintaan menggunakan HttpClientConnection secara langsung, tetapi perlu diingat bahwa pendekatan tingkat rendah ini bertele-tele dan sulit untuk dikelola. Koneksi tingkat rendah berguna untuk mengakses soket dan data koneksi seperti waktu tunggu dan informasi host target, tetapi untuk eksekusi standar, HttpClient adalah API yang jauh lebih mudah untuk dikerjakan.

3. Menggunakan PoolingHttpClientConnectionManager untuk Mendapatkan dan Mengelola Kumpulan Koneksi Multithread

The PoolingHttpClientConnectionManager akan membuat dan mengelola kolam koneksi untuk setiap rute atau host target kita gunakan. Ukuran default dari kumpulan koneksi bersamaan yang dapat dibuka oleh manajer adalah 2 untuk setiap rute atau host target , dan 20 untuk koneksi terbuka total . Pertama - mari kita lihat cara menyiapkan manajer koneksi ini di HttpClient sederhana:

Contoh 3.1. Menyetel PoolingHttpClientConnectionManager di HttpClient

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

Selanjutnya - mari kita lihat bagaimana pengelola koneksi yang sama dapat digunakan oleh dua HttpClients yang berjalan di dua utas berbeda:

Contoh 3.2. Menggunakan Dua HttpClients untuk Terhubung ke Satu Host Target

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 bahwa kami menggunakan implementasi utas khusus yang sangat sederhana - ini dia:

Contoh 3.3. Utas Kustom Menjalankan 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 (response.getEntity) - yang diperlukan untuk menggunakan seluruh konten respons (entitas) sehingga pengelola dapat melepaskan koneksi kembali ke kumpulan .

4. Konfigurasi Connection Manager

Default dari manajer koneksi penggabungan dipilih dengan baik tetapi - tergantung pada kasus penggunaan Anda - mungkin terlalu kecil. Jadi - mari kita lihat bagaimana kita dapat mengkonfigurasi:

  • jumlah koneksi
  • jumlah maksimum koneksi per rute (apa saja)
  • jumlah maksimum koneksi per satu rute tertentu

Contoh 4.1. Meningkatkan Jumlah Koneksi yang Dapat Dibuka dan Dikelola Melampaui Batas default

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 kita rekap API:

  • setMaxTotal (int max) : Setel jumlah maksimum dari total koneksi terbuka.
  • setDefaultMaxPerRoute (int max) : Setel jumlah maksimum koneksi bersamaan per rute, yang defaultnya adalah 2.
  • setMaxPerRoute (int max) : Setel jumlah total koneksi serentak ke rute tertentu, yang defaultnya adalah 2.

Jadi, tanpa mengubah default, kita akan mencapai batas dari pengelola koneksi dengan cukup mudah - mari kita lihat bagaimana tampilannya:

Contoh 4.2. Menggunakan Thread untuk Menjalankan Koneksi

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 bahas, batas koneksi per host adalah 2 secara default. Jadi, dalam contoh ini, kami mencoba memiliki 3 utas yang membuat 3 permintaan ke host yang sama , tetapi hanya 2 koneksi yang akan dialokasikan secara paralel.

Mari kita lihat log - kami memiliki tiga utas yang berjalan tetapi hanya 2 koneksi yang disewakan:

[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 Koneksi Keep-Alive

Mengutip HttpClient 4.3.3. referensi: "Jika Keep-Aliveheader tidak ada dalam respons, HttpClient menganggap koneksi dapat dipertahankan hidup tanpa batas." (Lihat Referensi HttpClient).

Untuk menyiasati ini, dan dapat mengelola koneksi yang mati, kita memerlukan implementasi strategi yang disesuaikan dan membangunnya ke dalam HttpClient .

Contoh 5.1. Strategi Keep Alive Kustom

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 pertama-tama akan mencoba menerapkan kebijakan Keep-Alive tuan rumah yang dinyatakan di tajuk. Jika informasi itu tidak ada di header respon itu akan membuat koneksi tetap hidup selama 5 detik.

Sekarang - mari buat klien dengan strategi kustom ini :

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

6. Ketekunan Koneksi / Penggunaan Kembali

Spesifikasi HTTP / 1.1 menyatakan bahwa koneksi dapat digunakan kembali jika belum ditutup - ini dikenal sebagai persistensi koneksi.

Setelah koneksi dilepaskan oleh pengelola, koneksi tersebut tetap terbuka untuk digunakan kembali. Saat menggunakan BasicHttpClientConnectionManager, yang hanya dapat mengatur satu koneksi, koneksi harus dilepaskan sebelum disewakan kembali:

Contoh 6.1. Penggunaan Kembali Koneksi 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 terjadi.

Pertama - perhatikan bahwa kami menggunakan koneksi level rendah terlebih dahulu, hanya agar kami memiliki kendali penuh atas kapan koneksi dilepaskan, kemudian koneksi level tinggi normal dengan HttpClient. Logika tingkat rendah yang kompleks sangat tidak relevan di sini - satu-satunya hal yang kita pedulikan adalah panggilan releaseConnection . Itu melepaskan satu-satunya koneksi yang tersedia dan memungkinkannya untuk digunakan kembali.

Kemudian, klien menjalankan permintaan GET lagi dengan sukses. Jika kita melewatkan melepaskan koneksi, kita akan mendapatkan 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 bahwa koneksi yang ada tidak ditutup, hanya dirilis dan kemudian digunakan kembali oleh permintaan kedua.

Berbeda dengan contoh di atas, The PoolingHttpClientConnectionManager memungkinkan koneksi digunakan kembali secara transparan tanpa perlu melepaskan koneksi secara implisit:

Contoh 6.2. PoolingHttpClientConnectionManager : Menggunakan Kembali Koneksi dengan Thread

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); }

Contoh di atas memiliki 10 utas, menjalankan 10 permintaan tetapi hanya berbagi 5 koneksi.

Tentu saja, contoh ini bergantung pada batas waktu Keep-Alive server . Untuk memastikan koneksi tidak mati sebelum digunakan kembali, disarankan untuk mengkonfigurasi klien dengan strategi Keep-Alive (Lihat Contoh 5.1.).

7. Mengkonfigurasi Timeout - Timeout Socket Menggunakan Connection Manager

Satu-satunya batas waktu yang dapat diatur saat manajer koneksi dikonfigurasi adalah batas waktu soket:

Contoh 7.1. Mengatur Timeout Socket ke 5 Detik

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

Untuk diskusi yang lebih mendalam tentang batas waktu di HttpClient - lihat ini.

8. Penggusuran Koneksi

Penggusuran koneksi digunakan untuk mendeteksi koneksi yang tidak aktif dan kedaluwarsa dan menutupnya ; ada dua opsi untuk melakukan ini.

  1. Mengandalkan pada HttpClien t untuk memeriksa apakah sambungan sudah usang sebelum menjalankan permintaan. Ini adalah opsi mahal yang tidak selalu dapat diandalkan.
  2. Buat utas monitor untuk menutup koneksi idle dan / atau tertutup.

Contoh 8.1. Mengatur HttpClient untuk Memeriksa Koneksi Basi

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

Contoh 8.2. Menggunakan Thread Monitor Koneksi Basi

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 kita membahas cara menggunakan HTTP Connection Management API dari HttpClient untuk menangani seluruh proses pengelolaan koneksi - mulai dari membuka dan mengalokasikannya, melalui mengelola penggunaan bersamaan oleh banyak agen, hingga akhirnya menutupnya.

Kami melihat bagaimana BasicHttpClientConnectionManager adalah solusi sederhana untuk menangani koneksi tunggal, dan bagaimana itu dapat mengelola koneksi tingkat rendah. Kami juga melihat bagaimana PoolingHttpClientConnectionManager dikombinasikan dengan HttpClient API menyediakan penggunaan koneksi HTTP yang efisien dan sesuai protokol.