##// END OF EJS Templates
vcsserver: use safer maxfd reporting, some linux systems get a problem with this
milka -
r913:279dbe3d default
parent child Browse files
Show More
@@ -1,692 +1,700 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2020 RhodeCode 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 os
19 19 import sys
20 20 import base64
21 21 import locale
22 22 import logging
23 23 import uuid
24 24 import wsgiref.util
25 25 import traceback
26 26 import tempfile
27 27 import resource
28 import psutil
28 29 from itertools import chain
29 30 from cStringIO import StringIO
30 31
31 32 import simplejson as json
32 33 import msgpack
33 34 from pyramid.config import Configurator
34 35 from pyramid.settings import asbool, aslist
35 36 from pyramid.wsgi import wsgiapp
36 37 from pyramid.compat import configparser
37 38 from pyramid.response import Response
38 39
39 40 from vcsserver.utils import safe_int
40 41
41 42 log = logging.getLogger(__name__)
42 43
43 44 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
44 45 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
45 46
46 47 try:
47 48 locale.setlocale(locale.LC_ALL, '')
48 49 except locale.Error as e:
49 50 log.error(
50 51 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
51 52 os.environ['LC_ALL'] = 'C'
52 53
53 54 import vcsserver
54 55 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
55 56 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
56 57 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
57 58 from vcsserver.echo_stub.echo_app import EchoApp
58 59 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
59 60 from vcsserver.lib.exc_tracking import store_exception
60 61 from vcsserver.server import VcsServer
61 62
62 63 try:
63 64 from vcsserver.git import GitFactory, GitRemote
64 65 except ImportError:
65 66 GitFactory = None
66 67 GitRemote = None
67 68
68 69 try:
69 70 from vcsserver.hg import MercurialFactory, HgRemote
70 71 except ImportError:
71 72 MercurialFactory = None
72 73 HgRemote = None
73 74
74 75 try:
75 76 from vcsserver.svn import SubversionFactory, SvnRemote
76 77 except ImportError:
77 78 SubversionFactory = None
78 79 SvnRemote = None
79 80
80 81
81 82 def _is_request_chunked(environ):
82 83 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
83 84 return stream
84 85
85 86
86 87 def _int_setting(settings, name, default):
87 88 settings[name] = int(settings.get(name, default))
88 89 return settings[name]
89 90
90 91
91 92 def _bool_setting(settings, name, default):
92 93 input_val = settings.get(name, default)
93 94 if isinstance(input_val, unicode):
94 95 input_val = input_val.encode('utf8')
95 96 settings[name] = asbool(input_val)
96 97 return settings[name]
97 98
98 99
99 100 def _list_setting(settings, name, default):
100 101 raw_value = settings.get(name, default)
101 102
102 103 # Otherwise we assume it uses pyramids space/newline separation.
103 104 settings[name] = aslist(raw_value)
104 105 return settings[name]
105 106
106 107
107 108 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
108 109 value = settings.get(name, default)
109 110
110 111 if default_when_empty and not value:
111 112 # use default value when value is empty
112 113 value = default
113 114
114 115 if lower:
115 116 value = value.lower()
116 117 settings[name] = value
117 118 return settings[name]
118 119
119 120
121 def log_max_fd():
122 try:
123 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
124 log.info('Max file descriptors value: %s', maxfd)
125 except Exception:
126 pass
127
128
120 129 class VCS(object):
121 130 def __init__(self, locale_conf=None, cache_config=None):
122 131 self.locale = locale_conf
123 132 self.cache_config = cache_config
124 133 self._configure_locale()
125 134
126 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
127 log.info('Max file descriptors value: %s', maxfd)
135 log_max_fd()
128 136
129 137 if GitFactory and GitRemote:
130 138 git_factory = GitFactory()
131 139 self._git_remote = GitRemote(git_factory)
132 140 else:
133 141 log.info("Git client import failed")
134 142
135 143 if MercurialFactory and HgRemote:
136 144 hg_factory = MercurialFactory()
137 145 self._hg_remote = HgRemote(hg_factory)
138 146 else:
139 147 log.info("Mercurial client import failed")
140 148
141 149 if SubversionFactory and SvnRemote:
142 150 svn_factory = SubversionFactory()
143 151
144 152 # hg factory is used for svn url validation
145 153 hg_factory = MercurialFactory()
146 154 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
147 155 else:
148 156 log.info("Subversion client import failed")
149 157
150 158 self._vcsserver = VcsServer()
151 159
152 160 def _configure_locale(self):
153 161 if self.locale:
154 162 log.info('Settings locale: `LC_ALL` to %s', self.locale)
155 163 else:
156 164 log.info(
157 165 'Configuring locale subsystem based on environment variables')
158 166 try:
159 167 # If self.locale is the empty string, then the locale
160 168 # module will use the environment variables. See the
161 169 # documentation of the package `locale`.
162 170 locale.setlocale(locale.LC_ALL, self.locale)
163 171
164 172 language_code, encoding = locale.getlocale()
165 173 log.info(
166 174 'Locale set to language code "%s" with encoding "%s".',
167 175 language_code, encoding)
168 176 except locale.Error:
169 177 log.exception(
170 178 'Cannot set locale, not configuring the locale system')
171 179
172 180
173 181 class WsgiProxy(object):
174 182 def __init__(self, wsgi):
175 183 self.wsgi = wsgi
176 184
177 185 def __call__(self, environ, start_response):
178 186 input_data = environ['wsgi.input'].read()
179 187 input_data = msgpack.unpackb(input_data)
180 188
181 189 error = None
182 190 try:
183 191 data, status, headers = self.wsgi.handle(
184 192 input_data['environment'], input_data['input_data'],
185 193 *input_data['args'], **input_data['kwargs'])
186 194 except Exception as e:
187 195 data, status, headers = [], None, None
188 196 error = {
189 197 'message': str(e),
190 198 '_vcs_kind': getattr(e, '_vcs_kind', None)
191 199 }
192 200
193 201 start_response(200, {})
194 202 return self._iterator(error, status, headers, data)
195 203
196 204 def _iterator(self, error, status, headers, data):
197 205 initial_data = [
198 206 error,
199 207 status,
200 208 headers,
201 209 ]
202 210
203 211 for d in chain(initial_data, data):
204 212 yield msgpack.packb(d)
205 213
206 214
207 215 def not_found(request):
208 216 return {'status': '404 NOT FOUND'}
209 217
210 218
211 219 class VCSViewPredicate(object):
212 220 def __init__(self, val, config):
213 221 self.remotes = val
214 222
215 223 def text(self):
216 224 return 'vcs view method = %s' % (self.remotes.keys(),)
217 225
218 226 phash = text
219 227
220 228 def __call__(self, context, request):
221 229 """
222 230 View predicate that returns true if given backend is supported by
223 231 defined remotes.
224 232 """
225 233 backend = request.matchdict.get('backend')
226 234 return backend in self.remotes
227 235
228 236
229 237 class HTTPApplication(object):
230 238 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
231 239
232 240 remote_wsgi = remote_wsgi
233 241 _use_echo_app = False
234 242
235 243 def __init__(self, settings=None, global_config=None):
236 244 self._sanitize_settings_and_apply_defaults(settings)
237 245
238 246 self.config = Configurator(settings=settings)
239 247 self.global_config = global_config
240 248 self.config.include('vcsserver.lib.rc_cache')
241 249
242 250 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
243 251 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
244 252 self._remotes = {
245 253 'hg': vcs._hg_remote,
246 254 'git': vcs._git_remote,
247 255 'svn': vcs._svn_remote,
248 256 'server': vcs._vcsserver,
249 257 }
250 258 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
251 259 self._use_echo_app = True
252 260 log.warning("Using EchoApp for VCS operations.")
253 261 self.remote_wsgi = remote_wsgi_stub
254 262
255 263 self._configure_settings(global_config, settings)
256 264 self._configure()
257 265
258 266 def _configure_settings(self, global_config, app_settings):
259 267 """
260 268 Configure the settings module.
261 269 """
262 270 settings_merged = global_config.copy()
263 271 settings_merged.update(app_settings)
264 272
265 273 git_path = app_settings.get('git_path', None)
266 274 if git_path:
267 275 settings.GIT_EXECUTABLE = git_path
268 276 binary_dir = app_settings.get('core.binary_dir', None)
269 277 if binary_dir:
270 278 settings.BINARY_DIR = binary_dir
271 279
272 280 # Store the settings to make them available to other modules.
273 281 vcsserver.PYRAMID_SETTINGS = settings_merged
274 282 vcsserver.CONFIG = settings_merged
275 283
276 284 def _sanitize_settings_and_apply_defaults(self, settings):
277 285 temp_store = tempfile.gettempdir()
278 286 default_cache_dir = os.path.join(temp_store, 'rc_cache')
279 287
280 288 # save default, cache dir, and use it for all backends later.
281 289 default_cache_dir = _string_setting(
282 290 settings,
283 291 'cache_dir',
284 292 default_cache_dir, lower=False, default_when_empty=True)
285 293
286 294 # ensure we have our dir created
287 295 if not os.path.isdir(default_cache_dir):
288 296 os.makedirs(default_cache_dir, mode=0o755)
289 297
290 298 # exception store cache
291 299 _string_setting(
292 300 settings,
293 301 'exception_tracker.store_path',
294 302 temp_store, lower=False, default_when_empty=True)
295 303
296 304 # repo_object cache
297 305 _string_setting(
298 306 settings,
299 307 'rc_cache.repo_object.backend',
300 308 'dogpile.cache.rc.file_namespace', lower=False)
301 309 _int_setting(
302 310 settings,
303 311 'rc_cache.repo_object.expiration_time',
304 312 30 * 24 * 60 * 60)
305 313 _string_setting(
306 314 settings,
307 315 'rc_cache.repo_object.arguments.filename',
308 316 os.path.join(default_cache_dir, 'vcsserver_cache_1'), lower=False)
309 317
310 318 def _configure(self):
311 319 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
312 320
313 321 self.config.add_route('service', '/_service')
314 322 self.config.add_route('status', '/status')
315 323 self.config.add_route('hg_proxy', '/proxy/hg')
316 324 self.config.add_route('git_proxy', '/proxy/git')
317 325
318 326 # rpc methods
319 327 self.config.add_route('vcs', '/{backend}')
320 328
321 329 # streaming rpc remote methods
322 330 self.config.add_route('vcs_stream', '/{backend}/stream')
323 331
324 332 # vcs operations clone/push as streaming
325 333 self.config.add_route('stream_git', '/stream/git/*repo_name')
326 334 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
327 335
328 336 self.config.add_view(self.status_view, route_name='status', renderer='json')
329 337 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
330 338
331 339 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
332 340 self.config.add_view(self.git_proxy(), route_name='git_proxy')
333 341 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
334 342 vcs_view=self._remotes)
335 343 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
336 344 vcs_view=self._remotes)
337 345
338 346 self.config.add_view(self.hg_stream(), route_name='stream_hg')
339 347 self.config.add_view(self.git_stream(), route_name='stream_git')
340 348
341 349 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
342 350
343 351 self.config.add_notfound_view(not_found, renderer='json')
344 352
345 353 self.config.add_view(self.handle_vcs_exception, context=Exception)
346 354
347 355 self.config.add_tween(
348 356 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
349 357 )
350 358 self.config.add_request_method(
351 359 'vcsserver.lib.request_counter.get_request_counter',
352 360 'request_count')
353 361
354 362 def wsgi_app(self):
355 363 return self.config.make_wsgi_app()
356 364
357 365 def _vcs_view_params(self, request):
358 366 remote = self._remotes[request.matchdict['backend']]
359 367 payload = msgpack.unpackb(request.body, use_list=True)
360 368 method = payload.get('method')
361 369 params = payload['params']
362 370 wire = params.get('wire')
363 371 args = params.get('args')
364 372 kwargs = params.get('kwargs')
365 373 context_uid = None
366 374
367 375 if wire:
368 376 try:
369 377 wire['context'] = context_uid = uuid.UUID(wire['context'])
370 378 except KeyError:
371 379 pass
372 380 args.insert(0, wire)
373 381 repo_state_uid = wire.get('repo_state_uid') if wire else None
374 382
375 383 # NOTE(marcink): trading complexity for slight performance
376 384 if log.isEnabledFor(logging.DEBUG):
377 385 no_args_methods = [
378 386
379 387 ]
380 388 if method in no_args_methods:
381 389 call_args = ''
382 390 else:
383 391 call_args = args[1:]
384 392
385 393 log.debug('method requested:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
386 394 method, call_args, kwargs, context_uid, repo_state_uid)
387 395
388 396 return payload, remote, method, args, kwargs
389 397
390 398 def vcs_view(self, request):
391 399
392 400 payload, remote, method, args, kwargs = self._vcs_view_params(request)
393 401 payload_id = payload.get('id')
394 402
395 403 try:
396 404 resp = getattr(remote, method)(*args, **kwargs)
397 405 except Exception as e:
398 406 exc_info = list(sys.exc_info())
399 407 exc_type, exc_value, exc_traceback = exc_info
400 408
401 409 org_exc = getattr(e, '_org_exc', None)
402 410 org_exc_name = None
403 411 org_exc_tb = ''
404 412 if org_exc:
405 413 org_exc_name = org_exc.__class__.__name__
406 414 org_exc_tb = getattr(e, '_org_exc_tb', '')
407 415 # replace our "faked" exception with our org
408 416 exc_info[0] = org_exc.__class__
409 417 exc_info[1] = org_exc
410 418
411 419 should_store_exc = True
412 420 if org_exc:
413 421 def get_exc_fqn(_exc_obj):
414 422 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
415 423 return module_name + '.' + org_exc_name
416 424
417 425 exc_fqn = get_exc_fqn(org_exc)
418 426
419 427 if exc_fqn in ['mercurial.error.RepoLookupError',
420 428 'vcsserver.exceptions.RefNotFoundException']:
421 429 should_store_exc = False
422 430
423 431 if should_store_exc:
424 432 store_exception(id(exc_info), exc_info)
425 433
426 434 tb_info = ''.join(
427 435 traceback.format_exception(exc_type, exc_value, exc_traceback))
428 436
429 437 type_ = e.__class__.__name__
430 438 if type_ not in self.ALLOWED_EXCEPTIONS:
431 439 type_ = None
432 440
433 441 resp = {
434 442 'id': payload_id,
435 443 'error': {
436 444 'message': e.message,
437 445 'traceback': tb_info,
438 446 'org_exc': org_exc_name,
439 447 'org_exc_tb': org_exc_tb,
440 448 'type': type_
441 449 }
442 450 }
443 451 try:
444 452 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
445 453 except AttributeError:
446 454 pass
447 455 else:
448 456 resp = {
449 457 'id': payload_id,
450 458 'result': resp
451 459 }
452 460
453 461 return resp
454 462
455 463 def vcs_stream_view(self, request):
456 464 payload, remote, method, args, kwargs = self._vcs_view_params(request)
457 465 # this method has a stream: marker we remove it here
458 466 method = method.split('stream:')[-1]
459 467 chunk_size = safe_int(payload.get('chunk_size')) or 4096
460 468
461 469 try:
462 470 resp = getattr(remote, method)(*args, **kwargs)
463 471 except Exception as e:
464 472 raise
465 473
466 474 def get_chunked_data(method_resp):
467 475 stream = StringIO(method_resp)
468 476 while 1:
469 477 chunk = stream.read(chunk_size)
470 478 if not chunk:
471 479 break
472 480 yield chunk
473 481
474 482 response = Response(app_iter=get_chunked_data(resp))
475 483 response.content_type = 'application/octet-stream'
476 484
477 485 return response
478 486
479 487 def status_view(self, request):
480 488 import vcsserver
481 489 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
482 490 'pid': os.getpid()}
483 491
484 492 def service_view(self, request):
485 493 import vcsserver
486 494
487 495 payload = msgpack.unpackb(request.body, use_list=True)
488 496 server_config, app_config = {}, {}
489 497
490 498 try:
491 499 path = self.global_config['__file__']
492 500 config = configparser.RawConfigParser()
493 501
494 502 config.read(path)
495 503
496 504 if config.has_section('server:main'):
497 505 server_config = dict(config.items('server:main'))
498 506 if config.has_section('app:main'):
499 507 app_config = dict(config.items('app:main'))
500 508
501 509 except Exception:
502 510 log.exception('Failed to read .ini file for display')
503 511
504 512 environ = os.environ.items()
505 513
506 514 resp = {
507 515 'id': payload.get('id'),
508 516 'result': dict(
509 517 version=vcsserver.__version__,
510 518 config=server_config,
511 519 app_config=app_config,
512 520 environ=environ,
513 521 payload=payload,
514 522 )
515 523 }
516 524 return resp
517 525
518 526 def _msgpack_renderer_factory(self, info):
519 527 def _render(value, system):
520 528 request = system.get('request')
521 529 if request is not None:
522 530 response = request.response
523 531 ct = response.content_type
524 532 if ct == response.default_content_type:
525 533 response.content_type = 'application/x-msgpack'
526 534 return msgpack.packb(value)
527 535 return _render
528 536
529 537 def set_env_from_config(self, environ, config):
530 538 dict_conf = {}
531 539 try:
532 540 for elem in config:
533 541 if elem[0] == 'rhodecode':
534 542 dict_conf = json.loads(elem[2])
535 543 break
536 544 except Exception:
537 545 log.exception('Failed to fetch SCM CONFIG')
538 546 return
539 547
540 548 username = dict_conf.get('username')
541 549 if username:
542 550 environ['REMOTE_USER'] = username
543 551 # mercurial specific, some extension api rely on this
544 552 environ['HGUSER'] = username
545 553
546 554 ip = dict_conf.get('ip')
547 555 if ip:
548 556 environ['REMOTE_HOST'] = ip
549 557
550 558 if _is_request_chunked(environ):
551 559 # set the compatibility flag for webob
552 560 environ['wsgi.input_terminated'] = True
553 561
554 562 def hg_proxy(self):
555 563 @wsgiapp
556 564 def _hg_proxy(environ, start_response):
557 565 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
558 566 return app(environ, start_response)
559 567 return _hg_proxy
560 568
561 569 def git_proxy(self):
562 570 @wsgiapp
563 571 def _git_proxy(environ, start_response):
564 572 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
565 573 return app(environ, start_response)
566 574 return _git_proxy
567 575
568 576 def hg_stream(self):
569 577 if self._use_echo_app:
570 578 @wsgiapp
571 579 def _hg_stream(environ, start_response):
572 580 app = EchoApp('fake_path', 'fake_name', None)
573 581 return app(environ, start_response)
574 582 return _hg_stream
575 583 else:
576 584 @wsgiapp
577 585 def _hg_stream(environ, start_response):
578 586 log.debug('http-app: handling hg stream')
579 587 repo_path = environ['HTTP_X_RC_REPO_PATH']
580 588 repo_name = environ['HTTP_X_RC_REPO_NAME']
581 589 packed_config = base64.b64decode(
582 590 environ['HTTP_X_RC_REPO_CONFIG'])
583 591 config = msgpack.unpackb(packed_config)
584 592 app = scm_app.create_hg_wsgi_app(
585 593 repo_path, repo_name, config)
586 594
587 595 # Consistent path information for hgweb
588 596 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
589 597 environ['REPO_NAME'] = repo_name
590 598 self.set_env_from_config(environ, config)
591 599
592 600 log.debug('http-app: starting app handler '
593 601 'with %s and process request', app)
594 602 return app(environ, ResponseFilter(start_response))
595 603 return _hg_stream
596 604
597 605 def git_stream(self):
598 606 if self._use_echo_app:
599 607 @wsgiapp
600 608 def _git_stream(environ, start_response):
601 609 app = EchoApp('fake_path', 'fake_name', None)
602 610 return app(environ, start_response)
603 611 return _git_stream
604 612 else:
605 613 @wsgiapp
606 614 def _git_stream(environ, start_response):
607 615 log.debug('http-app: handling git stream')
608 616 repo_path = environ['HTTP_X_RC_REPO_PATH']
609 617 repo_name = environ['HTTP_X_RC_REPO_NAME']
610 618 packed_config = base64.b64decode(
611 619 environ['HTTP_X_RC_REPO_CONFIG'])
612 620 config = msgpack.unpackb(packed_config)
613 621
614 622 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
615 623 self.set_env_from_config(environ, config)
616 624
617 625 content_type = environ.get('CONTENT_TYPE', '')
618 626
619 627 path = environ['PATH_INFO']
620 628 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
621 629 log.debug(
622 630 'LFS: Detecting if request `%s` is LFS server path based '
623 631 'on content type:`%s`, is_lfs:%s',
624 632 path, content_type, is_lfs_request)
625 633
626 634 if not is_lfs_request:
627 635 # fallback detection by path
628 636 if GIT_LFS_PROTO_PAT.match(path):
629 637 is_lfs_request = True
630 638 log.debug(
631 639 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
632 640 path, is_lfs_request)
633 641
634 642 if is_lfs_request:
635 643 app = scm_app.create_git_lfs_wsgi_app(
636 644 repo_path, repo_name, config)
637 645 else:
638 646 app = scm_app.create_git_wsgi_app(
639 647 repo_path, repo_name, config)
640 648
641 649 log.debug('http-app: starting app handler '
642 650 'with %s and process request', app)
643 651
644 652 return app(environ, start_response)
645 653
646 654 return _git_stream
647 655
648 656 def handle_vcs_exception(self, exception, request):
649 657 _vcs_kind = getattr(exception, '_vcs_kind', '')
650 658 if _vcs_kind == 'repo_locked':
651 659 # Get custom repo-locked status code if present.
652 660 status_code = request.headers.get('X-RC-Locked-Status-Code')
653 661 return HTTPRepoLocked(
654 662 title=exception.message, status_code=status_code)
655 663
656 664 elif _vcs_kind == 'repo_branch_protected':
657 665 # Get custom repo-branch-protected status code if present.
658 666 return HTTPRepoBranchProtected(title=exception.message)
659 667
660 668 exc_info = request.exc_info
661 669 store_exception(id(exc_info), exc_info)
662 670
663 671 traceback_info = 'unavailable'
664 672 if request.exc_info:
665 673 exc_type, exc_value, exc_tb = request.exc_info
666 674 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
667 675
668 676 log.error(
669 677 'error occurred handling this request for path: %s, \n tb: %s',
670 678 request.path, traceback_info)
671 679 raise exception
672 680
673 681
674 682 class ResponseFilter(object):
675 683
676 684 def __init__(self, start_response):
677 685 self._start_response = start_response
678 686
679 687 def __call__(self, status, response_headers, exc_info=None):
680 688 headers = tuple(
681 689 (h, v) for h, v in response_headers
682 690 if not wsgiref.util.is_hop_by_hop(h))
683 691 return self._start_response(status, headers, exc_info)
684 692
685 693
686 694 def main(global_config, **settings):
687 695 if MercurialFactory:
688 696 hgpatches.patch_largefiles_capabilities()
689 697 hgpatches.patch_subrepo_type_mapping()
690 698
691 699 app = HTTPApplication(settings=settings, global_config=global_config)
692 700 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now