HttpUrlConnector extension (#4613)

* Created HttpUrlConnector extension for Http100Continue

Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/ConnectorExtension.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/ConnectorExtension.java
new file mode 100644
index 0000000..022cbc6
--- /dev/null
+++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/ConnectorExtension.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 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.client.internal;
+
+import org.glassfish.jersey.client.ClientRequest;
+
+/**
+ * Connector extension interface to extend existing connector's functionality.
+ *
+ * @param <T> type of connection to be extended/processed
+ * @param <E> type of exception which can be thrown while processing/handling exeption
+ *
+ * @since 2.33
+ */
+interface ConnectorExtension<T, E extends Exception> {
+
+    /**
+     * Main function which allows extension of connector's functionality
+     *
+     * @param request request instance to work with (shall contain all required settings/params to be used in extension)
+     * @param extensionParam connector's instance which is being extended
+     */
+    void invoke(ClientRequest request, T extensionParam);
+
+    /**
+     * After connection is done some additional work may be done
+     *
+     * @param extensionParam connector's instance which is being extended
+     */
+    void postConnectionProcessing(T extensionParam);
+
+    /**
+     * Exception handling method
+     *
+     * @param request request instance to work with (shall contain all required settings/params to be used in extension)
+     * @param ex exception instance which comes from connector
+     * @param extensionParam connector's instance which is being extended
+     * @return true if exception was handled by this method, false otherwise
+     * @throws E can thor exception if required by handling
+     */
+    boolean handleException(ClientRequest request, T extensionParam, E ex) throws E;
+
+}
\ No newline at end of file
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
index 0f0614c..777ed40 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlConnector.java
@@ -102,6 +102,9 @@
     private final boolean isRestrictedHeaderPropertySet;
     private final LazyValue<SSLSocketFactory> sslSocketFactory;
 
+    private final ConnectorExtension<HttpURLConnection, IOException> connectorExtension
+            = new HttpUrlExpect100ContinueConnectorExtension();
+
     /**
      * Create new {@code HttpUrlConnector} instance.
      *
@@ -352,7 +355,7 @@
                     }
                 }
 
-                processExpect100Continue(request, uc, length, entityProcessing);
+                processExtentions(request, uc);
 
                 request.setStreamProvider(contentLength -> {
                     setOutboundHeaders(request.getStringHeaders(), uc);
@@ -364,11 +367,7 @@
                 setOutboundHeaders(request.getStringHeaders(), uc);
             }
         } catch (IOException ioe) {
-            if (uc.getResponseCode() == -1) {
-                throw ioe;
-            } else {
-                storedException = ioe;
-            }
+            storedException = handleException(request, ioe, uc);
         }
 
         final int code = uc.getResponseCode();
@@ -530,24 +529,19 @@
         }
     }
 
-    private static void processExpect100Continue(ClientRequest request, HttpURLConnection uc,
-                                          long length, RequestEntityProcessing entityProcessing) {
-        final Boolean expectContinueActivated = request.resolveProperty(
-                ClientProperties.EXPECT_100_CONTINUE, Boolean.class);
-        final Long expectContinueSizeThreshold = request.resolveProperty(
-                ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE,
-                ClientProperties.DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE);
+    private void processExtentions(ClientRequest request, HttpURLConnection uc) {
+        connectorExtension.invoke(request, uc);
+    }
 
-        final boolean allowStreaming = length > expectContinueSizeThreshold
-                || entityProcessing == RequestEntityProcessing.CHUNKED;
-
-        if (!Boolean.TRUE.equals(expectContinueActivated)
-                || !("POST".equals(uc.getRequestMethod()) || "PUT".equals(uc.getRequestMethod()))
-                || !allowStreaming
-        ) {
-            return;
+    private IOException handleException(ClientRequest request, IOException ex, HttpURLConnection uc) throws IOException {
+        if (connectorExtension.handleException(request, uc, ex)) {
+            return null;
         }
-        uc.setRequestProperty("Expect", "100-Continue");
+        if (uc.getResponseCode() == -1) {
+            throw ex;
+        } else {
+            return ex;
+        }
     }
 
     @Override
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlExpect100ContinueConnectorExtension.java b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlExpect100ContinueConnectorExtension.java
new file mode 100644
index 0000000..2e727eb
--- /dev/null
+++ b/core-client/src/main/java/org/glassfish/jersey/client/internal/HttpUrlExpect100ContinueConnectorExtension.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020 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.client.internal;
+
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.ClientRequest;
+import org.glassfish.jersey.client.RequestEntityProcessing;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.ProtocolException;
+
+class HttpUrlExpect100ContinueConnectorExtension
+        implements ConnectorExtension<HttpURLConnection, IOException> {
+
+    private static final String EXCEPTION_MESSAGE = "Server rejected operation";
+
+    @Override
+    public  void invoke(ClientRequest request, HttpURLConnection uc) {
+
+        final long length = request.getLengthLong();
+        final RequestEntityProcessing entityProcessing = request.resolveProperty(
+                ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.class);
+
+        final Boolean expectContinueActivated = request.resolveProperty(
+                ClientProperties.EXPECT_100_CONTINUE, Boolean.class);
+        final Long expectContinueSizeThreshold = request.resolveProperty(
+                ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE,
+                ClientProperties.DEFAULT_EXPECT_100_CONTINUE_THRESHOLD_SIZE);
+
+        final boolean allowStreaming = length > expectContinueSizeThreshold
+                || entityProcessing == RequestEntityProcessing.CHUNKED;
+
+        if (!Boolean.TRUE.equals(expectContinueActivated)
+                || !("POST".equals(uc.getRequestMethod()) || "PUT".equals(uc.getRequestMethod()))
+                || !allowStreaming
+        ) {
+            return;
+        }
+        uc.setRequestProperty("Expect", "100-Continue");
+    }
+
+    @Override
+    public void postConnectionProcessing(HttpURLConnection extensionParam) {
+        //nothing here, we do not process post connection extension
+    }
+
+    @Override
+    public boolean handleException(ClientRequest request, HttpURLConnection extensionParam, IOException ex) {
+
+        final Boolean expectContinueActivated = request.resolveProperty(
+                ClientProperties.EXPECT_100_CONTINUE, Boolean.FALSE);
+
+        return expectContinueActivated
+                && (ex instanceof ProtocolException && ex.getMessage().equals(EXCEPTION_MESSAGE));
+    }
+
+}
\ No newline at end of file