/** * Returns the resource lookup function used by {@link #resources(String, Resource)}. * The returned function can be {@linkplain Function#andThen(Function) composed} on, for * instance to return a default resource when the lookup function does not match: * <pre class="code"> * Mono<Resource> defaultResource = Mono.just(new ClassPathResource("index.html")); * Function<ServerRequest, Mono<Resource>> lookupFunction = * RouterFunctions.resourceLookupFunction("/resources/**", new FileSystemResource("public-resources/")) * .andThen(resourceMono -> resourceMono.switchIfEmpty(defaultResource)); * RouterFunction<ServerResponse> resources = RouterFunctions.resources(lookupFunction); * </pre> * @param pattern the pattern to match * @param location the location directory relative to which resources should be resolved * @return the default resource lookup function for the given parameters. */ public static Function<ServerRequest, Mono<Resource>> resourceLookupFunction(String pattern, Resource location) { return new PathResourceLookupFunction(pattern, location); }
@Override public Mono<Resource> apply(ServerRequest request) { PathContainer pathContainer = request.pathContainer(); if (!this.pattern.matches(pathContainer)) { return Mono.empty(); } pathContainer = this.pattern.extractPathWithinPattern(pathContainer); String path = processPath(pathContainer.value()); if (path.contains("%")) { path = StringUtils.uriDecode(path, StandardCharsets.UTF_8); } if (!StringUtils.hasLength(path) || isInvalidPath(path)) { return Mono.empty(); } try { Resource resource = this.location.createRelative(path); if (resource.exists() && resource.isReadable() && isResourceUnderLocation(resource)) { return Mono.just(resource); } else { return Mono.empty(); } } catch (IOException ex) { throw new UncheckedIOException(ex); } }
@Test public void notFound() throws Exception { ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/server/"); PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location); MockServerRequest request = MockServerRequest.builder() .uri(new URI("http://localhost/resources/foo")) .build(); Mono<Resource> result = function.apply(request); StepVerifier.create(result) .expectComplete() .verify(); }
@Test public void subPath() throws Exception { ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/server/"); PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location); MockServerRequest request = MockServerRequest.builder() .uri(new URI("http://localhost/resources/child/response.txt")) .build(); Mono<Resource> result = function.apply(request); String path = "org/springframework/web/reactive/function/server/child/response.txt"; File expected = new ClassPathResource(path).getFile(); StepVerifier.create(result) .expectNextMatches(resource -> { try { return expected.equals(resource.getFile()); } catch (IOException ex) { return false; } }) .expectComplete() .verify(); }
@Override public Mono<Resource> apply(ServerRequest request) { String path = processPath(request.path()); if (path.contains("%")) { path = UriUtils.decode(path, StandardCharsets.UTF_8); } if (!StringUtils.hasLength(path) || isInvalidPath(path)) { return Mono.empty(); } if (!PATH_MATCHER.match(this.pattern, path)) { return Mono.empty(); } else { path = PATH_MATCHER.extractPathWithinPattern(this.pattern, path); } try { Resource resource = this.location.createRelative(path); if (resource.exists() && resource.isReadable() && isResourceUnderLocation(resource)) { return Mono.just(resource); } else { return Mono.empty(); } } catch (IOException ex) { throw new UncheckedIOException(ex); } }
@Test public void normal() throws Exception { ClassPathResource location = new ClassPathResource("org/springframework/web/reactive/function/server/"); PathResourceLookupFunction function = new PathResourceLookupFunction("/resources/**", location); MockServerRequest request = MockServerRequest.builder() .uri(new URI("http://localhost/resources/response.txt")) .build(); Mono<Resource> result = function.apply(request); File expected = new ClassPathResource("response.txt", getClass()).getFile(); StepVerifier.create(result) .expectNextMatches(resource -> { try { return expected.equals(resource.getFile()); } catch (IOException ex) { return false; } }) .expectComplete() .verify(); }
@Test public void composeResourceLookupFunction() throws Exception { ClassPathResource defaultResource = new ClassPathResource("response.txt", getClass()); Function<ServerRequest, Mono<Resource>> lookupFunction = new PathResourceLookupFunction("/resources/**", new ClassPathResource("org/springframework/web/reactive/function/server/")); Function<ServerRequest, Mono<Resource>> customLookupFunction = lookupFunction.andThen(resourceMono -> resourceMono .switchIfEmpty(Mono.just(defaultResource))); MockServerRequest request = MockServerRequest.builder() .uri(new URI("http://localhost/resources/foo")) .build(); Mono<Resource> result = customLookupFunction.apply(request); StepVerifier.create(result) .expectNextMatches(resource -> { try { return defaultResource.getFile().equals(resource.getFile()); } catch (IOException ex) { return false; } }) .expectComplete() .verify(); }
/** * Route requests that match the given pattern to resources relative to the given root location. * For instance * <pre class="code"> * Resource location = new FileSystemResource("public-resources/"); * RoutingFunction<Resource> resources = RouterFunctions.resources("/resources/**", location); * </pre> * @param pattern the pattern to match * @param location the location directory relative to which resources should be resolved * @return a router function that routes to resources */ public static RouterFunction<ServerResponse> resources(String pattern, Resource location) { Assert.hasLength(pattern, "'pattern' must not be empty"); Assert.notNull(location, "'location' must not be null"); return resources(new PathResourceLookupFunction(pattern, location)); }