##// END OF EJS Templates
chore(code): small code improvements logging & stricter header checks
super-admin -
r5553:b90185f7 default
parent child Browse files
Show More
@@ -1,193 +1,194 b''
1 1
2 2
3 3 # Copyright (C) 2014-2023 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 Implementation of the scm_app interface using raw HTTP communication.
23 23 """
24 24
25 25 import base64
26 26 import logging
27 27 import urllib.parse
28 28 import wsgiref.util
29 29
30 30 import msgpack
31 31 import requests
32 32 import webob.request
33 33
34 34 import rhodecode
35 35 from rhodecode.lib.middleware.utils import get_path_info
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 def create_git_wsgi_app(repo_path, repo_name, config):
41 41 url = _vcs_streaming_url() + 'git/'
42 42 return VcsHttpProxy(url, repo_path, repo_name, config)
43 43
44 44
45 45 def create_hg_wsgi_app(repo_path, repo_name, config):
46 46 url = _vcs_streaming_url() + 'hg/'
47 47 return VcsHttpProxy(url, repo_path, repo_name, config)
48 48
49 49
50 50 def _vcs_streaming_url():
51 51 template = 'http://{}/stream/'
52 52 return template.format(rhodecode.CONFIG['vcs.server'])
53 53
54 54
55 55 # TODO: johbo: Avoid the global.
56 56 session = requests.Session()
57 57 # Requests speedup, avoid reading .netrc and similar
58 58 session.trust_env = False
59 59
60 60 # prevent urllib3 spawning our logs.
61 61 logging.getLogger("requests.packages.urllib3.connectionpool").setLevel(
62 62 logging.WARNING)
63 63
64 64
65 65 class VcsHttpProxy(object):
66 66 """
67 67 A WSGI application which proxies vcs requests.
68 68
69 69 The goal is to shuffle the data around without touching it. The only
70 70 exception is the extra data from the config object which we send to the
71 71 server as well.
72 72 """
73 73
74 74 def __init__(self, url, repo_path, repo_name, config):
75 75 """
76 76 :param str url: The URL of the VCSServer to call.
77 77 """
78 78 self._url = url
79 79 self._repo_name = repo_name
80 80 self._repo_path = repo_path
81 81 self._config = config
82 82 self.rc_extras = {}
83 83 log.debug(
84 84 "Creating VcsHttpProxy for repo %s, url %s",
85 85 repo_name, url)
86 86
87 87 def __call__(self, environ, start_response):
88 88 config = self._config
89 89 request = webob.request.Request(environ)
90 90 request_headers = request.headers
91 91
92 92 call_context = {
93 93 # TODO: johbo: Remove this, rely on URL path only
94 94 'repo_name': self._repo_name,
95 95 'repo_path': self._repo_path,
96 96 'path_info': get_path_info(environ),
97 97
98 98 'repo_store': self.rc_extras.get('repo_store'),
99 99 'server_config_file': self.rc_extras.get('config'),
100 100
101 101 'auth_user': self.rc_extras.get('username'),
102 102 'auth_user_id': str(self.rc_extras.get('user_id')),
103 103 'auth_user_ip': self.rc_extras.get('ip'),
104 104
105 105 'repo_config': config,
106 106 'locked_status_code': rhodecode.CONFIG.get('lock_ret_code'),
107 107 }
108 108
109 109 request_headers.update({
110 110 # TODO: johbo: Avoid encoding and put this into payload?
111 111 'X_RC_VCS_STREAM_CALL_CONTEXT': base64.b64encode(msgpack.packb(call_context))
112 112 })
113 113
114 114 method = environ['REQUEST_METHOD']
115 115
116 116 # Preserve the query string
117 117 url = self._url
118 118 url = urllib.parse.urljoin(url, self._repo_name)
119 119 if environ.get('QUERY_STRING'):
120 120 url += '?' + environ['QUERY_STRING']
121 121
122 122 log.debug('http-app: preparing request to: %s', url)
123 123 response = session.request(
124 124 method,
125 125 url,
126 126 data=_maybe_stream_request(environ),
127 127 headers=request_headers,
128 128 stream=True)
129 129
130 130 log.debug('http-app: got vcsserver response: %s', response)
131 131 if response.status_code >= 500:
132 132 log.error('Exception returned by vcsserver at: %s %s, %s',
133 133 url, response.status_code, response.content)
134 134
135 135 # Preserve the headers of the response, except hop_by_hop ones
136 136 response_headers = [
137 137 (h, v) for h, v in response.headers.items()
138 138 if not wsgiref.util.is_hop_by_hop(h)
139 139 ]
140 140
141 141 # Build status argument for start_response callable.
142 142 status = '{status_code} {reason_phrase}'.format(
143 143 status_code=response.status_code,
144 144 reason_phrase=response.reason)
145 145
146 146 start_response(status, response_headers)
147 147 return _maybe_stream_response(response)
148 148
149 149
150 150 def read_in_chunks(stream_obj, block_size=1024, chunks=-1):
151 151 """
152 152 Read Stream in chunks, default chunk size: 1k.
153 153 """
154 154 while chunks:
155 155 data = stream_obj.read(block_size)
156 156 if not data:
157 157 break
158 158 yield data
159 159 chunks -= 1
160 160
161 161
162 162 def _is_request_chunked(environ):
163 163 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
164 164 return stream
165 165
166 166
167 167 def _maybe_stream_request(environ):
168 168 path = get_path_info(environ)
169 169 stream = _is_request_chunked(environ)
170 log.debug('handling request `%s` with stream support: %s', path, stream)
170 req_method = environ['REQUEST_METHOD']
171 log.debug('handling scm request: %s `%s` with stream support: %s', req_method, path, stream)
171 172
172 173 if stream:
173 174 # set stream by 256k
174 175 return read_in_chunks(environ['wsgi.input'], block_size=1024 * 256)
175 176 else:
176 177 return environ['wsgi.input'].read()
177 178
178 179
179 180 def _maybe_stream_response(response):
180 181 """
181 182 Try to generate chunks from the response if it is chunked.
182 183 """
183 184 stream = _is_chunked(response)
184 185 log.debug('returning response with stream: %s', stream)
185 186 if stream:
186 187 # read in 256k Chunks
187 188 return response.raw.read_chunked(amt=1024 * 256)
188 189 else:
189 190 return [response.content]
190 191
191 192
192 193 def _is_chunked(response):
193 194 return response.headers.get('Transfer-Encoding', '') == 'chunked'
@@ -1,83 +1,83 b''
1 1 # Copyright (C) 2013-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 import logging
20 20 import urllib.request
21 21 import urllib.error
22 22 import urllib.parse
23 23 from packaging.version import Version
24 24
25 25 import rhodecode
26 26 from rhodecode.lib.ext_json import json
27 27 from rhodecode.model import BaseModel
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.settings import SettingsModel
30 30
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class UpdateModel(BaseModel):
36 36 UPDATE_SETTINGS_KEY = 'update_version'
37 37 UPDATE_URL_SETTINGS_KEY = 'rhodecode_update_url'
38 38
39 39 @staticmethod
40 40 def get_update_data(update_url):
41 41 """Return the JSON update data."""
42 42 ver = rhodecode.__version__
43 43 log.debug('Checking for upgrade on `%s` server', update_url)
44 44 opener = urllib.request.build_opener()
45 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
45 opener.addheaders = [('User-agent', f'RhodeCode-SCM/{ver.strip()}')]
46 46 response = opener.open(update_url)
47 47 response_data = response.read()
48 48 data = json.loads(response_data)
49 49 log.debug('update server returned data')
50 50 return data
51 51
52 52 def get_update_url(self):
53 53 settings = SettingsModel().get_all_settings()
54 54 return settings.get(self.UPDATE_URL_SETTINGS_KEY)
55 55
56 56 def store_version(self, version):
57 57 log.debug('Storing version %s into settings', version)
58 58 setting = SettingsModel().create_or_update_setting(
59 59 self.UPDATE_SETTINGS_KEY, version)
60 60 Session().add(setting)
61 61 Session().commit()
62 62
63 63 def get_stored_version(self, fallback=None):
64 64 obj = SettingsModel().get_setting_by_name(self.UPDATE_SETTINGS_KEY)
65 65 if obj:
66 66 return obj.app_settings_value
67 67 return fallback or '0.0.0'
68 68
69 69 def _sanitize_version(self, version):
70 70 """
71 71 Cleanup our custom ver.
72 72 e.g 4.11.0_20171204_204825_CE_default_EE_default to 4.11.0
73 73 """
74 74 return version.split('_')[0]
75 75
76 76 def is_outdated(self, cur_version, latest_version=None):
77 77 latest_version = latest_version or self.get_stored_version()
78 78 try:
79 79 cur_version = self._sanitize_version(cur_version)
80 80 return Version(latest_version) > Version(cur_version)
81 81 except Exception:
82 82 # could be invalid version, etc
83 83 return False
General Comments 0
You need to be logged in to leave comments. Login now