##// END OF EJS Templates
request-wrapper: add request counter.
marcink -
r756:9c499655 default
parent child Browse files
Show More
@@ -0,0 +1,27 b''
1 # -*- coding: utf-8 -*-
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2019 RhodeCode GmbH
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20
21 counter = 0
22
23
24 def get_request_counter(request):
25 global counter
26 counter += 1
27 return counter
@@ -1,621 +1,624 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
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
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
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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,
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
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import os
18 import os
19 import sys
19 import sys
20 import base64
20 import base64
21 import locale
21 import locale
22 import logging
22 import logging
23 import uuid
23 import uuid
24 import wsgiref.util
24 import wsgiref.util
25 import traceback
25 import traceback
26 import tempfile
26 import tempfile
27 from itertools import chain
27 from itertools import chain
28
28
29 import simplejson as json
29 import simplejson as json
30 import msgpack
30 import msgpack
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.compat import configparser
34 from pyramid.compat import configparser
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
39 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
40 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
41
41
42 try:
42 try:
43 locale.setlocale(locale.LC_ALL, '')
43 locale.setlocale(locale.LC_ALL, '')
44 except locale.Error as e:
44 except locale.Error as e:
45 log.error(
45 log.error(
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
46 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
47 os.environ['LC_ALL'] = 'C'
47 os.environ['LC_ALL'] = 'C'
48
48
49 import vcsserver
49 import vcsserver
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
50 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
51 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
52 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
53 from vcsserver.echo_stub.echo_app import EchoApp
53 from vcsserver.echo_stub.echo_app import EchoApp
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
54 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
55 from vcsserver.lib.exc_tracking import store_exception
55 from vcsserver.lib.exc_tracking import store_exception
56 from vcsserver.server import VcsServer
56 from vcsserver.server import VcsServer
57
57
58 try:
58 try:
59 from vcsserver.git import GitFactory, GitRemote
59 from vcsserver.git import GitFactory, GitRemote
60 except ImportError:
60 except ImportError:
61 GitFactory = None
61 GitFactory = None
62 GitRemote = None
62 GitRemote = None
63
63
64 try:
64 try:
65 from vcsserver.hg import MercurialFactory, HgRemote
65 from vcsserver.hg import MercurialFactory, HgRemote
66 except ImportError:
66 except ImportError:
67 MercurialFactory = None
67 MercurialFactory = None
68 HgRemote = None
68 HgRemote = None
69
69
70 try:
70 try:
71 from vcsserver.svn import SubversionFactory, SvnRemote
71 from vcsserver.svn import SubversionFactory, SvnRemote
72 except ImportError:
72 except ImportError:
73 SubversionFactory = None
73 SubversionFactory = None
74 SvnRemote = None
74 SvnRemote = None
75
75
76
76
77 def _is_request_chunked(environ):
77 def _is_request_chunked(environ):
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
78 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
79 return stream
79 return stream
80
80
81
81
82 def _int_setting(settings, name, default):
82 def _int_setting(settings, name, default):
83 settings[name] = int(settings.get(name, default))
83 settings[name] = int(settings.get(name, default))
84 return settings[name]
84 return settings[name]
85
85
86
86
87 def _bool_setting(settings, name, default):
87 def _bool_setting(settings, name, default):
88 input_val = settings.get(name, default)
88 input_val = settings.get(name, default)
89 if isinstance(input_val, unicode):
89 if isinstance(input_val, unicode):
90 input_val = input_val.encode('utf8')
90 input_val = input_val.encode('utf8')
91 settings[name] = asbool(input_val)
91 settings[name] = asbool(input_val)
92 return settings[name]
92 return settings[name]
93
93
94
94
95 def _list_setting(settings, name, default):
95 def _list_setting(settings, name, default):
96 raw_value = settings.get(name, default)
96 raw_value = settings.get(name, default)
97
97
98 # Otherwise we assume it uses pyramids space/newline separation.
98 # Otherwise we assume it uses pyramids space/newline separation.
99 settings[name] = aslist(raw_value)
99 settings[name] = aslist(raw_value)
100 return settings[name]
100 return settings[name]
101
101
102
102
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
103 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
104 value = settings.get(name, default)
104 value = settings.get(name, default)
105
105
106 if default_when_empty and not value:
106 if default_when_empty and not value:
107 # use default value when value is empty
107 # use default value when value is empty
108 value = default
108 value = default
109
109
110 if lower:
110 if lower:
111 value = value.lower()
111 value = value.lower()
112 settings[name] = value
112 settings[name] = value
113 return settings[name]
113 return settings[name]
114
114
115
115
116 class VCS(object):
116 class VCS(object):
117 def __init__(self, locale=None, cache_config=None):
117 def __init__(self, locale=None, cache_config=None):
118 self.locale = locale
118 self.locale = locale
119 self.cache_config = cache_config
119 self.cache_config = cache_config
120 self._configure_locale()
120 self._configure_locale()
121
121
122 if GitFactory and GitRemote:
122 if GitFactory and GitRemote:
123 git_factory = GitFactory()
123 git_factory = GitFactory()
124 self._git_remote = GitRemote(git_factory)
124 self._git_remote = GitRemote(git_factory)
125 else:
125 else:
126 log.info("Git client import failed")
126 log.info("Git client import failed")
127
127
128 if MercurialFactory and HgRemote:
128 if MercurialFactory and HgRemote:
129 hg_factory = MercurialFactory()
129 hg_factory = MercurialFactory()
130 self._hg_remote = HgRemote(hg_factory)
130 self._hg_remote = HgRemote(hg_factory)
131 else:
131 else:
132 log.info("Mercurial client import failed")
132 log.info("Mercurial client import failed")
133
133
134 if SubversionFactory and SvnRemote:
134 if SubversionFactory and SvnRemote:
135 svn_factory = SubversionFactory()
135 svn_factory = SubversionFactory()
136
136
137 # hg factory is used for svn url validation
137 # hg factory is used for svn url validation
138 hg_factory = MercurialFactory()
138 hg_factory = MercurialFactory()
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
139 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 else:
140 else:
141 log.info("Subversion client import failed")
141 log.info("Subversion client import failed")
142
142
143 self._vcsserver = VcsServer()
143 self._vcsserver = VcsServer()
144
144
145 def _configure_locale(self):
145 def _configure_locale(self):
146 if self.locale:
146 if self.locale:
147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
147 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 else:
148 else:
149 log.info(
149 log.info(
150 'Configuring locale subsystem based on environment variables')
150 'Configuring locale subsystem based on environment variables')
151 try:
151 try:
152 # If self.locale is the empty string, then the locale
152 # If self.locale is the empty string, then the locale
153 # module will use the environment variables. See the
153 # module will use the environment variables. See the
154 # documentation of the package `locale`.
154 # documentation of the package `locale`.
155 locale.setlocale(locale.LC_ALL, self.locale)
155 locale.setlocale(locale.LC_ALL, self.locale)
156
156
157 language_code, encoding = locale.getlocale()
157 language_code, encoding = locale.getlocale()
158 log.info(
158 log.info(
159 'Locale set to language code "%s" with encoding "%s".',
159 'Locale set to language code "%s" with encoding "%s".',
160 language_code, encoding)
160 language_code, encoding)
161 except locale.Error:
161 except locale.Error:
162 log.exception(
162 log.exception(
163 'Cannot set locale, not configuring the locale system')
163 'Cannot set locale, not configuring the locale system')
164
164
165
165
166 class WsgiProxy(object):
166 class WsgiProxy(object):
167 def __init__(self, wsgi):
167 def __init__(self, wsgi):
168 self.wsgi = wsgi
168 self.wsgi = wsgi
169
169
170 def __call__(self, environ, start_response):
170 def __call__(self, environ, start_response):
171 input_data = environ['wsgi.input'].read()
171 input_data = environ['wsgi.input'].read()
172 input_data = msgpack.unpackb(input_data)
172 input_data = msgpack.unpackb(input_data)
173
173
174 error = None
174 error = None
175 try:
175 try:
176 data, status, headers = self.wsgi.handle(
176 data, status, headers = self.wsgi.handle(
177 input_data['environment'], input_data['input_data'],
177 input_data['environment'], input_data['input_data'],
178 *input_data['args'], **input_data['kwargs'])
178 *input_data['args'], **input_data['kwargs'])
179 except Exception as e:
179 except Exception as e:
180 data, status, headers = [], None, None
180 data, status, headers = [], None, None
181 error = {
181 error = {
182 'message': str(e),
182 'message': str(e),
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
184 }
184 }
185
185
186 start_response(200, {})
186 start_response(200, {})
187 return self._iterator(error, status, headers, data)
187 return self._iterator(error, status, headers, data)
188
188
189 def _iterator(self, error, status, headers, data):
189 def _iterator(self, error, status, headers, data):
190 initial_data = [
190 initial_data = [
191 error,
191 error,
192 status,
192 status,
193 headers,
193 headers,
194 ]
194 ]
195
195
196 for d in chain(initial_data, data):
196 for d in chain(initial_data, data):
197 yield msgpack.packb(d)
197 yield msgpack.packb(d)
198
198
199
199
200 def not_found(request):
200 def not_found(request):
201 return {'status': '404 NOT FOUND'}
201 return {'status': '404 NOT FOUND'}
202
202
203
203
204 class VCSViewPredicate(object):
204 class VCSViewPredicate(object):
205 def __init__(self, val, config):
205 def __init__(self, val, config):
206 self.remotes = val
206 self.remotes = val
207
207
208 def text(self):
208 def text(self):
209 return 'vcs view method = %s' % (self.remotes.keys(),)
209 return 'vcs view method = %s' % (self.remotes.keys(),)
210
210
211 phash = text
211 phash = text
212
212
213 def __call__(self, context, request):
213 def __call__(self, context, request):
214 """
214 """
215 View predicate that returns true if given backend is supported by
215 View predicate that returns true if given backend is supported by
216 defined remotes.
216 defined remotes.
217 """
217 """
218 backend = request.matchdict.get('backend')
218 backend = request.matchdict.get('backend')
219 return backend in self.remotes
219 return backend in self.remotes
220
220
221
221
222 class HTTPApplication(object):
222 class HTTPApplication(object):
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
224
224
225 remote_wsgi = remote_wsgi
225 remote_wsgi = remote_wsgi
226 _use_echo_app = False
226 _use_echo_app = False
227
227
228 def __init__(self, settings=None, global_config=None):
228 def __init__(self, settings=None, global_config=None):
229 self._sanitize_settings_and_apply_defaults(settings)
229 self._sanitize_settings_and_apply_defaults(settings)
230
230
231 self.config = Configurator(settings=settings)
231 self.config = Configurator(settings=settings)
232 self.global_config = global_config
232 self.global_config = global_config
233 self.config.include('vcsserver.lib.rc_cache')
233 self.config.include('vcsserver.lib.rc_cache')
234
234
235 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
235 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
236 vcs = VCS(locale=settings_locale, cache_config=settings)
236 vcs = VCS(locale=settings_locale, cache_config=settings)
237 self._remotes = {
237 self._remotes = {
238 'hg': vcs._hg_remote,
238 'hg': vcs._hg_remote,
239 'git': vcs._git_remote,
239 'git': vcs._git_remote,
240 'svn': vcs._svn_remote,
240 'svn': vcs._svn_remote,
241 'server': vcs._vcsserver,
241 'server': vcs._vcsserver,
242 }
242 }
243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
243 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
244 self._use_echo_app = True
244 self._use_echo_app = True
245 log.warning("Using EchoApp for VCS operations.")
245 log.warning("Using EchoApp for VCS operations.")
246 self.remote_wsgi = remote_wsgi_stub
246 self.remote_wsgi = remote_wsgi_stub
247
247
248 self._configure_settings(global_config, settings)
248 self._configure_settings(global_config, settings)
249 self._configure()
249 self._configure()
250
250
251 def _configure_settings(self, global_config, app_settings):
251 def _configure_settings(self, global_config, app_settings):
252 """
252 """
253 Configure the settings module.
253 Configure the settings module.
254 """
254 """
255 settings_merged = global_config.copy()
255 settings_merged = global_config.copy()
256 settings_merged.update(app_settings)
256 settings_merged.update(app_settings)
257
257
258 git_path = app_settings.get('git_path', None)
258 git_path = app_settings.get('git_path', None)
259 if git_path:
259 if git_path:
260 settings.GIT_EXECUTABLE = git_path
260 settings.GIT_EXECUTABLE = git_path
261 binary_dir = app_settings.get('core.binary_dir', None)
261 binary_dir = app_settings.get('core.binary_dir', None)
262 if binary_dir:
262 if binary_dir:
263 settings.BINARY_DIR = binary_dir
263 settings.BINARY_DIR = binary_dir
264
264
265 # Store the settings to make them available to other modules.
265 # Store the settings to make them available to other modules.
266 vcsserver.PYRAMID_SETTINGS = settings_merged
266 vcsserver.PYRAMID_SETTINGS = settings_merged
267 vcsserver.CONFIG = settings_merged
267 vcsserver.CONFIG = settings_merged
268
268
269 def _sanitize_settings_and_apply_defaults(self, settings):
269 def _sanitize_settings_and_apply_defaults(self, settings):
270 temp_store = tempfile.gettempdir()
270 temp_store = tempfile.gettempdir()
271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
271 default_cache_dir = os.path.join(temp_store, 'rc_cache')
272
272
273 # save default, cache dir, and use it for all backends later.
273 # save default, cache dir, and use it for all backends later.
274 default_cache_dir = _string_setting(
274 default_cache_dir = _string_setting(
275 settings,
275 settings,
276 'cache_dir',
276 'cache_dir',
277 default_cache_dir, lower=False, default_when_empty=True)
277 default_cache_dir, lower=False, default_when_empty=True)
278
278
279 # ensure we have our dir created
279 # ensure we have our dir created
280 if not os.path.isdir(default_cache_dir):
280 if not os.path.isdir(default_cache_dir):
281 os.makedirs(default_cache_dir, mode=0o755)
281 os.makedirs(default_cache_dir, mode=0o755)
282
282
283 # exception store cache
283 # exception store cache
284 _string_setting(
284 _string_setting(
285 settings,
285 settings,
286 'exception_tracker.store_path',
286 'exception_tracker.store_path',
287 temp_store, lower=False, default_when_empty=True)
287 temp_store, lower=False, default_when_empty=True)
288
288
289 # repo_object cache
289 # repo_object cache
290 _string_setting(
290 _string_setting(
291 settings,
291 settings,
292 'rc_cache.repo_object.backend',
292 'rc_cache.repo_object.backend',
293 'dogpile.cache.rc.memory_lru')
293 'dogpile.cache.rc.memory_lru')
294 _int_setting(
294 _int_setting(
295 settings,
295 settings,
296 'rc_cache.repo_object.expiration_time',
296 'rc_cache.repo_object.expiration_time',
297 300)
297 300)
298 _int_setting(
298 _int_setting(
299 settings,
299 settings,
300 'rc_cache.repo_object.max_size',
300 'rc_cache.repo_object.max_size',
301 1024)
301 1024)
302
302
303 def _configure(self):
303 def _configure(self):
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
304 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
305
305
306 self.config.add_route('service', '/_service')
306 self.config.add_route('service', '/_service')
307 self.config.add_route('status', '/status')
307 self.config.add_route('status', '/status')
308 self.config.add_route('hg_proxy', '/proxy/hg')
308 self.config.add_route('hg_proxy', '/proxy/hg')
309 self.config.add_route('git_proxy', '/proxy/git')
309 self.config.add_route('git_proxy', '/proxy/git')
310 self.config.add_route('vcs', '/{backend}')
310 self.config.add_route('vcs', '/{backend}')
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
311 self.config.add_route('stream_git', '/stream/git/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
312 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
313
313
314 self.config.add_view(self.status_view, route_name='status', renderer='json')
314 self.config.add_view(self.status_view, route_name='status', renderer='json')
315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
315 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
316
316
317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
317 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
318 self.config.add_view(self.git_proxy(), route_name='git_proxy')
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
319 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
320 vcs_view=self._remotes)
320 vcs_view=self._remotes)
321
321
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
322 self.config.add_view(self.hg_stream(), route_name='stream_hg')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
323 self.config.add_view(self.git_stream(), route_name='stream_git')
324
324
325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
325 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
326
326
327 self.config.add_notfound_view(not_found, renderer='json')
327 self.config.add_notfound_view(not_found, renderer='json')
328
328
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
329 self.config.add_view(self.handle_vcs_exception, context=Exception)
330
330
331 self.config.add_tween(
331 self.config.add_tween(
332 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
332 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
333 )
333 )
334 self.config.add_request_method(
335 'vcsserver.lib.request_counter.get_request_counter',
336 'request_count')
334
337
335 def wsgi_app(self):
338 def wsgi_app(self):
336 return self.config.make_wsgi_app()
339 return self.config.make_wsgi_app()
337
340
338 def vcs_view(self, request):
341 def vcs_view(self, request):
339 remote = self._remotes[request.matchdict['backend']]
342 remote = self._remotes[request.matchdict['backend']]
340 payload = msgpack.unpackb(request.body, use_list=True)
343 payload = msgpack.unpackb(request.body, use_list=True)
341 method = payload.get('method')
344 method = payload.get('method')
342 params = payload.get('params')
345 params = payload.get('params')
343 wire = params.get('wire')
346 wire = params.get('wire')
344 args = params.get('args')
347 args = params.get('args')
345 kwargs = params.get('kwargs')
348 kwargs = params.get('kwargs')
346 context_uid = None
349 context_uid = None
347
350
348 if wire:
351 if wire:
349 try:
352 try:
350 wire['context'] = context_uid = uuid.UUID(wire['context'])
353 wire['context'] = context_uid = uuid.UUID(wire['context'])
351 except KeyError:
354 except KeyError:
352 pass
355 pass
353 args.insert(0, wire)
356 args.insert(0, wire)
354
357
355 # NOTE(marcink): trading complexity for slight performance
358 # NOTE(marcink): trading complexity for slight performance
356 if log.isEnabledFor(logging.DEBUG):
359 if log.isEnabledFor(logging.DEBUG):
357 no_args_methods = [
360 no_args_methods = [
358 'archive_repo'
361 'archive_repo'
359 ]
362 ]
360 if method in no_args_methods:
363 if method in no_args_methods:
361 call_args = ''
364 call_args = ''
362 else:
365 else:
363 call_args = args[1:]
366 call_args = args[1:]
364
367
365 repo_state_uid = wire.get('repo_state_uid') if wire else None
368 repo_state_uid = wire.get('repo_state_uid') if wire else None
366 log.debug('method called:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
369 log.debug('method called:%s with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
367 method, call_args, kwargs, context_uid, repo_state_uid)
370 method, call_args, kwargs, context_uid, repo_state_uid)
368
371
369 try:
372 try:
370 resp = getattr(remote, method)(*args, **kwargs)
373 resp = getattr(remote, method)(*args, **kwargs)
371 except Exception as e:
374 except Exception as e:
372 exc_info = list(sys.exc_info())
375 exc_info = list(sys.exc_info())
373 exc_type, exc_value, exc_traceback = exc_info
376 exc_type, exc_value, exc_traceback = exc_info
374
377
375 org_exc = getattr(e, '_org_exc', None)
378 org_exc = getattr(e, '_org_exc', None)
376 org_exc_name = None
379 org_exc_name = None
377 org_exc_tb = ''
380 org_exc_tb = ''
378 if org_exc:
381 if org_exc:
379 org_exc_name = org_exc.__class__.__name__
382 org_exc_name = org_exc.__class__.__name__
380 org_exc_tb = getattr(e, '_org_exc_tb', '')
383 org_exc_tb = getattr(e, '_org_exc_tb', '')
381 # replace our "faked" exception with our org
384 # replace our "faked" exception with our org
382 exc_info[0] = org_exc.__class__
385 exc_info[0] = org_exc.__class__
383 exc_info[1] = org_exc
386 exc_info[1] = org_exc
384
387
385 store_exception(id(exc_info), exc_info)
388 store_exception(id(exc_info), exc_info)
386
389
387 tb_info = ''.join(
390 tb_info = ''.join(
388 traceback.format_exception(exc_type, exc_value, exc_traceback))
391 traceback.format_exception(exc_type, exc_value, exc_traceback))
389
392
390 type_ = e.__class__.__name__
393 type_ = e.__class__.__name__
391 if type_ not in self.ALLOWED_EXCEPTIONS:
394 if type_ not in self.ALLOWED_EXCEPTIONS:
392 type_ = None
395 type_ = None
393
396
394 resp = {
397 resp = {
395 'id': payload.get('id'),
398 'id': payload.get('id'),
396 'error': {
399 'error': {
397 'message': e.message,
400 'message': e.message,
398 'traceback': tb_info,
401 'traceback': tb_info,
399 'org_exc': org_exc_name,
402 'org_exc': org_exc_name,
400 'org_exc_tb': org_exc_tb,
403 'org_exc_tb': org_exc_tb,
401 'type': type_
404 'type': type_
402 }
405 }
403 }
406 }
404 try:
407 try:
405 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
408 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
406 except AttributeError:
409 except AttributeError:
407 pass
410 pass
408 else:
411 else:
409 resp = {
412 resp = {
410 'id': payload.get('id'),
413 'id': payload.get('id'),
411 'result': resp
414 'result': resp
412 }
415 }
413
416
414 return resp
417 return resp
415
418
416 def status_view(self, request):
419 def status_view(self, request):
417 import vcsserver
420 import vcsserver
418 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
421 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
419 'pid': os.getpid()}
422 'pid': os.getpid()}
420
423
421 def service_view(self, request):
424 def service_view(self, request):
422 import vcsserver
425 import vcsserver
423
426
424 payload = msgpack.unpackb(request.body, use_list=True)
427 payload = msgpack.unpackb(request.body, use_list=True)
425
428
426 try:
429 try:
427 path = self.global_config['__file__']
430 path = self.global_config['__file__']
428 config = configparser.ConfigParser()
431 config = configparser.ConfigParser()
429 config.read(path)
432 config.read(path)
430 parsed_ini = config
433 parsed_ini = config
431 if parsed_ini.has_section('server:main'):
434 if parsed_ini.has_section('server:main'):
432 parsed_ini = dict(parsed_ini.items('server:main'))
435 parsed_ini = dict(parsed_ini.items('server:main'))
433 except Exception:
436 except Exception:
434 log.exception('Failed to read .ini file for display')
437 log.exception('Failed to read .ini file for display')
435 parsed_ini = {}
438 parsed_ini = {}
436
439
437 resp = {
440 resp = {
438 'id': payload.get('id'),
441 'id': payload.get('id'),
439 'result': dict(
442 'result': dict(
440 version=vcsserver.__version__,
443 version=vcsserver.__version__,
441 config=parsed_ini,
444 config=parsed_ini,
442 payload=payload,
445 payload=payload,
443 )
446 )
444 }
447 }
445 return resp
448 return resp
446
449
447 def _msgpack_renderer_factory(self, info):
450 def _msgpack_renderer_factory(self, info):
448 def _render(value, system):
451 def _render(value, system):
449 request = system.get('request')
452 request = system.get('request')
450 if request is not None:
453 if request is not None:
451 response = request.response
454 response = request.response
452 ct = response.content_type
455 ct = response.content_type
453 if ct == response.default_content_type:
456 if ct == response.default_content_type:
454 response.content_type = 'application/x-msgpack'
457 response.content_type = 'application/x-msgpack'
455 return msgpack.packb(value)
458 return msgpack.packb(value)
456 return _render
459 return _render
457
460
458 def set_env_from_config(self, environ, config):
461 def set_env_from_config(self, environ, config):
459 dict_conf = {}
462 dict_conf = {}
460 try:
463 try:
461 for elem in config:
464 for elem in config:
462 if elem[0] == 'rhodecode':
465 if elem[0] == 'rhodecode':
463 dict_conf = json.loads(elem[2])
466 dict_conf = json.loads(elem[2])
464 break
467 break
465 except Exception:
468 except Exception:
466 log.exception('Failed to fetch SCM CONFIG')
469 log.exception('Failed to fetch SCM CONFIG')
467 return
470 return
468
471
469 username = dict_conf.get('username')
472 username = dict_conf.get('username')
470 if username:
473 if username:
471 environ['REMOTE_USER'] = username
474 environ['REMOTE_USER'] = username
472 # mercurial specific, some extension api rely on this
475 # mercurial specific, some extension api rely on this
473 environ['HGUSER'] = username
476 environ['HGUSER'] = username
474
477
475 ip = dict_conf.get('ip')
478 ip = dict_conf.get('ip')
476 if ip:
479 if ip:
477 environ['REMOTE_HOST'] = ip
480 environ['REMOTE_HOST'] = ip
478
481
479 if _is_request_chunked(environ):
482 if _is_request_chunked(environ):
480 # set the compatibility flag for webob
483 # set the compatibility flag for webob
481 environ['wsgi.input_terminated'] = True
484 environ['wsgi.input_terminated'] = True
482
485
483 def hg_proxy(self):
486 def hg_proxy(self):
484 @wsgiapp
487 @wsgiapp
485 def _hg_proxy(environ, start_response):
488 def _hg_proxy(environ, start_response):
486 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
489 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
487 return app(environ, start_response)
490 return app(environ, start_response)
488 return _hg_proxy
491 return _hg_proxy
489
492
490 def git_proxy(self):
493 def git_proxy(self):
491 @wsgiapp
494 @wsgiapp
492 def _git_proxy(environ, start_response):
495 def _git_proxy(environ, start_response):
493 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
496 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
494 return app(environ, start_response)
497 return app(environ, start_response)
495 return _git_proxy
498 return _git_proxy
496
499
497 def hg_stream(self):
500 def hg_stream(self):
498 if self._use_echo_app:
501 if self._use_echo_app:
499 @wsgiapp
502 @wsgiapp
500 def _hg_stream(environ, start_response):
503 def _hg_stream(environ, start_response):
501 app = EchoApp('fake_path', 'fake_name', None)
504 app = EchoApp('fake_path', 'fake_name', None)
502 return app(environ, start_response)
505 return app(environ, start_response)
503 return _hg_stream
506 return _hg_stream
504 else:
507 else:
505 @wsgiapp
508 @wsgiapp
506 def _hg_stream(environ, start_response):
509 def _hg_stream(environ, start_response):
507 log.debug('http-app: handling hg stream')
510 log.debug('http-app: handling hg stream')
508 repo_path = environ['HTTP_X_RC_REPO_PATH']
511 repo_path = environ['HTTP_X_RC_REPO_PATH']
509 repo_name = environ['HTTP_X_RC_REPO_NAME']
512 repo_name = environ['HTTP_X_RC_REPO_NAME']
510 packed_config = base64.b64decode(
513 packed_config = base64.b64decode(
511 environ['HTTP_X_RC_REPO_CONFIG'])
514 environ['HTTP_X_RC_REPO_CONFIG'])
512 config = msgpack.unpackb(packed_config)
515 config = msgpack.unpackb(packed_config)
513 app = scm_app.create_hg_wsgi_app(
516 app = scm_app.create_hg_wsgi_app(
514 repo_path, repo_name, config)
517 repo_path, repo_name, config)
515
518
516 # Consistent path information for hgweb
519 # Consistent path information for hgweb
517 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
520 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
518 environ['REPO_NAME'] = repo_name
521 environ['REPO_NAME'] = repo_name
519 self.set_env_from_config(environ, config)
522 self.set_env_from_config(environ, config)
520
523
521 log.debug('http-app: starting app handler '
524 log.debug('http-app: starting app handler '
522 'with %s and process request', app)
525 'with %s and process request', app)
523 return app(environ, ResponseFilter(start_response))
526 return app(environ, ResponseFilter(start_response))
524 return _hg_stream
527 return _hg_stream
525
528
526 def git_stream(self):
529 def git_stream(self):
527 if self._use_echo_app:
530 if self._use_echo_app:
528 @wsgiapp
531 @wsgiapp
529 def _git_stream(environ, start_response):
532 def _git_stream(environ, start_response):
530 app = EchoApp('fake_path', 'fake_name', None)
533 app = EchoApp('fake_path', 'fake_name', None)
531 return app(environ, start_response)
534 return app(environ, start_response)
532 return _git_stream
535 return _git_stream
533 else:
536 else:
534 @wsgiapp
537 @wsgiapp
535 def _git_stream(environ, start_response):
538 def _git_stream(environ, start_response):
536 log.debug('http-app: handling git stream')
539 log.debug('http-app: handling git stream')
537 repo_path = environ['HTTP_X_RC_REPO_PATH']
540 repo_path = environ['HTTP_X_RC_REPO_PATH']
538 repo_name = environ['HTTP_X_RC_REPO_NAME']
541 repo_name = environ['HTTP_X_RC_REPO_NAME']
539 packed_config = base64.b64decode(
542 packed_config = base64.b64decode(
540 environ['HTTP_X_RC_REPO_CONFIG'])
543 environ['HTTP_X_RC_REPO_CONFIG'])
541 config = msgpack.unpackb(packed_config)
544 config = msgpack.unpackb(packed_config)
542
545
543 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
546 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
544 self.set_env_from_config(environ, config)
547 self.set_env_from_config(environ, config)
545
548
546 content_type = environ.get('CONTENT_TYPE', '')
549 content_type = environ.get('CONTENT_TYPE', '')
547
550
548 path = environ['PATH_INFO']
551 path = environ['PATH_INFO']
549 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
552 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
550 log.debug(
553 log.debug(
551 'LFS: Detecting if request `%s` is LFS server path based '
554 'LFS: Detecting if request `%s` is LFS server path based '
552 'on content type:`%s`, is_lfs:%s',
555 'on content type:`%s`, is_lfs:%s',
553 path, content_type, is_lfs_request)
556 path, content_type, is_lfs_request)
554
557
555 if not is_lfs_request:
558 if not is_lfs_request:
556 # fallback detection by path
559 # fallback detection by path
557 if GIT_LFS_PROTO_PAT.match(path):
560 if GIT_LFS_PROTO_PAT.match(path):
558 is_lfs_request = True
561 is_lfs_request = True
559 log.debug(
562 log.debug(
560 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
563 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
561 path, is_lfs_request)
564 path, is_lfs_request)
562
565
563 if is_lfs_request:
566 if is_lfs_request:
564 app = scm_app.create_git_lfs_wsgi_app(
567 app = scm_app.create_git_lfs_wsgi_app(
565 repo_path, repo_name, config)
568 repo_path, repo_name, config)
566 else:
569 else:
567 app = scm_app.create_git_wsgi_app(
570 app = scm_app.create_git_wsgi_app(
568 repo_path, repo_name, config)
571 repo_path, repo_name, config)
569
572
570 log.debug('http-app: starting app handler '
573 log.debug('http-app: starting app handler '
571 'with %s and process request', app)
574 'with %s and process request', app)
572
575
573 return app(environ, start_response)
576 return app(environ, start_response)
574
577
575 return _git_stream
578 return _git_stream
576
579
577 def handle_vcs_exception(self, exception, request):
580 def handle_vcs_exception(self, exception, request):
578 _vcs_kind = getattr(exception, '_vcs_kind', '')
581 _vcs_kind = getattr(exception, '_vcs_kind', '')
579 if _vcs_kind == 'repo_locked':
582 if _vcs_kind == 'repo_locked':
580 # Get custom repo-locked status code if present.
583 # Get custom repo-locked status code if present.
581 status_code = request.headers.get('X-RC-Locked-Status-Code')
584 status_code = request.headers.get('X-RC-Locked-Status-Code')
582 return HTTPRepoLocked(
585 return HTTPRepoLocked(
583 title=exception.message, status_code=status_code)
586 title=exception.message, status_code=status_code)
584
587
585 elif _vcs_kind == 'repo_branch_protected':
588 elif _vcs_kind == 'repo_branch_protected':
586 # Get custom repo-branch-protected status code if present.
589 # Get custom repo-branch-protected status code if present.
587 return HTTPRepoBranchProtected(title=exception.message)
590 return HTTPRepoBranchProtected(title=exception.message)
588
591
589 exc_info = request.exc_info
592 exc_info = request.exc_info
590 store_exception(id(exc_info), exc_info)
593 store_exception(id(exc_info), exc_info)
591
594
592 traceback_info = 'unavailable'
595 traceback_info = 'unavailable'
593 if request.exc_info:
596 if request.exc_info:
594 exc_type, exc_value, exc_tb = request.exc_info
597 exc_type, exc_value, exc_tb = request.exc_info
595 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
598 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
596
599
597 log.error(
600 log.error(
598 'error occurred handling this request for path: %s, \n tb: %s',
601 'error occurred handling this request for path: %s, \n tb: %s',
599 request.path, traceback_info)
602 request.path, traceback_info)
600 raise exception
603 raise exception
601
604
602
605
603 class ResponseFilter(object):
606 class ResponseFilter(object):
604
607
605 def __init__(self, start_response):
608 def __init__(self, start_response):
606 self._start_response = start_response
609 self._start_response = start_response
607
610
608 def __call__(self, status, response_headers, exc_info=None):
611 def __call__(self, status, response_headers, exc_info=None):
609 headers = tuple(
612 headers = tuple(
610 (h, v) for h, v in response_headers
613 (h, v) for h, v in response_headers
611 if not wsgiref.util.is_hop_by_hop(h))
614 if not wsgiref.util.is_hop_by_hop(h))
612 return self._start_response(status, headers, exc_info)
615 return self._start_response(status, headers, exc_info)
613
616
614
617
615 def main(global_config, **settings):
618 def main(global_config, **settings):
616 if MercurialFactory:
619 if MercurialFactory:
617 hgpatches.patch_largefiles_capabilities()
620 hgpatches.patch_largefiles_capabilities()
618 hgpatches.patch_subrepo_type_mapping()
621 hgpatches.patch_subrepo_type_mapping()
619
622
620 app = HTTPApplication(settings=settings, global_config=global_config)
623 app = HTTPApplication(settings=settings, global_config=global_config)
621 return app.wsgi_app()
624 return app.wsgi_app()
@@ -1,64 +1,65 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
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
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
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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,
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
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18
18
19
19
20 import time
20 import time
21 import logging
21 import logging
22
22
23
23
24 from vcsserver.utils import safe_str
24 from vcsserver.utils import safe_str
25
25
26
26
27 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
28
28
29
29
30 def get_access_path(request):
30 def get_access_path(request):
31 environ = request.environ
31 environ = request.environ
32 return environ.get('PATH_INFO')
32 return environ.get('PATH_INFO')
33
33
34
34
35 def get_user_agent(environ):
35 def get_user_agent(environ):
36 return environ.get('HTTP_USER_AGENT')
36 return environ.get('HTTP_USER_AGENT')
37
37
38
38
39 class RequestWrapperTween(object):
39 class RequestWrapperTween(object):
40 def __init__(self, handler, registry):
40 def __init__(self, handler, registry):
41 self.handler = handler
41 self.handler = handler
42 self.registry = registry
42 self.registry = registry
43
43
44 # one-time configuration code goes here
44 # one-time configuration code goes here
45
45
46 def __call__(self, request):
46 def __call__(self, request):
47 start = time.time()
47 start = time.time()
48 try:
48 try:
49 response = self.handler(request)
49 response = self.handler(request)
50 finally:
50 finally:
51 end = time.time()
51 end = time.time()
52 total = end - start
52 total = end - start
53 count = request.request_count()
53 log.info(
54 log.info(
54 'IP: %s %s Request to %s time: %.4fs [%s]',
55 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s]',
55 '127.0.0.1', request.environ.get('REQUEST_METHOD'),
56 count, '127.0.0.1', request.environ.get('REQUEST_METHOD'),
56 safe_str(get_access_path(request)), total, get_user_agent(request.environ))
57 safe_str(get_access_path(request)), total, get_user_agent(request.environ))
57
58
58 return response
59 return response
59
60
60
61
61 def includeme(config):
62 def includeme(config):
62 config.add_tween(
63 config.add_tween(
63 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
64 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
64 )
65 )
General Comments 0
You need to be logged in to leave comments. Login now