diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py
index 598b5e0..23c021d 100644
--- a/uvicorn/protocols/http/h11_impl.py
+++ b/uvicorn/protocols/http/h11_impl.py
@@ -361,6 +361,7 @@ class RequestResponseCycle:
         # Connection state
         self.disconnected = False
         self.keep_alive = True
+        self.waiting_for_100_continue = conn.they_are_waiting_for_100_continue
 
         # Request state
         self.body = b""
@@ -433,6 +434,7 @@ class RequestResponseCycle:
                 raise RuntimeError(msg % message_type)
 
             self.response_started = True
+            self.waiting_for_100_continue = False
 
             status_code = message["status"]
             headers = DEFAULT_HEADERS + message.get("headers", [])
@@ -465,7 +467,7 @@ class RequestResponseCycle:
             more_body = message.get("more_body", False)
 
             # Write response body
-            if self.scope['method'] == "HEAD":
+            if self.scope["method"] == "HEAD":
                 event = h11.Data()
             else:
                 event = h11.Data(data=body)
@@ -492,6 +494,14 @@ class RequestResponseCycle:
             self.on_response()
 
     async def receive(self):
+        if self.waiting_for_100_continue and not self.transport.is_closing():
+            event = h11.InformationalResponse(
+                status_code=100, headers=[], reason="Continue"
+            )
+            output = self.conn.send(event)
+            self.transport.write(output)
+            self.waiting_for_100_continue = False
+
         self.flow.resume_reading()
         await self.message_event.wait()
         self.message_event.clear()
diff --git a/uvicorn/protocols/http/httptools_impl.py b/uvicorn/protocols/http/httptools_impl.py
index 8a593fc..cc40fa6 100644
--- a/uvicorn/protocols/http/httptools_impl.py
+++ b/uvicorn/protocols/http/httptools_impl.py
@@ -144,6 +144,7 @@ class HttpToolsProtocol(asyncio.Protocol):
         # Per-request state
         self.scope = None
         self.headers = None
+        self.expect_100_continue = False
         self.cycle = None
         self.message_event = asyncio.Event()
 
@@ -196,6 +197,7 @@ class HttpToolsProtocol(asyncio.Protocol):
     def on_url(self, url):
         method = self.parser.get_method()
         parsed_url = httptools.parse_url(url)
+        self.expect_100_continue = False
         self.headers = []
         self.scope = {
             "type": "http",
@@ -211,7 +213,10 @@ class HttpToolsProtocol(asyncio.Protocol):
         }
 
     def on_header(self, name: bytes, value: bytes):
-        self.headers.append((name.lower(), value))
+        name = name.lower()
+        if name == b"expect" and value.lower() == b"100-continue":
+            self.expect_100_continue = True
+        self.headers.append((name, value))
 
     def on_headers_complete(self):
         http_version = self.parser.get_http_version()
@@ -242,6 +247,7 @@ class HttpToolsProtocol(asyncio.Protocol):
             flow=self.flow,
             logger=self.logger,
             message_event=self.message_event,
+            expect_100_continue=self.expect_100_continue,
             on_response=self.on_response_complete,
         )
         if existing_cycle is None or existing_cycle.response_complete:
@@ -337,7 +343,16 @@ class HttpToolsProtocol(asyncio.Protocol):
 
 
 class RequestResponseCycle:
-    def __init__(self, scope, transport, flow, logger, message_event, on_response):
+    def __init__(
+        self,
+        scope,
+        transport,
+        flow,
+        logger,
+        message_event,
+        expect_100_continue,
+        on_response,
+    ):
         self.scope = scope
         self.transport = transport
         self.flow = flow
@@ -348,6 +363,7 @@ class RequestResponseCycle:
         # Connection state
         self.disconnected = False
         self.keep_alive = True
+        self.waiting_for_100_continue = expect_100_continue
 
         # Request state
         self.body = b""
@@ -420,6 +436,7 @@ class RequestResponseCycle:
                 raise RuntimeError(msg % message_type)
 
             self.response_started = True
+            self.waiting_for_100_continue = False
 
             status_code = message["status"]
             headers = message.get("headers", [])
@@ -467,7 +484,7 @@ class RequestResponseCycle:
             more_body = message.get("more_body", False)
 
             # Write response body
-            if self.scope['method'] == "HEAD":
+            if self.scope["method"] == "HEAD":
                 self.expected_content_length = 0
             elif self.chunked_encoding:
                 content = [b"%x\r\n" % len(body), body, b"\r\n"]
@@ -497,6 +514,10 @@ class RequestResponseCycle:
             raise RuntimeError(msg % message_type)
 
     async def receive(self):
