• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tomdesair / tus-java-server / 28704942481

04 Jul 2026 11:35AM UTC coverage: 92.355%. First build
28704942481

Pull #105

github

web-flow
Merge 94b11f46d into e8bbe6ae5
Pull Request #105: feat: Implement IETF Resumable Uploads for HTTP (RUFH) Protocol

866 of 1022 branches covered (84.74%)

Branch coverage included in aggregate %.

443 of 496 new or added lines in 24 files covered. (89.31%)

2263 of 2366 relevant lines covered (95.65%)

6.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.5
/src/main/java/me/desair/tus/server/rufh/handler/RufhCreationPostRequestHandler.java
1
package me.desair.tus.server.rufh.handler;
2

3
import java.io.IOException;
4
import java.io.InputStream;
5
import java.util.LinkedHashMap;
6
import java.util.Map;
7
import me.desair.tus.server.HttpHeader;
8
import me.desair.tus.server.HttpMethod;
9
import me.desair.tus.server.exception.TusException;
10
import me.desair.tus.server.rufh.InterimResponseStrategy;
11
import me.desair.tus.server.upload.UploadInfo;
12
import me.desair.tus.server.upload.UploadLockingService;
13
import me.desair.tus.server.upload.UploadStorageService;
14
import me.desair.tus.server.util.AbstractRequestHandler;
15
import me.desair.tus.server.util.InterruptibleInputStream;
16
import me.desair.tus.server.util.StructuredHeaderUtil;
17
import me.desair.tus.server.util.TusServletRequest;
18
import me.desair.tus.server.util.TusServletResponse;
19

20
/**
21
 * Request handler for upload creation requests via HTTP POST, PUT, or PATCH.
22
 *
23
 * <p>Handles upload initialization, optional 104 interim responses, payload byte streaming, lock
24
 * registration via {@link InterruptibleInputStream}, and response headers.
25
 *
26
 * <p>Reference: Section 4.2 (Upload Creation) & Section 4.2.2 (Server Behavior) of
27
 * draft-ietf-httpbis-resumable-upload-11.
28
 */
