-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathProgram.cs
171 lines (147 loc) · 7.4 KB
/
Program.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
164
165
166
167
168
169
170
171
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Knapcode.MiniZip
{
public class Program
{
public static async Task Main(string[] args)
{
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
await LargeAsync();
}
private static async Task SmallAsync()
{
var url = "https://api.nuget.org/v3-flatcontainer/newtonsoft.json/10.0.3/newtonsoft.json.10.0.3.nupkg";
using (var httpClient = new HttpClient())
{
var httpZipProvider = new HttpZipProvider(httpClient);
using (var zipDirectoryReader = await httpZipProvider.GetReaderAsync(new Uri(url)))
{
var zipDirectory = await zipDirectoryReader.ReadAsync();
Console.WriteLine("Top 5 ZIP entries by compressed size:");
var entries = zipDirectory
.Entries
.OrderByDescending(x => x.GetCompressedSize())
.Take(5)
.ToList();
for (var i = 0; i < entries.Count; i++)
{
Console.WriteLine($"{i + 1}. {entries[i].GetName()}");
}
}
}
}
private static async Task LargeAsync()
{
// Use the top 5 NuGet packages as an example.
var urls = new[]
{
"https://api.nuget.org/v3-flatcontainer/newtonsoft.json/10.0.3/newtonsoft.json.10.0.3.nupkg",
"https://api.nuget.org/v3-flatcontainer/nunit/3.9.0/nunit.3.9.0.nupkg",
"https://api.nuget.org/v3-flatcontainer/entityframework/6.2.0/entityframework.6.2.0.nupkg",
"https://api.nuget.org/v3-flatcontainer/jquery/3.2.1/jquery.3.2.1.nupkg",
"https://api.nuget.org/v3-flatcontainer/htmlagilitypack/1.6.7/htmlagilitypack.1.6.7.nupkg",
};
// Set up and HTTP client that logs HTTP requests, to help clarify this example.
using (var httpClientHandler = new HttpClientHandler())
using (var loggingHandler = new LoggingHandler { InnerHandler = httpClientHandler })
using (var httpClient = new HttpClient(loggingHandler))
{
// This provider uses and HTTP client and initializes a ZipDirectoryReader from a URL. This URL
// must support HEAD method, Content-Length response header, and Range request header.
var httpZipProvider = new HttpZipProvider(httpClient)
{
RequireAcceptRanges = false,
};
foreach (var url in urls)
{
Console.WriteLine(new string('=', 40));
Console.WriteLine();
// Initialize the reader. This performs a HEAD request to determine if the length of the
// ZIP file and whether the URL supports Range requests.
using (var reader = await httpZipProvider.GetReaderAsync(new Uri(url)))
{
// Read the ZIP file by requesting just the Central Directory part of the .zip.
var zipDirectory = await reader.ReadAsync();
// At this point, we known all about the entries of the .zip file include name, compressed
// size, and relative offset in the .zip file.
Console.WriteLine("Top 5 ZIP entries by compressed size:");
var entries = zipDirectory
.Entries
.OrderByDescending(x => x.GetCompressedSize())
.Take(5)
.ToList();
for (var i = 0; i < entries.Count; i++)
{
Console.WriteLine($"{i + 1}. {entries[i].GetName()} ({entries[i].GetCompressedSize():N0} bytes)");
}
}
Console.WriteLine();
}
Console.WriteLine(new string('=', 40));
Console.WriteLine();
// Summarize the work done. For NuGet packages, it is very common to download less than 1% of the
// package content while determining the .zip entry metadata.
var ratio = ((double)loggingHandler.TotalResponseBodyBytes) / loggingHandler.TotalContentLength;
Console.WriteLine($"Total ZIP files checked: {urls.Length:N0}");
Console.WriteLine($"Total HTTP requests: {loggingHandler.TotalRequests:N0}");
Console.WriteLine($"Total Content-Length bytes: {loggingHandler.TotalContentLength:N0}");
Console.WriteLine($"Actual downloaded bytes: {loggingHandler.TotalResponseBodyBytes:N0}"); // well, sort of...
Console.WriteLine($"Downloaded %: {Math.Round(ratio * 100, 3):0.000}%");
}
}
private class LoggingHandler : DelegatingHandler
{
public long _totalResponseBodyBytes = 0;
public long _totalContentLength = 0;
public int _totalRequests = 0;
private static readonly HashSet<string> InterestingHeaders = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Range",
"Accept-Ranges",
"Content-Range",
"Content-Length",
"ETag",
"If-Match",
};
public long TotalResponseBodyBytes => _totalResponseBodyBytes;
public long TotalContentLength => _totalContentLength;
public int TotalRequests => _totalRequests;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine($"==> {request.Method} {request.RequestUri}");
OutputHeaders(request.Headers);
Console.WriteLine();
var response = await base.SendAsync(request, cancellationToken);
Console.WriteLine($"<== {(int)response.StatusCode} {response.ReasonPhrase}");
OutputHeaders(response.Headers.Concat(response.Content.Headers));
Console.WriteLine();
if (request.Method == HttpMethod.Get)
{
Interlocked.Add(ref _totalResponseBodyBytes, response.Content.Headers.ContentLength.Value);
}
else if (request.Method == HttpMethod.Head)
{
Interlocked.Add(ref _totalContentLength, response.Content.Headers.ContentLength.Value);
}
Interlocked.Increment(ref _totalRequests);
return response;
}
private static void OutputHeaders(IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers)
{
foreach (var header in headers)
{
if (InterestingHeaders.Contains(header.Key))
{
Console.WriteLine($" {header.Key}: {string.Join(", ", header.Value)}");
}
}
}
}
}
}