Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
using Windows.Storage.Streams;
using Xunit;

namespace Microsoft.WindowsAzure.ServiceLayer.UnitTests.ServiceBusTests
namespace Microsoft.WindowsAzure.ServiceLayer.UnitTests.HttpTests
{
/// <summary>
/// Tests for the Content class.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// Copyright 2012 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.ServiceLayer.Http;
using Xunit;

namespace Microsoft.WindowsAzure.ServiceLayer.UnitTests.HttpTests
{
/// <summary>
/// HTTP pipeline tests.
/// </summary>
public class PipelineTests
{
/// <summary>
/// Tests passing invalid arguments in the request's constructor.
/// </summary>
[Fact]
public void InvalidArgsInRequestConstructor()
{
Uri validUri = new Uri("http://microsoft.com");
Assert.Throws<ArgumentNullException>(() => new HttpRequest(null, validUri));
Assert.Throws<ArgumentNullException>(() => new HttpRequest("PUT", null));
}

/// <summary>
/// Tests passing invalid arguments in the response's constructor.
/// </summary>
[Fact]
public void InvalidArgsInResponseConstructor()
{
HttpRequest validRequest = new HttpRequest("PUT", new Uri("http://microsoft.com"));
Assert.Throws<ArgumentNullException>(() => new HttpResponse(null, 200));
Assert.Throws<ArgumentOutOfRangeException>(() => new HttpResponse(validRequest, -5));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration.cs" />
<Compile Include="HttpTests\PipelineTests.cs" />
<Compile Include="ServiceBusManagementTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceBusTests\ContentTests.cs" />
<Compile Include="HttpTests\ContentTests.cs" />
<Compile Include="ServiceBusTests\MessageHelper.cs" />
<Compile Include="ServiceBusTests\MessagePropertiesTests.cs" />
<Compile Include="ServiceBusTests\MessageSettingsTests.cs" />
Expand All @@ -134,6 +135,7 @@
<HintPath>References\xunit.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup />
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' ">
<VisualStudioVersion>11.0</VisualStudioVersion>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// Copyright 2012 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The identiation of Licensed seems to be off...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a copy/paste; all files have the same problem. I may eventually fix that, but it's definitely not my highest priority.

// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using System.Collections.Generic;
using System.Diagnostics;

using NetHttpResponseMessage = System.Net.Http.HttpResponseMessage;

namespace Microsoft.WindowsAzure.ServiceLayer.Http
{
/// <summary>
/// Represents an HTTP response message.
/// server.
/// </summary>
public sealed class HttpResponse
{
/// <summary>
/// Gets the request that lead to this response.
/// </summary>
public HttpRequest Request { get; private set; }

/// <summary>
/// Gets the status code of the HTTP response.
/// </summary>
public int StatusCode { get; set; }

/// <summary>
/// Tells whether the HTTP response was successful.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gets whether the HTTP request was successful. However, I am not sure this logic should really be build into an accessor. Could this logic be left for the app to handle it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been done for consistency with System.Net.Http.HttpResponseMessage class, which exposes both StatusCode and IsSuccessStatusCode. I was using this property with the .Net response class, and adding it here let me minimize changes.

/// </summary>
public bool IsSuccessStatusCode { get { return StatusCode >= 200 && StatusCode < 299; } }

/// <summary>
/// Gets or sets the reason phrase which typically is sent by servers
/// together with the status code.
/// </summary>
public string ReasonPhrase { get; set; }

/// <summary>
/// Gets or sets the content of the response.
/// </summary>
public HttpContent Content { get; set; }

/// <summary>
/// Gets the collection of HTTP response headers.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gets a dictionary of HTTP response headers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using sentences from MSDN article on System.Net.Http.HttpResponseMessage class.

/// </summary>
public IDictionary<string, string> Headers { get; private set; }

/// <summary>
/// Initializes the response object.
/// </summary>
/// <param name="originalRequest">Request that initiated the response.</param>
/// <param name="statusCode">Status code of the HTTP response.</param>
public HttpResponse(HttpRequest originalRequest, int statusCode)
{
if (originalRequest == null)
{
throw new ArgumentNullException("originalRequest");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about do a range validation for the statusCode here and throw OutOfRange exception if the statusCode is out of range? Have u thought about leveraging on existing HttpStatusCode enum? http://msdn.microsoft.com/en-us/library/system.net.httpstatuscode.aspx

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (!Enum.IsDefined(typeof(System.Net.HttpStatusCode), statusCode))
{
throw new ArgumentOutOfRangeException("statusCode");
}

Request = originalRequest;
StatusCode = statusCode;
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}

/// <summary>
/// Initializes the response object.
/// </summary>
/// <param name="originalRequest">Request that initiated the response.</param>
/// <param name="response">.Net HTTP response.</param>
internal HttpResponse(HttpRequest originalRequest, NetHttpResponseMessage response)
{
Debug.Assert(originalRequest != null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug code shouldn't be in product code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assertions are valuable tools for detecting errors.

Debug.Assert(response != null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug code shouldn't be in product code.


Request = originalRequest;
StatusCode = (int)response.StatusCode;
ReasonPhrase = response.ReasonPhrase;
Content = HttpContent.CreateFromResponse(response);
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember FxCop used to complain about this. FxCop recommends to create the dictionary by default, each time we need to renew the dictionary, we just call Headers.Clear(). What does FxCop say this time?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It currently complains about many things, but not about this one. I have decided to instantiate the object myself instead of letting users create their own dictionaries because I wanted to control which string comparer is used for keys. With my design keys are case-insensitive, and users cannot change that.


foreach (KeyValuePair<string, IEnumerable<string>> item in response.Headers)
{
string valueString = string.Join(string.Empty, item.Value);
Headers.Add(item.Key, valueString);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<Compile Include="Http\HttpContent.cs" />
<Compile Include="Http\HttpMethod.cs" />
<Compile Include="Http\HttpRequest.cs" />
<Compile Include="Http\HttpResponse.cs" />
<Compile Include="Http\IHttpContent.cs" />
<Compile Include="Http\MemoryContent.cs" />
<Compile Include="Http\StreamContent.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
using Windows.Foundation;
using Windows.Storage.Streams;

using NetHttpResponseMessage = System.Net.Http.HttpResponseMessage;

namespace Microsoft.WindowsAzure.ServiceLayer.ServiceBus
{
/// <summary>
Expand Down Expand Up @@ -231,9 +229,9 @@ public IAsyncInfo CopyContentToAsync(IOutputStream stream)
/// </summary>
/// <param name="response">Response with data.</param>
/// <returns></returns>
internal static BrokeredMessageInfo CreateFromPeekResponse(NetHttpResponseMessage response)
internal static BrokeredMessageInfo CreateFromPeekResponse(HttpResponse response)
{
if (response.StatusCode == System.Net.HttpStatusCode.NoContent || response.StatusCode == System.Net.HttpStatusCode.ResetContent)
if (response.StatusCode == (int)System.Net.HttpStatusCode.NoContent || response.StatusCode == (int)System.Net.HttpStatusCode.ResetContent)
{
return null;
}
Expand All @@ -244,21 +242,16 @@ internal static BrokeredMessageInfo CreateFromPeekResponse(NetHttpResponseMessag
/// Constructor. Initializes the object from the HTTP response.
/// </summary>
/// <param name="response">HTTP reponse with the data.</param>
internal BrokeredMessageInfo(NetHttpResponseMessage response)
internal BrokeredMessageInfo(HttpResponse response)
{
Debug.Assert(response.IsSuccessStatusCode);
_content = HttpContent.CreateFromResponse(response);
_content = response.Content;
_customProperties = new CustomPropertiesDictionary(response);

DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Dictionary<string, object>));
string propertiesString = null;
IEnumerable<string> values;

if (response.Headers.TryGetValues(Constants.BrokerPropertiesHeader, out values))
{
propertiesString = string.Join(string.Empty, values);
}

response.Headers.TryGetValue(Constants.BrokerPropertiesHeader, out propertiesString);
if (string.IsNullOrEmpty(propertiesString))
{
_brokerProperties = new BrokerProperties();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ internal CustomPropertiesDictionary()
/// Initializes a dictionary with properties from the response.
/// </summary>
/// <param name="response">Response.</param>
internal CustomPropertiesDictionary(NetHttpResponseMessage response)
internal CustomPropertiesDictionary(HttpResponse response)
: this()
{
foreach (KeyValuePair<string, IEnumerable<string>> item in response.Headers)
foreach (KeyValuePair<string, string> item in response.Headers)
{
string key = item.Key;
string valueString = string.Join(string.Empty, item.Value);
string valueString = item.Value;
JsonValue translatedValue;

if (JsonValue.TryParse(valueString, out translatedValue) && IsSupportedType(translatedValue.ValueType))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.ServiceLayer.Http;
Expand Down Expand Up @@ -626,7 +625,7 @@ IAsyncOperation<BrokeredMessageInfo> IServiceBusService.PeekQueueMessageAsync(st
Uri uri = ServiceConfig.GetUnlockedMessageUri(queueName, lockInterval);
HttpRequest request = new HttpRequest(HttpMethod.Post, uri);
return SendAsync(request, CheckNoContent)
.ContinueWith((t) => BrokeredMessageInfo.CreateFromPeekResponse(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith(t => BrokeredMessageInfo.CreateFromPeekResponse(t.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
.AsAsyncOperation();
}

Expand Down Expand Up @@ -857,11 +856,11 @@ private IAsyncOperation<IEnumerable<TInfo>> GetItemsAsync<TInfo>(
/// <param name="initAction">Initialization action.</param>
/// <param name="extraTypes">Extra types for deserialization.</param>
/// <returns>Collection of deserialized items.</returns>
private IEnumerable<TInfo> GetItems<TInfo>(NetHttpResponseMessage response, Action<SyndicationItem, TInfo> initAction, params Type[] extraTypes)
private IEnumerable<TInfo> GetItems<TInfo>(HttpResponse response, Action<SyndicationItem, TInfo> initAction, params Type[] extraTypes)
{
Debug.Assert(response.IsSuccessStatusCode);
SyndicationFeed feed = new SyndicationFeed();
feed.Load(response.Content.ReadAsStringAsync().Result);
feed.Load(response.Content.ReadAsStringAsync().AsTask().Result);

return SerializationHelper.DeserializeCollection<TInfo>(feed, initAction, extraTypes);
}
Expand Down Expand Up @@ -892,11 +891,11 @@ private IAsyncOperation<TInfo> GetItemAsync<TInfo>(Uri itemUri, Action<Syndicati
/// <param name="initAction">Initialization action for deserialized items.</param>
/// <param name="extraTypes">Extra types for deserialization.</param>
/// <returns>Deserialized object.</returns>
private TInfo GetItem<TInfo>(NetHttpResponseMessage response, Action<SyndicationItem, TInfo> initAction, params Type[] extraTypes)
private TInfo GetItem<TInfo>(HttpResponse response, Action<SyndicationItem, TInfo> initAction, params Type[] extraTypes)
{
Debug.Assert(response.IsSuccessStatusCode);
XmlDocument doc = new XmlDocument();
doc.LoadXml(response.Content.ReadAsStringAsync().Result);
doc.LoadXml(response.Content.ReadAsStringAsync().AsTask().Result);

SyndicationItem feedItem = new SyndicationItem();
feedItem.LoadFromXml(doc);
Expand Down Expand Up @@ -935,7 +934,7 @@ private IAsyncOperation<TInfo> CreateItemAsync<TInfo, TSettings>(

return Task.Factory
.StartNew(() => SetBody(request, itemSettings, ExtraRuleTypes))
.ContinueWith<NetHttpResponseMessage>(tr => SendAsync(request).Result, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith<HttpResponse>(tr => SendAsync(request).Result, TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith<TInfo>(tr => GetItem<TInfo>(tr.Result, initAction, ExtraRuleTypes), TaskContinuationOptions.OnlyOnRanToCompletion)
.AsAsyncOperation<TInfo>();
}
Expand Down Expand Up @@ -999,15 +998,15 @@ private static void InitRule(SyndicationItem feedItem, RuleInfo ruleInfo)
/// <param name="response">Source HTTP response.</param>
/// <param name="validators">Additional validators.</param>
/// <returns>Processed HTTP response.</returns>
private NetHttpResponseMessage CheckResponse(NetHttpResponseMessage response, IEnumerable<Func<NetHttpResponseMessage, NetHttpResponseMessage>> validators)
private HttpResponse CheckResponse(HttpResponse response, IEnumerable<Func<HttpResponse, HttpResponse>> validators)
{
if (!response.IsSuccessStatusCode)
{
throw new WindowsAzureServiceException(response);
}

// Pass the response through all validators.
foreach (Func<NetHttpResponseMessage, NetHttpResponseMessage> validator in validators)
foreach (Func<HttpResponse, HttpResponse> validator in validators)
{
response = validator(response);
}
Expand All @@ -1019,21 +1018,22 @@ private NetHttpResponseMessage CheckResponse(NetHttpResponseMessage response, IE
/// </summary>
/// <param name="request">Request to send.</param>
/// <returns>HTTP response.</returns>
private Task<NetHttpResponseMessage> SendAsync(HttpRequest request, params Func<NetHttpResponseMessage, NetHttpResponseMessage>[] validators)
private Task<HttpResponse> SendAsync(HttpRequest request, params Func<HttpResponse, HttpResponse>[] validators)
{
NetHttpRequestMessage netRequest = request.CreateNetRequest();
return Channel.SendAsync(netRequest)
.ContinueWith<NetHttpResponseMessage>((task) => CheckResponse(task.Result, validators), TaskContinuationOptions.OnlyOnRanToCompletion);
.ContinueWith<HttpResponse>(t => new HttpResponse(request, t.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
.ContinueWith<HttpResponse>(t => CheckResponse(t.Result, validators));
}

/// <summary>
/// Throws exceptions for response with no content.
/// </summary>
/// <param name="response">Source response.</param>
/// <returns>Processed HTTP response.</returns>
private NetHttpResponseMessage CheckNoContent(NetHttpResponseMessage response)
private HttpResponse CheckNoContent(HttpResponse response)
{
if (response.StatusCode == System.Net.HttpStatusCode.NoContent || response.StatusCode == HttpStatusCode.ResetContent)
if (response.StatusCode == (int)System.Net.HttpStatusCode.NoContent || response.StatusCode == (int)System.Net.HttpStatusCode.ResetContent)
{
throw new WindowsAzureServiceException(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.ServiceLayer.Http;

namespace Microsoft.WindowsAzure.ServiceLayer
{
Expand All @@ -31,7 +30,7 @@ internal class WindowsAzureServiceException: WindowsAzureException
/// <summary>
/// Gets the HTTP status code.
/// </summary>
internal HttpStatusCode StatusCode { get; private set; }
internal int StatusCode { get; private set; }

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason here that we drop the usage of HttpStatusCode?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't used anyway. I am planning to improve error handling soon, however.

/// <summary>
/// Gets the reason string fore th exception.
Expand All @@ -42,7 +41,7 @@ internal class WindowsAzureServiceException: WindowsAzureException
/// Constructor.
/// </summary>
/// <param name="response">Source HTTP response.</param>
internal WindowsAzureServiceException(HttpResponseMessage response)
internal WindowsAzureServiceException(HttpResponse response)
{
StatusCode = response.StatusCode;
ReasonPhrase = response.ReasonPhrase;
Expand Down