Giter Site home page Giter Site logo

tomdesair / tus-java-server Goto Github PK

View Code? Open in Web Editor NEW
121.0 8.0 58.0 669 KB

Library to receive tus v1.0.0 file uploads in a Java server environment

License: MIT License

Java 100.00%
java tus tus-protocol file-upload tomcat jboss jetty resumable-upload upload library

tus-java-server's People

Contributors

dependabot[bot] avatar edward-p avatar ghoshrahul avatar maxoid avatar nschwalbe avatar snyk-bot avatar tomdesair avatar zikani03 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tus-java-server's Issues

Response as JSON

I need your tus server to be able to return a json structure in the response. For example, I can perform response.setStatus(422) but along with that code I would like to send it a custom message. It's possible??

Java TUS Server returns Location without url hostname, which breaks flutter

As the title says, I've implemented the Java TUS Server sample, which upon uploading a file, returns a Location without the full-url, but just the endpoint, e.g. /api/uploads/random-file-upload-id

I just started looking into TUS, and not sure whether this is an issue on the server side or this flutter client but the client breaks with an exception saying that the Location arg. i.e. resource URL doesn't contain the hostname/base.

I've inspected the client, and fixed this with prepending the Location url with the server hostname, but as said, I'm not sure whether this is an issue on the server or client side impl.

Is there a way to set up the server to provide the full url for the resource?

My flutter dependencies:

  http: ^0.12.2
  tus_client: ^0.0.4

TUS Server dep:

        <dependency>
            <groupId>me.desair.tus</groupId>
            <artifactId>tus-java-server</artifactId>
            <version>1.0.0-2.0</version>
        </dependency>

How I temporarily fixed this is with providing a custom e.g. X-Location header param with the full uploaded resource url (containing server hostname), whcih I then use on the client.

Exception if cleanup called before any request handled

TusFileUploadService.cleanup()
calls
DiskStorageService.cleanupExpiredUploads(...)
and
DiskLockingService.cleanupStaleLocks()
Those methods throw "directory does not exist" exception if not a single upload was processed, because private method
AbstractDiskBasedService.init()
was never called.

How to fix:

Call
AbstractDiskBasedService.init()
inside
DiskStorageService.cleanupExpiredUploads(...)
and
DiskLockingService.cleanupStaleLocks()

Release latest changes

The latest released version of tus-java-server contains org.apache.commons.io:commons-io:2.6 as a dependency, which is vulnerable to CVE-2021-29425. I noticed commons-io was updated in #41, but this change was never released to Maven Central. Is it easy to get another release of tus-java-server with the latest changes? Thanks!

A base URI other than '/upload' causes a null pointer exception when calling getUploadInfo

Expected Behaviour

Ability to handle a TUS apload regardless of what the base URI is. For instance /api/upload causes this error but /upload works fine.

Actual Behaviour

UploadInfo is not captured from the request. Any attempts at using the TusFileUploadService to fetch (getUploadInfo) results in a null pointer exception.

Steps to reproduce

Create a basic implementation using a base URI other than simple 'upload'.
For example (in Spring):

@RequestMapping(value = { "/api/upload", "/api/upload/**" }, method = { RequestMethod.POST,
      RequestMethod.PATCH, RequestMethod.HEAD, RequestMethod.DELETE, RequestMethod.GET })
  public void upload(HttpServletRequest servletRequest,
      HttpServletResponse servletResponse) throws IOException {
    this.tusFileUploadService.process(servletRequest, servletResponse);

    String uploadURI = servletRequest.getRequestURI();

    UploadInfo uploadInfo = null;
    try {
      uploadInfo = this.tusFileUploadService.getUploadInfo(uploadURI);
      //this is null
    }
    catch (IOException | TusException e) {
      Application.logger.error("get upload info", e);
    }
}

Avoid file system locks in DiskStorageService

I have a question, what purposes of file system locks in DiskStorageService and what kind of risks if I override DiskStorageService implementation and remove this locking logic?

If we protect only from external invention when reading/writing can I provide pull request that make this behavior optional?

Thank you.

java17 support

Expected Behaviour

we cannot use in spring boot 3.x without java17 support

Actual Behaviour

(What faulty behaviour does the implementation have?)

Steps to reproduce

(Ideally, you specify detailed steps using the curl command.)

file getUploadedBytes UploadNotFoundException.

