Skip to content
This repository was archived by the owner on Jul 19, 2024. It is now read-only.

Commit 80ec09b

Browse files
authored
Merge pull request #527 from Azure/legacy-master
Legacy master
2 parents d1cce18 + 72e3d28 commit 80ec09b

File tree

7 files changed

+164
-17
lines changed

7 files changed

+164
-17
lines changed

ChangeLog.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
XXXX.XX.XX Version X.X.X
2+
* Fixed a bug in BlobInputStream that would return extra zeros at the end of the stream if the data was encrypted using client-side encryption.
3+
* MD5 checks on BlobInputStream are skipped if data being downloaded is also being decrypted via client-side encryption, even if disableMd5Calculation is set to false. Previously this check would always fail as MD5 is calculated on cipher text on upload but was calculated on plaintext on download.
4+
* Added a workaround to a JDK bug that would ignore connection timeouts on retries, causing hangs in some scenarios. This requires defaulting setting https keep-alive on all sockets. It can be disabled via BlobRequestOptions.
5+
16
2019.12.06 Version 8.6.0
27
* Added the skipDecode flag to the generate sas method on CloudBlob. This flag allows the customer to skip the url decode that happens by default on the string to sign right before signing. This resolves some problems with custom values for some of the query parameters when used with third party clients.
38

microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestHelper.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,7 @@ public static void assertStreamsAreEqual(InputStream src, InputStream dst) throw
283283
}
284284

285285
next = dst.read();
286-
while (next != -1) {
287-
assertEquals(0, next);
288-
next = dst.read();
289-
}
286+
assertEquals(next, -1);
290287
}
291288

