prompt: 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()
  
  ```
  
golden_diff: 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:
  
verification_info: {"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"}]}