We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
builtins.AttributeError: instead of intended log message
Hello,

Because I had set up an incorrect url, or an url that was not possible to access due to limited api token access I got this error message:
```
          File "/usr/local/lib/python3.10/site-packages/buildbot/changes/github.py", line 142, in _getPulls
            f"while loading {result.url}")
        builtins.AttributeError: '_Response' object has no attribute 'url'
```

However, looking at the code the error should probably be something more like:
```
"GitHubPullrequestPoller error {error_code} '{error message}' while loading {some_url}")
```

Is the attribute error the intended behaviour? If not then I guess I would do some kind of patch for it (i.e. checking for existing url attribute).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `master/buildbot/util/httpclientservice.py`
Content:
```
1 # This file is part of Buildbot. Buildbot is free software: you can)
2 # redistribute it and/or modify it under the terms of the GNU General Public
3 # License as published by the Free Software Foundation, version 2.
4 #
5 # This program is distributed in the hope that it will be useful, but WITHOUT
6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
7 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
8 # details.
9 #
10 # You should have received a copy of the GNU General Public License along with
11 # this program; if not, write to the Free Software Foundation, Inc., 51
12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
13 #
14 # Copyright Buildbot Team Members
15 
16 import json as jsonmodule
17 import textwrap
18 
19 from twisted.internet import defer
20 from twisted.logger import Logger
21 from twisted.web.client import Agent
22 from twisted.web.client import HTTPConnectionPool
23 from zope.interface import implementer
24 
25 from buildbot import config
26 from buildbot.interfaces import IHttpResponse
27 from buildbot.util import service
28 from buildbot.util import toJson
29 from buildbot.util import unicode2bytes
30 
31 try:
32     import txrequests
33 except ImportError:
34     txrequests = None
35 
36 try:
37     import treq
38     implementer(IHttpResponse)(treq.response._Response)
39 
40 except ImportError:
41     treq = None
42 
43 log = Logger()
44 
45 
46 @implementer(IHttpResponse)
47 class TxRequestsResponseWrapper:
48 
49     def __init__(self, res):
50         self._res = res
51 
52     def content(self):
53         return defer.succeed(self._res.content)
54 
55     def json(self):
56         return defer.succeed(self._res.json())
57 
58     @property
59     def code(self):
60         return self._res.status_code
61 
62     @property
63     def url(self):
64         return self._res.url
65 
66 
67 class HTTPClientService(service.SharedService):
68     """A SharedService class that can make http requests to remote services.
69 
70     I can use either txrequests or treq, depending on what I find installed
71 
72     I provide minimal get/post/put/delete API with automatic baseurl joining, and json data encoding
73     that is suitable for use from buildbot services.
74     """
75     TREQ_PROS_AND_CONS = textwrap.dedent("""
76        txrequests is based on requests and is probably a bit more mature, but it requires threads
77        to run, so has more overhead.
78        treq is better integrated in twisted and is more and more feature equivalent
79 
80        txrequests is 2.8x slower than treq due to the use of threads.
81 
82        http://treq.readthedocs.io/en/latest/#feature-parity-w-requests
83        pip install txrequests
84            or
85        pip install treq
86     """)
87     # Those could be in theory be overridden in master.cfg by using
88     # import buildbot.util.httpclientservice.HTTPClientService.PREFER_TREQ = True
89     # We prefer at the moment keeping it simple
90     PREFER_TREQ = False
91     MAX_THREADS = 5
92 
93     def __init__(self, base_url, auth=None, headers=None, verify=None, debug=False,
94                  skipEncoding=False):
95         assert not base_url.endswith(
96             "/"), "baseurl should not end with /: " + base_url
97         super().__init__()
98         self._base_url = base_url
99         self._auth = auth
100         self._headers = headers
101         self._pool = None
102         self._session = None
103         self.verify = verify
104         self.debug = debug
105         self.skipEncoding = skipEncoding
106 
107     def updateHeaders(self, headers):
108         if self._headers is None:
109             self._headers = {}
110         self._headers.update(headers)
111 
112     @staticmethod
113     def checkAvailable(from_module):
114         """Call me at checkConfig time to properly report config error
115            if neither txrequests or treq is installed
116         """
117         if txrequests is None and treq is None:
118             config.error(f"neither txrequests nor treq is installed, but {from_module} is "
119                          f"requiring it\n\n{HTTPClientService.TREQ_PROS_AND_CONS}")
120 
121     def startService(self):
122         # treq only supports basicauth, so we force txrequests if the auth is
123         # something else
124         if self._auth is not None and not isinstance(self._auth, tuple):
125             self.PREFER_TREQ = False
126         if txrequests is not None and not self.PREFER_TREQ:
127             self._session = txrequests.Session()
128             self._doRequest = self._doTxRequest
129         elif treq is None:
130             raise ImportError("{classname} requires either txrequest or treq install."
131                               " Users should call {classname}.checkAvailable() during checkConfig()"
132                               " to properly alert the user.".format(
133                                   classname=self.__class__.__name__))
134         else:
135             self._doRequest = self._doTReq
136             self._pool = HTTPConnectionPool(self.master.reactor)
137             self._pool.maxPersistentPerHost = self.MAX_THREADS
138             self._agent = Agent(self.master.reactor, pool=self._pool)
139         return super().startService()
140 
141     @defer.inlineCallbacks
142     def stopService(self):
143         if self._session:
144             yield self._session.close()
145         if self._pool:
146             yield self._pool.closeCachedConnections()
147         yield super().stopService()
148 
149     def _prepareRequest(self, ep, kwargs):
150         if ep.startswith('http://') or ep.startswith('https://'):
151             url = ep
152         else:
153             assert ep == "" or ep.startswith("/"), "ep should start with /: " + ep
154             url = self._base_url + ep
155         if self._auth is not None and 'auth' not in kwargs:
156             kwargs['auth'] = self._auth
157         headers = kwargs.get('headers', {})
158         if self._headers is not None:
159             headers.update(self._headers)
160         kwargs['headers'] = headers
161 
162         # we manually do the json encoding in order to automatically convert timestamps
163         # for txrequests and treq
164         json = kwargs.pop('json', None)
165         if isinstance(json, (dict, list)):
166             jsonStr = jsonmodule.dumps(json, default=toJson)
167             kwargs['headers']['Content-Type'] = 'application/json'
168             if self.skipEncoding:
169                 kwargs['data'] = jsonStr
170             else:
171                 jsonBytes = unicode2bytes(jsonStr)
172                 kwargs['data'] = jsonBytes
173         return url, kwargs
174 
175     @defer.inlineCallbacks
176     def _doTxRequest(self, method, ep, **kwargs):
177         url, kwargs = yield self._prepareRequest(ep, kwargs)
178         if self.debug:
179             log.debug("http {url} {kwargs}", url=url, kwargs=kwargs)
180 
181         def readContent(session, res):
182             # this forces reading of the content inside the thread
183             res.content
184             if self.debug:
185                 log.debug("==> {code}: {content}", code=res.status_code, content=res.content)
186             return res
187 
188         # read the whole content in the thread
189         kwargs['background_callback'] = readContent
190         if self.verify is False:
191             kwargs['verify'] = False
192 
193         res = yield self._session.request(method, url, **kwargs)
194         return IHttpResponse(TxRequestsResponseWrapper(res))
195 
196     @defer.inlineCallbacks
197     def _doTReq(self, method, ep, **kwargs):
198         url, kwargs = yield self._prepareRequest(ep, kwargs)
199         # treq requires header values to be an array
200         kwargs['headers'] = {k: [v]
201                              for k, v in kwargs['headers'].items()}
202         kwargs['agent'] = self._agent
203 
204         res = yield getattr(treq, method)(url, **kwargs)
205         return IHttpResponse(res)
206 
207     # lets be nice to the auto completers, and don't generate that code
208     def get(self, ep, **kwargs):
209         return self._doRequest('get', ep, **kwargs)
210 
211     def put(self, ep, **kwargs):
212         return self._doRequest('put', ep, **kwargs)
213 
214     def delete(self, ep, **kwargs):
215         return self._doRequest('delete', ep, **kwargs)
216 
217     def post(self, ep, **kwargs):
218         return self._doRequest('post', ep, **kwargs)
219
```
--- 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/master/buildbot/util/httpclientservice.py b/master/buildbot/util/httpclientservice.py
--- a/master/buildbot/util/httpclientservice.py
+++ b/master/buildbot/util/httpclientservice.py
@@ -35,8 +35,6 @@
 
 try:
     import treq
