forked from petabridge/akkadotnet-code-samples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHttpDownloaderActor.cs
163 lines (134 loc) · 6.14 KB
/
HttpDownloaderActor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Threading.Tasks;
using Akka.Actor;
namespace PipeTo.App.Actors
{
/// <summary>
/// Downloads content over HTTP asynchronously using <see cref="PipeTo"/>
/// </summary>
public class HttpDownloaderActor : ReceiveActor
{
#region Message types
/// <summary>
/// Command used to initiate the download of an image
/// </summary>
public class DownloadImage
{
public DownloadImage(string feedUri, string imageUrl)
{
ImageUrl = imageUrl;
FeedUri = feedUri;
}
public string FeedUri { get; private set; }
public string ImageUrl { get; private set; }
}
/// <summary>
/// Result of an asynchronous task from the HttpClient
/// </summary>
public class ImageDownloadResult
{
public ImageDownloadResult(DownloadImage imageDownloadCommand, HttpStatusCode statusCode) : this(imageDownloadCommand, statusCode, null)
{
}
public ImageDownloadResult(DownloadImage imageDownloadCommand, HttpStatusCode statusCode, Stream content)
{
Content = content;
StatusCode = statusCode;
ImageDownloadCommand = imageDownloadCommand;
}
public DownloadImage ImageDownloadCommand { get; private set; }
public HttpStatusCode StatusCode { get; private set; }
/// <summary>
/// Can be null!
/// </summary>
public Stream Content { get; private set; }
}
#endregion
private readonly HttpClient _httpClient;
private readonly string _consoleWriterActorPath;
public HttpDownloaderActor() : this(ActorNames.ConsoleWriterActor.Path) { }
public HttpDownloaderActor(string consoleWriterPath) : this(new HttpClient(), consoleWriterPath) { }
public HttpDownloaderActor(HttpClient httpClient, string consoleWriterActorPath)
{
_httpClient = httpClient;
_consoleWriterActorPath = consoleWriterActorPath;
Initialize();
}
/// <summary>
/// Used to define all of our <see cref="Receive"/> hooks for <see cref="HttpDownloaderActor"/>
/// </summary>
private void Initialize()
{
//Command to begin downloading an image
Receive<DownloadImage>(image =>
{
SendMessage(string.Format("Beginning download of img {0} for feed {1}", image.ImageUrl, image.FeedUri));
//check for relative URLs
var imageUrl = image.ImageUrl;
if (!Uri.IsWellFormedUriString(image.ImageUrl, UriKind.Absolute))
{
var baseAddress = new Uri(image.FeedUri);
//Combine the base address and relative URL of image to form an absolute one.
imageUrl = string.Format("{0}://{1}{2}", baseAddress.Scheme, baseAddress.Host, imageUrl);
}
//asynchronously download the image and pipe the results to ourself
_httpClient.GetAsync(imageUrl).ContinueWith(httpRequest =>
{
var response = httpRequest.Result;
//successful img download - which happened in a DIFFERENT THREAD
if (response.StatusCode == HttpStatusCode.OK)
{
// async call, inside an async call
// and we wait on it...
// BUT THIS IS STILL ASYNCHRONOUS?!?!
// INSERT INCEPTION HORN SOUND EFFECT HERE https://www.youtube.com/watch?v=ZKGJZt83_JE
var contentStream = response.Content.ReadAsStreamAsync();
try
{
contentStream.Wait(TimeSpan.FromSeconds(1));
return new ImageDownloadResult(image, response.StatusCode, contentStream.Result);
}
catch //timeout exceptions!
{
return new ImageDownloadResult(image, HttpStatusCode.PartialContent);
}
}
return new ImageDownloadResult(image, response.StatusCode);
}, TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.ExecuteSynchronously).PipeTo(Self);
});
//Process the results of our asynchronous download
Receive<ImageDownloadResult>(imagedownload =>
{
//Successful download
if (imagedownload.StatusCode == HttpStatusCode.OK)
{
//Print a status message
SendMessage(string.Format("Successfully downloaded image {0} [{1}kb]",
imagedownload.ImageDownloadCommand.ImageUrl, imagedownload.Content.Length/1000), PipeToSampleStatusCode.Success);
}
else //failed download
{
//Print a status message
SendMessage(string.Format("Failed to download image {0}",
imagedownload.ImageDownloadCommand.ImageUrl));
}
//Let the coordinator know that we've made progress, even if the download failed
Context.Parent.Tell(
new FeedParserCoordinator.DownloadComplete(imagedownload.ImageDownloadCommand.FeedUri, 0, 1));
});
}
#region Messaging methods
private void SendMessage(string message, PipeToSampleStatusCode pipeToSampleStatus = PipeToSampleStatusCode.Normal)
{
//create the message instance
var consoleMsg = StatusMessageHelper.CreateMessage(message, pipeToSampleStatus);
//Select the ConsoleWriterActor and send it a message
Context.ActorSelection(_consoleWriterActorPath).Tell(consoleMsg);
}
#endregion
}
}