Skip to content

Commit ca8030a

Browse files
authored
Fix query/ec2 protocol perf regression (#7367)
* Fix query/ec2 protocol perf regression * fix: Restore HTTP_REQUEST_URI_BEFORE_MODIFY as a lazy attribute The previous commit removed HTTP_REQUEST_URI_BEFORE_MODIFY outright, which broke customers who had started reading it despite it being internal API. Restore it as a deprecated derived view over a new HTTP_REQUEST_BEFORE_MODIFY attribute, which holds the marshalled request itself. Derived attributes apply their read mapping on each read, so the URI is only built if something actually asks for it, and the value is identical to before including the query string. EndpointResolutionStage reads the endpoint components straight off the snapshotted request, so the request path no longer builds a URI at all. This keeps the query/ec2 protocol regression fixed: those protocols still carry the entire payload in the raw query parameters at this point in the execution, so building a URI cost two passes over the whole payload per API call. * fix: Make HTTP_REQUEST_URI_BEFORE_MODIFY read-only There is no write usage of this attribute, so setting it now throws UnsupportedOperationException rather than projecting the URI back onto the snapshotted request. This matches UnmodifiableExecutionAttributes, and is safe because ExecutionAttributes copies, merges and putAbsentAttributes all duplicate the backing map directly and never invoke a derived attribute's write mapping. * Use new perf-improvement type * fix: Snapshot endpoint components instead of the marshalled request Holding the marshalled request kept its raw query parameters reachable until the end of the API call, because the signing stage swaps the interceptor context over to the signed request and LowCopyListMap#clear installs a fresh map rather than mutating the original. For query and ec2 that map is the request payload: measured at ~114KB retained for a 33KB payload, against ~69KB for the URI that 2.47.0 retained. Snapshot only the endpoint components instead, which retains ~130 bytes. The deprecated HTTP_REQUEST_URI_BEFORE_MODIFY becomes a view over those components, so it now carries no query string and always renders the port. The only known consumer parses the path for a "/invocations" suffix, which is unaffected; EndpointUrl#toUri also memoizes, so repeated reads no longer rebuild the URI. Guarded end to end in SyncClientHandlerTest: an interceptor reads the snapshot during modifyHttpRequest and asserts the URI has no query string, which fails if the snapshot is ever built from getUri() again. * chore: Restore perf-improvement changelog type * fix: Remove unused SdkHttpRequest import The javadoc reference that used it was removed, and Checkstyle's UnusedImports does not process javadoc, so the import became a build error. * fix: Make HTTP_REQUEST_URI_BEFORE_MODIFY writable again A customer unit test sets this attribute, so throwing on write is too strict. Writing now replaces the backing endpoint snapshot via EndpointUrl#fromUri, which also pre-populates the cached URI, so a written value reads back exactly as written including its query string. * test: Add E2E coverage for endpointOverride with interceptors S3 is the only service here whose endpoint rules rewrite the host taken from an endpointOverride (virtual-host addressing resolves {Bucket}.{url#authority}), so it is the only place these assertions can tell "the resolved host was applied" apart from "the interceptor's host was preserved". Extend EndpointOverrideEndpointResolutionTest with overrides that spell out the protocol's default port, and add EndpointOverrideInterceptorResolutionTest covering the override x modifyHttpRequest matrix: an interceptor that changes the host, scheme or port wins and the resolved path is still applied; one that leaves the endpoint alone, adds a header, or rebuilds the request from its own values does not suppress endpoint resolution. Verified these fail on the pre-fix snapshot: 6 failures, including an interceptor that merely restates the endpoint, which turns the builder's raw port from null into an explicit 443 and so used to look like a change.
1 parent 557d5d4 commit ca8030a

9 files changed

Lines changed: 569 additions & 19 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "perf-improvement",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Fix request-side performance regression that scaled with payload size for `query` and `ec2` protocol services where the request endpoint was re-parsed as a URI. The internal `HTTP_REQUEST_URI_BEFORE_MODIFY` execution attribute is now deprecated and computed on demand from `HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY`; the value the SDK records carries the scheme, host, port and path but no query string."
6+
}

