Repository: django/daphne

Issue:
`Daphne` sets  `server='daphne'` header for `websocket` connections, but not for `http-requests`
# Hi there,

( Correct me if i am wrong here ) 
Daphne ( by default ) doesn't set a server header. 

By default these servers sets a header to identify backend technologies:
- [Uvicorn](github.com/encode/uvicorn) : uvicorn
- [Hypercorn](https://gitlab.com/pgjones/hypercorn) : hypercorn-{implementation}
- [Gunicorn](https://github.com/benoitc/gunicorn) : gunicorn/{version}

# Why should we do it ?
To allow technology detection by extensions like [Wappalyzer](https://github.com/AliasIO/wappalyzer/)

Discussion:
baseplate-admin: @adamchainz Take a look please
baseplate-admin: hi, @adamchainz please take a look
baseplate-admin: Should i write under create 3.0.3 or write under 3.0.2?
adamchainz: No heading, as per: https://github.com/django/daphne/commit/2b6f15361646093e76e990675bf24304aad205bd

The release version number is determined at release time.

Version 3.0.2 is already released.

In general, you can check git history on the changelog file to see how a project handles things like this!
baseplate-admin: hi @adamchainz, The tests should pass now.
![Capture](https://user-images.githubusercontent.com/61817579/147585384-158912b8-2fcb-4e69-b1e9-2faddbae7b21.PNG)
 
Thank you so much for guiding a newbie like me and Cheers 🍻
Hopefully i can make more contribution to the Django project
baseplate-admin: But i see a little problem here 
baseplate-admin: Everything done 😃 Can we merge this now ?
adamchainz: I will let @carltongibson make the final review.
baseplate-admin: Sure. 😃
baseplate-admin: > I share @adamchainz concern about setting Server headers at all really. ... If we do proceed here, do we have a story for folks not wanting to set that, other than filter it later on?

Please check the Issue. Every other web server has a default header that users can override. 

> If we do proceed here, do we have a story for folks not wanting to set that, other than filter it later on?


Yep we do. You can simply add a header from backend that overrides that.

Minimal code to do it :
```python

async def avatar(request, user_id) -> HttpResponse:
    # Save a statistics to DB
    await increase_avatar_count_by_one()
    # Get user | Note that our user model is custom
    user = await sync_to_async(CustomUser.objects.get, thread_sensitive=True)(
        id=user_id
    )

    email = user.email
    result = await libravatar_raw_query(email, dict(request.GET))

    # Remove link to remove tracking
    result.headers.pop("Link", None)

    # Remove the Content-Type and Content-Length
    result.headers.pop("Content-Type", None)
    result.headers.pop("Content-Length", None)

    # No reason. Just to be a bit more transparent
    result.headers["server"] = f""

    # Pillow processing
    res = Image.open(BytesIO(await result.aread()))
    in_memory = BytesIO()
    res.save(in_memory, format="avif")

    return HttpResponse(
        # await result.aread(),
        in_memory.getvalue(),
        content_type="image/avif",
        headers=result.headers,
    )

```

![1](https://user-images.githubusercontent.com/61817579/147654283-840c4f87-37b4-474a-aa72-84259606416b.PNG)




> This isn't complete - you aren't overriding it for HTTP connections as well. I'd want to see this complete before I accept it in.

Essentially I fixed it.
adamchainz: > Please check the Issue. Every other web server has a default header that users can override.

Carlton's point is that he doesn't find "other servers do it" a good enough reason.

I can see both sides. Sure, we already send it for websockets, so why not for HTTP responses? And it does help the openness of the web, slightly.

Then again, it makes driveby vulnerability scans much easier. We'd rather have a little protection through obscurity if a Daphne/Twisted/whatever exploit is discovered.

Perhaps the better approach is actually the opposite: drop `Server` from websocket connections?

> The check being dropped here was added deliberately in #231. So (minor as it is) it's a breaking change and I want to be clear if we're going to proceed.

(I don't think this makes it a breaking change - afaict the check was added as a weird way of using "Daphne" a default that meant "don't send a server header".)
baseplate-admin: > Carlton's point is that he doesn't find "other servers do it" a good enough reason.

Fair enough.

> Perhaps the better approach is actually the opposite: drop Server from websocket connections?

If thats the goal (having no server header) just run 
```python
daphne -b 0.0.0.0 -p 8000 core.asgi:application --server-name=
```
This completely removes the server header addition.


> Then again, it makes driveby vulnerability scans much easier. We'd rather have a little protection through obscurity if a Daphne/Twisted/whatever exploit is discovered.

I agree with you on this. So lets make a change in documentation adding the best practices of deployment (preferably removing the server header for some super secure sites)
carltongibson: @baseplate-admin Sorry for the delay — I've had _something of a month™_. 

Yes, let's try and get this moving. 

Reading your last comment, did you prefer the `--no-server-name` flag or `--server-name=` to disable? (I don't really mind… happy to go with either really)


Thanks! 
baseplate-admin: >  Sorry for the delay — I've had something of a month™.

No problem :D 
Sorry if I disturbed you in a busy day.

> Reading your last comment, did you prefer the `--no-server-name` flag or `--server-name=` to disable? (I don't really mind… happy to go with either really)

I prefer `--no-server-name`.

For now if you set `--server-name=` when you call the main function.

Eg :
```bash
daphne -b 0.0.0.0 -p 8000 { asgi_app } --server-name=
```

It will remove the server header altogether. ( **No extra work needed here** )

<details>
<summary>
Heres a picture proving this point :
</summary>

![1](https://user-images.githubusercontent.com/61817579/153631275-c3b2056a-8afb-4cf4-9776-9fce5553ae30.PNG)

</details>

In my opinion it should be better if we passed something like `--no-server-name` (  _for production servers that want to disable this default server setting_  ) it would be better. Because it makes the developer aware that they wanna disable a feature, instead of silently disabling it. ( **For this some work is needed** )


Eg :
```zsh
daphne -b 0.0.0.0 -p 8000 { asgi_app } --no-server-name
```


Which in return should produce the same result as :

```bash
daphne -b 0.0.0.0 -p 8000 { asgi_app } --server-name=
```



**Now I have a small question. ( Sorry if this sounds stupid )**

**Should I fix the issue in this pull request or open another one just to add `--no-server-name` option?**
carltongibson: I think it's all one issue. If you're happy to add the extra flag, please do so here. 
baseplate-admin: Hey @carltongibson , Please tell me if i missed anything.

 ### Changes that I have made : 

* Added some tests that references #231 changes. 

* Added `--no-server-name`.

* Added tests that references `--no-server-name`

* Added 2 changes on `CHANGELOG.txt`

* I have also changed `Daphne` => `daphne` in [here](https://github.com/django/daphne/blob/6a5093982ca1eaffbc41f0114ac5bea7cb3902be/daphne/server.py#L59) because of [this](https://asgi.readthedocs.io/en/latest/specs/www.html?highlight=Header%20names%20must%20be%20lowercased.#accept-send-event) and other ASGI/WSGI servers follow the same naming system. ( Check #395 for images)

### Some Proof of my work :

<details>
<summary><b>Default Daphne : </b></summary>

\
Console output :
```powershell
PS C:\Programming\Blog-main> pipenv run daphne core.asgi:application
2022-02-12 00:41:10,622 INFO     Starting server at tcp:port=8000:interface=127.0.0.1
2022-02-12 00:41:10,622 INFO     HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2022-02-12 00:41:10,623 INFO     Configuring endpoint tcp:port=8000:interface=127.0.0.1
2022-02-12 00:41:10,624 INFO     Listening on TCP address 127.0.0.1:8000
127.0.0.1:61351 - - [12/Feb/2022:00:41:16] "GET /blog/" 200 3458
127.0.0.1:61351 - - [12/Feb/2022:00:41:16] "GET /static/core/css/root.css" 200 144
127.0.0.1:61355 - - [12/Feb/2022:00:41:16] "GET /static/core/css/main.css" 200 181
127.0.0.1:61356 - - [12/Feb/2022:00:41:16] "GET /static/blog/css/blog_list.css" 200 552
127.0.0.1:61357 - - [12/Feb/2022:00:41:16] "GET /static/core/css/navbar/navbar.css" 200 544
127.0.0.1:61358 - - [12/Feb/2022:00:41:16] "GET /static/core/css/footer/footer.css" 200 272
127.0.0.1:61359 - - [12/Feb/2022:00:41:16] "GET /static/blog/js/shapeDividerShadowDom.js" 200 529
127.0.0.1:61351 - - [12/Feb/2022:00:41:16] "GET /static/core/images/logo.png" 200 11449
127.0.0.1:61351 - - [12/Feb/2022:00:41:16] "GET /static/core/vendor/particles.js/particlesjs-config.json" 200 577
127.0.0.1:61356 - - [12/Feb/2022:00:41:16] "GET /static/core/vendor/particles.js/particlesjs-config.json" 200 577
```
![default](https://user-images.githubusercontent.com/61817579/153650328-e270fff2-74ff-48b4-95f9-d8d88d3f193c.PNG)
</details>
<details>
<summary><b>--no-server-name :</b></summary>

\
Console output :
```powershell
PS C:\Programming\Blog-main> pipenv run daphne core.asgi:application --no-server-name
2022-02-12 00:45:59,029 INFO     Starting server at tcp:port=8000:interface=127.0.0.1
2022-02-12 00:45:59,030 INFO     HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2022-02-12 00:45:59,030 INFO     Configuring endpoint tcp:port=8000:interface=127.0.0.1
2022-02-12 00:45:59,031 INFO     Listening on TCP address 127.0.0.1:8000
127.0.0.1:62208 - - [12/Feb/2022:00:46:00] "GET /blog/" 200 3458
127.0.0.1:62208 - - [12/Feb/2022:00:46:00] "GET /static/core/css/root.css" 200 144
127.0.0.1:62213 - - [12/Feb/2022:00:46:00] "GET /static/core/css/main.css" 200 181
127.0.0.1:62298 - - [12/Feb/2022:00:46:00] "GET /static/blog/css/blog_list.css" 200 552
127.0.0.1:62299 - - [12/Feb/2022:00:46:00] "GET /static/core/css/navbar/navbar.css" 200 544
127.0.0.1:62300 - - [12/Feb/2022:00:46:00] "GET /static/core/css/footer/footer.css" 200 272
127.0.0.1:62301 - - [12/Feb/2022:00:46:00] "GET /static/blog/js/shapeDividerShadowDom.js" 200 529
127.0.0.1:62208 - - [12/Feb/2022:00:46:00] "GET /static/core/images/logo.png" 200 11449
127.0.0.1:62302 - - [12/Feb/2022:00:46:00] "GET /static/blog/js/shapeDividerShadowDom.js" 200 529
127.0.0.1:62208 - - [12/Feb/2022:00:46:00] "GET /static/core/vendor/particles.js/particlesjs-config.json" 200 577
127.0.0.1:62298 - - [12/Feb/2022:00:46:00] "GET /static/core/vendor/particles.js/particlesjs-config.json" 200 577
```
![--no-server-name](https://user-images.githubusercontent.com/61817579/153651189-226e02c0-eaec-447e-bb2d-49409513c84d.PNG)
</details>

<details>
<summary><b>--server-name='php'</b></summary>

\
Console output :
```powershell
PS C:\Programming\Blog-main> pipenv run daphne core.asgi:application --server-name=php
2022-02-12 00:47:24,991 INFO     Starting server at tcp:port=8000:interface=127.0.0.1
2022-02-12 00:47:24,992 INFO     HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2022-02-12 00:47:24,993 INFO     Configuring endpoint tcp:port=8000:interface=127.0.0.1
2022-02-12 00:47:24,994 INFO     Listening on TCP address 127.0.0.1:8000
127.0.0.1:62500 - - [12/Feb/2022:00:47:26] "GET /blog/" 200 3458
127.0.0.1:62500 - - [12/Feb/2022:00:47:26] "GET /static/core/css/root.css" 200 144
127.0.0.1:62506 - - [12/Feb/2022:00:47:26] "GET /static/core/css/main.css" 200 181
127.0.0.1:62507 - - [12/Feb/2022:00:47:26] "GET /static/blog/css/blog_list.css" 200 552
127.0.0.1:62508 - - [12/Feb/2022:00:47:26] "GET /static/core/css/navbar/navbar.css" 200 544
127.0.0.1:62509 - - [12/Feb/2022:00:47:26] "GET /static/core/css/footer/footer.css" 200 272
127.0.0.1:62510 - - [12/Feb/2022:00:47:26] "GET /static/blog/js/shapeDividerShadowDom.js" 200 529
127.0.0.1:62500 - - [12/Feb/2022:00:47:26] "GET /static/core/images/logo.png" 200 11449
127.0.0.1:62500 - - [12/Feb/2022:00:47:26] "GET /static/core/vendor/particles.js/particlesjs-config.json" 200 577
127.0.0.1:62500 - - [12/Feb/2022:00:47:26] "GET /static/core/vendor/particles.js/particlesjs-config.json" 200 577
```
![server=php](https://user-images.githubusercontent.com/61817579/153651443-74d9c883-4885-4d2f-87ff-d193336507a3.PNG)
</details>

### The test Result :
<details>
<summary><b>Picture :</b></summary>

![Capture](https://user-images.githubusercontent.com/61817579/153651682-1cc2dc46-60ca-485e-962a-db9c428b1d4a.PNG)

</details>
<details>
<summary><b> Console Output : </b></summary>

```powershell
PS C:\Programming\daphne> pipenv run test
===================================================================================================== test session starts =====================================================================================================
platform win32 -- Python 3.10.2, pytest-7.0.1, pluggy-1.0.0
rootdir: C:\Programming\daphne, configfile: setup.cfg, testpaths: tests
plugins: hypothesis-6.36.1, asyncio-0.18.1
asyncio: mode=legacy
collected 59 items

tests\test_cli.py ..............                                                                                                                                                                                         [ 23%]
tests\test_http_request.py ..............                                                                                                                                                                                [ 47%]
tests\test_http_response.py .........                                                                                                                                                                                    [ 62%] 
tests\test_utils.py ............                                                                                                                                                                                         [ 83%] 
tests\test_websocket.py ..........                                                                                                                                                                                       [100%] 

====================================================================================================== warnings summary ======================================================================================================= 
..\..\Users\zarif\.virtualenvs\daphne-qhnhUZrd\lib\site-packages\pytest_asyncio\plugin.py:191
  C:\Users\zarif\.virtualenvs\daphne-qhnhUZrd\lib\site-packages\pytest_asyncio\plugin.py:191: DeprecationWarning: The 'asyncio_mode' default value will change to 'strict' in future, please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' in pytest configuration file.
    config.issue_config_time_warning(LEGACY_MODE, stacklevel=2)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
========================================================================================== 59 passed, 1 warning in 66.77s (0:01:06) =========================================================================================== 
PS C:\Programming\daphne>
```

</details>
TanimSk: @baseplate-admin `setting a default Server header for HTTP responses ` is a good approach
nice work! 
baseplate-admin: Just giving this pull request a little bump. 
carltongibson: Thanks @baseplate-admin. It's on the list. 

My aim is to swing back to channels &co once Django 4.1a1 is out of the door. 

Patch:
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 630c0fa..31e4650 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -11,7 +11,14 @@ Unreleased
   range of versions does not represent a good use of maintainer time. Going
   forward the latest Twisted version will be required.
 
-* Added `log-fmt` CLI argument.
+* Set ``daphne`` as default ``Server`` header.
+
+  This can be configured with the ``--server-name`` CLI argument.
+
+  Added the new ``--no-server-name`` CLI argument to disable the ``Server``
+  header, which is equivalent to ``--server-name=` (an empty name).
+
+* Added ``--log-fmt`` CLI argument.
 
 3.0.2 (2021-04-07)
 ------------------
diff --git a/daphne/cli.py b/daphne/cli.py
index 2e83a5c..accafe1 100755
--- a/daphne/cli.py
+++ b/daphne/cli.py
@@ -93,7 +93,7 @@ class CommandLineInterface:
         self.parser.add_argument(
             "--log-fmt",
             help="Log format to use",
-            default="%(asctime)-15s %(levelname)-8s %(message)s"
+            default="%(asctime)-15s %(levelname)-8s %(message)s",
         )
         self.parser.add_argument(
             "--ping-interval",
@@ -162,7 +162,10 @@ class CommandLineInterface:
             "--server-name",
             dest="server_name",
             help="specify which value should be passed to response header Server attribute",
-            default="Daphne",
+            default="daphne",
+        )
+        self.parser.add_argument(
+            "--no-server-name", dest="server_name", action="store_const", const=""
         )
 
         self.server = None
diff --git a/daphne/http_protocol.py b/daphne/http_protocol.py
index a289e93..f0657fd 100755
--- a/daphne/http_protocol.py
+++ b/daphne/http_protocol.py
@@ -249,8 +249,8 @@ class WebRequest(http.Request):
             # Write headers
             for header, value in message.get("headers", {}):
                 self.responseHeaders.addRawHeader(header, value)
-            if self.server.server_name and self.server.server_name.lower() != "daphne":
-                self.setHeader(b"server", self.server.server_name.encode("utf-8"))
+            if self.server.server_name and not self.responseHeaders.hasHeader("server"):
+                self.setHeader(b"server", self.server.server_name.encode())
             logger.debug(
                 "HTTP %s response started for %s", message["status"], self.client_addr
             )
diff --git a/daphne/server.py b/daphne/server.py
index 0d463d0..4334217 100755
--- a/daphne/server.py
+++ b/daphne/server.py
@@ -56,7 +56,7 @@ class Server:
         websocket_handshake_timeout=5,
         application_close_timeout=10,
         ready_callable=None,
-        server_name="Daphne",
+        server_name="daphne",
         # Deprecated and does not work, remove in version 2.2
         ws_protocols=None,
     ):


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 17335ed..51eab2e 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -240,3 +240,18 @@ class TestCLIInterface(TestCase):
             exc.exception.message,
             "--proxy-headers has to be passed for this parameter.",
         )
+
+    def test_custom_servername(self):
+        """
+        Passing `--server-name` will set the default server header
+        from 'daphne' to the passed one.
+        """
+        self.assertCLI([], {"server_name": "daphne"})
+        self.assertCLI(["--server-name", ""], {"server_name": ""})
+        self.assertCLI(["--server-name", "python"], {"server_name": "python"})
+
+    def test_no_servername(self):
+        """
+        Passing `--no-server-name` will set server name to '' (empty string)
+        """
+        self.assertCLI(["--no-server-name"], {"server_name": ""})
diff --git a/tests/test_http_response.py b/tests/test_http_response.py
index 1fc2439..22f6480 100644
--- a/tests/test_http_response.py
+++ b/tests/test_http_response.py
@@ -13,9 +13,12 @@ class TestHTTPResponse(DaphneTestCase):
         Lowercases and sorts headers, and strips transfer-encoding ones.
         """
         return sorted(
-            (name.lower(), value.strip())
-            for name, value in headers
-            if name.lower() != b"transfer-encoding"
+            [(b"server", b"daphne")]
+            + [
+                (name.lower(), value.strip())
+                for name, value in headers
+                if name.lower() not in (b"server", b"transfer-encoding")
+            ]
         )
 
     def encode_headers(self, headers):