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