diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 6f770af88a0da..b52d36caf04a5 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -204,7 +204,6 @@ "sdk/clientcore/core/**", "sdk/clientcore/core-json/**", "sdk/clientcore/http-okhttp3/**", - "sdk/clientcore/http-jdk-httpclient/**", "sdk/serialization/azure-json-gson/**", "sdk/serialization/azure-json/**", "sdk/serialization/azure-xml/**", diff --git a/common/perf-test-core/src/main/java/module-info.java b/common/perf-test-core/src/main/java/module-info.java index ca43808f6a39b..d364491172a31 100644 --- a/common/perf-test-core/src/main/java/module-info.java +++ b/common/perf-test-core/src/main/java/module-info.java @@ -16,4 +16,6 @@ requires io.netty.codec.http; requires okhttp3; requires io.vertx.core; + + exports com.azure.perf.test.core; } diff --git a/eng/.docsettings.yml b/eng/.docsettings.yml index 40e4cc792f34b..f8fa6e8c1ec40 100644 --- a/eng/.docsettings.yml +++ b/eng/.docsettings.yml @@ -141,7 +141,6 @@ known_content_issues: - ['sdk/parents/azure-code-customization-parent/README.md', '#3113'] - ['sdk/parents/azure-perf-test-parent/README.md', '#3113'] - ['sdk/parents/clientcore-parent/README.md', '#3113'] - - ['sdk/clientcore/http-jdk-httpclient/README.md', '#3113'] - ['sdk/search/azure-search-documents/src/test/README.md', '#3113'] - ['sdk/search/open-api/readme.md', '#3113'] - ['sdk/search/README.md', '#3113'] diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt index 03aab856d2a64..cc8d5afe24005 100644 --- a/eng/versioning/version_client.txt +++ b/eng/versioning/version_client.txt @@ -482,7 +482,6 @@ com.azure.tools:azure-sdk-build-tool;1.0.0;1.1.0-beta.1 io.clientcore:clientcore-parent;1.0.0-beta.1;1.0.0-beta.1 io.clientcore:core;1.0.0-beta.1;1.0.0-beta.1 io.clientcore:core-json;1.0.0-beta.1;1.0.0-beta.1 -io.clientcore:http-jdk-httpclient;1.0.0-beta.1;1.0.0-beta.1 io.clientcore:http-okhttp3;1.0.0-beta.1;1.0.0-beta.1 io.clientcore:http-stress;1.0.0-beta.1;1.0.0-beta.1 diff --git a/sdk/clientcore/ci.yml b/sdk/clientcore/ci.yml index e15ceb2f25e63..88da4ae902fcd 100644 --- a/sdk/clientcore/ci.yml +++ b/sdk/clientcore/ci.yml @@ -26,10 +26,6 @@ parameters: displayName: 'core' type: boolean default: true - - name: release_clientcorehttpjdkhttpclient - displayName: 'http-jdk-httpclient' - type: boolean - default: false - name: release_clientcorehttpokhttp3 displayName: 'http-okhttp3' type: boolean @@ -43,15 +39,17 @@ extends: template: /eng/pipelines/templates/stages/archetype-sdk-client.yml parameters: ServiceDirectory: clientcore + MatrixConfigs: + - Name: clientcore_ci_test_base + Path: sdk/clientcore/platform-matrix.json + Selection: sparse + NonSparseParameters: Agent + GenerateVMJobs: true Artifacts: - name: core groupId: io.clientcore safeName: core releaseInBatch: ${{ parameters.release_core }} - - name: http-jdk-httpclient - groupId: io.clientcore - safeName: httpjdkhttpclient - releaseInBatch: ${{ parameters.release_clientcorehttpjdkhttpclient }} - name: http-okhttp3 groupId: io.clientcore safeName: httpokhttp3 diff --git a/sdk/clientcore/core-json/pom.xml b/sdk/clientcore/core-json/pom.xml index a28f6e0f7d347..57ecd274b34fe 100644 --- a/sdk/clientcore/core-json/pom.xml +++ b/sdk/clientcore/core-json/pom.xml @@ -58,7 +58,6 @@ 0.85 0.75 - true checkstyle-suppressions.xml @@ -136,7 +135,7 @@ META-INF/** - **/io/clientcore/json/implementation/jackson/core/** + **/io/clientcore/core/json/implementation/jackson/core/** diff --git a/sdk/clientcore/core-json/src/main/java/module-info.java b/sdk/clientcore/core-json/src/main/java/module-info.java index c5cffba54f5cf..7197d91b7946b 100644 --- a/sdk/clientcore/core-json/src/main/java/module-info.java +++ b/sdk/clientcore/core-json/src/main/java/module-info.java @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/** + * This module provides a JSON tree model for representing JSON data. + */ module io.clientcore.core.json { exports io.clientcore.core.json; exports io.clientcore.core.json.models; diff --git a/sdk/clientcore/core/pom.xml b/sdk/clientcore/core/pom.xml index e51627c7b115f..0b0c863669acb 100644 --- a/sdk/clientcore/core/pom.xml +++ b/sdk/clientcore/core/pom.xml @@ -39,7 +39,7 @@ UTF-8 0.60 0.60 - true + --add-opens io.clientcore.core/io.clientcore.core.annotation=ALL-UNNAMED --add-opens io.clientcore.core/io.clientcore.core.credential=ALL-UNNAMED @@ -64,7 +64,6 @@ --add-exports io.clientcore.core/io.clientcore.core.shared=ALL-UNNAMED --add-exports io.clientcore.core/io.clientcore.core.implementation=ALL-UNNAMED - @@ -73,9 +72,7 @@ - - io.clientcore.core.json,com.azure.json,com.azure.xml,com.azure.core* - + io.clientcore.core.json* @@ -135,31 +132,11 @@ 2.5.2 test - - org.mockito - mockito-core - 4.11.0 - test - - - - - net.bytebuddy - byte-buddy - 1.15.5 - test - - - net.bytebuddy - byte-buddy-agent - 1.15.5 - test - - + org.apache.maven.plugins maven-jar-plugin diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java index 0b061e1dcb21e..2ae30c0bcd2e4 100644 --- a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java +++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClient.java @@ -3,333 +3,217 @@ package io.clientcore.core.http.client; -import io.clientcore.core.http.models.HttpHeader; +import io.clientcore.core.http.client.implementation.InputStreamTimeoutResponseSubscriber; +import io.clientcore.core.http.client.implementation.JdkHttpRequest; +import io.clientcore.core.http.client.implementation.JdkHttpResponse; import io.clientcore.core.http.models.HttpHeaderName; import io.clientcore.core.http.models.HttpHeaders; -import io.clientcore.core.http.models.HttpMethod; import io.clientcore.core.http.models.HttpRequest; -import io.clientcore.core.http.models.HttpResponse; -import io.clientcore.core.http.models.ProxyOptions; 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.implementation.AccessibleByteArrayOutputStream; -import io.clientcore.core.implementation.http.HttpResponseAccessHelper; -import io.clientcore.core.implementation.util.UriBuilder; -import io.clientcore.core.models.SocketConnection; -import io.clientcore.core.models.SocketConnectionCache; 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 javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.net.ConnectException; import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.ProtocolException; -import java.net.Proxy; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; +import java.net.http.HttpResponse; import java.time.Duration; -import java.util.Base64; -import java.util.List; -import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import static io.clientcore.core.http.client.implementation.JdkHttpUtils.fromJdkHttpHeaders; import static io.clientcore.core.http.models.ContentType.APPLICATION_OCTET_STREAM; -import static io.clientcore.core.http.models.HttpHeaderName.CONTENT_LENGTH; -import static io.clientcore.core.http.models.HttpHeaderName.CONTENT_TYPE; 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.NO_LISTENER_ERROR_MESSAGE; import static io.clientcore.core.util.ServerSentEventUtils.attemptRetry; +import static io.clientcore.core.util.ServerSentEventUtils.isTextEventStreamContentType; import static io.clientcore.core.util.ServerSentEventUtils.processTextEventStream; /** * HttpClient implementation using {@link HttpURLConnection} to send requests and receive responses. */ class DefaultHttpClient implements HttpClient { - // Implementation notes: - // Do not use HttpURLConnection.disconnect, rather use InputStream.close. Disconnect may close the Socket connection - // in keep alive scenarios which we don't want. InputStream.close may also close the Socket connection but it has - // finer control on whether that will happen based on keep alive information. - // In the scenario we receive an error response stream, keep alive won't be honored as the connection received an - // error status, and it will be handled appropriately. - private static final ClientLogger LOGGER = new ClientLogger(DefaultHttpClient.class); - private final long connectionTimeout; - private final long readTimeout; - private final ProxyOptions proxyOptions; - private final SSLSocketFactory sslSocketFactory; - private static final int MAX_CONNECTIONS; - private static final boolean KEEP_CONNECTION_ALIVE; - private static final SocketConnectionCache SOCKET_CONNECTION_CACHE; - - static { - String keepAlive = System.getProperty("http.keepAlive"); - KEEP_CONNECTION_ALIVE = keepAlive != null && Boolean.parseBoolean(keepAlive); - - String maxConnectionsString = System.getProperty("http.maxConnections"); - MAX_CONNECTIONS = maxConnectionsString != null ? Integer.parseInt(maxConnectionsString) : 0; - SOCKET_CONNECTION_CACHE = SocketConnectionCache.getInstance(KEEP_CONNECTION_ALIVE, MAX_CONNECTIONS); - } - - DefaultHttpClient(Duration connectionTimeout, Duration readTimeout, ProxyOptions proxyOptions, - SSLSocketFactory sslSocketFactory) { - this.connectionTimeout = connectionTimeout == null ? -1 : connectionTimeout.toMillis(); - this.readTimeout = readTimeout == null ? -1 : readTimeout.toMillis(); - this.proxyOptions = proxyOptions; - this.sslSocketFactory = sslSocketFactory; - } - - @Override - public Response send(HttpRequest httpRequest) throws IOException { - SocketConnection socketConnection; - if (httpRequest.getHttpMethod() == HttpMethod.PATCH) { - final URI requestUri = httpRequest.getUri(); - final String protocol = requestUri.getScheme(); - final String host = requestUri.getHost(); - final int port = requestUri.getPort(); - - socketConnection = SOCKET_CONNECTION_CACHE.get(new SocketConnection.SocketConnectionProperties(protocol, - host, port, getSslSocketFactory(), (int) readTimeout)); - - Response response = SocketClient.sendPatchRequest(httpRequest, socketConnection.getSocketInputStream(), - socketConnection.getSocketOutputStream()); - - // Handle connection reusing - SOCKET_CONNECTION_CACHE.reuseConnection(socketConnection); - return response; - - } else { - HttpURLConnection urlConnection = connect(httpRequest); - sendBody(httpRequest, urlConnection); - return receiveResponse(httpRequest, urlConnection); - } - } + private final Set restrictedHeaders; + private final Duration writeTimeout; + private final Duration responseTimeout; + private final Duration readTimeout; + private final boolean hasReadTimeout; - private SSLSocketFactory getSslSocketFactory() { - return (sslSocketFactory != null) ? sslSocketFactory : (SSLSocketFactory) SSLSocketFactory.getDefault(); - } + final java.net.http.HttpClient jdkHttpClient; - /** - * Open a connection based on the HttpRequest URL - * - *

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. * - *

By default, the connection timeout is 10 seconds.

+ *

Code Samples

* - * @param connectionTimeout Connect timeout {@link Duration}. + * + *
+     * HttpClient client = new DefaultHttpClientBuilder()
+     *         .connectionTimeout(Duration.ofSeconds(250)) // connection timeout of 250 seconds
+     *         .build();
+     * 
+ * * - * @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/DefaultHttpClientProvider.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClientProvider.java new file mode 100644 index 0000000000000..c1d502e85c837 --- /dev/null +++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/DefaultHttpClientProvider.java @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package io.clientcore.core.http.client; + +/** + * An {@link HttpClientProvider} that provides an implementation of HttpClient based on the default {@link HttpClient}. + */ +public class DefaultHttpClientProvider extends HttpClientProvider { + // Enum Singleton Pattern + private enum GlobalDefaultHttpClient { + HTTP_CLIENT(new DefaultHttpClientBuilder().build()); + + private final HttpClient httpClient; + + GlobalDefaultHttpClient(HttpClient httpClient) { + this.httpClient = httpClient; + } + + private HttpClient getHttpClient() { + return httpClient; + } + } + + /** + * Creates a new instance of {@link DefaultHttpClientProvider}. + */ + public DefaultHttpClientProvider() { + } + + @Override + public HttpClient getNewInstance() { + return new DefaultHttpClientBuilder().build(); + } + + @Override + public HttpClient getSharedInstance() { + return GlobalDefaultHttpClient.HTTP_CLIENT.getHttpClient(); + } +} diff --git a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClient.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClient.java index d68b5e2c6a62f..3de633ad942e1 100644 --- a/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClient.java +++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/http/client/HttpClient.java @@ -48,7 +48,6 @@ static HttpClient getNewInstance() { */ static HttpClient getSharedInstance() { return HttpClientProvider.getProviders() - .create(HttpClientProvider::getSharedInstance, - HttpClientProvider.GlobalDefaultHttpClient.HTTP_CLIENT::getHttpClient, null); + .create(HttpClientProvider::getSharedInstance, new DefaultHttpClientProvider()::getSharedInstance, null); } } 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..dcaf2b8c6361c 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 @@ -11,27 +11,19 @@ * implementation. */ public abstract class HttpClientProvider { - enum GlobalDefaultHttpClient { - HTTP_CLIENT(new DefaultHttpClientBuilder().build()); - - private final HttpClient httpClient; - - GlobalDefaultHttpClient(HttpClient httpClient) { - this.httpClient = httpClient; - } - - HttpClient getHttpClient() { - return httpClient; - } - } - - static final String NO_DEFAULT_PROVIDER_MESSAGE = "A request was made to load the default HttpClient provider " - + "but one could not be found on the classpath. If you are using a dependency manager, consider including " - + "a dependency on io.clientcore:http-okhttp3 or io.clientcore:http-jdk-httpclient. Additionally, refer to " + static final String NO_DEFAULT_PROVIDER_MESSAGE = "A request was made to load the default HttpClient provider but " + + "one could not be found on the classpath. If you are using a dependency manager, consider including a " + + "dependency on io.clientcore:http-okhttp3. Additionally, refer to " + "https://aka.ms/azsdk/java/docs/custom-httpclient to learn about writing your own implementation."; 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 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/implementation/util/Providers.java b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/Providers.java index 5c64081d17c3c..5e8abe009477c 100644 --- a/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/Providers.java +++ b/sdk/clientcore/core/src/main/java/io/clientcore/core/implementation/util/Providers.java @@ -81,14 +81,11 @@ public Providers(Class providerClass, String defaultImplementationNam } private String formatNoSpecificProviderErrorMessage(String selectedImplementation) { - return String.format( - "A request was made to use a specific " - + "%s but it wasn't found on the classpath. If you're using a dependency manager ensure you're " - + "including the dependency that provides the specific implementation. If you're including the " - + "specific implementation ensure that the %s service it supplies is being included in the " - + "'META-INF/services' file '%s'. The requested provider was: %s.", - providerClass.getSimpleName(), providerClass.getSimpleName(), providerClass.getName(), - selectedImplementation); + return "A request was made to use a specific " + providerClass.getSimpleName() + " but it wasn't found on the " + + "classpath. If you're using a dependency manager ensure you're including the dependency that provides " + + "the specific implementation. If you're including the specific implementation ensure that the " + + providerClass.getSimpleName() + " service it supplies is being included in the 'META-INF/services' file " + + "'" + providerClass.getName() + "'. The requested provider was: " + selectedImplementation + "."; } /** 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..cf892be81de20 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,8 @@ exports io.clientcore.core.util.serializer; exports io.clientcore.core.util.auth; - uses HttpClientProvider; + uses io.clientcore.core.http.client.HttpClientProvider; + + provides io.clientcore.core.http.client.HttpClientProvider + with io.clientcore.core.http.client.DefaultHttpClientProvider; } diff --git a/sdk/clientcore/core/src/main/resources/META-INF/services/io.clientcore.core.http.client.HttpClientProvider b/sdk/clientcore/core/src/main/resources/META-INF/services/io.clientcore.core.http.client.HttpClientProvider new file mode 100644 index 0000000000000..2285f70f5ae9e --- /dev/null +++ b/sdk/clientcore/core/src/main/resources/META-INF/services/io.clientcore.core.http.client.HttpClientProvider @@ -0,0 +1 @@ +io.clientcore.core.http.client.DefaultHttpClientProvider 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/http/client/DefaultHttpClientTests.java b/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientTests.java index f6b95be039cbe..fdadb41637058 100644 --- a/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientTests.java +++ b/sdk/clientcore/core/src/test/java/io/clientcore/core/http/client/DefaultHttpClientTests.java @@ -27,7 +27,7 @@ public static void stopTestServer() { @Override protected HttpClient getHttpClient() { - return HttpClient.getSharedInstance(); + return new DefaultHttpClientProvider().getSharedInstance(); } @Override 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. - * - *

Code Samples

- * - * - *
-     * HttpClient client = new JdkHttpClientBuilder()
-     *         .connectionTimeout(Duration.ofSeconds(250)) // connection timeout of 250 seconds
-     *         .build();
-     * 
- * - * - * 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. - *

- * This method is similar to JUnit's {@link Assertions#assertArrayEquals(byte[], byte[])} except that it takes - * advantage of hardware intrinsics offered by the JDK to optimize comparing the byte arrays. - *

- * If the arrays aren't equal this will call {@link Assertions#assertArrayEquals(byte[], byte[])} to take advantage - * of the better error message, but this is the exceptional case and worth the double comparison performance hit. - * - * @param expected The expected byte array. - * @param actual The actual byte array. - */ - public static void assertArraysEqual(byte[] expected, byte[] actual) { - if (!Arrays.equals(expected, actual)) { - Assertions.assertArrayEquals(expected, actual); - } - } - - private TestUtils() { - } -} diff --git a/sdk/clientcore/http-jdk-httpclient/src/test/resources/upload.txt b/sdk/clientcore/http-jdk-httpclient/src/test/resources/upload.txt deleted file mode 100644 index ff3bb63948b4b..0000000000000 --- a/sdk/clientcore/http-jdk-httpclient/src/test/resources/upload.txt +++ /dev/null @@ -1 +0,0 @@ -The quick brown fox jumps over the lazy dog \ No newline at end of file diff --git a/sdk/clientcore/http-okhttp3/pom.xml b/sdk/clientcore/http-okhttp3/pom.xml index fff3012d33471..66ab6c8a73bc0 100644 --- a/sdk/clientcore/http-okhttp3/pom.xml +++ b/sdk/clientcore/http-okhttp3/pom.xml @@ -74,6 +74,12 @@ com.squareup.okhttp3 okhttp 4.12.0 + + + com.squareup.okio + okio + + diff --git a/sdk/clientcore/http-okhttp3/src/main/java/io/clientcore/http/okhttp3/OkHttpHttpClientProvider.java b/sdk/clientcore/http-okhttp3/src/main/java/io/clientcore/http/okhttp3/OkHttpHttpClientProvider.java index a9fe7ff04f7c1..1f2218152d362 100644 --- a/sdk/clientcore/http-okhttp3/src/main/java/io/clientcore/http/okhttp3/OkHttpHttpClientProvider.java +++ b/sdk/clientcore/http-okhttp3/src/main/java/io/clientcore/http/okhttp3/OkHttpHttpClientProvider.java @@ -25,6 +25,12 @@ private HttpClient getHttpClient() { } } + /** + * Creates a new instance of {@link OkHttpHttpClientProvider}. + */ + public OkHttpHttpClientProvider() { + } + @Override public HttpClient getNewInstance() { return new OkHttpHttpClientBuilder().build(); diff --git a/sdk/clientcore/http-okhttp3/src/main/java/module-info.java b/sdk/clientcore/http-okhttp3/src/main/java/module-info.java index 99b6e29e6cc18..8a10a52f9b6d3 100644 --- a/sdk/clientcore/http-okhttp3/src/main/java/module-info.java +++ b/sdk/clientcore/http-okhttp3/src/main/java/module-info.java @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/** + * Provides the classes necessary to create an HTTP client using OkHttp. + */ module io.clientcore.http.okhttp3 { requires transitive io.clientcore.core; diff --git a/sdk/clientcore/http-stress/dockerfiles/java11 b/sdk/clientcore/http-stress/dockerfiles/java17 similarity index 92% rename from sdk/clientcore/http-stress/dockerfiles/java11 rename to sdk/clientcore/http-stress/dockerfiles/java17 index 581fcf17b7fc4..12d0470c5f756 100644 --- a/sdk/clientcore/http-stress/dockerfiles/java11 +++ b/sdk/clientcore/http-stress/dockerfiles/java17 @@ -1,6 +1,6 @@ ARG REGISTRY="azsdkengsys.azurecr.io" -ARG JRE_VERSION="11" -FROM ${REGISTRY}/java/jdk-mariner-mvn:jdk11-latest as builder +ARG JRE_VERSION="17" +FROM ${REGISTRY}/java/jdk-mariner-mvn:jdk17-latest as builder # Do not remove this line. Update ensures container images do not get flagged for out of date and vulnerable distro packages. RUN yum -y update @@ -28,7 +28,6 @@ mvn -f /stress/sdk/tools/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/parents/clientcore-parent/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/core-json/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/core/pom.xml clean install ${SKIP_CHECKS} && \ -mvn -f /stress/sdk/clientcore/http-jdk-httpclient/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/http-okhttp3/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/http-stress/pom.xml clean install ${SKIP_CHECKS} diff --git a/sdk/clientcore/http-stress/dockerfiles/java21 b/sdk/clientcore/http-stress/dockerfiles/java21 index f750230817d67..33a44888916cb 100644 --- a/sdk/clientcore/http-stress/dockerfiles/java21 +++ b/sdk/clientcore/http-stress/dockerfiles/java21 @@ -28,7 +28,6 @@ mvn -f /stress/sdk/tools/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/parents/clientcore-parent/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/core-json/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/core/pom.xml clean install ${SKIP_CHECKS} && \ -mvn -f /stress/sdk/clientcore/http-jdk-httpclient/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/http-okhttp3/pom.xml clean install ${SKIP_CHECKS} && \ mvn -f /stress/sdk/clientcore/http-stress/pom.xml clean install ${SKIP_CHECKS} diff --git a/sdk/clientcore/http-stress/pom.xml b/sdk/clientcore/http-stress/pom.xml index fe4026f1f478d..ea803e4af4364 100644 --- a/sdk/clientcore/http-stress/pom.xml +++ b/sdk/clientcore/http-stress/pom.xml @@ -34,11 +34,6 @@ core 1.0.0-beta.1 - - io.clientcore - http-jdk-httpclient - 1.0.0-beta.1 - io.clientcore http-okhttp3 diff --git a/sdk/clientcore/http-stress/scenarios-matrix.yaml b/sdk/clientcore/http-stress/scenarios-matrix.yaml index ecd7a78dc5dec..5a6500668ca2b 100644 --- a/sdk/clientcore/http-stress/scenarios-matrix.yaml +++ b/sdk/clientcore/http-stress/scenarios-matrix.yaml @@ -1,12 +1,12 @@ displayNames: java-template: "" dockerfiles/java21: jre21 - dockerfiles/java11: jre11 + dockerfiles/java17: jre17 matrix: image: - - dockerfiles/java11 + - dockerfiles/java17 - dockerfiles/java21 - httpClient: [default, okhttp, jdk] + httpClient: [default, okhttp] scenarios: get: imageBuildDir: ..\..\..\ diff --git a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/App.java b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/App.java index 57c301546483b..5832931adebf8 100644 --- a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/App.java +++ b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/App.java @@ -9,8 +9,7 @@ /** * Stress test application */ -public class App { - +public final class App { /** * Main method to invoke other stress tests. * @@ -24,4 +23,7 @@ public static void main(String[] args) { // add other stress tests here }, args); } + + private App() { + } } diff --git a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpGet.java b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpGet.java index 5bb3944b4937f..3953b16994189 100644 --- a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpGet.java +++ b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpGet.java @@ -9,14 +9,12 @@ import io.clientcore.core.http.models.HttpLogOptions; import io.clientcore.core.http.models.HttpMethod; import io.clientcore.core.http.models.HttpRequest; -import io.clientcore.core.http.models.HttpResponse; import io.clientcore.core.http.models.Response; import io.clientcore.core.http.pipeline.HttpLoggingPolicy; import io.clientcore.core.http.pipeline.HttpPipeline; import io.clientcore.core.http.pipeline.HttpPipelineBuilder; import io.clientcore.core.http.pipeline.HttpRetryPolicy; import io.clientcore.core.util.ClientLogger; -import io.clientcore.http.jdk.httpclient.JdkHttpClientProvider; import io.clientcore.http.okhttp3.OkHttpHttpClientProvider; import io.clientcore.http.stress.util.TelemetryHelper; import reactor.core.publisher.Mono; @@ -97,7 +95,7 @@ public Runnable runAsyncWithVirtualThread() { private Mono runInternalAsync() { return Mono.usingWhen(Mono.fromCallable(() -> pipeline.send(createRequest())), response -> { - ((HttpResponse) response).getBody().toBytes(); + response.getBody().toBytes(); return Mono.empty(); }, response -> Mono.fromRunnable(() -> { try { @@ -111,8 +109,7 @@ private Mono runInternalAsync() { // Method to run using CompletableFuture private CompletableFuture runAsyncWithCompletableFutureInternal() { return CompletableFuture.supplyAsync(() -> { - try { - Response response = pipeline.send(createRequest()); + try (Response response = pipeline.send(createRequest())) { response.getBody().toBytes(); } catch (Exception e) { LOGGER.logThrowableAsError(e); @@ -123,28 +120,24 @@ private CompletableFuture runAsyncWithCompletableFutureInternal() { // Method to run using ExecutorService private Runnable runAsyncWithExecutorServiceInternal() { - Runnable task = () -> { - try { - Response response = pipeline.send(createRequest()); + return () -> { + try (Response response = pipeline.send(createRequest())) { response.getBody().toBytes(); } catch (Exception e) { LOGGER.logThrowableAsError(e); } }; - return task; } // Method to run using Virtual Threads private Runnable runAsyncWithVirtualThreadInternal() { - Runnable task = () -> { - try { - Response response = pipeline.send(createRequest()); + return () -> { + try (Response response = pipeline.send(createRequest())) { response.getBody().toBytes(); } catch (Exception e) { LOGGER.logThrowableAsError(e); } }; - return task; } private HttpRequest createRequest() { @@ -163,8 +156,6 @@ private HttpPipelineBuilder getPipelineBuilder() { if (options.getHttpClient() == PerfStressOptions.HttpClientType.OKHTTP) { builder.httpClient(new OkHttpHttpClientProvider().getSharedInstance()); - } else if (options.getHttpClient() == PerfStressOptions.HttpClientType.JDK) { - builder.httpClient(new JdkHttpClientProvider().getSharedInstance()); } else { builder.httpClient(new DefaultHttpClientBuilder().build()); } diff --git a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpPatch.java b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpPatch.java index d6f7d00a66282..12b2fa87ac3fb 100644 --- a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpPatch.java +++ b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/HttpPatch.java @@ -16,7 +16,6 @@ import io.clientcore.core.http.pipeline.HttpRetryPolicy; import io.clientcore.core.util.ClientLogger; import io.clientcore.core.util.binarydata.BinaryData; -import io.clientcore.http.jdk.httpclient.JdkHttpClientProvider; import io.clientcore.http.okhttp3.OkHttpHttpClientProvider; import io.clientcore.http.stress.util.TelemetryHelper; import reactor.core.publisher.Mono; @@ -95,8 +94,6 @@ private HttpPipelineBuilder getPipelineBuilder() { if (options.getHttpClient() == PerfStressOptions.HttpClientType.OKHTTP) { builder.httpClient(new OkHttpHttpClientProvider().getSharedInstance()); - } else if (options.getHttpClient() == PerfStressOptions.HttpClientType.JDK) { - builder.httpClient(new JdkHttpClientProvider().getSharedInstance()); } else { builder.httpClient(new DefaultHttpClientBuilder().build()); } diff --git a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/ScenarioBase.java b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/ScenarioBase.java index 1c9a1a023aced..079c98850de8e 100644 --- a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/ScenarioBase.java +++ b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/ScenarioBase.java @@ -9,6 +9,8 @@ /** * Performance test for getting messages. + * + * @param The options configured for the test. */ public abstract class ScenarioBase extends PerfStressTest { private final TelemetryHelper telemetryHelper; diff --git a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/StressOptions.java b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/StressOptions.java index 713be84a762df..dcdbe3b62f781 100644 --- a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/StressOptions.java +++ b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/StressOptions.java @@ -13,6 +13,12 @@ public class StressOptions extends PerfStressOptions { @Parameter(names = { "--endpoint" }, description = "Service endpoint") private String serviceEndpoint; + /** + * Creates a new instance of {@link StressOptions}. + */ + public StressOptions() { + } + /** * Gets the service endpoint. * @return the service endpoint. diff --git a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/util/TelemetryHelper.java b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/util/TelemetryHelper.java index 2eabbb9bb02bf..6f365d14bb95e 100644 --- a/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/util/TelemetryHelper.java +++ b/sdk/clientcore/http-stress/src/main/java/io/clientcore/http/stress/util/TelemetryHelper.java @@ -3,7 +3,8 @@ package io.clientcore.http.stress.util; -import com.azure.monitor.opentelemetry.exporter.AzureMonitorExporterBuilder; +import com.azure.monitor.opentelemetry.exporter.AzureMonitorExporter; +import com.azure.monitor.opentelemetry.exporter.AzureMonitorExporterOptions; import io.clientcore.core.util.ClientLogger; import io.clientcore.http.stress.StressOptions; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -100,7 +101,8 @@ public static void init() { AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder(); String applicationInsightsConnectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"); if (applicationInsightsConnectionString != null) { - new AzureMonitorExporterBuilder().connectionString(applicationInsightsConnectionString).install(sdkBuilder); + AzureMonitorExporter.customize(AutoConfiguredOpenTelemetrySdk.builder(), + new AzureMonitorExporterOptions().connectionString(applicationInsightsConnectionString)); } else { System.setProperty("otel.traces.exporter", "none"); System.setProperty("otel.logs.exporter", "none"); @@ -145,7 +147,7 @@ public String getDescription() { public void instrumentRun(Runnable oneRun) { long start = System.currentTimeMillis(); Span span = tracer.spanBuilder("run").startSpan(); - try (Scope s = span.makeCurrent()) { + try (Scope ignored = span.makeCurrent()) { oneRun.run(); trackSuccess(start, span); } catch (Throwable e) { @@ -170,7 +172,7 @@ public Mono instrumentRunAsync(Mono runAsync) { return Mono.defer(() -> { long start = System.currentTimeMillis(); Span span = tracer.spanBuilder("runAsync").startSpan(); - try (Scope s = span.makeCurrent()) { + try (Scope ignored = span.makeCurrent()) { return runAsync.doOnError(e -> trackFailure(start, e, span)) .doOnCancel(() -> trackCancellation(start, span)) .doOnSuccess(v -> trackSuccess(start, span)) @@ -199,7 +201,7 @@ public CompletableFuture instrumentRunAsyncWithCompletableFuture(Completab Span span = startAndSpan.getValue(); return runAsyncFuture.whenComplete((result, throwable) -> { - try (Scope s = span.makeCurrent()) { + try (Scope ignored = span.makeCurrent()) { if (throwable != null) { trackFailure(start, throwable, span); } else { @@ -223,7 +225,7 @@ public Runnable instrumentRunAsyncWithRunnable(Runnable task) { return () -> { long start = System.currentTimeMillis(); Span span = tracer.spanBuilder("runAsyncRunnable").startSpan(); - try (Scope s = span.makeCurrent()) { + try (Scope ignored = span.makeCurrent()) { try { task.run(); trackSuccess(start, span); diff --git a/sdk/clientcore/http-stress/src/main/java/module-info.java b/sdk/clientcore/http-stress/src/main/java/module-info.java new file mode 100644 index 0000000000000..8d821220ba11f --- /dev/null +++ b/sdk/clientcore/http-stress/src/main/java/module-info.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Contains classes for stress tests. + */ +module io.clientcore.http.stress { + requires transitive com.azure.core.test.perf; + requires transitive io.clientcore.core; + requires transitive io.clientcore.core.json; + requires transitive io.clientcore.http.okhttp3; + + requires azure.monitor.opentelemetry.exporter; + requires com.azure.core; + requires jcommander; + requires io.opentelemetry.api; + requires io.opentelemetry.context; + requires io.opentelemetry.instrumentation.logback_appender_1_0; + requires io.opentelemetry.instrumentation.runtime_telemetry_java8; + requires io.opentelemetry.sdk; + requires io.opentelemetry.sdk.autoconfigure; + requires io.opentelemetry.sdk.autoconfigure.spi; + requires io.opentelemetry.sdk.trace; + + exports io.clientcore.http.stress; + exports io.clientcore.http.stress.util; +} diff --git a/sdk/clientcore/perf-tests.yml b/sdk/clientcore/perf-tests.yml index 42cee61789be6..58ba6b43d370d 100644 --- a/sdk/clientcore/perf-tests.yml +++ b/sdk/clientcore/perf-tests.yml @@ -8,7 +8,6 @@ PackageVersions: - 'io.clientcore:core': 1.0.0-beta.1 # {x-version-update;io.clientcore:core;dependency} 'io.clientcore:core-json': 1.0.0-beta.1 # {x-version-update;io.clientcore:core-json;dependency} 'io.clientcore:http-okhttp3': 1.0.0-beta.1 # {x-version-update;io.clientcore:http-okhttp3;dependency} -- 'io.clientcore:http-jdk-httpclient': # {x-version-update;io.clientcore:http-jdk-httpclient;dependency} Tests: - Test: httpget @@ -16,4 +15,3 @@ Tests: Arguments: - --sync --http-client default - --sync --http-client okhttp - - --sync --http-client jdk diff --git a/sdk/clientcore/platform-matrix.json b/sdk/clientcore/platform-matrix.json new file mode 100644 index 0000000000000..882d9f0b23c2d --- /dev/null +++ b/sdk/clientcore/platform-matrix.json @@ -0,0 +1,73 @@ +{ + "displayNames": { + "io.clientcore.core.http.client.DefaultHttpClientProvider": "DefaultHttpClient", + "io.clientcore.http.okhttp3.OkHttpHttpClientProvider": "OkHttpHttpClient" + }, + "matrix": { + "Agent": { + "ubuntu-20.04": { "OSVmImage": "env:LINUXVMIMAGE", "Pool": "env:LINUXPOOL" }, + "windows-2022": { "OSVmImage": "env:WINDOWSVMIMAGE", "Pool": "env:WINDOWSPOOL" }, + "macos-latest": { "OSVmImage": "env:MACVMIMAGE", "Pool": "env:MACPOOL" } + }, + "JavaTestVersion": [ "1.17", "1.21" ], + "TEST_HTTP_CLIENT_IMPLEMENTATION": [ "io.clientcore.core.http.client.DefaultHttpClientProvider", "io.clientcore.http.okhttp3.OkHttpHttpClientProvider" ], + "TestFromSource": { + "NotFromSource": { "TestFromSource": false } + }, + "RunAggregateReports": { + "$": { "RunAggregateReports": false } + }, + "TestOptions": { + "$": { "TestOptions": "" } + }, + "TestGoalsGrouping": { + "TestsOnly": { "TestGoals": "surefire:test failsafe:integration-test failsafe:verify" } + } + }, + "exclude": [ + { + "Pool": "env:LINUXPOOL", + "JavaTestVersion": "1.21" + } + ], + "include": [ + { + "Agent": { + "ubuntu-20.04": { "OSVmImage": "env:LINUXVMIMAGE", "Pool": "env:LINUXPOOL" } + }, + "JavaTestVersion": "1.21", + "TEST_HTTP_CLIENT_IMPLEMENTATION": "io.clientcore.core.http.client.DefaultHttpClientProvider", + "TestFromSource": { + "TestFromSource": { "TestFromSource": true } + }, + "RunAggregateReports": { + "$": { "RunAggregateReports": false } + }, + "TestOptions": { + "SkipRebuild": { "TestOptions": "-DskipCompile=true -DskipTestCompile=true -DcreateSourcesJar=false" } + }, + "TestGoalsGrouping": { + "Verify": { "TestGoals": "verify" } + } + }, + { + "Agent": { + "ubuntu-20.04": { "OSVmImage": "env:LINUXVMIMAGE", "Pool": "env:LINUXPOOL" } + }, + "JavaTestVersion": "1.21", + "TEST_HTTP_CLIENT_IMPLEMENTATION": "io.clientcore.core.http.client.DefaultHttpClientProvider", + "TestFromSource": { + "$": { "TestFromSource": false } + }, + "RunAggregateReports": { + "AggregateReports": { "RunAggregateReports": true } + }, + "TestOptions": { + "SkipRebuild": { "TestOptions": "-DskipCompile=true -DskipTestCompile=true -DcreateSourcesJar=false" } + }, + "TestGoalsGrouping": { + "Verify": { "TestGoals": "verify" } + } + } + ] +} diff --git a/sdk/clientcore/pom.xml b/sdk/clientcore/pom.xml index 259f8bc66bc9f..6d657936d431c 100644 --- a/sdk/clientcore/pom.xml +++ b/sdk/clientcore/pom.xml @@ -12,7 +12,6 @@ core core-json - http-jdk-httpclient http-okhttp3 http-stress diff --git a/sdk/parents/clientcore-parent/pom.xml b/sdk/parents/clientcore-parent/pom.xml index 88722349c211f..107ae14cebefc 100644 --- a/sdk/parents/clientcore-parent/pom.xml +++ b/sdk/parents/clientcore-parent/pom.xml @@ -86,13 +86,10 @@ ${project.build.directory} ${project.build.testSourceDirectory} - playback - 11080 - 11081 https://azuresdkartifacts.blob.core.windows.net/azure-sdk-for-java https://github.com/Azure/azure-sdk-for-java/issues - azure-client-sdk-parent + clientcore-parent 0.40 0.30 false @@ -168,16 +165,13 @@ false - + true true - - true - ${project.basedir}/${relative.path.to.eng.folder}/eng/code-quality-reports/src/main/resources/checkstyle/checkstyle-suppressions.xml @@ -204,31 +198,22 @@ true - - all - - + false false none - - - - - - - - io.clientcore.core* - ${basedir}/src/main/java:${basedir}/../../clientcore/core/src/main/java:${basedir}/../../clientcore/core-json/src/main/java:${basedir}/../../clientcore/core-okhttp3/src/main/java + @@ -757,7 +742,7 @@ -Xlint:cast -Xlint:classfile - -Xlint:${compiler.failondeprecatedstatus}deprecation + -Xlint:deprecation -Xlint:dep-ann -Xlint:divzero -Xlint:empty @@ -773,6 +758,9 @@ -Xlint:try -Xlint:unchecked -Xlint:varargs + -Xlint:-module + -Xlint:removal + -Xlint:-requires-transitive-automatic @@ -808,7 +796,7 @@ samedir= UTF-8 true - ${checkstyle.includeTestSourceDirectory} + true true ${checkstyle.failsOnError} ${checkstyle.failOnViolation} @@ -849,9 +837,6 @@ ${spotbugs.failOnError} ${spotbugs.includeTests} - - ${java.security.manager.configuration} - @@ -870,9 +855,9 @@ maven-javadoc-plugin 3.10.1 - 1.8 - Azure SDK for Java Reference Documentation - Azure SDK for Java Reference Documentation + 17 + ClientCore SDK for Java Reference Documentation + ClientCore SDK for Java Reference Documentation Visit the <a href="https://docs.microsoft.com/java/azure/">Azure for Java Developers</a> site for more Java documentation, including quick starts, tutorials, and code samples. @@ -891,8 +876,8 @@ false true true - false - ${doclint} + true + all true ${packageOutputDirectory} @@ -921,6 +906,7 @@ false ${defaultSurefireArgLine} + ${javaModulesSurefireArgLine} ${additionalSurefireArgLine} @@ -973,6 +959,7 @@ false ${defaultFailsafeArgLine} + ${javaModulesFailsafeArgLine} ${additionalFailsafeArgLine} @@ -1087,33 +1074,6 @@ - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - com.googlecode.addjars-maven-plugin - addjars-maven-plugin - [1.0.5,) - - add-jars - - - - - - - - - - @@ -1189,73 +1149,42 @@ - - - java8 + java-baseline - [1.8,9) + 17 - 8 - 8 + 17 + 17 - org.apache.maven.plugins maven-compiler-plugin 3.13.0 - - 1.8 - 1.8 - - module-info.java - - - module-info.java - - - 1.8 - 1.8 - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.10.1 - - - module-info.java - - + + + default-testCompile + + testCompile + + + false + + + - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.10.1 - - - module-info.java - - - - - - + - java9plus + java-18-plus - [9,) + [18,) @@ -1263,13 +1192,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.13.0 - - - -Xlint:-module - -Xlint:removal - -Xlint:-requires-transitive-automatic - - @@ -1279,41 +1201,23 @@ compile + + -Xlint:-options + ${java.vm.specification.version} - - - - - base-modules-compile - - compile - - - 11 - - module-info.java - - - - - + + base-compile compile - - -Xlint:-options - - 8 - - module-info.java - + 17 @@ -1324,65 +1228,29 @@ testCompile + + -Xlint:-options + ${java.vm.specification.version} ${java.vm.specification.version} false - + base-testCompile testCompile - - -Xlint:-options - - 8 - 8 - - module-info.java - + 17 + 17 false - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.1 - - - ${defaultSurefireArgLine} - ${javaModulesSurefireArgLine} - - - --add-opens java.base/java.lang.invoke=io.clientcore.core - ${additionalSurefireArgLine} - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.5.1 - - - ${defaultFailsafeArgLine} - ${javaModulesFailsafeArgLine} - - - --add-opens java.base/java.lang.invoke=io.clientcore.core - ${additionalFailsafeArgLine} - ${java.security.manager.configuration} - - - @@ -1480,92 +1348,6 @@ - - parallel-test-playback-no-azure-test-mode-env - - - !env.AZURE_TEST_MODE - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.1 - - ${AZURE_TEST_SYSTEM_PLAYBACK_PROPERTIES_FILE} - - - - - - - - parallel-test-playback-azure-test-mode-playback-env - - - env.AZURE_TEST_MODE - PLAYBACK - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.1 - - ${AZURE_TEST_SYSTEM_PLAYBACK_PROPERTIES_FILE} - - - - - - - - parallel-test-playback-no-azure-test-mode-prop - - - AZURE_TEST_MODE - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.1 - - ${AZURE_TEST_SYSTEM_PLAYBACK_PROPERTIES_FILE} - - - - - - - - parallel-test-playback-azure-test-mode-playback-prop - - - AZURE_TEST_MODE - PLAYBACK - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.1 - - ${AZURE_TEST_SYSTEM_PLAYBACK_PROPERTIES_FILE} - - - - - - sequential-test-azure-test-mode-live-env @@ -1704,54 +1486,5 @@ - - - test-module-base-compile - - - ${projectTestSourceDirectory}/module-info.java - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.13.0 - - - - - - base-modules-testCompile - - testCompile - - - 11 - 11 - - module-info.java - - - - - - - - - - - java-18-plus-allow-securitymanager - - [18,) - - - - -Djava.security.manager=allow - -