‎core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
3939
import software.amazon.awssdk.endpoints.Endpoint;
4040
import software.amazon.awssdk.endpoints.EndpointProvider;
41+
import software.amazon.awssdk.endpoints.EndpointUrl;
4142
import software.amazon.awssdk.http.SdkHttpExecutionAttributes;
4243
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
4344
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider;
@@ -212,11 +213,29 @@ public final class SdkInternalExecutionAttribute extends SdkExecutionAttribute {
212213
new ExecutionAttribute<>("EndpointResolver");
213214

214215
/**
215-
* The HTTP request URI captured before modifyHttpRequest interceptors run.
216-
* Used by EndpointResolutionStage to detect if a customer interceptor modified the URL.
216+
* The HTTP request endpoint (scheme, host, port and path) captured before modifyHttpRequest interceptors run.
217+
* Used by EndpointResolutionStage to detect if a customer interceptor modified the endpoint.
217218
*/
219+
public static final ExecutionAttribute<EndpointUrl> HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY =
220+
new ExecutionAttribute<>("HttpRequestEndpointBeforeModify");
221+
222+
/**
223+
* The HTTP request URI captured before modifyHttpRequest interceptors run. Writing this replaces
224+
* {@link #HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY} with the written URI's components.
225+
*
226+
* @deprecated Use {@link #HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY} instead. This is a view over that attribute, so the
227+
* value recorded by the SDK carries only the scheme, host, port and path: the query string is absent, and the port
228+
* is present even when it is the protocol's default. Reading it builds a {@link URI}, which
229+
* {@link #HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY} lets you avoid.
230+
*/
231+
@Deprecated
218232
public static final ExecutionAttribute<URI> HTTP_REQUEST_URI_BEFORE_MODIFY =
219-
new ExecutionAttribute<>("HttpRequestUriBeforeModify");
233+
ExecutionAttribute.derivedBuilder("HttpRequestUriBeforeModify",
234+
URI.class,
235+
() -> HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY)
236+
.readMapping(endpoint -> endpoint != null ? endpoint.toUri() : null)
237+
.writeMapping((endpoint, uri) -> uri != null ? EndpointUrl.fromUri(uri) : null)
238+
.build();
220239

221240
/**
222241
* The selected auth scheme for a request.

‎core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseClientHandler.java‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import software.amazon.awssdk.core.metrics.CoreMetric;
4242
import software.amazon.awssdk.core.signer.Signer;
4343
import software.amazon.awssdk.core.sync.RequestBody;
44+
import software.amazon.awssdk.endpoints.EndpointUrl;
4445
import software.amazon.awssdk.http.ContentStreamProvider;
4546
import software.amazon.awssdk.http.SdkHttpFullRequest;
4647
import software.amazon.awssdk.http.SdkHttpFullResponse;
@@ -81,11 +82,19 @@ static <InputT extends SdkRequest, OutputT> InterceptorContext finalizeSdkHttpFu
8182
addHttpRequest(executionContext, request);
8283
runAfterMarshallingInterceptors(executionContext);
8384

84-
// Snapshot the HTTP request URI before modifyHttpRequest interceptors run.
85-
// EndpointResolutionStage uses this to detect if a customer interceptor modified the URL.
85+
// Snapshot the HTTP request endpoint before modifyHttpRequest interceptors run.
86+
// EndpointResolutionStage uses this to detect if a customer interceptor modified the endpoint.
87+
//
88+
// Use the optimized EndpointUrl instead of an expensive URI to avoid the cost of parsing/re-parsing it.
89+
// Query/EC2 protocols have the entire request payload in query params at this point which increases the time
90+
// to build the URI in proportion to the size of the request.
91+
SdkHttpRequest marshalledRequest = executionContext.interceptorContext().httpRequest();
8692
executionContext.executionAttributes().putAttribute(
87-
SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY,
88-
executionContext.interceptorContext().httpRequest().getUri());
93+
SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY,
94+
EndpointUrl.fromComponents(marshalledRequest.protocol(),
95+
marshalledRequest.host(),
96+
marshalledRequest.port(),
97+
marshalledRequest.encodedPath()));
8998

9099
return runModifyHttpRequestAndHttpContentInterceptors(executionContext);
91100
}

‎core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/EndpointResolutionStage.java‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,23 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re
110110
}
111111

112112
/**
113-
* Detects if an interceptor modified the HTTP request URL in modifyHttpRequest().
114-
* Compares the current request's host and scheme against the snapshot taken before interceptors ran.
113+
* Detects if an interceptor modified the HTTP request endpoint in modifyHttpRequest().
114+
* Compares the current request's host, scheme and port against the snapshot taken before interceptors ran.
115115
*/
116116
private static boolean interceptorModifiedEndpoint(SdkHttpFullRequest.Builder request, ExecutionAttributes attrs) {
117-
URI preModifyUri = attrs.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY);
118-
if (preModifyUri == null) {
117+
EndpointUrl preModifyEndpoint =
118+
attrs.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY);
119+
if (preModifyEndpoint == null) {
119120
return false;
120121
}
121122
String requestHost = request.host();
123+
if (requestHost == null) {
124+
return false;
125+
}
122126
Integer requestPort = request.port();
123-
return requestHost != null
124-
&& (!requestHost.equals(preModifyUri.getHost())
125-
|| !String.valueOf(request.protocol()).equals(preModifyUri.getScheme())
126-
|| (requestPort != null && requestPort != preModifyUri.getPort()));
127+
return !requestHost.equals(preModifyEndpoint.host())
128+
|| !String.valueOf(request.protocol()).equals(preModifyEndpoint.scheme())
129+
|| (requestPort != null && requestPort != preModifyEndpoint.port());
127130
}
128131

