If a proxy is specified, the authorization type will default to 'Basic' unless Digest authentication is
- * specified in the 'Authorization' header.
- *
- * @param httpRequest The HTTP Request being sent
- * @return The HttpURLConnection object
- */
- private HttpURLConnection connect(HttpRequest httpRequest) throws IOException {
- HttpURLConnection connection;
- URL url = httpRequest.getUri().toURL();
+ DefaultHttpClient(java.net.http.HttpClient httpClient, Set restrictedHeaders, Duration writeTimeout,
+ Duration responseTimeout, Duration readTimeout) {
+ this.jdkHttpClient = httpClient;
- if (proxyOptions != null) {
- InetSocketAddress address = proxyOptions.getAddress();
+ this.restrictedHeaders = restrictedHeaders;
+ LOGGER.atVerbose().addKeyValue("headers", restrictedHeaders).log("Effective restricted headers.");
- if (address != null) {
- Proxy proxy = new Proxy(Proxy.Type.HTTP, address);
- connection = (HttpURLConnection) url.openConnection(proxy);
+ // Set the write and response timeouts to null if they are negative or zero.
+ // The writeTimeout is used with 'Flux.timeout(Duration)' which uses thread switching, always. When the timeout
+ // is zero or negative it's treated as an infinite timeout. So, setting this to null will prevent that thread
+ // switching with the same runtime behavior.
+ this.writeTimeout
+ = (writeTimeout != null && !writeTimeout.isNegative() && !writeTimeout.isZero()) ? writeTimeout : null;
- if (proxyOptions.getUsername() != null && proxyOptions.getPassword() != null) {
- String authString = proxyOptions.getUsername() + ":" + proxyOptions.getPassword();
- String authStringEnc = Base64.getEncoder().encodeToString(authString.getBytes());
- connection.setRequestProperty("Proxy-Authorization", "Basic " + authStringEnc);
- }
- } else {
- throw LOGGER.logThrowableAsWarning(new ConnectException("Invalid proxy address"));
- }
- } else {
- connection = (HttpURLConnection) url.openConnection();
- }
-
- if (connection instanceof HttpsURLConnection && sslSocketFactory != null) {
- HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
- httpsConnection.setSSLSocketFactory(sslSocketFactory);
- }
-
- if (connectionTimeout != -1) {
- connection.setConnectTimeout((int) connectionTimeout);
- }
-
- if (readTimeout != -1) {
- connection.setReadTimeout((int) readTimeout);
- }
-
- if (KEEP_CONNECTION_ALIVE) {
- connection.setRequestProperty(HttpHeaderName.CONNECTION.toString(), "keep-alive");
- }
-
- if (MAX_CONNECTIONS > 0) {
- connection.setRequestProperty(HttpHeaderName.CONNECTION.toString(), "keep-alive");
- connection.setRequestProperty(HttpHeaderName.KEEP_ALIVE.toString(), "max=" + MAX_CONNECTIONS);
- }
+ // The responseTimeout is used by JDK 'HttpRequest.timeout()' which will throw an exception when the timeout
+ // is non-null and is zero or negative. We treat zero or negative as an infinite timeout, so reset to null to
+ // prevent the exception from being thrown and have the behavior we want.
+ this.responseTimeout = (responseTimeout != null && !responseTimeout.isNegative() && !responseTimeout.isZero())
+ ? responseTimeout
+ : null;
+ this.readTimeout = readTimeout;
+ this.hasReadTimeout = readTimeout != null && !readTimeout.isNegative() && !readTimeout.isZero();
+ }
- connection.setRequestMethod(httpRequest.getHttpMethod().toString());
+ @Override
+ public Response> send(HttpRequest request) throws IOException {
+ java.net.http.HttpRequest jdkRequest = toJdkHttpRequest(request);
+ try {
+ // JDK HttpClient works differently than OkHttp and HttpUrlConnection where the response body handling has
+ // to be determined when the request is being sent, rather than being something that can be determined after
+ // the response has been received. Given that, we'll always consume the response body as an InputStream and
+ // after receiving it we'll handle ignoring, buffering, or streaming appropriately based on either the
+ // Content-Type header or the response body mode.
+ java.net.http.HttpResponse.BodyHandler bodyHandler
+ = getResponseHandler(hasReadTimeout, readTimeout,
+ java.net.http.HttpResponse.BodyHandlers::ofInputStream, InputStreamTimeoutResponseSubscriber::new);
- for (HttpHeader header : httpRequest.getHeaders()) {
- for (String value : header.getValues()) {
- connection.addRequestProperty(header.getName().toString(), value);
- }
+ java.net.http.HttpResponse jdKResponse = jdkHttpClient.send(jdkRequest, bodyHandler);
+ return toResponse(request, jdKResponse);
+ } catch (InterruptedException e) {
+ throw LOGGER.logThrowableAsError(new RuntimeException(e));
}
-
- return connection;
}
/**
- * Synchronously sends the content of an HttpRequest via an HttpUrlConnection instance.
+ * Converts the given clientcore request to the JDK HttpRequest type.
*
- * @param httpRequest The HTTP Request being sent
- * @param connection The HttpURLConnection that is being sent to
+ * @param request the clientcore request
+ * @return the HttpRequest
*/
- private static void sendBody(HttpRequest httpRequest, HttpURLConnection connection) throws IOException {
- BinaryData body = httpRequest.getBody();
- if (body == null) {
- return;
- }
-
- HttpMethod method = httpRequest.getHttpMethod();
- switch (httpRequest.getHttpMethod()) {
- case GET:
- case HEAD:
- return;
-
- case OPTIONS:
- case TRACE:
- case CONNECT:
- case POST:
- case PUT:
- case DELETE:
- connection.setDoOutput(true);
-
- try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) {
- body.writeTo(os);
- os.flush();
- }
- return;
-
- default:
- throw LOGGER.logThrowableAsError(new IllegalStateException("Unknown HTTP Method: " + method));
- }
+ private java.net.http.HttpRequest toJdkHttpRequest(HttpRequest request) {
+ return new JdkHttpRequest(request, restrictedHeaders, LOGGER, writeTimeout, responseTimeout);
}
/**
- * Receive the response from the remote server
+ * Gets the response body handler based on whether a read timeout is configured.
+ *
+ * When a read timeout is configured our custom handler is used that tracks the time taken between each read
+ * operation to pull the body from the network. If a timeout isn't configured the built-in JDK handler is used.
*
- * @param httpRequest The HTTP Request being sent
- * @param connection The HttpURLConnection being sent to
- * @return A Response object
- * @throws IOException If an I/O error occurs
- * @throws RuntimeException If the ServerSentEventListener is not set
+ * @param hasReadTimeout Flag indicating if a read timeout is configured.
+ * @param readTimeout The configured read timeout.
+ * @param jdkBodyHandler The JDK body handler to use when no read timeout is configured.
+ * @param timeoutSubscriber The supplier for the custom body subscriber to use when a read timeout is configured.
+ * @return The response body handler to use.
+ * @param The type of the response body.
*/
- private Response> receiveResponse(HttpRequest httpRequest, HttpURLConnection connection) throws IOException {
- HttpHeaders responseHeaders = getResponseHeaders(connection);
- HttpResponse> httpResponse = createHttpResponse(httpRequest, connection);
- RequestOptions options = httpRequest.getRequestOptions();
- ServerSentResult serverSentResult = null;
-
- // First check if we've gotten back an error response. If so, handle it now and ignore everything else.
- if (connection.getErrorStream() != null) {
- // Read the error stream to completion to ensure the connection is released back to the pool and set it as
- // the response body.
- eagerlyBufferResponseBody(httpResponse, connection.getErrorStream());
- return httpResponse;
- }
-
- if (isTextEventStream(responseHeaders)) {
- ServerSentEventListener listener = httpRequest.getServerSentEventListener();
+ private static java.net.http.HttpResponse.BodyHandler getResponseHandler(boolean hasReadTimeout,
+ Duration readTimeout, Supplier> jdkBodyHandler,
+ Function> timeoutSubscriber) {
+ return hasReadTimeout ? responseInfo -> timeoutSubscriber.apply(readTimeout.toMillis()) : jdkBodyHandler.get();
+ }
- if (listener == null) {
- connection.getInputStream().close();
- throw LOGGER.logThrowableAsError(new RuntimeException(NO_LISTENER_ERROR_MESSAGE));
- }
+ private Response> toResponse(HttpRequest request, java.net.http.HttpResponse response)
+ throws IOException {
+ HttpHeaders coreHeaders = fromJdkHttpHeaders(response.headers());
+ ServerSentResult serverSentResult = null;
- serverSentResult = processTextEventStream(connection.getInputStream(), listener);
+ String contentType = coreHeaders.getValue(HttpHeaderName.CONTENT_TYPE);
+ if (ServerSentEventUtils.isTextEventStreamContentType(contentType)) {
+ ServerSentEventListener listener = request.getServerSentEventListener();
+ if (listener != null) {
+ serverSentResult = processTextEventStream(response.body(), listener);
- if (serverSentResult.getException() != null) {
- // If an exception occurred while processing the text event stream, emit listener onError.
- connection.getInputStream().close();
- listener.onError(serverSentResult.getException());
- }
+ if (serverSentResult.getException() != null) {
+ // If an exception occurred while processing the text event stream, emit listener onError.
+ listener.onError(serverSentResult.getException());
+ }
- // If an error occurred or we want to reconnect
- if (!Thread.currentThread().isInterrupted() && attemptRetry(serverSentResult, httpRequest)) {
- return this.send(httpRequest);
+ // If an error occurred or we want to reconnect
+ if (!Thread.currentThread().isInterrupted() && attemptRetry(serverSentResult, request)) {
+ return this.send(request);
+ }
+ } else {
+ throw LOGGER.logThrowableAsError(new RuntimeException(ServerSentEventUtils.NO_LISTENER_ERROR_MESSAGE));
}
- // If no error occurred and we don't want to reconnect, continue response body handling.
}
+ return processResponse(request, response, serverSentResult, coreHeaders, contentType);
+ }
+
+ private Response> processResponse(HttpRequest request, java.net.http.HttpResponse response,
+ ServerSentResult serverSentResult, HttpHeaders coreHeaders, String contentType) throws IOException {
+ RequestOptions options = request.getRequestOptions();
ResponseBodyMode responseBodyMode = null;
if (options != null) {
responseBodyMode = options.getResponseBodyMode();
}
- if (responseBodyMode == null) {
- responseBodyMode = determineResponseBodyMode(httpRequest, httpResponse.getHeaders());
- }
+ responseBodyMode = getResponseBodyMode(request, contentType, responseBodyMode);
+
+ BinaryData body = null;
switch (responseBodyMode) {
case IGNORE:
- HttpResponseAccessHelper.setBody(httpResponse, BinaryData.empty());
-
- // Close the response InputStream rather than using disconnect. Disconnect will close the Socket
- // connection when the InputStream is still open, which can result in keep alive handling not being
- // applied.
- connection.getInputStream().close();
+ response.body().close();
break;
case STREAM:
- if (isTextEventStream(responseHeaders)) {
- HttpResponseAccessHelper.setBody(httpResponse, createBodyFromServerSentResult(serverSentResult));
+ if (isTextEventStreamContentType(contentType)) {
+ body = createBodyFromServerSentResult(serverSentResult);
} else {
- streamResponseBody(httpResponse, connection);
+ body = BinaryData.fromStream(response.body());
}
+
break;
case BUFFER:
case DESERIALIZE:
// Deserialization will occur at a later point in HttpResponseBodyDecoder.
- if (isTextEventStream(responseHeaders)) {
- HttpResponseAccessHelper.setBody(httpResponse, createBodyFromServerSentResult(serverSentResult));
+ if (isTextEventStreamContentType(contentType)) {
+ body = createBodyFromServerSentResult(serverSentResult);
} else {
- eagerlyBufferResponseBody(httpResponse, connection.getInputStream());
+ body = createBodyFromResponse(response);
}
break;
default:
- eagerlyBufferResponseBody(httpResponse, connection.getInputStream());
+ body = createBodyFromResponse(response);
break;
+
}
- return httpResponse;
- }
- private static HttpResponse> createHttpResponse(HttpRequest httpRequest, HttpURLConnection connection)
- throws IOException {
- return new HttpResponse<>(httpRequest, connection.getResponseCode(), getResponseHeaders(connection), null);
+ return new JdkHttpResponse(request, response.statusCode(), coreHeaders,
+ body == null ? BinaryData.empty() : body);
}
- private static boolean isTextEventStream(HttpHeaders responseHeaders) {
- if (responseHeaders != null) {
- return ServerSentEventUtils.isTextEventStreamContentType(responseHeaders.getValue(CONTENT_TYPE));
- }
+ private static ResponseBodyMode getResponseBodyMode(HttpRequest request, String contentType,
+ ResponseBodyMode responseBodyMode) {
+ if (responseBodyMode == null) {
+ if (request.getHttpMethod() == HEAD) {
+ responseBodyMode = IGNORE;
+ } else if (contentType != null
+ && APPLICATION_OCTET_STREAM.regionMatches(true, 0, contentType, 0, APPLICATION_OCTET_STREAM.length())) {
- return false;
+ responseBodyMode = STREAM;
+ } else {
+ responseBodyMode = BUFFER;
+ }
+ }
+ return responseBodyMode;
}
private BinaryData createBodyFromServerSentResult(ServerSentResult serverSentResult) {
@@ -339,243 +223,9 @@ private BinaryData createBodyFromServerSentResult(ServerSentResult serverSentRes
return BinaryData.fromString(bodyContent);
}
- private static void streamResponseBody(HttpResponse> httpResponse, HttpURLConnection connection)
- throws IOException {
-
- HttpResponseAccessHelper.setBody(httpResponse, BinaryData.fromStream(connection.getInputStream()));
- }
-
- private static void eagerlyBufferResponseBody(HttpResponse> httpResponse, InputStream stream) throws IOException {
- int contentLength = speculateContentLength(httpResponse.getHeaders());
- AccessibleByteArrayOutputStream outputStream = getAccessibleByteArrayOutputStream(stream, contentLength);
-
- HttpResponseAccessHelper.setBody(httpResponse, BinaryData.fromByteBuffer(outputStream.toByteBuffer()));
- }
-
- private static HttpHeaders getResponseHeaders(HttpURLConnection connection) {
- Map> hucHeaders = connection.getHeaderFields();
- HttpHeaders responseHeaders = new HttpHeaders(hucHeaders.size());
- for (Map.Entry> entry : hucHeaders.entrySet()) {
- if (entry.getKey() != null) {
- responseHeaders.add(HttpHeaderName.fromString(entry.getKey()), entry.getValue());
- }
- }
- return responseHeaders;
- }
-
- private static AccessibleByteArrayOutputStream getAccessibleByteArrayOutputStream(InputStream stream,
- int contentLength) throws IOException {
- AccessibleByteArrayOutputStream outputStream = (contentLength >= 0)
- ? new AccessibleByteArrayOutputStream(contentLength)
- : new AccessibleByteArrayOutputStream();
-
- try (InputStream inputStream = stream) {
- byte[] buffer = new byte[8192];
- int length;
- while ((length = inputStream.read(buffer)) != -1) {
- outputStream.write(buffer, 0, length);
- }
- }
- return outputStream;
- }
-
- private static int speculateContentLength(HttpHeaders headers) {
- String contentLength = headers.getValue(CONTENT_LENGTH);
- if (contentLength == null) {
- return -1;
- }
-
- // We're only speculating an integer sized Content-Length. If it's not an integer, or larger than an integer,
- // we'll return -1 to indicate that we don't know the content length.
- try {
- return Integer.parseInt(contentLength);
- } catch (NumberFormatException e) {
- return -1;
- }
- }
-
- private ResponseBodyMode determineResponseBodyMode(HttpRequest httpRequest, HttpHeaders responseHeaders) {
- HttpHeader contentType = responseHeaders.get(CONTENT_TYPE);
-
- if (httpRequest.getHttpMethod() == HEAD) {
- return IGNORE;
- } else if (contentType != null
- && APPLICATION_OCTET_STREAM.regionMatches(true, 0, contentType.getValue(), 0,
- APPLICATION_OCTET_STREAM.length())) {
-
- return STREAM;
- } else {
- return BUFFER;
- }
- }
-
- private static class SocketClient {
-
- private static final String HTTP_VERSION = " HTTP/1.1";
-
- /**
- * Calls buildAndSend to send a String representation of the request across the output stream, then calls
- * buildResponse to get an instance of HttpUrlConnectionResponse from the input stream
- *
- * @param httpRequest The HTTP Request being sent
- * @param bufferedInputStream the input stream from the socket
- * @param outputStream the output stream from the socket for writing the request
- * @return an instance of Response
- */
- private static Response> sendPatchRequest(HttpRequest httpRequest, BufferedInputStream bufferedInputStream,
- OutputStream outputStream) throws IOException {
- httpRequest.getHeaders().set(HttpHeaderName.HOST, httpRequest.getUri().getHost());
- OutputStreamWriter out = new OutputStreamWriter(outputStream);
-
- buildAndSend(httpRequest, out);
- Response> response = buildResponse(httpRequest, bufferedInputStream);
- HttpHeader locationHeader = response.getHeaders().get(HttpHeaderName.LOCATION);
- String redirectLocation = (locationHeader == null) ? null : locationHeader.getValue();
-
- if (redirectLocation != null) {
- if (redirectLocation.startsWith("http")) {
- httpRequest.setUri(redirectLocation);
- } else {
- UriBuilder uriBuilder = UriBuilder.parse(httpRequest.getUri()).setPath(redirectLocation);
- try {
- httpRequest.setUri(uriBuilder.toUri());
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
- }
- return sendPatchRequest(httpRequest, bufferedInputStream, outputStream);
- }
-
- return response;
-
- }
-
- /**
- * Converts an instance of HttpRequest to a String representation for sending over the output stream
- *
- * @param httpRequest The HTTP Request being sent
- * @param out output stream for writing the request
- * @throws IOException If an I/O error occurs
- */
- private static void buildAndSend(HttpRequest httpRequest, OutputStreamWriter out) throws IOException {
- final StringBuilder request = new StringBuilder();
-
- request.append("PATCH ").append(httpRequest.getUri().getPath()).append(HTTP_VERSION).append("\r\n");
-
- if (httpRequest.getHeaders().getSize() > 0) {
- for (HttpHeader header : httpRequest.getHeaders()) {
- header.getValues()
- .forEach(value -> request.append(header.getName()).append(':').append(value).append("\r\n"));
- }
- }
- if (httpRequest.getBody() != null) {
- request.append("\r\n").append(httpRequest.getBody().toString()).append("\r\n");
- }
-
- out.write(request.toString());
- out.flush();
- }
-
- /**
- * Reads the response from the input stream and extracts the information needed to construct an instance of
- * Response
- *
- * @param httpRequest The HTTP Request being sent
- * @param inputStream the input stream from the socket
- * @return an instance of Response
- * @throws IOException If an I/O error occurs
- */
- private static Response> buildResponse(HttpRequest httpRequest, BufferedInputStream inputStream)
- throws IOException {
- // Parse Http response from socket:
- // Status Line
- // Response Headers
- // Blank Line
- // Response Body
-
- int statusCode = readStatusCode(inputStream);
- HttpHeaders headers = readResponseHeaders(inputStream);
- // read body if present
- // TODO: (add chunked encoding support)
- HttpHeader contentLengthHeader = headers.get(CONTENT_LENGTH);
- byte[] body = getBody(inputStream, contentLengthHeader);
- if (body != null) {
- return new HttpResponse<>(httpRequest, statusCode, headers, BinaryData.fromBytes(body));
- }
- return new HttpResponse<>(httpRequest, statusCode, headers, null);
- }
-
- private static byte[] getBody(BufferedInputStream inputStream, HttpHeader contentLengthHeader)
- throws IOException {
- int contentLength;
- if (contentLengthHeader == null || contentLengthHeader.getValue() == null) {
- return null;
- } else {
- contentLength = Integer.parseInt(contentLengthHeader.getValue());
- }
- if (contentLength > 0) {
- byte[] buffer = new byte[contentLength];
- int bytesRead;
- int totalBytesRead = 0;
-
- while (totalBytesRead < contentLength
- && (bytesRead = inputStream.read(buffer, totalBytesRead, contentLength - totalBytesRead)) != -1) {
- totalBytesRead += bytesRead;
- }
-
- if (totalBytesRead != contentLength) {
- try {
- inputStream.close(); // close the input stream
- } catch (IOException e) {
- // handle the exception
- }
- throw new IOException("Read " + totalBytesRead + " bytes but expected " + contentLength);
- }
-
- return buffer;
- }
- return null;
- }
-
- private static int readStatusCode(InputStream inputStream) throws IOException {
- ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
- int b;
- while ((b = inputStream.read()) != -1 && b != '\n') {
- byteOutputStream.write(b);
- }
- String statusLine = byteOutputStream.toString("UTF-8").trim();
- if (statusLine.isEmpty()) {
- inputStream.close();
- throw new ProtocolException("Unexpected response from server.");
- }
- String[] parts = statusLine.split(" ");
- if (parts.length < 2) {
- inputStream.close();
- throw new ProtocolException(("Unexpected response from server. Status : " + statusLine));
- }
- return Integer.parseInt(parts[1]);
- }
-
- private static HttpHeaders readResponseHeaders(InputStream inputStream) throws IOException {
- HttpHeaders headers = new HttpHeaders();
- ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
- int b;
- while ((b = inputStream.read()) != -1) {
- if (b == '\n') {
- String headerLine = byteOutputStream.toString("UTF-8").trim();
- if (headerLine.isEmpty()) {
- return headers;
- }
- int split = headerLine.indexOf(':');
- String key = headerLine.substring(0, split);
- String value = headerLine.substring(split + 1).trim();
- headers.add(HttpHeaderName.fromString(key), value);
- byteOutputStream.reset();
- }
- byteOutputStream.write(b);
-
- }
- return headers;
+ private BinaryData createBodyFromResponse(HttpResponse response) throws IOException {
+ try (InputStream responseBody = response.body()) { // Use try-with-resources to close the stream.
+ return BinaryData.fromBytes(responseBody.readAllBytes());
}
}
}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClientBuilder.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClientBuilder.java
index a3685933482f9..3a4263ac2c334 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClientBuilder.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClientBuilder.java
@@ -3,14 +3,34 @@
package io.clientcore.core.http.client;
+import io.clientcore.core.http.client.implementation.JdkHttpClientProxySelector;
import io.clientcore.core.http.models.ProxyOptions;
import io.clientcore.core.util.ClientLogger;
+import io.clientcore.core.util.SharedExecutorService;
import io.clientcore.core.util.configuration.Configuration;
-import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.Reader;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.time.Duration;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+import static io.clientcore.core.http.client.implementation.JdkHttpUtils.getDefaultTimeoutFromEnvironment;
+import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_CONNECT_TIMEOUT;
+import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_READ_TIMEOUT;
+import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_RESPONSE_TIMEOUT;
+import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_WRITE_TIMEOUT;
/**
* Builder to configure and build an instance of the core {@link HttpClient} type using the JDK
@@ -18,168 +38,308 @@
*/
public class DefaultHttpClientBuilder {
private static final ClientLogger LOGGER = new ClientLogger(DefaultHttpClientBuilder.class);
- private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(10);
- private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofSeconds(60);
+
private static final Duration MINIMUM_TIMEOUT = Duration.ofMillis(1);
+ private static final Duration DEFAULT_CONNECTION_TIMEOUT;
+ private static final Duration DEFAULT_WRITE_TIMEOUT;
+ private static final Duration DEFAULT_RESPONSE_TIMEOUT;
+ private static final Duration DEFAULT_READ_TIMEOUT;
+
+ private static final String JAVA_HOME = System.getProperty("java.home");
+ private static final String JDK_HTTPCLIENT_ALLOW_RESTRICTED_HEADERS = "jdk.httpclient.allowRestrictedHeaders";
+
+ // These headers are restricted by default in native JDK12 HttpClient.
+ // These headers can be whitelisted by setting jdk.httpclient.allowRestrictedHeaders
+ // property in the network properties file: 'JAVA_HOME/conf/net.properties'
+ // e.g. white listing 'host' header.
+ //
+ // jdk.httpclient.allowRestrictedHeaders=host
+ // Also see - https://bugs.openjdk.java.net/browse/JDK-8213189
+ static final Set DEFAULT_RESTRICTED_HEADERS;
+
+ static {
+ Configuration configuration = Configuration.getGlobalConfiguration();
+
+ DEFAULT_CONNECTION_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_CONNECT_TIMEOUT,
+ Duration.ofSeconds(10), LOGGER);
+ DEFAULT_WRITE_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_WRITE_TIMEOUT,
+ Duration.ofSeconds(60), LOGGER);
+ DEFAULT_RESPONSE_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_RESPONSE_TIMEOUT,
+ Duration.ofSeconds(60), LOGGER);
+ DEFAULT_READ_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_READ_TIMEOUT,
+ Duration.ofSeconds(60), LOGGER);
+ DEFAULT_RESTRICTED_HEADERS = Set.of("connection", "content-length", "expect", "host", "upgrade");
+ }
+
+ private java.net.http.HttpClient.Builder httpClientBuilder;
+ private ProxyOptions proxyOptions;
private Configuration configuration;
+ private Executor executor;
+ private SSLContext sslContext;
+
private Duration connectionTimeout;
+ private Duration writeTimeout;
+ private Duration responseTimeout;
private Duration readTimeout;
- private ProxyOptions proxyOptions;
- private SSLSocketFactory sslSocketFactory;
/**
- * Creates a new instance of the builder with no set configuration.
+ * Creates DefaultHttpClientBuilder.
*/
public DefaultHttpClientBuilder() {
+ this.executor = SharedExecutorService.getInstance();
}
/**
- * Sets the connection timeout for a request to be sent.
+ * Creates DefaultHttpClientBuilder from the builder of an existing {@link java.net.http.HttpClient.Builder}.
*
- *
The connection timeout begins once the request attempts to connect to the remote host and finishes once the
- * connection is resolved.
+ * @param httpClientBuilder the HttpClient builder to use
+ * @throws NullPointerException if {@code httpClientBuilder} is null
+ */
+ public DefaultHttpClientBuilder(java.net.http.HttpClient.Builder httpClientBuilder) {
+ this.httpClientBuilder = Objects.requireNonNull(httpClientBuilder, "'httpClientBuilder' cannot be null.");
+ }
+
+ /**
+ * Sets the executor to be used for asynchronous and dependent tasks. This cannot be null.
+ *
+ * If this method is not invoked prior to {@link #build() building}, handling for a default will be based on whether
+ * the builder was created with the default constructor or the constructor that accepts an existing
+ * {@link java.net.http.HttpClient.Builder}. If the default constructor was used, the default executor will be
+ * {@link SharedExecutorService#getInstance()}. If the constructor that accepts an existing
+ * {@link java.net.http.HttpClient.Builder} was used, the executor from the existing builder will be used.
*
- *
If {@code connectionTimeout} is null, a 10-second timeout will be used, if it is a {@link Duration} less than or
- * equal to zero then no timeout will be applied. When applying the timeout the greatest of one millisecond and the
- * value of {@code connectTimeout} will be used.
+ * @param executor the executor to be used for asynchronous and dependent tasks
+ * @return the updated {@link DefaultHttpClientBuilder} object
+ * @throws NullPointerException if {@code executor} is null
+ */
+ public DefaultHttpClientBuilder executor(Executor executor) {
+ this.executor = Objects.requireNonNull(executor, "executor can not be null");
+ return this;
+ }
+
+ /**
+ * Sets the connection timeout.
*
- *
+ *
*
- * @return The updated {@link DefaultHttpClientBuilder} object.
+ * The default connection timeout is 10 seconds.
+ *
+ * @param connectionTimeout the connection timeout
+ * @return the updated {@link DefaultHttpClientBuilder} object
*/
public DefaultHttpClientBuilder connectionTimeout(Duration connectionTimeout) {
+ // setConnectionTimeout can be null
this.connectionTimeout = connectionTimeout;
+ return this;
+ }
+ /**
+ * Sets the writing timeout for a request to be sent.
+ *
+ * The writing timeout does not apply to the entire request but to the request being sent over the wire. For example
+ * a request body which emits {@code 10} {@code 8KB} buffers will trigger {@code 10} write operations, the last
+ * write tracker will update when each operation completes and the outbound buffer will be periodically checked to
+ * determine if it is still draining.
+ *
+ * If {@code writeTimeout} is null either {@link Configuration#PROPERTY_REQUEST_WRITE_TIMEOUT} or a 60-second
+ * timeout will be used, if it is a {@link Duration} less than or equal to zero then no write timeout will be
+ * applied. When applying the timeout the greatest of one millisecond and the value of {@code writeTimeout} will be
+ * used.
+ *
+ * @param writeTimeout Write operation timeout duration.
+ * @return The updated {@link DefaultHttpClientBuilder} object.
+ */
+ public DefaultHttpClientBuilder writeTimeout(Duration writeTimeout) {
+ this.writeTimeout = writeTimeout;
return this;
}
/**
- * Sets the read timeout duration used when reading the server response.
+ * Sets the response timeout duration used when waiting for a server to reply.
+ *
+ * The response timeout begins once the request write completes and finishes once the first response read is
+ * triggered when the server response is received.
+ *
+ * If {@code responseTimeout} is null either {@link Configuration#PROPERTY_REQUEST_RESPONSE_TIMEOUT} or a
+ * 60-second timeout will be used, if it is a {@link Duration} less than or equal to zero then no timeout will be
+ * applied to the response. When applying the timeout the greatest of one millisecond and the value of {@code
+ * responseTimeout} will be used.
*
- *
The read timeout begins once the first response read is triggered after the server response is received. This
+ * @param responseTimeout Response timeout duration.
+ * @return The updated {@link DefaultHttpClientBuilder} object.
+ */
+ public DefaultHttpClientBuilder responseTimeout(Duration responseTimeout) {
+ this.responseTimeout = responseTimeout;
+ return this;
+ }
+
+ /**
+ * Sets the read timeout duration used when reading the server response.
+ *
+ * The read timeout begins once the first response read is triggered after the server response is received. This
* timeout triggers periodically but won't fire its operation if another read operation has completed between when
- * the timeout is triggered and completes.
- *
- *
If {@code readTimeout} is null a 60-second timeout will be used, if it is a {@link Duration} less than or equal
- * to zero then no timeout period will be applied to response read. When applying the timeout the greatest of one
- * millisecond and the value of {@code readTimeout} will be used.
+ * the timeout is triggered and completes.
+ *
+ * If {@code readTimeout} is null or {@link Configuration#PROPERTY_REQUEST_READ_TIMEOUT} or a 60-second
+ * timeout will be used, if it is a {@link Duration} less than or equal to zero then no timeout period will be
+ * applied to response read. When applying the timeout the greatest of one millisecond and the value of {@code
+ * readTimeout} will be used.
*
* @param readTimeout Read timeout duration.
- *
* @return The updated {@link DefaultHttpClientBuilder} object.
*/
public DefaultHttpClientBuilder readTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
-
return this;
}
/**
- * Returns the timeout in milliseconds to use based on the passed {@link Duration} and default timeout.
+ * Sets the proxy.
*
- *
If the timeout is {@code null} the default timeout will be used. If the timeout is less than or equal to
- * zero no timeout will be used. If the timeout is less than one millisecond a timeout of one millisecond will be
- * used.
+ *
Code Samples
*
- * @param configuredTimeout The desired timeout {@link Duration} or null if using the default timeout.
- * @param defaultTimeout The default timeout {@link Duration} to be used if {@code configuredTimeout} is
- * {@code null}.
- *
- * @return The timeout in milliseconds
- */
- static Duration getTimeout(Duration configuredTimeout, Duration defaultTimeout) {
- if (configuredTimeout == null) {
- return defaultTimeout;
- }
-
- if (configuredTimeout.isZero() || configuredTimeout.isNegative()) {
- return Duration.ZERO;
- }
-
- if (configuredTimeout.compareTo(MINIMUM_TIMEOUT) < 0) {
- return MINIMUM_TIMEOUT;
- } else {
- return configuredTimeout;
- }
- }
-
- /**
- * Sets proxy configuration.
+ *
+ *
+ * final String proxyHost = "<proxy-host>"; // e.g. localhost
+ * final int proxyPort = 9999; // Proxy port
+ * ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP,
+ * new InetSocketAddress(proxyHost, proxyPort));
+ * HttpClient client = new DefaultHttpClientBuilder()
+ * .proxy(proxyOptions)
+ * .build();
+ *
+ *
*
* @param proxyOptions The proxy configuration to use.
- *
- * @return The updated {@link DefaultHttpClientBuilder} object.
+ * @return the updated {@link DefaultHttpClientBuilder} object
+ * @throws NullPointerException If {@code proxyOptions} is not null and the proxy type or address is not set.
*/
public DefaultHttpClientBuilder proxy(ProxyOptions proxyOptions) {
- this.proxyOptions = proxyOptions;
+ if (proxyOptions != null) {
+ Objects.requireNonNull(proxyOptions.getType(), "Proxy type is required.");
+ Objects.requireNonNull(proxyOptions.getAddress(), "Proxy address is required.");
+ }
+ // proxyOptions can be null
+ this.proxyOptions = proxyOptions;
return this;
}
/**
- * Sets the {@link SSLSocketFactory} to use for HTTPS connections.
- *
- * If left unset, or set to null, HTTPS connections will use the default SSL socket factory
- * ({@link SSLSocketFactory#getDefault()}).
+ * Sets the {@link SSLContext} to be used when opening secure connections.
*
- * @param sslSocketFactory The {@link SSLSocketFactory} to use for HTTPS connections.
+ * @param sslContext The SSL context to be used.
* @return The updated {@link DefaultHttpClientBuilder} object.
*/
- public DefaultHttpClientBuilder sslSocketFactory(SSLSocketFactory sslSocketFactory) {
- this.sslSocketFactory = sslSocketFactory;
-
+ public DefaultHttpClientBuilder sslContext(SSLContext sslContext) {
+ this.sslContext = sslContext;
return this;
}
/**
- * Sets the {@link Configuration} store to configure the {@link HttpClient} during construction.
- *
- *
The default configuration store is a clone of the
- * {@link Configuration#getGlobalConfiguration() global configuration store}, use your own {@link Configuration}
- * instance to bypass using global configuration settings during construction.
- *
- * @param configuration The {@link Configuration} store used to configure the {@link HttpClient} during
- * construction.
+ * Sets the configuration store that is used during construction of the HTTP client.
*
+ * @param configuration The configuration store used to
* @return The updated {@link DefaultHttpClientBuilder} object.
*/
public DefaultHttpClientBuilder configuration(Configuration configuration) {
this.configuration = configuration;
-
return this;
}
/**
- * Creates a new {@link HttpClient} instance on every call, using the configuration set in this builder at the time
- * of the {@code build()} method call.
+ * Build a HttpClient with current configurations.
*
- * @return A new {@link HttpClient} instance.
+ * @return a {@link HttpClient}.
*/
public HttpClient build() {
+ java.net.http.HttpClient.Builder httpClientBuilder
+ = this.httpClientBuilder == null ? java.net.http.HttpClient.newBuilder() : this.httpClientBuilder;
+
+ // Client Core JDK http client supports HTTP 1.1 by default.
+ httpClientBuilder.version(java.net.http.HttpClient.Version.HTTP_1_1);
+
+ httpClientBuilder = httpClientBuilder.connectTimeout(getTimeout(connectionTimeout, DEFAULT_CONNECTION_TIMEOUT));
+
+ Duration writeTimeout = getTimeout(this.writeTimeout, DEFAULT_WRITE_TIMEOUT);
+ Duration responseTimeout = getTimeout(this.responseTimeout, DEFAULT_RESPONSE_TIMEOUT);
+ Duration readTimeout = getTimeout(this.readTimeout, DEFAULT_READ_TIMEOUT);
+
Configuration buildConfiguration
= (configuration == null) ? Configuration.getGlobalConfiguration() : configuration;
ProxyOptions buildProxyOptions
= (proxyOptions == null) ? ProxyOptions.fromConfiguration(buildConfiguration) : proxyOptions;
- if (buildProxyOptions != null && buildProxyOptions.getUsername() != null) {
- Authenticator
- .setDefault(new ProxyAuthenticator(buildProxyOptions.getUsername(), buildProxyOptions.getPassword()));
+ if (executor != null) {
+ httpClientBuilder.executor(executor);
}
- if (buildProxyOptions != null
- && buildProxyOptions.getType() != ProxyOptions.Type.HTTP
- && buildProxyOptions.getType() != null) {
+ if (sslContext != null) {
+ httpClientBuilder.sslContext(sslContext);
+ }
+
+ if (buildProxyOptions != null) {
+ httpClientBuilder
+ = httpClientBuilder.proxy(new JdkHttpClientProxySelector(buildProxyOptions.getType().toProxyType(),
+ buildProxyOptions.getAddress(), buildProxyOptions.getNonProxyHosts()));
- throw LOGGER.logThrowableAsError(
- new IllegalArgumentException("Invalid proxy type. Only HTTP proxies are supported."));
+ if (buildProxyOptions.getUsername() != null) {
+ httpClientBuilder.authenticator(
+ new ProxyAuthenticator(buildProxyOptions.getUsername(), buildProxyOptions.getPassword()));
+ }
}
- return new DefaultHttpClient(getTimeout(connectionTimeout, DEFAULT_CONNECT_TIMEOUT),
- getTimeout(readTimeout, DEFAULT_READ_TIMEOUT), buildProxyOptions, sslSocketFactory);
+ return new DefaultHttpClient(httpClientBuilder.build(), Collections.unmodifiableSet(getRestrictedHeaders()),
+ writeTimeout, responseTimeout, readTimeout);
+ }
+
+ Set getRestrictedHeaders() {
+ // Compute the effective restricted headers by removing the allowed headers from default restricted headers
+ Set restrictedHeaders = new HashSet<>(DEFAULT_RESTRICTED_HEADERS);
+ removeAllowedHeaders(restrictedHeaders);
+ return restrictedHeaders;
+ }
+
+ private void removeAllowedHeaders(Set restrictedHeaders) {
+ Properties properties = getNetworkProperties();
+ String[] allowRestrictedHeadersNetProperties
+ = properties.getProperty(JDK_HTTPCLIENT_ALLOW_RESTRICTED_HEADERS, "").split(",");
+
+ // Read all allowed restricted headers from configuration
+ Configuration config = (this.configuration == null) ? Configuration.getGlobalConfiguration() : configuration;
+ String[] allowRestrictedHeadersSystemProperties
+ = config.get(JDK_HTTPCLIENT_ALLOW_RESTRICTED_HEADERS, "").split(",");
+
+ // Combine the set of all allowed restricted headers from both sources
+ for (String header : allowRestrictedHeadersSystemProperties) {
+ restrictedHeaders.remove(header.trim().toLowerCase(Locale.ROOT));
+ }
+
+ for (String header : allowRestrictedHeadersNetProperties) {
+ restrictedHeaders.remove(header.trim().toLowerCase(Locale.ROOT));
+ }
+ }
+
+ Properties getNetworkProperties() {
+ // Read all allowed restricted headers from JAVA_HOME/conf/net.properties
+ Path path = Paths.get(JAVA_HOME, "conf", "net.properties");
+ Properties properties = new Properties();
+ try (Reader reader = Files.newBufferedReader(path)) {
+ properties.load(reader);
+ } catch (IOException e) {
+ LOGGER.atWarning().addKeyValue("path", path).log("Cannot read net properties.", e);
+ }
+ return properties;
}
private static class ProxyAuthenticator extends Authenticator {
@@ -196,4 +356,12 @@ protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(this.userName, password.toCharArray());
}
}
+
+ private static Duration getTimeout(Duration configuredTimeout, Duration defaultTimeout) {
+ if (configuredTimeout == null) {
+ return defaultTimeout;
+ }
+
+ return configuredTimeout.compareTo(MINIMUM_TIMEOUT) < 0 ? MINIMUM_TIMEOUT : configuredTimeout;
+ }
}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClientProvider.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClientProvider.java
index b1efdeab8c5b9..be3dfd52a0173 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClientProvider.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClientProvider.java
@@ -32,6 +32,12 @@ HttpClient getHttpClient() {
private static Providers providers;
+ /**
+ * Creates a new instance of {@link HttpClientProvider}.
+ */
+ public HttpClientProvider() {
+ }
+
/**
* Gets a new instance of the {@link HttpClient} that this {@link HttpClientProvider} is configured to create.
*
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/BodyPublisherUtils.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/BodyPublisherUtils.java
similarity index 98%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/BodyPublisherUtils.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/BodyPublisherUtils.java
index 86cf83466ee0e..8f54037f86a2d 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/BodyPublisherUtils.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/BodyPublisherUtils.java
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient.implementation;
+package io.clientcore.core.http.client.implementation;
import io.clientcore.core.http.models.HttpHeaderName;
import io.clientcore.core.util.binarydata.BinaryData;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/HeaderFilteringMap.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/HeaderFilteringMap.java
similarity index 97%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/HeaderFilteringMap.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/HeaderFilteringMap.java
index 7719bd3a225f2..8158c66e9f9d7 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/HeaderFilteringMap.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/HeaderFilteringMap.java
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient.implementation;
+package io.clientcore.core.http.client.implementation;
import io.clientcore.core.http.models.HttpHeaders;
import io.clientcore.core.util.ClientLogger;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/InputStreamTimeoutResponseSubscriber.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/InputStreamTimeoutResponseSubscriber.java
similarity index 99%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/InputStreamTimeoutResponseSubscriber.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/InputStreamTimeoutResponseSubscriber.java
index 04b7eeafb9549..080dfde20510d 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/InputStreamTimeoutResponseSubscriber.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/InputStreamTimeoutResponseSubscriber.java
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient.implementation;
+package io.clientcore.core.http.client.implementation;
import java.io.IOException;
import java.io.InputStream;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpClientProxySelector.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpClientProxySelector.java
similarity index 96%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpClientProxySelector.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpClientProxySelector.java
index 3806d8442b57d..455d20e676cf3 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpClientProxySelector.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpClientProxySelector.java
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient.implementation;
+package io.clientcore.core.http.client.implementation;
import java.io.IOException;
import java.net.Proxy;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpRequest.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpRequest.java
similarity index 98%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpRequest.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpRequest.java
index 1dc0d4a38a792..7fb033ea3501a 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpRequest.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpRequest.java
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient.implementation;
+package io.clientcore.core.http.client.implementation;
import io.clientcore.core.http.models.HttpMethod;
import io.clientcore.core.util.ClientLogger;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpResponse.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpResponse.java
similarity index 95%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpResponse.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpResponse.java
index 8567a0a996f1c..29e1f1f69a135 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpResponse.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpResponse.java
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient.implementation;
+package io.clientcore.core.http.client.implementation;
import io.clientcore.core.http.models.HttpHeaders;
import io.clientcore.core.http.models.HttpRequest;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpUtils.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpUtils.java
similarity index 98%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpUtils.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpUtils.java
index e871cabd45a95..154bac4f63a22 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/JdkHttpUtils.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/JdkHttpUtils.java
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient.implementation;
+package io.clientcore.core.http.client.implementation;
import io.clientcore.core.http.models.HttpHeaderName;
import io.clientcore.core.http.models.HttpHeaders;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/package-info.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/package-info.java
similarity index 78%
rename from sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/package-info.java
rename to sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/package-info.java
index 3bdcded82c4bb..c2f7150d899ca 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/package-info.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/implementation/package-info.java
@@ -4,4 +4,4 @@
/**
* Package containing implementation classes used by the JDK-backed Client Core HTTP client.
*/
-package io.clientcore.http.jdk.httpclient;
+package io.clientcore.core.http.client.implementation;
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/HttpResponse.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/HttpResponse.java
index d44bff019f8ab..7236e288eeb4f 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/HttpResponse.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/HttpResponse.java
@@ -11,6 +11,8 @@
/**
* The response of an {@link HttpRequest}.
+ *
+ * @param The type of the response value.
*/
public class HttpResponse implements Response {
private boolean isValueDeserialized = false;
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/ServerSentEvent.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/ServerSentEvent.java
index bc7428f17f0be..b0362881b2444 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/ServerSentEvent.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/models/ServerSentEvent.java
@@ -19,6 +19,12 @@ public final class ServerSentEvent {
private String comment;
private Duration retryAfter;
+ /**
+ * Creates a new instance of {@link ServerSentEvent}.
+ */
+ public ServerSentEvent() {
+ }
+
/**
* Get event identifier.
*
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ConstructorReflectiveInvoker.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ConstructorReflectiveInvoker.java
deleted file mode 100644
index 1801c69026903..0000000000000
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ConstructorReflectiveInvoker.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package io.clientcore.core.implementation;
-
-import java.lang.reflect.Constructor;
-
-import static io.clientcore.core.implementation.MethodHandleReflectiveInvoker.createFinalArgs;
-
-/**
- * {@link Constructor}-based implementation of {@link ReflectiveInvoker}.
- */
-final class ConstructorReflectiveInvoker implements ReflectiveInvoker {
- private final Constructor> constructor;
-
- ConstructorReflectiveInvoker(Constructor> constructor) {
- this.constructor = constructor;
- }
-
- @Override
- public Object invokeStatic(Object... args) throws Exception {
- return constructor.newInstance(args);
- }
-
- @Override
- public Object invokeWithArguments(Object target, Object... args) throws Exception {
- return constructor.newInstance(createFinalArgs(args));
- }
-
- @Override
- public int getParameterCount() {
- return constructor.getParameterCount();
- }
-}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/MethodReflectiveInvoker.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/MethodReflectiveInvoker.java
deleted file mode 100644
index f1e530ac32976..0000000000000
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/MethodReflectiveInvoker.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package io.clientcore.core.implementation;
-
-import java.lang.reflect.Method;
-
-/**
- * {@link Method}-based implementation of {@link ReflectiveInvoker}.
- */
-final class MethodReflectiveInvoker implements ReflectiveInvoker {
- private final Method method;
-
- MethodReflectiveInvoker(Method method) {
- this.method = method;
- }
-
- @Override
- public Object invokeStatic(Object... args) throws Exception {
- return method.invoke(null, args);
- }
-
- @Override
- public Object invokeWithArguments(Object target, Object... args) throws Exception {
- return method.invoke(target, args);
- }
-
- @Override
- public int getParameterCount() {
- return method.getParameterCount();
- }
-}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtils.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtils.java
index 6348fbf654d6b..de28460f3887c 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtils.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtils.java
@@ -13,24 +13,7 @@
*/
public abstract class ReflectionUtils {
private static final ClientLogger LOGGER = new ClientLogger(ReflectionUtils.class);
- private static final ReflectionUtilsApi INSTANCE;
-
- static {
- ReflectionUtilsApi instance;
- try {
- LOGGER.atVerbose().log("Attempting to use java.lang.invoke package to handle reflection.");
- instance = new ReflectionUtilsMethodHandle();
- LOGGER.atVerbose().log("Successfully used java.lang.invoke package to handle reflection.");
- } catch (LinkageError ignored) {
- LOGGER.atVerbose()
- .log("Failed to use java.lang.invoke package to handle reflection. Falling back to "
- + "java.lang.reflect package to handle reflection.");
- instance = new ReflectionUtilsClassic();
- LOGGER.atVerbose().log("Successfully used java.lang.reflect package to handle reflection.");
- }
-
- INSTANCE = instance;
- }
+ private static final ReflectionUtilsApi INSTANCE = new ReflectionUtilsMethodHandle();
/**
* Creates an {@link ReflectiveInvoker} instance that will invoke a {@link Method}.
@@ -131,15 +114,6 @@ public static ReflectiveInvoker getConstructorInvoker(Class> targetClass, Cons
return INSTANCE.getConstructorInvoker(targetClass, constructor, scopeToAzureCore);
}
- /**
- * Determines whether a Java 9+ module-based implementation of {@link ReflectionUtilsApi} is being used.
- *
- * @return Whether a Java 9+ module-based implementation of {@link ReflectionUtilsApi} is being used.
- */
- public static boolean isModuleBased() {
- return INSTANCE.isModuleBased();
- }
-
/**
* Creates a dummy {@link ReflectiveInvoker} that will always return null. Used for scenarios where an {@link ReflectiveInvoker} is
* needed as an identifier but will never be used.
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsApi.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsApi.java
index f0948c0ed9d11..d7f413a28d5d9 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsApi.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsApi.java
@@ -62,11 +62,4 @@ ReflectiveInvoker getMethodInvoker(Class> targetClass, Method method, boolean
*/
ReflectiveInvoker getConstructorInvoker(Class> targetClass, Constructor> constructor,
boolean scopeToGenericCore) throws Exception;
-
- /**
- * Indicates whether the {@link ReflectionUtilsApi} instance uses Java 9+ modules.
- *
- * @return Whether the {@link ReflectionUtilsApi} instance uses Java 9+ modules.
- */
- boolean isModuleBased();
}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsClassic.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsClassic.java
deleted file mode 100644
index 05845f1040fae..0000000000000
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsClassic.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package io.clientcore.core.implementation;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-
-/**
- * Implementation for {@link ReflectionUtilsApi} using {@code java.lang.reflect} to handle reflectively invoking APIs.
- */
-final class ReflectionUtilsClassic implements ReflectionUtilsApi {
- @Override
- public ReflectiveInvoker getMethodInvoker(Class> targetClass, Method method, boolean scopeToGenericCore) {
- return new MethodReflectiveInvoker(method);
- }
-
- @Override
- public ReflectiveInvoker getConstructorInvoker(Class> targetClass, Constructor> constructor,
- boolean scopeToGenericCore) {
- return new ConstructorReflectiveInvoker(constructor);
- }
-
- @Override
- public boolean isModuleBased() {
- return false;
- }
-}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsMethodHandle.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsMethodHandle.java
index 1770b2e64ca97..239cc97793ac4 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsMethodHandle.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/ReflectionUtilsMethodHandle.java
@@ -2,99 +2,16 @@
// Licensed under the MIT License.
package io.clientcore.core.implementation;
-import io.clientcore.core.util.ClientLogger;
-
-import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
-import java.security.PrivilegedExceptionAction;
/**
* Implementation for {@link ReflectionUtilsApi} using {@code java.lang.invoke} to handle reflectively invoking APIs.
*/
-@SuppressWarnings("deprecation")
final class ReflectionUtilsMethodHandle implements ReflectionUtilsApi {
- private static final ClientLogger LOGGER = new ClientLogger(ReflectionUtilsMethodHandle.class);
- private static final boolean MODULE_BASED;
-
- private static final MethodHandle CLASS_GET_MODULE_METHOD_HANDLE;
- private static final MethodHandle MODULE_IS_NAMED_METHOD_HANDLE;
- private static final MethodHandle MODULE_ADD_READS_METHOD_HANDLE;
- private static final MethodHandle METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE;
- private static final MethodHandle MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE;
- private static final MethodHandle MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE;
-
- private static final MethodHandles.Lookup LOOKUP;
- private static final Object CORE_MODULE;
-
- private static final MethodHandle JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR;
-
- static {
- boolean moduleBased = false;
- MethodHandle classGetModule = null;
- MethodHandle moduleIsNamed = null;
- MethodHandle moduleAddReads = null;
- MethodHandle methodHandlesPrivateLookupIn = null;
- MethodHandle moduleIsOpenUnconditionally = null;
- MethodHandle moduleIsOpenToOtherModule = null;
-
- MethodHandles.Lookup lookup = MethodHandles.lookup();
- Object coreModule = null;
-
- MethodHandle jdkInternalPrivateLookupInConstructor = null;
-
- try {
- Class> moduleClass = Class.forName("java.lang.Module");
- classGetModule = lookup.unreflect(Class.class.getDeclaredMethod("getModule"));
- moduleIsNamed = lookup.unreflect(moduleClass.getDeclaredMethod("isNamed"));
- moduleAddReads = lookup.unreflect(moduleClass.getDeclaredMethod("addReads", moduleClass));
- methodHandlesPrivateLookupIn = lookup.findStatic(MethodHandles.class, "privateLookupIn",
- MethodType.methodType(MethodHandles.Lookup.class, Class.class, MethodHandles.Lookup.class));
- moduleIsOpenUnconditionally = lookup.unreflect(moduleClass.getDeclaredMethod("isOpen", String.class));
- moduleIsOpenToOtherModule
- = lookup.unreflect(moduleClass.getDeclaredMethod("isOpen", String.class, moduleClass));
-
- coreModule = classGetModule.invokeWithArguments(ReflectionUtils.class);
- moduleBased = true;
- } catch (Throwable throwable) {
- if (throwable instanceof Error) {
- throw (Error) throwable;
- } else {
- LOGGER.atInfo()
- .log("Unable to create MethodHandles to use Java 9+ MethodHandles.privateLookupIn. "
- + "Will attempt to fallback to using the package-private constructor.", throwable);
- }
- }
-
- if (!moduleBased) {
- try {
- Constructor privateLookupInConstructor
- = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
-
- if (!privateLookupInConstructor.isAccessible()) {
- privateLookupInConstructor.setAccessible(true);
- }
-
- jdkInternalPrivateLookupInConstructor = lookup.unreflectConstructor(privateLookupInConstructor);
- } catch (ReflectiveOperationException ex) {
- throw LOGGER.logThrowableAsError(
- new RuntimeException("Unable to use package-private MethodHandles.Lookup constructor.", ex));
- }
- }
-
- MODULE_BASED = moduleBased;
- CLASS_GET_MODULE_METHOD_HANDLE = classGetModule;
- MODULE_IS_NAMED_METHOD_HANDLE = moduleIsNamed;
- MODULE_ADD_READS_METHOD_HANDLE = moduleAddReads;
- METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE = methodHandlesPrivateLookupIn;
- MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE = moduleIsOpenUnconditionally;
- MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE = moduleIsOpenToOtherModule;
- LOOKUP = lookup;
- CORE_MODULE = coreModule;
- JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR = jdkInternalPrivateLookupInConstructor;
- }
+ private static final Module CORE_MODULE = ReflectionUtilsMethodHandle.class.getModule();
+ private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
@Override
public ReflectiveInvoker getMethodInvoker(Class> targetClass, Method method, boolean scopeToGenericCore)
@@ -112,11 +29,6 @@ public ReflectiveInvoker getConstructorInvoker(Class> targetClass, Constructor
return new MethodHandleReflectiveInvoker(lookup.unreflectConstructor(constructor));
}
- @Override
- public boolean isModuleBased() {
- return MODULE_BASED;
- }
-
/**
* Gets the {@link MethodHandles.Lookup} to use when performing reflective operations.
*
@@ -138,41 +50,34 @@ public boolean isModuleBased() {
private static MethodHandles.Lookup getLookupToUse(Class> targetClass, boolean scopeToGenericCore)
throws Exception {
try {
- if (MODULE_BASED) {
- if (!scopeToGenericCore) {
- return MethodHandles.publicLookup();
- }
-
- Object responseModule = CLASS_GET_MODULE_METHOD_HANDLE.invoke(targetClass);
+ if (!scopeToGenericCore) {
+ return MethodHandles.publicLookup();
+ }
- // The unnamed module is opened unconditionally, have Core read it and use a private proxy lookup to
- // enable all lookup scenarios.
- if (!(boolean) MODULE_IS_NAMED_METHOD_HANDLE.invoke(responseModule)) {
- MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule);
- return performSafePrivateLookupIn(targetClass);
- }
+ Module responseModule = targetClass.getModule();
- // If the response module is the Core module return the Core private lookup.
- if (responseModule == CORE_MODULE) {
- return LOOKUP;
- }
+ // The unnamed module is opened unconditionally, have Core read it and use a private proxy lookup to
+ // enable all lookup scenarios.
+ if (!responseModule.isNamed()) {
+ CORE_MODULE.addReads(responseModule);
+ return performSafePrivateLookupIn(targetClass);
+ }
- // Next check if the target class module is opened either unconditionally or to Core's module. If so,
- // also use a private proxy lookup to enable all lookup scenarios.
- String packageName = targetClass.getPackage().getName();
- if ((boolean) MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE.invokeWithArguments(responseModule,
- packageName)
- || (boolean) MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE.invokeWithArguments(responseModule,
- packageName, CORE_MODULE)) {
- MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule);
- return performSafePrivateLookupIn(targetClass);
- }
+ // If the response module is the Core module return the Core private lookup.
+ if (responseModule == CORE_MODULE) {
+ return LOOKUP;
+ }
- // Otherwise, return the public lookup as there are no specialty ways to access the other module.
- return MethodHandles.publicLookup();
- } else {
- return (MethodHandles.Lookup) JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR.invoke(targetClass);
+ // Next check if the target class module is opened either unconditionally or to Core's module. If so,
+ // also use a private proxy lookup to enable all lookup scenarios.
+ String packageName = targetClass.getPackage().getName();
+ if (responseModule.isOpen(packageName) || responseModule.isOpen(packageName, CORE_MODULE)) {
+ CORE_MODULE.addReads(responseModule);
+ return performSafePrivateLookupIn(targetClass);
}
+
+ // Otherwise, return the public lookup as there are no specialty ways to access the other module.
+ return MethodHandles.publicLookup();
} catch (Throwable throwable) {
// invoke(Class) throws a Throwable as the underlying method being called through reflection can throw
// anything, but the constructor being called is owned by the Java SDKs which won't throw Throwable. So,
@@ -186,25 +91,7 @@ private static MethodHandles.Lookup getLookupToUse(Class> targetClass, boolean
}
}
- @SuppressWarnings("removal")
private static MethodHandles.Lookup performSafePrivateLookupIn(Class> targetClass) throws Throwable {
- // MethodHandles::privateLookupIn() throws SecurityException if denied by the security manager
- if (System.getSecurityManager() == null) {
- return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE.invokeExact(targetClass,
- LOOKUP);
- } else {
- return java.security.AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
- try {
- return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE
- .invokeExact(targetClass, LOOKUP);
- } catch (Throwable throwable) {
- if (throwable instanceof Error) {
- throw (Error) throwable;
- } else {
- throw (Exception) throwable;
- }
- }
- });
- }
+ return MethodHandles.privateLookupIn(targetClass, LOOKUP);
}
}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/ImplUtils.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/ImplUtils.java
index a3e4fb174ac9b..880fbfd78f79a 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/ImplUtils.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/ImplUtils.java
@@ -101,7 +101,7 @@ public static boolean isNullOrEmpty(Map, ?> map) {
* @return True if the character sequence is null or empty, false otherwise.
*/
public static boolean isNullOrEmpty(CharSequence charSequence) {
- return charSequence == null || charSequence.length() == 0;
+ return charSequence == null || charSequence.isEmpty();
}
/**
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/ObjectsUtil.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/ObjectsUtil.java
deleted file mode 100644
index 3823f2aba7004..0000000000000
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/ObjectsUtil.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.core.implementation.util;
-
-import java.util.Objects;
-
-/**
- * This class brings in useful utils added to {@link Objects} in Java 9+.
- */
-public final class ObjectsUtil {
-
- private ObjectsUtil() {
- }
-
- /**
- * Returns the first argument if it is non-{@code null} and
- * otherwise returns the non-{@code null} second argument.
- *
- * @param obj an object
- * @param defaultObj a non-{@code null} object to return if the first argument
- * is {@code null}
- * @param the type of the reference
- * @return the first argument if it is non-{@code null} and
- * otherwise the second argument if it is non-{@code null}
- * @throws NullPointerException if both {@code obj} is null and
- * {@code defaultObj} is {@code null}
- */
- public static T requireNonNullElse(T obj, T defaultObj) {
- return (obj != null) ? obj : Objects.requireNonNull(defaultObj, "defaultObj");
- }
-}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/util/ServerSentEventUtils.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/ServerSentEventUtils.java
index 4bc3536061232..86b08ed19d106 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/util/ServerSentEventUtils.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/ServerSentEventUtils.java
@@ -19,17 +19,19 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.regex.Pattern;
/**
* Utility class for Server Sent Event handling.
*/
public final class ServerSentEventUtils {
private static final String DEFAULT_EVENT = "message";
- private static final Pattern DIGITS_ONLY = Pattern.compile("^\\d*$");
private static final HttpHeaderName LAST_EVENT_ID = HttpHeaderName.fromString("Last-Event-Id");
+
+ /**
+ * Error message for when no {@link ServerSentEventListener} is attached to the {@link HttpRequest}.
+ */
public static final String NO_LISTENER_ERROR_MESSAGE
- = "No ServerSentEventListener attached to HttpRequest to " + "handle the text/event-stream response";
+ = "No ServerSentEventListener attached to HttpRequest to handle the text/event-stream response";
private ServerSentEventUtils() {
}
@@ -38,7 +40,6 @@ private ServerSentEventUtils() {
* Checks if the {@code Content-Type} is a text event stream.
*
* @param contentType The content type.
- *
* @return {@code true} if the content type is a text event stream.
*/
public static boolean isTextEventStreamContentType(String contentType) {
@@ -73,6 +74,7 @@ public static ServerSentResult processTextEventStream(InputStream inputStream, S
*
* The method will retry the request with the last event id set and after the retry time.
*
+ *
* @param serverSentResult The {@link ServerSentResult result} of the retry.
* @param httpRequest The {@link HttpRequest} to send.
* @return {@code true} if the request was retried; {@code false} otherwise.
@@ -189,7 +191,7 @@ private static ServerSentEvent processLines(String[] lines) {
break;
case "retry":
- if (!value.isEmpty() && DIGITS_ONLY.matcher(value).matches()) {
+ if (!value.isEmpty() && isDigitsOnly(value)) {
ServerSentEventHelper.setRetryAfter(event, Duration.ofMillis(Long.parseLong(value)));
}
@@ -211,4 +213,18 @@ private static ServerSentEvent processLines(String[] lines) {
return event;
}
+
+ private static boolean isDigitsOnly(String str) {
+ int length = str.length();
+
+ for (int i = 0; i < length; i++) {
+ char c = str.charAt(i);
+
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/util/auth/ChallengeHandler.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/auth/ChallengeHandler.java
index 3b90616455b85..1a3735fdcc9fc 100644
--- a/sdk/clientcore/core/src/main/java/io/clientcore/core/util/auth/ChallengeHandler.java
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/auth/ChallengeHandler.java
@@ -5,17 +5,13 @@
import io.clientcore.core.http.models.HttpRequest;
import io.clientcore.core.http.models.Response;
-import io.clientcore.core.util.ClientLogger;
import java.util.Arrays;
-import java.util.List;
/**
* Class representing a challenge handler for authentication.
*/
public interface ChallengeHandler {
- ClientLogger LOGGER = new ClientLogger(ChallengeHandler.class);
-
/**
* Handles the authentication challenge based on the HTTP request and response.
*
@@ -44,48 +40,4 @@ public interface ChallengeHandler {
static ChallengeHandler of(ChallengeHandler... handlers) {
return new CompositeChallengeHandler(Arrays.asList(handlers));
}
-
- /**
- * A private static class to handle multiple challenge handlers in a composite way.
- */
- class CompositeChallengeHandler implements ChallengeHandler {
- private final List challengeHandlers;
-
- CompositeChallengeHandler(List challengeHandlers) {
- this.challengeHandlers = challengeHandlers;
- }
-
- @Override
- public boolean canHandle(Response> response, boolean isProxy) {
- for (ChallengeHandler handler : challengeHandlers) {
- if (handler.canHandle(response, isProxy)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void handleChallenge(HttpRequest request, Response> response, boolean isProxy) {
- // First, try to handle with DigestChallengeHandler, giving it priority
- for (ChallengeHandler handler : challengeHandlers) {
- if (handler.canHandle(response, isProxy) && handler instanceof DigestChallengeHandler) {
- handler.handleChallenge(request, response, isProxy);
- return;
- }
- }
-
- // If no DigestChallengeHandler was able to handle, try other handlers (e.g., Basic)
- for (ChallengeHandler handler : challengeHandlers) {
- if (handler.canHandle(response, isProxy)) {
- handler.handleChallenge(request, response, isProxy);
- return;
- }
- }
-
- // Log an error if no handler could handle the challenge
- LOGGER.logThrowableAsError(
- new UnsupportedOperationException("None of the challenge handlers could handle the challenge."));
- }
- }
}
diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/util/auth/CompositeChallengeHandler.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/auth/CompositeChallengeHandler.java
new file mode 100644
index 0000000000000..5eca550da5693
--- /dev/null
+++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/util/auth/CompositeChallengeHandler.java
@@ -0,0 +1,52 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package io.clientcore.core.util.auth;
+
+import io.clientcore.core.http.models.HttpRequest;
+import io.clientcore.core.http.models.Response;
+import io.clientcore.core.util.ClientLogger;
+
+import java.util.List;
+
+final class CompositeChallengeHandler implements ChallengeHandler {
+ private static final ClientLogger LOGGER = new ClientLogger(CompositeChallengeHandler.class);
+
+ private final List challengeHandlers;
+
+ CompositeChallengeHandler(List challengeHandlers) {
+ this.challengeHandlers = challengeHandlers;
+ }
+
+ @Override
+ public boolean canHandle(Response> response, boolean isProxy) {
+ for (ChallengeHandler handler : challengeHandlers) {
+ if (handler.canHandle(response, isProxy)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void handleChallenge(HttpRequest request, Response> response, boolean isProxy) {
+ // First, try to handle with DigestChallengeHandler, giving it priority
+ for (ChallengeHandler handler : challengeHandlers) {
+ if (handler.canHandle(response, isProxy) && handler instanceof DigestChallengeHandler) {
+ handler.handleChallenge(request, response, isProxy);
+ return;
+ }
+ }
+
+ // If no DigestChallengeHandler was able to handle, try other handlers (e.g., Basic)
+ for (ChallengeHandler handler : challengeHandlers) {
+ if (handler.canHandle(response, isProxy)) {
+ handler.handleChallenge(request, response, isProxy);
+ return;
+ }
+ }
+
+ // Log an error if no handler could handle the challenge
+ LOGGER.logThrowableAsError(
+ new UnsupportedOperationException("None of the challenge handlers could handle the challenge."));
+ }
+}
diff --git a/sdk/clientcore/core/src/main/java/module-info.java b/sdk/clientcore/core/src/main/java/module-info.java
index 7919995b4380a..8df1fed08b217 100644
--- a/sdk/clientcore/core/src/main/java/module-info.java
+++ b/sdk/clientcore/core/src/main/java/module-info.java
@@ -1,10 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-import io.clientcore.core.http.client.HttpClientProvider;
-
+/**
+ * This module provides core functionality for the Java SDK.
+ */
module io.clientcore.core {
requires transitive io.clientcore.core.json;
+ requires java.net.http;
// public API surface area
exports io.clientcore.core.annotation;
@@ -22,5 +24,5 @@
exports io.clientcore.core.util.serializer;
exports io.clientcore.core.util.auth;
- uses HttpClientProvider;
+ uses io.clientcore.core.http.client.HttpClientProvider;
}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/samples/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilderJavaDocCodeSnippets.java b/sdk/clientcore/core/src/samples/java/io/clientcore/core/http/client/DefaultHttpClientBuilderJavaDocCodeSnippets.java
similarity index 56%
rename from sdk/clientcore/http-jdk-httpclient/src/samples/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilderJavaDocCodeSnippets.java
rename to sdk/clientcore/core/src/samples/java/io/clientcore/core/http/client/DefaultHttpClientBuilderJavaDocCodeSnippets.java
index 4fa144005dd6d..dcdc2fa9ef572 100644
--- a/sdk/clientcore/http-jdk-httpclient/src/samples/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilderJavaDocCodeSnippets.java
+++ b/sdk/clientcore/core/src/samples/java/io/clientcore/core/http/client/DefaultHttpClientBuilderJavaDocCodeSnippets.java
@@ -1,44 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient;
+package io.clientcore.core.http.client;
-import io.clientcore.core.http.client.HttpClient;
import io.clientcore.core.http.models.ProxyOptions;
import java.net.InetSocketAddress;
import java.time.Duration;
/**
- * Code snippets for {@link JdkHttpClientBuilder}
+ * Code snippets for {@link DefaultHttpClientBuilder}
*/
-public class JdkHttpClientBuilderJavaDocCodeSnippets {
-
+@SuppressWarnings("unused")
+public class DefaultHttpClientBuilderJavaDocCodeSnippets {
/**
* Code snippet for simple http client instantiation.
*/
public void simpleInstantiation() {
- // BEGIN: io.clientcore.http.jdk.httpclient.instantiation-simple
- HttpClient client = new JdkHttpClientBuilder()
+ // BEGIN: io.clientcore.core.http.client.instantiation-simple
+ HttpClient client = new DefaultHttpClientBuilder()
.build();
- // END: io.clientcore.http.jdk.httpclient.instantiation-simple
+ // END: io.clientcore.core.http.client.instantiation-simple
}
public void proxySample() {
- // BEGIN: io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.proxy#ProxyOptions
+ // BEGIN: io.clientcore.core.http.client.DefaultHttpClientBuilder.proxy#ProxyOptions
final String proxyHost = ""; // e.g. localhost
final int proxyPort = 9999; // Proxy port
ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP,
new InetSocketAddress(proxyHost, proxyPort));
- HttpClient client = new JdkHttpClientBuilder()
+ HttpClient client = new DefaultHttpClientBuilder()
.proxy(proxyOptions)
.build();
- // END: io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.proxy#ProxyOptions
+ // END: io.clientcore.core.http.client.DefaultHttpClientBuilder.proxy#ProxyOptions
}
public void proxyBasicAuthenticationSample() {
- // BEGIN: io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder#setProxyAuthenticator
+ // BEGIN: io.clientcore.core.http.client.DefaultHttpClientBuilder#setProxyAuthenticator
final String proxyHost = ""; // e.g. localhost
final int proxyPort = 9999; // Proxy port
final String proxyUser = "";
@@ -47,20 +46,20 @@ public void proxyBasicAuthenticationSample() {
ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP,
new InetSocketAddress(proxyHost, proxyPort));
proxyOptions = proxyOptions.setCredentials(proxyUser, proxyPassword);
- HttpClient client = new JdkHttpClientBuilder()
+ HttpClient client = new DefaultHttpClientBuilder()
.proxy(proxyOptions)
.build();
- // END: io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder#setProxyAuthenticator
+ // END: io.clientcore.core.http.client.DefaultHttpClientBuilder#setProxyAuthenticator
}
public void connectionTimeoutSample() {
- // BEGIN: io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.connectionTimeout#Duration
- HttpClient client = new JdkHttpClientBuilder()
+ // BEGIN: io.clientcore.core.http.client.DefaultHttpClientBuilder.connectionTimeout#Duration
+ HttpClient client = new DefaultHttpClientBuilder()
.connectionTimeout(Duration.ofSeconds(250)) // connection timeout of 250 seconds
.build();
- // END: io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.connectionTimeout#Duration
+ // END: io.clientcore.core.http.client.DefaultHttpClientBuilder.connectionTimeout#Duration
}
}
diff --git a/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientBuilderTests.java b/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientBuilderTests.java
index c82c274ca13d5..504f137c0e5f3 100644
--- a/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientBuilderTests.java
+++ b/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientBuilderTests.java
@@ -101,6 +101,8 @@ public void buildWithHttpProxyFromExplicitConfiguration() throws IOException {
Configuration configuration
= new ConfigurationBuilder().putProperty("http.proxy.hostname", proxyEndpoint.getHost())
.putProperty("http.proxy.port", String.valueOf(proxyEndpoint.getPort()))
+ .putProperty("http.proxy.username", PROXY_USERNAME)
+ .putProperty("http.proxy.password", PROXY_PASSWORD)
.build();
HttpClient httpClient = new DefaultHttpClientBuilder().configuration(configuration).build();
@@ -115,35 +117,14 @@ public void buildWithHttpProxyFromExplicitConfiguration() throws IOException {
@Test
public void buildWithNullProxyAddress() {
ProxyOptions mockPoxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, null);
-
- HttpClient httpClient = new DefaultHttpClientBuilder().proxy(mockPoxyOptions).build();
-
- final String serviceUri = "http://localhost:80" + SERVICE_ENDPOINT;
-
- assertThrows(IOException.class, () -> httpClient.send(new HttpRequest(HttpMethod.GET, serviceUri)).close());
- }
-
- @Test
- public void buildWithInvalidProxyType() {
- ProxyOptions clientProxyOptions
- = new ProxyOptions(ProxyOptions.Type.SOCKS5, new InetSocketAddress("test.com", 8080));
-
- assertThrows(IllegalArgumentException.class,
- () -> new DefaultHttpClientBuilder().proxy(clientProxyOptions).build());
+ assertThrows(NullPointerException.class, () -> new DefaultHttpClientBuilder().proxy(mockPoxyOptions));
}
@Test
- public void buildWithNullProxyType() throws IOException {
+ public void buildWithNullProxyType() {
ProxyOptions mockPoxyOptions
= new ProxyOptions(null, new InetSocketAddress(proxyEndpoint.getHost(), proxyEndpoint.getPort()));
-
- HttpClient httpClient = new DefaultHttpClientBuilder().proxy(mockPoxyOptions).build();
-
- final String serviceUri = "http://localhost:80" + SERVICE_ENDPOINT;
-
- try (Response> response = httpClient.send(new HttpRequest(HttpMethod.GET, serviceUri))) {
- assertNotNull(response);
- }
+ assertThrows(NullPointerException.class, () -> new DefaultHttpClientBuilder().proxy(mockPoxyOptions));
}
@Test
diff --git a/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientTest.java b/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientTest.java
index 4362594812a1a..e03a945adc778 100644
--- a/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientTest.java
+++ b/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientTest.java
@@ -197,12 +197,12 @@ public void validateHeadersReturnAsIs() throws IOException {
HttpHeaders responseHeaders = response.getHeaders();
HttpHeader singleValueHeader = responseHeaders.get(singleValueHeaderName);
- assertEquals(singleValueHeaderName.getCaseSensitiveName(), singleValueHeader.getName().toString());
+ assertEquals(singleValueHeaderName.getCaseInsensitiveName(), singleValueHeader.getName().toString());
assertEquals(singleValueHeaderValue, singleValueHeader.getValue());
HttpHeader multiValueHeader = responseHeaders.get(multiValueHeaderName);
- assertEquals(multiValueHeaderName.getCaseSensitiveName(), multiValueHeader.getName().toString());
+ assertEquals(multiValueHeaderName.getCaseInsensitiveName(), multiValueHeader.getName().toString());
assertEquals(multiValueHeaderValue.size(), multiValueHeader.getValues().size());
assertTrue(multiValueHeaderValue.containsAll(multiValueHeader.getValues()));
}
@@ -248,7 +248,7 @@ public void testCustomSslSocketFactory() throws IOException, GeneralSecurityExce
// Initialize the SSL context with a trust manager that trusts all certificates.
sslContext.init(null, new TrustManager[] { new InsecureTrustManager() }, null);
- HttpClient httpClient = new DefaultHttpClientBuilder().sslSocketFactory(sslContext.getSocketFactory()).build();
+ HttpClient httpClient = new DefaultHttpClientBuilder().sslContext(sslContext).build();
try (Response> response = httpClient.send(new HttpRequest(HttpMethod.GET, httpsUri(server, "/short")))) {
TestUtils.assertArraysEqual(SHORT_BODY, response.getBody().toBytes());
diff --git a/sdk/clientcore/core/src/test/java/io/clientcore/core/util/auth/CompositeChallengeHandlerTest.java b/sdk/clientcore/core/src/test/java/io/clientcore/core/util/auth/CompositeChallengeHandlerTest.java
index 34d6ef37f7710..2973cc0c6d6c9 100644
--- a/sdk/clientcore/core/src/test/java/io/clientcore/core/util/auth/CompositeChallengeHandlerTest.java
+++ b/sdk/clientcore/core/src/test/java/io/clientcore/core/util/auth/CompositeChallengeHandlerTest.java
@@ -3,106 +3,82 @@
package io.clientcore.core.util.auth;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
import io.clientcore.core.http.models.HttpRequest;
import io.clientcore.core.http.models.Response;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
-import java.util.Arrays;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Tests for {@link ChallengeHandler.CompositeChallengeHandler}.
*/
public class CompositeChallengeHandlerTest {
- private ChallengeHandler.CompositeChallengeHandler compositeChallengeHandler;
-
- @Mock
- private DigestChallengeHandler digestHandler;
- @Mock
- private BasicChallengeHandler basicHandler;
- @Mock
- private HttpRequest request;
- @Mock
- private Response> response;
-
- private AutoCloseable closeable;
-
- @BeforeEach
- public void setUp() {
- closeable = MockitoAnnotations.openMocks(this);
-
- // Initializing composite with mocked challenge handlers
- compositeChallengeHandler
- = new ChallengeHandler.CompositeChallengeHandler(Arrays.asList(digestHandler, basicHandler));
- }
-
- @AfterEach
- public void tearDown() throws Exception {
- closeable.close();
- }
-
- @Test
- public void testDigestHandlerHandlesChallengeWhenAvailable() {
- when(digestHandler.canHandle(response, false)).thenReturn(true);
-
- compositeChallengeHandler.handleChallenge(request, response, false);
-
- // Digest handler should be called
- verify(digestHandler).handleChallenge(request, response, false);
- verify(basicHandler, never()).handleChallenge(request, response, false);
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ public void firstAvailableChallengeHandlerHandlesChallenge(boolean isProxy) {
+ MockHandler digestHandler = new MockHandler(true, false);
+ MockHandler basicHandler = new MockHandler(false, true);
+
+ ChallengeHandler challengeHandler = ChallengeHandler.of(digestHandler, basicHandler);
+ assertDoesNotThrow(() -> challengeHandler.handleChallenge(null, null, isProxy));
}
- @Test
- public void testBasicHandlerHandlesChallengeWhenDigestNotAvailable() {
- when(digestHandler.canHandle(response, false)).thenReturn(false);
- when(basicHandler.canHandle(response, false)).thenReturn(true);
-
- compositeChallengeHandler.handleChallenge(request, response, false);
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ public void lastAvailableChallengeHandlerHandlesChallenge(boolean isProxy) {
+ MockHandler digestHandler = new MockHandler(false, true);
+ MockHandler basicHandler = new MockHandler(true, false);
- // Basic handler should be called
- verify(digestHandler, never()).handleChallenge(request, response, false);
- verify(basicHandler).handleChallenge(request, response, false);
+ ChallengeHandler challengeHandler = ChallengeHandler.of(digestHandler, basicHandler);
+ assertDoesNotThrow(() -> challengeHandler.handleChallenge(null, null, isProxy));
}
- @Test
- public void testNoHandlerHandlesChallengeLogsErrorIsNoOp() {
- when(digestHandler.canHandle(response, false)).thenReturn(false);
- when(basicHandler.canHandle(response, false)).thenReturn(false);
-
- compositeChallengeHandler.handleChallenge(request, response, false);
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ public void noChallengeHandlerHandlesChallenge(boolean isProxy) {
+ MockHandler digestHandler = new MockHandler(false, true);
+ MockHandler basicHandler = new MockHandler(false, true);
- verify(digestHandler, never()).handleChallenge(request, response, false);
- verify(basicHandler, never()).handleChallenge(request, response, false);
+ ChallengeHandler challengeHandler = ChallengeHandler.of(digestHandler, basicHandler);
+ assertDoesNotThrow(() -> challengeHandler.handleChallenge(null, null, isProxy));
}
- @Test
- public void testDigestHandlerHandlesProxyChallenge() {
- when(digestHandler.canHandle(response, true)).thenReturn(true);
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ public void firstChallengeHandlerHandlesChallengeWhenAllAreAvailable(boolean isProxy) {
+ MockHandler digestHandler = new MockHandler(true, false);
+ MockHandler basicHandler = new MockHandler(true, false);
- compositeChallengeHandler.handleChallenge(request, response, true);
-
- // Digest handler should handle the proxy challenge
- verify(digestHandler).handleChallenge(request, response, true);
- verify(basicHandler, never()).handleChallenge(request, response, true);
+ ChallengeHandler challengeHandler = ChallengeHandler.of(digestHandler, basicHandler);
+ assertDoesNotThrow(() -> challengeHandler.handleChallenge(null, null, isProxy));
+ assertEquals(1, digestHandler.handleChallengeCount);
+ assertEquals(0, basicHandler.handleChallengeCount);
}
- @Test
- public void testBasicHandlerHandlesProxyChallengeWhenDigestNotAvailable() {
- when(digestHandler.canHandle(response, true)).thenReturn(false);
- when(basicHandler.canHandle(response, true)).thenReturn(true);
-
- compositeChallengeHandler.handleChallenge(request, response, true);
-
- // Basic handler should handle the proxy challenge
- verify(digestHandler, never()).handleChallenge(request, response, true);
- verify(basicHandler).handleChallenge(request, response, true);
+ private static final class MockHandler implements ChallengeHandler {
+ private final boolean canHandle;
+ private final boolean handleChallengeThrows;
+
+ int handleChallengeCount = 0;
+
+ MockHandler(boolean canHandle, boolean handleChallengeThrows) {
+ this.canHandle = canHandle;
+ this.handleChallengeThrows = handleChallengeThrows;
+ }
+
+ @Override
+ public boolean canHandle(Response> response, boolean isProxy) {
+ return canHandle;
+ }
+
+ @Override
+ public void handleChallenge(HttpRequest request, Response> response, boolean isProxy) {
+ handleChallengeCount++;
+ if (handleChallengeThrows) {
+ throw new IllegalStateException("Should not be called");
+ }
+ }
}
}
diff --git a/sdk/clientcore/http-jdk-httpclient/CHANGELOG.md b/sdk/clientcore/http-jdk-httpclient/CHANGELOG.md
deleted file mode 100644
index 4144f75694a03..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/CHANGELOG.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Release History
-
-## 1.0.0-beta.1 (Unreleased)
diff --git a/sdk/clientcore/http-jdk-httpclient/README.md b/sdk/clientcore/http-jdk-httpclient/README.md
deleted file mode 100644
index 17659be932676..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/README.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# Client Core JDK HTTP plugin library for Java
-
-This is a clientcore HTTP client that makes use of the asynchronous HttpClient that was made generally available as
-part of JDK 12.
-
-## Getting started
-
-### Prerequisites
-
-- A [Java Development Kit (JDK)][jdk_link], version 12 or later.
-
-### Adding the package to your product
-
-[//]: # ({x-version-update-start;io.clientcore:http-jdk-httpclient;current})
-```xml
-
- io.clientcore
- http-jdk-httpclient
- 1.0.0-beta.1
-
-```
-[//]: # ({x-version-update-end})
-
-### Java 12 requirement
-
-While the JDK added `HttpClient` in JDK 11, this library requires JDK 12 or later. This is due to certain headers being
-disallowed in JDK 11 which JDK 12 allowed to be overridden using system properties. The headers disallowed are required
-for the library to function correctly.
-
-## Key concepts
-
-## Examples
-
-The following sections provide several code snippets covering some of the most common client configuration scenarios.
-
-- [Create a Simple Client](#create-a-simple-client)
-- [Create a Client with Proxy](#create-a-client-with-proxy)
-
-### Create a Simple Client
-
-Create a HttpClient.
-
-```java readme-sample-createBasicClient
-HttpClient client = new JdkHttpClientBuilder().build();
-```
-
-Create a HttpClient using a connection timeout of 60 seconds.
-
-```java readme-sample-createClientWithConnectionTimeout
-HttpClient client = new JdkHttpClientBuilder().connectionTimeout(Duration.ofSeconds(60)).build();
-```
-
-### Create a Client with Proxy
-
-Create a HttpClient that is using a proxy.
-
-```java readme-sample-createProxyClient
-HttpClient client = new JdkHttpClientBuilder()
- .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("", 8888)))
- .build();
-```
-
-## Next steps
-
-## Contributing
-
-For details on contributing to this repository, see the [contributing guide](https://github.com/Azure/azure-sdk-for-java/blob/main/CONTRIBUTING.md).
-
-1. Fork it
-1. Create your feature branch (`git checkout -b my-new-feature`)
-1. Commit your changes (`git commit -am 'Add some feature'`)
-1. Push to the branch (`git push origin my-new-feature`)
-1. Create new Pull Request
-
-
-[logging]: https://github.com/Azure/azure-sdk-for-java/wiki/Logging-in-Azure-SDK
-[jdk_link]: https://docs.microsoft.com/java/azure/jdk/?view=azure-java-stable
-
-![Impressions](https://azure-sdk-impressions.azurewebsites.net/api/impressions/azure-sdk-for-java%2Fsdk%2Fclientcore%2Fhttp-jdk-httpclient%2FREADME.png)
diff --git a/sdk/clientcore/http-jdk-httpclient/checkstyle-suppressions.xml b/sdk/clientcore/http-jdk-httpclient/checkstyle-suppressions.xml
deleted file mode 100644
index e6721c80b865e..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/checkstyle-suppressions.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sdk/clientcore/http-jdk-httpclient/pom.xml b/sdk/clientcore/http-jdk-httpclient/pom.xml
deleted file mode 100644
index 5cb54b1b687f8..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/pom.xml
+++ /dev/null
@@ -1,274 +0,0 @@
-
-
- 4.0.0
-
- io.clientcore
- clientcore-parent
- 1.0.0-beta.1
- ../../parents/clientcore-parent
-
-
- io.clientcore
- http-jdk-httpclient
- jar
- 1.0.0-beta.1
-
- Client Core JDK HTTP Client Library
- This package contains the Client Core HTTP client library using the JDK HttpClient API.
- https://github.com/Azure/azure-sdk-for-java
-
-
-
- The MIT License (MIT)
- http://opensource.org/licenses/MIT
- repo
-
-
-
-
-
- azure-java-build-docs
- ${site.url}/site/${project.artifactId}
-
-
-
-
- https://github.com/Azure/azure-sdk-for-java
- scm:git:https://github.com/Azure/azure-sdk-for-java.git
- scm:git:https://github.com/Azure/azure-sdk-for-java.git
-
-
-
- UTF-8
- 11
- 11
- true
-
-
-
- --add-exports io.clientcore.core/io.clientcore.core.implementation.http=ALL-UNNAMED
- --add-exports io.clientcore.core/io.clientcore.core.implementation.http.serializer=ALL-UNNAMED
- --add-exports io.clientcore.core/io.clientcore.core.implementation.util=ALL-UNNAMED
-
- --add-opens io.clientcore.http.jdk.httpclient/io.clientcore.http.jdk.httpclient=ALL-UNNAMED
-
-
-
-
-
-
- io.clientcore.core.annotation,io.clientcore.core.credential,io.clientcore.core.http,io.clientcore.core.http.annotation,io.clientcore.core.http.client,
- io.clientcore.core.http.exception,io.clientcore.core.http.models,io.clientcore.core.http.pipeline,io.clientcore.core.implementation,
- io.clientcore.core.implementation*,io.clientcore.core.models,io.clientcore.core.util,io.clientcore.core.util*,io.clientcore.core.json,
- com.azure.json,com.azure.xml,com.azure.core*
-
-
- checkstyle-suppressions.xml
-
- false
- spotbugs-exclude.xml
-
-
-
-
- microsoft
- Microsoft
-
-
-
-
-
- io.clientcore
- core
- 1.0.0-beta.1
-
-
-
-
- io.clientcore
- core
- 1.0.0-beta.1
- test-jar
- test
-
-
-
- org.junit.jupiter
- junit-jupiter-api
- 5.11.2
- test
-
-
- org.junit.jupiter
- junit-jupiter-engine
- 5.11.2
- test
-
-
- org.junit.jupiter
- junit-jupiter-params
- 5.11.2
- test
-
-
- org.eclipse.jetty
- jetty-server
- 9.4.56.v20240826
- test
-
-
- org.eclipse.jetty
- jetty-servlet
- 9.4.56.v20240826
- test
-
-
- org.conscrypt
- conscrypt-openjdk-uber
- 2.5.2
- test
-
-
-
-
-
-
- java8
-
- [,11)
-
-
-
-
- org.jacoco
- jacoco-maven-plugin
- 0.8.12
-
- true
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.13.0
-
- true
- true
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 3.5.1
-
- true
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 3.10.1
-
- true
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.4.2
-
- true
-
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- 3.5.0
-
- true
-
-
-
-
- com.github.spotbugs
- spotbugs-maven-plugin
- 4.8.3.1
-
- true
-
-
-
-
- org.revapi
- revapi-maven-plugin
- 0.14.6
-
- true
-
-
-
-
-
-
-
-
- java-lts
-
- [11,)
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.13.0
-
- 11
-
-
-
- default-compile
-
-
-
- base-compile
-
- compile
-
-
- 11
-
-
-
-
- base-testCompile
-
- testCompile
-
-
- 11
-
-
-
-
-
-
-
-
-
-
diff --git a/sdk/clientcore/http-jdk-httpclient/spotbugs-exclude.xml b/sdk/clientcore/http-jdk-httpclient/spotbugs-exclude.xml
deleted file mode 100644
index 27210b83fb365..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/spotbugs-exclude.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClient.java b/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClient.java
deleted file mode 100644
index 72c76011ebbf9..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClient.java
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import io.clientcore.core.http.models.HttpHeaderName;
-import io.clientcore.core.http.models.HttpHeaders;
-import io.clientcore.core.http.models.HttpRequest;
-import io.clientcore.core.http.models.RequestOptions;
-import io.clientcore.core.http.models.Response;
-import io.clientcore.core.http.models.ResponseBodyMode;
-import io.clientcore.core.http.models.ServerSentEventListener;
-import io.clientcore.core.util.ClientLogger;
-import io.clientcore.core.util.ServerSentEventUtils;
-import io.clientcore.core.util.ServerSentResult;
-import io.clientcore.core.util.binarydata.BinaryData;
-import io.clientcore.http.jdk.httpclient.implementation.InputStreamTimeoutResponseSubscriber;
-import io.clientcore.http.jdk.httpclient.implementation.JdkHttpRequest;
-import io.clientcore.http.jdk.httpclient.implementation.JdkHttpResponse;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.http.HttpResponse;
-import java.time.Duration;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-import static io.clientcore.core.http.models.ContentType.APPLICATION_OCTET_STREAM;
-import static io.clientcore.core.http.models.HttpMethod.HEAD;
-import static io.clientcore.core.http.models.ResponseBodyMode.BUFFER;
-import static io.clientcore.core.http.models.ResponseBodyMode.IGNORE;
-import static io.clientcore.core.http.models.ResponseBodyMode.STREAM;
-import static io.clientcore.core.util.ServerSentEventUtils.attemptRetry;
-import static io.clientcore.core.util.ServerSentEventUtils.isTextEventStreamContentType;
-import static io.clientcore.core.util.ServerSentEventUtils.processTextEventStream;
-import static io.clientcore.http.jdk.httpclient.implementation.JdkHttpUtils.fromJdkHttpHeaders;
-
-/**
- * HttpClient implementation for the JDK HttpClient.
- */
-class JdkHttpClient implements HttpClient {
- private static final ClientLogger LOGGER = new ClientLogger(JdkHttpClient.class);
-
- private final Set restrictedHeaders;
- private final Duration writeTimeout;
- private final Duration responseTimeout;
- private final Duration readTimeout;
- private final boolean hasReadTimeout;
-
- final java.net.http.HttpClient jdkHttpClient;
-
- JdkHttpClient(java.net.http.HttpClient httpClient, Set restrictedHeaders, Duration writeTimeout,
- Duration responseTimeout, Duration readTimeout) {
- this.jdkHttpClient = httpClient;
- int javaVersion = getJavaVersion();
- if (javaVersion <= 11) {
- throw LOGGER.logThrowableAsError(
- new UnsupportedOperationException("JdkAsyncHttpClient is not supported in Java version 11 and below."));
- }
-
- this.restrictedHeaders = restrictedHeaders;
- LOGGER.atVerbose().addKeyValue("headers", restrictedHeaders).log("Effective restricted headers.");
-
- // Set the write and response timeouts to null if they are negative or zero.
- // The writeTimeout is used with 'Flux.timeout(Duration)' which uses thread switching, always. When the timeout
- // is zero or negative it's treated as an infinite timeout. So, setting this to null will prevent that thread
- // switching with the same runtime behavior.
- this.writeTimeout
- = (writeTimeout != null && !writeTimeout.isNegative() && !writeTimeout.isZero()) ? writeTimeout : null;
-
- // The responseTimeout is used by JDK 'HttpRequest.timeout()' which will throw an exception when the timeout
- // is non-null and is zero or negative. We treat zero or negative as an infinite timeout, so reset to null to
- // prevent the exception from being thrown and have the behavior we want.
- this.responseTimeout = (responseTimeout != null && !responseTimeout.isNegative() && !responseTimeout.isZero())
- ? responseTimeout
- : null;
- this.readTimeout = readTimeout;
- this.hasReadTimeout = readTimeout != null && !readTimeout.isNegative() && !readTimeout.isZero();
- }
-
- @Override
- public Response> send(HttpRequest request) throws IOException {
- java.net.http.HttpRequest jdkRequest = toJdkHttpRequest(request);
- try {
- // JDK HttpClient works differently than OkHttp and HttpUrlConnection where the response body handling has
- // to be determined when the request is being sent, rather than being something that can be determined after
- // the response has been received. Given that, we'll always consume the response body as an InputStream and
- // after receiving it we'll handle ignoring, buffering, or streaming appropriately based on either the
- // Content-Type header or the response body mode.
- HttpResponse.BodyHandler bodyHandler = getResponseHandler(hasReadTimeout, readTimeout,
- HttpResponse.BodyHandlers::ofInputStream, InputStreamTimeoutResponseSubscriber::new);
-
- java.net.http.HttpResponse jdKResponse = jdkHttpClient.send(jdkRequest, bodyHandler);
- return toResponse(request, jdKResponse);
- } catch (InterruptedException e) {
- throw LOGGER.logThrowableAsError(new RuntimeException(e));
- }
- }
-
- /**
- * Converts the given clientcore request to the JDK HttpRequest type.
- *
- * @param request the clientcore request
- * @return the HttpRequest
- */
- private java.net.http.HttpRequest toJdkHttpRequest(HttpRequest request) {
- return new JdkHttpRequest(request, restrictedHeaders, LOGGER, writeTimeout, responseTimeout);
- }
-
- /**
- * Get the java runtime major version.
- *
- * @return the java major version
- */
- private static int getJavaVersion() {
- return Runtime.version().feature();
- }
-
- /**
- * Gets the response body handler based on whether a read timeout is configured.
- *
- * When a read timeout is configured our custom handler is used that tracks the time taken between each read
- * operation to pull the body from the network. If a timeout isn't configured the built-in JDK handler is used.
- *
- * @param hasReadTimeout Flag indicating if a read timeout is configured.
- * @param readTimeout The configured read timeout.
- * @param jdkBodyHandler The JDK body handler to use when no read timeout is configured.
- * @param timeoutSubscriber The supplier for the custom body subscriber to use when a read timeout is configured.
- * @return The response body handler to use.
- * @param The type of the response body.
- */
- private static HttpResponse.BodyHandler getResponseHandler(boolean hasReadTimeout, Duration readTimeout,
- Supplier> jdkBodyHandler,
- Function> timeoutSubscriber) {
- return hasReadTimeout ? responseInfo -> timeoutSubscriber.apply(readTimeout.toMillis()) : jdkBodyHandler.get();
- }
-
- private Response> toResponse(HttpRequest request, HttpResponse response) throws IOException {
- HttpHeaders coreHeaders = fromJdkHttpHeaders(response.headers());
- ServerSentResult serverSentResult = null;
-
- String contentType = coreHeaders.getValue(HttpHeaderName.CONTENT_TYPE);
- if (ServerSentEventUtils.isTextEventStreamContentType(contentType)) {
- ServerSentEventListener listener = request.getServerSentEventListener();
- if (listener != null) {
- serverSentResult = processTextEventStream(response.body(), listener);
-
- if (serverSentResult.getException() != null) {
- // If an exception occurred while processing the text event stream, emit listener onError.
- listener.onError(serverSentResult.getException());
- }
-
- // If an error occurred or we want to reconnect
- if (!Thread.currentThread().isInterrupted() && attemptRetry(serverSentResult, request)) {
- return this.send(request);
- }
- } else {
- throw LOGGER.logThrowableAsError(new RuntimeException(ServerSentEventUtils.NO_LISTENER_ERROR_MESSAGE));
- }
- }
-
- return processResponse(request, response, serverSentResult, coreHeaders, contentType);
- }
-
- private Response> processResponse(HttpRequest request, HttpResponse response,
- ServerSentResult serverSentResult, HttpHeaders coreHeaders, String contentType) throws IOException {
- RequestOptions options = request.getRequestOptions();
- ResponseBodyMode responseBodyMode = null;
-
- if (options != null) {
- responseBodyMode = options.getResponseBodyMode();
- }
-
- responseBodyMode = getResponseBodyMode(request, contentType, responseBodyMode);
-
- BinaryData body = null;
-
- switch (responseBodyMode) {
- case IGNORE:
- response.body().close();
-
- break;
-
- case STREAM:
- if (isTextEventStreamContentType(contentType)) {
- body = createBodyFromServerSentResult(serverSentResult);
- } else {
- body = BinaryData.fromStream(response.body());
- }
-
- break;
-
- case BUFFER:
- case DESERIALIZE:
- // Deserialization will occur at a later point in HttpResponseBodyDecoder.
- if (isTextEventStreamContentType(contentType)) {
- body = createBodyFromServerSentResult(serverSentResult);
- } else {
- body = createBodyFromResponse(response);
- }
- break;
-
- default:
- body = createBodyFromResponse(response);
- break;
-
- }
-
- return new JdkHttpResponse(request, response.statusCode(), coreHeaders,
- body == null ? BinaryData.empty() : body);
- }
-
- private static ResponseBodyMode getResponseBodyMode(HttpRequest request, String contentType,
- ResponseBodyMode responseBodyMode) {
- if (responseBodyMode == null) {
- if (request.getHttpMethod() == HEAD) {
- responseBodyMode = IGNORE;
- } else if (contentType != null
- && APPLICATION_OCTET_STREAM.regionMatches(true, 0, contentType, 0, APPLICATION_OCTET_STREAM.length())) {
-
- responseBodyMode = STREAM;
- } else {
- responseBodyMode = BUFFER;
- }
- }
- return responseBodyMode;
- }
-
- private BinaryData createBodyFromServerSentResult(ServerSentResult serverSentResult) {
- String bodyContent = (serverSentResult != null && serverSentResult.getData() != null)
- ? String.join("\n", serverSentResult.getData())
- : "";
- return BinaryData.fromString(bodyContent);
- }
-
- private BinaryData createBodyFromResponse(HttpResponse response) throws IOException {
- try (InputStream responseBody = response.body()) { // Use try-with-resources to close the stream.
- return BinaryData.fromBytes(responseBody.readAllBytes());
- }
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilder.java b/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilder.java
deleted file mode 100644
index b5328bbe46120..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilder.java
+++ /dev/null
@@ -1,362 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import io.clientcore.core.http.models.ProxyOptions;
-import io.clientcore.core.util.ClientLogger;
-import io.clientcore.core.util.SharedExecutorService;
-import io.clientcore.core.util.configuration.Configuration;
-import io.clientcore.http.jdk.httpclient.implementation.JdkHttpClientProxySelector;
-
-import javax.net.ssl.SSLContext;
-import java.io.IOException;
-import java.io.Reader;
-import java.net.Authenticator;
-import java.net.PasswordAuthentication;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.Duration;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_CONNECT_TIMEOUT;
-import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_READ_TIMEOUT;
-import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_RESPONSE_TIMEOUT;
-import static io.clientcore.core.util.configuration.Configuration.PROPERTY_REQUEST_WRITE_TIMEOUT;
-import static io.clientcore.http.jdk.httpclient.implementation.JdkHttpUtils.getDefaultTimeoutFromEnvironment;
-
-/**
- * Builder to configure and build an instance of the clientcore {@link HttpClient} type using the JDK HttpClient APIs,
- * first introduced as preview in JDK 9, but made generally available from JDK 11 onwards.
- */
-public class JdkHttpClientBuilder {
- private static final ClientLogger LOGGER = new ClientLogger(JdkHttpClientBuilder.class);
-
- private static final Duration MINIMUM_TIMEOUT = Duration.ofMillis(1);
- private static final Duration DEFAULT_CONNECTION_TIMEOUT;
- private static final Duration DEFAULT_WRITE_TIMEOUT;
- private static final Duration DEFAULT_RESPONSE_TIMEOUT;
- private static final Duration DEFAULT_READ_TIMEOUT;
-
- private static final String JAVA_HOME = System.getProperty("java.home");
- private static final String JDK_HTTPCLIENT_ALLOW_RESTRICTED_HEADERS = "jdk.httpclient.allowRestrictedHeaders";
-
- // These headers are restricted by default in native JDK12 HttpClient.
- // These headers can be whitelisted by setting jdk.httpclient.allowRestrictedHeaders
- // property in the network properties file: 'JAVA_HOME/conf/net.properties'
- // e.g white listing 'host' header.
- //
- // jdk.httpclient.allowRestrictedHeaders=host
- // Also see - https://bugs.openjdk.java.net/browse/JDK-8213189
- static final Set DEFAULT_RESTRICTED_HEADERS;
-
- static {
- Configuration configuration = Configuration.getGlobalConfiguration();
-
- DEFAULT_CONNECTION_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_CONNECT_TIMEOUT,
- Duration.ofSeconds(10), LOGGER);
- DEFAULT_WRITE_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_WRITE_TIMEOUT,
- Duration.ofSeconds(60), LOGGER);
- DEFAULT_RESPONSE_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_RESPONSE_TIMEOUT,
- Duration.ofSeconds(60), LOGGER);
- DEFAULT_READ_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_REQUEST_READ_TIMEOUT,
- Duration.ofSeconds(60), LOGGER);
-
- DEFAULT_RESTRICTED_HEADERS = Set.of("connection", "content-length", "expect", "host", "upgrade");
- }
-
- private java.net.http.HttpClient.Builder httpClientBuilder;
- private ProxyOptions proxyOptions;
- private Configuration configuration;
- private Executor executor;
- private SSLContext sslContext;
-
- private Duration connectionTimeout;
- private Duration writeTimeout;
- private Duration responseTimeout;
- private Duration readTimeout;
-
- /**
- * Creates JdkHttpClientBuilder.
- */
- public JdkHttpClientBuilder() {
- this.executor = SharedExecutorService.getInstance();
- }
-
- /**
- * Creates JdkHttpClientBuilder from the builder of an existing {@link java.net.http.HttpClient.Builder}.
- *
- * @param httpClientBuilder the HttpClient builder to use
- * @throws NullPointerException if {@code httpClientBuilder} is null
- */
- public JdkHttpClientBuilder(java.net.http.HttpClient.Builder httpClientBuilder) {
- this.httpClientBuilder = Objects.requireNonNull(httpClientBuilder, "'httpClientBuilder' cannot be null.");
- }
-
- /**
- * Sets the executor to be used for asynchronous and dependent tasks. This cannot be null.
- *
- * If this method is not invoked prior to {@link #build() building}, handling for a default will be based on whether
- * the builder was created with the default constructor or the constructor that accepts an existing
- * {@link java.net.http.HttpClient.Builder}. If the default constructor was used, the default executor will be
- * {@link SharedExecutorService#getInstance()}. If the constructor that accepts an existing
- * {@link java.net.http.HttpClient.Builder} was used, the executor from the existing builder will be used.
- *
- * @param executor the executor to be used for asynchronous and dependent tasks
- * @return the updated JdkHttpClientBuilder object
- * @throws NullPointerException if {@code executor} is null
- */
- public JdkHttpClientBuilder executor(Executor executor) {
- this.executor = Objects.requireNonNull(executor, "executor can not be null");
- return this;
- }
-
- /**
- * Sets the connection timeout.
- *
- *
- *
- *
- * The default connection timeout is 10 seconds.
- *
- * @param connectionTimeout the connection timeout
- * @return the updated JdkHttpClientBuilder object
- */
- public JdkHttpClientBuilder connectionTimeout(Duration connectionTimeout) {
- // setConnectionTimeout can be null
- this.connectionTimeout = connectionTimeout;
- return this;
- }
-
- /**
- * Sets the writing timeout for a request to be sent.
- *
- * The writing timeout does not apply to the entire request but to the request being sent over the wire. For example
- * a request body which emits {@code 10} {@code 8KB} buffers will trigger {@code 10} write operations, the last
- * write tracker will update when each operation completes and the outbound buffer will be periodically checked to
- * determine if it is still draining.
- *
- * If {@code writeTimeout} is null either {@link Configuration#PROPERTY_REQUEST_WRITE_TIMEOUT} or a 60-second
- * timeout will be used, if it is a {@link Duration} less than or equal to zero then no write timeout will be
- * applied. When applying the timeout the greatest of one millisecond and the value of {@code writeTimeout} will be
- * used.
- *
- * @param writeTimeout Write operation timeout duration.
- * @return The updated {@link JdkHttpClientBuilder} object.
- */
- public JdkHttpClientBuilder writeTimeout(Duration writeTimeout) {
- this.writeTimeout = writeTimeout;
- return this;
- }
-
- /**
- * Sets the response timeout duration used when waiting for a server to reply.
- *
- * The response timeout begins once the request write completes and finishes once the first response read is
- * triggered when the server response is received.
- *
- * If {@code responseTimeout} is null either {@link Configuration#PROPERTY_REQUEST_RESPONSE_TIMEOUT} or a
- * 60-second timeout will be used, if it is a {@link Duration} less than or equal to zero then no timeout will be
- * applied to the response. When applying the timeout the greatest of one millisecond and the value of {@code
- * responseTimeout} will be used.
- *
- * @param responseTimeout Response timeout duration.
- * @return The updated {@link JdkHttpClientBuilder} object.
- */
- public JdkHttpClientBuilder responseTimeout(Duration responseTimeout) {
- this.responseTimeout = responseTimeout;
- return this;
- }
-
- /**
- * Sets the read timeout duration used when reading the server response.
- *
- * The read timeout begins once the first response read is triggered after the server response is received. This
- * timeout triggers periodically but won't fire its operation if another read operation has completed between when
- * the timeout is triggered and completes.
- *
- * If {@code readTimeout} is null or {@link Configuration#PROPERTY_REQUEST_READ_TIMEOUT} or a 60-second
- * timeout will be used, if it is a {@link Duration} less than or equal to zero then no timeout period will be
- * applied to response read. When applying the timeout the greatest of one millisecond and the value of {@code
- * readTimeout} will be used.
- *
- * @param readTimeout Read timeout duration.
- * @return The updated {@link JdkHttpClientBuilder} object.
- */
- public JdkHttpClientBuilder readTimeout(Duration readTimeout) {
- this.readTimeout = readTimeout;
- return this;
- }
-
- /**
- * Sets the proxy.
- *
- *
Code Samples
- *
- *
- *
- * final String proxyHost = "<proxy-host>"; // e.g. localhost
- * final int proxyPort = 9999; // Proxy port
- * ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP,
- * new InetSocketAddress(proxyHost, proxyPort));
- * HttpClient client = new JdkHttpClientBuilder()
- * .proxy(proxyOptions)
- * .build();
- *
- *
- *
- * @param proxyOptions The proxy configuration to use.
- * @return the updated JdkHttpClientBuilder object
- */
- public JdkHttpClientBuilder proxy(ProxyOptions proxyOptions) {
- // proxyOptions can be null
- this.proxyOptions = proxyOptions;
- return this;
- }
-
- /**
- * Sets the {@link SSLContext} to be used when opening secure connections.
- *
- * @param sslContext The SSL context to be used.
- * @return The updated JdkHttpClientBuilder object.
- */
- public JdkHttpClientBuilder sslContext(SSLContext sslContext) {
- this.sslContext = sslContext;
- return this;
- }
-
- /**
- * Sets the configuration store that is used during construction of the HTTP client.
- *
- * @param configuration The configuration store used to
- * @return The updated JdkHttpClientBuilder object.
- */
- public JdkHttpClientBuilder configuration(Configuration configuration) {
- this.configuration = configuration;
- return this;
- }
-
- /**
- * Build a HttpClient with current configurations.
- *
- * @return a {@link HttpClient}.
- */
- public HttpClient build() {
- java.net.http.HttpClient.Builder httpClientBuilder
- = this.httpClientBuilder == null ? java.net.http.HttpClient.newBuilder() : this.httpClientBuilder;
-
- // Client Core JDK http client supports HTTP 1.1 by default.
- httpClientBuilder.version(java.net.http.HttpClient.Version.HTTP_1_1);
-
- httpClientBuilder = httpClientBuilder.connectTimeout(getTimeout(connectionTimeout, DEFAULT_CONNECTION_TIMEOUT));
-
- Duration writeTimeout = getTimeout(this.writeTimeout, DEFAULT_WRITE_TIMEOUT);
- Duration responseTimeout = getTimeout(this.responseTimeout, DEFAULT_RESPONSE_TIMEOUT);
- Duration readTimeout = getTimeout(this.readTimeout, DEFAULT_READ_TIMEOUT);
-
- Configuration buildConfiguration
- = (configuration == null) ? Configuration.getGlobalConfiguration() : configuration;
-
- ProxyOptions buildProxyOptions
- = (proxyOptions == null) ? ProxyOptions.fromConfiguration(buildConfiguration) : proxyOptions;
-
- if (executor != null) {
- httpClientBuilder.executor(executor);
- }
-
- if (sslContext != null) {
- httpClientBuilder.sslContext(sslContext);
- }
-
- if (buildProxyOptions != null) {
- httpClientBuilder
- = httpClientBuilder.proxy(new JdkHttpClientProxySelector(buildProxyOptions.getType().toProxyType(),
- buildProxyOptions.getAddress(), buildProxyOptions.getNonProxyHosts()));
-
- if (buildProxyOptions.getUsername() != null) {
- httpClientBuilder.authenticator(
- new ProxyAuthenticator(buildProxyOptions.getUsername(), buildProxyOptions.getPassword()));
- }
- }
-
- return new JdkHttpClient(httpClientBuilder.build(), Collections.unmodifiableSet(getRestrictedHeaders()),
- writeTimeout, responseTimeout, readTimeout);
- }
-
- Set getRestrictedHeaders() {
- // Compute the effective restricted headers by removing the allowed headers from default restricted headers
- Set restrictedHeaders = new HashSet<>(DEFAULT_RESTRICTED_HEADERS);
- removeAllowedHeaders(restrictedHeaders);
- return restrictedHeaders;
- }
-
- private void removeAllowedHeaders(Set restrictedHeaders) {
- Properties properties = getNetworkProperties();
- String[] allowRestrictedHeadersNetProperties
- = properties.getProperty(JDK_HTTPCLIENT_ALLOW_RESTRICTED_HEADERS, "").split(",");
-
- // Read all allowed restricted headers from configuration
- Configuration config = (this.configuration == null) ? Configuration.getGlobalConfiguration() : configuration;
- String[] allowRestrictedHeadersSystemProperties
- = config.get(JDK_HTTPCLIENT_ALLOW_RESTRICTED_HEADERS, "").split(",");
-
- // Combine the set of all allowed restricted headers from both sources
- for (String header : allowRestrictedHeadersSystemProperties) {
- restrictedHeaders.remove(header.trim().toLowerCase(Locale.ROOT));
- }
-
- for (String header : allowRestrictedHeadersNetProperties) {
- restrictedHeaders.remove(header.trim().toLowerCase(Locale.ROOT));
- }
- }
-
- Properties getNetworkProperties() {
- // Read all allowed restricted headers from JAVA_HOME/conf/net.properties
- Path path = Paths.get(JAVA_HOME, "conf", "net.properties");
- Properties properties = new Properties();
- try (Reader reader = Files.newBufferedReader(path)) {
- properties.load(reader);
- } catch (IOException e) {
- LOGGER.atWarning().addKeyValue("path", path).log("Cannot read net properties.", e);;
- }
- return properties;
- }
-
- private static class ProxyAuthenticator extends Authenticator {
- private final String userName;
- private final String password;
-
- ProxyAuthenticator(String userName, String password) {
- this.userName = userName;
- this.password = password;
- }
-
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(this.userName, password.toCharArray());
- }
- }
-
- private static Duration getTimeout(Duration configuredTimeout, Duration defaultTimeout) {
- if (configuredTimeout == null) {
- return defaultTimeout;
- }
-
- return configuredTimeout.compareTo(MINIMUM_TIMEOUT) < 0 ? MINIMUM_TIMEOUT : configuredTimeout;
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClientProvider.java b/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClientProvider.java
deleted file mode 100644
index c69e6018a7214..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/JdkHttpClientProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import io.clientcore.core.http.client.HttpClientProvider;
-
-/**
- * An {@link HttpClientProvider} that provides an implementation of HttpClient based on native JDK HttpClient.
- *
- * NOTE: This implementation is only available in Java 11+ as that is when {@link java.net.http.HttpClient} was
- * introduced.
- */
-public final class JdkHttpClientProvider extends HttpClientProvider {
- // Enum Singleton Pattern
- private enum GlobalJdkHttpClient {
- HTTP_CLIENT(new JdkHttpClientBuilder().build());
-
- private final HttpClient httpClient;
-
- GlobalJdkHttpClient(HttpClient httpClient) {
- this.httpClient = httpClient;
- }
-
- private HttpClient getHttpClient() {
- return httpClient;
- }
- }
-
- @Override
- public HttpClient getNewInstance() {
- return new JdkHttpClientBuilder().build();
- }
-
- @Override
- public HttpClient getSharedInstance() {
- return GlobalJdkHttpClient.HTTP_CLIENT.getHttpClient();
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/package-info.java b/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/package-info.java
deleted file mode 100644
index 56d6a5e8a1911..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/io/clientcore/http/jdk/httpclient/implementation/package-info.java
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-/**
- * Package containing implementation classes used by the JDK-backed Client Core HTTP client.
- */
-package io.clientcore.http.jdk.httpclient.implementation;
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/java/module-info.java b/sdk/clientcore/http-jdk-httpclient/src/main/java/module-info.java
deleted file mode 100644
index 3754020b74488..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/main/java/module-info.java
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-module io.clientcore.http.jdk.httpclient {
- requires transitive io.clientcore.core;
- requires java.net.http;
-
- exports io.clientcore.http.jdk.httpclient;
-
- provides io.clientcore.core.http.client.HttpClientProvider
- with io.clientcore.http.jdk.httpclient.JdkHttpClientProvider;
-
- uses io.clientcore.core.http.client.HttpClientProvider;
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/main/resources/META-INF/services/io.clientcore.core.http.client.HttpClientProvider b/sdk/clientcore/http-jdk-httpclient/src/main/resources/META-INF/services/io.clientcore.core.http.client.HttpClientProvider
deleted file mode 100644
index 80e960a53c7b0..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/main/resources/META-INF/services/io.clientcore.core.http.client.HttpClientProvider
+++ /dev/null
@@ -1 +0,0 @@
-io.clientcore.http.jdk.httpclient.JdkHttpClientProvider
diff --git a/sdk/clientcore/http-jdk-httpclient/src/samples/java/io/clientcore/http/jdk/httpclient/ReadmeSamples.java b/sdk/clientcore/http-jdk-httpclient/src/samples/java/io/clientcore/http/jdk/httpclient/ReadmeSamples.java
deleted file mode 100644
index 23b231845b17a..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/samples/java/io/clientcore/http/jdk/httpclient/ReadmeSamples.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import io.clientcore.core.http.models.ProxyOptions;
-
-import java.net.InetSocketAddress;
-import java.time.Duration;
-
-/**
- * Class containing code snippets that will be injected to README.md.
- */
-public class ReadmeSamples {
-
- /**
- * Sample code for creating async JDK HttpClient.
- */
- public void createBasicClient() {
- // BEGIN: readme-sample-createBasicClient
- HttpClient client = new JdkHttpClientBuilder().build();
- // END: readme-sample-createBasicClient
- }
-
- /**
- * Sample code for create async JDK HttpClient with connection timeout.
- */
- public void createClientWithConnectionTimeout() {
- // BEGIN: readme-sample-createClientWithConnectionTimeout
- HttpClient client = new JdkHttpClientBuilder().connectionTimeout(Duration.ofSeconds(60)).build();
- // END: readme-sample-createClientWithConnectionTimeout
- }
-
- /**
- * Sample code for creating async JDK HttpClient with proxy.
- */
- public void createProxyClient() {
- // BEGIN: readme-sample-createProxyClient
- HttpClient client = new JdkHttpClientBuilder()
- .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("", 8888)))
- .build();
- // END: readme-sample-createProxyClient
- }
-
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilderTests.java b/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilderTests.java
deleted file mode 100644
index f3bfafb6216ac..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientBuilderTests.java
+++ /dev/null
@@ -1,313 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import io.clientcore.core.http.models.HttpMethod;
-import io.clientcore.core.http.models.HttpRequest;
-import io.clientcore.core.http.models.ProxyOptions;
-import io.clientcore.core.http.models.Response;
-import io.clientcore.core.shared.TestConfigurationSource;
-import io.clientcore.core.util.configuration.Configuration;
-import io.clientcore.core.util.configuration.ConfigurationBuilder;
-import io.clientcore.core.util.configuration.ConfigurationSource;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledForJreRange;
-import org.junit.jupiter.api.condition.JRE;
-import org.junit.jupiter.api.parallel.Execution;
-import org.junit.jupiter.api.parallel.ExecutionMode;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.stream.Stream;
-
-import static io.clientcore.http.jdk.httpclient.JdkHttpClientLocalTestServer.PROXY_PASSWORD;
-import static io.clientcore.http.jdk.httpclient.JdkHttpClientLocalTestServer.PROXY_USERNAME;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Tests {@link JdkHttpClientBuilder}.
- */
-@DisabledForJreRange(max = JRE.JAVA_11)
-@Execution(ExecutionMode.SAME_THREAD)
-public class JdkHttpClientBuilderTests {
- private static final String PROXY_USER_INFO = PROXY_USERNAME + ":" + PROXY_PASSWORD + "@";
- static final String SERVICE_ENDPOINT = "/default";
- private static final ConfigurationSource EMPTY_SOURCE = new TestConfigurationSource();
-
- private static final String SERVER_HTTP_URI = JdkHttpClientLocalTestServer.getServer().getHttpUri();
- private static final int PROXY_SERVER_HTTP_PORT = JdkHttpClientLocalTestServer.getProxyServer().getHttpPort();
-
- /**
- * Tests that an {@link JdkHttpClient} is able to be built from an existing
- * {@link java.net.http.HttpClient.Builder}.
- */
- @Test
- public void buildClientWithExistingClient() throws IOException {
- final String[] marker = new String[1];
- final java.net.http.HttpClient.Builder existingClientBuilder = java.net.http.HttpClient.newBuilder();
- existingClientBuilder.executor(new Executor() {
- private final ExecutorService executorService = Executors.newFixedThreadPool(2);
-
- @Override
- public void execute(Runnable command) {
- marker[0] = "on_custom_executor";
- executorService.submit(command);
- }
- });
-
- final JdkHttpClient client = (JdkHttpClient) new JdkHttpClientBuilder(existingClientBuilder).build();
-
- final String defaultUri = SERVER_HTTP_URI + SERVICE_ENDPOINT;
-
- try (Response> response = client.send(new HttpRequest(HttpMethod.GET, defaultUri))) {
- assertEquals(200, response.getStatusCode());
-
- assertNotNull(marker[0]);
- assertEquals(marker[0], "on_custom_executor");
- }
- }
-
- /**
- * Tests that instantiating an {@link JdkHttpClientBuilder} with a {@code null} {@link JdkHttpClient} will throw a
- * {@link NullPointerException}.
- */
- @Test
- public void startingWithNullClientThrows() {
- assertThrows(NullPointerException.class, () -> new JdkHttpClientBuilder(null));
- }
-
- /**
- * Tests building a client with a given {@code Executor}.
- */
- @Test
- public void buildWithExecutor() throws IOException {
- final String[] marker = new String[1];
- final HttpClient httpClient = new JdkHttpClientBuilder().executor(new Executor() {
- private final ExecutorService executorService = Executors.newFixedThreadPool(10);
-
- @Override
- public void execute(Runnable command) {
- marker[0] = "on_custom_executor";
- executorService.submit(command);
- }
- }).build();
-
- final String defaultUri = SERVER_HTTP_URI + SERVICE_ENDPOINT;
-
- try (Response> response = httpClient.send(new HttpRequest(HttpMethod.GET, defaultUri))) {
- assertEquals(200, response.getStatusCode());
-
- assertNotNull(marker[0]);
- assertEquals(marker[0], "on_custom_executor");
- }
- }
-
- /**
- * Tests that passing a {@code null} {@code executor} to the builder will throw a {@link NullPointerException}.
- */
- @Test
- public void nullExecutorThrows() {
- assertThrows(NullPointerException.class, () -> new JdkHttpClientBuilder().executor(null));
- }
-
- /**
- * Tests building a client with a given proxy.
- */
- @Test
- public void buildWithHttpProxy() throws IOException {
- ProxyOptions clientProxyOptions
- = new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("localhost", PROXY_SERVER_HTTP_PORT))
- .setCredentials(PROXY_USERNAME, PROXY_PASSWORD);
-
- HttpClient httpClient
- = new JdkHttpClientBuilder(java.net.http.HttpClient.newBuilder()).proxy(clientProxyOptions).build();
- // Uri of the service behind proxy
- final String serviceUri = "http://localhost:80" + SERVICE_ENDPOINT;
-
- try (Response> response = httpClient.send(new HttpRequest(HttpMethod.GET, serviceUri))) {
- assertNotNull(response);
- }
- }
-
- @Test
- public void buildWithHttpProxyFromEnvConfiguration() throws IOException {
- Configuration configuration = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
- new TestConfigurationSource()
- .put(Configuration.PROPERTY_HTTP_PROXY,
- "http://" + PROXY_USER_INFO + "localhost:" + PROXY_SERVER_HTTP_PORT)
- .put("java.net.useSystemProxies", "true")).build();
-
- configurationProxyTest(configuration);
- }
-
- @Test
- public void buildWithHttpProxyFromExplicitConfiguration() throws IOException {
- Configuration configuration = new ConfigurationBuilder().putProperty("http.proxy.hostname", "localhost")
- .putProperty("http.proxy.port", String.valueOf(PROXY_SERVER_HTTP_PORT))
- .build();
-
- configurationProxyTest(configuration);
- }
-
- @ParameterizedTest
- @MethodSource("buildWithExplicitConfigurationProxySupplier")
- public void buildWithNonProxyConfigurationProxy(Configuration configuration) throws IOException {
- final HttpClient httpClient = new JdkHttpClientBuilder().configuration(configuration).build();
-
- final String defaultUri = SERVER_HTTP_URI + SERVICE_ENDPOINT;
-
- try (Response> response = httpClient.send(new HttpRequest(HttpMethod.GET, defaultUri))) {
- assertEquals(200, response.getStatusCode());
- }
- }
-
- private static Stream buildWithExplicitConfigurationProxySupplier() {
- List arguments = new ArrayList<>();
-
- final Configuration envConfiguration = new ConfigurationBuilder(EMPTY_SOURCE, EMPTY_SOURCE,
- new TestConfigurationSource().put(Configuration.PROPERTY_HTTP_PROXY, "http://localhost:8888")
- .put(Configuration.PROPERTY_NO_PROXY, "localhost")).build();
-
- arguments.add(Arguments.of(envConfiguration));
-
- final Configuration explicitConfiguration
- = new ConfigurationBuilder().putProperty("http.proxy.hostname", "localhost")
- .putProperty("http.proxy.port", "42")
- .putProperty("http.proxy.non-proxy-hosts", "localhost")
- .build();
-
- arguments.add(Arguments.of(explicitConfiguration));
- return arguments.stream();
- }
-
- @Test
- void testAllowedHeadersFromNetworkProperties() {
- Properties properties = new Properties();
- properties.put("jdk.httpclient.allowRestrictedHeaders", "content-length, upgrade");
-
- JdkHttpClientBuilder jdkHttpClientBuilder = new JdkHttpClientBuilderOverriddenProperties(properties);
-
- Set expectedRestrictedHeaders = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
- expectedRestrictedHeaders
- .addAll(io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.DEFAULT_RESTRICTED_HEADERS);
- expectedRestrictedHeaders.removeAll(Arrays.asList("content-length", "upgrade"));
-
- validateRestrictedHeaders(jdkHttpClientBuilder, expectedRestrictedHeaders, 3);
- }
-
- @Test
- void testAllowedHeadersFromConfiguration() {
- Configuration configuration = new ConfigurationBuilder(EMPTY_SOURCE,
- new TestConfigurationSource().put("jdk.httpclient.allowRestrictedHeaders", "content-length, upgrade"),
- EMPTY_SOURCE).build();
-
- Properties properties = new Properties();
-
- JdkHttpClientBuilder jdkHttpClientBuilder
- = new JdkHttpClientBuilderOverriddenProperties(properties).configuration(configuration);
-
- Set expectedRestrictedHeaders = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
- expectedRestrictedHeaders
- .addAll(io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.DEFAULT_RESTRICTED_HEADERS);
- expectedRestrictedHeaders.removeAll(Arrays.asList("content-length", "upgrade"));
-
- validateRestrictedHeaders(jdkHttpClientBuilder, expectedRestrictedHeaders, 3);
- }
-
- @Test
- void testAllowedHeadersFromBoth() {
- Configuration configuration = new ConfigurationBuilder(new TestConfigurationSource(),
- new TestConfigurationSource().put("jdk.httpclient.allowRestrictedHeaders", "content-length, upgrade"),
- new TestConfigurationSource()).build();
-
- Properties properties = new Properties();
- properties.put("jdk.httpclient.allowRestrictedHeaders", "host, connection, upgrade");
-
- JdkHttpClientBuilder jdkHttpClientBuilder
- = new JdkHttpClientBuilderOverriddenProperties(properties).configuration(configuration);
-
- Set expectedRestrictedHeaders = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
- expectedRestrictedHeaders
- .addAll(io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.DEFAULT_RESTRICTED_HEADERS);
- Arrays.asList("content-length", "host", "connection", "upgrade").forEach(expectedRestrictedHeaders::remove);
-
- validateRestrictedHeaders(jdkHttpClientBuilder, expectedRestrictedHeaders, 1);
- }
-
- @Test
- void testAllowedHeadersFromSystemProperties() {
- Properties properties = new Properties();
- properties.setProperty("jdk.httpclient.allowRestrictedHeaders", "content-length, upgrade");
-
- JdkHttpClientBuilder jdkHttpClientBuilder = new JdkHttpClientBuilderOverriddenProperties(properties);
-
- Set expectedRestrictedHeaders = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
- expectedRestrictedHeaders
- .addAll(io.clientcore.http.jdk.httpclient.JdkHttpClientBuilder.DEFAULT_RESTRICTED_HEADERS);
- expectedRestrictedHeaders.removeAll(Arrays.asList("content-length", "upgrade"));
-
- validateRestrictedHeaders(jdkHttpClientBuilder, expectedRestrictedHeaders, 3);
- }
-
- @Test
- void testCaseInsensitivity() {
- Properties properties = new Properties();
- properties.setProperty("jdk.httpclient.allowRestrictedHeaders", "content-LENGTH");
-
- JdkHttpClientBuilder jdkHttpClientBuilder = new JdkHttpClientBuilderOverriddenProperties(properties);
-
- Set restrictedHeaders = jdkHttpClientBuilder.getRestrictedHeaders();
- assertTrue(restrictedHeaders.contains("connection"), "connection header is missing");
-
- assertFalse(restrictedHeaders.contains("content-length"), "content-length not removed");
- }
-
- private static void configurationProxyTest(Configuration configuration) throws IOException {
- HttpClient httpClient
- = new JdkHttpClientBuilder(java.net.http.HttpClient.newBuilder()).configuration(configuration).build();
- // Uri of the service behind proxy
- final String serviceUri = "http://localhost:80" + SERVICE_ENDPOINT;
- try (Response> response = httpClient.send(new HttpRequest(HttpMethod.GET, serviceUri))) {
- assertNotNull(response);
- }
- }
-
- private void validateRestrictedHeaders(JdkHttpClientBuilder jdkHttpClientBuilder,
- Set expectedRestrictedHeaders, int expectedRestrictedHeadersSize) {
- Set restrictedHeaders = jdkHttpClientBuilder.getRestrictedHeaders();
- assertEquals(expectedRestrictedHeadersSize, restrictedHeaders.size());
- assertEquals(expectedRestrictedHeaders, restrictedHeaders);
- }
-
- private static final class JdkHttpClientBuilderOverriddenProperties extends JdkHttpClientBuilder {
- private final Properties properties;
-
- JdkHttpClientBuilderOverriddenProperties(Properties properties) {
- this.properties = properties;
- }
-
- @Override
- Properties getNetworkProperties() {
- return properties;
- }
- };
-
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientLocalTestServer.java b/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientLocalTestServer.java
deleted file mode 100644
index 77ea63fbce423..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientLocalTestServer.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.shared.LocalTestServer;
-import org.eclipse.jetty.util.Callback;
-import org.junit.jupiter.api.condition.DisabledForJreRange;
-import org.junit.jupiter.api.condition.JRE;
-
-import javax.servlet.ServletException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Objects;
-import java.util.concurrent.Semaphore;
-
-/**
- * {@link LocalTestServer} used by all tests in this package.
- */
-@DisabledForJreRange(max = JRE.JAVA_11)
-public final class JdkHttpClientLocalTestServer {
- private static volatile LocalTestServer server;
- private static final Semaphore SERVER_SEMAPHORE = new Semaphore(1);
-
- private static volatile LocalTestServer proxyServer;
- private static final Semaphore PROXY_SERVER_SEMAPHORE = new Semaphore(1);
-
- public static final byte[] SHORT_BODY = "hi there".getBytes(StandardCharsets.UTF_8);
- public static final byte[] LONG_BODY = createLongBody();
-
- public static final String PROXY_USERNAME = "foo";
- public static final String PROXY_PASSWORD = "bar";
-
- /**
- * Gets the {@link LocalTestServer} instance.
- *
- * @return The {@link LocalTestServer} instance.
- */
- public static LocalTestServer getServer() {
- if (server == null) {
- SERVER_SEMAPHORE.acquireUninterruptibly();
- try {
- if (server == null) {
- server = initializeServer();
- }
- } finally {
- SERVER_SEMAPHORE.release();
- }
- }
-
- return server;
- }
-
- private static LocalTestServer initializeServer() {
- LocalTestServer testServer = new LocalTestServer((req, resp, requestBody) -> {
- String path = req.getServletPath();
- boolean get = "GET".equalsIgnoreCase(req.getMethod());
- boolean post = "POST".equalsIgnoreCase(req.getMethod());
-
- if (get && "/default".equals(path)) {
- resp.setStatus(200);
- resp.flushBuffer();
- } else if (get && "/short".equals(path)) {
- resp.setContentLength(SHORT_BODY.length);
- resp.getHttpOutput().write(SHORT_BODY);
- resp.getHttpOutput().flush();
- resp.getHttpOutput().complete(Callback.NOOP);
- } else if (get && "/long".equals(path)) {
- resp.setContentLength(LONG_BODY.length);
- resp.getHttpOutput().write(LONG_BODY);
- resp.getHttpOutput().flush();
- resp.getHttpOutput().complete(Callback.NOOP);
- } else if (get && "/error".equals(path)) {
- resp.setStatus(500);
- resp.setContentLength(5);
- resp.getHttpOutput().write("error".getBytes(StandardCharsets.UTF_8));
- resp.getHttpOutput().flush();
- resp.getHttpOutput().complete(Callback.NOOP);
- } else if (post && "/shortPost".equals(path)) {
- resp.setContentLength(SHORT_BODY.length);
- resp.getHttpOutput().write(SHORT_BODY);
- resp.getHttpOutput().flush();
- resp.getHttpOutput().complete(Callback.NOOP);
- } else if (get && "/connectionClose".equals(path)) {
- resp.getHttpChannel().getConnection().close();
- } else if (post && "/shortPostWithBodyValidation".equals(path)) {
- if (!Arrays.equals(LONG_BODY, 1, 43, requestBody, 0, 42)) {
- resp.sendError(400, "Request body does not match expected value");
- }
- } else if (get && "/noResponse".equals(path)) {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- } else if (get && "/slowResponse".equals(path)) {
- resp.setContentLength(SHORT_BODY.length);
- resp.setBufferSize(4);
- resp.getHttpOutput().write(SHORT_BODY, 0, 5);
-
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
-
- resp.getHttpOutput().write(SHORT_BODY, 5, 3);
- resp.getHttpOutput().flush();
- resp.getHttpOutput().complete(Callback.NOOP);
- } else {
- throw new ServletException("Unexpected request: " + req.getMethod() + " " + path);
- }
- });
-
- testServer.start();
-
- Runtime.getRuntime().addShutdownHook(new Thread(testServer::stop));
-
- return testServer;
- }
-
- private static byte[] createLongBody() {
- byte[] duplicateBytes = "abcdefghijk".getBytes(StandardCharsets.UTF_8);
- byte[] longBody = new byte[duplicateBytes.length * 100000];
-
- for (int i = 0; i < 100000; i++) {
- System.arraycopy(duplicateBytes, 0, longBody, i * duplicateBytes.length, duplicateBytes.length);
- }
-
- return longBody;
- }
-
- /**
- * Gets the proxy {@link LocalTestServer} instance.
- *
- * @return The proxy {@link LocalTestServer} instance.
- */
- public static LocalTestServer getProxyServer() {
- if (proxyServer == null) {
- PROXY_SERVER_SEMAPHORE.acquireUninterruptibly();
- try {
- if (proxyServer == null) {
- proxyServer = initializeProxyServer();
- }
- } finally {
- PROXY_SERVER_SEMAPHORE.release();
- }
- }
-
- return proxyServer;
- }
-
- private static LocalTestServer initializeProxyServer() {
- LocalTestServer proxyServer = new LocalTestServer((req, resp, requestBody) -> {
- String requestUri = req.getRequestURL().toString();
- if (!Objects.equals(requestUri, "/default")) {
- throw new ServletException("Unexpected request to proxy server");
- }
-
- String proxyAuthorization = req.getHeader("Proxy-Authorization");
- if (proxyAuthorization == null) {
- resp.setStatus(407);
- resp.setHeader("Proxy-Authenticate", "Basic");
- return;
- }
-
- if (!proxyAuthorization.startsWith("Basic")) {
- resp.setStatus(401);
- return;
- }
-
- String encodedCred = proxyAuthorization.substring("Basic".length());
- encodedCred = encodedCred.trim();
- final Base64.Decoder decoder = Base64.getDecoder();
- final byte[] decodedCred = decoder.decode(encodedCred);
- if (!new String(decodedCred).equals(PROXY_USERNAME + ":" + PROXY_PASSWORD)) {
- resp.setStatus(401);
- }
- });
-
- proxyServer.start();
-
- Runtime.getRuntime().addShutdownHook(new Thread(proxyServer::stop));
-
- return proxyServer;
- }
-
- private JdkHttpClientLocalTestServer() {
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientProviderTests.java b/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientProviderTests.java
deleted file mode 100644
index 5b6bf90f7587f..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientProviderTests.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.models.ProxyOptions;
-import io.clientcore.core.util.configuration.Configuration;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledForJreRange;
-import org.junit.jupiter.api.condition.JRE;
-
-import java.net.ProxySelector;
-import java.util.Optional;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Tests {@link JdkHttpClientProvider}.
- */
-@DisabledForJreRange(max = JRE.JAVA_11)
-public class JdkHttpClientProviderTests {
- @Test
- public void testGetSharedInstance() {
- JdkHttpClient jdkHttpClient = (JdkHttpClient) new JdkHttpClientProvider().getSharedInstance();
- ProxyOptions environmentProxy = ProxyOptions.fromConfiguration(Configuration.getGlobalConfiguration());
-
- if (environmentProxy == null) {
- assertTrue(jdkHttpClient.jdkHttpClient.proxy().isEmpty());
- } else {
- // Proxy isn't configured on the OkHttp HttpClient when a proxy exists, the ProxySelector is configured.
- Optional optionalProxySelector = jdkHttpClient.jdkHttpClient.proxy();
-
- assertTrue(optionalProxySelector.isPresent());
- assertEquals(environmentProxy.getAddress(), optionalProxySelector.get().select(null).get(0).address());
- }
- }
-
- @Test
- public void testGetNewInstance() {
- JdkHttpClient jdkHttpClient = (JdkHttpClient) new JdkHttpClientProvider().getNewInstance();
- ProxyOptions environmentProxy = ProxyOptions.fromConfiguration(Configuration.getGlobalConfiguration());
-
- if (environmentProxy == null) {
- assertTrue(jdkHttpClient.jdkHttpClient.proxy().isEmpty());
- } else {
- // Proxy isn't configured on the OkHttp HttpClient when a proxy exists, the ProxySelector is configured.
- Optional optionalProxySelector = jdkHttpClient.jdkHttpClient.proxy();
-
- assertTrue(optionalProxySelector.isPresent());
- assertEquals(environmentProxy.getAddress(), optionalProxySelector.get().select(null).get(0).address());
- }
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientSingletonTests.java b/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientSingletonTests.java
deleted file mode 100644
index b5c4bdcde8e2d..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientSingletonTests.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledForJreRange;
-import org.junit.jupiter.api.condition.JRE;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-
-@DisabledForJreRange(max = JRE.JAVA_11)
-public class JdkHttpClientSingletonTests {
- @Test
- public void testGetSharedInstance() {
- HttpClient client1 = new JdkHttpClientProvider().getSharedInstance();
- HttpClient client2 = new JdkHttpClientProvider().getSharedInstance();
-
- assertEquals(client1, client2);
- }
-
- @Test
- public void testGetNewInstance() {
- HttpClient client1 = new JdkHttpClientProvider().getNewInstance();
- HttpClient client2 = new JdkHttpClientProvider().getNewInstance();
-
- assertNotEquals(client1, client2);
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientTests.java b/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientTests.java
deleted file mode 100644
index c20792dc8c447..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientTests.java
+++ /dev/null
@@ -1,346 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import io.clientcore.core.http.models.HttpHeaderName;
-import io.clientcore.core.http.models.HttpMethod;
-import io.clientcore.core.http.models.HttpRequest;
-import io.clientcore.core.http.models.RequestOptions;
-import io.clientcore.core.http.models.Response;
-import io.clientcore.core.http.models.ResponseBodyMode;
-import io.clientcore.core.shared.InsecureTrustManager;
-import io.clientcore.core.util.binarydata.BinaryData;
-import org.conscrypt.Conscrypt;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
-import org.junit.jupiter.api.condition.DisabledForJreRange;
-import org.junit.jupiter.api.condition.JRE;
-import org.junit.jupiter.api.parallel.Execution;
-import org.junit.jupiter.api.parallel.ExecutionMode;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.http.HttpTimeoutException;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.WritableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.GeneralSecurityException;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.TimeUnit;
-
-import static io.clientcore.http.jdk.httpclient.JdkHttpClientLocalTestServer.LONG_BODY;
-import static io.clientcore.http.jdk.httpclient.JdkHttpClientLocalTestServer.SHORT_BODY;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTimeout;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-@DisabledForJreRange(max = JRE.JAVA_11)
-@Execution(ExecutionMode.SAME_THREAD)
-public class JdkHttpClientTests {
- private static final String SERVER_HTTP_URI = JdkHttpClientLocalTestServer.getServer().getHttpUri();
- private static final String SERVER_HTTPS_URI = JdkHttpClientLocalTestServer.getServer().getHttpsUri();
-
- @Test
- public void testResponseShortBodyAsByteArray() throws IOException {
- checkBodyReceived(SHORT_BODY, "/short");
- }
-
- @Test
- public void testResponseLongBodyAsByteArray() throws IOException {
- checkBodyReceived(LONG_BODY, "/long");
- }
-
- @Test
- public void testBufferResponse() throws IOException {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- try (Response> response = doRequest(client, "/long")) {
- TestUtils.assertArraysEqual(LONG_BODY, response.getBody().toReplayableBinaryData().toBytes());
- }
- }
-
- @Test
- public void testBufferedResponse() throws IOException {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- HttpRequest request = new HttpRequest(HttpMethod.GET, uri("/long"))
- .setRequestOptions(new RequestOptions().setResponseBodyMode(ResponseBodyMode.BUFFER));
-
- try (Response> response = client.send(request)) {
- TestUtils.assertArraysEqual(LONG_BODY, response.getBody().toBytes());
- }
- }
-
- @Test
- public void testMultipleGetBodyBytes() throws IOException {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- try (Response> response = doRequest(client, "/short")) {
- TestUtils.assertArraysEqual(SHORT_BODY, response.getBody().toBytes());
-
- // Second call should return the same body as BinaryData caches the result.
- TestUtils.assertArraysEqual(SHORT_BODY, response.getBody().toBytes());
- }
- }
-
- @Test
- @Timeout(20)
- public void testWhenServerReturnsBodyAndNoErrorsWhenHttp500Returned() throws IOException {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- try (Response> response = doRequest(client, "/error")) {
- assertEquals(500, response.getStatusCode());
- assertEquals("error", response.getBody().toString());
- }
- }
-
- // TODO (alzimmer): re-enable this test when progress reporting is added
- // @Test
- // public void testProgressReporter() throws IOException {
- // HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- //
- // ConcurrentLinkedDeque progress = new ConcurrentLinkedDeque<>();
- // HttpRequest request = new HttpRequest(HttpMethod.POST, uri("/shortPost"));
- // request.getHeaders().set(HttpHeaderName.CONTENT_LENGTH, String.valueOf(SHORT_BODY.length + LONG_BODY.length));
- // request.setBody(
- // BinaryData.fromListByteBuffer(Arrays.asList(ByteBuffer.wrap(LONG_BODY), ByteBuffer.wrap(SHORT_BODY))));
- //
- // Contexts contexts = Contexts.with(Context.NONE)
- // .setHttpRequestProgressReporter(ProgressReporter.withProgressListener(progress::add));
- //
- // try (Response> response = client.send(request)) {
- // assertEquals(200, response.getStatusCode());
- // List progressList = progress.stream().collect(Collectors.toList());
- // assertEquals(LONG_BODY.length, progressList.get(0));
- // assertEquals(SHORT_BODY.length + LONG_BODY.length, progressList.get(1));
- // }
- // }
-
- @Test
- public void testFileUpload() throws IOException {
- Path tempFile = writeToTempFile(LONG_BODY);
- tempFile.toFile().deleteOnExit();
- BinaryData body = BinaryData.fromFile(tempFile, 1L, 42L);
-
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- HttpRequest request = new HttpRequest(HttpMethod.POST, uri("/shortPostWithBodyValidation")).setBody(body);
-
- try (Response> response = client.send(request)) {
- assertEquals(200, response.getStatusCode());
- }
- }
-
- @Test
- public void testRequestBodyIsErrorShouldPropagateToResponse() {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- HttpRequest request = new HttpRequest(HttpMethod.POST, uri("/shortPost"));
- request.getHeaders().set(HttpHeaderName.CONTENT_LENGTH, "132");
- request.setBody(BinaryData.fromStream(new InputStream() {
- @Override
- public int read() throws IOException {
- throw new RuntimeException("boo");
- }
- }));
-
- IOException thrown = assertThrows(IOException.class, () -> client.send(request).close());
- assertEquals("boo", thrown.getCause().getMessage());
- }
-
- @Test
- public void testRequestBodyEndsInErrorShouldPropagateToResponse() {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- String contentChunk = "abcdefgh";
- int repetitions = 1000;
- HttpRequest request = new HttpRequest(HttpMethod.POST, uri("/shortPost"));
- request.getHeaders()
- .set(HttpHeaderName.CONTENT_LENGTH, String.valueOf(contentChunk.length() * (repetitions + 1)));
- request.setBody(BinaryData.fromStream(new InputStream() {
- int count = 0;
-
- @Override
- public int read() throws IOException {
- if (count++ < repetitions) {
- return contentChunk.charAt(count % contentChunk.length());
- } else {
- throw new RuntimeException("boo");
- }
- }
- }));
-
- IOException thrown = assertThrows(IOException.class, () -> client.send(request).close());
- assertEquals("boo", thrown.getCause().getMessage());
- }
-
- @Test
- public void testServerShutsDownSocketShouldPushErrorToContent() {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
-
- HttpRequest request = new HttpRequest(HttpMethod.GET, uri("/connectionClose"));
- assertThrows(IOException.class, () -> client.send(request).close());
- }
-
- @Test
- public void testConcurrentRequests() throws InterruptedException {
- int numRequests = 100; // 100 = 1GB of data read
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
-
- ForkJoinPool pool = new ForkJoinPool();
- List> requests = new ArrayList<>(numRequests);
- for (int i = 0; i < numRequests; i++) {
- requests.add(() -> {
- try (Response> response = doRequest(client, "/long")) {
- byte[] body = response.getBody().toBytes();
- TestUtils.assertArraysEqual(LONG_BODY, body);
- return null;
- }
- });
- }
-
- pool.invokeAll(requests);
- pool.shutdown();
- assertTrue(pool.awaitTermination(60, TimeUnit.SECONDS));
- }
-
- @Test
- public void testIOExceptionInWriteBodyTo() {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
-
- assertThrows(IOException.class, () -> {
- try (Response> response = doRequest(client, "/long")) {
- response.getBody().writeTo(new ThrowingWritableByteChannel());
- }
- });
- }
-
- @Test
- public void noResponseTimesOut() {
- HttpClient client = new JdkHttpClientBuilder().responseTimeout(Duration.ofSeconds(1)).build();
-
- assertThrows(HttpTimeoutException.class, () -> assertTimeout(Duration.ofSeconds(5), () -> {
- try (Response> response = doRequest(client, "/noResponse")) {
- assertNotNull(response);
- }
- }));
- }
-
- @Test
- public void slowStreamReadingTimesOut() {
- // Set both the response timeout and read timeout to make sure we aren't getting a response timeout when the
- // response body is slow to be sent.
- HttpClient client = new JdkHttpClientBuilder().responseTimeout(Duration.ofSeconds(1))
- .readTimeout(Duration.ofSeconds(1))
- .build();
-
- assertThrows(HttpTimeoutException.class, () -> assertTimeout(Duration.ofSeconds(5), () -> {
- try (Response> response = doRequest(client, "/slowResponse")) {
- TestUtils.assertArraysEqual(SHORT_BODY, response.getBody().toBytes());
- }
- }));
- }
-
- @Test
- public void slowEagerReadingTimesOut() {
- // Set both the response timeout and read timeout to make sure we aren't getting a response timeout when the
- // response body is slow to be sent.
- HttpClient client = new JdkHttpClientBuilder().responseTimeout(Duration.ofSeconds(1))
- .readTimeout(Duration.ofSeconds(1))
- .build();
-
- assertThrows(HttpTimeoutException.class, () -> assertTimeout(Duration.ofSeconds(5), () -> {
- try (Response> response = doRequest(client, "/slowResponse", ResponseBodyMode.BUFFER)) {
- TestUtils.assertArraysEqual(SHORT_BODY, response.getBody().toBytes());
- }
- }));
- }
-
- @Test
- public void testCustomSslContext() throws IOException, GeneralSecurityException {
- SSLContext sslContext = SSLContext.getInstance("TLSv1.2", Conscrypt.newProvider());
-
- // Initialize the SSL context with a trust manager that trusts all certificates.
- sslContext.init(null, new TrustManager[] { new InsecureTrustManager() }, null);
-
- HttpClient httpClient = new JdkHttpClientBuilder().sslContext(sslContext).build();
-
- try (Response> response = httpClient.send(new HttpRequest(HttpMethod.GET, httpsUri("/short")))) {
- TestUtils.assertArraysEqual(SHORT_BODY, response.getBody().toBytes());
- }
- }
-
- private static URI uri(String path) {
- try {
- return new URI(SERVER_HTTP_URI + path);
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static URI httpsUri(String path) {
- try {
- return new URI(SERVER_HTTPS_URI + path);
- } catch (URISyntaxException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static void checkBodyReceived(byte[] expectedBody, String path) throws IOException {
- HttpClient client = new JdkHttpClientProvider().getSharedInstance();
- try (Response> response = doRequest(client, path)) {
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- WritableByteChannel body = Channels.newChannel(outStream);
- response.getBody().writeTo(body);
- TestUtils.assertArraysEqual(expectedBody, outStream.toByteArray());
- }
- }
-
- private static Response> doRequest(HttpClient client, String path) throws IOException {
- return doRequest(client, path, null);
- }
-
- private static Response> doRequest(HttpClient client, String path, ResponseBodyMode bodyMode) throws IOException {
- HttpRequest request = new HttpRequest(HttpMethod.GET, uri(path))
- .setRequestOptions(new RequestOptions().setResponseBodyMode(bodyMode));
- return client.send(request);
- }
-
- private static Path writeToTempFile(byte[] body) throws IOException {
- Path tempFile = Files.createTempFile("data", null);
- tempFile.toFile().deleteOnExit();
- String tempFilePath = tempFile.toString();
- FileOutputStream outputStream = new FileOutputStream(tempFilePath);
- outputStream.write(body);
- outputStream.close();
- return tempFile;
- }
-
- private static final class ThrowingWritableByteChannel implements WritableByteChannel {
- private boolean open = true;
-
- @Override
- public int write(ByteBuffer src) throws IOException {
- throw new IOException();
- }
-
- @Override
- public boolean isOpen() {
- return open;
- }
-
- @Override
- public void close() throws IOException {
- open = false;
- }
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientTestsTests.java b/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientTestsTests.java
deleted file mode 100644
index 1ba2de8afba1a..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/JdkHttpClientTestsTests.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import io.clientcore.core.http.client.HttpClient;
-import io.clientcore.core.shared.HttpClientTests;
-import io.clientcore.core.shared.HttpClientTestsServer;
-import io.clientcore.core.shared.LocalTestServer;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.condition.DisabledForJreRange;
-import org.junit.jupiter.api.condition.JRE;
-import org.junit.jupiter.api.parallel.Execution;
-import org.junit.jupiter.api.parallel.ExecutionMode;
-
-@DisabledForJreRange(max = JRE.JAVA_11)
-@Execution(ExecutionMode.SAME_THREAD)
-public class JdkHttpClientTestsTests extends HttpClientTests {
- private static LocalTestServer server;
-
- @BeforeAll
- public static void startTestServer() {
- server = HttpClientTestsServer.getHttpClientTestsServer();
- server.start();
- }
-
- @AfterAll
- public static void stopTestServer() {
- if (server != null) {
- server.stop();
- }
- }
-
- @Override
- @Deprecated
- protected int getPort() {
- return server.getHttpPort();
- }
-
- @Override
- protected HttpClient getHttpClient() {
- return new JdkHttpClientProvider().getSharedInstance();
- }
-
- @Override
- protected String getServerUri(boolean secure) {
- return secure ? server.getHttpsUri() : server.getHttpUri();
- }
-
- @Override
- protected boolean isSecure() {
- return false;
- }
-}
diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/TestUtils.java b/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/TestUtils.java
deleted file mode 100644
index 653777555c605..0000000000000
--- a/sdk/clientcore/http-jdk-httpclient/src/test/java/io/clientcore/http/jdk/httpclient/TestUtils.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-package io.clientcore.http.jdk.httpclient;
-
-import org.junit.jupiter.api.Assertions;
-
-import java.util.Arrays;
-
-public final class TestUtils {
- /**
- * Asserts that two arrays are equal.
- *