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