129132
/**

‎core/sdk-core/src/test/java/software/amazon/awssdk/core/client/handler/SyncClientHandlerTest.java‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515

1616
package software.amazon.awssdk.core.client.handler;
1717

18+
import static java.util.Collections.singletonList;
1819
import static org.assertj.core.api.Assertions.assertThat;
1920
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2021
import static org.mockito.ArgumentMatchers.any;
2122
import static org.mockito.Mockito.verifyNoMoreInteractions;
2223
import static org.mockito.Mockito.when;
2324

2425
import java.io.ByteArrayInputStream;
26+
import java.net.URI;
27+
import java.util.ArrayList;
2528
import java.util.Arrays;
2629
import java.util.HashMap;
2730
import java.util.List;
@@ -41,14 +44,20 @@
4144
import software.amazon.awssdk.core.exception.RetryableException;
4245
import software.amazon.awssdk.core.exception.SdkServiceException;
4346
import software.amazon.awssdk.core.http.HttpResponseHandler;
47+
import software.amazon.awssdk.core.interceptor.Context;
48+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
49+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
50+
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
4451
import software.amazon.awssdk.core.protocol.VoidSdkResponse;
4552
import software.amazon.awssdk.core.runtime.transform.Marshaller;
4653
import software.amazon.awssdk.core.sync.ResponseTransformer;
54+
import software.amazon.awssdk.endpoints.EndpointUrl;
4755
import software.amazon.awssdk.http.AbortableInputStream;
4856
import software.amazon.awssdk.http.HttpExecuteResponse;
4957
import software.amazon.awssdk.http.ExecutableHttpRequest;
5058
import software.amazon.awssdk.http.SdkHttpClient;
5159
import software.amazon.awssdk.http.SdkHttpFullRequest;
60+
import software.amazon.awssdk.http.SdkHttpRequest;
5261
import software.amazon.awssdk.http.SdkHttpResponse;
5362
import software.amazon.awssdk.retries.DefaultRetryStrategy;
5463
import utils.HttpTestUtils;
@@ -189,6 +198,69 @@ private void mockSuccessfulApiCall() throws Exception {
189198
when(responseHandler.handle(any(), any())).thenReturn(VoidSdkResponse.builder().build());
190199
}
191200

201+
/**
202+
* The pre-modify snapshot must never encode the marshalled request's query parameters. For the query and ec2
203+
* protocols those parameters still hold the entire request payload when the snapshot is taken, so encoding them
204+
* costs a full pass over the payload on every API call, and retaining them keeps the payload alive for the rest of
205+
* the call. Guards the regression fixed in this change.
206+
*/
207+
@Test
208+
public void snapshottedEndpoint_doesNotEncodeOrRetainQueryParameters() throws Exception {
209+
SdkHttpFullRequest.Builder marshalled =
210+
ValidSdkObjects.sdkHttpFullRequest(8080)
211+
.encodedPath("/2015-03-31/functions/my-function/invocations")
212+
.putRawQueryParameter("Qualifier", "prod");
213+
// A handful of payload-shaped parameters is enough: the assertion is that the query string is absent
214+
// entirely, so any of these appearing in the snapshot is a failure.
215+
for (int i = 0; i < 20; i++) {
216+
marshalled.putRawQueryParameter("MetricData.member." + i + ".Value", "12345.6789");
217+
}
218+
219+
List<EndpointUrl> capturedEndpoint = new ArrayList<>();
220+
List<URI> capturedUri = new ArrayList<>();
221+
ExecutionInterceptor interceptor = new ExecutionInterceptor() {
222+
@Override
223+
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes attrs) {
224+
capturedEndpoint.add(
225+
attrs.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY));
226+
capturedUri.add(attrs.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY));
227+
return context.httpRequest();
228+
}
229+
};
230+
231+
SdkSyncClientHandler handler = new SdkSyncClientHandler(
232+
clientConfiguration().toBuilder()
233+
.option(SdkClientOption.EXECUTION_INTERCEPTORS, singletonList(interceptor))
234+
.build());
235+
236+
when(marshaller.marshall(request)).thenReturn(marshalled.build());
237+
when(httpClient.prepareRequest(any())).thenReturn(httpClientCall);
238+
when(httpClientCall.call()).thenReturn(HttpExecuteResponse.builder()
239+
.response(SdkHttpResponse.builder()
240+
.statusCode(200)
241+
.build())
242+
.build());
243+
when(responseHandler.handle(any(), any())).thenReturn(VoidSdkResponse.builder().build());
244+
245+
handler.execute(clientExecutionParams());
246+
247+
// The endpoint snapshot holds components only; nothing references the query parameters.
248+
assertThat(capturedEndpoint).hasSize(1);
249+
assertThat(capturedEndpoint.get(0).host()).isEqualTo("localhost");
250+
assertThat(capturedEndpoint.get(0).encodedPath()).isEqualTo("/2015-03-31/functions/my-function/invocations");
251+
252+
// The deprecated URI view carries no query string, so the payload was never encoded.
253+
URI uri = capturedUri.get(0);
254+
assertThat(uri.getQuery()).isNull();
255+
assertThat(uri.toString()).isEqualTo("http://localhost:8080/2015-03-31/functions/my-function/invocations");
256+
257+
// The known consumer pattern: extract the path suffix following "/invocations".
258+
String path = uri.toString();
259+
int idx = path.indexOf("/invocations");
260+
assertThat(idx).isGreaterThanOrEqualTo(0);
261+
assertThat(path.substring(idx + "/invocations".length())).isEmpty();
262+
}
263+
192264
private void expectRetrievalFromMocks() {
193265
when(marshaller.marshall(request)).thenReturn(marshalledRequest);
194266
when(httpClient.prepareRequest(any())).thenReturn(httpClientCall);
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.core.interceptor;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.net.URI;
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
import software.amazon.awssdk.endpoints.EndpointUrl;
24+
25+
/**
26+
* Tests for the deprecated {@link SdkInternalExecutionAttribute#HTTP_REQUEST_URI_BEFORE_MODIFY}, which is a derived
27+
* view over {@link SdkInternalExecutionAttribute#HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY}.
28+
*/
29+
class SdkInternalExecutionAttributeTest {
30+
31+
private static final EndpointUrl ENDPOINT =
32+
EndpointUrl.fromComponents("https", "lambda.us-east-1.amazonaws.com", 443,
33+
"/2015-03-31/functions/my-function/invocations");
34+
35+
private ExecutionAttributes attributes;
36+
37+
@BeforeEach
38+
void setup() {
39+
attributes = new ExecutionAttributes();
40+
}
41+
42+
@Test
43+
void httpRequestUriBeforeModify_noSnapshot_isNull() {
44+
assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY)).isNull();
45+
}
46+
47+
@Test
48+
void httpRequestUriBeforeModify_rendersSnapshottedEndpoint() {
49+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY, ENDPOINT);
50+
51+
assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY))
52+
.isEqualTo(URI.create("https://lambda.us-east-1.amazonaws.com:443"
53+
+ "/2015-03-31/functions/my-function/invocations"));
54+
}
55+
56+
@Test
57+
void httpRequestUriBeforeModify_neverCarriesQueryString() {
58+
// The snapshot holds components only, so the query string is always absent. This is what keeps the query and
59+
// ec2 protocols cheap: for those the raw query parameters are the entire request payload at this point.
60+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY, ENDPOINT);
61+
62+
URI uri = attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY);
63+
64+
assertThat(uri.getQuery()).isNull();
65+
assertThat(uri.getRawPath()).isEqualTo("/2015-03-31/functions/my-function/invocations");
66+
}
67+
68+
@Test
69+
void httpRequestUriBeforeModify_repeatedReadsShareTheSameUri() {
70+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY, ENDPOINT);
71+
72+
assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY))
73+
.isSameAs(attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY));
74+
}
75+
76+
@Test
77+
void httpRequestUriBeforeModify_writeReplacesTheEndpoint() {
78+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY, ENDPOINT);
79+
80+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY,
81+
URI.create("http://custom.example.com:8443/my-path"));
82+
83+
EndpointUrl endpoint =
84+
attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY);
85+
assertThat(endpoint.scheme()).isEqualTo("http");
86+
assertThat(endpoint.host()).isEqualTo("custom.example.com");
87+
assertThat(endpoint.port()).isEqualTo(8443);
88+
assertThat(endpoint.encodedPath()).isEqualTo("/my-path");
89+
}
90+
91+
@Test
92+
void httpRequestUriBeforeModify_writeRoundTripsExactly() {
93+
// A written URI reads back unchanged, query string included, so a caller that sets this attribute sees
94+
// precisely what it set.
95+
URI written = URI.create("https://custom.example.com/my-path?Qualifier=prod");
96+
97+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY, written);
98+
99+
assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY))
100+
.isEqualTo(written);
101+
}
102+
103+
@Test
104+
void httpRequestUriBeforeModify_writeNull_clearsTheEndpoint() {
105+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY, ENDPOINT);
106+
107+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY, null);
108+
109+
assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY)).isNull();
110+
assertThat(attributes.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY)).isNull();
111+
}
112+
113+
@Test
114+
void httpRequestEndpointBeforeModify_survivesCopy() {
115+
attributes.putAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY, ENDPOINT);
116+
117+
// Copies duplicate the backing map rather than re-setting each attribute, so the read-only derived view must
118+
// still resolve against the copy.
119+
ExecutionAttributes copy = attributes.copy();
120+
121+
assertThat(copy.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_ENDPOINT_BEFORE_MODIFY))
122+
.isSameAs(ENDPOINT);
123+
assertThat(copy.getAttribute(SdkInternalExecutionAttribute.HTTP_REQUEST_URI_BEFORE_MODIFY))
124+
.isEqualTo(ENDPOINT.toUri());
125+
}
126+
}

0 commit comments

Comments
 (0)