Expected Behaviour

To get the uploaded bytes using the tusFileUploadService.getUploadedBytes(String uploadUrl).

Actual Behaviour

I am uploading using this code in my controller:

@RequestMapping(value = "/api/upload")
//access Cross
@CrossOrigin(origins = "*")
public class TusController {
	@Autowired
	private TusFileUploadService tusFileUploadService;

	@Autowired
	HelperService helperService;

	@RequestMapping(value = {"", "/**"}, method = {RequestMethod.POST, RequestMethod.PATCH, RequestMethod.HEAD,
		RequestMethod.DELETE, RequestMethod.OPTIONS, RequestMethod.GET})
	public void processUpload(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse) throws IOException, TusException, InterruptedException {
		tusFileUploadService.process(servletRequest, servletResponse);
		//access response header Location,Upload-Offset,Upload-length
		servletResponse.addHeader("Access-Control-Expose-Headers","Location,Upload-Offset,Upload-Length");
	}
	
		@RequestMapping(method = RequestMethod.GET, value = "/getBytesTus")
	public String getUploadedBytes() throws TusException, IOException {
                System.out.println(tusFileUploadService.getUploadedBytes("/api/upload"));
		return "done";
	}
}

Here the upload url is "/api/upload" and when I try to get the uploaded bytes using the getUploadedBytes() api which uses the getUploadedBytes with the upload URI I get this error: Method threw 'me.desair.tus.server.exception.UploadNotFoundException' exception.

Method to add URL sent to Tus client?

I'm implementing this library to replace XHR Upload with the Uppy client.

