##// END OF EJS Templates
pyro4: Add the custom header `X-RhodeCode-Backend` to the pyro4 backend responses....
Martin Bornhold -
r848:2956cade default
parent child Browse files
Show More
@@ -1,84 +1,85 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 SimpleGit middleware for handling git protocol request (push/clone etc.)
23 23 It's implemented with basic auth function
24 24 """
25 25 import re
26 26 import logging
27 27 import urlparse
28 28
29 29 import rhodecode
30 30 from rhodecode.lib import utils2
31 31 from rhodecode.lib.middleware import simplevcs
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 GIT_PROTO_PAT = re.compile(
37 37 r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
38 38
39 39
40 40 class SimpleGit(simplevcs.SimpleVCS):
41 41
42 42 SCM = 'git'
43 43
44 44 def _get_repository_name(self, environ):
45 45 """
46 46 Gets repository name out of PATH_INFO header
47 47
48 48 :param environ: environ where PATH_INFO is stored
49 49 """
50 50 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
51 51 return repo_name
52 52
53 53 _ACTION_MAPPING = {
54 54 'git-receive-pack': 'push',
55 55 'git-upload-pack': 'pull',
56 56 }
57 57
58 58 def _get_action(self, environ):
59 59 """
60 60 Maps git request commands into a pull or push command.
61 61 In case of unknown/unexpected data, it returns 'pull' to be safe.
62 62
63 63 :param environ:
64 64 """
65 65 path = environ['PATH_INFO']
66 66
67 67 if path.endswith('/info/refs'):
68 68 query = urlparse.parse_qs(environ['QUERY_STRING'])
69 69 service_cmd = query.get('service', [''])[0]
70 70 return self._ACTION_MAPPING.get(service_cmd, 'pull')
71 71 elif path.endswith('/git-receive-pack'):
72 72 return 'push'
73 73 elif path.endswith('/git-upload-pack'):
74 74 return 'pull'
75 75
76 76 return 'pull'
77 77
78 78 def _create_wsgi_app(self, repo_path, repo_name, config):
79 return self.scm_app.create_git_wsgi_app(repo_path, repo_name, config)
79 return self.scm_app.create_git_wsgi_app(
80 repo_path, repo_name, config, self.SCM)
80 81
81 82 def _create_config(self, extras, repo_name):
82 83 extras['git_update_server_info'] = utils2.str2bool(
83 84 rhodecode.CONFIG.get('git_update_server_info'))
84 85 return extras
@@ -1,80 +1,81 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 SimpleHG middleware for handling mercurial protocol request
23 23 (push/clone etc.). It's implemented with basic auth function
24 24 """
25 25
26 26 import logging
27 27 import urlparse
28 28
29 29 from rhodecode.lib import utils
30 30 from rhodecode.lib.ext_json import json
31 31 from rhodecode.lib.middleware import simplevcs
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 class SimpleHg(simplevcs.SimpleVCS):
37 37
38 38 SCM = 'hg'
39 39
40 40 def _get_repository_name(self, environ):
41 41 """
42 42 Gets repository name out of PATH_INFO header
43 43
44 44 :param environ: environ where PATH_INFO is stored
45 45 """
46 46 return environ['PATH_INFO'].strip('/')
47 47
48 48 _ACTION_MAPPING = {
49 49 'changegroup': 'pull',
50 50 'changegroupsubset': 'pull',
51 51 'getbundle': 'pull',
52 52 'stream_out': 'pull',
53 53 'listkeys': 'pull',
54 54 'unbundle': 'push',
55 55 'pushkey': 'push',
56 56 }
57 57
58 58 def _get_action(self, environ):
59 59 """
60 60 Maps mercurial request commands into a pull or push command.
61 61 In case of unknown/unexpected data, it returns 'pull' to be safe.
62 62
63 63 :param environ:
64 64 """
65 65 query = urlparse.parse_qs(environ['QUERY_STRING'],
66 66 keep_blank_values=True)
67 67 if 'cmd' in query:
68 68 cmd = query['cmd'][0]
69 69 return self._ACTION_MAPPING.get(cmd, 'pull')
70 70
71 71 return 'pull'
72 72
73 73 def _create_wsgi_app(self, repo_path, repo_name, config):
74 return self.scm_app.create_hg_wsgi_app(repo_path, repo_name, config)
74 return self.scm_app.create_hg_wsgi_app(
75 repo_path, repo_name, config, self.SCM)
75 76
76 77 def _create_config(self, extras, repo_name):
77 78 config = utils.make_db_config(repo=repo_name)
78 79 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
79 80
80 81 return config.serialize()
@@ -1,63 +1,63 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Equivalent of rhodecode.lib.middleware.scm_app but using remote apps.
23 23 """
24 24
25 25 import logging
26 26
27 27 from rhodecode.lib.middleware.utils import wsgi_app_caller_client
28 28
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 HG_REMOTE_WSGI = None
34 34 GIT_REMOTE_WSGI = None
35 35
36 36
37 def create_git_wsgi_app(repo_path, repo_name, config):
37 def create_git_wsgi_app(repo_path, repo_name, config, backend):
38 38 """
39 39 Return a WSGI app backed by a remote app to handle Git.
40 40
41 41 config is a dictionary holding the extras.
42 42 """
43 43 factory = GIT_REMOTE_WSGI
44 44 if not factory:
45 45 log.error('Pyro server has not been initialized yet')
46 46
47 47 return wsgi_app_caller_client.RemoteAppCaller(
48 factory, repo_path, repo_name, config)
48 factory, backend, repo_path, repo_name, config)
49 49
50 50
51 def create_hg_wsgi_app(repo_path, repo_name, config):
51 def create_hg_wsgi_app(repo_path, repo_name, config, backend):
52 52 """
53 53 Return a WSGI app backed by a remote app to handle Mercurial.
54 54
55 55 config is a list of 3-item tuples representing a ConfigObject (it is the
56 56 serialized version of the config object).
57 57 """
58 58 factory = HG_REMOTE_WSGI
59 59 if not factory:
60 60 log.error('Pyro server has not been initialized yet')
61 61
62 62 return wsgi_app_caller_client.RemoteAppCaller(
63 factory, repo_path, repo_name, config)
63 factory, backend, repo_path, repo_name, config)
@@ -1,140 +1,140 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Implementation of the scm_app interface using raw HTTP communication.
23 23 """
24 24
25 25 import base64
26 26 import logging
27 27 import urlparse
28 28 import wsgiref.util
29 29
30 30 import msgpack
31 31 import requests
32 32 import webob.request
33 33
34 34 import rhodecode
35 35
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 def create_git_wsgi_app(repo_path, repo_name, config):
40 def create_git_wsgi_app(repo_path, repo_name, config, backend):
41 41 url = _vcs_streaming_url() + 'git/'
42 return VcsHttpProxy(url, repo_path, repo_name, config, 'git')
42 return VcsHttpProxy(url, repo_path, repo_name, config, backend)
43 43
44 44
45 def create_hg_wsgi_app(repo_path, repo_name, config):
45 def create_hg_wsgi_app(repo_path, repo_name, config, backend):
46 46 url = _vcs_streaming_url() + 'hg/'
47 return VcsHttpProxy(url, repo_path, repo_name, config, 'hg')
47 return VcsHttpProxy(url, repo_path, repo_name, config, backend)
48 48
49 49
50 50 def _vcs_streaming_url():
51 51 template = 'http://{}/stream/'
52 52 return template.format(rhodecode.CONFIG['vcs.server'])
53 53
54 54
55 55 # TODO: johbo: Avoid the global.
56 56 session = requests.Session()
57 57 # Requests speedup, avoid reading .netrc and similar
58 58 session.trust_env = False
59 59
60 60
61 61 class VcsHttpProxy(object):
62 62 """
63 63 A WSGI application which proxies vcs requests.
64 64
65 65 The goal is to shuffle the data around without touching it. The only
66 66 exception is the extra data from the config object which we send to the
67 67 server as well.
68 68 """
69 69
70 70 def __init__(self, url, repo_path, repo_name, config, backend):
71 71 """
72 72 :param str url: The URL of the VCSServer to call.
73 73 """
74 74 self._url = url
75 75 self._repo_name = repo_name
76 76 self._repo_path = repo_path
77 77 self._config = config
78 78 self._backend = backend
79 79 log.debug(
80 80 "Creating VcsHttpProxy for repo %s, url %s",
81 81 repo_name, url)
82 82
83 83 def __call__(self, environ, start_response):
84 84 status = '200 OK'
85 85
86 86 config = msgpack.packb(self._config)
87 87 request = webob.request.Request(environ)
88 88 request_headers = request.headers
89 89 request_headers.update({
90 90 # TODO: johbo: Remove this, rely on URL path only
91 91 'X-RC-Repo-Name': self._repo_name,
92 92 'X-RC-Repo-Path': self._repo_path,
93 93 'X-RC-Path-Info': environ['PATH_INFO'],
94 94 # TODO: johbo: Avoid encoding and put this into payload?
95 95 'X-RC-Repo-Config': base64.b64encode(config),
96 96 })
97 97
98 98 data = environ['wsgi.input'].read()
99 99 method = environ['REQUEST_METHOD']
100 100
101 101 # Preserve the query string
102 102 url = self._url
103 103 url = urlparse.urljoin(url, self._repo_name)
104 104 if environ.get('QUERY_STRING'):
105 105 url += '?' + environ['QUERY_STRING']
106 106
107 107 response = session.request(
108 108 method, url,
109 109 data=data,
110 110 headers=request_headers,
111 111 stream=True)
112 112
113 113 # Preserve the headers of the response, except hop_by_hop ones
114 114 response_headers = [
115 115 (h, v) for h, v in response.headers.items()
116 116 if not wsgiref.util.is_hop_by_hop(h)
117 117 ]
118 118
119 119 # Add custom response header to indicate that this is a VCS response
120 120 # and which backend is used.
121 121 response_headers.append(('X-RhodeCode-Backend', self._backend))
122 122
123 123 # TODO: johbo: Better way to get the status including text?
124 124 status = str(response.status_code)
125 125 start_response(status, response_headers)
126 126 return _maybe_stream(response)
127 127
128 128
129 129 def _maybe_stream(response):
130 130 """
131 131 Try to generate chunks from the response if it is chunked.
132 132 """
133 133 if _is_chunked(response):
134 134 return response.raw.read_chunked()
135 135 else:
136 136 return [response.content]
137 137
138 138
139 139 def _is_chunked(response):
140 140 return response.headers.get('Transfer-Encoding', '') == 'chunked'
@@ -1,98 +1,104 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Utility to call a WSGI app wrapped in a WSGIAppCaller object.
23 23 """
24 24
25 25 import logging
26 26
27 27 from Pyro4.errors import ConnectionClosedError
28 28
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 def _get_clean_environ(environ):
34 34 """Return a copy of the WSGI environment without wsgi.* keys.
35 35
36 36 It also omits any non-string values.
37 37
38 38 :param environ: WSGI environment to clean
39 39 :type environ: dict
40 40
41 41 :returns: WSGI environment to pass to WSGIAppCaller.handle.
42 42 :rtype: dict
43 43 """
44 44 clean_environ = dict(
45 45 (k, v) for k, v in environ.iteritems()
46 46 if type(v) == str and type(k) == str and not k.startswith('wsgi.')
47 47 )
48 48
49 49 return clean_environ
50 50
51 51
52 52 # pylint: disable=too-few-public-methods
53 53 class RemoteAppCaller(object):
54 54 """Create and calls a remote WSGI app using the given factory.
55 55
56 56 It first cleans the environment, so as to reduce the data transferred.
57 57 """
58 58
59 def __init__(self, remote_wsgi, *args, **kwargs):
59 def __init__(self, remote_wsgi, backend, *args, **kwargs):
60 60 """
61 61 :param remote_wsgi: The remote wsgi object that creates a
62 62 WSGIAppCaller. This object
63 63 has to have a handle method, with the signature:
64 64 handle(environ, start_response, *args, **kwargs)
65 :param backend: Key (str) of the SCM backend that is in use.
65 66 :param args: args to be passed to the app creation
66 67 :param kwargs: kwargs to be passed to the app creation
67 68 """
68 69 self._remote_wsgi = remote_wsgi
70 self._backend = backend
69 71 self._args = args
70 72 self._kwargs = kwargs
71 73
72 74 def __call__(self, environ, start_response):
73 75 """
74 76 :param environ: WSGI environment with which the app will be run
75 77 :type environ: dict
76 78 :param start_response: callable of WSGI protocol
77 79 :type start_response: callable
78 80
79 81 :returns: an iterable with the data returned by the app
80 82 :rtype: iterable<str>
81 83 """
82 84 log.debug("Forwarding WSGI request via proxy %s", self._remote_wsgi)
83 85 input_data = environ['wsgi.input'].read()
84 86 clean_environ = _get_clean_environ(environ)
85 87
86 88 try:
87 89 data, status, headers = self._remote_wsgi.handle(
88 90 clean_environ, input_data, *self._args, **self._kwargs)
89 91 except ConnectionClosedError:
90 92 log.debug('Remote Pyro Server ConnectionClosedError')
91 93 self._remote_wsgi._pyroReconnect(tries=15)
92 94 data, status, headers = self._remote_wsgi.handle(
93 95 clean_environ, input_data, *self._args, **self._kwargs)
94 96
97 # Add custom response header to indicate that this is a VCS response
98 # and which backend is used.
99 headers.append(('X-RhodeCode-Backend', self._backend))
100
95 101 log.debug("Got result from proxy, returning to WSGI container")
96 102 start_response(status, headers)
97 103
98 104 return data
@@ -1,41 +1,41 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Provides a mock implementation of the scm_app module.
23 23
24 24 It resembles the same API as :mod:`rhodecode.lib.middleware.utils.scm_app`
25 25 for testing purposes.
26 26 """
27 27 import mock
28 28
29 29
30 def create_git_wsgi_app(repo_path, repo_name, config):
30 def create_git_wsgi_app(repo_path, repo_name, config, backend):
31 31 return mock_git_wsgi
32 32
33 33
34 def create_hg_wsgi_app(repo_path, repo_name, config):
34 def create_hg_wsgi_app(repo_path, repo_name, config, backend):
35 35 return mock_hg_wsgi
36 36
37 37
38 38 # Utilities to ease testing
39 39
40 40 mock_hg_wsgi = mock.Mock()
41 41 mock_git_wsgi = mock.Mock()
General Comments 0
You need to be logged in to leave comments. Login now