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