001/* 002 * Copyright 2016-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2016-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2016-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.listener; 037 038 039 040import java.util.List; 041 042import com.unboundid.ldap.protocol.AbandonRequestProtocolOp; 043import com.unboundid.ldap.protocol.AddRequestProtocolOp; 044import com.unboundid.ldap.protocol.BindRequestProtocolOp; 045import com.unboundid.ldap.protocol.CompareRequestProtocolOp; 046import com.unboundid.ldap.protocol.DeleteRequestProtocolOp; 047import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp; 048import com.unboundid.ldap.protocol.LDAPMessage; 049import com.unboundid.ldap.protocol.ModifyRequestProtocolOp; 050import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp; 051import com.unboundid.ldap.protocol.SearchRequestProtocolOp; 052import com.unboundid.ldap.sdk.Control; 053import com.unboundid.ldap.sdk.LDAPException; 054import com.unboundid.util.FixedRateBarrier; 055import com.unboundid.util.NotMutable; 056import com.unboundid.util.NotNull; 057import com.unboundid.util.Nullable; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.Validator; 061 062 063 064/** 065 * This class provides an implementation of an LDAP listener request handler 066 * that can be used to apply rate limiting to client requests. It uses one or 067 * more {@link FixedRateBarrier} instances to enforce the rate limiting, and 068 * provides the ability to control rate limiting on a per-operation-type basis. 069 */ 070@NotMutable() 071@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 072public final class RateLimiterRequestHandler 073 extends LDAPListenerRequestHandler 074{ 075 // The rate limiters that will be used for each type of operation. 076 @Nullable private final FixedRateBarrier abandonRateLimiter; 077 @Nullable private final FixedRateBarrier addRateLimiter; 078 @Nullable private final FixedRateBarrier bindRateLimiter; 079 @Nullable private final FixedRateBarrier compareRateLimiter; 080 @Nullable private final FixedRateBarrier deleteRateLimiter; 081 @Nullable private final FixedRateBarrier extendedRateLimiter; 082 @Nullable private final FixedRateBarrier modifyRateLimiter; 083 @Nullable private final FixedRateBarrier modifyDNRateLimiter; 084 @Nullable private final FixedRateBarrier searchRateLimiter; 085 086 // The downstream request handler that will be used to process the requests 087 // after any appropriate rate limiting has been performed. 088 @NotNull private final LDAPListenerRequestHandler downstreamRequestHandler; 089 090 091 092 /** 093 * Creates a new rate limiter request handler that will limit the rate of 094 * operations to the specified maximum number per second. The rate limiting 095 * will be enforced for all types of operations except abandon and unbind. 096 * No rate limiting will be enforced for abandon or unbind operations. 097 * 098 * @param downstreamRequestHandler The downstream request handler that will 099 * be used to actually process the requests 100 * after any appropriate rate limiting has 101 * been performed. It must not be 102 * {@code null}. 103 * @param maxPerSecond The maximum number of operations that 104 * will be allowed per second, across all 105 * types of operations except abandon and 106 * unbind. It must be greater than zero. 107 */ 108 public RateLimiterRequestHandler( 109 @NotNull final LDAPListenerRequestHandler downstreamRequestHandler, 110 final int maxPerSecond) 111 { 112 Validator.ensureNotNull(downstreamRequestHandler); 113 Validator.ensureTrue(maxPerSecond > 0); 114 115 this.downstreamRequestHandler = downstreamRequestHandler; 116 117 final FixedRateBarrier rateLimiter = 118 new FixedRateBarrier(1000L, maxPerSecond); 119 120 abandonRateLimiter = null; 121 addRateLimiter = rateLimiter; 122 bindRateLimiter = rateLimiter; 123 compareRateLimiter = rateLimiter; 124 deleteRateLimiter = rateLimiter; 125 extendedRateLimiter = rateLimiter; 126 modifyRateLimiter = rateLimiter; 127 modifyDNRateLimiter = rateLimiter; 128 searchRateLimiter = rateLimiter; 129 } 130 131 132 133 /** 134 * Creates a new rate limiter request handler that will use the provided 135 * {@link FixedRateBarrier} to perform rate limiting for all types of 136 * operations except abandon and unbind. No rate limiting will be enforced 137 * for abandon or unbind operations. 138 * 139 * @param downstreamRequestHandler The downstream request handler that will 140 * be used to actually process the requests 141 * after any appropriate rate limiting has 142 * been performed. It must not be 143 * {@code null}. 144 * @param rateLimiter The fixed-rate barrier that will be used 145 * to achieve the rate limiting for all 146 * types of operations except abandon and 147 * unbind. It may be {@code null} if no 148 * rate limiting should be performed for any 149 * operation types. 150 */ 151 public RateLimiterRequestHandler( 152 @NotNull final LDAPListenerRequestHandler downstreamRequestHandler, 153 @Nullable final FixedRateBarrier rateLimiter) 154 { 155 this(downstreamRequestHandler, null, rateLimiter, rateLimiter, rateLimiter, 156 rateLimiter, rateLimiter, rateLimiter, rateLimiter, rateLimiter); 157 } 158 159 160 161 /** 162 * Creates a new rate limiter request handler that can use the provided 163 * {@link FixedRateBarrier} instances to perform rate limiting for different 164 * types of operations. The same barrier instance can be provided for 165 * multiple operation types if performance for those operations should be 166 * limited in aggregate rather than individually (e.g., if you don't want the 167 * total combined rate of search and modify operations to exceed a given 168 * threshold, then you could provide the same barrier instance for the 169 * {@code modifyRateLimiter} and {@code searchRateLimiter} arguments). 170 * 171 * @param downstreamRequestHandler The downstream request handler that will 172 * be used to actually process the requests 173 * after any appropriate rate limiting has 174 * been performed. It must not be 175 * {@code null}. 176 * @param abandonRateLimiter The fixed-rate barrier to use when 177 * processing abandon operations. It may be 178 * {@code null} if no rate limiting should 179 * be enforced for abandon operations. 180 * @param addRateLimiter The fixed-rate barrier to use when 181 * processing add operations. It may be 182 * {@code null} if no rate limiting should 183 * be enforced for add operations. 184 * @param bindRateLimiter The fixed-rate barrier to use when 185 * processing bind operations. It may be 186 * {@code null} if no rate limiting should 187 * be enforced for bind operations. 188 * @param compareRateLimiter The fixed-rate barrier to use when 189 * processing compare operations. It may be 190 * {@code null} if no rate limiting should 191 * be enforced for compare operations. 192 * @param deleteRateLimiter The fixed-rate barrier to use when 193 * processing delete operations. It may be 194 * {@code null} if no rate limiting should 195 * be enforced for delete operations. 196 * @param extendedRateLimiter The fixed-rate barrier to use when 197 * processing extended operations. It may 198 * be {@code null} if no rate limiting 199 * should be enforced for extended 200 * operations. 201 * @param modifyRateLimiter The fixed-rate barrier to use when 202 * processing modify operations. It may be 203 * {@code null} if no rate limiting should 204 * be enforced for modify operations. 205 * @param modifyDNRateLimiter The fixed-rate barrier to use when 206 * processing modify DN operations. It may 207 * be {@code null} if no rate limiting 208 * should be enforced for modify DN 209 * operations. 210 * @param searchRateLimiter The fixed-rate barrier to use when 211 * processing search operations. It may be 212 * {@code null} if no rate limiting should 213 * be enforced for search operations. 214 */ 215 public RateLimiterRequestHandler( 216 @Nullable final LDAPListenerRequestHandler downstreamRequestHandler, 217 @Nullable final FixedRateBarrier abandonRateLimiter, 218 @Nullable final FixedRateBarrier addRateLimiter, 219 @Nullable final FixedRateBarrier bindRateLimiter, 220 @Nullable final FixedRateBarrier compareRateLimiter, 221 @Nullable final FixedRateBarrier deleteRateLimiter, 222 @Nullable final FixedRateBarrier extendedRateLimiter, 223 @Nullable final FixedRateBarrier modifyRateLimiter, 224 @Nullable final FixedRateBarrier modifyDNRateLimiter, 225 @Nullable final FixedRateBarrier searchRateLimiter) 226 { 227 Validator.ensureNotNull(downstreamRequestHandler); 228 229 this.downstreamRequestHandler = downstreamRequestHandler; 230 this.abandonRateLimiter = abandonRateLimiter; 231 this.addRateLimiter = addRateLimiter; 232 this.bindRateLimiter = bindRateLimiter; 233 this.compareRateLimiter = compareRateLimiter; 234 this.deleteRateLimiter = deleteRateLimiter; 235 this.extendedRateLimiter = extendedRateLimiter; 236 this.modifyRateLimiter = modifyRateLimiter; 237 this.modifyDNRateLimiter = modifyDNRateLimiter; 238 this.searchRateLimiter = searchRateLimiter; 239 } 240 241 242 243 /** 244 * {@inheritDoc} 245 */ 246 @Override() 247 @NotNull 248 public RateLimiterRequestHandler newInstance( 249 @NotNull final LDAPListenerClientConnection connection) 250 throws LDAPException 251 { 252 return new RateLimiterRequestHandler( 253 downstreamRequestHandler.newInstance(connection), abandonRateLimiter, 254 addRateLimiter, bindRateLimiter, compareRateLimiter, deleteRateLimiter, 255 extendedRateLimiter, modifyRateLimiter, modifyDNRateLimiter, 256 searchRateLimiter); 257 } 258 259 260 261 /** 262 * {@inheritDoc} 263 */ 264 @Override() 265 public void processAbandonRequest(final int messageID, 266 @NotNull final AbandonRequestProtocolOp request, 267 @NotNull final List<Control> controls) 268 { 269 if (abandonRateLimiter != null) 270 { 271 abandonRateLimiter.await(); 272 } 273 274 downstreamRequestHandler.processAbandonRequest(messageID, request, 275 controls); 276 } 277 278 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override() 284 @NotNull() 285 public LDAPMessage processAddRequest(final int messageID, 286 @NotNull final AddRequestProtocolOp request, 287 @NotNull final List<Control> controls) 288 { 289 if (addRateLimiter != null) 290 { 291 addRateLimiter.await(); 292 } 293 294 return downstreamRequestHandler.processAddRequest(messageID, request, 295 controls); 296 } 297 298 299 300 /** 301 * {@inheritDoc} 302 */ 303 @Override() 304 @NotNull() 305 public LDAPMessage processBindRequest(final int messageID, 306 @NotNull final BindRequestProtocolOp request, 307 @NotNull final List<Control> controls) 308 { 309 if (bindRateLimiter != null) 310 { 311 bindRateLimiter.await(); 312 } 313 314 return downstreamRequestHandler.processBindRequest(messageID, request, 315 controls); 316 } 317 318 319 320 /** 321 * {@inheritDoc} 322 */ 323 @Override() 324 @NotNull() 325 public LDAPMessage processCompareRequest(final int messageID, 326 @NotNull final CompareRequestProtocolOp request, 327 @NotNull final List<Control> controls) 328 { 329 if (compareRateLimiter != null) 330 { 331 compareRateLimiter.await(); 332 } 333 334 return downstreamRequestHandler.processCompareRequest(messageID, request, 335 controls); 336 } 337 338 339 340 /** 341 * {@inheritDoc} 342 */ 343 @Override() 344 @NotNull() 345 public LDAPMessage processDeleteRequest(final int messageID, 346 @NotNull final DeleteRequestProtocolOp request, 347 @NotNull final List<Control> controls) 348 { 349 if (deleteRateLimiter != null) 350 { 351 deleteRateLimiter.await(); 352 } 353 354 return downstreamRequestHandler.processDeleteRequest(messageID, request, 355 controls); 356 } 357 358 359 360 /** 361 * {@inheritDoc} 362 */ 363 @Override() 364 @NotNull() 365 public LDAPMessage processExtendedRequest(final int messageID, 366 @NotNull final ExtendedRequestProtocolOp request, 367 @NotNull final List<Control> controls) 368 { 369 if (extendedRateLimiter != null) 370 { 371 extendedRateLimiter.await(); 372 } 373 374 return downstreamRequestHandler.processExtendedRequest(messageID, request, 375 controls); 376 } 377 378 379 380 /** 381 * {@inheritDoc} 382 */ 383 @Override() 384 @NotNull() 385 public LDAPMessage processModifyRequest(final int messageID, 386 @NotNull final ModifyRequestProtocolOp request, 387 @NotNull final List<Control> controls) 388 { 389 if (modifyRateLimiter != null) 390 { 391 modifyRateLimiter.await(); 392 } 393 394 return downstreamRequestHandler.processModifyRequest(messageID, request, 395 controls); 396 } 397 398 399 400 /** 401 * {@inheritDoc} 402 */ 403 @Override() 404 @NotNull() 405 public LDAPMessage processModifyDNRequest(final int messageID, 406 @NotNull final ModifyDNRequestProtocolOp request, 407 @NotNull final List<Control> controls) 408 { 409 if (modifyDNRateLimiter != null) 410 { 411 modifyDNRateLimiter.await(); 412 } 413 414 return downstreamRequestHandler.processModifyDNRequest(messageID, request, 415 controls); 416 } 417 418 419 420 /** 421 * {@inheritDoc} 422 */ 423 @Override() 424 @NotNull() 425 public LDAPMessage processSearchRequest(final int messageID, 426 @NotNull final SearchRequestProtocolOp request, 427 @NotNull final List<Control> controls) 428 { 429 if (searchRateLimiter != null) 430 { 431 searchRateLimiter.await(); 432 } 433 434 return downstreamRequestHandler.processSearchRequest(messageID, request, 435 controls); 436 } 437}