##// END OF EJS Templates
vcs middleware: Fix up TODO note...
johbo -
r785:52ca3d84 default
parent child Browse files
Show More
@@ -1,197 +1,198 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 import gzip
21 import gzip
22 import shutil
22 import shutil
23 import logging
23 import logging
24 import tempfile
24 import tempfile
25 import urlparse
25 import urlparse
26
26
27 from webob.exc import HTTPNotFound
27 from webob.exc import HTTPNotFound
28
28
29 import rhodecode
29 import rhodecode
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 from rhodecode.lib.middleware.simplehg import SimpleHg
32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 from rhodecode.model.settings import VcsSettingsModel
34 from rhodecode.model.settings import VcsSettingsModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 def is_git(environ):
39 def is_git(environ):
40 """
40 """
41 Returns True if requests should be handled by GIT wsgi middleware
41 Returns True if requests should be handled by GIT wsgi middleware
42 """
42 """
43 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
43 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
44 log.debug(
44 log.debug(
45 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
45 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
46 is_git_path is not None)
46 is_git_path is not None)
47
47
48 return is_git_path
48 return is_git_path
49
49
50
50
51 def is_hg(environ):
51 def is_hg(environ):
52 """
52 """
53 Returns True if requests target is mercurial server - header
53 Returns True if requests target is mercurial server - header
54 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
54 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
55 """
55 """
56 is_hg_path = False
56 is_hg_path = False
57
57
58 http_accept = environ.get('HTTP_ACCEPT')
58 http_accept = environ.get('HTTP_ACCEPT')
59
59
60 if http_accept and http_accept.startswith('application/mercurial'):
60 if http_accept and http_accept.startswith('application/mercurial'):
61 query = urlparse.parse_qs(environ['QUERY_STRING'])
61 query = urlparse.parse_qs(environ['QUERY_STRING'])
62 if 'cmd' in query:
62 if 'cmd' in query:
63 is_hg_path = True
63 is_hg_path = True
64
64
65 log.debug(
65 log.debug(
66 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
66 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
67 is_hg_path)
67 is_hg_path)
68
68
69 return is_hg_path
69 return is_hg_path
70
70
71
71
72 def is_svn(environ):
72 def is_svn(environ):
73 """
73 """
74 Returns True if requests target is Subversion server
74 Returns True if requests target is Subversion server
75 """
75 """
76 http_dav = environ.get('HTTP_DAV', '')
76 http_dav = environ.get('HTTP_DAV', '')
77 magic_path_segment = rhodecode.CONFIG.get(
77 magic_path_segment = rhodecode.CONFIG.get(
78 'rhodecode_subversion_magic_path', '/!svn')
78 'rhodecode_subversion_magic_path', '/!svn')
79 is_svn_path = (
79 is_svn_path = (
80 'subversion' in http_dav or
80 'subversion' in http_dav or
81 magic_path_segment in environ['PATH_INFO'])
81 magic_path_segment in environ['PATH_INFO'])
82 log.debug(
82 log.debug(
83 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
83 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
84 is_svn_path)
84 is_svn_path)
85
85
86 return is_svn_path
86 return is_svn_path
87
87
88
88
89 class GunzipMiddleware(object):
89 class GunzipMiddleware(object):
90 """
90 """
91 WSGI middleware that unzips gzip-encoded requests before
91 WSGI middleware that unzips gzip-encoded requests before
92 passing on to the underlying application.
92 passing on to the underlying application.
93 """
93 """
94
94
95 def __init__(self, application):
95 def __init__(self, application):
96 self.app = application
96 self.app = application
97
97
98 def __call__(self, environ, start_response):
98 def __call__(self, environ, start_response):
99 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
99 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
100
100
101 if b'gzip' in accepts_encoding_header:
101 if b'gzip' in accepts_encoding_header:
102 log.debug('gzip detected, now running gunzip wrapper')
102 log.debug('gzip detected, now running gunzip wrapper')
103 wsgi_input = environ['wsgi.input']
103 wsgi_input = environ['wsgi.input']
104
104
105 if not hasattr(environ['wsgi.input'], 'seek'):
105 if not hasattr(environ['wsgi.input'], 'seek'):
106 # The gzip implementation in the standard library of Python 2.x
106 # The gzip implementation in the standard library of Python 2.x
107 # requires the '.seek()' and '.tell()' methods to be available
107 # requires the '.seek()' and '.tell()' methods to be available
108 # on the input stream. Read the data into a temporary file to
108 # on the input stream. Read the data into a temporary file to
109 # work around this limitation.
109 # work around this limitation.
110
110
111 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
111 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
112 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
112 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
113 wsgi_input.seek(0)
113 wsgi_input.seek(0)
114
114
115 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
115 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
116 # since we "Ungzipped" the content we say now it's no longer gzip
116 # since we "Ungzipped" the content we say now it's no longer gzip
117 # content encoding
117 # content encoding
118 del environ['HTTP_CONTENT_ENCODING']
118 del environ['HTTP_CONTENT_ENCODING']
119
119
120 # content length has changes ? or i'm not sure
120 # content length has changes ? or i'm not sure
121 if 'CONTENT_LENGTH' in environ:
121 if 'CONTENT_LENGTH' in environ:
122 del environ['CONTENT_LENGTH']
122 del environ['CONTENT_LENGTH']
123 else:
123 else:
124 log.debug('content not gzipped, gzipMiddleware passing '
124 log.debug('content not gzipped, gzipMiddleware passing '
125 'request further')
125 'request further')
126 return self.app(environ, start_response)
126 return self.app(environ, start_response)
127
127
128
128
129 class VCSMiddleware(object):
129 class VCSMiddleware(object):
130
130
131 def __init__(self, app, config, appenlight_client, registry):
131 def __init__(self, app, config, appenlight_client, registry):
132 self.application = app
132 self.application = app
133 self.config = config
133 self.config = config
134 self.appenlight_client = appenlight_client
134 self.appenlight_client = appenlight_client
135 self.registry = registry
135 self.registry = registry
136 self.use_gzip = True
136 self.use_gzip = True
137 # order in which we check the middlewares, based on vcs.backends config
137 # order in which we check the middlewares, based on vcs.backends config
138 self.check_middlewares = config['vcs.backends']
138 self.check_middlewares = config['vcs.backends']
139 self.checks = {
139 self.checks = {
140 'hg': (is_hg, SimpleHg),
140 'hg': (is_hg, SimpleHg),
141 'git': (is_git, SimpleGit),
141 'git': (is_git, SimpleGit),
142 'svn': (is_svn, SimpleSvn),
142 'svn': (is_svn, SimpleSvn),
143 }
143 }
144
144
145 def vcs_config(self, repo_name=None):
145 def vcs_config(self, repo_name=None):
146 """
146 """
147 returns serialized VcsSettings
147 returns serialized VcsSettings
148 """
148 """
149 return VcsSettingsModel(repo=repo_name).get_ui_settings_as_config_obj()
149 return VcsSettingsModel(repo=repo_name).get_ui_settings_as_config_obj()
150
150
151 def wrap_in_gzip_if_enabled(self, app, config):
151 def wrap_in_gzip_if_enabled(self, app, config):
152 if self.use_gzip:
152 if self.use_gzip:
153 app = GunzipMiddleware(app)
153 app = GunzipMiddleware(app)
154 return app
154 return app
155
155
156 def _get_handler_app(self, environ):
156 def _get_handler_app(self, environ):
157 app = None
157 app = None
158 log.debug('Checking vcs types in order: %r', self.check_middlewares)
158 log.debug('Checking vcs types in order: %r', self.check_middlewares)
159 for vcs_type in self.check_middlewares:
159 for vcs_type in self.check_middlewares:
160 vcs_check, handler = self.checks[vcs_type]
160 vcs_check, handler = self.checks[vcs_type]
161 if vcs_check(environ):
161 if vcs_check(environ):
162 log.debug(
162 log.debug(
163 'Found VCS Middleware to handle the request %s', handler)
163 'Found VCS Middleware to handle the request %s', handler)
164 app = handler(self.application, self.config, self.registry)
164 app = handler(self.application, self.config, self.registry)
165 break
165 break
166
166
167 return app
167 return app
168
168
169 def __call__(self, environ, start_response):
169 def __call__(self, environ, start_response):
170 # check if we handle one of interesting protocols, optionally extract
170 # check if we handle one of interesting protocols, optionally extract
171 # specific vcsSettings and allow changes of how things are wrapped
171 # specific vcsSettings and allow changes of how things are wrapped
172 vcs_handler = self._get_handler_app(environ)
172 vcs_handler = self._get_handler_app(environ)
173 if vcs_handler:
173 if vcs_handler:
174 # translate the _REPO_ID into real repo NAME for usage
174 # translate the _REPO_ID into real repo NAME for usage
175 # in middleware
175 # in middleware
176 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
176 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
177 repo_name = vcs_handler._get_repository_name(environ)
177 repo_name = vcs_handler._get_repository_name(environ)
178
178
179 # check for type, presence in database and on filesystem
179 # check for type, presence in database and on filesystem
180 if not vcs_handler.is_valid_and_existing_repo(
180 if not vcs_handler.is_valid_and_existing_repo(
181 repo_name, vcs_handler.basepath, vcs_handler.SCM):
181 repo_name, vcs_handler.basepath, vcs_handler.SCM):
182 return HTTPNotFound()(environ, start_response)
182 return HTTPNotFound()(environ, start_response)
183
183
184 # TODO(marcink): this is probably not needed anymore
184 # TODO: johbo: Needed for the Pyro4 backend and Mercurial only.
185 # Remove once we fully switched to the HTTP backend.
185 environ['REPO_NAME'] = repo_name
186 environ['REPO_NAME'] = repo_name
186
187
187 # register repo_name and it's config back to the handler
188 # register repo_name and it's config back to the handler
188 vcs_handler.repo_name = repo_name
189 vcs_handler.repo_name = repo_name
189 vcs_handler.repo_vcs_config = self.vcs_config(repo_name)
190 vcs_handler.repo_vcs_config = self.vcs_config(repo_name)
190
191
191 vcs_handler = self.wrap_in_gzip_if_enabled(
192 vcs_handler = self.wrap_in_gzip_if_enabled(
192 vcs_handler, self.config)
193 vcs_handler, self.config)
193 vcs_handler, _ = wrap_in_appenlight_if_enabled(
194 vcs_handler, _ = wrap_in_appenlight_if_enabled(
194 vcs_handler, self.config, self.appenlight_client)
195 vcs_handler, self.config, self.appenlight_client)
195 return vcs_handler(environ, start_response)
196 return vcs_handler(environ, start_response)
196
197
197 return self.application(environ, start_response)
198 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now