##// END OF EJS Templates
svn-support: enable better logging of handled SVN commands.
marcink -
r1217:63dcab29 default
parent child Browse files
Show More
@@ -1,155 +1,157 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 logging
21 import logging
22 from urlparse import urljoin
22 from urlparse import urljoin
23
23
24 import requests
24 import requests
25 from webob.exc import HTTPNotAcceptable
25 from webob.exc import HTTPNotAcceptable
26
26
27 from rhodecode.lib.middleware import simplevcs
27 from rhodecode.lib.middleware import simplevcs
28 from rhodecode.lib.utils import is_valid_repo
28 from rhodecode.lib.utils import is_valid_repo
29 from rhodecode.lib.utils2 import str2bool
29 from rhodecode.lib.utils2 import str2bool
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class SimpleSvnApp(object):
34 class SimpleSvnApp(object):
35 IGNORED_HEADERS = [
35 IGNORED_HEADERS = [
36 'connection', 'keep-alive', 'content-encoding',
36 'connection', 'keep-alive', 'content-encoding',
37 'transfer-encoding', 'content-length']
37 'transfer-encoding', 'content-length']
38
38
39 def __init__(self, config):
39 def __init__(self, config):
40 self.config = config
40 self.config = config
41
41
42 def __call__(self, environ, start_response):
42 def __call__(self, environ, start_response):
43 request_headers = self._get_request_headers(environ)
43 request_headers = self._get_request_headers(environ)
44
44
45 data = environ['wsgi.input']
45 data = environ['wsgi.input']
46 # johbo: Avoid that we end up with sending the request in chunked
46 # johbo: Avoid that we end up with sending the request in chunked
47 # transfer encoding (mainly on Gunicorn). If we know the content
47 # transfer encoding (mainly on Gunicorn). If we know the content
48 # length, then we should transfer the payload in one request.
48 # length, then we should transfer the payload in one request.
49 if environ['REQUEST_METHOD'] == 'MKCOL' or 'CONTENT_LENGTH' in environ:
49 if environ['REQUEST_METHOD'] == 'MKCOL' or 'CONTENT_LENGTH' in environ:
50 data = data.read()
50 data = data.read()
51
51
52 log.debug('Calling: %s method via `%s`', environ['REQUEST_METHOD'],
53 self._get_url(environ['PATH_INFO']))
52 response = requests.request(
54 response = requests.request(
53 environ['REQUEST_METHOD'], self._get_url(environ['PATH_INFO']),
55 environ['REQUEST_METHOD'], self._get_url(environ['PATH_INFO']),
54 data=data, headers=request_headers)
56 data=data, headers=request_headers)
55
57
56 response_headers = self._get_response_headers(response.headers)
58 response_headers = self._get_response_headers(response.headers)
57 start_response(
59 start_response(
58 '{} {}'.format(response.status_code, response.reason),
60 '{} {}'.format(response.status_code, response.reason),
59 response_headers)
61 response_headers)
60 return response.iter_content(chunk_size=1024)
62 return response.iter_content(chunk_size=1024)
61
63
62 def _get_url(self, path):
64 def _get_url(self, path):
63 return urljoin(
65 return urljoin(
64 self.config.get('subversion_http_server_url', ''), path)
66 self.config.get('subversion_http_server_url', ''), path)
65
67
66 def _get_request_headers(self, environ):
68 def _get_request_headers(self, environ):
67 headers = {}
69 headers = {}
68
70
69 for key in environ:
71 for key in environ:
70 if not key.startswith('HTTP_'):
72 if not key.startswith('HTTP_'):
71 continue
73 continue
72 new_key = key.split('_')
74 new_key = key.split('_')
73 new_key = [k.capitalize() for k in new_key[1:]]
75 new_key = [k.capitalize() for k in new_key[1:]]
74 new_key = '-'.join(new_key)
76 new_key = '-'.join(new_key)
75 headers[new_key] = environ[key]
77 headers[new_key] = environ[key]
76
78
77 if 'CONTENT_TYPE' in environ:
79 if 'CONTENT_TYPE' in environ:
78 headers['Content-Type'] = environ['CONTENT_TYPE']
80 headers['Content-Type'] = environ['CONTENT_TYPE']
79
81
80 if 'CONTENT_LENGTH' in environ:
82 if 'CONTENT_LENGTH' in environ:
81 headers['Content-Length'] = environ['CONTENT_LENGTH']
83 headers['Content-Length'] = environ['CONTENT_LENGTH']
82
84
83 return headers
85 return headers
84
86
85 def _get_response_headers(self, headers):
87 def _get_response_headers(self, headers):
86 headers = [
88 headers = [
87 (h, headers[h])
89 (h, headers[h])
88 for h in headers
90 for h in headers
89 if h.lower() not in self.IGNORED_HEADERS
91 if h.lower() not in self.IGNORED_HEADERS
90 ]
92 ]
91
93
92 return headers
94 return headers
93
95
94
96
95 class DisabledSimpleSvnApp(object):
97 class DisabledSimpleSvnApp(object):
96 def __init__(self, config):
98 def __init__(self, config):
97 self.config = config
99 self.config = config
98
100
99 def __call__(self, environ, start_response):
101 def __call__(self, environ, start_response):
100 reason = 'Cannot handle SVN call because: SVN HTTP Proxy is not enabled'
102 reason = 'Cannot handle SVN call because: SVN HTTP Proxy is not enabled'
101 log.warning(reason)
103 log.warning(reason)
102 return HTTPNotAcceptable(reason)(environ, start_response)
104 return HTTPNotAcceptable(reason)(environ, start_response)
103
105
104
106
105 class SimpleSvn(simplevcs.SimpleVCS):
107 class SimpleSvn(simplevcs.SimpleVCS):
106
108
107 SCM = 'svn'
109 SCM = 'svn'
108 READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
110 READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
109 DEFAULT_HTTP_SERVER = 'http://localhost:8090'
111 DEFAULT_HTTP_SERVER = 'http://localhost:8090'
110
112
111 def _get_repository_name(self, environ):
113 def _get_repository_name(self, environ):
112 """
114 """
113 Gets repository name out of PATH_INFO header
115 Gets repository name out of PATH_INFO header
114
116
115 :param environ: environ where PATH_INFO is stored
117 :param environ: environ where PATH_INFO is stored
116 """
118 """
117 path = environ['PATH_INFO'].split('!')
119 path = environ['PATH_INFO'].split('!')
118 repo_name = path[0].strip('/')
120 repo_name = path[0].strip('/')
119
121
120 # SVN includes the whole path in it's requests, including
122 # SVN includes the whole path in it's requests, including
121 # subdirectories inside the repo. Therefore we have to search for
123 # subdirectories inside the repo. Therefore we have to search for
122 # the repo root directory.
124 # the repo root directory.
123 if not is_valid_repo(repo_name, self.basepath, self.SCM):
125 if not is_valid_repo(repo_name, self.basepath, self.SCM):
124 current_path = ''
126 current_path = ''
125 for component in repo_name.split('/'):
127 for component in repo_name.split('/'):
126 current_path += component
128 current_path += component
127 if is_valid_repo(current_path, self.basepath, self.SCM):
129 if is_valid_repo(current_path, self.basepath, self.SCM):
128 return current_path
130 return current_path
129 current_path += '/'
131 current_path += '/'
130
132
131 return repo_name
133 return repo_name
132
134
133 def _get_action(self, environ):
135 def _get_action(self, environ):
134 return (
136 return (
135 'pull'
137 'pull'
136 if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
138 if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
137 else 'push')
139 else 'push')
138
140
139 def _create_wsgi_app(self, repo_path, repo_name, config):
141 def _create_wsgi_app(self, repo_path, repo_name, config):
140 if self._is_svn_enabled():
142 if self._is_svn_enabled():
141 return SimpleSvnApp(config)
143 return SimpleSvnApp(config)
142 # we don't have http proxy enabled return dummy request handler
144 # we don't have http proxy enabled return dummy request handler
143 return DisabledSimpleSvnApp(config)
145 return DisabledSimpleSvnApp(config)
144
146
145 def _is_svn_enabled(self):
147 def _is_svn_enabled(self):
146 conf = self.repo_vcs_config
148 conf = self.repo_vcs_config
147 return str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
149 return str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
148
150
149 def _create_config(self, extras, repo_name):
151 def _create_config(self, extras, repo_name):
150 conf = self.repo_vcs_config
152 conf = self.repo_vcs_config
151 server_url = conf.get('vcs_svn_proxy', 'http_server_url')
153 server_url = conf.get('vcs_svn_proxy', 'http_server_url')
152 server_url = server_url or self.DEFAULT_HTTP_SERVER
154 server_url = server_url or self.DEFAULT_HTTP_SERVER
153
155
154 extras['subversion_http_server_url'] = server_url
156 extras['subversion_http_server_url'] = server_url
155 return extras
157 return extras
General Comments 0
You need to be logged in to leave comments. Login now