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