Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #127 from mono/http-client
Browse files Browse the repository at this point in the history
Support using HttpClient when downloading files
  • Loading branch information
slluis authored Nov 20, 2018
2 parents e84472d + c3bb5b6 commit 67cd093
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 35 deletions.
3 changes: 3 additions & 0 deletions Mono.Addins.Setup/Mono.Addins.Setup.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<PackageReference Include="SharpZipLib" Version="0.86.0" />
<ProjectReference Include="..\Mono.Addins\Mono.Addins.csproj">
<Project>{91DD5A2D-9FE3-4C3C-9253-876141874DAD}</Project>
Expand Down Expand Up @@ -89,6 +90,8 @@
<Compile Include="Mono.Addins.Setup\WebRequestHelper.cs" />
<Compile Include="Mono.Addins.Setup\AddinRepositoryProvider.cs" />
<Compile Include="Mono.Addins.Setup\MonoAddinsRepositoryProvider.cs" />
<Compile Include="Mono.Addins.Setup\HttpClientProvider.cs" />
<Compile Include="Mono.Addins.Setup\DownloadFileRequest.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
59 changes: 37 additions & 22 deletions Mono.Addins.Setup/Mono.Addins.Setup/AddinStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Mono.Addins.Setup
{
Expand Down Expand Up @@ -641,28 +642,30 @@ internal string DownloadFile (IProgressMonitor monitor, string url)

try {
monitor.BeginTask ("Requesting " + url, 2);
var resp = WebRequestHelper.GetResponse (
() => (HttpWebRequest)WebRequest.Create (url),
r => r.Headers ["Pragma"] = "no-cache"
);
monitor.Step (1);
monitor.BeginTask ("Downloading " + url, (int) resp.ContentLength);
var task = DownloadFileRequest.DownloadFile (url, noCache: true);
if (!WaitForTask (task, monitor))
throw new InstallException ("Installation cancelled.");

file = Path.GetTempFileName ();
fs = new FileStream (file, FileMode.Create, FileAccess.Write);
s = resp.GetResponseStream ();
byte[] buffer = new byte [4096];

int n;
while ((n = s.Read (buffer, 0, buffer.Length)) != 0) {
monitor.Step (n);
fs.Write (buffer, 0, n);
if (monitor.IsCancelRequested)
throw new InstallException ("Installation cancelled.");
using (var request = task.Result) {
monitor.Step (1);
monitor.BeginTask ("Downloading " + url, (int) request.ContentLength);

file = Path.GetTempFileName ();
fs = new FileStream (file, FileMode.Create, FileAccess.Write);
s = request.Stream;
byte[] buffer = new byte [4096];

int n;
while ((n = s.Read (buffer, 0, buffer.Length)) != 0) {
monitor.Step (n);
fs.Write (buffer, 0, n);
if (monitor.IsCancelRequested)
throw new InstallException ("Installation cancelled.");
}
fs.Close ();
s.Close ();
return file;
}
fs.Close ();
s.Close ();
return file;
} catch {
if (fs != null)
fs.Close ();
Expand All @@ -673,10 +676,22 @@ internal string DownloadFile (IProgressMonitor monitor, string url)
throw;
} finally {
monitor.EndTask ();
monitor.EndTask ();
}
}


static bool WaitForTask (Task<DownloadFileRequest> task, IProgressMonitor monitor)
{
bool result = SpinWait.SpinUntil (() => {
return monitor.IsCancelRequested || task.IsCompleted || task.IsFaulted;
}, 100000); // Use same default timeout as HttpClient.

if (monitor.IsCancelRequested)
return false;
if (!result)
throw new InstallException ("Timed out.");
return result;
}

internal bool HasWriteAccess (string file)
{
FileInfo f = new FileInfo (file);
Expand Down
129 changes: 129 additions & 0 deletions Mono.Addins.Setup/Mono.Addins.Setup/DownloadFileRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// WebRequestWrapper.cs
//
// Author:
// Matt Ward <[email protected]>
//
// Copyright (c) 2018 Microsoft
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace Mono.Addins.Setup
{
abstract class DownloadFileRequest : IDisposable
{
public abstract void Dispose ();
public abstract int ContentLength { get; }
public abstract Stream Stream { get; }

public static Task<DownloadFileRequest> DownloadFile (string url, bool noCache)
{
if (HttpClientProvider.HasCustomCreation || !WebRequestHelper.HasCustomRequestHandler)
return HttpClientDownloadFileRequest.Create (url, noCache);

return WebRequestDownloadFileRequest.Create (url, noCache);
}
}

class HttpClientDownloadFileRequest : DownloadFileRequest
{
HttpClient client;
HttpResponseMessage response;
Stream stream;

public static Task<DownloadFileRequest> Create (string url, bool noCache)
{
// Use Task.Run to avoid hanging the UI thread when waiting for the GetAsync method to return
// with the response for an .mpack file download.
return Task.Run<DownloadFileRequest> (async () => {
var client = HttpClientProvider.CreateHttpClient (url);
if (noCache)
client.DefaultRequestHeaders.Add ("Pragma", "no-cache");

var response = await client.GetAsync (url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait (false);
var stream = await response.Content.ReadAsStreamAsync ().ConfigureAwait (false);

return new HttpClientDownloadFileRequest {
client = client,
response = response,
stream = stream
};
});
}

public override int ContentLength {
get { return (int)response.Content.Headers.ContentLength; }
}

public override Stream Stream {
get { return stream; }
}

public override void Dispose ()
{
stream?.Dispose ();
response?.Dispose ();
client.Dispose ();
}
}

class WebRequestDownloadFileRequest : DownloadFileRequest
{
WebResponse response;
Stream stream;

public static Task<DownloadFileRequest> Create (string url, bool noCache)
{
var response = WebRequestHelper.GetResponse (
() => (HttpWebRequest)WebRequest.Create (url),
r => {
if (noCache)
r.Headers ["Pragma"] = "no-cache";
}
);

var request = new WebRequestDownloadFileRequest {
response = response,
stream = response.GetResponseStream ()
};

return Task.FromResult<DownloadFileRequest> (request);
}

public override int ContentLength {
get { return (int)response.ContentLength; }
}

public override Stream Stream {
get { return stream; }
}

public override void Dispose ()
{
stream?.Dispose ();
response.Dispose ();
}
}
}
65 changes: 65 additions & 0 deletions Mono.Addins.Setup/Mono.Addins.Setup/HttpClientProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// HttpClientProvider.cs
//
// Author:
// Matt Ward <[email protected]>
//
// Copyright (c) 2018 Microsoft
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Net;
using System.Net.Http;

namespace Mono.Addins.Setup
{
/// <summary>
/// Creates a HttpClient with support for authenticated proxies.
/// </summary>
public static class HttpClientProvider
{
static Func<string, HttpClient> httpClientFactory;

public static void SetHttpClientFactory (Func<string, HttpClient> factory)
{
httpClientFactory = factory;
}

static internal bool HasCustomCreation {
get { return httpClientFactory != null; }
}

/// <summary>
/// Creates a new HttpClient.
/// </summary>
/// <returns>The HttpClient.</returns>
/// <param name="uri">The request url.</param>
public static HttpClient CreateHttpClient (string uri)
{
if (httpClientFactory != null)
return httpClientFactory.Invoke (uri);

var handler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
return new HttpClient (handler);
}
}
}
32 changes: 19 additions & 13 deletions Mono.Addins.Setup/Mono.Addins.Setup/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,27 @@ public IAsyncResult BeginDownloadSupportFile (string name, AsyncCallback cb, obj
}

res.FilePath = cachedFile;
WebRequestHelper.GetResponseAsync (() => (HttpWebRequest)WebRequest.Create (u)).ContinueWith (t => {
var request = DownloadFileRequest.DownloadFile (u.ToString (), false).ContinueWith (t => {
try {
var resp = t.Result;
string dir = Path.GetDirectoryName (res.FilePath);
lock (this) {
if (!Directory.Exists (dir))
Directory.CreateDirectory (dir);
}
byte[] buffer = new byte [8092];
using (var s = resp.GetResponseStream ()) {
using (var f = File.OpenWrite (res.FilePath)) {
int nr = 0;
while ((nr = s.Read (buffer, 0, buffer.Length)) > 0)
f.Write (buffer, 0, nr);
using (var resp = t.Result) {
string dir = Path.GetDirectoryName (res.FilePath);
lock (this) {
if (!Directory.Exists (dir))
Directory.CreateDirectory (dir);
}
if (File.Exists (res.FilePath)) {
res.SetDone ();
return;
}
byte [] buffer = new byte [8092];
using (var s = resp.Stream) {
using (var f = File.OpenWrite (res.FilePath)) {
int nr = 0;
while ((nr = s.Read (buffer, 0, buffer.Length)) > 0)
f.Write (buffer, 0, nr);
}
}
res.SetDone ();
}
} catch (Exception ex) {
res.Error = ex;
Expand Down
4 changes: 4 additions & 0 deletions Mono.Addins.Setup/Mono.Addins.Setup/WebRequestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public static void SetRequestHandler (Func<Func<HttpWebRequest>, Action<HttpWebR
_handler = handler;
}

static internal bool HasCustomRequestHandler {
get { return _handler != null; }
}

/// <summary>
/// Gets the web response, using the request handler to handle proxy authentication
/// if necessary.
Expand Down

0 comments on commit 67cd093

Please sign in to comment.