My implementation is very similar to the demos provided:

    @RequestMapping(value = {"/tusUpload", "/tusUpload/**"}, method = {RequestMethod.POST,
            RequestMethod.PATCH, RequestMethod.HEAD, RequestMethod.DELETE, RequestMethod.GET})
    public void upload(HttpServletRequest servletRequest,
                       HttpServletResponse servletResponse) throws IOException {
        tusFileUploadService.process(servletRequest, servletResponse);
        String uploadURI = servletRequest.getRequestURI();

        UploadInfo uploadInfo = null;
        try {
            uploadInfo = tusFileUploadService.getUploadInfo(uploadURI);
        } catch (IOException | TusException e) {
            e.printStackTrace();
        }

        if (uploadInfo != null && !uploadInfo.isUploadInProgress()) {
            try (InputStream stream = tusFileUploadService.getUploadedBytes(uploadURI)) {
                String uploadURL = upload(new MockMultipartFile(uploadInfo.getFileName(), uploadInfo.getFileName(), uploadInfo.getFileMimeType(), IOUtils.toByteArray(stream))); // I know this is poor practice, just wanted to get something working first.
                System.out.println(response);
// what to do here to tell the Uppy client what URL it's uploaded at?
            } catch (IOException | TusException e) {
                e.printStackTrace();
            }
            try {
                tusFileUploadService.deleteUpload(uploadURI);
            } catch (IOException | TusException e) {
                e.printStackTrace();
            }
        }

My question is: What's the method (Is there one?) to tell the Uppy client the stored URL? Currently the client receives a URL, however it's not one that's hosted permanentlly (my uploadURL string is what I want the client to display).

URL the Uppy client displays for the "Copy Link" button and when clicking on the file's icon:
/tusUpload/f23a6d79-5bd8-4d50-8904-215752aaf483

Unexpected single newline character in chunk size, response head already sent

Hello,

We are facing an exception when trying to upload a file from tus/tus-java-client (demo code) to tomdesair/tus-java-server. The server is located within a Quarkus server. Both tus modules are on their latest versions. We successfully uploaded the same files using Uppy (from tomdesair/tus-java-server-spring-demo) on a web interface.

This exception is raised on the upload start:

java.io.IOException: Protocol violation: Unexpected single newline character in chunk size
    at me.desair.tus.server.util.HttpChunkedEncodingInputStream$ChunkSizeState$2.process(HttpChunkedEncodingInputStream.java:388)
    at me.desair.tus.server.util.HttpChunkedEncodingInputStream.readChunkSizeInformation(HttpChunkedEncodingInputStream.java:213)
    at me.desair.tus.server.util.HttpChunkedEncodingInputStream.getChunkSize(HttpChunkedEncodingInputStream.java:188)
    at me.desair.tus.server.util.HttpChunkedEncodingInputStream.nextChunk(HttpChunkedEncodingInputStream.java:163)
    at me.desair.tus.server.util.HttpChunkedEncodingInputStream.read(HttpChunkedEncodingInputStream.java:119)
    at org.apache.commons.io.input.ProxyInputStream.read(ProxyInputStream.java:102)
    at java.base/java.security.DigestInputStream.read(DigestInputStream.java:162)
    at java.base/java.security.DigestInputStream.read(DigestInputStream.java:162)
    at java.base/java.security.DigestInputStream.read(DigestInputStream.java:162)
    at java.base/java.security.DigestInputStream.read(DigestInputStream.java:162)
    at java.base/java.security.DigestInputStream.read(DigestInputStream.java:162)
    at java.base/java.nio.channels.Channels$ReadableByteChannelImpl.read(Channels.java:388)
    at java.base/sun.nio.ch.FileChannelImpl.transferFromArbitraryChannel(FileChannelImpl.java:746)
    at java.base/sun.nio.ch.FileChannelImpl.transferFrom(FileChannelImpl.java:782)
    at me.desair.tus.server.upload.disk.DiskStorageService.append(DiskStorageService.java:163)
    at me.desair.tus.server.upload.cache.ThreadLocalCachedStorageAndLockingService.append(ThreadLocalCachedStorageAndLockingService.java:89)
    at me.desair.tus.server.core.CorePatchRequestHandler.process(CorePatchRequestHandler.java:49)
    at me.desair.tus.server.util.AbstractTusExtension.process(AbstractTusExtension.java:49)
    at me.desair.tus.server.TusFileUploadService.executeProcessingByFeatures(TusFileUploadService.java:422)
    at XX.YY.ZZ.service.CustomTusService_Subclass.executeProcessingByFeatures$$superaccessor18(CustomTusService_Subclass.zig:3177)
    at XX.YY.ZZ.service.CustomTusService_Subclass$$function$$18.apply(CustomTusService_Subclass$$function$$18.zig:53)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at XX.YY.ZZ.service.CustomTusService_Subclass.executeProcessingByFeatures(CustomTusService_Subclass.zig:3110)
    at me.desair.tus.server.TusFileUploadService.processLockedRequest(TusFileUploadService.java:408)
    at XX.YY.ZZ.service.CustomTusService_Subclass.processLockedRequest$$superaccessor11(CustomTusService_Subclass.zig:2324)
    at XX.YY.ZZ.service.CustomTusService_Subclass$$function$$11.apply(CustomTusService_Subclass$$function$$11.zig:53)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at XX.YY.ZZ.service.CustomTusService_Subclass.processLockedRequest(CustomTusService_Subclass.zig:2266)
    at me.desair.tus.server.TusFileUploadService.process(TusFileUploadService.java:301)
    at XX.YY.ZZ.service.CustomTusService_Subclass.process$$superaccessor20(CustomTusService_Subclass.zig:3456)
    at XX.YY.ZZ.service.CustomTusService_Subclass$$function$$20.apply(CustomTusService_Subclass$$function$$20.zig:47)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at XX.YY.ZZ.service.CustomTusService_Subclass.process(CustomTusService_Subclass.zig:3400)
    at me.desair.tus.server.TusFileUploadService.process(TusFileUploadService.java:274)
    at XX.YY.ZZ.service.CustomTusService_Subclass.process$$superaccessor28(CustomTusService_Subclass.zig:4466)
    at XX.YY.ZZ.service.CustomTusService_Subclass$$function$$28.apply(CustomTusService_Subclass$$function$$28.zig:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at XX.YY.ZZ.service.CustomTusService_Subclass.process(CustomTusService_Subclass.zig:4412)
    at XX.YY.ZZ.service.CustomTusService_ClientProxy.process(CustomTusService_ClientProxy.zig:687)
    at XX.YY.ZZ.controller.upload.Upload.processUpload(Upload.java:53)
    at XX.YY.ZZ.controller.upload.Upload_Subclass.processUpload$$superaccessor3(Upload_Subclass.zig:557)
    at XX.YY.ZZ.controller.upload.Upload_Subclass$$function$$3.apply(Upload_Subclass$$function$$3.zig:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at XX.YY.ZZ.controller.upload.Upload_Subclass.processUpload(Upload_Subclass.zig:503)
    at XX.YY.ZZ.controller.upload.Upload.process2Upload(Upload.java:64)
    at XX.YY.ZZ.controller.upload.Upload_Subclass.process2Upload$$superaccessor4(Upload_Subclass.zig:688)
    at XX.YY.ZZ.controller.upload.Upload_Subclass$$function$$4.apply(Upload_Subclass$$function$$4.zig:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
    at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
    at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
    at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
    at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
    at XX.YY.ZZ.controller.upload.Upload_Subclass.process2Upload(Upload_Subclass.zig:634)
    at XX.YY.ZZ.controller.upload.Upload_ClientProxy.process2Upload(Upload_ClientProxy.zig:136)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
    at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:643)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:507)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:457)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:459)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:419)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:393)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:68)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
    at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:35)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:63)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:67)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:133)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:65)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:247)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:56)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:111)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:108)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$9$1.call(UndertowDeploymentRecorder.java:587)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:227)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:152)
    at io.undertow.server.handlers.PathHandler.handleRequest(PathHandler.java:91)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$1.handleRequest(UndertowDeploymentRecorder.java:119)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:290)
    at io.undertow.server.DefaultExchangeHandler.handle(DefaultExchangeHandler.java:18)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$5$1.run(UndertowDeploymentRecorder.java:413)
    at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:231)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.base/java.lang.Thread.run(Thread.java:831)
    at org.jboss.threads.JBossThread.run(JBossThread.java:501)

