We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
feature: `client.health`
### Feature request

client to have a health function:
```python
client.health()
await client.async_health()
```

### Motivation

For HTTP, would probably just need to invoke `/readyz`, and for gRPC is to invoke the `Health` rpc from `grpc.health.v1.HealthServicer`.

### Other

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/bentoml/_internal/client/http.py`
Content:
```
1 from __future__ import annotations
2 
3 import json
4 import time
5 import socket
6 import typing as t
7 import logging
8 import urllib.error
9 import urllib.request
10 from http.client import HTTPConnection
11 from urllib.parse import urlparse
12 
13 import aiohttp
14 import starlette.requests
15 import starlette.datastructures
16 
17 from . import Client
18 from .. import io_descriptors as io
19 from ..service import Service
20 from ...exceptions import RemoteException
21 from ...exceptions import BentoMLException
22 from ..configuration import get_debug_mode
23 from ..service.inference_api import InferenceAPI
24 
25 logger = logging.getLogger(__name__)
26 
27 
28 class HTTPClient(Client):
29     @staticmethod
30     def wait_until_server_ready(
31         host: str,
32         port: int,
33         timeout: int = 30,
34         check_interval: int = 1,
35         # set kwargs here to omit gRPC kwargs
36         **kwargs: t.Any,
37     ) -> None:
38         start_time = time.time()
39         status = None
40 
41         logger.debug("Waiting for host %s to be ready.", f"{host}:{port}")
42         while time.time() - start_time < timeout:
43             try:
44                 conn = HTTPConnection(host, port)
45                 conn.request("GET", "/readyz")
46                 status = conn.getresponse().status
47                 if status == 200:
48                     break
49                 else:
50                     time.sleep(check_interval)
51             except (
52                 ConnectionError,
53                 urllib.error.URLError,
54                 socket.timeout,
55                 ConnectionRefusedError,
56             ):
57                 logger.debug("Server is not ready. Retrying...")
58                 time.sleep(check_interval)
59 
60         # try to connect one more time and raise exception.
61         try:
62             conn = HTTPConnection(host, port)
63             conn.request("GET", "/readyz")
64             status = conn.getresponse().status
65             if status != 200:
66                 raise TimeoutError(
67                     f"Timed out waiting {timeout} seconds for server at '{host}:{port}' to be ready."
68                 )
69         except (
70             ConnectionError,
71             urllib.error.URLError,
72             socket.timeout,
73             ConnectionRefusedError,
74             TimeoutError,
75         ) as err:
76             logger.error("Caught exception while connecting to %s:%s:", host, port)
77             logger.error(err)
78             raise
79 
80     @classmethod
81     def from_url(cls, server_url: str, **kwargs: t.Any) -> HTTPClient:
82         server_url = server_url if "://" in server_url else "http://" + server_url
83         url_parts = urlparse(server_url)
84 
85         # TODO: SSL support
86         conn = HTTPConnection(url_parts.netloc)
87         conn.set_debuglevel(logging.DEBUG if get_debug_mode() else 0)
88         conn.request("GET", url_parts.path + "/docs.json")
89         resp = conn.getresponse()
90         if resp.status != 200:
91             raise RemoteException(
92                 f"Failed to get OpenAPI schema from the server: {resp.status} {resp.reason}:\n{resp.read()}"
93             )
94         openapi_spec = json.load(resp)
95         conn.close()
96 
97         dummy_service = Service(openapi_spec["info"]["title"])
98 
99         for route, spec in openapi_spec["paths"].items():
100             for meth_spec in spec.values():
101                 if "tags" in meth_spec and "Service APIs" in meth_spec["tags"]:
102                     if "x-bentoml-io-descriptor" not in meth_spec["requestBody"]:
103                         # TODO: better message stating min version for from_url to work
104                         raise BentoMLException(
105                             f"Malformed BentoML spec received from BentoML server {server_url}"
106                         )
107                     if "x-bentoml-io-descriptor" not in meth_spec["responses"]["200"]:
108                         raise BentoMLException(
109                             f"Malformed BentoML spec received from BentoML server {server_url}"
110                         )
111                     if "x-bentoml-name" not in meth_spec:
112                         raise BentoMLException(
113                             f"Malformed BentoML spec received from BentoML server {server_url}"
114                         )
115                     try:
116                         api = InferenceAPI(
117                             None,
118                             io.from_spec(
119                                 meth_spec["requestBody"]["x-bentoml-io-descriptor"]
120                             ),
121                             io.from_spec(
122                                 meth_spec["responses"]["200"]["x-bentoml-io-descriptor"]
123                             ),
124                             name=meth_spec["x-bentoml-name"],
125                             doc=meth_spec["description"],
126                             route=route.lstrip("/"),
127                         )
128                         dummy_service.apis[meth_spec["x-bentoml-name"]] = api
129                     except BentoMLException as e:
130                         logger.error(
131                             "Failed to instantiate client for API %s: ",
132                             meth_spec["x-bentoml-name"],
133                             e,
134                         )
135 
136         return cls(dummy_service, server_url)
137 
138     async def _call(
139         self, inp: t.Any = None, *, _bentoml_api: InferenceAPI, **kwargs: t.Any
140     ) -> t.Any:
141         # All gRPC kwargs should be poped out.
142         kwargs = {k: v for k, v in kwargs.items() if not k.startswith("_grpc_")}
143         api = _bentoml_api
144 
145         if api.multi_input:
146             if inp is not None:
147                 raise BentoMLException(
148                     f"'{api.name}' takes multiple inputs; all inputs must be passed as keyword arguments."
149                 )
150             fake_resp = await api.input.to_http_response(kwargs, None)
151         else:
152             fake_resp = await api.input.to_http_response(inp, None)
153         req_body = fake_resp.body
154 
155         async with aiohttp.ClientSession(self.server_url) as sess:
156             async with sess.post(
157                 "/" + api.route,
158                 data=req_body,
159                 headers={"content-type": fake_resp.headers["content-type"]},
160             ) as resp:
161                 if resp.status != 200:
162                     raise BentoMLException(
163                         f"Error making request: {resp.status}: {str(await resp.read())}"
164                     )
165 
166                 fake_req = starlette.requests.Request(scope={"type": "http"})
167                 headers = starlette.datastructures.Headers(headers=resp.headers)
168                 fake_req._body = await resp.read()
169                 # Request.headers sets a _headers variable. We will need to set this
170                 # value to our fake request object.
171                 fake_req._headers = headers  # type: ignore (request._headers is property)
172 
173         return await api.output.from_http_request(fake_req)
174
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/bentoml/_internal/client/http.py b/src/bentoml/_internal/client/http.py
--- a/src/bentoml/_internal/client/http.py
+++ b/src/bentoml/_internal/client/http.py
@@ -4,6 +4,7 @@
 import time
 import socket
 import typing as t
