We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Asgi request headers are not retrieved properly
Environment: 
Python: python3.8

fastapi==0.63.0
opentelemetry-api==0.16b1
opentelemetry-sdk==0.16b1
opentelemetry-instrumentation-fastapi==0.16b1
opentelemetry-exporter-google-cloud==0.16b1
opentelemetry-tools-google-cloud==0.16b1

When using `CloudTraceFormatPropagator` for [GCP](https://github.com/GoogleCloudPlatform/opentelemetry-operations-python), `X-Cloud-Trace-Context` header cannot be retrieved.

**Steps to reproduce**
Describe exactly how to reproduce the error. Include a code sample if applicable.

```
# server.py
import uvicorn
from fastapi import FastAPI, Request
from opentelemetry import trace
from opentelemetry.propagators import set_global_textmap
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.tools import cloud_trace_propagator

set_global_textmap(cloud_trace_propagator.CloudTraceFormatPropagator())

app = FastAPI()

tracer = trace.get_tracer("test")
FastAPIInstrumentor.instrument_app(app)

@app.get("/trace")
async def test(r: Request):
    with tracer.start_as_current_span("test") as span:
        trace_id = span.get_span_context().trace_id
        print(f"{trace_id:32x}")  # should print trace ID from `X-Cloud-Trace-Context` header value

uvicorn.run(app)
```

```
# client.py
import requests

r = requests.Session()
r.headers.setdefault("X-Cloud-Trace-Context",
                        "f3ef5c2ede256aa77491057e600eca11/15104302039794794507;o=1")
r.get("http://localhost:8000/trace")
```

**What is the expected behavior?**
Printed value should be `f3ef5c2ede256aa77491057e600eca11` based from the header sent

**What is the actual behavior?**
A newly generated value everything `/trace` is called

**Additional context**
`X-Cloud-Trace-Context` header value is not retrieved properly in `CloudTraceFormatPropagator`


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """
16 The opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used
17 on any ASGI framework (such as Django-channels / Quart) to track requests
18 timing through OpenTelemetry.
19 """
20 
21 import typing
22 import urllib
23 from functools import wraps
24 from typing import Tuple
25 
26 from asgiref.compatibility import guarantee_single_callable
27 
28 from opentelemetry import context, propagators, trace
29 from opentelemetry.instrumentation.asgi.version import __version__  # noqa
30 from opentelemetry.instrumentation.utils import http_status_to_status_code
31 from opentelemetry.trace.propagation.textmap import DictGetter
32 from opentelemetry.trace.status import Status, StatusCode
33 
34 
35 class CarrierGetter(DictGetter):
36     def get(
37         self, carrier: dict, key: str
38     ) -> typing.Optional[typing.List[str]]:
39         """Getter implementation to retrieve a HTTP header value from the ASGI
40         scope.
41 
42         Args:
43             carrier: ASGI scope object
44             key: header name in scope
45         Returns:
46             A list with a single string with the header value if it exists,
47                 else None.
48         """
49         headers = carrier.get("headers")
50         decoded = [
51             _value.decode("utf8")
52             for (_key, _value) in headers
53             if _key.decode("utf8") == key
54         ]
55         if not decoded:
56             return None
57         return decoded
58 
59 
60 carrier_getter = CarrierGetter()
61 
62 
63 def collect_request_attributes(scope):
64     """Collects HTTP request attributes from the ASGI scope and returns a
65     dictionary to be used as span creation attributes."""
66     server_host, port, http_url = get_host_port_url_tuple(scope)
67     query_string = scope.get("query_string")
68     if query_string and http_url:
69         if isinstance(query_string, bytes):
70             query_string = query_string.decode("utf8")
71         http_url = http_url + ("?" + urllib.parse.unquote(query_string))
72 
73     result = {
74         "http.scheme": scope.get("scheme"),
75         "http.host": server_host,
76         "net.host.port": port,
77         "http.flavor": scope.get("http_version"),
78         "http.target": scope.get("path"),
79         "http.url": http_url,
80     }
81     http_method = scope.get("method")
82     if http_method:
83         result["http.method"] = http_method
84 
85     http_host_value_list = carrier_getter.get(scope, "host")
86     if http_host_value_list:
87         result["http.server_name"] = ",".join(http_host_value_list)
88     http_user_agent = carrier_getter.get(scope, "user-agent")
89     if http_user_agent:
90         result["http.user_agent"] = http_user_agent[0]
91 
92     if "client" in scope and scope["client"] is not None:
93         result["net.peer.ip"] = scope.get("client")[0]
94         result["net.peer.port"] = scope.get("client")[1]
95 
96     # remove None values
97     result = {k: v for k, v in result.items() if v is not None}
98 
99     return result
100 
101 
102 def get_host_port_url_tuple(scope):
103     """Returns (host, port, full_url) tuple.
104     """
105     server = scope.get("server") or ["0.0.0.0", 80]
106     port = server[1]
107     server_host = server[0] + (":" + str(port) if port != 80 else "")
108     full_path = scope.get("root_path", "") + scope.get("path", "")
109     http_url = scope.get("scheme", "http") + "://" + server_host + full_path
110     return server_host, port, http_url
111 
112 
113 def set_status_code(span, status_code):
114     """Adds HTTP response attributes to span using the status_code argument."""
115     if not span.is_recording():
116         return
117     try:
118         status_code = int(status_code)
119     except ValueError:
120         span.set_status(
121             Status(
122                 StatusCode.ERROR,
123                 "Non-integer HTTP status: " + repr(status_code),
124             )
125         )
126     else:
127         span.set_attribute("http.status_code", status_code)
128         span.set_status(Status(http_status_to_status_code(status_code)))
129 
130 
131 def get_default_span_details(scope: dict) -> Tuple[str, dict]:
132     """Default implementation for span_details_callback
133 
134     Args:
135         scope: the asgi scope dictionary
136 
137     Returns:
138         a tuple of the span, and any attributes to attach to the
139         span.
140     """
141     method_or_path = scope.get("method") or scope.get("path")
142 
143     return method_or_path, {}
144 
145 
146 class OpenTelemetryMiddleware:
147     """The ASGI application middleware.
148 
149     This class is an ASGI middleware that starts and annotates spans for any
150     requests it is invoked with.
151 
152     Args:
153         app: The ASGI application callable to forward requests to.
154         span_details_callback: Callback which should return a string
155             and a tuple, representing the desired span name and a
156             dictionary with any additional span attributes to set.
157             Optional: Defaults to get_default_span_details.
158     """
159 
160     def __init__(self, app, excluded_urls=None, span_details_callback=None):
161         self.app = guarantee_single_callable(app)
162         self.tracer = trace.get_tracer(__name__, __version__)
163         self.span_details_callback = (
164             span_details_callback or get_default_span_details
165         )
166         self.excluded_urls = excluded_urls
167 
168     async def __call__(self, scope, receive, send):
169         """The ASGI application
170 
171         Args:
172             scope: A ASGI environment.
173             receive: An awaitable callable yielding dictionaries
174             send: An awaitable callable taking a single dictionary as argument.
175         """
176         if scope["type"] not in ("http", "websocket"):
177             return await self.app(scope, receive, send)
178 
179         _, _, url = get_host_port_url_tuple(scope)
180         if self.excluded_urls and self.excluded_urls.url_disabled(url):
181             return await self.app(scope, receive, send)
182 
183         token = context.attach(propagators.extract(carrier_getter, scope))
184         span_name, additional_attributes = self.span_details_callback(scope)
185 
186         try:
187             with self.tracer.start_as_current_span(
188                 span_name + " asgi", kind=trace.SpanKind.SERVER,
189             ) as span:
190                 if span.is_recording():
191                     attributes = collect_request_attributes(scope)
192                     attributes.update(additional_attributes)
193                     for key, value in attributes.items():
194                         span.set_attribute(key, value)
195 
196                 @wraps(receive)
197                 async def wrapped_receive():
198                     with self.tracer.start_as_current_span(
199                         span_name + " asgi." + scope["type"] + ".receive"
200                     ) as receive_span:
201                         message = await receive()
202                         if receive_span.is_recording():
203                             if message["type"] == "websocket.receive":
204                                 set_status_code(receive_span, 200)
205                             receive_span.set_attribute("type", message["type"])
206                     return message
207 
208                 @wraps(send)
209                 async def wrapped_send(message):
210                     with self.tracer.start_as_current_span(
211                         span_name + " asgi." + scope["type"] + ".send"
212                     ) as send_span:
213                         if send_span.is_recording():
214                             if message["type"] == "http.response.start":
215                                 status_code = message["status"]
216                                 set_status_code(send_span, status_code)
217                             elif message["type"] == "websocket.send":
218                                 set_status_code(send_span, 200)
219                             send_span.set_attribute("type", message["type"])
220                         await send(message)
221 
222                 await self.app(scope, wrapped_receive, wrapped_send)
223         finally:
224             context.detach(token)
225
```
--- 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/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
@@ -47,6 +47,11 @@
                 else None.
         """
         headers = carrier.get("headers")
+        if not headers:
+            return None
+
+        # asgi header keys are in lower case
+        key = key.lower()
         decoded = [
             _value.decode("utf8")
             for (_key, _value) in headers

{"golden_diff": "diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py\n--- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py\n+++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py\n@@ -47,6 +47,11 @@\n                 else None.\n         \"\"\"\n         headers = carrier.get(\"headers\")\n+        if not headers:\n+            return None\n+\n+        # asgi header keys are in lower case\n+        key = key.lower()\n         decoded = [\n             _value.decode(\"utf8\")\n             for (_key, _value) in headers\n", "issue": "Asgi request headers are not retrieved properly\nEnvironment: \r\nPython: python3.8\r\n\r\nfastapi==0.63.0\r\nopentelemetry-api==0.16b1\r\nopentelemetry-sdk==0.16b1\r\nopentelemetry-instrumentation-fastapi==0.16b1\r\nopentelemetry-exporter-google-cloud==0.16b1\r\nopentelemetry-tools-google-cloud==0.16b1\r\n\r\nWhen using `CloudTraceFormatPropagator` for [GCP](https://github.com/GoogleCloudPlatform/opentelemetry-operations-python), `X-Cloud-Trace-Context` header cannot be retrieved.\r\n\r\n**Steps to reproduce**\r\nDescribe exactly how to reproduce the error. Include a code sample if applicable.\r\n\r\n```\r\n# server.py\r\nimport uvicorn\r\nfrom fastapi import FastAPI, Request\r\nfrom opentelemetry import trace\r\nfrom opentelemetry.propagators import set_global_textmap\r\nfrom opentelemetry.instrumentation.fastapi import FastAPIInstrumentor\r\nfrom opentelemetry.sdk.trace import TracerProvider\r\nfrom opentelemetry.tools import cloud_trace_propagator\r\n\r\nset_global_textmap(cloud_trace_propagator.CloudTraceFormatPropagator())\r\n\r\napp = FastAPI()\r\n\r\ntracer = trace.get_tracer(\"test\")\r\nFastAPIInstrumentor.instrument_app(app)\r\n\r\n@app.get(\"/trace\")\r\nasync def test(r: Request):\r\n    with tracer.start_as_current_span(\"test\") as span:\r\n        trace_id = span.get_span_context().trace_id\r\n        print(f\"{trace_id:32x}\")  # should print trace ID from `X-Cloud-Trace-Context` header value\r\n\r\nuvicorn.run(app)\r\n```\r\n\r\n```\r\n# client.py\r\nimport requests\r\n\r\nr = requests.Session()\r\nr.headers.setdefault(\"X-Cloud-Trace-Context\",\r\n                        \"f3ef5c2ede256aa77491057e600eca11/15104302039794794507;o=1\")\r\nr.get(\"http://localhost:8000/trace\")\r\n```\r\n\r\n**What is the expected behavior?**\r\nPrinted value should be `f3ef5c2ede256aa77491057e600eca11` based from the header sent\r\n\r\n**What is the actual behavior?**\r\nA newly generated value everything `/trace` is called\r\n\r\n**Additional context**\r\n`X-Cloud-Trace-Context` header value is not retrieved properly in `CloudTraceFormatPropagator`\r\n\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used\non any ASGI framework (such as Django-channels / Quart) to track requests\ntiming through OpenTelemetry.\n\"\"\"\n\nimport typing\nimport urllib\nfrom functools import wraps\nfrom typing import Tuple\n\nfrom asgiref.compatibility import guarantee_single_callable\n\nfrom opentelemetry import context, propagators, trace\nfrom opentelemetry.instrumentation.asgi.version import __version__  # noqa\nfrom opentelemetry.instrumentation.utils import http_status_to_status_code\nfrom opentelemetry.trace.propagation.textmap import DictGetter\nfrom opentelemetry.trace.status import Status, StatusCode\n\n\nclass CarrierGetter(DictGetter):\n    def get(\n        self, carrier: dict, key: str\n    ) -> typing.Optional[typing.List[str]]:\n        \"\"\"Getter implementation to retrieve a HTTP header value from the ASGI\n        scope.\n\n        Args:\n            carrier: ASGI scope object\n            key: header name in scope\n        Returns:\n            A list with a single string with the header value if it exists,\n                else None.\n        \"\"\"\n        headers = carrier.get(\"headers\")\n        decoded = [\n            _value.decode(\"utf8\")\n            for (_key, _value) in headers\n            if _key.decode(\"utf8\") == key\n        ]\n        if not decoded:\n            return None\n        return decoded\n\n\ncarrier_getter = CarrierGetter()\n\n\ndef collect_request_attributes(scope):\n    \"\"\"Collects HTTP request attributes from the ASGI scope and returns a\n    dictionary to be used as span creation attributes.\"\"\"\n    server_host, port, http_url = get_host_port_url_tuple(scope)\n    query_string = scope.get(\"query_string\")\n    if query_string and http_url:\n        if isinstance(query_string, bytes):\n            query_string = query_string.decode(\"utf8\")\n        http_url = http_url + (\"?\" + urllib.parse.unquote(query_string))\n\n    result = {\n        \"http.scheme\": scope.get(\"scheme\"),\n        \"http.host\": server_host,\n        \"net.host.port\": port,\n        \"http.flavor\": scope.get(\"http_version\"),\n        \"http.target\": scope.get(\"path\"),\n        \"http.url\": http_url,\n    }\n    http_method = scope.get(\"method\")\n    if http_method:\n        result[\"http.method\"] = http_method\n\n    http_host_value_list = carrier_getter.get(scope, \"host\")\n    if http_host_value_list:\n        result[\"http.server_name\"] = \",\".join(http_host_value_list)\n    http_user_agent = carrier_getter.get(scope, \"user-agent\")\n    if http_user_agent:\n        result[\"http.user_agent\"] = http_user_agent[0]\n\n    if \"client\" in scope and scope[\"client\"] is not None:\n        result[\"net.peer.ip\"] = scope.get(\"client\")[0]\n        result[\"net.peer.port\"] = scope.get(\"client\")[1]\n\n    # remove None values\n    result = {k: v for k, v in result.items() if v is not None}\n\n    return result\n\n\ndef get_host_port_url_tuple(scope):\n    \"\"\"Returns (host, port, full_url) tuple.\n    \"\"\"\n    server = scope.get(\"server\") or [\"0.0.0.0\", 80]\n    port = server[1]\n    server_host = server[0] + (\":\" + str(port) if port != 80 else \"\")\n    full_path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n    http_url = scope.get(\"scheme\", \"http\") + \"://\" + server_host + full_path\n    return server_host, port, http_url\n\n\ndef set_status_code(span, status_code):\n    \"\"\"Adds HTTP response attributes to span using the status_code argument.\"\"\"\n    if not span.is_recording():\n        return\n    try:\n        status_code = int(status_code)\n    except ValueError:\n        span.set_status(\n            Status(\n                StatusCode.ERROR,\n                \"Non-integer HTTP status: \" + repr(status_code),\n            )\n        )\n    else:\n        span.set_attribute(\"http.status_code\", status_code)\n        span.set_status(Status(http_status_to_status_code(status_code)))\n\n\ndef get_default_span_details(scope: dict) -> Tuple[str, dict]:\n    \"\"\"Default implementation for span_details_callback\n\n    Args:\n        scope: the asgi scope dictionary\n\n    Returns:\n        a tuple of the span, and any attributes to attach to the\n        span.\n    \"\"\"\n    method_or_path = scope.get(\"method\") or scope.get(\"path\")\n\n    return method_or_path, {}\n\n\nclass OpenTelemetryMiddleware:\n    \"\"\"The ASGI application middleware.\n\n    This class is an ASGI middleware that starts and annotates spans for any\n    requests it is invoked with.\n\n    Args:\n        app: The ASGI application callable to forward requests to.\n        span_details_callback: Callback which should return a string\n            and a tuple, representing the desired span name and a\n            dictionary with any additional span attributes to set.\n            Optional: Defaults to get_default_span_details.\n    \"\"\"\n\n    def __init__(self, app, excluded_urls=None, span_details_callback=None):\n        self.app = guarantee_single_callable(app)\n        self.tracer = trace.get_tracer(__name__, __version__)\n        self.span_details_callback = (\n            span_details_callback or get_default_span_details\n        )\n        self.excluded_urls = excluded_urls\n\n    async def __call__(self, scope, receive, send):\n        \"\"\"The ASGI application\n\n        Args:\n            scope: A ASGI environment.\n            receive: An awaitable callable yielding dictionaries\n            send: An awaitable callable taking a single dictionary as argument.\n        \"\"\"\n        if scope[\"type\"] not in (\"http\", \"websocket\"):\n            return await self.app(scope, receive, send)\n\n        _, _, url = get_host_port_url_tuple(scope)\n        if self.excluded_urls and self.excluded_urls.url_disabled(url):\n            return await self.app(scope, receive, send)\n\n        token = context.attach(propagators.extract(carrier_getter, scope))\n        span_name, additional_attributes = self.span_details_callback(scope)\n\n        try:\n            with self.tracer.start_as_current_span(\n                span_name + \" asgi\", kind=trace.SpanKind.SERVER,\n            ) as span:\n                if span.is_recording():\n                    attributes = collect_request_attributes(scope)\n                    attributes.update(additional_attributes)\n                    for key, value in attributes.items():\n                        span.set_attribute(key, value)\n\n                @wraps(receive)\n                async def wrapped_receive():\n                    with self.tracer.start_as_current_span(\n                        span_name + \" asgi.\" + scope[\"type\"] + \".receive\"\n                    ) as receive_span:\n                        message = await receive()\n                        if receive_span.is_recording():\n                            if message[\"type\"] == \"websocket.receive\":\n                                set_status_code(receive_span, 200)\n                            receive_span.set_attribute(\"type\", message[\"type\"])\n                    return message\n\n                @wraps(send)\n                async def wrapped_send(message):\n                    with self.tracer.start_as_current_span(\n                        span_name + \" asgi.\" + scope[\"type\"] + \".send\"\n                    ) as send_span:\n                        if send_span.is_recording():\n                            if message[\"type\"] == \"http.response.start\":\n                                status_code = message[\"status\"]\n                                set_status_code(send_span, status_code)\n                            elif message[\"type\"] == \"websocket.send\":\n                                set_status_code(send_span, 200)\n                            send_span.set_attribute(\"type\", message[\"type\"])\n                        await send(message)\n\n                await self.app(scope, wrapped_receive, wrapped_send)\n        finally:\n            context.detach(token)\n", "path": "instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe opentelemetry-instrumentation-asgi package provides an ASGI middleware that can be used\non any ASGI framework (such as Django-channels / Quart) to track requests\ntiming through OpenTelemetry.\n\"\"\"\n\nimport typing\nimport urllib\nfrom functools import wraps\nfrom typing import Tuple\n\nfrom asgiref.compatibility import guarantee_single_callable\n\nfrom opentelemetry import context, propagators, trace\nfrom opentelemetry.instrumentation.asgi.version import __version__  # noqa\nfrom opentelemetry.instrumentation.utils import http_status_to_status_code\nfrom opentelemetry.trace.propagation.textmap import DictGetter\nfrom opentelemetry.trace.status import Status, StatusCode\n\n\nclass CarrierGetter(DictGetter):\n    def get(\n        self, carrier: dict, key: str\n    ) -> typing.Optional[typing.List[str]]:\n        \"\"\"Getter implementation to retrieve a HTTP header value from the ASGI\n        scope.\n\n        Args:\n            carrier: ASGI scope object\n            key: header name in scope\n        Returns:\n            A list with a single string with the header value if it exists,\n                else None.\n        \"\"\"\n        headers = carrier.get(\"headers\")\n        if not headers:\n            return None\n\n        # asgi header keys are in lower case\n        key = key.lower()\n        decoded = [\n            _value.decode(\"utf8\")\n            for (_key, _value) in headers\n            if _key.decode(\"utf8\") == key\n        ]\n        if not decoded:\n            return None\n        return decoded\n\n\ncarrier_getter = CarrierGetter()\n\n\ndef collect_request_attributes(scope):\n    \"\"\"Collects HTTP request attributes from the ASGI scope and returns a\n    dictionary to be used as span creation attributes.\"\"\"\n    server_host, port, http_url = get_host_port_url_tuple(scope)\n    query_string = scope.get(\"query_string\")\n    if query_string and http_url:\n        if isinstance(query_string, bytes):\n            query_string = query_string.decode(\"utf8\")\n        http_url = http_url + (\"?\" + urllib.parse.unquote(query_string))\n\n    result = {\n        \"http.scheme\": scope.get(\"scheme\"),\n        \"http.host\": server_host,\n        \"net.host.port\": port,\n        \"http.flavor\": scope.get(\"http_version\"),\n        \"http.target\": scope.get(\"path\"),\n        \"http.url\": http_url,\n    }\n    http_method = scope.get(\"method\")\n    if http_method:\n        result[\"http.method\"] = http_method\n\n    http_host_value_list = carrier_getter.get(scope, \"host\")\n    if http_host_value_list:\n        result[\"http.server_name\"] = \",\".join(http_host_value_list)\n    http_user_agent = carrier_getter.get(scope, \"user-agent\")\n    if http_user_agent:\n        result[\"http.user_agent\"] = http_user_agent[0]\n\n    if \"client\" in scope and scope[\"client\"] is not None:\n        result[\"net.peer.ip\"] = scope.get(\"client\")[0]\n        result[\"net.peer.port\"] = scope.get(\"client\")[1]\n\n    # remove None values\n    result = {k: v for k, v in result.items() if v is not None}\n\n    return result\n\n\ndef get_host_port_url_tuple(scope):\n    \"\"\"Returns (host, port, full_url) tuple.\n    \"\"\"\n    server = scope.get(\"server\") or [\"0.0.0.0\", 80]\n    port = server[1]\n    server_host = server[0] + (\":\" + str(port) if port != 80 else \"\")\n    full_path = scope.get(\"root_path\", \"\") + scope.get(\"path\", \"\")\n    http_url = scope.get(\"scheme\", \"http\") + \"://\" + server_host + full_path\n    return server_host, port, http_url\n\n\ndef set_status_code(span, status_code):\n    \"\"\"Adds HTTP response attributes to span using the status_code argument.\"\"\"\n    if not span.is_recording():\n        return\n    try:\n        status_code = int(status_code)\n    except ValueError:\n        span.set_status(\n            Status(\n                StatusCode.ERROR,\n                \"Non-integer HTTP status: \" + repr(status_code),\n            )\n        )\n    else:\n        span.set_attribute(\"http.status_code\", status_code)\n        span.set_status(Status(http_status_to_status_code(status_code)))\n\n\ndef get_default_span_details(scope: dict) -> Tuple[str, dict]:\n    \"\"\"Default implementation for span_details_callback\n\n    Args:\n        scope: the asgi scope dictionary\n\n    Returns:\n        a tuple of the span, and any attributes to attach to the\n        span.\n    \"\"\"\n    method_or_path = scope.get(\"method\") or scope.get(\"path\")\n\n    return method_or_path, {}\n\n\nclass OpenTelemetryMiddleware:\n    \"\"\"The ASGI application middleware.\n\n    This class is an ASGI middleware that starts and annotates spans for any\n    requests it is invoked with.\n\n    Args:\n        app: The ASGI application callable to forward requests to.\n        span_details_callback: Callback which should return a string\n            and a tuple, representing the desired span name and a\n            dictionary with any additional span attributes to set.\n            Optional: Defaults to get_default_span_details.\n    \"\"\"\n\n    def __init__(self, app, excluded_urls=None, span_details_callback=None):\n        self.app = guarantee_single_callable(app)\n        self.tracer = trace.get_tracer(__name__, __version__)\n        self.span_details_callback = (\n            span_details_callback or get_default_span_details\n        )\n        self.excluded_urls = excluded_urls\n\n    async def __call__(self, scope, receive, send):\n        \"\"\"The ASGI application\n\n        Args:\n            scope: A ASGI environment.\n            receive: An awaitable callable yielding dictionaries\n            send: An awaitable callable taking a single dictionary as argument.\n        \"\"\"\n        if scope[\"type\"] not in (\"http\", \"websocket\"):\n            return await self.app(scope, receive, send)\n\n        _, _, url = get_host_port_url_tuple(scope)\n        if self.excluded_urls and self.excluded_urls.url_disabled(url):\n            return await self.app(scope, receive, send)\n\n        token = context.attach(propagators.extract(carrier_getter, scope))\n        span_name, additional_attributes = self.span_details_callback(scope)\n\n        try:\n            with self.tracer.start_as_current_span(\n                span_name + \" asgi\", kind=trace.SpanKind.SERVER,\n            ) as span:\n                if span.is_recording():\n                    attributes = collect_request_attributes(scope)\n                    attributes.update(additional_attributes)\n                    for key, value in attributes.items():\n                        span.set_attribute(key, value)\n\n                @wraps(receive)\n                async def wrapped_receive():\n                    with self.tracer.start_as_current_span(\n                        span_name + \" asgi.\" + scope[\"type\"] + \".receive\"\n                    ) as receive_span:\n                        message = await receive()\n                        if receive_span.is_recording():\n                            if message[\"type\"] == \"websocket.receive\":\n                                set_status_code(receive_span, 200)\n                            receive_span.set_attribute(\"type\", message[\"type\"])\n                    return message\n\n                @wraps(send)\n                async def wrapped_send(message):\n                    with self.tracer.start_as_current_span(\n                        span_name + \" asgi.\" + scope[\"type\"] + \".send\"\n                    ) as send_span:\n                        if send_span.is_recording():\n                            if message[\"type\"] == \"http.response.start\":\n                                status_code = message[\"status\"]\n                                set_status_code(send_span, status_code)\n                            elif message[\"type\"] == \"websocket.send\":\n                                set_status_code(send_span, 200)\n                            send_span.set_attribute(\"type\", message[\"type\"])\n                        await send(message)\n\n                await self.app(scope, wrapped_receive, wrapped_send)\n        finally:\n            context.detach(token)\n", "path": "instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py"}]}