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