##// END OF EJS Templates
fix(pycurl): report nicer error log on failed calls in pycurl
super-admin -
r5324:d7dccd8e default
parent child Browse files
Show More
@@ -1,189 +1,194 b''
1 1 # Copyright (C) 2014-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 """
20 20 Various version Control System version lib (vcs) management abstraction layer
21 21 for Python. Build with server client architecture.
22 22 """
23 23 import io
24 24 import atexit
25 25 import logging
26 26
27 27 import rhodecode
28 28 from rhodecode.lib.str_utils import safe_bytes
29 29 from rhodecode.lib.vcs.conf import settings
30 30 from rhodecode.lib.vcs.backends import get_vcs_instance, get_backend
31 31 from rhodecode.lib.vcs.exceptions import (
32 32 VCSError, RepositoryError, CommitError, VCSCommunicationError)
33 33
34 34 __all__ = [
35 35 'get_vcs_instance', 'get_backend',
36 36 'VCSError', 'RepositoryError', 'CommitError', 'VCSCommunicationError',
37 37 'CurlSession', 'CurlResponse'
38 38 ]
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42 # The pycurl library directly accesses C API functions and is not patched by
43 43 # gevent. This will potentially lead to deadlocks due to incompatibility to
44 44 # gevent. Therefore we check if gevent is active and import a gevent compatible
45 45 # wrapper in that case.
46 46 try:
47 47 from gevent import monkey
48 48 if monkey.is_module_patched('__builtin__'):
49 49 import geventcurl as pycurl
50 50 log.debug('Using gevent comapatible pycurl: %s', pycurl)
51 51 else:
52 52 import pycurl
53 53 except ImportError:
54 54 import pycurl
55 55
56 56
57 57 def connect_http(server_and_port):
58 58 log.debug('Initialized VCSServer connections to %s.', server_and_port)
59 59
60 60 from rhodecode.lib.vcs import connection, client_http
61 61 from rhodecode.lib.middleware.utils import scm_app
62 62
63 63 session_factory = client_http.ThreadlocalSessionFactory()
64 64
65 65 connection.Git = client_http.RemoteVCSMaker(
66 66 server_and_port, '/git', 'git', session_factory)
67 67 connection.Hg = client_http.RemoteVCSMaker(
68 68 server_and_port, '/hg', 'hg', session_factory)
69 69 connection.Svn = client_http.RemoteVCSMaker(
70 70 server_and_port, '/svn', 'svn', session_factory)
71 71 connection.Service = client_http.ServiceConnection(
72 72 server_and_port, '/_service', session_factory)
73 73
74 74 scm_app.HG_REMOTE_WSGI = client_http.VcsHttpProxy(
75 75 server_and_port, '/proxy/hg')
76 76 scm_app.GIT_REMOTE_WSGI = client_http.VcsHttpProxy(
77 77 server_and_port, '/proxy/git')
78 78
79 79 @atexit.register
80 80 def free_connection_resources():
81 81 connection.Git = None
82 82 connection.Hg = None
83 83 connection.Svn = None
84 84 connection.Service = None
85 85
86 86
87 87 def connect_vcs(server_and_port, protocol):
88 88 """
89 89 Initializes the connection to the vcs server.
90 90
91 91 :param server_and_port: str, e.g. "localhost:9900"
92 92 :param protocol: str or "http"
93 93 """
94 94 if protocol == 'http':
95 95 connect_http(server_and_port)
96 96 else:
97 97 raise Exception(f'Invalid vcs server protocol "{protocol}"')
98 98
99 99
100 100 class CurlSession(object):
101 101 """
102 102 Modeled so that it provides a subset of the requests interface.
103 103
104 104 This has been created so that it does only provide a minimal API for our
105 105 needs. The parts which it provides are based on the API of the library
106 106 `requests` which allows us to easily benchmark against it.
107 107
108 108 Please have a look at the class :class:`requests.Session` when you extend
109 109 it.
110 110 """
111 111 CURL_UA = f'RhodeCode HTTP {rhodecode.__version__}'
112 112
113 113 def __init__(self):
114 114 curl = pycurl.Curl()
115 115 # TODO: johbo: I did test with 7.19 of libcurl. This version has
116 116 # trouble with 100 - continue being set in the expect header. This
117 117 # can lead to massive performance drops, switching it off here.
118 118
119 119 curl.setopt(curl.TCP_NODELAY, True)
120 120 curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP)
121 121 curl.setopt(curl.USERAGENT, safe_bytes(self.CURL_UA))
122 122 curl.setopt(curl.SSL_VERIFYPEER, 0)
123 123 curl.setopt(curl.SSL_VERIFYHOST, 0)
124 124 self._curl = curl
125 125
126 126 def post(self, url, data, allow_redirects=False, headers=None):
127 127 headers = headers or {}
128 128 # format is ['header_name1: header_value1', 'header_name2: header_value2'])
129 129 headers_list = [b"Expect:"] + [safe_bytes('{}: {}'.format(k, v)) for k, v in headers.items()]
130 130 response_buffer = io.BytesIO()
131 131
132 132 curl = self._curl
133 133 curl.setopt(curl.URL, url)
134 134 curl.setopt(curl.POST, True)
135 135 curl.setopt(curl.POSTFIELDS, data)
136 136 curl.setopt(curl.FOLLOWLOCATION, allow_redirects)
137 137 curl.setopt(curl.WRITEDATA, response_buffer)
138 138 curl.setopt(curl.HTTPHEADER, headers_list)
139 curl.perform()
139
140 try:
141 curl.perform()
142 except pycurl.error as exc:
143 log.error('Failed to call endpoint url: {} using pycurl'.format(url))
144 raise
140 145
141 146 status_code = curl.getinfo(pycurl.HTTP_CODE)
142 147 content_type = curl.getinfo(pycurl.CONTENT_TYPE)
143 148 return CurlResponse(response_buffer, status_code, content_type)
144 149
145 150
146 151 class CurlResponse(object):
147 152 """
148 153 The response of a request, modeled after the requests API.
149 154
150 155 This class provides a subset of the response interface known from the
151 156 library `requests`. It is intentionally kept similar, so that we can use
152 157 `requests` as a drop in replacement for benchmarking purposes.
153 158 """
154 159
155 160 def __init__(self, response_buffer, status_code, content_type=''):
156 161 self._response_buffer = response_buffer
157 162 self._status_code = status_code
158 163 self._content_type = content_type
159 164
160 165 def __repr__(self):
161 166 return f'CurlResponse(code={self._status_code}, content_type={self._content_type})'
162 167
163 168 @property
164 169 def content(self):
165 170 try:
166 171 return self._response_buffer.getvalue()
167 172 finally:
168 173 self._response_buffer.close()
169 174
170 175 @property
171 176 def status_code(self):
172 177 return self._status_code
173 178
174 179 @property
175 180 def content_type(self):
176 181 return self._content_type
177 182
178 183 def iter_content(self, chunk_size):
179 184 self._response_buffer.seek(0)
180 185 while 1:
181 186 chunk = self._response_buffer.read(chunk_size)
182 187 if not chunk:
183 188 break
184 189 yield chunk
185 190
186 191
187 192 def _create_http_rpc_session():
188 193 session = CurlSession()
189 194 return session
General Comments 0
You need to be logged in to leave comments. Login now