Skip to content

Commit 7632ef5

Browse files
author
Local Merge
committed
addressing review comments
1 parent 4aab056 commit 7632ef5

2 files changed

Lines changed: 132 additions & 2 deletions

File tree

sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/policy/DecodedResponse.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ class DecodedResponse extends HttpResponse {
4242
super(httpResponse.getRequest());
4343
this.originalResponse = httpResponse;
4444
this.decodedBody = decodedBody;
45-
HttpHeaders headers = new HttpHeaders();
46-
httpResponse.getHeaders().stream().forEach(h -> headers.set(h.getName(), h.getValue()));
45+
HttpHeaders headers = new HttpHeaders(httpResponse.getHeaders());
4746
headers.set(HttpHeaderName.CONTENT_LENGTH, String.valueOf(decodedContentLength));
4847
this.adjustedHeaders = headers;
4948
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.storage.common.policy;
5+
6+
import com.azure.core.http.HttpHeaderName;
7+
import com.azure.core.http.HttpHeaders;
8+
import com.azure.core.http.HttpMethod;
9+
import com.azure.core.http.HttpPipeline;
10+
import com.azure.core.http.HttpPipelineBuilder;
11+
import com.azure.core.http.HttpRequest;
12+
import com.azure.core.http.HttpResponse;
13+
import com.azure.core.test.http.MockHttpResponse;
14+
import com.azure.core.util.Context;
15+
import com.azure.core.util.FluxUtil;
16+
import com.azure.storage.common.implementation.Constants;
17+
import com.azure.storage.common.implementation.contentvalidation.StructuredMessageConstants;
18+
import com.azure.storage.common.implementation.contentvalidation.StructuredMessageEncoder;
19+
import com.azure.storage.common.implementation.contentvalidation.StructuredMessageFlags;
20+
import org.junit.jupiter.api.Test;
21+
import reactor.core.publisher.Flux;
22+
import reactor.core.publisher.Mono;
23+
24+
import java.io.IOException;
25+
import java.net.MalformedURLException;
26+
import java.net.URL;
27+
import java.nio.ByteBuffer;
28+
import java.util.Objects;
29+
import java.util.concurrent.ThreadLocalRandom;
30+
31+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
32+
import static org.junit.jupiter.api.Assertions.assertEquals;
33+
import static org.junit.jupiter.api.Assertions.assertNotNull;
34+
35+
public class StorageContentValidationDecoderPolicyTests {
36+
37+
private static HttpRequest getRequest() {
38+
try {
39+
return new HttpRequest(HttpMethod.GET, new URL("http://example.com/blob"));
40+
} catch (MalformedURLException e) {
41+
throw new RuntimeException(e);
42+
}
43+
}
44+
45+
private static byte[] encodeToBytes(byte[] data, int segmentSize, StructuredMessageFlags flags) throws IOException {
46+
StructuredMessageEncoder encoder = new StructuredMessageEncoder(data.length, segmentSize, flags);
47+
Flux<ByteBuffer> flux = encoder.encode(ByteBuffer.wrap(data));
48+
ByteBuffer encoded
49+
= ByteBuffer.wrap(Objects.requireNonNull(FluxUtil.collectBytesInByteBufferStream(flux).block()));
50+
byte[] bytes = new byte[encoded.remaining()];
51+
encoded.get(bytes);
52+
return bytes;
53+
}
54+
55+
@Test
56+
public void contentLengthIsOverriddenToDecodedSizeWhenDecodingApplied() throws IOException {
57+
byte[] payload = new byte[64];
58+
ThreadLocalRandom.current().nextBytes(payload);
59+
60+
byte[] encoded = encodeToBytes(payload, 64, StructuredMessageFlags.STORAGE_CRC64);
61+
long encodedLen = encoded.length;
62+
long decodedLen = payload.length;
63+
64+
HttpHeaders responseHeaders = new HttpHeaders().set(HttpHeaderName.CONTENT_LENGTH, String.valueOf(encodedLen))
65+
.set(Constants.HeaderConstants.STRUCTURED_BODY_TYPE_HEADER_NAME,
66+
StructuredMessageConstants.STRUCTURED_BODY_TYPE_VALUE)
67+
.set(Constants.HeaderConstants.STRUCTURED_CONTENT_LENGTH_HEADER_NAME, String.valueOf(decodedLen));
68+
69+
HttpPipeline pipeline = new HttpPipelineBuilder().policies(new StorageContentValidationDecoderPolicy())
70+
.httpClient(request -> Mono.just(new MockHttpResponse(request, 200, responseHeaders, encoded)))
71+
.build();
72+
73+
Context ctx = new Context(StructuredMessageConstants.STRUCTURED_MESSAGE_DECODING_CONTEXT_KEY, true);
74+
HttpResponse response = pipeline.send(getRequest(), ctx).block();
75+
76+
assertNotNull(response);
77+
assertEquals(String.valueOf(decodedLen), response.getHeaders().getValue(HttpHeaderName.CONTENT_LENGTH));
78+
}
79+
80+
@Test
81+
public void contentLengthMatchesActualDecodedBodySize() throws IOException {
82+
byte[] payload = new byte[128];
83+
ThreadLocalRandom.current().nextBytes(payload);
84+
85+
byte[] encoded = encodeToBytes(payload, 64, StructuredMessageFlags.STORAGE_CRC64);
86+
long encodedLen = encoded.length;
87+
long decodedLen = payload.length;
88+
89+
HttpHeaders responseHeaders = new HttpHeaders().set(HttpHeaderName.CONTENT_LENGTH, String.valueOf(encodedLen))
90+
.set(Constants.HeaderConstants.STRUCTURED_BODY_TYPE_HEADER_NAME,
91+
StructuredMessageConstants.STRUCTURED_BODY_TYPE_VALUE)
92+
.set(Constants.HeaderConstants.STRUCTURED_CONTENT_LENGTH_HEADER_NAME, String.valueOf(decodedLen));
93+
94+
HttpPipeline pipeline = new HttpPipelineBuilder().policies(new StorageContentValidationDecoderPolicy())
95+
.httpClient(request -> Mono.just(new MockHttpResponse(request, 200, responseHeaders, encoded)))
96+
.build();
97+
98+
Context ctx = new Context(StructuredMessageConstants.STRUCTURED_MESSAGE_DECODING_CONTEXT_KEY, true);
99+
HttpResponse response = pipeline.send(getRequest(), ctx).block();
100+
101+
assertNotNull(response);
102+
assertEquals(String.valueOf(decodedLen), response.getHeaders().getValue(HttpHeaderName.CONTENT_LENGTH));
103+
104+
// Consume the body and verify it matches the original payload — the reported Content-Length
105+
// must equal the actual number of bytes produced by the decoder.
106+
byte[] body = Objects.requireNonNull(FluxUtil.collectBytesInByteBufferStream(response.getBody()).block());
107+
assertEquals(decodedLen, body.length);
108+
assertArrayEquals(payload, body);
109+
}
110+
111+
@Test
112+
public void contentLengthIsUnchangedWhenDecodingNotApplied() throws IOException {
113+
byte[] payload = new byte[64];
114+
ThreadLocalRandom.current().nextBytes(payload);
115+
116+
byte[] encoded = encodeToBytes(payload, 64, StructuredMessageFlags.STORAGE_CRC64);
117+
long encodedLen = encoded.length;
118+
119+
HttpHeaders responseHeaders = new HttpHeaders().set(HttpHeaderName.CONTENT_LENGTH, String.valueOf(encodedLen));
120+
121+
HttpPipeline pipeline = new HttpPipelineBuilder().policies(new StorageContentValidationDecoderPolicy())
122+
.httpClient(request -> Mono.just(new MockHttpResponse(request, 200, responseHeaders, encoded)))
123+
.build();
124+
125+
// No decoding context flag set — policy should pass the response through unchanged.
126+
HttpResponse response = pipeline.send(getRequest()).block();
127+
128+
assertNotNull(response);
129+
assertEquals(String.valueOf(encodedLen), response.getHeaders().getValue(HttpHeaderName.CONTENT_LENGTH));
130+
}
131+
}

0 commit comments

Comments
 (0)