29
public class RufhCreationPostRequestHandler extends AbstractRequestHandler {
30

31
  private final InterimResponseStrategy interimResponseStrategy;
32

33
  public RufhCreationPostRequestHandler(InterimResponseStrategy interimResponseStrategy) {
4✔
34
    this.interimResponseStrategy = interimResponseStrategy;
6✔
35
  }
2✔
36

37
  @Override
38
  public boolean supports(HttpMethod method) {
39
    return HttpMethod.POST.equals(method)
14✔
40
        || HttpMethod.PUT.equals(method)
8✔
41
        || HttpMethod.PATCH.equals(method);
8✔
42
  }
43

44
  @Override
45
  public void process(
46
      HttpMethod method,
47
      TusServletRequest servletRequest,
48
      TusServletResponse servletResponse,
49
      UploadStorageService uploadStorageService,
50
      String ownerKey)
51
      throws IOException, TusException {
NEW
52
    process(method, servletRequest, servletResponse, uploadStorageService, null, ownerKey);
×
NEW
53
  }
×
54

55
  @Override
56
  public void process(
57
      HttpMethod method,
58
      TusServletRequest servletRequest,
59
      TusServletResponse servletResponse,
60
      UploadStorageService uploadStorageService,
61
      UploadLockingService uploadLockingService,
62
      String ownerKey)
63
      throws IOException, TusException {
64

65
    if (HttpMethod.PATCH.equals(method)
8✔
66
        && isExistingUpload(servletRequest, uploadStorageService, ownerKey)) {
2!
67
      // Existing upload on PATCH request is handled by RufhAppendPatchRequestHandler
68
      return;
1✔
69
    }
70

71
    UploadInfo uploadInfo = new UploadInfo();
4✔
72
    String uploadLengthHeader = servletRequest.getHeader(HttpHeader.UPLOAD_LENGTH);
4✔
73
    Long uploadLength = StructuredHeaderUtil.parseInteger(uploadLengthHeader);
3✔
74
    if (uploadLength != null && uploadLength >= 0) {
7!
75
      uploadInfo.setLength(uploadLength);
3✔
76
    }
77

78
    String uploadCompleteHeader = servletRequest.getHeader(HttpHeader.UPLOAD_COMPLETE);
4✔
79
    Boolean uploadComplete = StructuredHeaderUtil.parseBoolean(uploadCompleteHeader);
3✔
80

81
    uploadInfo = uploadStorageService.create(uploadInfo, ownerKey);
5✔
82
    String uploadUri = getUploadUri(uploadInfo, servletRequest, uploadStorageService);
6✔
83

84
    if (interimResponseStrategy != null) {
3!
NEW
85
      interimResponseStrategy.sendInterimResponse(servletResponse, uploadUri, 0L);
×
86
    }
87

88
    InputStream is = servletRequest.getContentInputStream();
3✔
89
    if (is != null && servletRequest.getContentLengthLong() != 0) {
7!
90
      if (uploadLockingService != null) {
2!
91
        InterruptibleInputStream interruptibleStream = new InterruptibleInputStream(is);
5✔
92
        uploadLockingService.registerInputStream(uploadUri, interruptibleStream);
4✔
93
        is = interruptibleStream;
2✔
94
      }
95
      UploadInfo appended = uploadStorageService.append(uploadInfo, is);
5✔
96
      if (appended != null) {
2!
97
        uploadInfo = appended;
2✔
98
      }
99
    }
100

101
    servletResponse.setHeader(HttpHeader.UPLOAD_DRAFT, "11");
4✔
102

103
    boolean isFinished = Boolean.TRUE.equals(uploadComplete) || isUploadCompleted(uploadInfo);
12!
104
    if (isFinished) {
2✔
105
      uploadInfo.setLength(uploadInfo.getOffset());
4✔
106
      uploadStorageService.update(uploadInfo);
3✔
107
      servletResponse.setStatus(200);
3✔
108
      servletResponse.setHeader(HttpHeader.LOCATION, uploadUri);
4✔
109
      servletResponse.setHeader(
4✔
110
          HttpHeader.UPLOAD_COMPLETE, StructuredHeaderUtil.formatBoolean(true));
1✔
111
      servletResponse.setHeader(HttpHeader.UPLOAD_OFFSET, String.valueOf(uploadInfo.getOffset()));
7✔
112
    } else {
113
      servletResponse.setStatus(201);
3✔
114
      servletResponse.setHeader(HttpHeader.LOCATION, uploadUri);
4✔
115
      servletResponse.setHeader(
4✔
116
          HttpHeader.UPLOAD_COMPLETE, StructuredHeaderUtil.formatBoolean(false));
1✔
117
      servletResponse.setHeader(HttpHeader.UPLOAD_OFFSET, String.valueOf(uploadInfo.getOffset()));
6✔
118

119
      addUploadLimitHeader(servletResponse, uploadStorageService);
4✔
120
    }
121
  }
1✔
122

123
  private boolean isExistingUpload(
124
      TusServletRequest request, UploadStorageService uploadStorageService, String ownerKey)
125
      throws IOException {
126
    String requestUri = request.getRequestURI();
3✔
127
    return uploadStorageService.getUploadInfo(requestUri, ownerKey) != null;
8!
128
  }
129

130
  private boolean isUploadCompleted(UploadInfo uploadInfo) {
131
    return uploadInfo != null && !uploadInfo.isUploadInProgress();
7!
132
  }
133

134
  private String getUploadUri(
135
      UploadInfo uploadInfo,
136
      TusServletRequest servletRequest,
137
      UploadStorageService storageService) {
138
    String baseUri = storageService.getUploadUri();
3✔
139
    if (baseUri == null) {
2!
NEW
140
      baseUri = servletRequest.getRequestURI();
×
141
    }
142
    String idStr = uploadInfo.getId() != null ? uploadInfo.getId().toString() : "";
8!
143
    return baseUri + (baseUri.endsWith("/") ? "" : "/") + idStr;
9!
144
  }
145

146
  private void addUploadLimitHeader(
147
      TusServletResponse response, UploadStorageService uploadStorageService) {
148
    if (uploadStorageService == null) {
2!
NEW
149
      return;
×
150
    }
151
    Map<String, Object> limits = new LinkedHashMap<>();
4✔
152
    long maxSize = uploadStorageService.getMaxUploadSize();
3✔
153
    if (maxSize > 0) {
4!
NEW
154
      limits.put("max-size", maxSize);
×
155
    }
156
    Long maxAppendSize = uploadStorageService.getMaxAppendSize();
3✔
157
    if (maxAppendSize != null && maxAppendSize > 0) {
7!
NEW
158
      limits.put("max-append-size", maxAppendSize);
×
159
    }
160
    if (!limits.isEmpty()) {
3!
NEW
161
      response.setHeader(HttpHeader.UPLOAD_LIMIT, StructuredHeaderUtil.formatDictionary(limits));
×
162
    }
163
  }
1✔
164
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc