We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Cannot define a custom HEAD handler in HTTPEndpoint
### Checklist

<!-- Please make sure you check all these items before submitting your bug report. -->

- [x] The bug is reproducible against the latest release and/or `master`.
- [x] There are no similar issues or pull requests to fix it yet.

### Describe the bug

I am unable to set a custom head method for a HTTPEndpoint

### How to reproduce

Python:
```python
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.responses import Response
from starlette.endpoints import HTTPEndpoint

class TestHandler(HTTPEndpoint):
    async def head(self, request):
        print("Called head")
        return Response("head")

    async def get(self, request):
        print("Called get")
        return Response("get")

routes = [
    Route("/", TestHandler),
]

app = Starlette(routes=routes)
```
Then run uvicorn, and call `curl -XHEAD localhost:8000/` and `curl -XGET localhost:8000/`

You will see in uvicorn logs, both times the get handler gets called.
```
Called get
INFO:     127.0.0.1:53438 - "HEAD / HTTP/1.1" 200 OK
Called get
INFO:     127.0.0.1:53440 - "GET / HTTP/1.1" 200 OK
```

### Expected behavior

When calling the HEAD method, it should invoke the head() function

### Actual behavior

When calling the HEAD method, it invokes the get() function


### Environment

- OS: Linux
- Python version: Python 3.9.5
- Starlette version: 0.15.0

### Additional context

The issue is caused by the following code:
https://github.com/encode/starlette/blob/master/starlette/endpoints.py#L26

It should see if there is a head method, and if so, don't call the get method when head is requested

Eg. linked issues, or a description of what you were trying to achieve. -->


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `starlette/endpoints.py`
Content:
```
1 import asyncio
2 import json
3 import typing
4 
5 from starlette import status
6 from starlette.concurrency import run_in_threadpool
7 from starlette.exceptions import HTTPException
8 from starlette.requests import Request
9 from starlette.responses import PlainTextResponse, Response
10 from starlette.types import Message, Receive, Scope, Send
11 from starlette.websockets import WebSocket
12 
13 
14 class HTTPEndpoint:
15     def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:
16         assert scope["type"] == "http"
17         self.scope = scope
18         self.receive = receive
19         self.send = send
20 
21     def __await__(self) -> typing.Generator:
22         return self.dispatch().__await__()
23 
24     async def dispatch(self) -> None:
25         request = Request(self.scope, receive=self.receive)
26         handler_name = "get" if request.method == "HEAD" else request.method.lower()
27         handler = getattr(self, handler_name, self.method_not_allowed)
28         is_async = asyncio.iscoroutinefunction(handler)
29         if is_async:
30             response = await handler(request)
31         else:
32             response = await run_in_threadpool(handler, request)
33         await response(self.scope, self.receive, self.send)
34 
35     async def method_not_allowed(self, request: Request) -> Response:
36         # If we're running inside a starlette application then raise an
37         # exception, so that the configurable exception handler can deal with
38         # returning the response. For plain ASGI apps, just return the response.
39         if "app" in self.scope:
40             raise HTTPException(status_code=405)
41         return PlainTextResponse("Method Not Allowed", status_code=405)
42 
43 
44 class WebSocketEndpoint:
45 
46     encoding: typing.Optional[str] = None  # May be "text", "bytes", or "json".
47 
48     def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:
49         assert scope["type"] == "websocket"
50         self.scope = scope
51         self.receive = receive
52         self.send = send
53 
54     def __await__(self) -> typing.Generator:
55         return self.dispatch().__await__()
56 
57     async def dispatch(self) -> None:
58         websocket = WebSocket(self.scope, receive=self.receive, send=self.send)
59         await self.on_connect(websocket)
60 
61         close_code = status.WS_1000_NORMAL_CLOSURE
62 
63         try:
64             while True:
65                 message = await websocket.receive()
66                 if message["type"] == "websocket.receive":
67                     data = await self.decode(websocket, message)
68                     await self.on_receive(websocket, data)
69                 elif message["type"] == "websocket.disconnect":
70                     close_code = int(message.get("code", status.WS_1000_NORMAL_CLOSURE))
71                     break
72         except Exception as exc:
73             close_code = status.WS_1011_INTERNAL_ERROR
74             raise exc
75         finally:
76             await self.on_disconnect(websocket, close_code)
77 
78     async def decode(self, websocket: WebSocket, message: Message) -> typing.Any:
79 
80         if self.encoding == "text":
81             if "text" not in message:
82                 await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
83                 raise RuntimeError("Expected text websocket messages, but got bytes")
84             return message["text"]
85 
86         elif self.encoding == "bytes":
87             if "bytes" not in message:
88                 await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
89                 raise RuntimeError("Expected bytes websocket messages, but got text")
90             return message["bytes"]
91 
92         elif self.encoding == "json":
93             if message.get("text") is not None:
94                 text = message["text"]
95             else:
96                 text = message["bytes"].decode("utf-8")
97 
98             try:
99                 return json.loads(text)
100             except json.decoder.JSONDecodeError:
101                 await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)
102                 raise RuntimeError("Malformed JSON data received.")
103 
104         assert (
105             self.encoding is None
106         ), f"Unsupported 'encoding' attribute {self.encoding}"
107         return message["text"] if message.get("text") else message["bytes"]
108 
109     async def on_connect(self, websocket: WebSocket) -> None:
110         """Override to handle an incoming websocket connection"""
111         await websocket.accept()
112 
113     async def on_receive(self, websocket: WebSocket, data: typing.Any) -> None:
114         """Override to handle an incoming websocket message"""
115 
116     async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:
117         """Override to handle a disconnecting websocket"""
118
```
--- 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/starlette/endpoints.py b/starlette/endpoints.py
--- a/starlette/endpoints.py
+++ b/starlette/endpoints.py
@@ -23,7 +23,12 @@
 
     async def dispatch(self) -> None:
         request = Request(self.scope, receive=self.receive)
