##// END OF EJS Templates
vcsserver: http main added time measuring tween for debugging.
marcink -
r154:1e471c21 default
parent child Browse files
Show More
@@ -0,0 +1,60 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2017 RodeCode GmbH
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18
19
20 import time
21 import logging
22
23
24 from vcsserver.utils import safe_str
25
26
27 log = logging.getLogger(__name__)
28
29
30 def get_access_path(request):
31 environ = request.environ
32 return environ.get('PATH_INFO')
33
34
35 class RequestWrapperTween(object):
36 def __init__(self, handler, registry):
37 self.handler = handler
38 self.registry = registry
39
40 # one-time configuration code goes here
41
42 def __call__(self, request):
43 start = time.time()
44 try:
45 response = self.handler(request)
46 finally:
47 end = time.time()
48
49 log.info('IP: %s Request to %s time: %.3fs' % (
50 '127.0.0.1',
51 safe_str(get_access_path(request)), end - start)
52 )
53
54 return response
55
56
57 def includeme(config):
58 config.add_tween(
59 'vcsserver.tweens.RequestWrapperTween',
60 )
@@ -1,403 +1,408 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):
157 157 self.config = Configurator(settings=settings)
158 158 locale = settings.get('locale', '') or 'en_US.UTF-8'
159 159 vcs = VCS(locale=locale, cache_config=settings)
160 160 self._remotes = {
161 161 'hg': vcs._hg_remote,
162 162 'git': vcs._git_remote,
163 163 'svn': vcs._svn_remote,
164 164 'server': vcs._vcsserver,
165 165 }
166 166 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
167 167 self._use_echo_app = True
168 168 log.warning("Using EchoApp for VCS operations.")
169 169 self.remote_wsgi = remote_wsgi_stub
170 170 self._configure_settings(settings)
171 171 self._configure()
172 172
173 173 def _configure_settings(self, app_settings):
174 174 """
175 175 Configure the settings module.
176 176 """
177 177 git_path = app_settings.get('git_path', None)
178 178 if git_path:
179 179 settings.GIT_EXECUTABLE = git_path
180 180
181 181 def _configure(self):
182 182 self.config.add_renderer(
183 183 name='msgpack',
184 184 factory=self._msgpack_renderer_factory)
185 185
186 186 self.config.add_route('service', '/_service')
187 187 self.config.add_route('status', '/status')
188 188 self.config.add_route('hg_proxy', '/proxy/hg')
189 189 self.config.add_route('git_proxy', '/proxy/git')
190 190 self.config.add_route('vcs', '/{backend}')
191 191 self.config.add_route('stream_git', '/stream/git/*repo_name')
192 192 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
193 193
194 194 self.config.add_view(
195 195 self.status_view, route_name='status', renderer='json')
196 196 self.config.add_view(
197 197 self.service_view, route_name='service', renderer='msgpack')
198 198
199 199 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
200 200 self.config.add_view(self.git_proxy(), route_name='git_proxy')
201 201 self.config.add_view(
202 202 self.vcs_view, route_name='vcs', renderer='msgpack',
203 203 custom_predicates=[self.is_vcs_view])
204 204
205 205 self.config.add_view(self.hg_stream(), route_name='stream_hg')
206 206 self.config.add_view(self.git_stream(), route_name='stream_git')
207 207
208 208 def notfound(request):
209 209 return {'status': '404 NOT FOUND'}
210 210 self.config.add_notfound_view(notfound, renderer='json')
211 211
212 212 self.config.add_view(
213 213 self.handle_vcs_exception, context=Exception,
214 214 custom_predicates=[self.is_vcs_exception])
215 215
216 216 self.config.add_view(
217 217 self.general_error_handler, context=Exception)
218 218
219 self.config.add_tween(
220 'vcsserver.tweens.RequestWrapperTween',
221 )
222
219 223 def wsgi_app(self):
220 224 return self.config.make_wsgi_app()
221 225
222 226 def vcs_view(self, request):
223 227 remote = self._remotes[request.matchdict['backend']]
224 228 payload = msgpack.unpackb(request.body, use_list=True)
225 229 method = payload.get('method')
226 230 params = payload.get('params')
227 231 wire = params.get('wire')
228 232 args = params.get('args')
229 233 kwargs = params.get('kwargs')
230 234 if wire:
231 235 try:
232 236 wire['context'] = uuid.UUID(wire['context'])
233 237 except KeyError:
234 238 pass
235 239 args.insert(0, wire)
236 240
241 log.debug('method called:%s with kwargs:%s', method, kwargs)
237 242 try:
238 243 resp = getattr(remote, method)(*args, **kwargs)
239 244 except Exception as e:
240 245 tb_info = traceback.format_exc()
241 246
242 247 type_ = e.__class__.__name__
243 248 if type_ not in self.ALLOWED_EXCEPTIONS:
244 249 type_ = None
245 250
246 251 resp = {
247 252 'id': payload.get('id'),
248 253 'error': {
249 254 'message': e.message,
250 255 'traceback': tb_info,
251 256 'type': type_
252 257 }
253 258 }
254 259 try:
255 260 resp['error']['_vcs_kind'] = e._vcs_kind
256 261 except AttributeError:
257 262 pass
258 263 else:
259 264 resp = {
260 265 'id': payload.get('id'),
261 266 'result': resp
262 267 }
263 268
264 269 return resp
265 270
266 271 def status_view(self, request):
267 272 return {'status': 'OK'}
268 273
269 274 def service_view(self, request):
270 275 import vcsserver
271 276 payload = msgpack.unpackb(request.body, use_list=True)
272 277 resp = {
273 278 'id': payload.get('id'),
274 279 'result': dict(
275 280 version=vcsserver.__version__,
276 281 config={},
277 282 payload=payload,
278 283 )
279 284 }
280 285 return resp
281 286
282 287 def _msgpack_renderer_factory(self, info):
283 288 def _render(value, system):
284 289 value = msgpack.packb(value)
285 290 request = system.get('request')
286 291 if request is not None:
287 292 response = request.response
288 293 ct = response.content_type
289 294 if ct == response.default_content_type:
290 295 response.content_type = 'application/x-msgpack'
291 296 return value
292 297 return _render
293 298
294 299 def hg_proxy(self):
295 300 @wsgiapp
296 301 def _hg_proxy(environ, start_response):
297 302 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
298 303 return app(environ, start_response)
299 304 return _hg_proxy
300 305
301 306 def git_proxy(self):
302 307 @wsgiapp
303 308 def _git_proxy(environ, start_response):
304 309 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
305 310 return app(environ, start_response)
306 311 return _git_proxy
307 312
308 313 def hg_stream(self):
309 314 if self._use_echo_app:
310 315 @wsgiapp
311 316 def _hg_stream(environ, start_response):
312 317 app = EchoApp('fake_path', 'fake_name', None)
313 318 return app(environ, start_response)
314 319 return _hg_stream
315 320 else:
316 321 @wsgiapp
317 322 def _hg_stream(environ, start_response):
318 323 repo_path = environ['HTTP_X_RC_REPO_PATH']
319 324 repo_name = environ['HTTP_X_RC_REPO_NAME']
320 325 packed_config = base64.b64decode(
321 326 environ['HTTP_X_RC_REPO_CONFIG'])
322 327 config = msgpack.unpackb(packed_config)
323 328 app = scm_app.create_hg_wsgi_app(
324 329 repo_path, repo_name, config)
325 330
326 331 # Consitent path information for hgweb
327 332 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
328 333 environ['REPO_NAME'] = repo_name
329 334 return app(environ, ResponseFilter(start_response))
330 335 return _hg_stream
331 336
332 337 def git_stream(self):
333 338 if self._use_echo_app:
334 339 @wsgiapp
335 340 def _git_stream(environ, start_response):
336 341 app = EchoApp('fake_path', 'fake_name', None)
337 342 return app(environ, start_response)
338 343 return _git_stream
339 344 else:
340 345 @wsgiapp
341 346 def _git_stream(environ, start_response):
342 347 repo_path = environ['HTTP_X_RC_REPO_PATH']
343 348 repo_name = environ['HTTP_X_RC_REPO_NAME']
344 349 packed_config = base64.b64decode(
345 350 environ['HTTP_X_RC_REPO_CONFIG'])
346 351 config = msgpack.unpackb(packed_config)
347 352
348 353 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
349 354 app = scm_app.create_git_wsgi_app(
350 355 repo_path, repo_name, config)
351 356 return app(environ, start_response)
352 357 return _git_stream
353 358
354 359 def is_vcs_view(self, context, request):
355 360 """
356 361 View predicate that returns true if given backend is supported by
357 362 defined remotes.
358 363 """
359 364 backend = request.matchdict.get('backend')
360 365 return backend in self._remotes
361 366
362 367 def is_vcs_exception(self, context, request):
363 368 """
364 369 View predicate that returns true if the context object is a VCS
365 370 exception.
366 371 """
367 372 return hasattr(context, '_vcs_kind')
368 373
369 374 def handle_vcs_exception(self, exception, request):
370 375 if exception._vcs_kind == 'repo_locked':
371 376 # Get custom repo-locked status code if present.
372 377 status_code = request.headers.get('X-RC-Locked-Status-Code')
373 378 return HTTPRepoLocked(
374 379 title=exception.message, status_code=status_code)
375 380
376 381 # Re-raise exception if we can not handle it.
377 382 raise exception
378 383
379 384 def general_error_handler(self, exception, request):
380 385 log.exception(
381 386 'error occurred handling this request for path: %s',
382 387 request.path)
383 388 raise exception
384 389
385 390
386 391 class ResponseFilter(object):
387 392
388 393 def __init__(self, start_response):
389 394 self._start_response = start_response
390 395
391 396 def __call__(self, status, response_headers, exc_info=None):
392 397 headers = tuple(
393 398 (h, v) for h, v in response_headers
394 399 if not wsgiref.util.is_hop_by_hop(h))
395 400 return self._start_response(status, headers, exc_info)
396 401
397 402
398 403 def main(global_config, **settings):
399 404 if MercurialFactory:
400 405 hgpatches.patch_largefiles_capabilities()
401 406 hgpatches.patch_subrepo_type_mapping()
402 407 app = HTTPApplication(settings=settings)
403 408 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now