001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.function; 018 019import java.io.IOException; 020import java.net.InetSocketAddress; 021import java.net.URI; 022import java.security.Principal; 023import java.time.Instant; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.EnumSet; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.LinkedHashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.Optional; 034import java.util.Set; 035import java.util.concurrent.ConcurrentHashMap; 036import java.util.function.Function; 037import java.util.function.Predicate; 038 039import javax.servlet.ServletException; 040import javax.servlet.http.Cookie; 041import javax.servlet.http.HttpServletRequest; 042import javax.servlet.http.HttpSession; 043 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046 047import org.springframework.core.ParameterizedTypeReference; 048import org.springframework.http.HttpHeaders; 049import org.springframework.http.HttpMethod; 050import org.springframework.http.MediaType; 051import org.springframework.http.converter.HttpMessageConverter; 052import org.springframework.http.server.PathContainer; 053import org.springframework.lang.NonNull; 054import org.springframework.lang.Nullable; 055import org.springframework.util.Assert; 056import org.springframework.util.MultiValueMap; 057import org.springframework.web.cors.CorsUtils; 058import org.springframework.web.util.UriBuilder; 059import org.springframework.web.util.UriUtils; 060import org.springframework.web.util.pattern.PathPattern; 061import org.springframework.web.util.pattern.PathPatternParser; 062 063/** 064 * Implementations of {@link RequestPredicate} that implement various useful 065 * request matching operations, such as matching based on path, HTTP method, etc. 066 * 067 * @author Arjen Poutsma 068 * @since 5.2 069 */ 070public abstract class RequestPredicates { 071 072 private static final Log logger = LogFactory.getLog(RequestPredicates.class); 073 074 /** 075 * Return a {@code RequestPredicate} that always matches. 076 * @return a predicate that always matches 077 */ 078 public static RequestPredicate all() { 079 return request -> true; 080 } 081 082 /** 083 * Return a {@code RequestPredicate} that matches if the request's 084 * HTTP method is equal to the given method. 085 * @param httpMethod the HTTP method to match against 086 * @return a predicate that tests against the given HTTP method 087 */ 088 public static RequestPredicate method(HttpMethod httpMethod) { 089 return new HttpMethodPredicate(httpMethod); 090 } 091 092 /** 093 * Return a {@code RequestPredicate} that matches if the request's 094 * HTTP method is equal to one the of the given methods. 095 * @param httpMethods the HTTP methods to match against 096 * @return a predicate that tests against the given HTTP methods 097 */ 098 public static RequestPredicate methods(HttpMethod... httpMethods) { 099 return new HttpMethodPredicate(httpMethods); 100 } 101 102 /** 103 * Return a {@code RequestPredicate} that tests the request path 104 * against the given path pattern. 105 * @param pattern the pattern to match to 106 * @return a predicate that tests against the given path pattern 107 */ 108 public static RequestPredicate path(String pattern) { 109 Assert.notNull(pattern, "'pattern' must not be null"); 110 if (!pattern.isEmpty() && !pattern.startsWith("/")) { 111 pattern = "/" + pattern; 112 } 113 return pathPredicates(PathPatternParser.defaultInstance).apply(pattern); 114 } 115 116 /** 117 * Return a function that creates new path-matching {@code RequestPredicates} 118 * from pattern Strings using the given {@link PathPatternParser}. 119 * <p>This method can be used to specify a non-default, customized 120 * {@code PathPatternParser} when resolving path patterns. 121 * @param patternParser the parser used to parse patterns given to the returned function 122 * @return a function that resolves a pattern String into a path-matching 123 * {@code RequestPredicates} instance 124 */ 125 public static Function<String, RequestPredicate> pathPredicates(PathPatternParser patternParser) { 126 Assert.notNull(patternParser, "PathPatternParser must not be null"); 127 return pattern -> new PathPatternPredicate(patternParser.parse(pattern)); 128 } 129 130 /** 131 * Return a {@code RequestPredicate} that tests the request's headers 132 * against the given headers predicate. 133 * @param headersPredicate a predicate that tests against the request headers 134 * @return a predicate that tests against the given header predicate 135 */ 136 public static RequestPredicate headers(Predicate<ServerRequest.Headers> headersPredicate) { 137 return new HeadersPredicate(headersPredicate); 138 } 139 140 /** 141 * Return a {@code RequestPredicate} that tests if the request's 142 * {@linkplain ServerRequest.Headers#contentType() content type} is 143 * {@linkplain MediaType#includes(MediaType) included} by any of the given media types. 144 * @param mediaTypes the media types to match the request's content type against 145 * @return a predicate that tests the request's content type against the given media types 146 */ 147 public static RequestPredicate contentType(MediaType... mediaTypes) { 148 Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); 149 return new ContentTypePredicate(mediaTypes); 150 } 151 152 /** 153 * Return a {@code RequestPredicate} that tests if the request's 154 * {@linkplain ServerRequest.Headers#accept() accept} header is 155 * {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with any of the given media types. 156 * @param mediaTypes the media types to match the request's accept header against 157 * @return a predicate that tests the request's accept header against the given media types 158 */ 159 public static RequestPredicate accept(MediaType... mediaTypes) { 160 Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); 161 return new AcceptPredicate(mediaTypes); 162 } 163 164 /** 165 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code GET} 166 * and the given {@code pattern} matches against the request path. 167 * @param pattern the path pattern to match against 168 * @return a predicate that matches if the request method is GET and if the given pattern 169 * matches against the request path 170 */ 171 public static RequestPredicate GET(String pattern) { 172 return method(HttpMethod.GET).and(path(pattern)); 173 } 174 175 /** 176 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code HEAD} 177 * and the given {@code pattern} matches against the request path. 178 * @param pattern the path pattern to match against 179 * @return a predicate that matches if the request method is HEAD and if the given pattern 180 * matches against the request path 181 */ 182 public static RequestPredicate HEAD(String pattern) { 183 return method(HttpMethod.HEAD).and(path(pattern)); 184 } 185 186 /** 187 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code POST} 188 * and the given {@code pattern} matches against the request path. 189 * @param pattern the path pattern to match against 190 * @return a predicate that matches if the request method is POST and if the given pattern 191 * matches against the request path 192 */ 193 public static RequestPredicate POST(String pattern) { 194 return method(HttpMethod.POST).and(path(pattern)); 195 } 196 197 /** 198 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PUT} 199 * and the given {@code pattern} matches against the request path. 200 * @param pattern the path pattern to match against 201 * @return a predicate that matches if the request method is PUT and if the given pattern 202 * matches against the request path 203 */ 204 public static RequestPredicate PUT(String pattern) { 205 return method(HttpMethod.PUT).and(path(pattern)); 206 } 207 208 /** 209 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code PATCH} 210 * and the given {@code pattern} matches against the request path. 211 * @param pattern the path pattern to match against 212 * @return a predicate that matches if the request method is PATCH and if the given pattern 213 * matches against the request path 214 */ 215 public static RequestPredicate PATCH(String pattern) { 216 return method(HttpMethod.PATCH).and(path(pattern)); 217 } 218 219 /** 220 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code DELETE} 221 * and the given {@code pattern} matches against the request path. 222 * @param pattern the path pattern to match against 223 * @return a predicate that matches if the request method is DELETE and if the given pattern 224 * matches against the request path 225 */ 226 public static RequestPredicate DELETE(String pattern) { 227 return method(HttpMethod.DELETE).and(path(pattern)); 228 } 229 230 /** 231 * Return a {@code RequestPredicate} that matches if request's HTTP method is {@code OPTIONS} 232 * and the given {@code pattern} matches against the request path. 233 * @param pattern the path pattern to match against 234 * @return a predicate that matches if the request method is OPTIONS and if the given pattern 235 * matches against the request path 236 */ 237 public static RequestPredicate OPTIONS(String pattern) { 238 return method(HttpMethod.OPTIONS).and(path(pattern)); 239 } 240 241 /** 242 * Return a {@code RequestPredicate} that matches if the request's path has the given extension. 243 * @param extension the path extension to match against, ignoring case 244 * @return a predicate that matches if the request's path has the given file extension 245 */ 246 public static RequestPredicate pathExtension(String extension) { 247 Assert.notNull(extension, "'extension' must not be null"); 248 return new PathExtensionPredicate(extension); 249 } 250 251 /** 252 * Return a {@code RequestPredicate} that matches if the request's path matches the given 253 * predicate. 254 * @param extensionPredicate the predicate to test against the request path extension 255 * @return a predicate that matches if the given predicate matches against the request's path 256 * file extension 257 */ 258 public static RequestPredicate pathExtension(Predicate<String> extensionPredicate) { 259 return new PathExtensionPredicate(extensionPredicate); 260 } 261 262 /** 263 * Return a {@code RequestPredicate} that matches if the request's parameter of the given name 264 * has the given value. 265 * @param name the name of the parameter to test against 266 * @param value the value of the parameter to test against 267 * @return a predicate that matches if the parameter has the given value 268 * @see ServerRequest#param(String) 269 */ 270 public static RequestPredicate param(String name, String value) { 271 return new ParamPredicate(name, value); 272 } 273 274 /** 275 * Return a {@code RequestPredicate} that tests the request's parameter of the given name 276 * against the given predicate. 277 * @param name the name of the parameter to test against 278 * @param predicate the predicate to test against the parameter value 279 * @return a predicate that matches the given predicate against the parameter of the given name 280 * @see ServerRequest#param(String) 281 */ 282 public static RequestPredicate param(String name, Predicate<String> predicate) { 283 return new ParamPredicate(name, predicate); 284 } 285 286 287 private static void traceMatch(String prefix, Object desired, @Nullable Object actual, boolean match) { 288 if (logger.isTraceEnabled()) { 289 logger.trace(String.format("%s \"%s\" %s against value \"%s\"", 290 prefix, desired, match ? "matches" : "does not match", actual)); 291 } 292 } 293 294 private static void restoreAttributes(ServerRequest request, Map<String, Object> attributes) { 295 request.attributes().clear(); 296 request.attributes().putAll(attributes); 297 } 298 299 private static Map<String, String> mergePathVariables(Map<String, String> oldVariables, 300 Map<String, String> newVariables) { 301 302 if (!newVariables.isEmpty()) { 303 Map<String, String> mergedVariables = new LinkedHashMap<>(oldVariables); 304 mergedVariables.putAll(newVariables); 305 return mergedVariables; 306 } 307 else { 308 return oldVariables; 309 } 310 } 311 312 private static PathPattern mergePatterns(@Nullable PathPattern oldPattern, PathPattern newPattern) { 313 if (oldPattern != null) { 314 return oldPattern.combine(newPattern); 315 } 316 else { 317 return newPattern; 318 } 319 320 } 321 322 323 /** 324 * Receives notifications from the logical structure of request predicates. 325 */ 326 public interface Visitor { 327 328 /** 329 * Receive notification of an HTTP method predicate. 330 * @param methods the HTTP methods that make up the predicate 331 * @see RequestPredicates#method(HttpMethod) 332 */ 333 void method(Set<HttpMethod> methods); 334 335 /** 336 * Receive notification of an path predicate. 337 * @param pattern the path pattern that makes up the predicate 338 * @see RequestPredicates#path(String) 339 */ 340 void path(String pattern); 341 342 /** 343 * Receive notification of an path extension predicate. 344 * @param extension the path extension that makes up the predicate 345 * @see RequestPredicates#pathExtension(String) 346 */ 347 void pathExtension(String extension); 348 349 /** 350 * Receive notification of an HTTP header predicate. 351 * @param name the name of the HTTP header to check 352 * @param value the desired value of the HTTP header 353 * @see RequestPredicates#headers(Predicate) 354 * @see RequestPredicates#contentType(MediaType...) 355 * @see RequestPredicates#accept(MediaType...) 356 */ 357 void header(String name, String value); 358 359 /** 360 * Receive notification of a parameter predicate. 361 * @param name the name of the parameter 362 * @param value the desired value of the parameter 363 * @see RequestPredicates#param(String, String) 364 */ 365 void param(String name, String value); 366 367 /** 368 * Receive first notification of a logical AND predicate. 369 * The first subsequent notification will contain the left-hand side of the AND-predicate; 370 * followed by {@link #and()}, followed by the right-hand side, followed by {@link #endAnd()}. 371 * @see RequestPredicate#and(RequestPredicate) 372 */ 373 void startAnd(); 374 375 /** 376 * Receive "middle" notification of a logical AND predicate. 377 * The following notification contains the right-hand side, followed by {@link #endAnd()}. 378 * @see RequestPredicate#and(RequestPredicate) 379 */ 380 void and(); 381 382 /** 383 * Receive last notification of a logical AND predicate. 384 * @see RequestPredicate#and(RequestPredicate) 385 */ 386 void endAnd(); 387 388 /** 389 * Receive first notification of a logical OR predicate. 390 * The first subsequent notification will contain the left-hand side of the OR-predicate; 391 * the second notification contains the right-hand side, followed by {@link #endOr()}. 392 * @see RequestPredicate#or(RequestPredicate) 393 */ 394 void startOr(); 395 396 /** 397 * Receive "middle" notification of a logical OR predicate. 398 * The following notification contains the right-hand side, followed by {@link #endOr()}. 399 * @see RequestPredicate#or(RequestPredicate) 400 */ 401 void or(); 402 403 /** 404 * Receive last notification of a logical OR predicate. 405 * @see RequestPredicate#or(RequestPredicate) 406 */ 407 void endOr(); 408 409 /** 410 * Receive first notification of a negated predicate. 411 * The first subsequent notification will contain the negated predicated, followed 412 * by {@link #endNegate()}. 413 * @see RequestPredicate#negate() 414 */ 415 void startNegate(); 416 417 /** 418 * Receive last notification of a negated predicate. 419 * @see RequestPredicate#negate() 420 */ 421 void endNegate(); 422 423 /** 424 * Receive first notification of an unknown predicate. 425 */ 426 void unknown(RequestPredicate predicate); 427 } 428 429 private static class HttpMethodPredicate implements RequestPredicate { 430 431 private final Set<HttpMethod> httpMethods; 432 433 434 public HttpMethodPredicate(HttpMethod httpMethod) { 435 Assert.notNull(httpMethod, "HttpMethod must not be null"); 436 this.httpMethods = EnumSet.of(httpMethod); 437 } 438 439 public HttpMethodPredicate(HttpMethod... httpMethods) { 440 Assert.notEmpty(httpMethods, "HttpMethods must not be empty"); 441 this.httpMethods = EnumSet.copyOf(Arrays.asList(httpMethods)); 442 } 443 444 @Override 445 public boolean test(ServerRequest request) { 446 HttpMethod method = method(request); 447 boolean match = this.httpMethods.contains(method); 448 traceMatch("Method", this.httpMethods, method, match); 449 return match; 450 } 451 452 @Nullable 453 private static HttpMethod method(ServerRequest request) { 454 if (CorsUtils.isPreFlightRequest(request.servletRequest())) { 455 String accessControlRequestMethod = 456 request.headers().firstHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD); 457 return HttpMethod.resolve(accessControlRequestMethod); 458 } 459 else { 460 return request.method(); 461 } 462 } 463 464 @Override 465 public void accept(Visitor visitor) { 466 visitor.method(Collections.unmodifiableSet(this.httpMethods)); 467 } 468 469 @Override 470 public String toString() { 471 if (this.httpMethods.size() == 1) { 472 return this.httpMethods.iterator().next().toString(); 473 } 474 else { 475 return this.httpMethods.toString(); 476 } 477 } 478 } 479 480 481 private static class PathPatternPredicate implements RequestPredicate { 482 483 private final PathPattern pattern; 484 485 public PathPatternPredicate(PathPattern pattern) { 486 Assert.notNull(pattern, "'pattern' must not be null"); 487 this.pattern = pattern; 488 } 489 490 @Override 491 public boolean test(ServerRequest request) { 492 PathContainer pathContainer = request.pathContainer(); 493 PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer); 494 traceMatch("Pattern", this.pattern.getPatternString(), request.path(), info != null); 495 if (info != null) { 496 mergeAttributes(request, info.getUriVariables(), this.pattern); 497 return true; 498 } 499 else { 500 return false; 501 } 502 } 503 504 private static void mergeAttributes(ServerRequest request, Map<String, String> variables, 505 PathPattern pattern) { 506 Map<String, String> pathVariables = mergePathVariables(request.pathVariables(), variables); 507 request.attributes().put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 508 Collections.unmodifiableMap(pathVariables)); 509 510 pattern = mergePatterns( 511 (PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE), 512 pattern); 513 request.attributes().put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern); 514 } 515 @Override 516 public Optional<ServerRequest> nest(ServerRequest request) { 517 return Optional.ofNullable(this.pattern.matchStartOfPath(request.pathContainer())) 518 .map(info -> new SubPathServerRequestWrapper(request, info, this.pattern)); 519 } 520 521 @Override 522 public void accept(Visitor visitor) { 523 visitor.path(this.pattern.getPatternString()); 524 } 525 526 @Override 527 public String toString() { 528 return this.pattern.getPatternString(); 529 } 530 } 531 532 533 private static class HeadersPredicate implements RequestPredicate { 534 535 private final Predicate<ServerRequest.Headers> headersPredicate; 536 537 public HeadersPredicate(Predicate<ServerRequest.Headers> headersPredicate) { 538 Assert.notNull(headersPredicate, "Predicate must not be null"); 539 this.headersPredicate = headersPredicate; 540 } 541 542 @Override 543 public boolean test(ServerRequest request) { 544 if (CorsUtils.isPreFlightRequest(request.servletRequest())) { 545 return true; 546 } 547 else { 548 return this.headersPredicate.test(request.headers()); 549 } 550 } 551 552 @Override 553 public String toString() { 554 return this.headersPredicate.toString(); 555 } 556 } 557 558 private static class ContentTypePredicate extends HeadersPredicate { 559 560 private final Set<MediaType> mediaTypes; 561 562 public ContentTypePredicate(MediaType... mediaTypes) { 563 this(new HashSet<>(Arrays.asList(mediaTypes))); 564 } 565 566 private ContentTypePredicate(Set<MediaType> mediaTypes) { 567 super(headers -> { 568 MediaType contentType = 569 headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM); 570 boolean match = mediaTypes.stream() 571 .anyMatch(mediaType -> mediaType.includes(contentType)); 572 traceMatch("Content-Type", mediaTypes, contentType, match); 573 return match; 574 }); 575 this.mediaTypes = mediaTypes; 576 } 577 578 @Override 579 public void accept(Visitor visitor) { 580 visitor.header(HttpHeaders.CONTENT_TYPE, 581 (this.mediaTypes.size() == 1) ? 582 this.mediaTypes.iterator().next().toString() : 583 this.mediaTypes.toString()); 584 } 585 586 @Override 587 public String toString() { 588 return String.format("Content-Type: %s", 589 (this.mediaTypes.size() == 1) ? 590 this.mediaTypes.iterator().next().toString() : 591 this.mediaTypes.toString()); 592 } 593 } 594 595 private static class AcceptPredicate extends HeadersPredicate { 596 597 private final Set<MediaType> mediaTypes; 598 599 public AcceptPredicate(MediaType... mediaTypes) { 600 this(new HashSet<>(Arrays.asList(mediaTypes))); 601 } 602 603 private AcceptPredicate(Set<MediaType> mediaTypes) { 604 super(headers -> { 605 List<MediaType> acceptedMediaTypes = acceptedMediaTypes(headers); 606 boolean match = acceptedMediaTypes.stream() 607 .anyMatch(acceptedMediaType -> mediaTypes.stream() 608 .anyMatch(acceptedMediaType::isCompatibleWith)); 609 traceMatch("Accept", mediaTypes, acceptedMediaTypes, match); 610 return match; 611 }); 612 this.mediaTypes = mediaTypes; 613 } 614 615 @NonNull 616 private static List<MediaType> acceptedMediaTypes(ServerRequest.Headers headers) { 617 List<MediaType> acceptedMediaTypes = headers.accept(); 618 if (acceptedMediaTypes.isEmpty()) { 619 acceptedMediaTypes = Collections.singletonList(MediaType.ALL); 620 } 621 else { 622 MediaType.sortBySpecificityAndQuality(acceptedMediaTypes); 623 } 624 return acceptedMediaTypes; 625 } 626 627 @Override 628 public void accept(Visitor visitor) { 629 visitor.header(HttpHeaders.ACCEPT, 630 (this.mediaTypes.size() == 1) ? 631 this.mediaTypes.iterator().next().toString() : 632 this.mediaTypes.toString()); 633 } 634 635 @Override 636 public String toString() { 637 return String.format("Accept: %s", 638 (this.mediaTypes.size() == 1) ? 639 this.mediaTypes.iterator().next().toString() : 640 this.mediaTypes.toString()); 641 } 642 } 643 644 private static class PathExtensionPredicate implements RequestPredicate { 645 646 private final Predicate<String> extensionPredicate; 647 648 @Nullable 649 private final String extension; 650 public PathExtensionPredicate(Predicate<String> extensionPredicate) { 651 Assert.notNull(extensionPredicate, "Predicate must not be null"); 652 this.extensionPredicate = extensionPredicate; 653 this.extension = null; 654 } 655 656 public PathExtensionPredicate(String extension) { 657 Assert.notNull(extension, "Extension must not be null"); 658 659 this.extensionPredicate = s -> { 660 boolean match = extension.equalsIgnoreCase(s); 661 traceMatch("Extension", extension, s, match); 662 return match; 663 }; 664 this.extension = extension; 665 } 666 667 @Override 668 public boolean test(ServerRequest request) { 669 String pathExtension = UriUtils.extractFileExtension(request.path()); 670 return this.extensionPredicate.test(pathExtension); 671 } 672 673 @Override 674 public void accept(Visitor visitor) { 675 visitor.pathExtension( 676 (this.extension != null) ? 677 this.extension : 678 this.extensionPredicate.toString()); 679 } 680 681 @Override 682 public String toString() { 683 return String.format("*.%s", 684 (this.extension != null) ? 685 this.extension : 686 this.extensionPredicate); 687 } 688 689 } 690 691 692 private static class ParamPredicate implements RequestPredicate { 693 694 private final String name; 695 696 private final Predicate<String> valuePredicate; 697 698 @Nullable 699 private final String value; 700 701 public ParamPredicate(String name, Predicate<String> valuePredicate) { 702 Assert.notNull(name, "Name must not be null"); 703 Assert.notNull(valuePredicate, "Predicate must not be null"); 704 this.name = name; 705 this.valuePredicate = valuePredicate; 706 this.value = null; 707 } 708 709 public ParamPredicate(String name, String value) { 710 Assert.notNull(name, "Name must not be null"); 711 Assert.notNull(value, "Value must not be null"); 712 this.name = name; 713 this.valuePredicate = value::equals; 714 this.value = value; 715 } 716 717 @Override 718 public boolean test(ServerRequest request) { 719 Optional<String> s = request.param(this.name); 720 return s.filter(this.valuePredicate).isPresent(); 721 } 722 723 @Override 724 public void accept(Visitor visitor) { 725 visitor.param(this.name, 726 (this.value != null) ? 727 this.value : 728 this.valuePredicate.toString()); 729 } 730 731 @Override 732 public String toString() { 733 return String.format("?%s %s", this.name, 734 (this.value != null) ? 735 this.value : 736 this.valuePredicate); 737 } 738 } 739 740 741 /** 742 * {@link RequestPredicate} for where both {@code left} and {@code right} predicates 743 * must match. 744 */ 745 static class AndRequestPredicate implements RequestPredicate { 746 747 private final RequestPredicate left; 748 749 private final RequestPredicate right; 750 751 public AndRequestPredicate(RequestPredicate left, RequestPredicate right) { 752 Assert.notNull(left, "Left RequestPredicate must not be null"); 753 Assert.notNull(right, "Right RequestPredicate must not be null"); 754 this.left = left; 755 this.right = right; 756 } 757 758 @Override 759 public boolean test(ServerRequest request) { 760 Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); 761 762 if (this.left.test(request) && this.right.test(request)) { 763 return true; 764 } 765 restoreAttributes(request, oldAttributes); 766 return false; 767 } 768 769 @Override 770 public Optional<ServerRequest> nest(ServerRequest request) { 771 return this.left.nest(request).flatMap(this.right::nest); 772 } 773 774 @Override 775 public void accept(Visitor visitor) { 776 visitor.startAnd(); 777 this.left.accept(visitor); 778 visitor.and(); 779 this.right.accept(visitor); 780 visitor.endAnd(); 781 } 782 783 @Override 784 public String toString() { 785 return String.format("(%s && %s)", this.left, this.right); 786 } 787 } 788 789 /** 790 * {@link RequestPredicate} that negates a delegate predicate. 791 */ 792 static class NegateRequestPredicate implements RequestPredicate { 793 private final RequestPredicate delegate; 794 795 public NegateRequestPredicate(RequestPredicate delegate) { 796 Assert.notNull(delegate, "Delegate must not be null"); 797 this.delegate = delegate; 798 } 799 800 @Override 801 public boolean test(ServerRequest request) { 802 Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); 803 boolean result = !this.delegate.test(request); 804 if (!result) { 805 restoreAttributes(request, oldAttributes); 806 } 807 return result; 808 } 809 810 @Override 811 public void accept(Visitor visitor) { 812 visitor.startNegate(); 813 this.delegate.accept(visitor); 814 visitor.endNegate(); 815 } 816 817 @Override 818 public String toString() { 819 return "!" + this.delegate.toString(); 820 } 821 } 822 823 /** 824 * {@link RequestPredicate} where either {@code left} or {@code right} predicates 825 * may match. 826 */ 827 static class OrRequestPredicate implements RequestPredicate { 828 829 private final RequestPredicate left; 830 831 private final RequestPredicate right; 832 833 public OrRequestPredicate(RequestPredicate left, RequestPredicate right) { 834 Assert.notNull(left, "Left RequestPredicate must not be null"); 835 Assert.notNull(right, "Right RequestPredicate must not be null"); 836 this.left = left; 837 this.right = right; 838 } 839 840 @Override 841 public boolean test(ServerRequest request) { 842 Map<String, Object> oldAttributes = new HashMap<>(request.attributes()); 843 844 if (this.left.test(request)) { 845 return true; 846 } 847 else { 848 restoreAttributes(request, oldAttributes); 849 if (this.right.test(request)) { 850 return true; 851 } 852 } 853 restoreAttributes(request, oldAttributes); 854 return false; 855 } 856 857 @Override 858 public Optional<ServerRequest> nest(ServerRequest request) { 859 Optional<ServerRequest> leftResult = this.left.nest(request); 860 if (leftResult.isPresent()) { 861 return leftResult; 862 } 863 else { 864 return this.right.nest(request); 865 } 866 } 867 868 @Override 869 public void accept(Visitor visitor) { 870 visitor.startOr(); 871 this.left.accept(visitor); 872 visitor.or(); 873 this.right.accept(visitor); 874 visitor.endOr(); 875 } 876 877 @Override 878 public String toString() { 879 return String.format("(%s || %s)", this.left, this.right); 880 } 881 } 882 883 884 private static class SubPathServerRequestWrapper implements ServerRequest { 885 886 private final ServerRequest request; 887 888 private final PathContainer pathContainer; 889 890 private final Map<String, Object> attributes; 891 892 public SubPathServerRequestWrapper(ServerRequest request, 893 PathPattern.PathRemainingMatchInfo info, PathPattern pattern) { 894 this.request = request; 895 this.pathContainer = new SubPathContainer(info.getPathRemaining()); 896 this.attributes = mergeAttributes(request, info.getUriVariables(), pattern); 897 } 898 899 private static Map<String, Object> mergeAttributes(ServerRequest request, 900 Map<String, String> pathVariables, PathPattern pattern) { 901 Map<String, Object> result = new ConcurrentHashMap<>(request.attributes()); 902 903 result.put(RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 904 mergePathVariables(request.pathVariables(), pathVariables)); 905 906 pattern = mergePatterns( 907 (PathPattern) request.attributes().get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE), 908 pattern); 909 result.put(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE, pattern); 910 return result; 911 } 912 913 @Override 914 public HttpMethod method() { 915 return this.request.method(); 916 } 917 918 @Override 919 public String methodName() { 920 return this.request.methodName(); 921 } 922 923 @Override 924 public URI uri() { 925 return this.request.uri(); 926 } 927 928 @Override 929 public UriBuilder uriBuilder() { 930 return this.request.uriBuilder(); 931 } 932 933 @Override 934 public String path() { 935 return this.pathContainer.value(); 936 } 937 938 @Override 939 public PathContainer pathContainer() { 940 return this.pathContainer; 941 } 942 943 @Override 944 public Headers headers() { 945 return this.request.headers(); 946 } 947 948 @Override 949 public MultiValueMap<String, Cookie> cookies() { 950 return this.request.cookies(); 951 } 952 953 @Override 954 public Optional<InetSocketAddress> remoteAddress() { 955 return this.request.remoteAddress(); 956 } 957 958 @Override 959 public List<HttpMessageConverter<?>> messageConverters() { 960 return this.request.messageConverters(); 961 } 962 963 @Override 964 public <T> T body(Class<T> bodyType) throws ServletException, IOException { 965 return this.request.body(bodyType); 966 } 967 968 @Override 969 public <T> T body(ParameterizedTypeReference<T> bodyType) 970 throws ServletException, IOException { 971 return this.request.body(bodyType); 972 } 973 974 @Override 975 public Optional<Object> attribute(String name) { 976 return this.request.attribute(name); 977 } 978 979 @Override 980 public Map<String, Object> attributes() { 981 return this.attributes; 982 } 983 984 @Override 985 public Optional<String> param(String name) { 986 return this.request.param(name); 987 } 988 989 @Override 990 public MultiValueMap<String, String> params() { 991 return this.request.params(); 992 } 993 994 @Override 995 @SuppressWarnings("unchecked") 996 public Map<String, String> pathVariables() { 997 return (Map<String, String>) this.attributes.getOrDefault( 998 RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap()); 999 } 1000 1001 @Override 1002 public HttpSession session() { 1003 return this.request.session(); 1004 } 1005 1006 1007 1008 @Override 1009 public Optional<Principal> principal() { 1010 return this.request.principal(); 1011 } 1012 1013 @Override 1014 public HttpServletRequest servletRequest() { 1015 return this.request.servletRequest(); 1016 } 1017 1018 @Override 1019 public Optional<ServerResponse> checkNotModified(Instant lastModified) { 1020 return this.request.checkNotModified(lastModified); 1021 } 1022 1023 @Override 1024 public Optional<ServerResponse> checkNotModified(String etag) { 1025 return this.request.checkNotModified(etag); 1026 } 1027 1028 @Override 1029 public Optional<ServerResponse> checkNotModified(Instant lastModified, String etag) { 1030 return this.request.checkNotModified(lastModified, etag); 1031 } 1032 1033 @Override 1034 public String toString() { 1035 return method() + " " + path(); 1036 } 1037 1038 private static class SubPathContainer implements PathContainer { 1039 1040 private static final PathContainer.Separator SEPARATOR = () -> "/"; 1041 1042 1043 private final String value; 1044 1045 private final List<Element> elements; 1046 1047 public SubPathContainer(PathContainer original) { 1048 this.value = prefixWithSlash(original.value()); 1049 this.elements = prependWithSeparator(original.elements()); 1050 } 1051 1052 private static String prefixWithSlash(String path) { 1053 if (!path.startsWith("/")) { 1054 path = "/" + path; 1055 } 1056 return path; 1057 } 1058 1059 private static List<Element> prependWithSeparator(List<Element> elements) { 1060 List<Element> result = new ArrayList<>(elements); 1061 if (result.isEmpty() || !(result.get(0) instanceof Separator)) { 1062 result.add(0, SEPARATOR); 1063 } 1064 return Collections.unmodifiableList(result); 1065 } 1066 1067 1068 @Override 1069 public String value() { 1070 return this.value; 1071 } 1072 1073 @Override 1074 public List<Element> elements() { 1075 return this.elements; 1076 } 1077 } 1078 } 1079 1080}