##// END OF EJS Templates
moved time measure of request to separate middleware for better results (the last one in stack)
marcink -
r3489:d997a314 beta
parent child Browse files
Show More
@@ -0,0 +1,47 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.middleware.wrapper
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 request time mesuring app
7
8 :created_on: May 23, 2013
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import time
26 import logging
27 from rhodecode.lib.base import _get_ip_addr, _get_access_path
28 from rhodecode.lib.utils2 import safe_unicode
29
30
31 class RequestWrapper(object):
32
33 def __init__(self, app, config):
34 self.application = app
35 self.config = config
36
37 def __call__(self, environ, start_response):
38 start = time.time()
39 try:
40 return self.application(environ, start_response)
41 finally:
42 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
43 log.info('IP: %s Request to %s time: %.3fs' % (
44 _get_ip_addr(environ),
45 safe_unicode(_get_access_path(environ)), time.time() - start)
46 )
47
@@ -1,92 +1,93 b''
1 1 """Pylons middleware initialization"""
2 2
3 3 from beaker.middleware import SessionMiddleware
4 4 from routes.middleware import RoutesMiddleware
5 5 from paste.cascade import Cascade
6 6 from paste.registry import RegistryManager
7 7 from paste.urlparser import StaticURLParser
8 8 from paste.deploy.converters import asbool
9 9 from paste.gzipper import make_gzip_middleware
10 10
11 11 from pylons.middleware import ErrorHandler, StatusCodeRedirect
12 12 from pylons.wsgiapp import PylonsApp
13 13
14 14 from rhodecode.lib.middleware.simplehg import SimpleHg
15 15 from rhodecode.lib.middleware.simplegit import SimpleGit
16 16 from rhodecode.lib.middleware.https_fixup import HttpsFixup
17 17 from rhodecode.config.environment import load_environment
18 from rhodecode.lib.middleware.wrapper import RequestWrapper
18 19
19 20
20 21 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
21 22 """Create a Pylons WSGI application and return it
22 23
23 24 ``global_conf``
24 25 The inherited configuration for this application. Normally from
25 26 the [DEFAULT] section of the Paste ini file.
26 27
27 28 ``full_stack``
28 29 Whether or not this application provides a full WSGI stack (by
29 30 default, meaning it handles its own exceptions and errors).
30 31 Disable full_stack when this application is "managed" by
31 32 another WSGI middleware.
32 33
33 34 ``app_conf``
34 35 The application's local configuration. Normally specified in
35 36 the [app:<name>] section of the Paste ini file (where <name>
36 37 defaults to main).
37 38
38 39 """
39 40 # Configure the Pylons environment
40 41 config = load_environment(global_conf, app_conf)
41 42
42 43 # The Pylons WSGI app
43 44 app = PylonsApp(config=config)
44 45
45 46 # Routing/Session/Cache Middleware
46 47 app = RoutesMiddleware(app, config['routes.map'])
47 48 app = SessionMiddleware(app, config)
48 49
49 50 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
50 51 if asbool(config['pdebug']):
51 52 from rhodecode.lib.profiler import ProfilingMiddleware
52 53 app = ProfilingMiddleware(app)
53 54
54 55 if asbool(full_stack):
55 56
56 57 from rhodecode.lib.middleware.sentry import Sentry
57 58 from rhodecode.lib.middleware.errormator import Errormator
58 59 if Errormator:
59 60 app = Errormator(app, config)
60 61 elif Sentry:
61 62 app = Sentry(app, config)
62 63
63 64 # Handle Python exceptions
64 65 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
65 66
66 67 # we want our low level middleware to get to the request ASAP. We don't
67 68 # need any pylons stack middleware in them
68 69 app = SimpleHg(app, config)
69 70 app = SimpleGit(app, config)
70
71 app = RequestWrapper(app, config)
71 72 # Display error documents for 401, 403, 404 status codes (and
72 73 # 500 when debug is disabled)
73 74 if asbool(config['debug']):
74 75 app = StatusCodeRedirect(app)
75 76 else:
76 77 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
77 78
78 79 #enable https redirets based on HTTP_X_URL_SCHEME set by proxy
79 80 app = HttpsFixup(app, config)
80 81
81 82 # Establish the Registry for this application
82 83 app = RegistryManager(app)
83 84
84 85 if asbool(static_files):
85 86 # Serve static files
86 87 static_app = StaticURLParser(config['pylons.paths']['static_files'])
87 88 app = Cascade([static_app, app])
88 89 app = make_gzip_middleware(app, global_conf, compress_level=1)
89 90
90 91 app.config = config
91 92
92 93 return app
@@ -1,338 +1,333 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12 12
13 13 from pylons import config, tmpl_context as c, request, session, url
14 14 from pylons.controllers import WSGIController
15 15 from pylons.controllers.util import redirect
16 16 from pylons.templating import render_mako as render
17 17
18 18 from rhodecode import __version__, BACKENDS
19 19
20 20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 21 safe_str, safe_int
22 22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 25 from rhodecode.model import meta
26 26
27 27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
28 28 from rhodecode.model.notification import NotificationModel
29 29 from rhodecode.model.scm import ScmModel
30 30 from rhodecode.model.meta import Session
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 def _get_ip_addr(environ):
36 36 proxy_key = 'HTTP_X_REAL_IP'
37 37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
38 38 def_key = 'REMOTE_ADDR'
39 39
40 40 ip = environ.get(proxy_key)
41 41 if ip:
42 42 return ip
43 43
44 44 ip = environ.get(proxy_key2)
45 45 if ip:
46 46 # HTTP_X_FORWARDED_FOR can have mutliple ips inside
47 47 # the left-most being the original client, and each successive proxy
48 48 # that passed the request adding the IP address where it received the
49 49 # request from.
50 50 if ',' in ip:
51 51 ip = ip.split(',')[0].strip()
52 52 return ip
53 53
54 54 ip = environ.get(def_key, '0.0.0.0')
55 55 return ip
56 56
57 57
58 58 def _get_access_path(environ):
59 59 path = environ.get('PATH_INFO')
60 60 org_req = environ.get('pylons.original_request')
61 61 if org_req:
62 62 path = org_req.environ.get('PATH_INFO')
63 63 return path
64 64
65 65
66 66 class BasicAuth(AuthBasicAuthenticator):
67 67
68 68 def __init__(self, realm, authfunc, auth_http_code=None):
69 69 self.realm = realm
70 70 self.authfunc = authfunc
71 71 self._rc_auth_http_code = auth_http_code
72 72
73 73 def build_authentication(self):
74 74 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
75 75 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
76 76 # return 403 if alternative http return code is specified in
77 77 # RhodeCode config
78 78 return HTTPForbidden(headers=head)
79 79 return HTTPUnauthorized(headers=head)
80 80
81 81 def authenticate(self, environ):
82 82 authorization = AUTHORIZATION(environ)
83 83 if not authorization:
84 84 return self.build_authentication()
85 85 (authmeth, auth) = authorization.split(' ', 1)
86 86 if 'basic' != authmeth.lower():
87 87 return self.build_authentication()
88 88 auth = auth.strip().decode('base64')
89 89 _parts = auth.split(':', 1)
90 90 if len(_parts) == 2:
91 91 username, password = _parts
92 92 if self.authfunc(environ, username, password):
93 93 return username
94 94 return self.build_authentication()
95 95
96 96 __call__ = authenticate
97 97
98 98
99 99 class BaseVCSController(object):
100 100
101 101 def __init__(self, application, config):
102 102 self.application = application
103 103 self.config = config
104 104 # base path of repo locations
105 105 self.basepath = self.config['base_path']
106 106 #authenticate this mercurial request using authfunc
107 107 self.authenticate = BasicAuth('', authfunc,
108 108 config.get('auth_ret_code'))
109 109 self.ip_addr = '0.0.0.0'
110 110
111 111 def _handle_request(self, environ, start_response):
112 112 raise NotImplementedError()
113 113
114 114 def _get_by_id(self, repo_name):
115 115 """
116 116 Get's a special pattern _<ID> from clone url and tries to replace it
117 117 with a repository_name for support of _<ID> non changable urls
118 118
119 119 :param repo_name:
120 120 """
121 121 try:
122 122 data = repo_name.split('/')
123 123 if len(data) >= 2:
124 124 by_id = data[1].split('_')
125 125 if len(by_id) == 2 and by_id[1].isdigit():
126 126 _repo_name = Repository.get(by_id[1]).repo_name
127 127 data[1] = _repo_name
128 128 except:
129 129 log.debug('Failed to extract repo_name from id %s' % (
130 130 traceback.format_exc()
131 131 )
132 132 )
133 133
134 134 return '/'.join(data)
135 135
136 136 def _invalidate_cache(self, repo_name):
137 137 """
138 138 Set's cache for this repository for invalidation on next access
139 139
140 140 :param repo_name: full repo name, also a cache key
141 141 """
142 142 invalidate_cache('get_repo_cached_%s' % repo_name)
143 143
144 144 def _check_permission(self, action, user, repo_name, ip_addr=None):
145 145 """
146 146 Checks permissions using action (push/pull) user and repository
147 147 name
148 148
149 149 :param action: push or pull action
150 150 :param user: user instance
151 151 :param repo_name: repository name
152 152 """
153 153 #check IP
154 154 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
155 155 if not authuser.ip_allowed:
156 156 return False
157 157 else:
158 158 log.info('Access for IP:%s allowed' % (ip_addr))
159 159 if action == 'push':
160 160 if not HasPermissionAnyMiddleware('repository.write',
161 161 'repository.admin')(user,
162 162 repo_name):
163 163 return False
164 164
165 165 else:
166 166 #any other action need at least read permission
167 167 if not HasPermissionAnyMiddleware('repository.read',
168 168 'repository.write',
169 169 'repository.admin')(user,
170 170 repo_name):
171 171 return False
172 172
173 173 return True
174 174
175 175 def _get_ip_addr(self, environ):
176 176 return _get_ip_addr(environ)
177 177
178 178 def _check_ssl(self, environ, start_response):
179 179 """
180 180 Checks the SSL check flag and returns False if SSL is not present
181 181 and required True otherwise
182 182 """
183 183 org_proto = environ['wsgi._org_proto']
184 184 #check if we have SSL required ! if not it's a bad request !
185 185 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
186 186 if require_ssl and org_proto == 'http':
187 187 log.debug('proto is %s and SSL is required BAD REQUEST !'
188 188 % org_proto)
189 189 return False
190 190 return True
191 191
192 192 def _check_locking_state(self, environ, action, repo, user_id):
193 193 """
194 194 Checks locking on this repository, if locking is enabled and lock is
195 195 present returns a tuple of make_lock, locked, locked_by.
196 196 make_lock can have 3 states None (do nothing) True, make lock
197 197 False release lock, This value is later propagated to hooks, which
198 198 do the locking. Think about this as signals passed to hooks what to do.
199 199
200 200 """
201 201 locked = False # defines that locked error should be thrown to user
202 202 make_lock = None
203 203 repo = Repository.get_by_repo_name(repo)
204 204 user = User.get(user_id)
205 205
206 206 # this is kind of hacky, but due to how mercurial handles client-server
207 207 # server see all operation on changeset; bookmarks, phases and
208 208 # obsolescence marker in different transaction, we don't want to check
209 209 # locking on those
210 210 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
211 211 locked_by = repo.locked
212 212 if repo and repo.enable_locking and not obsolete_call:
213 213 if action == 'push':
214 214 #check if it's already locked !, if it is compare users
215 215 user_id, _date = repo.locked
216 216 if user.user_id == user_id:
217 217 log.debug('Got push from user %s, now unlocking' % (user))
218 218 # unlock if we have push from user who locked
219 219 make_lock = False
220 220 else:
221 221 # we're not the same user who locked, ban with 423 !
222 222 locked = True
223 223 if action == 'pull':
224 224 if repo.locked[0] and repo.locked[1]:
225 225 locked = True
226 226 else:
227 227 log.debug('Setting lock on repo %s by %s' % (repo, user))
228 228 make_lock = True
229 229
230 230 else:
231 231 log.debug('Repository %s do not have locking enabled' % (repo))
232 232 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
233 233 % (make_lock, locked, locked_by))
234 234 return make_lock, locked, locked_by
235 235
236 236 def __call__(self, environ, start_response):
237 237 start = time.time()
238 238 try:
239 239 return self._handle_request(environ, start_response)
240 240 finally:
241 241 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
242 242 log.debug('Request time: %.3fs' % (time.time() - start))
243 243 meta.Session.remove()
244 244
245 245
246 246 class BaseController(WSGIController):
247 247
248 248 def __before__(self):
249 249 """
250 250 __before__ is called before controller methods and after __call__
251 251 """
252 252 c.rhodecode_version = __version__
253 253 c.rhodecode_instanceid = config.get('instance_id')
254 254 c.rhodecode_name = config.get('rhodecode_title')
255 255 c.use_gravatar = str2bool(config.get('use_gravatar'))
256 256 c.ga_code = config.get('rhodecode_ga_code')
257 257 # Visual options
258 258 c.visual = AttributeDict({})
259 259 rc_config = RhodeCodeSetting.get_app_settings()
260 260
261 261 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
262 262 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
263 263 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
264 264 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
265 265 c.visual.lightweight_dashboard_items = safe_int(config.get('dashboard_items', 100))
266 266 c.visual.repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
267 267
268 268 c.repo_name = get_repo_slug(request)
269 269 c.backends = BACKENDS.keys()
270 270 c.unread_notifications = NotificationModel()\
271 271 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
272 272 self.cut_off_limit = int(config.get('cut_off_limit'))
273 273
274 274 self.sa = meta.Session
275 275 self.scm_model = ScmModel(self.sa)
276 276
277 277 def __call__(self, environ, start_response):
278 278 """Invoke the Controller"""
279 279 # WSGIController.__call__ dispatches to the Controller method
280 280 # the request is routed to. This routing information is
281 281 # available in environ['pylons.routes_dict']
282 start = time.time()
283 282 try:
284 283 self.ip_addr = _get_ip_addr(environ)
285 284 # make sure that we update permissions each time we call controller
286 285 api_key = request.GET.get('api_key')
287 286 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
288 287 user_id = cookie_store.get('user_id', None)
289 288 username = get_container_username(environ, config)
290 289 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
291 290 request.user = auth_user
292 291 self.rhodecode_user = c.rhodecode_user = auth_user
293 292 if not self.rhodecode_user.is_authenticated and \
294 293 self.rhodecode_user.user_id is not None:
295 294 self.rhodecode_user.set_authenticated(
296 295 cookie_store.get('is_authenticated')
297 296 )
298 297 log.info('IP: %s User: %s accessed %s' % (
299 298 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
300 299 )
301 300 return WSGIController.__call__(self, environ, start_response)
302 301 finally:
303 log.info('IP: %s Request to %s time: %.3fs' % (
304 _get_ip_addr(environ),
305 safe_unicode(_get_access_path(environ)), time.time() - start)
306 )
307 302 meta.Session.remove()
308 303
309 304
310 305 class BaseRepoController(BaseController):
311 306 """
312 307 Base class for controllers responsible for loading all needed data for
313 308 repository loaded items are
314 309
315 310 c.rhodecode_repo: instance of scm repository
316 311 c.rhodecode_db_repo: instance of db
317 312 c.repository_followers: number of followers
318 313 c.repository_forks: number of forks
319 314 """
320 315
321 316 def __before__(self):
322 317 super(BaseRepoController, self).__before__()
323 318 if c.repo_name:
324 319
325 320 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
326 321 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
327 322 # update last change according to VCS data
328 323 dbr.update_changeset_cache(dbr.get_changeset())
329 324 if c.rhodecode_repo is None:
330 325 log.error('%s this repository is present in database but it '
331 326 'cannot be created as an scm instance', c.repo_name)
332 327
333 328 redirect(url('home'))
334 329
335 330 # some globals counter for menu
336 331 c.repository_followers = self.scm_model.get_followers(dbr)
337 332 c.repository_forks = self.scm_model.get_forks(dbr)
338 333 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
General Comments 0
You need to be logged in to leave comments. Login now