Followed by this exception:

2021-04-30 11:37:56,339 ERROR [io.und.req.io] (executor-thread-1) Exception handling request 697b1502-90e6-4dc0-9a44-61f8a78dedcb-1 to /test/api/upload/163b7706-b869-4ef7-ba3e-06a55d200d50: org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalStateException: Response head already sent
    at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:230)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:519)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
    at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:35)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:63)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:67)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:133)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:65)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:247)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:56)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:111)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:108)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$9$1.call(UndertowDeploymentRecorder.java:587)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:227)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:152)
    at io.undertow.server.handlers.PathHandler.handleRequest(PathHandler.java:91)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$1.handleRequest(UndertowDeploymentRecorder.java:119)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:290)
    at io.undertow.server.DefaultExchangeHandler.handle(DefaultExchangeHandler.java:18)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$5$1.run(UndertowDeploymentRecorder.java:413)
    at io.quarkus.runtime.CleanableExecutor$CleaningRunnable.run(CleanableExecutor.java:231)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.base/java.lang.Thread.run(Thread.java:831)
    at org.jboss.threads.JBossThread.run(JBossThread.java:501)

The upload then fail on the client at different percentages of completion.

Thanks for your help

Cannot resume upload after server unexpected shutdown

Expected Behaviour

I was trying the https://github.com/tomdesair/tus-java-server-spring-demo with a simple Java Client (based on official tus-java-client)
If I kill the client and resume the upload everything is just fine.
I would expect the same when I kill the server with pending uploads.

Actual Behaviour

After a unexpected shutdown (CTRL+C or power loss) of the tus-java-server, I could not resume the upload that was in progress.
I shutdown the server during a big (1gb) upload.
Then I restarted the server and tried to run the client again. And the server can not handle this upload anymore. (I put the stacktrace and java client at the end)

If I try this same scenario with tusd (official server), everything works as expected (I can resume the upload)

I have the following stacktrace

java.io.EOFException: null
	at java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2681) ~[na:1.8.0_221]
	at java.io.ObjectInputStream$BlockDataInputStream.readShort(ObjectInputStream.java:3156) ~[na:1.8.0_221]
	at java.io.ObjectInputStream.readStreamHeader(ObjectInputStream.java:862) ~[na:1.8.0_221]
	at java.io.ObjectInputStream.<init>(ObjectInputStream.java:358) ~[na:1.8.0_221]
	at me.desair.tus.server.util.Utils.readSerializable(Utils.java:86) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.upload.disk.DiskStorageService.getUploadInfo(DiskStorageService.java:95) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.upload.cache.ThreadLocalCachedStorageAndLockingService.getUploadInfo(ThreadLocalCachedStorageAndLockingService.java:53) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.upload.cache.ThreadLocalCachedStorageAndLockingService.getUploadInfo(ThreadLocalCachedStorageAndLockingService.java:61) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.core.validation.IdExistsValidator.validate(IdExistsValidator.java:24) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.util.AbstractTusExtension.validate(AbstractTusExtension.java:37) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.TusFileUploadService.validateRequest(TusFileUploadService.java:431) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.TusFileUploadService.processLockedRequest(TusFileUploadService.java:406) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.TusFileUploadService.process(TusFileUploadService.java:301) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.tus.server.TusFileUploadService.process(TusFileUploadService.java:274) ~[tus-java-server-1.0.0-2.0.jar!/:na]
	at me.desair.spring.tus.FileUploadController.processUpload(FileUploadController.java:24) ~[classes!/:0.0.1-SNAPSHOT]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_221]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_221]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_221]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_221]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:849) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:760) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at javax.servlet.http.HttpServlet.doHead(HttpServlet.java:245) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) ~[spring-webmvc-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.21.RELEASE.jar!/:4.3.21.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_221]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_221]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.35.jar!/:8.5.35]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_221]