292289
public static void assertStreamsAreEqualAtIndex(ByteArrayInputStream src, ByteArrayInputStream dst, int srcIndex,

microsoft-azure-storage/src/com/microsoft/azure/storage/RequestOptions.java

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public abstract class RequestOptions {
5656
*/
5757
private Boolean requireEncryption;
5858

59+
/**
60+
* A value to indicate whether we should disable socket keep-alive.
61+
*/
62+
private Boolean disableHttpsSocketKeepAlive;
63+
5964
/**
6065
* Creates an instance of the <code>RequestOptions</code> class.
6166
*/
@@ -78,6 +83,7 @@ public RequestOptions(final RequestOptions other) {
7883
this.setMaximumExecutionTimeInMs(other.getMaximumExecutionTimeInMs());
7984
this.setOperationExpiryTimeInMs(other.getOperationExpiryTimeInMs());
8085
this.setRequireEncryption(other.requireEncryption());
86+
this.setDisableHttpsSocketKeepAlive(other.disableHttpsSocketKeepAlive());
8187
}
8288
}
8389

@@ -100,6 +106,10 @@ protected static void applyBaseDefaultsInternal(final RequestOptions modifiedOpt
100106
if (modifiedOptions.requireEncryption() == null) {
101107
modifiedOptions.setRequireEncryption(false);
102108
}
109+
110+
if (modifiedOptions.disableHttpsSocketKeepAlive() == null) {
111+
modifiedOptions.setDisableHttpsSocketKeepAlive(false);
112+
}
103113
}
104114

105115
/**
@@ -132,6 +142,10 @@ protected static void populateRequestOptions(RequestOptions modifiedOptions,
132142
modifiedOptions.setOperationExpiryTimeInMs(new Date().getTime()
133143
+ modifiedOptions.getMaximumExecutionTimeInMs());
134144
}
145+
146+
if (modifiedOptions.disableHttpsSocketKeepAlive() == null) {
147+
modifiedOptions.setDisableHttpsSocketKeepAlive(clientOptions.disableHttpsSocketKeepAlive());
148+
}
135149
}
136150

137151
/**
@@ -178,18 +192,29 @@ public final LocationMode getLocationMode() {
178192
public Integer getMaximumExecutionTimeInMs() {
179193
return this.maximumExecutionTimeInMs;
180194
}
181-
195+
182196
/**
183197
* Gets a value to indicate whether all data written and read must be encrypted. Use <code>true</code> to
184198
* encrypt/decrypt data for transactions; otherwise, <code>false</code>. For more
185199
* information about require encryption defaults, see {@link #setRequireEncryption(Boolean)}.
186-
*
200+
*
187201
* @return A value to indicate whether all data written and read must be encrypted.
188202
*/
189203
public Boolean requireEncryption() {
190204
return this.requireEncryption;
191205
}
192206

207+
/**
208+
* Gets a value to indicate whether https socket keep-alive should be disabled. Use <code>true</code> to disable
209+
* keep-alive; otherwise, <code>false</code>. For more information about disableHttpsSocketKeepAlive defaults, see
210+
* {@link ServiceClient#getDefaultRequestOptions()}
211+
*
212+
* @return A value to indicate whther https socket keep-alive should be disabled.
213+
*/
214+
public Boolean disableHttpsSocketKeepAlive() {
215+
return this.disableHttpsSocketKeepAlive;
216+
}
217+
193218
/**
194219
* RESERVED FOR INTERNAL USE.
195220
*
@@ -283,8 +308,8 @@ public void setMaximumExecutionTimeInMs(Integer maximumExecutionTimeInMs) {
283308
* <p>
284309
* The default is set in the client and is by default false, indicating encryption is not required. You can change
285310
* the value on this request by setting this property. You can also change the value on the
286-
* {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the service
287-
* client will use the appropriate value.
311+
* {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the
312+
* service client will use the appropriate value.
288313
*
289314
* @param requireEncryption
290315
* A value to indicate whether all data written and read must be encrypted.
@@ -293,6 +318,28 @@ public void setRequireEncryption(Boolean requireEncryption) {
293318
this.requireEncryption = requireEncryption;
294319
}
295320

321+
/**
322+
* Sets a value to indicate whether https socket keep-alive should be disabled. Use <code>true</code> to disable
323+
* keep-alive; otherwise, <code>false</code>
324+
* <p>
325+
* The default is set in the client and is by default false, indicating that https socket keep-alive will be
326+
* enabled. You can change the value on this request by setting this property. You can also change the value on
327+
* on the {@link ServiceClient#getDefaultRequestOptions()} object so that all subsequent requests made via the
328+
* service client will use the appropriate value.
329+
* <p>
330+
* Setting keep-alive on https sockets is to work around a bug in the JVM where connection timeouts are not honored
331+
* on retried requests. In those cases, we use socket keep-alive as a fallback. Unfortunately, the timeout value
332+
* must be taken from a JVM property rather than configured locally. Therefore, in rare cases the JVM has configured
333+
* aggressively short keep-alive times, it may be beneficial to disable the use of keep-alives lest they interfere
334+
* with long running data transfer operations.
335+
*
336+
* @param disableHttpsSocketKeepAlive
337+
* A value to indicate whether https socket keep-alive should be disabled.
338+
*/
339+
public void setDisableHttpsSocketKeepAlive(Boolean disableHttpsSocketKeepAlive) {
340+
this.disableHttpsSocketKeepAlive = disableHttpsSocketKeepAlive;
341+
}
342+
296343
/**
297344
* RESERVED FOR INTERNAL USE.
298345
*

microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobInputStream.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,10 +302,19 @@ private synchronized void dispatchRead(final int readLength) throws IOException
302302
try {
303303
final byte[] byteBuffer = new byte[readLength];
304304

305-
this.parentBlobRef.downloadRangeInternal(this.currentAbsoluteReadPosition, (long) readLength, byteBuffer,
306-
0, this.accessCondition, this.options, this.opContext);
307-
308-
this.currentBuffer = new ByteArrayInputStream(byteBuffer);
305+
int numBytes = this.parentBlobRef.downloadRangeInternal(this.currentAbsoluteReadPosition, (long) readLength,
306+
byteBuffer, 0, this.accessCondition, this.options, this.opContext);
307+
308+
/*
309+
In the case of client-side decryption, we may get fewer bytes than we request at the end of the blob when
310+
we remove padding. We want to ensure our data is the correct size, even in this case. Also, in this case,
311+
we can no longer validate the MD5 because it was calculated on the ciphertext on upload, but this
312+
inputstream calculates it on the plaintext.
313+
*/
314+
if (numBytes < readLength && this.options.getEncryptionPolicy() != null) {
315+
this.validateBlobMd5 = false;
316+
}
317+
this.currentBuffer = new ByteArrayInputStream(byteBuffer, 0, numBytes);
309318
this.bufferSize = readLength;
310319
this.bufferStartOffset = this.currentAbsoluteReadPosition;
311320
}

microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2644,7 +2644,8 @@ public final BlobInputStream openInputStream(final AccessCondition accessConditi
26442644
}
26452645

