/* * Copyright 2005,2006 WSO2, Inc. http://wso2.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * */ package org.apache.synapse.commons.throttle.module.handler; import org.apache.axis2.AxisFault; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.AxisOperation; import org.apache.axis2.description.AxisService; import org.apache.axis2.handlers.AbstractHandler; import org.apache.axis2.transport.http.HTTPConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.commons.throttle.core.AccessRateController; import org.apache.synapse.commons.throttle.core.ConcurrentAccessController; import org.apache.synapse.commons.throttle.core.RoleBasedAccessRateController; import org.apache.synapse.commons.throttle.core.Throttle; import org.apache.synapse.commons.throttle.core.ThrottleConstants; import org.apache.synapse.commons.throttle.core.ThrottleConfiguration; import org.apache.synapse.commons.throttle.core.ThrottleException; import org.apache.synapse.commons.throttle.core.ThrottleContext; import org.apache.synapse.commons.throttle.core.AccessInformation; import org.apache.synapse.commons.throttle.module.utils.impl.DummyHandler; import org.apache.synapse.commons.throttle.module.utils.impl.DummyAuthenticator; import org.apache.synapse.commons.throttle.module.utils.StatCollector; import javax.cache.Cache; import javax.cache.CacheManager; import javax.cache.Caching; import javax.servlet.http.HttpServletRequest; import javax.xml.namespace.QName; import java.util.Map; public abstract class ThrottleHandler extends AbstractHandler { private static Log log = LogFactory.getLog(ThrottleHandler.class.getName()); /* The AccessRateController - control(limit) access for a remote caller */ private AccessRateController accessRateController; private RoleBasedAccessRateController roleBasedAccessController; public static final String THROTTLING_CACHE_MANAGER = "throttling.cache.manager"; public static final String THROTTLING_CACHE = "throttling.cache"; private boolean debugOn; public ThrottleHandler() { this.debugOn = log.isDebugEnabled(); this.accessRateController = new AccessRateController(); this.roleBasedAccessController = new RoleBasedAccessRateController(); } /** * @return int - indicates the type of the throttle according to the scope */ protected abstract int getThrottleType(); /** * Loads a throttle metadata for a particular throttle type * * @param messageContext - The messageContext * @param throttleType - The type of throttle * @return IPBaseThrottleConfiguration - The IPBaseThrottleConfiguration - load from AxisConfiguration * @throws ThrottleException Throws if the throttle type is unsupported */ public Throttle loadThrottle(MessageContext messageContext, int throttleType) throws ThrottleException { Throttle throttle = null; ConfigurationContext configContext = messageContext.getConfigurationContext(); //the Parameter which hold throttle ipbase object // to get throttles map from the configuration context Map throttles = (Map) configContext.getPropertyNonReplicable(ThrottleConstants.THROTTLES_MAP); if (throttles == null) { if (debugOn) { log.debug("Couldn't find throttles object map .. thottlling will not be occurred "); } return null; } switch (throttleType) { case ThrottleConstants.GLOBAL_THROTTLE: { throttle = (Throttle) throttles.get(ThrottleConstants.GLOBAL_THROTTLE_KEY); break; } case ThrottleConstants.OPERATION_BASED_THROTTLE: { AxisOperation axisOperation = messageContext.getAxisOperation(); if (axisOperation != null) { QName opName = axisOperation.getName(); if (opName != null) { AxisService service = (AxisService) axisOperation.getParent(); if (service != null) { String currentServiceName = service.getName(); if (currentServiceName != null) { throttle = (Throttle) throttles.get(currentServiceName + opName.getLocalPart()); } } } } else { if (debugOn) { log.debug("Couldn't find axis operation "); } return null; } break; } case ThrottleConstants.SERVICE_BASED_THROTTLE: { AxisService axisService = messageContext.getAxisService(); if (axisService != null) { throttle = (Throttle) throttles.get(axisService.getName()); } else { if (debugOn) { log.debug("Couldn't find axis service "); } return null; } break; } default: { throw new ThrottleException("Unsupported Throttle type"); } } return throttle; } /** * processing through the throttle * 1) concurrent throttling * 2) access rate based throttling - domain or ip * * @param throttle The Throttle object - holds all configuration and state data * of the throttle * @param messageContext The MessageContext , that holds all data per message basis * @throws AxisFault Throws when access must deny for caller * @throws ThrottleException ThrottleException */ public void process(Throttle throttle, MessageContext messageContext) throws ThrottleException, AxisFault { String throttleId = throttle.getId(); ConfigurationContext cc = messageContext.getConfigurationContext(); // acquiring cache manager. Cache<String, ConcurrentAccessController> cache; CacheManager cacheManager = Caching.getCacheManagerFactory().getCacheManager(THROTTLING_CACHE_MANAGER); if (cacheManager != null) { cache = cacheManager.getCache(THROTTLING_CACHE); } else { cache = Caching.getCacheManager().getCache(THROTTLING_CACHE); } if (log.isDebugEnabled()) { log.debug("created throttling cache : " + cache); } // Get the concurrent access controller ConcurrentAccessController cac; String key = null; key = ThrottleConstants.THROTTLE_PROPERTY_PREFIX + throttleId + ThrottleConstants.CAC_SUFFIX; cac = cache.get(key); // check for concurrent access boolean canAccess = doConcurrentThrottling(cac, messageContext); if (canAccess) { // if the concurrent access is success then // do the access rate based throttling if (messageContext.getFLOW() == MessageContext.IN_FLOW) { //gets the remote caller domain name String domain = null; HttpServletRequest request = (HttpServletRequest) messageContext.getPropertyNonReplicable( HTTPConstants.MC_HTTP_SERVLETREQUEST); if (request != null) { domain = request.getRemoteHost(); } // Domain name based throttling //check whether a configuration has been defined for this domain name or not String callerId = null; if (domain != null) { //loads the ThrottleContext ThrottleContext throttleCtxt = throttle.getThrottleContext(ThrottleConstants.DOMAIN_BASED_THROTTLE_KEY); if (throttleCtxt != null) { //Loads the ThrottleConfiguration ThrottleConfiguration throttleConfig = throttleCtxt.getThrottleConfiguration(); if (throttleConfig != null) { //check for configuration for this caller callerId = throttleConfig.getConfigurationKeyOfCaller(domain); if (callerId != null) { // If this is a clustered env. throttleCtxt.setThrottleId(throttleId); AccessInformation infor = accessRateController.canAccess(throttleCtxt, callerId, ThrottleConstants.DOMAIN_BASE); StatCollector.collect(infor, domain, ThrottleConstants.DOMAIN_BASE); //check for the permission for access if (!infor.isAccessAllowed()) { //In the case of both of concurrency throttling and //rate based throttling have enabled , //if the access rate less than maximum concurrent access , //then it is possible to occur death situation.To avoid that reset, //if the access has denied by rate based throttling if (cac != null) { cac.incrementAndGet(); cache.put(key, cac); if (debugOn) { log.debug("Added the state of ConcurrentAccessController " + "to cache with key : " + key); } } throw new AxisFault(" Access deny for a " + "caller with Domain " + domain + " " + " : Reason : " + infor.getFaultReason()); } } else { if (debugOn) { log.debug("Could not find the Throttle Context for domain-Based " + "Throttling for domain name " + domain + " Throttling for this " + "domain name may not be configured from policy"); } } } } } else { if (debugOn) { log.debug("Could not find the domain of the caller - IP-based throttling may occur"); } } //IP based throttling - Only if there is no configuration for caller domain name if (callerId == null) { String ip = (String) messageContext.getProperty(MessageContext.REMOTE_ADDR); if (ip != null) { // loads IP based throttle context ThrottleContext context = throttle.getThrottleContext(ThrottleConstants.IP_BASED_THROTTLE_KEY); if (context != null) { //Loads the ThrottleConfiguration ThrottleConfiguration config = context.getThrottleConfiguration(); if (config != null) { // check for configuration for this ip callerId = config.getConfigurationKeyOfCaller(ip); if (callerId != null) { context.setThrottleId(throttleId); AccessInformation infor = accessRateController.canAccess(context, callerId, ThrottleConstants.IP_BASE); // check for the permission for access StatCollector.collect(infor,ip,ThrottleConstants.IP_BASE); if (!infor.isAccessAllowed()) { //In the case of both of concurrency throttling and //rate based throttling have enabled , //if the access rate less than maximum concurrent access , //then it is possible to occur death situation.To avoid that reset, //if the access has denied by rate based throttling if (cac != null) { cac.incrementAndGet(); // set back if this is a clustered env cache.put(key, cac); if(debugOn) { log.debug("Added the state of ConcurrentAccessController " + "to cache with key : " + key); } } throw new AxisFault(" Access deny for a " + "caller with IP " + ip + " " + " : Reason : " + infor.getFaultReason()); } } } } else { if (debugOn) { log.debug("Could not find the throttle Context for IP-Based throttling"); } } } else { if (debugOn) { log.debug("Could not find the IP address of the caller " + "- throttling will not occur"); } } } } // all the replication functionality of the access rate based throttling handles by itself // just replicate the current state of ConcurrentAccessController if (cac != null) { cache.put(key, cac); if(debugOn) { log.debug("Added the state of ConcurrentAccessController " + "to cache with key : " + key); } } //finally engage rolebased access throttling if available doRoleBasedAccessThrottling(throttle, messageContext); } else { //replicate the current state of ConcurrentAccessController if (cac != null) { cache.put(key, cac); if(debugOn) { log.debug("Added the state of ConcurrentAccessController " + "to cache with key : " + key); } } throw new AxisFault("Access has currently been denied since " + " maximum concurrent access have exceeded"); } } /** * Helper method for handling concurrent throttling * * @param concurrentAccessController ConcurrentAccessController * @param messageContext MessageContext - message level states * @return true if access is allowed through concurrent throttling ,o.w false */ private boolean doConcurrentThrottling(ConcurrentAccessController concurrentAccessController, MessageContext messageContext) { boolean canAccess = true; int available; if (concurrentAccessController != null) { if (messageContext.getFLOW() == MessageContext.IN_FLOW) { available = concurrentAccessController.getAndDecrement(); canAccess = available > 0; if (debugOn) { log.debug("Concurrency Throttle : Access " + (canAccess ? "allowed" : "denied") + " :: " + available + " of available of " + concurrentAccessController.getLimit() + " connections"); } if (debugOn) { if (!canAccess) { log.debug("Concurrency Throttle : Access has currently been denied since allowed" + " maximum concurrent access have exceeded"); } } } else if (messageContext.getFLOW() == MessageContext.OUT_FLOW) { available = concurrentAccessController.incrementAndGet(); if (debugOn) { log.debug("Concurrency Throttle : Connection returned" + " :: " + available + " of available of " + concurrentAccessController.getLimit() + " connections"); } } } return canAccess; } /** * Helper method for handling role based Access throttling * * @param messageContext MessageContext - message level states * @return true if access is allowed through concurrent throttling ,o.w false */ private boolean doRoleBasedAccessThrottling(Throttle throttle, MessageContext messageContext) throws AxisFault,ThrottleException { boolean canAccess = true; if (throttle.getThrottleContext(ThrottleConstants.ROLE_BASED_THROTTLE_KEY) == null) { //if no role base throttle config return immediately return canAccess; } ConfigurationContext cc = messageContext.getConfigurationContext(); String throttleId = throttle.getId(); // acquiring cache manager. Cache<String, ConcurrentAccessController> cache; CacheManager cacheManager = Caching.getCacheManagerFactory().getCacheManager(THROTTLING_CACHE_MANAGER); if (cacheManager != null) { cache = cacheManager.getCache(THROTTLING_CACHE); } else { cache = Caching.getCacheManager().getCache(THROTTLING_CACHE); } if (log.isDebugEnabled()) { log.debug("created throttling cache : " + cache); } String key = null; ConcurrentAccessController cac = null; key = ThrottleConstants.THROTTLE_PROPERTY_PREFIX + throttleId + ThrottleConstants.CAC_SUFFIX; cac = cache.get(key); if (messageContext.getFLOW() == MessageContext.IN_FLOW) { //gets the remote caller role name String consumerKey = null; boolean isAuthenticated = false; String roleID = null; HttpServletRequest request = (HttpServletRequest) messageContext.getPropertyNonReplicable( HTTPConstants.MC_HTTP_SERVLETREQUEST); if (request != null) { String oAuthHeader = request.getHeader("OAuth"); // consumerKey = Utils.extractCustomerKeyFromAuthHeader(oAuthHeader); // roleID = Utils.extractCustomerKeyFromAuthHeader(oAuthHeader); DummyAuthenticator authFuture = new DummyAuthenticator(oAuthHeader); consumerKey = authFuture.getAPIKey(); new DummyHandler().authenticateUser(authFuture); roleID = (String) authFuture.getAuthorizedRoles().get(0); isAuthenticated = authFuture.isAuthenticated(); } if(!isAuthenticated){ throw new AxisFault(" Access deny for a " + "caller with consumer Key: " + consumerKey + " " + " : Reason : Authentication failure"); } // Domain name based throttling //check whether a configuration has been defined for this role name or not String consumerRoleID = null; if (consumerKey != null && isAuthenticated) { //loads the ThrottleContext ThrottleContext context = throttle.getThrottleContext(ThrottleConstants.ROLE_BASED_THROTTLE_KEY); if (context != null) { //Loads the ThrottleConfiguration ThrottleConfiguration config = context.getThrottleConfiguration(); if (config != null) { //check for configuration for this caller consumerRoleID = config.getConfigurationKeyOfCaller(roleID); if (consumerRoleID != null) { context.setThrottleId(throttleId); AccessInformation infor = roleBasedAccessController.canAccess(context, consumerKey, consumerRoleID); StatCollector.collect(infor, consumerKey, ThrottleConstants.ROLE_BASE); //check for the permission for access if (!infor.isAccessAllowed()) { //In the case of both of concurrency throttling and //rate based throttling have enabled , //if the access rate less than maximum concurrent access , //then it is possible to occur death situation.To avoid that reset, //if the access has denied by rate based throttling if (cac != null) { cac.incrementAndGet(); cache.put(key, cac); if(debugOn) { log.debug("Added the state of ConcurrentAccessController " + "to cache with key : " + key); } } throw new AxisFault(" Access deny for a " + "caller with Domain " + consumerKey + " " + " : Reason : " + infor.getFaultReason()); } } else { if (debugOn) { log.debug("Could not find the Throttle Context for role-Based " + "Throttling for role name " + consumerKey + " Throttling for this " + "role name may not be configured from policy"); } } } } } else { if (debugOn) { log.debug("Could not find the role of the caller - role based throttling NOT applied"); } } } return canAccess; } public InvocationResponse invoke(MessageContext msgContext) throws AxisFault { //Load throttle try { Throttle throttle = loadThrottle(msgContext, getThrottleType()); if (throttle != null) { process(throttle, msgContext); } } catch (ThrottleException e) { log.error(e.getMessage()); throw new AxisFault(e.getMessage()); } return InvocationResponse.CONTINUE; } }