Skip to content

Commit

Permalink
refactor: redirect to original image if thumbnail is inaccessible (#6556
Browse files Browse the repository at this point in the history
)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.19.x

#### What this PR does / why we need it:
获取缩略图时检查缩略图链接是否可访问否则重定向到原图链接

#### Does this PR introduce a user-facing change?
```release-note
获取缩略图时检查缩略图链接是否可访问否则重定向到原图链接
```
  • Loading branch information
guqing authored Aug 30, 2024
1 parent ba49dca commit 9a0ebda
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import run.halo.app.extension.PageRequestImpl;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.exception.NotFoundException;

@Slf4j
@Component
Expand Down Expand Up @@ -76,7 +77,8 @@ static String getYear() {
@Override
public Mono<URI> getOriginalImageUri(URI thumbnailUri) {
return fetchThumbnail(thumbnailUri)
.map(local -> URI.create(local.getSpec().getImageUri()));
.map(local -> URI.create(local.getSpec().getImageUri()))
.switchIfEmpty(Mono.error(() -> new NotFoundException("Resource not found.")));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.io.IOException;
import java.net.URI;
import java.time.Instant;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.fn.builders.operation.Builder;
Expand All @@ -17,9 +18,11 @@
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
Expand All @@ -41,6 +44,7 @@
@Component
@RequiredArgsConstructor
public class ThumbnailEndpoint implements CustomEndpoint {
private final WebClient webClient = WebClient.builder().build();
private final LocalThumbnailService localThumbnailService;
private final WebProperties webProperties;
private final ThumbnailService thumbnailService;
Expand All @@ -63,8 +67,26 @@ public RouterFunction<ServerResponse> endpoint() {
private Mono<ServerResponse> getThumbnailByUri(ServerRequest request) {
var query = new ThumbnailQuery(request.queryParams());
return thumbnailService.generate(query.getUri(), query.getSize())
.filterWhen(uri -> isAccessible(request, uri))
.defaultIfEmpty(query.getUri())
.flatMap(uri -> ServerResponse.permanentRedirect(uri).build());
.flatMap(uri -> ServerResponse.temporaryRedirect(uri).build());
}

Mono<Boolean> isAccessible(ServerRequest request, URI uri) {
var url = Optional.of(uri)
.filter(URI::isAbsolute)
.orElseGet(() -> request.uriBuilder().replacePath(uri.toASCIIString()).build());
// resource handler does not support head access for Halo, so use get request here
return webClient.get()
.uri(url)
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
.header(HttpHeaders.RANGE, "bytes=0-0")
.exchangeToMono(response -> {
var statusCode = response.statusCode();
return Mono.just(statusCode.is2xxSuccessful() || statusCode.is3xxRedirection());
})
.onErrorReturn(false)
.defaultIfEmpty(false);
}

static class ThumbnailQuery {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package run.halo.app.theme.endpoint;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import java.net.URI;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import run.halo.app.core.attachment.ThumbnailService;

/**
* Tests for {@link ThumbnailEndpoint}.
*
* @author guqing
* @since 2.19.0
*/
@ExtendWith(MockitoExtension.class)
class ThumbnailEndpointTest {

WebTestClient webClient;

@Mock
private ThumbnailService thumbnailService;

@InjectMocks
private ThumbnailEndpoint endpoint;

@BeforeEach
void setUp() {
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint())
.build();
}

@Test
void thumbnailUriNotAccessible() {
when(thumbnailService.generate(any(), any()))
.thenReturn(Mono.just(URI.create("/thumbnail-not-found.png")));
webClient.get()
.uri("/thumbnails/-/via-uri?size=l&uri=/myavatar.png")
.exchange()
.expectAll(responseSpec -> responseSpec.expectHeader().location("/myavatar.png"))
.expectStatus()
.is3xxRedirection();
}
}

0 comments on commit 9a0ebda

Please sign in to comment.