26462646
/**
2647-
* Opens a blob input stream to download the blob using the specified request options and operation context.
2647+
* Opens a blob input stream to download the blob using the specified request options and operation context. If
2648+
* the blob is decrypted as it is downloaded, the final MD5 validation will be skipped.
26482649
* <p>
26492650
* Use {@link #setStreamMinimumReadSizeInBytes(int)} to configure the read size.
26502651
*

microsoft-azure-storage/src/com/microsoft/azure/storage/core/BaseRequest.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
import java.util.List;
2828
import java.util.Map;
2929
import java.util.Map.Entry;
30-
import java.util.Set;
3130

3231
import com.microsoft.azure.storage.*;
33-
import com.microsoft.azure.storage.blob.BlobBatchOperation;
34-
import com.microsoft.azure.storage.blob.BlobRequestOptions;
35-
import com.microsoft.azure.storage.blob.CloudBlobClient;
32+
import com.microsoft.azure.storage.Constants;
33+
import com.microsoft.azure.storage.OperationContext;
34+
import com.microsoft.azure.storage.RequestOptions;
35+
import com.microsoft.azure.storage.StorageException;
36+
37+
import javax.net.ssl.HttpsURLConnection;
3638

3739
import static com.microsoft.azure.storage.Constants.QueryConstants.PROPERTIES;
3840

@@ -223,6 +225,14 @@ protected PasswordAuthentication getPasswordAuthentication() {
223225
retConnection = (HttpURLConnection) resourceUrl.openConnection();
224226
}
225227

228+
/*
229+
If we are using https, check if we should enable socket keep-alive timeouts to work around JVM bug.
230+
*/
231+
if (retConnection instanceof HttpsURLConnection && !options.disableHttpsSocketKeepAlive()) {
232+
HttpsURLConnection httpsConnection = ((HttpsURLConnection) retConnection);
233+
httpsConnection.setSSLSocketFactory(new KeepAliveSocketFactory(httpsConnection.getSSLSocketFactory()));
234+
}
235+
226236
/*
227237
* ReadTimeout must be explicitly set to avoid a bug in JDK 6. In certain cases, this bug causes an immediate
228238
* read timeout exception to be thrown even if ReadTimeout is not set.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.azure.storage.core;
5+
6+
import javax.net.ssl.SSLSocketFactory;
7+
import java.io.IOException;
8+
import java.net.InetAddress;
9+
import java.net.Socket;
10+
import java.net.UnknownHostException;
11+
12+
/**
13+
* RESERVED FOR INTERNAL USE.
14+
*
15+
* This type is used to help work around a bug in the JDK where connection timeouts are not honored on a retried
16+
* request. In other words, if a customer set a timeout on an operation, this timeout is only ever respected on the
17+
* first attempt at the request. Retries will cause a different underlying connection implementation to be loaded that
18+
* will ignore the timeout parameter. Therefore, requests can potentially hang forever if the connection is broken
19+
* after these retries.
20+
*
21+
* Enabling keep-alive timeouts acts as a fallback in these scenarios so that, even if the operation timeout is ignored,
22+
* the socket will still eventually timeout and the request will be cancelled. We enable keep alive timeouts via a
23+
* wrapper implementation of a SocketFactory. We use a default socket factory to get sockets from the system and then
24+
* simply set the keep-alive option to true before returning to the client. This factory will be set on the
25+
* HttpsUrlConnection objects.
26+
*/
27+
public class KeepAliveSocketFactory extends SSLSocketFactory {
28+
private SSLSocketFactory delegate;
29+
30+
KeepAliveSocketFactory(SSLSocketFactory delegate) {
31+
this.delegate = delegate;
32+
}
33+
34+
@Override
35+
public String[] getDefaultCipherSuites() {
36+
return delegate.getDefaultCipherSuites();
37+
}
38+
39+
@Override
40+
public String[] getSupportedCipherSuites() {
41+
return delegate.getSupportedCipherSuites();
42+
}
43+
44+
@Override
45+
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
46+
Socket ret = delegate.createSocket(socket, s, i, b);
47+
ret.setKeepAlive(true);
48+
return ret;
49+
}
50+
51+
@Override
52+
public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
53+
Socket ret = delegate.createSocket(s, i);
54+
ret.setKeepAlive(true);
55+
return ret;
56+
}
57+
58+
@Override
59+
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
60+
Socket ret = delegate.createSocket(s, i, inetAddress, i1);
61+
ret.setKeepAlive(true);
62+
return ret;
63+
}
64+
65+
@Override
66+
public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
67+
Socket ret = delegate.createSocket(inetAddress, i);
68+
ret.setKeepAlive(true);
69+
return ret;
70+
}
71+
72+
@Override
73+
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
74+
Socket ret = delegate.createSocket(inetAddress, i, inetAddress1, i1);
75+
ret.setKeepAlive(true);
76+
return ret;
77+
}
78+
}

0 commit comments

Comments
 (0)