Skip to content
This repository has been archived by the owner on Jun 30, 2023. It is now read-only.

Adding timeout to http request (sync and async) #118

Merged
merged 1 commit into from
Sep 18, 2014
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
45 changes: 44 additions & 1 deletion src/ADAL.Common/HttpWebRequestWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// limitations under the License.
//----------------------------------------------------------------------

using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
Expand All @@ -26,6 +27,8 @@ internal class HttpWebRequestWrapper : IHttpWebRequest
{
private readonly HttpWebRequest request;

private int timeoutInMilliSeconds = 30000;

public HttpWebRequestWrapper(string uri)
{
this.request = (HttpWebRequest)WebRequest.Create(uri);
Expand Down Expand Up @@ -73,6 +76,14 @@ public WebHeaderCollection Headers
}
}

public int TimeoutInMilliSeconds
Copy link
Member

Choose a reason for hiding this comment

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

How is this method used? I ask because value could be negative.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is in an internal class and the value may only change by our tests, so no need to worry.

{
set
{
this.timeoutInMilliSeconds = value;
}
}

public async Task<IHttpWebResponse> GetResponseSyncOrAsync(CallState callState)
{
if (this.BodyParameters != null)
Expand All @@ -86,10 +97,42 @@ public async Task<IHttpWebResponse> GetResponseSyncOrAsync(CallState callState)
#if ADAL_NET
if (callState != null && callState.CallSync)
{
this.request.Timeout = this.timeoutInMilliSeconds;
return NetworkPlugin.HttpWebRequestFactory.CreateResponse(this.request.GetResponse());
}

Task<WebResponse> getResponseTask = this.request.GetResponseAsync();
System.Threading.ThreadPool.RegisterWaitForSingleObject(
((IAsyncResult)getResponseTask).AsyncWaitHandle,
delegate (object state, bool timedOut)
{
if (timedOut)
{
((HttpWebRequest)state).Abort();
}
},
this.request,
this.timeoutInMilliSeconds,
true);

return NetworkPlugin.HttpWebRequestFactory.CreateResponse(await getResponseTask);
#else
var timer = Windows.System.Threading.ThreadPoolTimer.CreateTimer(
delegate
{
this.request.Abort();
},
TimeSpan.FromMilliseconds(this.timeoutInMilliSeconds));

try
{
return NetworkPlugin.HttpWebRequestFactory.CreateResponse(await this.request.GetResponseAsync());
Copy link
Contributor

Choose a reason for hiding this comment

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

HttpWebRequest class has timeout option. You should be able to set timeout for specific request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does not work for async calls. That is why I had to use the other approaches.

}
finally
{
timer.Cancel();
}
#endif
return NetworkPlugin.HttpWebRequestFactory.CreateResponse(await this.request.GetResponseAsync());
}

public async Task<Stream> GetRequestStreamSyncOrAsync(CallState callState)
Expand Down
26 changes: 26 additions & 0 deletions tests/Test.ADAL.NET.Unit/Test.ADAL.NET.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<DefineConstants>TEST_ADAL_NET</DefineConstants>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -45,7 +47,23 @@
<DelaySign>true</DelaySign>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Owin, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Host.HttpListener, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Microsoft.Owin.Host.HttpListener.2.1.0\lib\net45\Microsoft.Owin.Host.HttpListener.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Hosting, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net45\Microsoft.Owin.Hosting.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Runtime.Serialization" />
Expand Down Expand Up @@ -137,6 +155,7 @@
<Link>valid_cert2.pfx</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
Expand All @@ -158,6 +177,13 @@
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
Expand Down
73 changes: 73 additions & 0 deletions tests/Test.ADAL.NET.Unit/UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,27 @@

using System;
using System.Collections.Generic;
using System.Net;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Owin.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Owin;

using Test.ADAL.Common;

