diff --git a/docs/changelog.md b/docs/changelog.md
index cc237cd..c97fe01 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -8,6 +8,11 @@ A changelog:
 
 *Analogy: git blame :p*
 
+## In progress
+
+* Only set the body and Content-Length header when necessary
+  ([#31](https://github.com/pyrates/roll/pull/31))
+
 ## 0.6.0 — 2017-11-22
 
 * Changed `Roll.hook` signature to also accept kwargs
diff --git a/roll/__init__.py b/roll/__init__.py
index 0af795d..a9a4ff6 100644
--- a/roll/__init__.py
+++ b/roll/__init__.py
@@ -146,6 +146,10 @@ class Protocol(asyncio.Protocol):
     """
 
     __slots__ = ('app', 'req', 'parser', 'resp', 'writer')
+    _BODYLESS_METHODS = ('HEAD', 'CONNECT')
+    _BODYLESS_STATUSES = (HTTPStatus.CONTINUE, HTTPStatus.SWITCHING_PROTOCOLS,
+                          HTTPStatus.PROCESSING, HTTPStatus.NO_CONTENT,
+                          HTTPStatus.NOT_MODIFIED)
     Query = Query
     Request = Request
     Response = Response
@@ -201,7 +205,11 @@ class Protocol(asyncio.Protocol):
             self.response.status.value, self.response.status.phrase.encode())
         if not isinstance(self.response.body, bytes):
             self.response.body = str(self.response.body).encode()
-        if 'Content-Length' not in self.response.headers:
+        # https://tools.ietf.org/html/rfc7230#section-3.3.2 :scream:
+        bodyless = (self.response.status in self._BODYLESS_STATUSES or
+                    (hasattr(self, 'request') and
+                     self.request.method in self._BODYLESS_METHODS))
+        if 'Content-Length' not in self.response.headers and not bodyless:
             length = len(self.response.body)
             self.response.headers['Content-Length'] = length
         for key, value in self.response.headers.items():
@@ -211,7 +219,9 @@ class Protocol(asyncio.Protocol):
                     payload += b'%b: %b\r\n' % (key.encode(), str(v).encode())
             else:
                 payload += b'%b: %b\r\n' % (key.encode(), str(value).encode())
-        payload += b'\r\n%b' % self.response.body
+        payload += b'\r\n'
+        if self.response.body and not bodyless:
+            payload += self.response.body
         self.writer.write(payload)
         if not self.parser.should_keep_alive():
             self.writer.close()

"built-in" headers
Which headers should Roll provides and which should be let to the users?

https://stackoverflow.com/questions/4726515/what-http-response-headers-are-required/25586633#25586633

cf #23 
diff --git a/roll/testing.py b/roll/testing.py
index 96afd23..ab769c7 100644
--- a/roll/testing.py
+++ b/roll/testing.py
@@ -79,6 +79,9 @@ class Client:
     async def options(self, path, **kwargs):
         return await self.request(path, method='OPTIONS', **kwargs)
 
+    async def connect(self, path, **kwargs):
+        return await self.request(path, method='CONNECT', **kwargs)
+
 
 @pytest.fixture
 def client(app, event_loop):
diff --git a/tests/test_response.py b/tests/test_response.py
index 0f60035..7353355 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -23,6 +23,8 @@ async def test_can_set_status_from_httpstatus(client, app):
 
     resp = await client.get('/test')
     assert resp.status == HTTPStatus.ACCEPTED
+    assert client.protocol.writer.data == \
+        b'HTTP/1.1 202 Accepted\r\nContent-Length: 0\r\n\r\n'
 
 
 async def test_write(client, app):
@@ -37,6 +39,56 @@ async def test_write(client, app):
         b'HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\nbody'
 
 
+async def test_write_get_204_no_content_type(client, app):
+
+    @app.route('/test')
+    async def get(req, resp):
+        resp.status = HTTPStatus.NO_CONTENT
+
+    await client.get('/test')
+    assert client.protocol.writer.data == b'HTTP/1.1 204 No Content\r\n\r\n'
+
+
+async def test_write_get_304_no_content_type(client, app):
+
+    @app.route('/test')
+    async def get(req, resp):
+        resp.status = HTTPStatus.NOT_MODIFIED
+
+    await client.get('/test')
+    assert client.protocol.writer.data == b'HTTP/1.1 304 Not Modified\r\n\r\n'
+
+
+async def test_write_get_1XX_no_content_type(client, app):
+
+    @app.route('/test')
+    async def get(req, resp):
+        resp.status = HTTPStatus.CONTINUE
+
+    await client.get('/test')
+    assert client.protocol.writer.data == b'HTTP/1.1 100 Continue\r\n\r\n'
+
+
+async def test_write_head_no_content_type(client, app):
+
+    @app.route('/test', methods=['HEAD'])
+    async def head(req, resp):
+        resp.status = HTTPStatus.OK
+
+    await client.head('/test')
+    assert client.protocol.writer.data == b'HTTP/1.1 200 OK\r\n\r\n'
+
+
+async def test_write_connect_no_content_type(client, app):
+
+    @app.route('/test', methods=['CONNECT'])
+    async def connect(req, resp):
+        resp.status = HTTPStatus.OK
+
+    await client.connect('/test')
+    assert client.protocol.writer.data == b'HTTP/1.1 200 OK\r\n\r\n'
+
+
 async def test_write_set_cookie(client, app):
 
     @app.route('/test')

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-asyncio
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
autoroutes==0.2.0
certifi==2021.5.30
httptools==0.0.9
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-asyncio==0.16.0
-e git+https://github.com/pyrates/roll.git@ff3dc372850dbdda786722d8e1fcd90b0d10e6d8#egg=roll
tomli==1.2.3
typing_extensions==4.1.1
uvloop==0.8.1
zipp==3.6.0

name: roll
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
      - autoroutes==0.2.0
      - httptools==0.0.9
      - 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-asyncio==0.16.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - uvloop==0.8.1
      - zipp==3.6.0
prefix: /opt/conda/envs/roll

tests/test_response.py::test_write_get_204_no_content_type tests/test_response.py::test_write_get_304_no_content_type tests/test_response.py::test_write_get_1XX_no_content_type tests/test_response.py::test_write_head_no_content_type tests/test_response.py::test_write_connect_no_content_type
tests/test_response.py::test_can_set_status_from_numeric_value tests/test_response.py::test_can_set_status_from_httpstatus tests/test_response.py::test_write tests/test_response.py::test_write_set_cookie tests/test_response.py::test_write_multiple_set_cookie
docs/changelog.md roll/__init__.py
docs/changelog.md roll/__init__.py