# Buggy code
async def api_request(self, path, method="GET", body=None, client=None):
    """Make an authenticated API request of the proxy."""
    client = client or AsyncHTTPClient()
    url = url_path_join(self.api_url, "api/routes", path)

    if isinstance(body, dict):
        body = json.dumps(body)
    self.log.debug("Proxy: Fetching %s %s", method, url)
    req = HTTPRequest(
        url,
        method=method,
        headers={"Authorization": "token {}".format(self.auth_token)},
        body=body,
    )
    async with self.semaphore:
        result = await client.fetch(req)
        return result


# Traceback (tornado.curl_httpclient.CurlError)
[I 2020-10-20 16:48:25.540 JupyterHub base:893] User testuser-aa6aa5ea-64bb-4719-93c7-04557f25c3d5 took 2.179 seconds to start
[I 2020-10-20 16:48:25.540 JupyterHub proxy:257] Adding user testuser-aa6aa5ea-64bb-4719-93c7-04557f25c3d5 to proxy /user/testuser-aa6aa5ea-64bb-4719-93c7-04557f25c3d5/ => http://192.168.79.75:8888
[D 2020-10-20 16:48:25.540 JupyterHub proxy:765] Proxy: Fetching POST http://proxy-api:8001/api/routes/user/testuser-aa6aa5ea-64bb-4719-93c7-04557f25c3d5
[I 2020-10-20 16:48:25.541 JupyterHub log:181] 200 GET /hub/api (@10.20.0.120) 1.70ms
[E 2020-10-20 16:48:25.542 JupyterHub base:915] Failed to add testuser-aa6aa5ea-64bb-4719-93c7-04557f25c3d5 to proxy!
Traceback (most recent call last):
File "/usr/local/lib/python3.8/dist-packages/jupyterhub/handlers/base.py", line 908, in finish_user_spawn
await self.proxy.add_user(user, server_name)
File "/usr/local/lib/python3.8/dist-packages/jupyterhub/proxy.py", line 270, in add_user
await self.add_route(
File "/usr/local/lib/python3.8/dist-packages/jupyterhub/proxy.py", line 781, in add_route
await self.api_request(path, method='POST', body=body)
File "/usr/local/lib/python3.8/dist-packages/jupyterhub/proxy.py", line 773, in api_request
result = await client.fetch(req)
tornado.curl_httpclient.CurlError: HTTP 599: Send failure: Broken pipe

# Fixed code
async def api_request(self, path, method="GET", body=None, client=None):
    """Make an authenticated API request of the proxy."""
    client = client or AsyncHTTPClient()
    url = url_path_join(self.api_url, "api/routes", path)

    if isinstance(body, dict):
        body = json.dumps(body)
    self.log.debug("Proxy: Fetching %s %s", method, url)
    req = HTTPRequest(
        url,
        method=method,
        headers={"Authorization": "token {}".format(self.auth_token)},
        body=body,
        connect_timeout=3,  # default: 20s
        request_timeout=10,  # default: 20s
    )

    async def _wait_for_api_request():
        try:
            async with self.semaphore:
                return await client.fetch(req)
        except HTTPError as e:
            # Retry on potentially transient errors in CHP, typically
            # numbered 500 and up. Note that CHP isn't able to emit 429
            # errors.
            if e.code >= 500:
                self.log.warning(
                    "api_request to the proxy failed with status code {}, retrying...".format(
                        e.code
                    )
                )
                return False  # a falsy return value make exponential_backoff retry
            else:
                self.log.error("api_request to proxy failed: {0}".format(e))
                # An unhandled error here will help the hub invoke cleanup logic
                raise

    result = await exponential_backoff(
        _wait_for_api_request,
        'Repeated api_request to proxy path "{}" failed.'.format(path),
        timeout=30,
    )
    return result