namespace Test.ADAL.NET.Unit
{
[TestClass]
[DeploymentItem("valid_cert.pfx")]
[DeploymentItem("valid_cert2.pfx")]
[DeploymentItem("Microsoft.Owin.Host.HttpListener.dll")]
public class UnitTests
{
private const string ComplexString = "asdfk+j0a-=skjwe43;1l234 1#$!$#%345903485qrq@#$!@#$!(rekr341!#$%Ekfaآزمايشsdsdfsddfdgsfgjsglk==CVADS";
Expand Down Expand Up @@ -255,6 +263,52 @@ public void SignWithCertificateTest()
}
}

[TestMethod]
[TestCategory("AdalDotNetUnit")]
public async Task TimeoutTest()
{
const string TestServiceUrl = "http://localhost:8080";
using (WebApp.Start<TestService>(TestServiceUrl))
{
HttpWebRequestWrapper webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=0&response_code=200") { TimeoutInMilliSeconds = 10000 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), true)); // Synchronous

webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=0&response_code=200") { TimeoutInMilliSeconds = 10000 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), false)); // Asynchronous

try
{
webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=0&response_code=400") { TimeoutInMilliSeconds = 10000 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), false));
}
catch (WebException ex)
{
Verify.AreEqual(ex.Status, WebExceptionStatus.ProtocolError);
}


try
{
webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=10000&response_code=200") { TimeoutInMilliSeconds = 500 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), true)); // Synchronous
}
catch (WebException ex)
{
Verify.AreEqual(ex.Status, WebExceptionStatus.Timeout);
}

try
{
webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=10000&response_code=200") { TimeoutInMilliSeconds = 500 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), false)); // Asynchronous
}
catch (WebException ex)
{
Verify.AreEqual(ex.Status, WebExceptionStatus.RequestCanceled);
}
}
}

private static void RunAuthenticationParametersPositive(string authenticateHeader, string expectedAuthority, string excepectedResource)
{
AuthenticationParameters parameters = AuthenticationParameters.CreateFromResponseAuthenticateHeader(authenticateHeader);
Expand Down Expand Up @@ -334,5 +388,24 @@ private SecureString StringToSecureString(string str)

return secureStr;
}

internal class TestService
{
public void Configuration(IAppBuilder app)
{
app.Run(ctx =>
{
int delay = int.Parse(ctx.Request.Query["delay"]);
if (delay > 0)
{
Thread.Sleep(delay);
}

var response = ctx.Response;
response.StatusCode = int.Parse(ctx.Request.Query["response_code"]);
return response.WriteAsync("dummy");
});
}
}
}
}
31 changes: 31 additions & 0 deletions tests/Test.ADAL.WinRT.Unit/UnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//----------------------------------------------------------------------

using System;
using System.Net;
using System.Threading.Tasks;
using Windows.Storage;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
Expand Down Expand Up @@ -106,5 +107,35 @@ public async Task MsAppRedirectUriTest()
Verify.IsNotNullOrEmptyString(result.Error);
Verify.AreEqual(result.Error, Sts.AuthenticationUiFailedError);
}

[TestMethod]
[TestCategory("AdalWinRTUnit")]
[Ignore] // This test requires TestService to run in a non-WinRT app. The code can be found in tests\Test.ADAL.NET.Unit\UnitTests.cs
public async Task TimeoutTest()
{
const string TestServiceUrl = "http://localhost:8080";
HttpWebRequestWrapper webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=0&response_code=200") { TimeoutInMilliSeconds = 10000 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), false)); // Asynchronous

try
{
webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=0&response_code=400") { TimeoutInMilliSeconds = 10000 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), false));
}
catch (WebException ex)
{
Verify.AreEqual((int)(ex.Status), 7); // ProtocolError
}

try
{
webRequest = new HttpWebRequestWrapper(TestServiceUrl + "?delay=10000&response_code=200") { TimeoutInMilliSeconds = 500 };
await webRequest.GetResponseSyncOrAsync(new CallState(Guid.NewGuid(), false)); // Asynchronous
}
catch (WebException ex)
{
Verify.AreEqual((int)(ex.Status), 6); // RequestCanceled
}
}
}
}