-    implementer(IHttpResponse)(treq.response._Response)
-
 except ImportError:
     treq = None
 
@@ -64,6 +62,27 @@
         return self._res.url
 
 
+@implementer(IHttpResponse)
+class TreqResponseWrapper:
+
+    def __init__(self, res):
+        self._res = res
+
+    def content(self):
+        return self._res.content()
+
+    def json(self):
+        return self._res.json()
+
+    @property
+    def code(self):
+        return self._res.code
+
+    @property
+    def url(self):
+        return self._res.request.absoluteURI.decode()
+
+
 class HTTPClientService(service.SharedService):
     """A SharedService class that can make http requests to remote services.
 
@@ -202,7 +221,7 @@
         kwargs['agent'] = self._agent
 
         res = yield getattr(treq, method)(url, **kwargs)
-        return IHttpResponse(res)
+        return IHttpResponse(TreqResponseWrapper(res))
 
     # lets be nice to the auto completers, and don't generate that code
     def get(self, ep, **kwargs):

{"golden_diff": "diff --git a/master/buildbot/util/httpclientservice.py b/master/buildbot/util/httpclientservice.py\n--- a/master/buildbot/util/httpclientservice.py\n+++ b/master/buildbot/util/httpclientservice.py\n@@ -35,8 +35,6 @@\n \n try:\n     import treq\n-    implementer(IHttpResponse)(treq.response._Response)\n-\n except ImportError:\n     treq = None\n \n@@ -64,6 +62,27 @@\n         return self._res.url\n \n \n+@implementer(IHttpResponse)\n+class TreqResponseWrapper:\n+\n+    def __init__(self, res):\n+        self._res = res\n+\n+    def content(self):\n+        return self._res.content()\n+\n+    def json(self):\n+        return self._res.json()\n+\n+    @property\n+    def code(self):\n+        return self._res.code\n+\n+    @property\n+    def url(self):\n+        return self._res.request.absoluteURI.decode()\n+\n+\n class HTTPClientService(service.SharedService):\n     \"\"\"A SharedService class that can make http requests to remote services.\n \n@@ -202,7 +221,7 @@\n         kwargs['agent'] = self._agent\n \n         res = yield getattr(treq, method)(url, **kwargs)\n-        return IHttpResponse(res)\n+        return IHttpResponse(TreqResponseWrapper(res))\n \n     # lets be nice to the auto completers, and don't generate that code\n     def get(self, ep, **kwargs):\n", "issue": "builtins.AttributeError: instead of intended log message\nHello,\r\n\r\nBecause I had set up an incorrect url, or an url that was not possible to access due to limited api token access I got this error message:\r\n```\r\n          File \"/usr/local/lib/python3.10/site-packages/buildbot/changes/github.py\", line 142, in _getPulls\r\n            f\"while loading {result.url}\")\r\n        builtins.AttributeError: '_Response' object has no attribute 'url'\r\n```\r\n\r\nHowever, looking at the code the error should probably be something more like:\r\n```\r\n\"GitHubPullrequestPoller error {error_code} '{error message}' while loading {some_url}\")\r\n```\r\n\r\nIs the attribute error the intended behaviour? If not then I guess I would do some kind of patch for it (i.e. checking for existing url attribute).\n", "before_files": [{"content": "# This file is part of Buildbot. Buildbot is free software: you can)\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nimport json as jsonmodule\nimport textwrap\n\nfrom twisted.internet import defer\nfrom twisted.logger import Logger\nfrom twisted.web.client import Agent\nfrom twisted.web.client import HTTPConnectionPool\nfrom zope.interface import implementer\n\nfrom buildbot import config\nfrom buildbot.interfaces import IHttpResponse\nfrom buildbot.util import service\nfrom buildbot.util import toJson\nfrom buildbot.util import unicode2bytes\n\ntry:\n    import txrequests\nexcept ImportError:\n    txrequests = None\n\ntry:\n    import treq\n    implementer(IHttpResponse)(treq.response._Response)\n\nexcept ImportError:\n    treq = None\n\nlog = Logger()\n\n\n@implementer(IHttpResponse)\nclass TxRequestsResponseWrapper:\n\n    def __init__(self, res):\n        self._res = res\n\n    def content(self):\n        return defer.succeed(self._res.content)\n\n    def json(self):\n        return defer.succeed(self._res.json())\n\n    @property\n    def code(self):\n        return self._res.status_code\n\n    @property\n    def url(self):\n        return self._res.url\n\n\nclass HTTPClientService(service.SharedService):\n    \"\"\"A SharedService class that can make http requests to remote services.\n\n    I can use either txrequests or treq, depending on what I find installed\n\n    I provide minimal get/post/put/delete API with automatic baseurl joining, and json data encoding\n    that is suitable for use from buildbot services.\n    \"\"\"\n    TREQ_PROS_AND_CONS = textwrap.dedent(\"\"\"\n       txrequests is based on requests and is probably a bit more mature, but it requires threads\n       to run, so has more overhead.\n       treq is better integrated in twisted and is more and more feature equivalent\n\n       txrequests is 2.8x slower than treq due to the use of threads.\n\n       http://treq.readthedocs.io/en/latest/#feature-parity-w-requests\n       pip install txrequests\n           or\n       pip install treq\n    \"\"\")\n    # Those could be in theory be overridden in master.cfg by using\n    # import buildbot.util.httpclientservice.HTTPClientService.PREFER_TREQ = True\n    # We prefer at the moment keeping it simple\n    PREFER_TREQ = False\n    MAX_THREADS = 5\n\n    def __init__(self, base_url, auth=None, headers=None, verify=None, debug=False,\n                 skipEncoding=False):\n        assert not base_url.endswith(\n            \"/\"), \"baseurl should not end with /: \" + base_url\n        super().__init__()\n        self._base_url = base_url\n        self._auth = auth\n        self._headers = headers\n        self._pool = None\n        self._session = None\n        self.verify = verify\n        self.debug = debug\n        self.skipEncoding = skipEncoding\n\n    def updateHeaders(self, headers):\n        if self._headers is None:\n            self._headers = {}\n        self._headers.update(headers)\n\n    @staticmethod\n    def checkAvailable(from_module):\n        \"\"\"Call me at checkConfig time to properly report config error\n           if neither txrequests or treq is installed\n        \"\"\"\n        if txrequests is None and treq is None:\n            config.error(f\"neither txrequests nor treq is installed, but {from_module} is \"\n                         f\"requiring it\\n\\n{HTTPClientService.TREQ_PROS_AND_CONS}\")\n\n    def startService(self):\n        # treq only supports basicauth, so we force txrequests if the auth is\n        # something else\n        if self._auth is not None and not isinstance(self._auth, tuple):\n            self.PREFER_TREQ = False\n        if txrequests is not None and not self.PREFER_TREQ:\n            self._session = txrequests.Session()\n            self._doRequest = self._doTxRequest\n        elif treq is None:\n            raise ImportError(\"{classname} requires either txrequest or treq install.\"\n                              \" Users should call {classname}.checkAvailable() during checkConfig()\"\n                              \" to properly alert the user.\".format(\n                                  classname=self.__class__.__name__))\n        else:\n            self._doRequest = self._doTReq\n            self._pool = HTTPConnectionPool(self.master.reactor)\n            self._pool.maxPersistentPerHost = self.MAX_THREADS\n            self._agent = Agent(self.master.reactor, pool=self._pool)\n        return super().startService()\n\n    @defer.inlineCallbacks\n    def stopService(self):\n        if self._session:\n            yield self._session.close()\n        if self._pool:\n            yield self._pool.closeCachedConnections()\n        yield super().stopService()\n\n    def _prepareRequest(self, ep, kwargs):\n        if ep.startswith('http://') or ep.startswith('https://'):\n            url = ep\n        else:\n            assert ep == \"\" or ep.startswith(\"/\"), \"ep should start with /: \" + ep\n            url = self._base_url + ep\n        if self._auth is not None and 'auth' not in kwargs:\n            kwargs['auth'] = self._auth\n        headers = kwargs.get('headers', {})\n        if self._headers is not None:\n            headers.update(self._headers)\n        kwargs['headers'] = headers\n\n        # we manually do the json encoding in order to automatically convert timestamps\n        # for txrequests and treq\n        json = kwargs.pop('json', None)\n        if isinstance(json, (dict, list)):\n            jsonStr = jsonmodule.dumps(json, default=toJson)\n            kwargs['headers']['Content-Type'] = 'application/json'\n            if self.skipEncoding:\n                kwargs['data'] = jsonStr\n            else:\n                jsonBytes = unicode2bytes(jsonStr)\n                kwargs['data'] = jsonBytes\n        return url, kwargs\n\n    @defer.inlineCallbacks\n    def _doTxRequest(self, method, ep, **kwargs):\n        url, kwargs = yield self._prepareRequest(ep, kwargs)\n        if self.debug:\n            log.debug(\"http {url} {kwargs}\", url=url, kwargs=kwargs)\n\n        def readContent(session, res):\n            # this forces reading of the content inside the thread\n            res.content\n            if self.debug:\n                log.debug(\"==> {code}: {content}\", code=res.status_code, content=res.content)\n            return res\n\n        # read the whole content in the thread\n        kwargs['background_callback'] = readContent\n        if self.verify is False:\n            kwargs['verify'] = False\n\n        res = yield self._session.request(method, url, **kwargs)\n        return IHttpResponse(TxRequestsResponseWrapper(res))\n\n    @defer.inlineCallbacks\n    def _doTReq(self, method, ep, **kwargs):\n        url, kwargs = yield self._prepareRequest(ep, kwargs)\n        # treq requires header values to be an array\n        kwargs['headers'] = {k: [v]\n                             for k, v in kwargs['headers'].items()}\n        kwargs['agent'] = self._agent\n\n        res = yield getattr(treq, method)(url, **kwargs)\n        return IHttpResponse(res)\n\n    # lets be nice to the auto completers, and don't generate that code\n    def get(self, ep, **kwargs):\n        return self._doRequest('get', ep, **kwargs)\n\n    def put(self, ep, **kwargs):\n        return self._doRequest('put', ep, **kwargs)\n\n    def delete(self, ep, **kwargs):\n        return self._doRequest('delete', ep, **kwargs)\n\n    def post(self, ep, **kwargs):\n        return self._doRequest('post', ep, **kwargs)\n", "path": "master/buildbot/util/httpclientservice.py"}], "after_files": [{"content": "# This file is part of Buildbot. Buildbot is free software: you can)\n# redistribute it and/or modify it under the terms of the GNU General Public\n# License as published by the Free Software Foundation, version 2.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more\n# details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51\n# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n#\n# Copyright Buildbot Team Members\n\nimport json as jsonmodule\nimport textwrap\n\nfrom twisted.internet import defer\nfrom twisted.logger import Logger\nfrom twisted.web.client import Agent\nfrom twisted.web.client import HTTPConnectionPool\nfrom zope.interface import implementer\n\nfrom buildbot import config\nfrom buildbot.interfaces import IHttpResponse\nfrom buildbot.util import service\nfrom buildbot.util import toJson\nfrom buildbot.util import unicode2bytes\n\ntry:\n    import txrequests\nexcept ImportError:\n    txrequests = None\n\ntry:\n    import treq\nexcept ImportError:\n    treq = None\n\nlog = Logger()\n\n\n@implementer(IHttpResponse)\nclass TxRequestsResponseWrapper:\n\n    def __init__(self, res):\n        self._res = res\n\n    def content(self):\n        return defer.succeed(self._res.content)\n\n    def json(self):\n        return defer.succeed(self._res.json())\n\n    @property\n    def code(self):\n        return self._res.status_code\n\n    @property\n    def url(self):\n        return self._res.url\n\n\n@implementer(IHttpResponse)\nclass TreqResponseWrapper:\n\n    def __init__(self, res):\n        self._res = res\n\n    def content(self):\n        return self._res.content()\n\n    def json(self):\n        return self._res.json()\n\n    @property\n    def code(self):\n        return self._res.code\n\n    @property\n    def url(self):\n        return self._res.request.absoluteURI.decode()\n\n\nclass HTTPClientService(service.SharedService):\n    \"\"\"A SharedService class that can make http requests to remote services.\n\n    I can use either txrequests or treq, depending on what I find installed\n\n    I provide minimal get/post/put/delete API with automatic baseurl joining, and json data encoding\n    that is suitable for use from buildbot services.\n    \"\"\"\n    TREQ_PROS_AND_CONS = textwrap.dedent(\"\"\"\n       txrequests is based on requests and is probably a bit more mature, but it requires threads\n       to run, so has more overhead.\n       treq is better integrated in twisted and is more and more feature equivalent\n\n       txrequests is 2.8x slower than treq due to the use of threads.\n\n       http://treq.readthedocs.io/en/latest/#feature-parity-w-requests\n       pip install txrequests\n           or\n       pip install treq\n    \"\"\")\n    # Those could be in theory be overridden in master.cfg by using\n    # import buildbot.util.httpclientservice.HTTPClientService.PREFER_TREQ = True\n    # We prefer at the moment keeping it simple\n    PREFER_TREQ = False\n    MAX_THREADS = 5\n\n    def __init__(self, base_url, auth=None, headers=None, verify=None, debug=False,\n                 skipEncoding=False):\n        assert not base_url.endswith(\n            \"/\"), \"baseurl should not end with /: \" + base_url\n        super().__init__()\n        self._base_url = base_url\n        self._auth = auth\n        self._headers = headers\n        self._pool = None\n        self._session = None\n        self.verify = verify\n        self.debug = debug\n        self.skipEncoding = skipEncoding\n\n    def updateHeaders(self, headers):\n        if self._headers is None:\n            self._headers = {}\n        self._headers.update(headers)\n\n    @staticmethod\n    def checkAvailable(from_module):\n        \"\"\"Call me at checkConfig time to properly report config error\n           if neither txrequests or treq is installed\n        \"\"\"\n        if txrequests is None and treq is None:\n            config.error(f\"neither txrequests nor treq is installed, but {from_module} is \"\n                         f\"requiring it\\n\\n{HTTPClientService.TREQ_PROS_AND_CONS}\")\n\n    def startService(self):\n        # treq only supports basicauth, so we force txrequests if the auth is\n        # something else\n        if self._auth is not None and not isinstance(self._auth, tuple):\n            self.PREFER_TREQ = False\n        if txrequests is not None and not self.PREFER_TREQ:\n            self._session = txrequests.Session()\n            self._doRequest = self._doTxRequest\n        elif treq is None:\n            raise ImportError(\"{classname} requires either txrequest or treq install.\"\n                              \" Users should call {classname}.checkAvailable() during checkConfig()\"\n                              \" to properly alert the user.\".format(\n                                  classname=self.__class__.__name__))\n        else:\n            self._doRequest = self._doTReq\n            self._pool = HTTPConnectionPool(self.master.reactor)\n            self._pool.maxPersistentPerHost = self.MAX_THREADS\n            self._agent = Agent(self.master.reactor, pool=self._pool)\n        return super().startService()\n\n    @defer.inlineCallbacks\n    def stopService(self):\n        if self._session:\n            yield self._session.close()\n        if self._pool:\n            yield self._pool.closeCachedConnections()\n        yield super().stopService()\n\n    def _prepareRequest(self, ep, kwargs):\n        if ep.startswith('http://') or ep.startswith('https://'):\n            url = ep\n        else:\n            assert ep == \"\" or ep.startswith(\"/\"), \"ep should start with /: \" + ep\n            url = self._base_url + ep\n        if self._auth is not None and 'auth' not in kwargs:\n            kwargs['auth'] = self._auth\n        headers = kwargs.get('headers', {})\n        if self._headers is not None:\n            headers.update(self._headers)\n        kwargs['headers'] = headers\n\n        # we manually do the json encoding in order to automatically convert timestamps\n        # for txrequests and treq\n        json = kwargs.pop('json', None)\n        if isinstance(json, (dict, list)):\n            jsonStr = jsonmodule.dumps(json, default=toJson)\n            kwargs['headers']['Content-Type'] = 'application/json'\n            if self.skipEncoding:\n                kwargs['data'] = jsonStr\n            else:\n                jsonBytes = unicode2bytes(jsonStr)\n                kwargs['data'] = jsonBytes\n        return url, kwargs\n\n    @defer.inlineCallbacks\n    def _doTxRequest(self, method, ep, **kwargs):\n        url, kwargs = yield self._prepareRequest(ep, kwargs)\n        if self.debug:\n            log.debug(\"http {url} {kwargs}\", url=url, kwargs=kwargs)\n\n        def readContent(session, res):\n            # this forces reading of the content inside the thread\n            res.content\n            if self.debug:\n                log.debug(\"==> {code}: {content}\", code=res.status_code, content=res.content)\n            return res\n\n        # read the whole content in the thread\n        kwargs['background_callback'] = readContent\n        if self.verify is False:\n            kwargs['verify'] = False\n\n        res = yield self._session.request(method, url, **kwargs)\n        return IHttpResponse(TxRequestsResponseWrapper(res))\n\n    @defer.inlineCallbacks\n    def _doTReq(self, method, ep, **kwargs):\n        url, kwargs = yield self._prepareRequest(ep, kwargs)\n        # treq requires header values to be an array\n        kwargs['headers'] = {k: [v]\n                             for k, v in kwargs['headers'].items()}\n        kwargs['agent'] = self._agent\n\n        res = yield getattr(treq, method)(url, **kwargs)\n        return IHttpResponse(TreqResponseWrapper(res))\n\n    # lets be nice to the auto completers, and don't generate that code\n    def get(self, ep, **kwargs):\n        return self._doRequest('get', ep, **kwargs)\n\n    def put(self, ep, **kwargs):\n        return self._doRequest('put', ep, **kwargs)\n\n    def delete(self, ep, **kwargs):\n        return self._doRequest('delete', ep, **kwargs)\n\n    def post(self, ep, **kwargs):\n        return self._doRequest('post', ep, **kwargs)\n", "path": "master/buildbot/util/httpclientservice.py"}]}