Steps to reproduce (you have to put the path of a big file)

I start the server demo
I run this java client
I kill server
I kill client
I start the server demo
I run this java client
(I got error above)

public class Client {

  public static void main(String args[]) throws IOException, ProtocolException {
    Path testFile = Paths.get("/path/to/big/file");
    // Upload file to server
    var client = new TusClient();
    client.setUploadCreationURL(URI.create("http://localhost:8080/test/api/upload").toURL());
    client.enableResuming(new MyTusURLPropertiesStore("./fingerprints.properties"));
    TusUpload upload = new TusUpload(testFile.toFile());
    var executor = new TusExecutor() {
      @Override
      protected void makeAttempt() throws ProtocolException, IOException {
        TusUploader uploader = client.resumeOrCreateUpload(upload);
        uploader.setChunkSize(1024);
        do {
          long totalBytes = upload.getSize();
          long bytesUploaded = uploader.getOffset();
          double progress = (double) bytesUploaded / totalBytes * 100;
          System.out.printf("Upload at %6.2f %%.\n", progress);
        } while (uploader.uploadChunk() > -1);
        uploader.finish();
      }
    };
    System.out.println(executor.makeAttempts() ? "Upload successful" :  "Upload interrupted");
  }
}

class MyTusURLPropertiesStore implements TusURLStore {

  private final Path arquivoConfig;
  String propertiesFile;

  Properties properties;

  public MyTusURLPropertiesStore(String propertiesFile) throws IOException {
    this.propertiesFile = propertiesFile;
    arquivoConfig = Paths.get(propertiesFile);
    if (!Files.exists(arquivoConfig)) {
      Files.createDirectories(arquivoConfig.getParent());
      Files.createFile(arquivoConfig);
    }
    try (InputStream inputStream = Files.newInputStream(arquivoConfig)) {
      this.properties = new Properties();
      this.properties.load(inputStream);
    }
  }

