diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/App.config b/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/App.config index a3b421db1507..231f27d6bc26 100644 --- a/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/App.config +++ b/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/App.config @@ -7,7 +7,7 @@ - + diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/Azure.Batch.IntegrationTests.csproj b/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/Azure.Batch.IntegrationTests.csproj index 074857e9699e..26102d80aecc 100644 --- a/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/Azure.Batch.IntegrationTests.csproj +++ b/src/SDKs/Batch/DataPlane/Azure.Batch.IntegrationTests/Azure.Batch.IntegrationTests.csproj @@ -74,5 +74,7 @@ + + diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch.ProtocolTests/Azure.Batch.ProtocolTests.csproj b/src/SDKs/Batch/DataPlane/Azure.Batch.ProtocolTests/Azure.Batch.ProtocolTests.csproj index 6da3b2251c60..8173349e01f8 100644 --- a/src/SDKs/Batch/DataPlane/Azure.Batch.ProtocolTests/Azure.Batch.ProtocolTests.csproj +++ b/src/SDKs/Batch/DataPlane/Azure.Batch.ProtocolTests/Azure.Batch.ProtocolTests.csproj @@ -25,6 +25,10 @@ + + + + diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch.Unit.Tests/Azure.Batch.Unit.Tests.csproj b/src/SDKs/Batch/DataPlane/Azure.Batch.Unit.Tests/Azure.Batch.Unit.Tests.csproj index 8320eea53ea3..0e40fe65f98b 100644 --- a/src/SDKs/Batch/DataPlane/Azure.Batch.Unit.Tests/Azure.Batch.Unit.Tests.csproj +++ b/src/SDKs/Batch/DataPlane/Azure.Batch.Unit.Tests/Azure.Batch.Unit.Tests.csproj @@ -3,7 +3,7 @@ Debug $(LibraryNugetPackageFolder) - + $(BuiltPackageOutputDir) false true @@ -25,7 +25,12 @@ bin\$(Configuration)\ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + + + + + @@ -43,7 +48,7 @@ false - netcoreapp1.1 + netcoreapp1.1;net452 diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch.Unit.Tests/HttpClientBehaviorTests.cs b/src/SDKs/Batch/DataPlane/Azure.Batch.Unit.Tests/HttpClientBehaviorTests.cs new file mode 100644 index 000000000000..edace3741334 --- /dev/null +++ b/src/SDKs/Batch/DataPlane/Azure.Batch.Unit.Tests/HttpClientBehaviorTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Azure.Batch.Unit.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Batch.Protocol; + using Microsoft.Net.Http.Server; + using Xunit; + + public class HttpClientBehaviorTests + { + private const string url = "http://localhost:2055"; + + [Theory] + [MemberData(nameof(HttpMethods))] + public async Task HttpClient_IncludesContentLengthHeaderOnExpectedHttpVerbs(HttpMethod httpMethod) + { + BatchSharedKeyCredential creds = new BatchSharedKeyCredential(ClientUnitTestCommon.DummyAccountName, ClientUnitTestCommon.DummyAccountKey); + + HttpRequestMessage message = new HttpRequestMessage(httpMethod, url); + message.Headers.Add("client-request-id", Guid.NewGuid().ToString()); + + await creds.ProcessHttpRequestAsync(message, CancellationToken.None); + Assert.NotNull(message.Headers.Authorization); + + var settings = new WebListenerSettings() + { + Authentication = { Schemes = AuthenticationSchemes.None }, + UrlPrefixes = { url } + }; + using (WebListener listener = new WebListener(settings)) + { + listener.Start(); + Task listenTask = AcceptAndAssertAsync(httpMethod, listener, AssertRequestHasExpectedContentLength); + + HttpClient client = new HttpClient(); + await client.SendAsync(message); + + await listenTask; + } + } + + private static IEnumerable HttpMethods() + { + yield return new[] { HttpMethod.Delete }; + yield return new[] { HttpMethod.Post }; + yield return new[] { HttpMethod.Get }; + yield return new[] { HttpMethod.Head }; + yield return new[] { new HttpMethod("PATCH") }; + yield return new[] { HttpMethod.Put }; + yield return new[] { HttpMethod.Options }; + } + + private static async Task AcceptAndAssertAsync(HttpMethod httpMethod, WebListener listener, Action assertLambda) + { + using (RequestContext ctx = await listener.AcceptAsync()) + { + assertLambda(httpMethod, ctx); + ctx.Response.StatusCode = 200; + } + } + + private static void AssertRequestHasExpectedContentLength(HttpMethod httpMethod, RequestContext ctx) + { + if (httpMethod == HttpMethod.Head || httpMethod == HttpMethod.Get) + { + Assert.DoesNotContain(ctx.Request.Headers.Keys, str => str == "Content-Length"); + } + else if (httpMethod == HttpMethod.Delete || httpMethod == new HttpMethod("PATCH") || httpMethod == HttpMethod.Options) + { +#if !FullNetFx + Assert.DoesNotContain(ctx.Request.Headers.Keys, str => str == "Content-Length"); +#else + Assert.Contains(ctx.Request.Headers.Keys, str => str == "Content-Length"); + Assert.Equal("0", ctx.Request.Headers["Content-Length"].Single()); +#endif + } + else if (httpMethod == HttpMethod.Post || httpMethod == HttpMethod.Put) + { + Assert.Contains(ctx.Request.Headers.Keys, str => str == "Content-Length"); + Assert.Equal("0", ctx.Request.Headers["Content-Length"].Single()); + } + else + { + throw new ArgumentException($"Unexpected HTTP request type: {httpMethod}"); + } + } + } +} diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch/AssemblyAttributes.cs b/src/SDKs/Batch/DataPlane/Azure.Batch/AssemblyAttributes.cs index 72c63aeffdca..3d89c21f9f28 100644 --- a/src/SDKs/Batch/DataPlane/Azure.Batch/AssemblyAttributes.cs +++ b/src/SDKs/Batch/DataPlane/Azure.Batch/AssemblyAttributes.cs @@ -9,8 +9,8 @@ [assembly: AssemblyTitle("Microsoft.Azure.Batch")] [assembly: AssemblyDescription("Client library for interacting with the Azure Batch service.")] -[assembly: AssemblyVersion("7.0.0.0")] -[assembly: AssemblyFileVersion("7.0.0.0")] +[assembly: AssemblyVersion("7.0.1.0")] +[assembly: AssemblyFileVersion("7.0.1.0")] [assembly: AssemblyCompany("Microsoft Corporation")] [assembly: AssemblyProduct("Microsoft Azure")] [assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation. All rights reserved.")] diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch/Azure.Batch.csproj b/src/SDKs/Batch/DataPlane/Azure.Batch/Azure.Batch.csproj index ebe0f44f4ecd..2067d2ed7f83 100644 --- a/src/SDKs/Batch/DataPlane/Azure.Batch/Azure.Batch.csproj +++ b/src/SDKs/Batch/DataPlane/Azure.Batch/Azure.Batch.csproj @@ -3,7 +3,7 @@ Azure.Batch This client library provides access to the Microsoft Azure Batch service. - 7.0.0 + 7.0.1 $(DefineConstants);CODESIGN true Microsoft.Azure.Batch diff --git a/src/SDKs/Batch/DataPlane/Azure.Batch/Protocol/BatchSharedKeyCredential.cs b/src/SDKs/Batch/DataPlane/Azure.Batch/Protocol/BatchSharedKeyCredential.cs index e629c44216db..d37fca67fa24 100644 --- a/src/SDKs/Batch/DataPlane/Azure.Batch/Protocol/BatchSharedKeyCredential.cs +++ b/src/SDKs/Batch/DataPlane/Azure.Batch/Protocol/BatchSharedKeyCredential.cs @@ -74,24 +74,27 @@ public override Task ProcessHttpRequestAsync(HttpRequestMessage httpRequest, Can signature.Append(httpRequest.Content != null && httpRequest.Content.Headers.Contains("Content-Language") ? httpRequest.Content.Headers.GetValues("Content-Language").FirstOrDefault() : string.Empty).Append('\n'); // Handle content length - if (httpRequest.Content != null) - { - signature.Append(httpRequest.Content.Headers.ContentLength.HasValue ? httpRequest.Content.Headers.ContentLength.ToString() : string.Empty).Append('\n'); - } - else + long? contentLength = httpRequest.Content?.Headers?.ContentLength; + + if (contentLength == null) { - // Because C# httpRequest adds a content-length = 0 header for POST and DELETE even if there is no body, we have to - // sign the request knowing that there will be content-length set. For all other methods that have no body, there will be - // no content length set and thus we append \n with no 0. - if ((httpRequest.Method == HttpMethod.Delete) || (httpRequest.Method == HttpMethod.Post)) + // Because C# httpRequest adds a content-length = 0 header for DELETE, PATCH, and OPTIONS even if there is no body (but only in netframework), we have to + // sign the request knowing that there will be content-length set. +#if FullNetFx + if (httpRequest.Method == HttpMethod.Delete || httpRequest.Method == new HttpMethod("PATCH") || httpRequest.Method == HttpMethod.Options) { - signature.Append("0\n"); + contentLength = 0; } - else +#endif + + // Because C# httpRequest adds a content-length = 0 header for POST even if there is no body, we have to + // sign the request knowing that there will be content-length set. + if (httpRequest.Method == HttpMethod.Post) { - signature.Append('\n'); + contentLength = 0; } } + signature.Append(contentLength).Append('\n'); signature.Append(httpRequest.Content != null && httpRequest.Content.Headers.Contains("Content-MD5") ? httpRequest.Content.Headers.GetValues("Content-MD5").FirstOrDefault() : string.Empty).Append('\n'); signature.Append(httpRequest.Content != null && httpRequest.Content.Headers.Contains("Content-Type") ? httpRequest.Content.Headers.GetValues("Content-Type").FirstOrDefault() : string.Empty).Append('\n'); diff --git a/src/SDKs/Batch/DataPlane/changelog.md b/src/SDKs/Batch/DataPlane/changelog.md index a56b68f5ee67..c9287a74e29b 100644 --- a/src/SDKs/Batch/DataPlane/changelog.md +++ b/src/SDKs/Batch/DataPlane/changelog.md @@ -3,6 +3,14 @@ ### Upcoming changes These changes are planned but haven't been published yet. +### Changes in 7.0.1 +#### Bug fixes +- Fixed a bug where requests using HTTP DELETE (for example, `DeletePool` and `DeleteJob`) failed with an authentication error in the netstandard package. This was due to a change made to `HttpClient` in netcore. + - This bug impacted the 6.1.0 release as well. + +#### REST API version +This version of the Batch .NET client library targets version 2017-05-01.5.0 of the Azure Batch REST API. + ### Changes in 7.0.0 #### License Moved source code and NuGet package from Apache 2.0 license to MIT license. This is more consistent with the other Azure SDKs as well as other open source projects from Microsoft such as .NET.