##// END OF EJS Templates
exceptions: use only one exception handler to track errors with nicer...
marcink -
r178:f5cbb023 default
parent child Browse files
Show More
@@ -1,424 +1,409 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2017 RodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
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 General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import base64
19 19 import locale
20 20 import logging
21 21 import uuid
22 22 import wsgiref.util
23 23 import traceback
24 24 from itertools import chain
25 25
26 26 import msgpack
27 27 from beaker.cache import CacheManager
28 28 from beaker.util import parse_cache_config_options
29 29 from pyramid.config import Configurator
30 30 from pyramid.wsgi import wsgiapp
31 31
32 32 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
33 33 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
34 34 from vcsserver.echo_stub.echo_app import EchoApp
35 35 from vcsserver.exceptions import HTTPRepoLocked
36 36 from vcsserver.server import VcsServer
37 37
38 38 try:
39 39 from vcsserver.git import GitFactory, GitRemote
40 40 except ImportError:
41 41 GitFactory = None
42 42 GitRemote = None
43 43 try:
44 44 from vcsserver.hg import MercurialFactory, HgRemote
45 45 except ImportError:
46 46 MercurialFactory = None
47 47 HgRemote = None
48 48 try:
49 49 from vcsserver.svn import SubversionFactory, SvnRemote
50 50 except ImportError:
51 51 SubversionFactory = None
52 52 SvnRemote = None
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class VCS(object):
58 58 def __init__(self, locale=None, cache_config=None):
59 59 self.locale = locale
60 60 self.cache_config = cache_config
61 61 self._configure_locale()
62 62 self._initialize_cache()
63 63
64 64 if GitFactory and GitRemote:
65 65 git_repo_cache = self.cache.get_cache_region(
66 66 'git', region='repo_object')
67 67 git_factory = GitFactory(git_repo_cache)
68 68 self._git_remote = GitRemote(git_factory)
69 69 else:
70 70 log.info("Git client import failed")
71 71
72 72 if MercurialFactory and HgRemote:
73 73 hg_repo_cache = self.cache.get_cache_region(
74 74 'hg', region='repo_object')
75 75 hg_factory = MercurialFactory(hg_repo_cache)
76 76 self._hg_remote = HgRemote(hg_factory)
77 77 else:
78 78 log.info("Mercurial client import failed")
79 79
80 80 if SubversionFactory and SvnRemote:
81 81 svn_repo_cache = self.cache.get_cache_region(
82 82 'svn', region='repo_object')
83 83 svn_factory = SubversionFactory(svn_repo_cache)
84 84 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
85 85 else:
86 86 log.info("Subversion client import failed")
87 87
88 88 self._vcsserver = VcsServer()
89 89
90 90 def _initialize_cache(self):
91 91 cache_config = parse_cache_config_options(self.cache_config)
92 92 log.info('Initializing beaker cache: %s' % cache_config)
93 93 self.cache = CacheManager(**cache_config)
94 94
95 95 def _configure_locale(self):
96 96 if self.locale:
97 97 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
98 98 else:
99 99 log.info(
100 100 'Configuring locale subsystem based on environment variables')
101 101 try:
102 102 # If self.locale is the empty string, then the locale
103 103 # module will use the environment variables. See the
104 104 # documentation of the package `locale`.
105 105 locale.setlocale(locale.LC_ALL, self.locale)
106 106
107 107 language_code, encoding = locale.getlocale()
108 108 log.info(
109 109 'Locale set to language code "%s" with encoding "%s".',
110 110 language_code, encoding)
111 111 except locale.Error:
112 112 log.exception(
113 113 'Cannot set locale, not configuring the locale system')
114 114
115 115
116 116 class WsgiProxy(object):
117 117 def __init__(self, wsgi):
118 118 self.wsgi = wsgi
119 119
120 120 def __call__(self, environ, start_response):
121 121 input_data = environ['wsgi.input'].read()
122 122 input_data = msgpack.unpackb(input_data)
123 123
124 124 error = None
125 125 try:
126 126 data, status, headers = self.wsgi.handle(
127 127 input_data['environment'], input_data['input_data'],
128 128 *input_data['args'], **input_data['kwargs'])
129 129 except Exception as e:
130 130 data, status, headers = [], None, None
131 131 error = {
132 132 'message': str(e),
133 133 '_vcs_kind': getattr(e, '_vcs_kind', None)
134 134 }
135 135
136 136 start_response(200, {})
137 137 return self._iterator(error, status, headers, data)
138 138
139 139 def _iterator(self, error, status, headers, data):
140 140 initial_data = [
141 141 error,
142 142 status,
143 143 headers,
144 144 ]
145 145
146 146 for d in chain(initial_data, data):
147 147 yield msgpack.packb(d)
148 148
149 149
150 150 class HTTPApplication(object):
151 151 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
152 152
153 153 remote_wsgi = remote_wsgi
154 154 _use_echo_app = False
155 155
156 156 def __init__(self, settings=None, global_config=None):
157 157 self.config = Configurator(settings=settings)
158 158 self.global_config = global_config
159 159
160 160 locale = settings.get('locale', '') or 'en_US.UTF-8'
161 161 vcs = VCS(locale=locale, cache_config=settings)
162 162 self._remotes = {
163 163 'hg': vcs._hg_remote,
164 164 'git': vcs._git_remote,
165 165 'svn': vcs._svn_remote,
166 166 'server': vcs._vcsserver,
167 167 }
168 168 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
169 169 self._use_echo_app = True
170 170 log.warning("Using EchoApp for VCS operations.")
171 171 self.remote_wsgi = remote_wsgi_stub
172 172 self._configure_settings(settings)
173 173 self._configure()
174 174
175 175 def _configure_settings(self, app_settings):
176 176 """
177 177 Configure the settings module.
178 178 """
179 179 git_path = app_settings.get('git_path', None)
180 180 if git_path:
181 181 settings.GIT_EXECUTABLE = git_path
182 182
183 183 def _configure(self):
184 184 self.config.add_renderer(
185 185 name='msgpack',
186 186 factory=self._msgpack_renderer_factory)
187 187
188 188 self.config.add_route('service', '/_service')
189 189 self.config.add_route('status', '/status')
190 190 self.config.add_route('hg_proxy', '/proxy/hg')
191 191 self.config.add_route('git_proxy', '/proxy/git')
192 192 self.config.add_route('vcs', '/{backend}')
193 193 self.config.add_route('stream_git', '/stream/git/*repo_name')
194 194 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
195 195
196 196 self.config.add_view(
197 197 self.status_view, route_name='status', renderer='json')
198 198 self.config.add_view(
199 199 self.service_view, route_name='service', renderer='msgpack')
200 200
201 201 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
202 202 self.config.add_view(self.git_proxy(), route_name='git_proxy')
203 203 self.config.add_view(
204 204 self.vcs_view, route_name='vcs', renderer='msgpack',
205 205 custom_predicates=[self.is_vcs_view])
206 206
207 207 self.config.add_view(self.hg_stream(), route_name='stream_hg')
208 208 self.config.add_view(self.git_stream(), route_name='stream_git')
209 209
210 210 def notfound(request):
211 211 return {'status': '404 NOT FOUND'}
212 212 self.config.add_notfound_view(notfound, renderer='json')
213 213
214 self.config.add_view(
215 self.handle_vcs_exception, context=Exception,
216 custom_predicates=[self.is_vcs_exception])
217
218 self.config.add_view(
219 self.general_error_handler, context=Exception)
214 self.config.add_view(self.handle_vcs_exception, context=Exception)
220 215
221 216 self.config.add_tween(
222 217 'vcsserver.tweens.RequestWrapperTween',
223 218 )
224 219
225 220 def wsgi_app(self):
226 221 return self.config.make_wsgi_app()
227 222
228 223 def vcs_view(self, request):
229 224 remote = self._remotes[request.matchdict['backend']]
230 225 payload = msgpack.unpackb(request.body, use_list=True)
231 226 method = payload.get('method')
232 227 params = payload.get('params')
233 228 wire = params.get('wire')
234 229 args = params.get('args')
235 230 kwargs = params.get('kwargs')
236 231 if wire:
237 232 try:
238 233 wire['context'] = uuid.UUID(wire['context'])
239 234 except KeyError:
240 235 pass
241 236 args.insert(0, wire)
242 237
243 238 log.debug('method called:%s with kwargs:%s', method, kwargs)
244 239 try:
245 240 resp = getattr(remote, method)(*args, **kwargs)
246 241 except Exception as e:
247 242 tb_info = traceback.format_exc()
248 243
249 244 type_ = e.__class__.__name__
250 245 if type_ not in self.ALLOWED_EXCEPTIONS:
251 246 type_ = None
252 247
253 248 resp = {
254 249 'id': payload.get('id'),
255 250 'error': {
256 251 'message': e.message,
257 252 'traceback': tb_info,
258 253 'type': type_
259 254 }
260 255 }
261 256 try:
262 257 resp['error']['_vcs_kind'] = e._vcs_kind
263 258 except AttributeError:
264 259 pass
265 260 else:
266 261 resp = {
267 262 'id': payload.get('id'),
268 263 'result': resp
269 264 }
270 265
271 266 return resp
272 267
273 268 def status_view(self, request):
274 269 return {'status': 'OK'}
275 270
276 271 def service_view(self, request):
277 272 import vcsserver
278 273 import ConfigParser as configparser
279 274
280 275 payload = msgpack.unpackb(request.body, use_list=True)
281 276
282 277 try:
283 278 path = self.global_config['__file__']
284 279 config = configparser.ConfigParser()
285 280 config.read(path)
286 281 parsed_ini = config
287 282 if parsed_ini.has_section('server:main'):
288 283 parsed_ini = dict(parsed_ini.items('server:main'))
289 284 except Exception:
290 285 log.exception('Failed to read .ini file for display')
291 286 parsed_ini = {}
292 287
293 288 resp = {
294 289 'id': payload.get('id'),
295 290 'result': dict(
296 291 version=vcsserver.__version__,
297 292 config=parsed_ini,
298 293 payload=payload,
299 294 )
300 295 }
301 296 return resp
302 297
303 298 def _msgpack_renderer_factory(self, info):
304 299 def _render(value, system):
305 300 value = msgpack.packb(value)
306 301 request = system.get('request')
307 302 if request is not None:
308 303 response = request.response
309 304 ct = response.content_type
310 305 if ct == response.default_content_type:
311 306 response.content_type = 'application/x-msgpack'
312 307 return value
313 308 return _render
314 309
315 310 def hg_proxy(self):
316 311 @wsgiapp
317 312 def _hg_proxy(environ, start_response):
318 313 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
319 314 return app(environ, start_response)
320 315 return _hg_proxy
321 316
322 317 def git_proxy(self):
323 318 @wsgiapp
324 319 def _git_proxy(environ, start_response):
325 320 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
326 321 return app(environ, start_response)
327 322 return _git_proxy
328 323
329 324 def hg_stream(self):
330 325 if self._use_echo_app:
331 326 @wsgiapp
332 327 def _hg_stream(environ, start_response):
333 328 app = EchoApp('fake_path', 'fake_name', None)
334 329 return app(environ, start_response)
335 330 return _hg_stream
336 331 else:
337 332 @wsgiapp
338 333 def _hg_stream(environ, start_response):
339 334 repo_path = environ['HTTP_X_RC_REPO_PATH']
340 335 repo_name = environ['HTTP_X_RC_REPO_NAME']
341 336 packed_config = base64.b64decode(
342 337 environ['HTTP_X_RC_REPO_CONFIG'])
343 338 config = msgpack.unpackb(packed_config)
344 339 app = scm_app.create_hg_wsgi_app(
345 340 repo_path, repo_name, config)
346 341
347 342 # Consitent path information for hgweb
348 343 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
349 344 environ['REPO_NAME'] = repo_name
350 345 return app(environ, ResponseFilter(start_response))
351 346 return _hg_stream
352 347
353 348 def git_stream(self):
354 349 if self._use_echo_app:
355 350 @wsgiapp
356 351 def _git_stream(environ, start_response):
357 352 app = EchoApp('fake_path', 'fake_name', None)
358 353 return app(environ, start_response)
359 354 return _git_stream
360 355 else:
361 356 @wsgiapp
362 357 def _git_stream(environ, start_response):
363 358 repo_path = environ['HTTP_X_RC_REPO_PATH']
364 359 repo_name = environ['HTTP_X_RC_REPO_NAME']
365 360 packed_config = base64.b64decode(
366 361 environ['HTTP_X_RC_REPO_CONFIG'])
367 362 config = msgpack.unpackb(packed_config)
368 363
369 364 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
370 365 app = scm_app.create_git_wsgi_app(
371 366 repo_path, repo_name, config)
372 367 return app(environ, start_response)
373 368 return _git_stream
374 369
375 370 def is_vcs_view(self, context, request):
376 371 """
377 372 View predicate that returns true if given backend is supported by
378 373 defined remotes.
379 374 """
380 375 backend = request.matchdict.get('backend')
381 376 return backend in self._remotes
382 377
383 def is_vcs_exception(self, context, request):
384 """
385 View predicate that returns true if the context object is a VCS
386 exception.
387 """
388 return hasattr(context, '_vcs_kind')
389
390 378 def handle_vcs_exception(self, exception, request):
391 if exception._vcs_kind == 'repo_locked':
379 _vcs_kind = getattr(exception, '_vcs_kind', '')
380 if _vcs_kind == 'repo_locked':
392 381 # Get custom repo-locked status code if present.
393 382 status_code = request.headers.get('X-RC-Locked-Status-Code')
394 383 return HTTPRepoLocked(
395 384 title=exception.message, status_code=status_code)
396 385
397 386 # Re-raise exception if we can not handle it.
398 raise exception
399
400 def general_error_handler(self, exception, request):
401 387 log.exception(
402 'error occurred handling this request for path: %s',
403 request.path)
388 'error occurred handling this request for path: %s', request.path)
404 389 raise exception
405 390
406 391
407 392 class ResponseFilter(object):
408 393
409 394 def __init__(self, start_response):
410 395 self._start_response = start_response
411 396
412 397 def __call__(self, status, response_headers, exc_info=None):
413 398 headers = tuple(
414 399 (h, v) for h, v in response_headers
415 400 if not wsgiref.util.is_hop_by_hop(h))
416 401 return self._start_response(status, headers, exc_info)
417 402
418 403
419 404 def main(global_config, **settings):
420 405 if MercurialFactory:
421 406 hgpatches.patch_largefiles_capabilities()
422 407 hgpatches.patch_subrepo_type_mapping()
423 408 app = HTTPApplication(settings=settings, global_config=global_config)
424 409 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now