__init__.py
194 lines
| 6.6 KiB
| text/x-python
|
PythonLexer
r5608 | # Copyright (C) 2014-2024 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
""" | ||||
Various version Control System version lib (vcs) management abstraction layer | ||||
for Python. Build with server client architecture. | ||||
""" | ||||
r4965 | import io | |||
r2928 | import atexit | |||
import logging | ||||
r1 | ||||
r3860 | import rhodecode | |||
r4965 | from rhodecode.lib.str_utils import safe_bytes | |||
r2928 | from rhodecode.lib.vcs.conf import settings | |||
from rhodecode.lib.vcs.backends import get_vcs_instance, get_backend | ||||
from rhodecode.lib.vcs.exceptions import ( | ||||
VCSError, RepositoryError, CommitError, VCSCommunicationError) | ||||
r1 | ||||
__all__ = [ | ||||
r4965 | 'get_vcs_instance', 'get_backend', | |||
r5322 | 'VCSError', 'RepositoryError', 'CommitError', 'VCSCommunicationError', | |||
'CurlSession', 'CurlResponse' | ||||
r2928 | ] | |||
r1 | ||||
Martin Bornhold
|
r474 | log = logging.getLogger(__name__) | ||
r1 | ||||
Martin Bornhold
|
r474 | # The pycurl library directly accesses C API functions and is not patched by | ||
# gevent. This will potentially lead to deadlocks due to incompatibility to | ||||
# gevent. Therefore we check if gevent is active and import a gevent compatible | ||||
# wrapper in that case. | ||||
try: | ||||
from gevent import monkey | ||||
if monkey.is_module_patched('__builtin__'): | ||||
import geventcurl as pycurl | ||||
log.debug('Using gevent comapatible pycurl: %s', pycurl) | ||||
else: | ||||
import pycurl | ||||
except ImportError: | ||||
import pycurl | ||||
r1 | ||||
r4965 | def connect_http(server_and_port): | |||
log.debug('Initialized VCSServer connections to %s.', server_and_port) | ||||
r1 | ||||
from rhodecode.lib.vcs import connection, client_http | ||||
from rhodecode.lib.middleware.utils import scm_app | ||||
Martin Bornhold
|
r244 | session_factory = client_http.ThreadlocalSessionFactory() | ||
r1 | ||||
r3895 | connection.Git = client_http.RemoteVCSMaker( | |||
r1126 | server_and_port, '/git', 'git', session_factory) | |||
r3895 | connection.Hg = client_http.RemoteVCSMaker( | |||
r1126 | server_and_port, '/hg', 'hg', session_factory) | |||
r3895 | connection.Svn = client_http.RemoteVCSMaker( | |||
r1126 | server_and_port, '/svn', 'svn', session_factory) | |||
r1111 | connection.Service = client_http.ServiceConnection( | |||
server_and_port, '/_service', session_factory) | ||||
r1 | ||||
scm_app.HG_REMOTE_WSGI = client_http.VcsHttpProxy( | ||||
server_and_port, '/proxy/hg') | ||||
scm_app.GIT_REMOTE_WSGI = client_http.VcsHttpProxy( | ||||
server_and_port, '/proxy/git') | ||||
@atexit.register | ||||
def free_connection_resources(): | ||||
connection.Git = None | ||||
connection.Hg = None | ||||
connection.Svn = None | ||||
r1111 | connection.Service = None | |||
r1 | ||||
Martin Bornhold
|
r959 | def connect_vcs(server_and_port, protocol): | ||
r1 | """ | |||
Initializes the connection to the vcs server. | ||||
:param server_and_port: str, e.g. "localhost:9900" | ||||
r1409 | :param protocol: str or "http" | |||
r1 | """ | |||
r1409 | if protocol == 'http': | |||
r1 | connect_http(server_and_port) | |||
Martin Bornhold
|
r960 | else: | ||
r5091 | raise Exception(f'Invalid vcs server protocol "{protocol}"') | |||
r1 | ||||
class CurlSession(object): | ||||
""" | ||||
Modeled so that it provides a subset of the requests interface. | ||||
This has been created so that it does only provide a minimal API for our | ||||
needs. The parts which it provides are based on the API of the library | ||||
`requests` which allows us to easily benchmark against it. | ||||
Please have a look at the class :class:`requests.Session` when you extend | ||||
it. | ||||
""" | ||||
r4965 | CURL_UA = f'RhodeCode HTTP {rhodecode.__version__}' | |||
r1 | ||||
def __init__(self): | ||||
curl = pycurl.Curl() | ||||
# TODO: johbo: I did test with 7.19 of libcurl. This version has | ||||
# trouble with 100 - continue being set in the expect header. This | ||||
# can lead to massive performance drops, switching it off here. | ||||
r4887 | ||||
r1 | curl.setopt(curl.TCP_NODELAY, True) | |||
curl.setopt(curl.PROTOCOLS, curl.PROTO_HTTP) | ||||
r4965 | curl.setopt(curl.USERAGENT, safe_bytes(self.CURL_UA)) | |||
r3929 | curl.setopt(curl.SSL_VERIFYPEER, 0) | |||
curl.setopt(curl.SSL_VERIFYHOST, 0) | ||||
r1 | self._curl = curl | |||
r4887 | def post(self, url, data, allow_redirects=False, headers=None): | |||
headers = headers or {} | ||||
# format is ['header_name1: header_value1', 'header_name2: header_value2']) | ||||
r4965 | headers_list = [b"Expect:"] + [safe_bytes('{}: {}'.format(k, v)) for k, v in headers.items()] | |||
response_buffer = io.BytesIO() | ||||
r1 | ||||
curl = self._curl | ||||
curl.setopt(curl.URL, url) | ||||
curl.setopt(curl.POST, True) | ||||
curl.setopt(curl.POSTFIELDS, data) | ||||
curl.setopt(curl.FOLLOWLOCATION, allow_redirects) | ||||
curl.setopt(curl.WRITEDATA, response_buffer) | ||||
r4887 | curl.setopt(curl.HTTPHEADER, headers_list) | |||
r5324 | ||||
try: | ||||
curl.perform() | ||||
r5647 | except pycurl.error: | |||
r5607 | log.error('Failed to call endpoint url: %s using pycurl', url) | |||
r5324 | raise | |||
r1 | ||||
r1410 | status_code = curl.getinfo(pycurl.HTTP_CODE) | |||
r5074 | content_type = curl.getinfo(pycurl.CONTENT_TYPE) | |||
return CurlResponse(response_buffer, status_code, content_type) | ||||
r1 | ||||
class CurlResponse(object): | ||||
""" | ||||
The response of a request, modeled after the requests API. | ||||
This class provides a subset of the response interface known from the | ||||
library `requests`. It is intentionally kept similar, so that we can use | ||||
`requests` as a drop in replacement for benchmarking purposes. | ||||
""" | ||||
r5074 | def __init__(self, response_buffer, status_code, content_type=''): | |||
r1 | self._response_buffer = response_buffer | |||
r1410 | self._status_code = status_code | |||
r5074 | self._content_type = content_type | |||
def __repr__(self): | ||||
return f'CurlResponse(code={self._status_code}, content_type={self._content_type})' | ||||
r1 | ||||
@property | ||||
def content(self): | ||||
r3895 | try: | |||
return self._response_buffer.getvalue() | ||||
finally: | ||||
self._response_buffer.close() | ||||
r1 | ||||
r1410 | @property | |||
def status_code(self): | ||||
return self._status_code | ||||
r5074 | @property | |||
def content_type(self): | ||||
return self._content_type | ||||
r3895 | def iter_content(self, chunk_size): | |||
self._response_buffer.seek(0) | ||||
while 1: | ||||
chunk = self._response_buffer.read(chunk_size) | ||||
if not chunk: | ||||
break | ||||
yield chunk | ||||
r1 | ||||
def _create_http_rpc_session(): | ||||
session = CurlSession() | ||||
return session | ||||