Skip to content

TUS1.0.0文件传输协议兼容性补丁 #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ public Mono<ResponseEntity<Object>> header(@NonNull @PathVariable("id") final Lo
parameters = {@Parameter(in = ParameterIn.PATH, name = "id",
required = true, description = "上传工作单元的ID", schema = @Schema(type = "string", format = "SnowflakeID")),
@Parameter(name = "Upload-Offset", in = ParameterIn.HEADER, required = true, schema = @Schema(type = "integer")),
@Parameter(name = "Content-Length", in = ParameterIn.HEADER, required = true, schema = @Schema(type = "integer")),
@Parameter(name = "Content-Type", example = "application/offset+octet-stream", required = true, schema = @Schema(type = "string"))})
@Parameter(name = "Content-Type", in = ParameterIn.HEADER, required = true, example = "application/offset+octet-stream", schema = @Schema(type = "string"))})
@RequestMapping(
method = {RequestMethod.POST, RequestMethod.PATCH,},
value = {"/{id}"},
Expand All @@ -195,16 +194,15 @@ public Mono<ResponseEntity<Object>> header(@NonNull @PathVariable("id") final Lo
public Mono<ResponseEntity<Object>> uploadProcess(
@NonNull @PathVariable("id") final Long id,
@NonNull final ServerHttpRequest request,
@RequestHeader(name = "Upload-Offset") final long offset,
@RequestHeader(name = "Content-Length") final long length
@RequestHeader(name = "Upload-Offset") final long offset
) {
request.getHeaders().forEach((k, v) -> log.debug("headers: {} {}", k, v));

log.debug("4 PATCH START");
log.debug("SnowflakeID value: " + id);

return uploadService
.uploadChunkAndGetUpdatedOffset(id, request.getBody(), offset, length)
.appendFileContent(id, request.getBody(), offset)
.log()
.map(e -> ResponseEntity
.status(NO_CONTENT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,43 @@ public Mono<ServerResponse> handle(ServerRequest request) {
String uploadId = request.pathVariable("uploadId");

return fileRepository.findById(Long.parseLong(uploadId))
.flatMap(e ->
ServerResponse.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + new String(e.getOriginalName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1))
.flatMap(e -> {
String originalFileName = e.getOriginalName();
String utf8EscapedFileName = escapeUtf8FileName(originalFileName);
String attachment = String.format("attachment; filename=\"%s\"; filename*=utf-8''\"%s\"", utf8EscapedFileName, utf8EscapedFileName);
return ServerResponse.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, attachment)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body((p, a) -> p.writeWith(DataBufferUtils.read(Paths.get(fileDirectory.toString(), uploadId), new DefaultDataBufferFactory(), 4096)))
.doOnNext(a -> log.info("Download file ID: {}", uploadId))
.doOnNext(a -> log.info("Download file ID: {}", uploadId));
}
)
.switchIfEmpty(ServerResponse.notFound().build());


}

private static String escapeUtf8FileName(String originalFileName) {
byte[] utf8 = originalFileName.getBytes(StandardCharsets.UTF_8);
StringBuilder strBuilder = new StringBuilder();
for (byte b: utf8) {
char ch = (char)b;
if ((ch >= '0' && ch <='9') ||
(ch >= 'a' && ch <='z') ||
(ch >= 'A' && ch <='Z') ||
ch == '-' ||
ch == '_' ||
ch == '.'
) {
strBuilder.append(ch);
continue;
} else if ((b >= 0x00 && b <= 0x1F) || b == 0x7F) {
strBuilder.append('_');
continue;
}
// RFC5987 规范: 对 URL 中的 UTF-8 文本进行转义编码
strBuilder.append(String.format("%%%02X", b));
}
return strBuilder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,11 @@ public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
rogueRequest = true;
}

if (!contentLength.isPresent()) {
rogueRequest = true;
}

if (rogueRequest) {
return ServerResponse.badRequest().build();
}

return uploadService.uploadChunkAndGetUpdatedOffset(Long.valueOf(uploadId),parts,offset.get(),contentLength.get())
return uploadService.appendFileContent(Long.valueOf(uploadId),parts,offset.orElseGet(() -> 0L))
.log()
.flatMap(r -> ServerResponse
.noContent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ public class DownloadRouter {
@Bean
public RouterFunction<ServerResponse> route() {
return RouterFunctions
.route(GET("/download/{uploadId}").and(accept(MediaType.APPLICATION_JSON)), downloadHandler);
.route(GET("/download/{uploadId}"), downloadHandler);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,42 +84,36 @@ public Mono<File> mergePartialUploads(Long[] extractPartialUploadIds, Optional<S

}

public Mono<File> uploadChunkAndGetUpdatedOffset(
public Mono<File> appendFileContent(
final Long id,
final Flux<DataBuffer> parts,
final long offset,
final long length
final long offset
) {

Mono<File> fileOne = fileRepository.findById(id)
.switchIfEmpty(Mono.error(new GlobalException(HttpStatus.INTERNAL_SERVER_ERROR, "File record not found.")))
.map(e -> this.isValid(e, offset, length));
.map(this::fileEntityIsValid);
return Mono
.zip(fileOne,fileStorage.writeChunk(id, parts, offset))
.flatMap((Tuple2<File, Integer> data) -> this.save(data.getT1(),data.getT2()));
.flatMap((Tuple2<File, Integer> tuple) -> {
File file = tuple.getT1();
Integer nBytesAppended = tuple.getT2();

long contentOffset = file.getContentOffset();
log.info("[OLD OFFSET] {}", contentOffset);
contentOffset += nBytesAppended;
log.info("[OFFSET] {}", contentOffset);
file.setContentOffset(contentOffset);
file.setLastUploadedChunkNumber(file.getLastUploadedChunkNumber() + 1);
log.debug("File patching: {}", file);
return fileRepository.save(file);
});
}

public Mono<File> save(File file,Integer offset) {
log.info("[OLD OFFSET] {}", file.getContentOffset());
log.info("[OFFSET] {}", file.getContentOffset() + offset);
file.setContentOffset(file.getContentOffset() + offset);
file.setLastUploadedChunkNumber(file.getLastUploadedChunkNumber() + 1);
log.debug("File patching: {}", file);
return fileRepository.save(file);
}

private File isValid(File file, long offset, long length) {
if (offset != file.getContentOffset() && checkContentLengthWithCurrentOffset(length, offset, file.getContentLength())) {
throw new GlobalException(HttpStatus.INTERNAL_SERVER_ERROR, "Offset mismatch.");
}
private File fileEntityIsValid(File file) {
if (uploadExpiredUtils.checkUploadExpired(file)) {
throw new GlobalException(HttpStatus.INTERNAL_SERVER_ERROR, "Upload Expires.");
}
return file;
}

private boolean checkContentLengthWithCurrentOffset(long contentLength, long offset, long entityLength) {
return contentLength + offset <= entityLength;
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package priv.dino.tus.server.manage.controllers;
package priv.dino.tus.server.manage.controller;

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import priv.dino.tus.server.core.configuration.properties.TusServerProperties;
import priv.dino.tus.server.core.util.UploadExpiredUtils;
import priv.dino.tus.server.manage.controller.UploadController;
import priv.dino.tus.server.manage.domain.File;
import priv.dino.tus.server.manage.repository.FileRepository;
import priv.dino.tus.server.manage.service.UploadService;
Expand Down Expand Up @@ -146,12 +145,12 @@ void uploadProcess() {
put("test", Collections.singletonList("test"));
}});
Mockito
.when(uploadService.uploadChunkAndGetUpdatedOffset(1L, body, 0, 3))
.when(uploadService.appendFileContent(1L, body, 0))
.thenReturn(Mono.just(File.builder().contentOffset(3L).build()));


final UploadController uploadController = new UploadController(filesRepository, tusServerProperties, uploadService, uploadExpiredUtils);
uploadController.uploadProcess(1L, request, 0, 3)
uploadController.uploadProcess(1L, request, 0)
.subscribe(v -> {
assertEquals(NO_CONTENT, v.getStatusCode());
assertEquals("3", Objects.requireNonNull(v.getHeaders().get("Upload-Offset")).get(0));
Expand Down