+        if self.waiting_for_100_continue and not self.transport.is_closing():
+            self.transport.write(b"HTTP/1.1 100 Continue\r\n")
+            self.waiting_for_100_continue = False
+
         self.flow.resume_reading()
         await self.message_event.wait()
         self.message_event.clear()

Support `Expect: 100-Continue`
* Deal with `Expect: 100-Continue` headers gracefully, by only sending if the request body is read prior to sending the response headers.
diff --git a/tests/protocols/test_http.py b/tests/protocols/test_http.py
index 4ec0a9b..0a4f1d6 100644
--- a/tests/protocols/test_http.py
+++ b/tests/protocols/test_http.py
@@ -625,3 +625,61 @@ def test_shutdown_during_idle(protocol_cls):
     protocol.shutdown()
     assert protocol.transport.buffer == b""
     assert protocol.transport.is_closing()
+
+
+@pytest.mark.parametrize("protocol_cls", [HttpToolsProtocol, H11Protocol])
+def test_100_continue_sent_when_body_consumed(protocol_cls):
+    class App:
+        def __init__(self, scope):
+            self.scope = scope
+
+        async def __call__(self, receive, send):
+            body = b""
+            more_body = True
+            while more_body:
+                message = await receive()
+                body += message.get("body", b"")
+                more_body = message.get("more_body", False)
+            response = Response(b"Body: " + body, media_type="text/plain")
+            await response(receive, send)
+
+    protocol = get_connected_protocol(App, protocol_cls)
+    EXPECT_100_REQUEST = b"\r\n".join(
+        [
+            b"POST / HTTP/1.1",
+            b"Host: example.org",
+            b"Expect: 100-continue",
+            b"Content-Type: application/json",
+            b"Content-Length: 18",
+            b"",
+            b'{"hello": "world"}',
+        ]
+    )
+    protocol.data_received(EXPECT_100_REQUEST)
+    protocol.loop.run_one()
+    assert b"HTTP/1.1 100 Continue" in protocol.transport.buffer
+    assert b"HTTP/1.1 200 OK" in protocol.transport.buffer
+    assert b'Body: {"hello": "world"}' in protocol.transport.buffer
+
+
+@pytest.mark.parametrize("protocol_cls", [HttpToolsProtocol, H11Protocol])
+def test_100_continue_not_sent_when_body_not_consumed(protocol_cls):
+    def app(scope):
+        return Response(b"", status_code=204)
+
+    protocol = get_connected_protocol(app, protocol_cls)
+    EXPECT_100_REQUEST = b"\r\n".join(
+        [
+            b"POST / HTTP/1.1",
+            b"Host: example.org",
+            b"Expect: 100-continue",
+            b"Content-Type: application/json",
+            b"Content-Length: 18",
+            b"",
+            b'{"hello": "world"}',
+        ]
+    )
+    protocol.data_received(EXPECT_100_REQUEST)
+    protocol.loop.run_one()
+    assert b"HTTP/1.1 100 Continue" not in protocol.transport.buffer
+    assert b"HTTP/1.1 204 No Content" in protocol.transport.buffer

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov requests codecov
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
click==8.0.4
codecov==2.1.13
coverage==6.2
dataclasses==0.8
h11==0.13.0
httptools==0.6.0
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
requests==2.27.1
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
-e git+https://github.com/encode/uvicorn.git@fa5185f348aa3be814f986abdec967d9de4a57f3#egg=uvicorn
uvloop==0.14.0
websockets==9.1
zipp==3.6.0

name: uvicorn
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - click==8.0.4
      - codecov==2.1.13
      - coverage==6.2
      - dataclasses==0.8
      - h11==0.13.0
      - httptools==0.6.0
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - requests==2.27.1
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - uvloop==0.14.0
      - websockets==9.1
      - zipp==3.6.0
prefix: /opt/conda/envs/uvicorn