  private void gravar() {
    try (OutputStream outputStream = Files.newOutputStream(arquivoConfig)) {
      this.properties.store(outputStream, null);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  @Override
  public void set(String fingerprint, URL url) {
    try {
      this.properties.put(fingerprint, url.toURI().toASCIIString());
    } catch (URISyntaxException e) {
      e.printStackTrace();
    }
    gravar();
  }

  @Override
  public URL get(String fingerprint) {
    Object o = this.properties.get(fingerprint);
    if (o != null) {
      try {
        return URI.create((String) o).toURL();
      } catch (MalformedURLException e) {
        e.printStackTrace();
      }
    }
    return null;
  }

  @Override
  public void remove(String fingerprint) {
    this.properties.remove(fingerprint);
    gravar();
  }
}

JDK11 support

In JDK11 the javax.xml.bind package was removed. You use the javax.xml.bind.DatatypeConverter for Base64 encoding. Maybe you can replace this with java.util.Base64 from Java 8 or other implementation?

Reverse proxy

Does this work with Nginx reverse proxy? How do I set it up?

John

Providing our own error message

Expected Behaviour

When a call exceeds the defined MaxUploadSize we would like to provide our own error response, so that it matches our other responses made to the frontend.

Actual Behaviour

A (correct) 413 is returned with a predefined error message.

Question

Is there a way to define/use our own response in this case?

Undertow throws exception when TUS tries to respond with 409 or some other error status

CoreDefaultResponseHeadersHandler always writes header Content-Length=0
If some error detected, like incorrect offset, TUS calls HttpServletResponse.sendError() with some message. In other words there is nonempty response with header Content-Length=0. Undertow throws exception, probably because it allocates output buffer based on Content-Length header.

How to fix:

Remove setting Content-Length=0 from CoreDefaultResponseHeadersHandler.
Or overwrite Content-Length header before calling HttpServletResponse.sendError()

Possible to add a body in the response ?

Hi thanks for a great lib. Im using it and its working fine. BUt i need some data to be returned in the body. Is there a way to modify the response? because i tried to modify the HttpServletResponse and that doest work. Thanks for help

Unexpected single newline character in chunk size

Expected Behaviour

(Please describe the behaviour you expected to see. If possible please refer to paragraphs of the official tus protocol specification on https://tus.io/.)

upload failed with exception

Actual Behaviour

(What faulty behaviour does the implementation have?)

java.io.IOException: Protocol violation: Unexpected single newline character in chunk size at me.desair.tus.server.util.HttpChunkedEncodingInputStream$ChunkSizeState$2.process(HttpChunkedEncodingInputStream.java:389) at me.desair.tus.server.util.HttpChunkedEncodingInputStream.readChunkSizeInformation(HttpChunkedEncodingInputStream.java:214) at me.desair.tus.server.util.HttpChunkedEncodingInputStream.getChunkSize(HttpChunkedEncodingInputStream.java:189) at me.desair.tus.server.util.HttpChunkedEncodingInputStream.nextChunk(HttpChunkedEncodingInputStream.java:164) at me.desair.tus.server.util.HttpChunkedEncodingInputStream.read(HttpChunkedEncodingInputStream.java:120) at org.apache.commons.io.input.ProxyInputStream.read(ProxyInputStream.java:99) at java.security.DigestInputStream.read(DigestInputStream.java:161) at java.security.DigestInputStream.read(DigestInputStream.java:161) at java.security.DigestInputStream.read(DigestInputStream.java:161) at java.security.DigestInputStream.read(DigestInputStream.java:161) at java.security.DigestInputStream.read(DigestInputStream.java:161) at java.nio.channels.Channels$ReadableByteChannelImpl.read(Channels.java:385) at sun.nio.ch.FileChannelImpl.transferFromArbitraryChannel(FileChannelImpl.java:673) at sun.nio.ch.FileChannelImpl.transferFrom(FileChannelImpl.java:711) at me.desair.tus.server.upload.disk.DiskStorageService.append(DiskStorageService.java:158) at me.desair.tus.server.core.CorePatchRequestHandler.process(CorePatchRequestHandler.java:49) at me.desair.tus.server.util.AbstractTusExtension.process(AbstractTusExtension.java:49) at me.desair.tus.server.TusFileUploadService.executeProcessingByFeatures(TusFileUploadService.java:293) at me.desair.tus.server.TusFileUploadService.processLockedRequest(TusFileUploadService.java:279) at me.desair.tus.server.TusFileUploadService.process(TusFileUploadService.java:172) at me.desair.tus.server.TusFileUploadService.process(TusFileUploadService.java:155) at com.acmedcare.tiffany.framework.nas.rest.server.NasServerBootstrap$FileUploadController.processUpload(NasServerBootstrap.java:112) at sun.reflect.GeneratedMethodAccessor28.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877) at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)

Steps to reproduce

(Ideally, you specify detailed steps using the curl command.)

Use tus-java-client Demo Code

// Create a new TusClient instance
TusClient client = new TusClient();

// Configure tus HTTP endpoint. This URL will be used for creating new uploads
// using the Creation extension
client.setUploadCreationURL(new URL("https://master.tus.io/files"));

// Enable resumable uploads by storing the upload URL in memory
client.enableResuming(new TusURLMemoryStore());

// Open a file using which we will then create a TusUpload. If you do not have
// a File object, you can manually construct a TusUpload using an InputStream.
// See the documentation for more information.
File file = new File("./cute_kitten.png");
final TusUpload upload = new TusUpload(file);

System.out.println("Starting upload...");

// We wrap our uploading code in the TusExecutor class which will automatically catch
// exceptions and issue retries with small delays between them and take fully
// advantage of tus' resumability to offer more reliability.
// This step is optional but highly recommended.
TusExecutor executor = new TusExecutor() {
    @Override
    protected void makeAttempt() throws ProtocolException, IOException {
        // First try to resume an upload. If that's not possible we will create a new
        // upload and get a TusUploader in return. This class is responsible for opening
        // a connection to the remote server and doing the uploading.
        TusUploader uploader = client.resumeOrCreateUpload(upload);
        
        // Alternatively, if your tus server does not support the Creation extension
        // and you obtained an upload URL from another service, you can instruct
        // tus-java-client to upload to a specific URL. Please note that this is usually
        // _not_ necessary and only if the tus server does not support the Creation
        // extension. The Vimeo API would be an example where this method is needed.
        // TusUploader uploader = client.beginOrResumeUploadFromURL(upload, new URL("https://tus.server.net/files/my_file"));

        // Upload the file in chunks of 1KB sizes.
        uploader.setChunkSize(1024);

        // Upload the file as long as data is available. Once the
        // file has been fully uploaded the method will return -1
        do {
            // Calculate the progress using the total size of the uploading file and
            // the current offset.
            long totalBytes = upload.getSize();
            long bytesUploaded = uploader.getOffset();
            double progress = (double) bytesUploaded / totalBytes * 100;

            System.out.printf("Upload at %06.2f%%.\n", progress);
        } while(uploader.uploadChunk() > -1);

        // Allow the HTTP connection to be closed and cleaned up
        uploader.finish();

        System.out.println("Upload finished.");
        System.out.format("Upload available at: %s", uploader.getUploadURL().toString());
    }
};
executor.makeAttempts();

Error when upload file to tus on eureka gateway

When I integrate my file service into a eureka gateway, I meet a error when I try to upload a file via request /upload
Meet a error :
me.desair.tus.server.TusFileUploadService: Unable to process request POST http://ip:port/upload. Sent response status 405 with message "POST requests have to be send to /api/file/upload"
/api/file is the prefix of this service in my gateway

Dynamic upload path

Expected Behaviour

It would be nice if the uploadURI can contain some path variables because I need some extra information to the file upload and it fits better to my REST URIs than putting it into the Upload-Metadata.
E.g. /users/{userId}/files/upload

Actual Behaviour

POST /users/123/files/upload
returns 405 with message: POST requests have to be send to /users/{userId}/files/upload

how can i manage the content stored on server side

thank u for sharing such a good project, I found the uploaded content is stored at uploads folder of a name like uuid. But how can I get the list of the data information, because I may want to manually delete some content like old images or files. is there an API i can get them?

UploadStorageService - Files in the disk and Info in the database

Expected Behaviour

Hi Tom, thanks for the great work.. the library works great for our file transfer use case.

We are currently using springframework.. and we have the below requirements

