@Override public Mono<Resource> transform(ServerWebExchange exchange, Resource resource, ResourceTransformerChain transformerChain) { Resource cachedResource = this.cache.get(resource, Resource.class); if (cachedResource != null) { logger.trace(exchange.getLogPrefix() + "Resource resolved from cache"); return Mono.just(cachedResource); } return transformerChain.transform(exchange, resource) .doOnNext(transformed -> this.cache.put(resource, transformed)); }
/** * A transformer can use this method when a resource being transformed * contains links to other resources. Such links need to be replaced with the * public facing link as determined by the resource resolver chain (e.g. the * public URL may have a version inserted). * @param resourcePath the path to a resource that needs to be re-written * @param exchange the current exchange * @param resource the resource being transformed * @param transformerChain the transformer chain * @return the resolved URL or an empty {@link Mono} */ protected Mono<String> resolveUrlPath(String resourcePath, ServerWebExchange exchange, Resource resource, ResourceTransformerChain transformerChain) { if (resourcePath.startsWith("/")) { // full resource path ResourceUrlProvider urlProvider = getResourceUrlProvider(); return (urlProvider != null ? urlProvider.getForUriString(resourcePath, exchange) : Mono.empty()); } else { // try resolving as relative path return transformerChain.getResolverChain() .resolveUrlPath(resourcePath, Collections.singletonList(resource)); } }
protected Mono<Resource> getResource(ServerWebExchange exchange) { String name = HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE; PathContainer pathWithinHandler = exchange.getRequiredAttribute(name); String path = processPath(pathWithinHandler.value()); if (!StringUtils.hasText(path) || isInvalidPath(path)) { return Mono.empty(); } if (isInvalidEncodedPath(path)) { return Mono.empty(); } Assert.state(this.resolverChain != null, "ResourceResolverChain not initialized"); Assert.state(this.transformerChain != null, "ResourceTransformerChain not initialized"); return this.resolverChain.resolveResource(exchange, path, getLocations()) .flatMap(resource -> this.transformerChain.transform(exchange, resource)); }
/** * A transformer can use this method when a resource being transformed * contains links to other resources. Such links need to be replaced with the * public facing link as determined by the resource resolver chain (e.g. the * public URL may have a version inserted). * @param resourcePath the path to a resource that needs to be re-written * @param exchange the current exchange * @param resource the resource being transformed * @param transformerChain the transformer chain * @return the resolved URL or null */ protected Mono<String> resolveUrlPath(String resourcePath, ServerWebExchange exchange, Resource resource, ResourceTransformerChain transformerChain) { if (resourcePath.startsWith("/")) { // full resource path ResourceUrlProvider urlProvider = getResourceUrlProvider(); return (urlProvider != null ? urlProvider.getForRequestUrl(exchange, resourcePath) : Mono.empty()); } else { // try resolving as relative path return transformerChain.getResolverChain() .resolveUrlPath(resourcePath, Collections.singletonList(resource)); } }
@Override public Mono<Resource> transform(ServerWebExchange exchange, Resource inputResource, ResourceTransformerChain chain) { return chain.transform(exchange, inputResource) .flatMap(outputResource -> { String name = outputResource.getFilename(); if (!this.fileExtension.equals(StringUtils.getFilenameExtension(name))) { return Mono.just(outputResource); } DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory(); Flux<DataBuffer> flux = DataBufferUtils .read(outputResource, bufferFactory, StreamUtils.BUFFER_SIZE); return DataBufferUtils.join(flux) .flatMap(dataBuffer -> { CharBuffer charBuffer = DEFAULT_CHARSET.decode(dataBuffer.asByteBuffer()); DataBufferUtils.release(dataBuffer); String content = charBuffer.toString(); return transform(content, outputResource, chain, exchange); }); }); }
private Mono<LineOutput> processLine(LineInfo info, ServerWebExchange exchange, Resource resource, ResourceTransformerChain chain) { if (!info.isLink()) { return Mono.just(new LineOutput(info.getLine(), null)); } String link = toAbsolutePath(info.getLine(), exchange.getRequest()); Mono<String> pathMono = resolveUrlPath(link, exchange, resource, chain) .doOnNext(path -> { if (logger.isTraceEnabled()) { logger.trace("Link modified: " + path + " (original: " + info.getLine() + ")"); } }); Mono<Resource> resourceMono = chain.getResolverChain() .resolveResource(null, info.getLine(), Collections.singletonList(resource)); return Flux.zip(pathMono, resourceMono, LineOutput::new).next(); }
@SuppressWarnings("deprecation") @Override public Mono<Resource> transform(ServerWebExchange exchange, Resource inputResource, ResourceTransformerChain transformerChain) { return transformerChain.transform(exchange, inputResource) .flatMap(outputResource -> { String filename = outputResource.getFilename(); if (!"css".equals(StringUtils.getFilenameExtension(filename)) || inputResource instanceof EncodedResourceResolver.EncodedResource || inputResource instanceof GzipResourceResolver.GzippedResource) { return Mono.just(outputResource); } DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory(); Flux<DataBuffer> flux = DataBufferUtils .read(outputResource, bufferFactory, StreamUtils.BUFFER_SIZE); return DataBufferUtils.join(flux) .flatMap(dataBuffer -> { CharBuffer charBuffer = DEFAULT_CHARSET.decode(dataBuffer.asByteBuffer()); DataBufferUtils.release(dataBuffer); String cssContent = charBuffer.toString(); return transformContent(cssContent, outputResource, transformerChain, exchange); }); }); }
@Test public void transformExtLinksNotAllowed() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/external.css")); List<ResourceTransformer> transformers = Collections.singletonList(new CssLinkResourceTransformer()); ResourceResolverChain mockChain = Mockito.mock(DefaultResourceResolverChain.class); ResourceTransformerChain chain = new DefaultResourceTransformerChain(mockChain, transformers); Resource resource = getResource("external.css"); String expected = "@import url(\"http://example.org/fonts/css\");\n" + "body { background: url(\"file:///home/spring/image.png\") }\n" + "figure { background: url(\"//example.org/style.css\")}"; StepVerifier.create(chain.transform(exchange, resource) .cast(TransformedResource.class)) .consumeNextWith(transformedResource -> { String result = new String(transformedResource.getByteArray(), StandardCharsets.UTF_8); result = StringUtils.deleteAny(result, "\r"); assertEquals(expected, result); }) .expectComplete() .verify(); List<Resource> locations = Collections.singletonList(resource); Mockito.verify(mockChain, Mockito.never()).resolveUrlPath("http://example.org/fonts/css", locations); Mockito.verify(mockChain, Mockito.never()).resolveUrlPath("file:///home/spring/image.png", locations); Mockito.verify(mockChain, Mockito.never()).resolveUrlPath("//example.org/style.css", locations); }
@Test public void transformSkippedForNonCssResource() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/images/image.png")); Resource expected = getResource("images/image.png"); StepVerifier.create(this.transformerChain.transform(exchange, expected)) .expectNext(expected) .expectComplete() .verify(); }
@Test public void transformNoLinks() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/foo.css")); Resource expected = getResource("foo.css"); StepVerifier.create(this.transformerChain.transform(exchange, expected)) .consumeNextWith(resource -> assertSame(expected, resource)) .expectComplete().verify(); }
@Test public void transformSkippedForGzippedResource() throws Exception { EncodedResourceResolverTests.createGzippedFile("main.css"); MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/main.css")); Resource resource = getResource("main.css"); EncodedResource gzipped = new EncodedResource(resource, "gzip", ".gz"); StepVerifier.create(this.transformerChain.transform(exchange, gzipped)) .expectNext(gzipped) .expectComplete() .verify(); }
@Test public void transform() { MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/main.css")); Resource css = getResource("main.css"); String expected = "\n" + "@import url(\"/static/bar-11e16cf79faee7ac698c805cf28248d2.css?#iefix\");\n" + "@import url('/static/bar-11e16cf79faee7ac698c805cf28248d2.css#bla-normal');\n" + "@import url(/static/bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" + "@import \"/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" + "@import '/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" + "body { background: url(\"/static/images/image-f448cd1d5dba82b774f3202c878230b3.png?#iefix\") }\n"; StepVerifier.create(this.transformerChain.transform(exchange, css) .cast(TransformedResource.class)) .consumeNextWith(transformedResource -> { String result = new String(transformedResource.getByteArray(), StandardCharsets.UTF_8); result = StringUtils.deleteAny(result, "\r"); assertEquals(expected, result); }) .expectComplete() .verify(); }
@Override public Mono<Resource> transform(ServerWebExchange exchange, Resource resource, ResourceTransformerChain transformerChain) { Resource cachedResource = this.cache.get(resource, Resource.class); if (cachedResource != null) { if (logger.isTraceEnabled()) { logger.trace("Found match: " + cachedResource); } return Mono.just(cachedResource); } return transformerChain.transform(exchange, resource) .doOnNext(transformed -> { if (logger.isTraceEnabled()) { logger.trace("Putting transformed resource in cache: " + transformed); } this.cache.put(resource, transformed); }); }
@Override public Mono<Resource> transform(ServerWebExchange exchange, Resource inputResource, ResourceTransformerChain chain) { return chain.transform(exchange, inputResource) .then(resource -> { String name = resource.getFilename(); if (!this.fileExtension.equals(StringUtils.getFilenameExtension(name))) { return Mono.just(resource); } String content = new String(getResourceBytes(resource), DEFAULT_CHARSET); if (!content.startsWith(MANIFEST_HEADER)) { if (logger.isTraceEnabled()) { logger.trace("Manifest should start with 'CACHE MANIFEST', skip: " + resource); } return Mono.just(resource); } if (logger.isTraceEnabled()) { logger.trace("Transforming resource: " + resource); } return Flux.generate(new LineGenerator(content)) .concatMap(info -> processLine(info, exchange, resource, chain)) .collect(() -> new LineAggregator(resource, content), LineAggregator::add) .then(aggregator -> Mono.just(aggregator.createResource())); }); }
.then(resource -> { ResourceTransformerChain transformerChain = createTransformerChain(resolveChain); return transformerChain.transform(exchange, resource); });
ResourceTransformerChain transformerChain) { return transformerChain.transform(exchange, resource) .then(newResource -> { String filename = newResource.getFilename();