ApacheConnectionClosingStrategy.java

/*
 * Copyright (c) 2019, 2022 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.apache.connector;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.glassfish.jersey.client.ClientRequest;

import java.io.IOException;
import java.io.InputStream;

/**
 * /**
 * Strategy that defines the way the Apache client releases resources. The client enables closing the content stream
 * and the response. From the Apache documentation:
 * <pre>
 *     The difference between closing the content stream and closing the response is that
 *     the former will attempt to keep the underlying connection alive by consuming the
 *     entity content while the latter immediately shuts down and discards the connection.
 * </pre>
 * With Apache Client before 4.5.1, it was ok to close the response and the content stream. This is the default for
 * Apache Client 4.5 and older.
 * <p/>
 * For Apache Client 4.5.1+, first the content stream and the response is should be closed.
 * <p/>
 * In the case of Chunk content stream, the stream is not closed on the server side, and the client can hung on reading
 * the closing chunk. Using the {@link org.glassfish.jersey.client.ClientProperties#READ_TIMEOUT} property can prevent
 * this hanging forever and the reading of the closing chunk is terminated when the time is out. The other option, when
 * the timeout is not set, is to abort the Apache client request. This is the default for Apache Client 4.5.1+ when the
 * read timeout is not set.
 * <p/>
 * Another option is not to close the content stream, which is possible by the Apache client documentation. In this case,
 * however, the server side may not be notified and would not not close its chunk stream.
 */
public interface ApacheConnectionClosingStrategy {
    /**
     * Method to close the connection.
     * @param clientRequest The {@link ClientRequest} to get {@link ClientRequest#getConfiguration() configuration},
     *                      and {@link ClientRequest#resolveProperty(String, Class) resolve properties}.
     * @param request Apache {@code HttpUriRequest} that can be {@code abort}ed.
     * @param response Apache {@code CloseableHttpResponse} that can be {@code close}d.
     * @param stream The entity stream that can be {@link InputStream#close() closed}.
     * @throws IOException In case of some of the closing methods throws {@link IOException}
     */
    void close(ClientRequest clientRequest, HttpUriRequest request, CloseableHttpResponse response, InputStream stream)
            throws IOException;

    /**
     * Strategy that aborts Apache HttpRequests for the case of Chunked Stream, closes the stream, and response next.
     */
    class GracefulClosingStrategy implements ApacheConnectionClosingStrategy {
        private static final String UNIX_PROTOCOL = "unix";

        static final GracefulClosingStrategy INSTANCE = new GracefulClosingStrategy();

        @Override
        public void close(ClientRequest clientRequest, HttpUriRequest request, CloseableHttpResponse response, InputStream stream)
                throws IOException {
            if (response.getEntity() != null && response.getEntity().isChunked()
                    && !request.getURI().getScheme().equals(UNIX_PROTOCOL)) {
                request.abort();
            }
            try {
                stream.close();
            } catch (IOException ex) {
                // Ignore
            } finally {
                response.close();
            }
        }
    }

    /**
     * Strategy that closes the response and content stream next. This is a behaviour of Jersey 2.28.
     */
    class ImmediateClosingStrategy implements ApacheConnectionClosingStrategy {
        static final ImmediateClosingStrategy INSTANCE = new ImmediateClosingStrategy();

        @Override
        public void close(ClientRequest clientRequest, HttpUriRequest request, CloseableHttpResponse response, InputStream stream)
                throws IOException {
            response.close();
            stream.close();
        }
    }
}