##// END OF EJS Templates
http-mode: expose tracebacks back to the client.
marcink -
r146:e9421363 default
parent child Browse files
Show More
@@ -1,376 +1,380 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2016 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 import traceback
23 24 from itertools import chain
24 25
25 26 import msgpack
26 27 from beaker.cache import CacheManager
27 28 from beaker.util import parse_cache_config_options
28 29 from pyramid.config import Configurator
29 30 from pyramid.wsgi import wsgiapp
30 31
31 32 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
32 33 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
33 34 from vcsserver.echo_stub.echo_app import EchoApp
34 35 from vcsserver.exceptions import HTTPRepoLocked
35 36 from vcsserver.server import VcsServer
36 37
37 38 try:
38 39 from vcsserver.git import GitFactory, GitRemote
39 40 except ImportError:
40 41 GitFactory = None
41 42 GitRemote = None
42 43 try:
43 44 from vcsserver.hg import MercurialFactory, HgRemote
44 45 except ImportError:
45 46 MercurialFactory = None
46 47 HgRemote = None
47 48 try:
48 49 from vcsserver.svn import SubversionFactory, SvnRemote
49 50 except ImportError:
50 51 SubversionFactory = None
51 52 SvnRemote = None
52 53
53 54 log = logging.getLogger(__name__)
54 55
55 56
56 57 class VCS(object):
57 58 def __init__(self, locale=None, cache_config=None):
58 59 self.locale = locale
59 60 self.cache_config = cache_config
60 61 self._configure_locale()
61 62 self._initialize_cache()
62 63
63 64 if GitFactory and GitRemote:
64 65 git_repo_cache = self.cache.get_cache_region(
65 66 'git', region='repo_object')
66 67 git_factory = GitFactory(git_repo_cache)
67 68 self._git_remote = GitRemote(git_factory)
68 69 else:
69 70 log.info("Git client import failed")
70 71
71 72 if MercurialFactory and HgRemote:
72 73 hg_repo_cache = self.cache.get_cache_region(
73 74 'hg', region='repo_object')
74 75 hg_factory = MercurialFactory(hg_repo_cache)
75 76 self._hg_remote = HgRemote(hg_factory)
76 77 else:
77 78 log.info("Mercurial client import failed")
78 79
79 80 if SubversionFactory and SvnRemote:
80 81 svn_repo_cache = self.cache.get_cache_region(
81 82 'svn', region='repo_object')
82 83 svn_factory = SubversionFactory(svn_repo_cache)
83 84 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
84 85 else:
85 86 log.info("Subversion client import failed")
86 87
87 88 self._vcsserver = VcsServer()
88 89
89 90 def _initialize_cache(self):
90 91 cache_config = parse_cache_config_options(self.cache_config)
91 92 log.info('Initializing beaker cache: %s' % cache_config)
92 93 self.cache = CacheManager(**cache_config)
93 94
94 95 def _configure_locale(self):
95 96 if self.locale:
96 97 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
97 98 else:
98 99 log.info(
99 100 'Configuring locale subsystem based on environment variables')
100 101 try:
101 102 # If self.locale is the empty string, then the locale
102 103 # module will use the environment variables. See the
103 104 # documentation of the package `locale`.
104 105 locale.setlocale(locale.LC_ALL, self.locale)
105 106
106 107 language_code, encoding = locale.getlocale()
107 108 log.info(
108 109 'Locale set to language code "%s" with encoding "%s".',
109 110 language_code, encoding)
110 111 except locale.Error:
111 112 log.exception(
112 113 'Cannot set locale, not configuring the locale system')
113 114
114 115
115 116 class WsgiProxy(object):
116 117 def __init__(self, wsgi):
117 118 self.wsgi = wsgi
118 119
119 120 def __call__(self, environ, start_response):
120 121 input_data = environ['wsgi.input'].read()
121 122 input_data = msgpack.unpackb(input_data)
122 123
123 124 error = None
124 125 try:
125 126 data, status, headers = self.wsgi.handle(
126 127 input_data['environment'], input_data['input_data'],
127 128 *input_data['args'], **input_data['kwargs'])
128 129 except Exception as e:
129 130 data, status, headers = [], None, None
130 131 error = {
131 132 'message': str(e),
132 133 '_vcs_kind': getattr(e, '_vcs_kind', None)
133 134 }
134 135
135 136 start_response(200, {})
136 137 return self._iterator(error, status, headers, data)
137 138
138 139 def _iterator(self, error, status, headers, data):
139 140 initial_data = [
140 141 error,
141 142 status,
142 143 headers,
143 144 ]
144 145
145 146 for d in chain(initial_data, data):
146 147 yield msgpack.packb(d)
147 148
148 149
149 150 class HTTPApplication(object):
150 151 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
151 152
152 153 remote_wsgi = remote_wsgi
153 154 _use_echo_app = False
154 155
155 156 def __init__(self, settings=None):
156 157 self.config = Configurator(settings=settings)
157 158 locale = settings.get('', 'en_US.UTF-8')
158 159 vcs = VCS(locale=locale, cache_config=settings)
159 160 self._remotes = {
160 161 'hg': vcs._hg_remote,
161 162 'git': vcs._git_remote,
162 163 'svn': vcs._svn_remote,
163 164 'server': vcs._vcsserver,
164 165 }
165 166 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
166 167 self._use_echo_app = True
167 168 log.warning("Using EchoApp for VCS operations.")
168 169 self.remote_wsgi = remote_wsgi_stub
169 170 self._configure_settings(settings)
170 171 self._configure()
171 172
172 173 def _configure_settings(self, app_settings):
173 174 """
174 175 Configure the settings module.
175 176 """
176 177 git_path = app_settings.get('git_path', None)
177 178 if git_path:
178 179 settings.GIT_EXECUTABLE = git_path
179 180
180 181 def _configure(self):
181 182 self.config.add_renderer(
182 183 name='msgpack',
183 184 factory=self._msgpack_renderer_factory)
184 185
185 186 self.config.add_route('service', '/_service')
186 187 self.config.add_route('status', '/status')
187 188 self.config.add_route('hg_proxy', '/proxy/hg')
188 189 self.config.add_route('git_proxy', '/proxy/git')
189 190 self.config.add_route('vcs', '/{backend}')
190 191 self.config.add_route('stream_git', '/stream/git/*repo_name')
191 192 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
192 193
193 194 self.config.add_view(
194 195 self.status_view, route_name='status', renderer='json')
195 196 self.config.add_view(
196 197 self.service_view, route_name='service', renderer='msgpack')
197 198
198 199 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
199 200 self.config.add_view(self.git_proxy(), route_name='git_proxy')
200 201 self.config.add_view(
201 202 self.vcs_view, route_name='vcs', renderer='msgpack')
202 203
203 204 self.config.add_view(self.hg_stream(), route_name='stream_hg')
204 205 self.config.add_view(self.git_stream(), route_name='stream_git')
205 206 self.config.add_view(
206 207 self.handle_vcs_exception, context=Exception,
207 208 custom_predicates=[self.is_vcs_exception])
208 209
209 210 def wsgi_app(self):
210 211 return self.config.make_wsgi_app()
211 212
212 213 def vcs_view(self, request):
213 214 remote = self._remotes[request.matchdict['backend']]
214 215 payload = msgpack.unpackb(request.body, use_list=True)
215 216 method = payload.get('method')
216 217 params = payload.get('params')
217 218 wire = params.get('wire')
218 219 args = params.get('args')
219 220 kwargs = params.get('kwargs')
220 221 if wire:
221 222 try:
222 223 wire['context'] = uuid.UUID(wire['context'])
223 224 except KeyError:
224 225 pass
225 226 args.insert(0, wire)
226 227
227 228 try:
228 229 resp = getattr(remote, method)(*args, **kwargs)
229 230 except Exception as e:
231 tb_info = traceback.format_exc()
232
230 233 type_ = e.__class__.__name__
231 234 if type_ not in self.ALLOWED_EXCEPTIONS:
232 235 type_ = None
233 236
234 237 resp = {
235 238 'id': payload.get('id'),
236 239 'error': {
237 240 'message': e.message,
241 'traceback': tb_info,
238 242 'type': type_
239 243 }
240 244 }
241 245 try:
242 246 resp['error']['_vcs_kind'] = e._vcs_kind
243 247 except AttributeError:
244 248 pass
245 249 else:
246 250 resp = {
247 251 'id': payload.get('id'),
248 252 'result': resp
249 253 }
250 254
251 255 return resp
252 256
253 257 def status_view(self, request):
254 258 return {'status': 'OK'}
255 259
256 260 def service_view(self, request):
257 261 import vcsserver
258 262 payload = msgpack.unpackb(request.body, use_list=True)
259 263 resp = {
260 264 'id': payload.get('id'),
261 265 'result': dict(
262 266 version=vcsserver.__version__,
263 267 config={},
264 268 payload=payload,
265 269 )
266 270 }
267 271 return resp
268 272
269 273 def _msgpack_renderer_factory(self, info):
270 274 def _render(value, system):
271 275 value = msgpack.packb(value)
272 276 request = system.get('request')
273 277 if request is not None:
274 278 response = request.response
275 279 ct = response.content_type
276 280 if ct == response.default_content_type:
277 281 response.content_type = 'application/x-msgpack'
278 282 return value
279 283 return _render
280 284
281 285 def hg_proxy(self):
282 286 @wsgiapp
283 287 def _hg_proxy(environ, start_response):
284 288 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
285 289 return app(environ, start_response)
286 290 return _hg_proxy
287 291
288 292 def git_proxy(self):
289 293 @wsgiapp
290 294 def _git_proxy(environ, start_response):
291 295 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
292 296 return app(environ, start_response)
293 297 return _git_proxy
294 298
295 299 def hg_stream(self):
296 300 if self._use_echo_app:
297 301 @wsgiapp
298 302 def _hg_stream(environ, start_response):
299 303 app = EchoApp('fake_path', 'fake_name', None)
300 304 return app(environ, start_response)
301 305 return _hg_stream
302 306 else:
303 307 @wsgiapp
304 308 def _hg_stream(environ, start_response):
305 309 repo_path = environ['HTTP_X_RC_REPO_PATH']
306 310 repo_name = environ['HTTP_X_RC_REPO_NAME']
307 311 packed_config = base64.b64decode(
308 312 environ['HTTP_X_RC_REPO_CONFIG'])
309 313 config = msgpack.unpackb(packed_config)
310 314 app = scm_app.create_hg_wsgi_app(
311 315 repo_path, repo_name, config)
312 316
313 317 # Consitent path information for hgweb
314 318 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
315 319 environ['REPO_NAME'] = repo_name
316 320 return app(environ, ResponseFilter(start_response))
317 321 return _hg_stream
318 322
319 323 def git_stream(self):
320 324 if self._use_echo_app:
321 325 @wsgiapp
322 326 def _git_stream(environ, start_response):
323 327 app = EchoApp('fake_path', 'fake_name', None)
324 328 return app(environ, start_response)
325 329 return _git_stream
326 330 else:
327 331 @wsgiapp
328 332 def _git_stream(environ, start_response):
329 333 repo_path = environ['HTTP_X_RC_REPO_PATH']
330 334 repo_name = environ['HTTP_X_RC_REPO_NAME']
331 335 packed_config = base64.b64decode(
332 336 environ['HTTP_X_RC_REPO_CONFIG'])
333 337 config = msgpack.unpackb(packed_config)
334 338
335 339 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
336 340 app = scm_app.create_git_wsgi_app(
337 341 repo_path, repo_name, config)
338 342 return app(environ, start_response)
339 343 return _git_stream
340 344
341 345 def is_vcs_exception(self, context, request):
342 346 """
343 347 View predicate that returns true if the context object is a VCS
344 348 exception.
345 349 """
346 350 return hasattr(context, '_vcs_kind')
347 351
348 352 def handle_vcs_exception(self, exception, request):
349 353 if exception._vcs_kind == 'repo_locked':
350 354 # Get custom repo-locked status code if present.
351 355 status_code = request.headers.get('X-RC-Locked-Status-Code')
352 356 return HTTPRepoLocked(
353 357 title=exception.message, status_code=status_code)
354 358
355 359 # Re-raise exception if we can not handle it.
356 360 raise exception
357 361
358 362
359 363 class ResponseFilter(object):
360 364
361 365 def __init__(self, start_response):
362 366 self._start_response = start_response
363 367
364 368 def __call__(self, status, response_headers, exc_info=None):
365 369 headers = tuple(
366 370 (h, v) for h, v in response_headers
367 371 if not wsgiref.util.is_hop_by_hop(h))
368 372 return self._start_response(status, headers, exc_info)
369 373
370 374
371 375 def main(global_config, **settings):
372 376 if MercurialFactory:
373 377 hgpatches.patch_largefiles_capabilities()
374 378 hgpatches.patch_subrepo_type_mapping()
375 379 app = HTTPApplication(settings=settings)
376 380 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now