+import asyncio
 import logging
 import urllib.error
 import urllib.request
@@ -77,6 +78,14 @@
             logger.error(err)
             raise
 
+    async def async_health(self) -> t.Any:
+        async with aiohttp.ClientSession(self.server_url) as sess:
+            async with sess.get("/readyz") as resp:
+                return resp
+
+    def health(self) -> t.Any:
+        return asyncio.run(self.async_health())
+
     @classmethod
     def from_url(cls, server_url: str, **kwargs: t.Any) -> HTTPClient:
         server_url = server_url if "://" in server_url else "http://" + server_url

{"golden_diff": "diff --git a/src/bentoml/_internal/client/http.py b/src/bentoml/_internal/client/http.py\n--- a/src/bentoml/_internal/client/http.py\n+++ b/src/bentoml/_internal/client/http.py\n@@ -4,6 +4,7 @@\n import time\n import socket\n import typing as t\n+import asyncio\n import logging\n import urllib.error\n import urllib.request\n@@ -77,6 +78,14 @@\n             logger.error(err)\n             raise\n \n+    async def async_health(self) -> t.Any:\n+        async with aiohttp.ClientSession(self.server_url) as sess:\n+            async with sess.get(\"/readyz\") as resp:\n+                return resp\n+\n+    def health(self) -> t.Any:\n+        return asyncio.run(self.async_health())\n+\n     @classmethod\n     def from_url(cls, server_url: str, **kwargs: t.Any) -> HTTPClient:\n         server_url = server_url if \"://\" in server_url else \"http://\" + server_url\n", "issue": "feature: `client.health`\n### Feature request\n\nclient to have a health function:\r\n```python\r\nclient.health()\r\nawait client.async_health()\r\n```\n\n### Motivation\n\nFor HTTP, would probably just need to invoke `/readyz`, and for gRPC is to invoke the `Health` rpc from `grpc.health.v1.HealthServicer`.\n\n### Other\n\n_No response_\n", "before_files": [{"content": "from __future__ import annotations\n\nimport json\nimport time\nimport socket\nimport typing as t\nimport logging\nimport urllib.error\nimport urllib.request\nfrom http.client import HTTPConnection\nfrom urllib.parse import urlparse\n\nimport aiohttp\nimport starlette.requests\nimport starlette.datastructures\n\nfrom . import Client\nfrom .. import io_descriptors as io\nfrom ..service import Service\nfrom ...exceptions import RemoteException\nfrom ...exceptions import BentoMLException\nfrom ..configuration import get_debug_mode\nfrom ..service.inference_api import InferenceAPI\n\nlogger = logging.getLogger(__name__)\n\n\nclass HTTPClient(Client):\n    @staticmethod\n    def wait_until_server_ready(\n        host: str,\n        port: int,\n        timeout: int = 30,\n        check_interval: int = 1,\n        # set kwargs here to omit gRPC kwargs\n        **kwargs: t.Any,\n    ) -> None:\n        start_time = time.time()\n        status = None\n\n        logger.debug(\"Waiting for host %s to be ready.\", f\"{host}:{port}\")\n        while time.time() - start_time < timeout:\n            try:\n                conn = HTTPConnection(host, port)\n                conn.request(\"GET\", \"/readyz\")\n                status = conn.getresponse().status\n                if status == 200:\n                    break\n                else:\n                    time.sleep(check_interval)\n            except (\n                ConnectionError,\n                urllib.error.URLError,\n                socket.timeout,\n                ConnectionRefusedError,\n            ):\n                logger.debug(\"Server is not ready. Retrying...\")\n                time.sleep(check_interval)\n\n        # try to connect one more time and raise exception.\n        try:\n            conn = HTTPConnection(host, port)\n            conn.request(\"GET\", \"/readyz\")\n            status = conn.getresponse().status\n            if status != 200:\n                raise TimeoutError(\n                    f\"Timed out waiting {timeout} seconds for server at '{host}:{port}' to be ready.\"\n                )\n        except (\n            ConnectionError,\n            urllib.error.URLError,\n            socket.timeout,\n            ConnectionRefusedError,\n            TimeoutError,\n        ) as err:\n            logger.error(\"Caught exception while connecting to %s:%s:\", host, port)\n            logger.error(err)\n            raise\n\n    @classmethod\n    def from_url(cls, server_url: str, **kwargs: t.Any) -> HTTPClient:\n        server_url = server_url if \"://\" in server_url else \"http://\" + server_url\n        url_parts = urlparse(server_url)\n\n        # TODO: SSL support\n        conn = HTTPConnection(url_parts.netloc)\n        conn.set_debuglevel(logging.DEBUG if get_debug_mode() else 0)\n        conn.request(\"GET\", url_parts.path + \"/docs.json\")\n        resp = conn.getresponse()\n        if resp.status != 200:\n            raise RemoteException(\n                f\"Failed to get OpenAPI schema from the server: {resp.status} {resp.reason}:\\n{resp.read()}\"\n            )\n        openapi_spec = json.load(resp)\n        conn.close()\n\n        dummy_service = Service(openapi_spec[\"info\"][\"title\"])\n\n        for route, spec in openapi_spec[\"paths\"].items():\n            for meth_spec in spec.values():\n                if \"tags\" in meth_spec and \"Service APIs\" in meth_spec[\"tags\"]:\n                    if \"x-bentoml-io-descriptor\" not in meth_spec[\"requestBody\"]:\n                        # TODO: better message stating min version for from_url to work\n                        raise BentoMLException(\n                            f\"Malformed BentoML spec received from BentoML server {server_url}\"\n                        )\n                    if \"x-bentoml-io-descriptor\" not in meth_spec[\"responses\"][\"200\"]:\n                        raise BentoMLException(\n                            f\"Malformed BentoML spec received from BentoML server {server_url}\"\n                        )\n                    if \"x-bentoml-name\" not in meth_spec:\n                        raise BentoMLException(\n                            f\"Malformed BentoML spec received from BentoML server {server_url}\"\n                        )\n                    try:\n                        api = InferenceAPI(\n                            None,\n                            io.from_spec(\n                                meth_spec[\"requestBody\"][\"x-bentoml-io-descriptor\"]\n                            ),\n                            io.from_spec(\n                                meth_spec[\"responses\"][\"200\"][\"x-bentoml-io-descriptor\"]\n                            ),\n                            name=meth_spec[\"x-bentoml-name\"],\n                            doc=meth_spec[\"description\"],\n                            route=route.lstrip(\"/\"),\n                        )\n                        dummy_service.apis[meth_spec[\"x-bentoml-name\"]] = api\n                    except BentoMLException as e:\n                        logger.error(\n                            \"Failed to instantiate client for API %s: \",\n                            meth_spec[\"x-bentoml-name\"],\n                            e,\n                        )\n\n        return cls(dummy_service, server_url)\n\n    async def _call(\n        self, inp: t.Any = None, *, _bentoml_api: InferenceAPI, **kwargs: t.Any\n    ) -> t.Any:\n        # All gRPC kwargs should be poped out.\n        kwargs = {k: v for k, v in kwargs.items() if not k.startswith(\"_grpc_\")}\n        api = _bentoml_api\n\n        if api.multi_input:\n            if inp is not None:\n                raise BentoMLException(\n                    f\"'{api.name}' takes multiple inputs; all inputs must be passed as keyword arguments.\"\n                )\n            fake_resp = await api.input.to_http_response(kwargs, None)\n        else:\n            fake_resp = await api.input.to_http_response(inp, None)\n        req_body = fake_resp.body\n\n        async with aiohttp.ClientSession(self.server_url) as sess:\n            async with sess.post(\n                \"/\" + api.route,\n                data=req_body,\n                headers={\"content-type\": fake_resp.headers[\"content-type\"]},\n            ) as resp:\n                if resp.status != 200:\n                    raise BentoMLException(\n                        f\"Error making request: {resp.status}: {str(await resp.read())}\"\n                    )\n\n                fake_req = starlette.requests.Request(scope={\"type\": \"http\"})\n                headers = starlette.datastructures.Headers(headers=resp.headers)\n                fake_req._body = await resp.read()\n                # Request.headers sets a _headers variable. We will need to set this\n                # value to our fake request object.\n                fake_req._headers = headers  # type: ignore (request._headers is property)\n\n        return await api.output.from_http_request(fake_req)\n", "path": "src/bentoml/_internal/client/http.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport json\nimport time\nimport socket\nimport typing as t\nimport asyncio\nimport logging\nimport urllib.error\nimport urllib.request\nfrom http.client import HTTPConnection\nfrom urllib.parse import urlparse\n\nimport aiohttp\nimport starlette.requests\nimport starlette.datastructures\n\nfrom . import Client\nfrom .. import io_descriptors as io\nfrom ..service import Service\nfrom ...exceptions import RemoteException\nfrom ...exceptions import BentoMLException\nfrom ..configuration import get_debug_mode\nfrom ..service.inference_api import InferenceAPI\n\nlogger = logging.getLogger(__name__)\n\n\nclass HTTPClient(Client):\n    @staticmethod\n    def wait_until_server_ready(\n        host: str,\n        port: int,\n        timeout: int = 30,\n        check_interval: int = 1,\n        # set kwargs here to omit gRPC kwargs\n        **kwargs: t.Any,\n    ) -> None:\n        start_time = time.time()\n        status = None\n\n        logger.debug(\"Waiting for host %s to be ready.\", f\"{host}:{port}\")\n        while time.time() - start_time < timeout:\n            try:\n                conn = HTTPConnection(host, port)\n                conn.request(\"GET\", \"/readyz\")\n                status = conn.getresponse().status\n                if status == 200:\n                    break\n                else:\n                    time.sleep(check_interval)\n            except (\n                ConnectionError,\n                urllib.error.URLError,\n                socket.timeout,\n                ConnectionRefusedError,\n            ):\n                logger.debug(\"Server is not ready. Retrying...\")\n                time.sleep(check_interval)\n\n        # try to connect one more time and raise exception.\n        try:\n            conn = HTTPConnection(host, port)\n            conn.request(\"GET\", \"/readyz\")\n            status = conn.getresponse().status\n            if status != 200:\n                raise TimeoutError(\n                    f\"Timed out waiting {timeout} seconds for server at '{host}:{port}' to be ready.\"\n                )\n        except (\n            ConnectionError,\n            urllib.error.URLError,\n            socket.timeout,\n            ConnectionRefusedError,\n            TimeoutError,\n        ) as err:\n            logger.error(\"Caught exception while connecting to %s:%s:\", host, port)\n            logger.error(err)\n            raise\n\n    async def async_health(self) -> t.Any:\n        async with aiohttp.ClientSession(self.server_url) as sess:\n            async with sess.get(\"/readyz\") as resp:\n                return resp\n\n    def health(self) -> t.Any:\n        return asyncio.run(self.async_health())\n\n    @classmethod\n    def from_url(cls, server_url: str, **kwargs: t.Any) -> HTTPClient:\n        server_url = server_url if \"://\" in server_url else \"http://\" + server_url\n        url_parts = urlparse(server_url)\n\n        # TODO: SSL support\n        conn = HTTPConnection(url_parts.netloc)\n        conn.set_debuglevel(logging.DEBUG if get_debug_mode() else 0)\n        conn.request(\"GET\", url_parts.path + \"/docs.json\")\n        resp = conn.getresponse()\n        if resp.status != 200:\n            raise RemoteException(\n                f\"Failed to get OpenAPI schema from the server: {resp.status} {resp.reason}:\\n{resp.read()}\"\n            )\n        openapi_spec = json.load(resp)\n        conn.close()\n\n        dummy_service = Service(openapi_spec[\"info\"][\"title\"])\n\n        for route, spec in openapi_spec[\"paths\"].items():\n            for meth_spec in spec.values():\n                if \"tags\" in meth_spec and \"Service APIs\" in meth_spec[\"tags\"]:\n                    if \"x-bentoml-io-descriptor\" not in meth_spec[\"requestBody\"]:\n                        # TODO: better message stating min version for from_url to work\n                        raise BentoMLException(\n                            f\"Malformed BentoML spec received from BentoML server {server_url}\"\n                        )\n                    if \"x-bentoml-io-descriptor\" not in meth_spec[\"responses\"][\"200\"]:\n                        raise BentoMLException(\n                            f\"Malformed BentoML spec received from BentoML server {server_url}\"\n                        )\n                    if \"x-bentoml-name\" not in meth_spec:\n                        raise BentoMLException(\n                            f\"Malformed BentoML spec received from BentoML server {server_url}\"\n                        )\n                    try:\n                        api = InferenceAPI(\n                            None,\n                            io.from_spec(\n                                meth_spec[\"requestBody\"][\"x-bentoml-io-descriptor\"]\n                            ),\n                            io.from_spec(\n                                meth_spec[\"responses\"][\"200\"][\"x-bentoml-io-descriptor\"]\n                            ),\n                            name=meth_spec[\"x-bentoml-name\"],\n                            doc=meth_spec[\"description\"],\n                            route=route.lstrip(\"/\"),\n                        )\n                        dummy_service.apis[meth_spec[\"x-bentoml-name\"]] = api\n                    except BentoMLException as e:\n                        logger.error(\n                            \"Failed to instantiate client for API %s: \",\n                            meth_spec[\"x-bentoml-name\"],\n                            e,\n                        )\n\n        return cls(dummy_service, server_url)\n\n    async def _call(\n        self, inp: t.Any = None, *, _bentoml_api: InferenceAPI, **kwargs: t.Any\n    ) -> t.Any:\n        # All gRPC kwargs should be poped out.\n        kwargs = {k: v for k, v in kwargs.items() if not k.startswith(\"_grpc_\")}\n        api = _bentoml_api\n\n        if api.multi_input:\n            if inp is not None:\n                raise BentoMLException(\n                    f\"'{api.name}' takes multiple inputs; all inputs must be passed as keyword arguments.\"\n                )\n            fake_resp = await api.input.to_http_response(kwargs, None)\n        else:\n            fake_resp = await api.input.to_http_response(inp, None)\n        req_body = fake_resp.body\n\n        async with aiohttp.ClientSession(self.server_url) as sess:\n            async with sess.post(\n                \"/\" + api.route,\n                data=req_body,\n                headers={\"content-type\": fake_resp.headers[\"content-type\"]},\n            ) as resp:\n                if resp.status != 200:\n                    raise BentoMLException(\n                        f\"Error making request: {resp.status}: {str(await resp.read())}\"\n                    )\n\n                fake_req = starlette.requests.Request(scope={\"type\": \"http\"})\n                headers = starlette.datastructures.Headers(headers=resp.headers)\n                fake_req._body = await resp.read()\n                # Request.headers sets a _headers variable. We will need to set this\n                # value to our fake request object.\n                fake_req._headers = headers  # type: ignore (request._headers is property)\n\n        return await api.output.from_http_request(fake_req)\n", "path": "src/bentoml/_internal/client/http.py"}]}