mirror of https://github.com/halo-dev/halo
refactor: redirect to original image if thumbnail is inaccessible (#6556)
#### 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 获取缩略图时检查缩略图链接是否可访问否则重定向到原图链接 ```pull/6563/head v2.19.0-rc.4
parent
ba49dcab35
commit
9a0ebdad25
|
@ -45,6 +45,7 @@ import run.halo.app.extension.Metadata;
|
|||
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
|
||||
|
@ -76,7 +77,8 @@ public class LocalThumbnailServiceImpl implements LocalThumbnailService {
|
|||
@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
|
||||
|
|
|
@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
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;
|
||||
|
@ -17,9 +18,11 @@ import org.springframework.boot.autoconfigure.web.WebProperties;
|
|||
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;
|
||||
|
@ -41,6 +44,7 @@ import run.halo.app.extension.GroupVersion;
|
|||
@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;
|
||||
|
@ -63,8 +67,26 @@ public class ThumbnailEndpoint implements CustomEndpoint {
|
|||
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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue