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