##// END OF EJS Templates
http: added catch-all exception handler to show errors in logs.
marcink -
r150:ed9a1494 default
parent child Browse files
Show More
@@ -1,380 +1,390 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
204 204 self.config.add_view(self.hg_stream(), route_name='stream_hg')
205 205 self.config.add_view(self.git_stream(), route_name='stream_git')
206 206 self.config.add_view(
207 207 self.handle_vcs_exception, context=Exception,
208 208 custom_predicates=[self.is_vcs_exception])
209 209
210 self.config.add_view(
211 self.general_error_handler, context=Exception)
212
213
210 214 def wsgi_app(self):
211 215 return self.config.make_wsgi_app()
212 216
213 217 def vcs_view(self, request):
214 218 remote = self._remotes[request.matchdict['backend']]
215 219 payload = msgpack.unpackb(request.body, use_list=True)
216 220 method = payload.get('method')
217 221 params = payload.get('params')
218 222 wire = params.get('wire')
219 223 args = params.get('args')
220 224 kwargs = params.get('kwargs')
221 225 if wire:
222 226 try:
223 227 wire['context'] = uuid.UUID(wire['context'])
224 228 except KeyError:
225 229 pass
226 230 args.insert(0, wire)
227 231
228 232 try:
229 233 resp = getattr(remote, method)(*args, **kwargs)
230 234 except Exception as e:
231 235 tb_info = traceback.format_exc()
232 236
233 237 type_ = e.__class__.__name__
234 238 if type_ not in self.ALLOWED_EXCEPTIONS:
235 239 type_ = None
236 240
237 241 resp = {
238 242 'id': payload.get('id'),
239 243 'error': {
240 244 'message': e.message,
241 245 'traceback': tb_info,
242 246 'type': type_
243 247 }
244 248 }
245 249 try:
246 250 resp['error']['_vcs_kind'] = e._vcs_kind
247 251 except AttributeError:
248 252 pass
249 253 else:
250 254 resp = {
251 255 'id': payload.get('id'),
252 256 'result': resp
253 257 }
254 258
255 259 return resp
256 260
257 261 def status_view(self, request):
258 262 return {'status': 'OK'}
259 263
260 264 def service_view(self, request):
261 265 import vcsserver
262 266 payload = msgpack.unpackb(request.body, use_list=True)
263 267 resp = {
264 268 'id': payload.get('id'),
265 269 'result': dict(
266 270 version=vcsserver.__version__,
267 271 config={},
268 272 payload=payload,
269 273 )
270 274 }
271 275 return resp
272 276
273 277 def _msgpack_renderer_factory(self, info):
274 278 def _render(value, system):
275 279 value = msgpack.packb(value)
276 280 request = system.get('request')
277 281 if request is not None:
278 282 response = request.response
279 283 ct = response.content_type
280 284 if ct == response.default_content_type:
281 285 response.content_type = 'application/x-msgpack'
282 286 return value
283 287 return _render
284 288
285 289 def hg_proxy(self):
286 290 @wsgiapp
287 291 def _hg_proxy(environ, start_response):
288 292 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
289 293 return app(environ, start_response)
290 294 return _hg_proxy
291 295
292 296 def git_proxy(self):
293 297 @wsgiapp
294 298 def _git_proxy(environ, start_response):
295 299 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
296 300 return app(environ, start_response)
297 301 return _git_proxy
298 302
299 303 def hg_stream(self):
300 304 if self._use_echo_app:
301 305 @wsgiapp
302 306 def _hg_stream(environ, start_response):
303 307 app = EchoApp('fake_path', 'fake_name', None)
304 308 return app(environ, start_response)
305 309 return _hg_stream
306 310 else:
307 311 @wsgiapp
308 312 def _hg_stream(environ, start_response):
309 313 repo_path = environ['HTTP_X_RC_REPO_PATH']
310 314 repo_name = environ['HTTP_X_RC_REPO_NAME']
311 315 packed_config = base64.b64decode(
312 316 environ['HTTP_X_RC_REPO_CONFIG'])
313 317 config = msgpack.unpackb(packed_config)
314 318 app = scm_app.create_hg_wsgi_app(
315 319 repo_path, repo_name, config)
316 320
317 321 # Consitent path information for hgweb
318 322 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
319 323 environ['REPO_NAME'] = repo_name
320 324 return app(environ, ResponseFilter(start_response))
321 325 return _hg_stream
322 326
323 327 def git_stream(self):
324 328 if self._use_echo_app:
325 329 @wsgiapp
326 330 def _git_stream(environ, start_response):
327 331 app = EchoApp('fake_path', 'fake_name', None)
328 332 return app(environ, start_response)
329 333 return _git_stream
330 334 else:
331 335 @wsgiapp
332 336 def _git_stream(environ, start_response):
333 337 repo_path = environ['HTTP_X_RC_REPO_PATH']
334 338 repo_name = environ['HTTP_X_RC_REPO_NAME']
335 339 packed_config = base64.b64decode(
336 340 environ['HTTP_X_RC_REPO_CONFIG'])
337 341 config = msgpack.unpackb(packed_config)
338 342
339 343 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
340 344 app = scm_app.create_git_wsgi_app(
341 345 repo_path, repo_name, config)
342 346 return app(environ, start_response)
343 347 return _git_stream
344 348
345 349 def is_vcs_exception(self, context, request):
346 350 """
347 351 View predicate that returns true if the context object is a VCS
348 352 exception.
349 353 """
350 354 return hasattr(context, '_vcs_kind')
351 355
352 356 def handle_vcs_exception(self, exception, request):
353 357 if exception._vcs_kind == 'repo_locked':
354 358 # Get custom repo-locked status code if present.
355 359 status_code = request.headers.get('X-RC-Locked-Status-Code')
356 360 return HTTPRepoLocked(
357 361 title=exception.message, status_code=status_code)
358 362
359 363 # Re-raise exception if we can not handle it.
360 364 raise exception
361 365
366 def general_error_handler(self, exception, request):
367 log.exception(
368 'error occurred handling this request for path: %s',
369 request.path)
370 raise exception
371
362 372
363 373 class ResponseFilter(object):
364 374
365 375 def __init__(self, start_response):
366 376 self._start_response = start_response
367 377
368 378 def __call__(self, status, response_headers, exc_info=None):
369 379 headers = tuple(
370 380 (h, v) for h, v in response_headers
371 381 if not wsgiref.util.is_hop_by_hop(h))
372 382 return self._start_response(status, headers, exc_info)
373 383
374 384
375 385 def main(global_config, **settings):
376 386 if MercurialFactory:
377 387 hgpatches.patch_largefiles_capabilities()
378 388 hgpatches.patch_subrepo_type_mapping()
379 389 app = HTTPApplication(settings=settings)
380 390 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now