-        handler_name = "get" if request.method == "HEAD" else request.method.lower()
+        handler_name = (
+            "get"
+            if request.method == "HEAD" and not hasattr(self, "head")
+            else request.method.lower()
+        )
+
         handler = getattr(self, handler_name, self.method_not_allowed)
         is_async = asyncio.iscoroutinefunction(handler)
         if is_async:

{"golden_diff": "diff --git a/starlette/endpoints.py b/starlette/endpoints.py\n--- a/starlette/endpoints.py\n+++ b/starlette/endpoints.py\n@@ -23,7 +23,12 @@\n \n     async def dispatch(self) -> None:\n         request = Request(self.scope, receive=self.receive)\n-        handler_name = \"get\" if request.method == \"HEAD\" else request.method.lower()\n+        handler_name = (\n+            \"get\"\n+            if request.method == \"HEAD\" and not hasattr(self, \"head\")\n+            else request.method.lower()\n+        )\n+\n         handler = getattr(self, handler_name, self.method_not_allowed)\n         is_async = asyncio.iscoroutinefunction(handler)\n         if is_async:\n", "issue": "Cannot define a custom HEAD handler in HTTPEndpoint\n### Checklist\r\n\r\n<!-- Please make sure you check all these items before submitting your bug report. -->\r\n\r\n- [x] The bug is reproducible against the latest release and/or `master`.\r\n- [x] There are no similar issues or pull requests to fix it yet.\r\n\r\n### Describe the bug\r\n\r\nI am unable to set a custom head method for a HTTPEndpoint\r\n\r\n### How to reproduce\r\n\r\nPython:\r\n```python\r\nfrom starlette.applications import Starlette\r\nfrom starlette.routing import Route\r\nfrom starlette.responses import Response\r\nfrom starlette.endpoints import HTTPEndpoint\r\n\r\nclass TestHandler(HTTPEndpoint):\r\n    async def head(self, request):\r\n        print(\"Called head\")\r\n        return Response(\"head\")\r\n\r\n    async def get(self, request):\r\n        print(\"Called get\")\r\n        return Response(\"get\")\r\n\r\nroutes = [\r\n    Route(\"/\", TestHandler),\r\n]\r\n\r\napp = Starlette(routes=routes)\r\n```\r\nThen run uvicorn, and call `curl -XHEAD localhost:8000/` and `curl -XGET localhost:8000/`\r\n\r\nYou will see in uvicorn logs, both times the get handler gets called.\r\n```\r\nCalled get\r\nINFO:     127.0.0.1:53438 - \"HEAD / HTTP/1.1\" 200 OK\r\nCalled get\r\nINFO:     127.0.0.1:53440 - \"GET / HTTP/1.1\" 200 OK\r\n```\r\n\r\n### Expected behavior\r\n\r\nWhen calling the HEAD method, it should invoke the head() function\r\n\r\n### Actual behavior\r\n\r\nWhen calling the HEAD method, it invokes the get() function\r\n\r\n\r\n### Environment\r\n\r\n- OS: Linux\r\n- Python version: Python 3.9.5\r\n- Starlette version: 0.15.0\r\n\r\n### Additional context\r\n\r\nThe issue is caused by the following code:\r\nhttps://github.com/encode/starlette/blob/master/starlette/endpoints.py#L26\r\n\r\nIt should see if there is a head method, and if so, don't call the get method when head is requested\r\n\r\nEg. linked issues, or a description of what you were trying to achieve. -->\r\n\n", "before_files": [{"content": "import asyncio\nimport json\nimport typing\n\nfrom starlette import status\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse, Response\nfrom starlette.types import Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocket\n\n\nclass HTTPEndpoint:\n    def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] == \"http\"\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n\n    def __await__(self) -> typing.Generator:\n        return self.dispatch().__await__()\n\n    async def dispatch(self) -> None:\n        request = Request(self.scope, receive=self.receive)\n        handler_name = \"get\" if request.method == \"HEAD\" else request.method.lower()\n        handler = getattr(self, handler_name, self.method_not_allowed)\n        is_async = asyncio.iscoroutinefunction(handler)\n        if is_async:\n            response = await handler(request)\n        else:\n            response = await run_in_threadpool(handler, request)\n        await response(self.scope, self.receive, self.send)\n\n    async def method_not_allowed(self, request: Request) -> Response:\n        # If we're running inside a starlette application then raise an\n        # exception, so that the configurable exception handler can deal with\n        # returning the response. For plain ASGI apps, just return the response.\n        if \"app\" in self.scope:\n            raise HTTPException(status_code=405)\n        return PlainTextResponse(\"Method Not Allowed\", status_code=405)\n\n\nclass WebSocketEndpoint:\n\n    encoding: typing.Optional[str] = None  # May be \"text\", \"bytes\", or \"json\".\n\n    def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] == \"websocket\"\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n\n    def __await__(self) -> typing.Generator:\n        return self.dispatch().__await__()\n\n    async def dispatch(self) -> None:\n        websocket = WebSocket(self.scope, receive=self.receive, send=self.send)\n        await self.on_connect(websocket)\n\n        close_code = status.WS_1000_NORMAL_CLOSURE\n\n        try:\n            while True:\n                message = await websocket.receive()\n                if message[\"type\"] == \"websocket.receive\":\n                    data = await self.decode(websocket, message)\n                    await self.on_receive(websocket, data)\n                elif message[\"type\"] == \"websocket.disconnect\":\n                    close_code = int(message.get(\"code\", status.WS_1000_NORMAL_CLOSURE))\n                    break\n        except Exception as exc:\n            close_code = status.WS_1011_INTERNAL_ERROR\n            raise exc\n        finally:\n            await self.on_disconnect(websocket, close_code)\n\n    async def decode(self, websocket: WebSocket, message: Message) -> typing.Any:\n\n        if self.encoding == \"text\":\n            if \"text\" not in message:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Expected text websocket messages, but got bytes\")\n            return message[\"text\"]\n\n        elif self.encoding == \"bytes\":\n            if \"bytes\" not in message:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Expected bytes websocket messages, but got text\")\n            return message[\"bytes\"]\n\n        elif self.encoding == \"json\":\n            if message.get(\"text\") is not None:\n                text = message[\"text\"]\n            else:\n                text = message[\"bytes\"].decode(\"utf-8\")\n\n            try:\n                return json.loads(text)\n            except json.decoder.JSONDecodeError:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Malformed JSON data received.\")\n\n        assert (\n            self.encoding is None\n        ), f\"Unsupported 'encoding' attribute {self.encoding}\"\n        return message[\"text\"] if message.get(\"text\") else message[\"bytes\"]\n\n    async def on_connect(self, websocket: WebSocket) -> None:\n        \"\"\"Override to handle an incoming websocket connection\"\"\"\n        await websocket.accept()\n\n    async def on_receive(self, websocket: WebSocket, data: typing.Any) -> None:\n        \"\"\"Override to handle an incoming websocket message\"\"\"\n\n    async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:\n        \"\"\"Override to handle a disconnecting websocket\"\"\"\n", "path": "starlette/endpoints.py"}], "after_files": [{"content": "import asyncio\nimport json\nimport typing\n\nfrom starlette import status\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.exceptions import HTTPException\nfrom starlette.requests import Request\nfrom starlette.responses import PlainTextResponse, Response\nfrom starlette.types import Message, Receive, Scope, Send\nfrom starlette.websockets import WebSocket\n\n\nclass HTTPEndpoint:\n    def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] == \"http\"\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n\n    def __await__(self) -> typing.Generator:\n        return self.dispatch().__await__()\n\n    async def dispatch(self) -> None:\n        request = Request(self.scope, receive=self.receive)\n        handler_name = (\n            \"get\"\n            if request.method == \"HEAD\" and not hasattr(self, \"head\")\n            else request.method.lower()\n        )\n\n        handler = getattr(self, handler_name, self.method_not_allowed)\n        is_async = asyncio.iscoroutinefunction(handler)\n        if is_async:\n            response = await handler(request)\n        else:\n            response = await run_in_threadpool(handler, request)\n        await response(self.scope, self.receive, self.send)\n\n    async def method_not_allowed(self, request: Request) -> Response:\n        # If we're running inside a starlette application then raise an\n        # exception, so that the configurable exception handler can deal with\n        # returning the response. For plain ASGI apps, just return the response.\n        if \"app\" in self.scope:\n            raise HTTPException(status_code=405)\n        return PlainTextResponse(\"Method Not Allowed\", status_code=405)\n\n\nclass WebSocketEndpoint:\n\n    encoding: typing.Optional[str] = None  # May be \"text\", \"bytes\", or \"json\".\n\n    def __init__(self, scope: Scope, receive: Receive, send: Send) -> None:\n        assert scope[\"type\"] == \"websocket\"\n        self.scope = scope\n        self.receive = receive\n        self.send = send\n\n    def __await__(self) -> typing.Generator:\n        return self.dispatch().__await__()\n\n    async def dispatch(self) -> None:\n        websocket = WebSocket(self.scope, receive=self.receive, send=self.send)\n        await self.on_connect(websocket)\n\n        close_code = status.WS_1000_NORMAL_CLOSURE\n\n        try:\n            while True:\n                message = await websocket.receive()\n                if message[\"type\"] == \"websocket.receive\":\n                    data = await self.decode(websocket, message)\n                    await self.on_receive(websocket, data)\n                elif message[\"type\"] == \"websocket.disconnect\":\n                    close_code = int(message.get(\"code\", status.WS_1000_NORMAL_CLOSURE))\n                    break\n        except Exception as exc:\n            close_code = status.WS_1011_INTERNAL_ERROR\n            raise exc\n        finally:\n            await self.on_disconnect(websocket, close_code)\n\n    async def decode(self, websocket: WebSocket, message: Message) -> typing.Any:\n\n        if self.encoding == \"text\":\n            if \"text\" not in message:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Expected text websocket messages, but got bytes\")\n            return message[\"text\"]\n\n        elif self.encoding == \"bytes\":\n            if \"bytes\" not in message:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Expected bytes websocket messages, but got text\")\n            return message[\"bytes\"]\n\n        elif self.encoding == \"json\":\n            if message.get(\"text\") is not None:\n                text = message[\"text\"]\n            else:\n                text = message[\"bytes\"].decode(\"utf-8\")\n\n            try:\n                return json.loads(text)\n            except json.decoder.JSONDecodeError:\n                await websocket.close(code=status.WS_1003_UNSUPPORTED_DATA)\n                raise RuntimeError(\"Malformed JSON data received.\")\n\n        assert (\n            self.encoding is None\n        ), f\"Unsupported 'encoding' attribute {self.encoding}\"\n        return message[\"text\"] if message.get(\"text\") else message[\"bytes\"]\n\n    async def on_connect(self, websocket: WebSocket) -> None:\n        \"\"\"Override to handle an incoming websocket connection\"\"\"\n        await websocket.accept()\n\n    async def on_receive(self, websocket: WebSocket, data: typing.Any) -> None:\n        \"\"\"Override to handle an incoming websocket message\"\"\"\n\n    async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:\n        \"\"\"Override to handle a disconnecting websocket\"\"\"\n", "path": "starlette/endpoints.py"}]}