  1. Store the files in a particular hierarchy - /uploads/folder1/folder2/filename.zip
  2. Store the UploadInfo in the database instead of local folder
  3. provide the filereference directly to UploadIdFactory instead of parsing from the URL (spring already do this)

Can you provide guidelines on how to implement the same. We can develop the same and share.

[Bug]uploadInfo.isUploadInProgress() always be true even the uploading has finished

@controller
@RequestMapping("/upload")
public class FileUploadController {
@value("${tus.server.data.directory}")
protected String tusDataPath;

@Autowired
private TusFileUploadService tusFileUploadService;

@Autowired
private FileUploadService fileUploadService;


//@CrossOrigin(origins = "http://127.0.0.1:8080",exposedHeaders = {"Location","Upload-Offset","Upload-Length"})
@RequestMapping(value = {"", "/**"}, method = {RequestMethod.POST, RequestMethod.PATCH, RequestMethod.HEAD,
        RequestMethod.DELETE, RequestMethod.OPTIONS, RequestMethod.GET})
public void processUpload(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse)  {
   // tusFileUploadService.withUploadURI(servletRequest.getContextPath() + "/upload");
    try {
        tusFileUploadService.process(servletRequest, servletResponse);
        String hLocation = servletResponse.getHeader(HttpHeader.LOCATION);
        if(hLocation!=null){
            while(true){
                UploadInfo ui = tusFileUploadService.getUploadInfo(hLocation);
                if(ui!=null && !ui.isUploadInProgress()) {
                    InputStream uploadedBytes = tusFileUploadService.getUploadedBytes(hLocation);
                    ///////////////////////////// others code here
                    break;
                }
            }
        }

    } catch (IOException | TusException e) {
        e.printStackTrace();
    }
}

}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.