tests/protocols/test_http.py::test_100_continue_sent_when_body_consumed[HttpToolsProtocol] tests/protocols/test_http.py::test_100_continue_sent_when_body_consumed[H11Protocol]
tests/protocols/test_http.py::test_get_request[HttpToolsProtocol] tests/protocols/test_http.py::test_get_request[H11Protocol] tests/protocols/test_http.py::test_head_request[HttpToolsProtocol] tests/protocols/test_http.py::test_head_request[H11Protocol] tests/protocols/test_http.py::test_post_request[HttpToolsProtocol] tests/protocols/test_http.py::test_post_request[H11Protocol] tests/protocols/test_http.py::test_keepalive[HttpToolsProtocol] tests/protocols/test_http.py::test_keepalive[H11Protocol] tests/protocols/test_http.py::test_keepalive_timeout[HttpToolsProtocol] tests/protocols/test_http.py::test_keepalive_timeout[H11Protocol] tests/protocols/test_http.py::test_close[HttpToolsProtocol] tests/protocols/test_http.py::test_close[H11Protocol] tests/protocols/test_http.py::test_chunked_encoding[HttpToolsProtocol] tests/protocols/test_http.py::test_chunked_encoding[H11Protocol] tests/protocols/test_http.py::test_pipelined_requests[HttpToolsProtocol] tests/protocols/test_http.py::test_pipelined_requests[H11Protocol] tests/protocols/test_http.py::test_undersized_request[HttpToolsProtocol] tests/protocols/test_http.py::test_undersized_request[H11Protocol] tests/protocols/test_http.py::test_oversized_request[HttpToolsProtocol] tests/protocols/test_http.py::test_oversized_request[H11Protocol] tests/protocols/test_http.py::test_large_post_request[HttpToolsProtocol] tests/protocols/test_http.py::test_large_post_request[H11Protocol] tests/protocols/test_http.py::test_invalid_http[HttpToolsProtocol] tests/protocols/test_http.py::test_invalid_http[H11Protocol] tests/protocols/test_http.py::test_app_exception[HttpToolsProtocol] tests/protocols/test_http.py::test_app_exception[H11Protocol] tests/protocols/test_http.py::test_app_init_exception[HttpToolsProtocol] tests/protocols/test_http.py::test_app_init_exception[H11Protocol] tests/protocols/test_http.py::test_exception_during_response[HttpToolsProtocol] tests/protocols/test_http.py::test_exception_during_response[H11Protocol] tests/protocols/test_http.py::test_no_response_returned[HttpToolsProtocol] tests/protocols/test_http.py::test_no_response_returned[H11Protocol] tests/protocols/test_http.py::test_partial_response_returned[HttpToolsProtocol] tests/protocols/test_http.py::test_partial_response_returned[H11Protocol] tests/protocols/test_http.py::test_duplicate_start_message[HttpToolsProtocol] tests/protocols/test_http.py::test_duplicate_start_message[H11Protocol] tests/protocols/test_http.py::test_missing_start_message[HttpToolsProtocol] tests/protocols/test_http.py::test_missing_start_message[H11Protocol] tests/protocols/test_http.py::test_message_after_body_complete[HttpToolsProtocol] tests/protocols/test_http.py::test_message_after_body_complete[H11Protocol] tests/protocols/test_http.py::test_value_returned[HttpToolsProtocol] tests/protocols/test_http.py::test_value_returned[H11Protocol] tests/protocols/test_http.py::test_early_disconnect[HttpToolsProtocol] tests/protocols/test_http.py::test_early_disconnect[H11Protocol] tests/protocols/test_http.py::test_early_response[HttpToolsProtocol] tests/protocols/test_http.py::test_early_response[H11Protocol] tests/protocols/test_http.py::test_read_after_response[HttpToolsProtocol] tests/protocols/test_http.py::test_read_after_response[H11Protocol] tests/protocols/test_http.py::test_http10_request[HttpToolsProtocol] tests/protocols/test_http.py::test_http10_request[H11Protocol] tests/protocols/test_http.py::test_root_path[HttpToolsProtocol] tests/protocols/test_http.py::test_root_path[H11Protocol] tests/protocols/test_http.py::test_proxy_headers[HttpToolsProtocol] tests/protocols/test_http.py::test_proxy_headers[H11Protocol] tests/protocols/test_http.py::test_max_concurrency[HttpToolsProtocol] tests/protocols/test_http.py::test_max_concurrency[H11Protocol] tests/protocols/test_http.py::test_shutdown_during_request[HttpToolsProtocol] tests/protocols/test_http.py::test_shutdown_during_request[H11Protocol] tests/protocols/test_http.py::test_shutdown_during_idle[HttpToolsProtocol] tests/protocols/test_http.py::test_shutdown_during_idle[H11Protocol] tests/protocols/test_http.py::test_100_continue_not_sent_when_body_not_consumed[HttpToolsProtocol] tests/protocols/test_http.py::test_100_continue_not_sent_when_body_not_consumed[H11Protocol]
uvicorn/protocols/http/httptools_impl.py uvicorn/protocols/http/h11_impl.py
uvicorn/protocols/http/httptools_impl.py uvicorn/protocols/http/h11_impl.py