Skip to content

attempt to eliminate the first byte stream corruption in the EntityInputStream#isEmpty method #5806

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 2 commits into
base: 3.1
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
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.servlet;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import org.glassfish.jersey.innate.io.ExternalStreamListener;
import org.glassfish.jersey.innate.io.ExternalStreamWrapper;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

abstract class ServletEntityInputStream implements ExternalStreamWrapper {

private boolean waitForInputEnable;
private long waitForInputTimeOut;

private final ExternalStreamListener listener = new ExternalStreamListener() {

@Override
public boolean isEmpty() {
try {
return getWrappedStream().available() == 0
|| getWrappedStream().isFinished();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Override
public boolean isReady() {
return processReadiness();
}

boolean processReadiness() {

final AtomicBoolean ready = new AtomicBoolean(getWrappedStream().isReady());
if (waitForInputEnable && !ready.get()) {

final CountDownLatch latch = new CountDownLatch(1);

getWrappedStream().setReadListener(new ReadListener() {
@Override
public void onDataAvailable() {
ready.set(true);
latch.countDown();
}

@Override
public void onAllDataRead() {
ready.set(false);
latch.countDown();
}

@Override
public void onError(Throwable t) {
ready.set(false);
latch.countDown();
}

});
if (!ready.get()) {
try {
latch.await(waitForInputTimeOut, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
ready.set(getWrappedStream().isReady());
}
}
}
return ready.get() || getWrappedStream().isReady();
}

};

public ServletEntityInputStream(boolean waitForInputEnable, long waitForInputTimeOut) {
this.waitForInputEnable = waitForInputEnable;
this.waitForInputTimeOut = waitForInputTimeOut;
}

protected abstract ServletInputStream getWrappedStream();

@Override
public ExternalStreamListener getListener() {
return listener;
}

@Override
public InputStream getExternalStream() {
return getWrappedStream();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2025 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -138,6 +138,30 @@ public final class ServletProperties {
*/
public static final String QUERY_PARAMS_AS_FORM_PARAMS_DISABLED = "jersey.config.servlet.form.queryParams.disabled";

/**
* Indicates if any input stream is expected and shall be waited while using POST method. It can happen that a delay
* appear between the time the request is received and the time the input stream is available. In this case, the
* server will wait for the input stream to be available before processing the request.
*
* @since 3.1.11
*/
public static final String WAIT_FOR_INPUT = "jersey.config.servlet.expect.input";

/**
* Timeout which shall be respected while waiting for the input stream to be available.
* The timeout is in milliseconds.
*
* @since 3.1.11
*/
public static final String WAIT_FOR_INPUT_TIMEOUT = "jersey.config.servlet.input.timeout";

/**
* Default timeout which shall be respected while waiting for the input stream to be available.
*
* @since 3.1.11
*/
public static final Long WAIT_FOR_INPUT_DEFAULT_TIMEOUT = 100L;

/**
* Identifies the object that will be used as a parent {@code HK2 ServiceLocator} in the Jersey
* {@link WebComponent}.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2025 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -17,7 +17,6 @@
package org.glassfish.jersey.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.reflect.Type;
import java.net.URI;
Expand All @@ -38,6 +37,7 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;

import jakarta.servlet.ServletInputStream;
import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.GenericType;
Expand All @@ -56,7 +56,6 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.glassfish.jersey.innate.io.InputStreamWrapper;
import org.glassfish.jersey.internal.ServiceFinderBinder;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
Expand Down Expand Up @@ -423,20 +422,20 @@ private void initContainerRequest(
final HttpServletResponse servletResponse,
final ResponseWriter responseWriter) throws IOException {

try {
requestContext.setEntityStream(new InputStreamWrapper() {
@Override
protected InputStream getWrapped() {
try {
return servletRequest.getInputStream();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
boolean waitForInputEnable = requestContext.resolveProperty(ServletProperties.WAIT_FOR_INPUT, Boolean.TRUE);
long waitForInputTimeOut = requestContext.resolveProperty(ServletProperties.WAIT_FOR_INPUT_TIMEOUT,
ServletProperties.WAIT_FOR_INPUT_DEFAULT_TIMEOUT);
requestContext.wrapEntityInputStream(new ServletEntityInputStream(waitForInputEnable,
waitForInputTimeOut) {
@Override
protected ServletInputStream getWrappedStream() {
try {
return servletRequest.getInputStream();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
});

requestContext.setRequestScopedInitializer(requestScopedInitializer.get(new RequestContextProvider() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.innate.io;

import org.glassfish.jersey.message.internal.EntityInputStream;

import java.util.EventListener;

/**
* Provides possibility to externally check whether an input stream for an entity is empty or not.
* <p>
* Is being used in the {@link EntityInputStream#isEmpty()} check
* </p>
*/
public interface ExternalStreamListener extends EventListener {

/**
* Provides information if the underlying stream is empty
*
* @return true if the underlying stream is empty
*/
boolean isEmpty();

/**
* Can be used to provide readiness information.
* <p>
* If the stream is not ready the calling check in the {@link EntityInputStream#isEmpty()} method will validate
* the underlying stream as not empty.
* </p>
* <p>
* Throws:
* IllegalStateException - if one of the following conditions is true
* the associated request is neither upgraded nor the async started
* underlying setReadListener is called more than once within the scope of the same request.
* </p>
* @return true if the underlying stream is ready.
*/
boolean isReady();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.innate.io;

import java.io.InputStream;

public interface ExternalStreamWrapper {

ExternalStreamListener getListener();

InputStream getExternalStream();

}
Loading
Loading