# Pooling HTTP connections automatically

**URL:** <https://forum.crystal-lang.org/t/pooling-http-connections-automatically/7030>\
**Category:** Community\
**Created:** [July 21, 2024, 8:55pm UTC](https://forum.crystal-lang.org/t/pooling-http-connections-automatically/7030 "2024-07-21T20:55:37Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [July 21, 2024, 8:55pm UTC](https://forum.crystal-lang.org/t/pooling-http-connections-automatically/7030/1 "2024-07-21T20:55:37Z")

</div>

TL;DR: HTTP connection pooling is rad, API clients should do it automatically, so [I wrote a shard](https://github.com/jgaskins/http_client) for it that you can use anywhere you’d use a plain `HTTP::Client`. The idea is you can add the shard, remove 2 `:` characters from your code, and get connection pooling effectively for free.

> **Happy birthday to the ground**
>
> ![GIF showing a cake being thrown on the ground face-down. Text on the image reads "Happy birthday to the ground".](https://canada1.discourse-cdn.com/flex036/uploads/crystal_lang/original/2X/7/7e7346891ce7035bfce7efc3310a5849d3829bbe.gif)

* * *

So many shards I’ve seen that talk to third-party services over HTTP seem to either only set up a single `HTTP::Client` inside their client or use [one-shot requests](https://crystal-lang.org/api/1.13.1/HTTP/Client.html#one-shot-usage). I don’t think either of these is the right approach for general use. Using a single `HTTP::Client` instance means that the application becomes responsible for handling (or not handling) concurrent requests. Executing one-shot requests take extra time and resources — a fresh TCP connection (including [slow start](https://en.wikipedia.org/wiki/TCP_congestion_control#Slow_start)) and TLS negotiation don’t seem like a big deal at low volume, but at scale it can add a lot of friction.

> **One thing I've been doing in every HTTP API client I've been building to avoid all of these problems is to let it support a connection pool. Click here for a surprisingly incomplete list.**
>
> * * *
> 
> - [AWS](https://github.com/jgaskins/aws/blob/69ff77cd628971b05459449fd86dc884f600939e/src/client.cr#L64)
> - [Google](https://github.com/jgaskins/google/blob/cb915ded37b2a83fbf2145e3c9d1cc5011c53f92/src/client.cr#L28)
> - [Kubernetes](https://github.com/jgaskins/kubernetes/blob/d1634cd6d5c20f370209c96273df1b4529678a14/src/kubernetes.cr#L106)
> - [GitHub](https://github.com/jgaskins/github/blob/7e713190ec29308632d1d20e8342064581f2de8b/src/client.cr#L18)
> - [Elasticsearch](https://github.com/jgaskins/elasticsearch/blob/9aae4ae5c365ef88b0bad329e3f815b5348c0f9b/src/client.cr#L32)
> - [Anthropic](https://github.com/jgaskins/anthropic/blob/0702a4d35f6e0476109beb05d7d4d2fabb146326/src/client.cr#L23) (Claude AI)
> - [Voyage AI](https://github.com/jgaskins/voyage_ai/blob/0b2a580f4a37a3f6a5c9a6dd6622f881043753e5/src/voyage_ai.cr#L15) (embeddings for semantic search, RAG, and other AI)
> 
> There are several more, some of which are still vendored into apps and I haven’t yet extracted (Stripe, Postmark, MS Outlook, etc). Turns out I’ve written a lot of shards that talk to third-party services over HTTP.
> 
> * * *

To be clear, I don’t blame the authors of any API-client shards that don’t implement connection pooling. It is honestly _irritating_ to write the boilerplate manually each time and, if you click through to those shards, you may notice that I haven’t implemented it consistently across them. There hasn’t been a good abstraction to use yet.

There’s been discussion around adding connection-pooling support ([among other features](https://github.com/crystal-lang/crystal/issues/6011)) to `HTTP::Client`, but it hasn’t materialized yet, so I’ve implemented it in a shard I’m un-creatively calling [`HTTPClient`](https://github.com/jgaskins/http_client). It wraps a `DB::Pool(HTTP::Client)` (this entire pattern was the purpose of [this PR](https://github.com/crystal-lang/crystal-db/pull/131)) and [transparently delegates requests to a connection in the pool](https://github.com/jgaskins/http_client/blob/676a7f5b69ccb58edb04448d0977e7a705298217/src/http_client.cr#L55).

There’s [an example](https://github.com/jgaskins/http_client/blob/676a7f5b69ccb58edb04448d0977e7a705298217/examples/demo.cr#L22-L29) in the repo that demonstrates sending two concurrent requests (including an experiment that automatically deserializes responses)

---

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [July 21, 2024, 9:19pm UTC](https://forum.crystal-lang.org/t/pooling-http-connections-automatically/7030/2 "2024-07-21T21:19:46Z")

</div>

> [@jgaskins](#):
>
> I’m un-creatively calling [`HTTPClient`](https://github.com/jgaskins/http_client)

How about `PooledHTTPClient`? A bit more characters, but I think having two things so closely named is a bit dangerous.

I have a few projects that could use it, just on principle.

---

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [July 21, 2024, 10:42pm UTC](https://forum.crystal-lang.org/t/pooling-http-connections-automatically/7030/3 "2024-07-21T22:42:45Z")

</div>

`HTTPClient` wasn’t my first choice. I considered that exact name and several others. It was just the one that bothered me the least.

IMO, a client already pools connections if the wire protocol can’t multiplex a single TCP connection\* so calling it a pooled client would be redundant from that perspective. I acknowledge that the similarity to `HTTP::Client` isn’t great, but also I just wanted to publish it so I could stop rewriting the same irritating boilerplate. Otherwise, I would’ve been stuck on the name so long I would just never publish it.

* * *

\* The context behind my perspective is that a “client” isn’t just an implementation of the wire protocol on the client side of a client/server conversation. We have other words for that (driver, connection, etc), and we too often conflate the idea of a TCP connection with a client, which is higher-level — usually a more complete solution. With that perspective, I think `HTTP::Client` is actually a misnomer, albeit an understandable one. If I need to make 10 concurrent requests to a server, a single client can make them, even if it requires 10 TCP connections.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [November 13, 2024, 7:16am UTC](https://forum.crystal-lang.org/t/pooling-http-connections-automatically/7030/4 "2024-11-13T07:16:25Z")

</div>

I consider our stdlib builtin HTTP::Client should support this feature.

---

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [November 13, 2024, 10:49pm UTC](https://forum.crystal-lang.org/t/pooling-http-connections-automatically/7030/5 "2024-11-13T22:49:11Z")

</div>

> [@zw963](#):
>
> stdlib builtin HTTP::Client should support this feature

Agreed. I proposed extracting `DB::Pool` to its own shard a few years ago. It’s by far the most robust connection pool in the Crystal ecosystem.
