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