├── Logo.png ├── .gitignore ├── examples ├── download │ ├── download.csproj │ └── Program.cs ├── example1 │ ├── example1.csproj │ └── Program.cs └── TDown │ ├── TDown.csproj │ ├── Program.cs │ ├── Form1.cs │ └── Form1.Designer.cs ├── src ├── HttpDown │ ├── HttpDownExtension.cs │ ├── HttpDown.Option.cs │ └── HttpDown.cs ├── Core │ ├── ITask.cs │ ├── Val.cs │ ├── Files.cs │ ├── HttpOption.cs │ ├── Api.cs │ ├── Http.cs │ └── MimeMapping.cs ├── HttpLib.csproj ├── HttpCore.Call.cs ├── Config.cs ├── HttpCore.cs └── HttpCore.Request.cs ├── LICENSE ├── HttpLib.sln └── README.md /Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eviav/HttpLib/HEAD/Logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin-debug/ 3 | bin-release/ 4 | [Oo]bj/ 5 | [Bb]in/ 6 | 7 | # Other files and folders 8 | *.csproj.user 9 | 10 | # Executables 11 | *.swf 12 | *.air 13 | *.ipa 14 | *.apk 15 | *.vcxproj.filters 16 | *.vcxproj.user 17 | *.pubxml.user 18 | 19 | /.vs 20 | /packages 21 | /nuget* 22 | -------------------------------------------------------------------------------- /examples/download/download.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/example1/example1.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/HttpDown/HttpDownExtension.cs: -------------------------------------------------------------------------------- 1 | namespace HttpLib 2 | { 3 | public static class HttpDownExtension 4 | { 5 | /// 6 | /// 下载(多线程) 7 | /// 8 | /// 核心 9 | /// 保存路径 10 | /// 任务id 11 | public static HttpDown downLoad(this HttpCore core, string savePath, string? id = null) => new HttpDown(core, savePath, id); 12 | } 13 | } -------------------------------------------------------------------------------- /examples/TDown/TDown.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Core/ITask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace HttpLib 5 | { 6 | public class ITask 7 | { 8 | public static Task Run(Action action, Action? end = null) 9 | { 10 | if (end == null) 11 | { 12 | #if NET40 13 | return Task.Factory.StartNew(action); 14 | #else 15 | return Task.Run(action); 16 | #endif 17 | } 18 | #if NET40 19 | return Task.Factory.StartNew(action).ContinueWith(action => { end(); }); 20 | #else 21 | return Task.Run(action).ContinueWith(action => { end(); }); 22 | #endif 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /examples/TDown/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows.Forms; 4 | 5 | namespace TDown 6 | { 7 | static class Program 8 | { 9 | public static System.Diagnostics.Process Pro = System.Diagnostics.Process.GetCurrentProcess(); 10 | public static string ExePath = Pro.MainModule.FileName; 11 | public static string BasePath = new FileInfo(ExePath).DirectoryName + Path.DirectorySeparatorChar; 12 | 13 | /// 14 | /// The main entry point for the application. 15 | /// 16 | [STAThread] 17 | static void Main() 18 | { 19 | Application.SetHighDpiMode(HighDpiMode.SystemAware); 20 | Application.EnableVisualStyles(); 21 | Application.SetCompatibleTextRenderingDefault(false); 22 | Application.Run(new Form1()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Administrators 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Core/Val.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HttpLib 4 | { 5 | public class Val 6 | { 7 | public Val(string key, int? value) : this(key, value?.ToString()) { } 8 | public Val(string key, long? value) : this(key, value?.ToString()) { } 9 | public Val(string key, double? value) : this(key, value?.ToString()) { } 10 | public Val(string key, int value) : this(key, value.ToString()) { } 11 | public Val(string key, long value) : this(key, value.ToString()) { } 12 | public Val(string key, double value) : this(key, value.ToString()) { } 13 | public Val(string key, string? value) 14 | { 15 | Key = key; 16 | Value = value; 17 | } 18 | 19 | /// 20 | /// 键 21 | /// 22 | public string Key { get; set; } 23 | /// 24 | /// 值 25 | /// 26 | public string? Value { get; set; } 27 | 28 | public override string ToString() => Key + "=" + Value; 29 | 30 | public string ToStringEscape() 31 | { 32 | if (Value != null) return Key + "=" + Uri.EscapeDataString(Value); 33 | else return Key + "="; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/HttpLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net4.0;net4.5;net4.6;net4.8;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0 4 | Tom 5 | 10.0 6 | disable 7 | enable 8 | $(Version) 9 | $(Version) 10 | 3.0.6 11 | Copyright © Tom 2021-2025 12 | 便捷的HttpClient库 13 | 支持GET/POST/PUT/Delete 上传文件/文件参数 14 | =========================== 15 | 使用HttpWebRequest实现 16 | Tom.HttpLib 17 | Logo.png 18 | https://github.com/Haku-Men/HttpLib 19 | git 20 | Http;HttpLib;HttpHelper;HttpClient;HttpDown;DownLoad 21 | https://github.com/EVA-SS/HttpLib 22 | README.md 23 | True 24 | False 25 | MIT 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/download/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using HttpLib; 4 | 5 | var savapath = Http.Get("https://dldir1.qq.com/qqfile/qq/QQNT/Windows/QQ_9.9.9_240422_x64_01.exe") 6 | .redirect() 7 | .responseProgres((bytesSent, totalBytes) => 8 | { 9 | Console.SetCursorPosition(0, 0); 10 | if (totalBytes > 0) 11 | { 12 | double prog = (bytesSent * 1.0) / (totalBytes * 1.0); 13 | Console.Write("{0}% 下载 {1}/{2} ", Math.Round(prog * 100.0, 1).ToString("N1"), CountSize(bytesSent), CountSize(totalBytes)); 14 | } 15 | else 16 | { 17 | Console.Write("{0} 下载 ", CountSize(bytesSent)); 18 | } 19 | }).download(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "qq.exe"); 20 | 21 | if (savapath != null) Console.WriteLine("下载成功保存至:" + savapath); 22 | else Console.WriteLine("下载失败"); 23 | 24 | 25 | Console.ReadLine(); 26 | 27 | static string CountSize(double Size) 28 | { 29 | string houzui = "B"; 30 | double FactSize = Size; 31 | if (FactSize >= 1024) 32 | { 33 | houzui = "K"; 34 | FactSize /= 1024.00; 35 | } 36 | if (FactSize >= 1024) 37 | { 38 | houzui = "M"; 39 | FactSize /= 1024.00; 40 | } 41 | if (FactSize >= 1024) 42 | { 43 | houzui = "G"; 44 | FactSize /= 1024.00; 45 | } 46 | if (FactSize >= 1024) 47 | { 48 | houzui = "T"; 49 | FactSize /= 1024.00; 50 | } 51 | return string.Format("{0} {1}", Math.Round(FactSize, 2), houzui); 52 | } -------------------------------------------------------------------------------- /src/Core/Files.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace HttpLib 4 | { 5 | /// 6 | /// 文件 7 | /// 8 | public class Files 9 | { 10 | /// 11 | /// 参数名称 12 | /// 13 | public string Name { get; set; } 14 | /// 15 | /// 文件名 16 | /// 17 | public string FileName { get; set; } 18 | /// 19 | /// 文件类型 20 | /// 21 | public string ContentType { get; set; } 22 | /// 23 | /// 文件流 24 | /// 25 | public Stream Stream { get; set; } 26 | 27 | /// 28 | /// 文件大小 29 | /// 30 | public long Size { get; private set; } 31 | 32 | /// 33 | /// 添加文件 34 | /// 35 | /// 参数名称 36 | /// 文件名称 37 | /// 文件类型 38 | /// 字节流 39 | public Files(string name, string fileName, string contentType, byte[] data) 40 | { 41 | Name = name; 42 | FileName = fileName; 43 | ContentType = contentType; 44 | Size = data.Length; 45 | Stream = new MemoryStream(data); 46 | } 47 | 48 | /// 49 | /// 添加文件 50 | /// 51 | /// 文件名称 52 | /// 文件类型 53 | /// 字节流 54 | public Files(string fileName, string contentType, byte[] data) : this("file", fileName, contentType, data) 55 | { } 56 | 57 | /// 58 | /// 添加文件 59 | /// 60 | /// 参数名称 61 | /// 文件路径 62 | public Files(string name, string fullName) 63 | { 64 | Name = name; 65 | var fileInfo = new FileInfo(fullName); 66 | FileName = fileInfo.Name; 67 | ContentType = MimeMapping.GetMimeMapping(fullName); 68 | Stream = File.OpenRead(fullName); 69 | Size = Stream.Length; 70 | } 71 | 72 | /// 73 | /// 添加文件 74 | /// 75 | /// 文件路径 76 | public Files(string fullName) : this("file", fullName) { } 77 | } 78 | } -------------------------------------------------------------------------------- /HttpLib.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32616.157 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{9F853A87-AAE2-4752-9019-E8D1C43FB10E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "example1", "examples\example1\example1.csproj", "{ECE9ECDB-4E9B-4BAE-A0AE-826F095E7272}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpLib", "src\HttpLib.csproj", "{BB60B69E-6842-4421-8068-EB59B8899B9E}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "download", "examples\download\download.csproj", "{23FDA92E-C67E-401B-9E54-E44BF1E2220D}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TDown", "examples\TDown\TDown.csproj", "{EB71932D-C4E7-47B1-9FB9-36604FE648E2}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {ECE9ECDB-4E9B-4BAE-A0AE-826F095E7272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {ECE9ECDB-4E9B-4BAE-A0AE-826F095E7272}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {ECE9ECDB-4E9B-4BAE-A0AE-826F095E7272}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {ECE9ECDB-4E9B-4BAE-A0AE-826F095E7272}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {BB60B69E-6842-4421-8068-EB59B8899B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {BB60B69E-6842-4421-8068-EB59B8899B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {BB60B69E-6842-4421-8068-EB59B8899B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BB60B69E-6842-4421-8068-EB59B8899B9E}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {23FDA92E-C67E-401B-9E54-E44BF1E2220D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {23FDA92E-C67E-401B-9E54-E44BF1E2220D}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {23FDA92E-C67E-401B-9E54-E44BF1E2220D}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {23FDA92E-C67E-401B-9E54-E44BF1E2220D}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {EB71932D-C4E7-47B1-9FB9-36604FE648E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {EB71932D-C4E7-47B1-9FB9-36604FE648E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {EB71932D-C4E7-47B1-9FB9-36604FE648E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {EB71932D-C4E7-47B1-9FB9-36604FE648E2}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {ECE9ECDB-4E9B-4BAE-A0AE-826F095E7272} = {9F853A87-AAE2-4752-9019-E8D1C43FB10E} 44 | {23FDA92E-C67E-401B-9E54-E44BF1E2220D} = {9F853A87-AAE2-4752-9019-E8D1C43FB10E} 45 | {EB71932D-C4E7-47B1-9FB9-36604FE648E2} = {9F853A87-AAE2-4752-9019-E8D1C43FB10E} 46 | EndGlobalSection 47 | GlobalSection(ExtensibilityGlobals) = postSolution 48 | SolutionGuid = {0B251537-182B-4B3E-9549-9AC4710A1C90} 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /src/HttpCore.Call.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace HttpLib 5 | { 6 | partial class HttpCore 7 | { 8 | #region 回调 9 | 10 | #region 上传进度的回调 11 | 12 | Action _requestProgres = null; 13 | Action _requestProgresMax = null; 14 | /// 15 | /// 上传进度的回调函数 16 | /// 17 | public HttpCore requestProgres(Action action) 18 | { 19 | _requestProgres = action; 20 | return this; 21 | } 22 | public HttpCore requestProgresMax(Action action) 23 | { 24 | _requestProgresMax = action; 25 | return this; 26 | } 27 | 28 | #endregion 29 | 30 | #region 下载进度的回调 31 | 32 | Action _responseProgres = null; 33 | Action _responseProgresMax = null; 34 | /// 35 | /// 下载进度的回调函数 36 | /// 37 | public HttpCore responseProgres(Action action) 38 | { 39 | _responseProgres = action; 40 | return this; 41 | } 42 | 43 | /// 44 | /// 下载进度的回调函数 45 | /// 46 | public HttpCore responseProgresMax(Action action) 47 | { 48 | _responseProgresMax = action; 49 | return this; 50 | } 51 | 52 | #endregion 53 | 54 | #endregion 55 | 56 | #region 请求 57 | 58 | Action action_Request = null; 59 | public HttpCore webrequest(Action action) 60 | { 61 | action_Request = action; 62 | return this; 63 | } 64 | 65 | Func action_before = null; 66 | /// 67 | /// 请求之前处理 68 | /// 69 | /// 请求之前处理回调 70 | /// 返回true继续 反之取消请求 71 | public HttpCore before(Func action) 72 | { 73 | action_before = action; 74 | return this; 75 | } 76 | 77 | Action action_fail = null; 78 | /// 79 | /// 接口调用失败的回调函数 80 | /// 81 | /// 错误Http响应头+错误 82 | /// 83 | public HttpCore fail(Action action) 84 | { 85 | action_fail = action; 86 | return this; 87 | } 88 | 89 | #endregion 90 | 91 | #region 终止 92 | 93 | public void abort() 94 | { 95 | if (req != null) 96 | { 97 | try 98 | { 99 | req.Abort(); 100 | req = null; 101 | } 102 | catch 103 | { } 104 | } 105 | if (response != null) 106 | { 107 | try 108 | { 109 | response.Close(); 110 | #if !NET40 111 | response.Dispose(); 112 | #endif 113 | response = null; 114 | } 115 | catch 116 | { } 117 | } 118 | } 119 | 120 | #endregion 121 | } 122 | } -------------------------------------------------------------------------------- /examples/example1/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using HttpLib; 4 | 5 | Config.fail += Config_fail;//全局异常 6 | 7 | List headerss = new List { 8 | new Val("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"), 9 | new Val("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36 Edg/86.0.622.63"), 10 | }; 11 | 12 | var ip = Http.Get("https://www.baidu.com/s").IP; 13 | Console.WriteLine("IP:baidu.com " + ip); 14 | var a = Http.Get("https://www.baidu.com/s").header(headerss) 15 | .data(new { wd = "GitHub - Haku-Men HttpLib" }) 16 | .data(new Val("ie", "utf-8")) 17 | //.header(new { userAgent = "测试1", accept = "*/*" }) 18 | .redirect(true) 19 | .before((response, result) => 20 | { 21 | return true; //继续请求 22 | }) 23 | .fail(result => 24 | { 25 | if (result.Exception == null) return; 26 | Console.Write(result.Exception.GetType()); 27 | Console.WriteLine(result.Exception.Message); 28 | }).request(out _); 29 | Console.SetCursorPosition(0, 5); 30 | Console.WriteLine(a); 31 | Console.ReadLine(); 32 | 33 | return; 34 | 35 | List headers = new List { 36 | new Val("accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"), 37 | new Val("user-agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36 Edg/86.0.622.63"), 38 | }; 39 | var a2 = Http.Post("http://localhost:61489/api/values").header(headers) 40 | .data(new { wd = new string[] { "GitHub - Haku-Men HttpLib", "321231" } }) 41 | .data(new List { new Files(@"C:\Users\ttgx\Desktop\Flowing.exe") }) 42 | .data(new List { new Files(@"C:\Users\ttgx\Desktop\ABT44B9D51AC6E96DA9220E39EED30D1945666375E8401AD7DE974193FD48211E72.jfif") }) 43 | .data(new Val("abc", "123")) 44 | .data(new Val("abc1", "1234")) 45 | .requestProgres((bytesSent, totalBytes) => 46 | { 47 | double prog = (bytesSent * 1.0) / (totalBytes * 1.0); 48 | 49 | int top = 0; 50 | Console.SetCursorPosition(0, top); 51 | Console.Write("{0}% 上传 {1}/{2} ", Math.Round(prog * 100.0, 1).ToString("N1"), CountSize(bytesSent), CountSize(totalBytes)); 52 | 53 | }) 54 | .responseProgres((bytesSent, totalBytes) => 55 | { 56 | int top = 2; 57 | Console.SetCursorPosition(0, top); 58 | if (totalBytes > 0) 59 | { 60 | double prog = (bytesSent * 1.0) / (totalBytes * 1.0); 61 | Console.Write("{0}% 下载 {1}/{2} ", Math.Round(prog * 100.0, 1).ToString("N1"), CountSize(bytesSent), CountSize(totalBytes)); 62 | } 63 | else 64 | { 65 | Console.Write("{0} 下载 ", CountSize(bytesSent)); 66 | } 67 | }).fail(result => 68 | { 69 | }).request(out _); 70 | Console.SetCursorPosition(0, 5); 71 | Console.WriteLine(a2); 72 | Console.ReadLine(); 73 | 74 | static void Config_fail(HttpCore core, ResultResponse result) 75 | { 76 | if (result.Exception == null) return; 77 | Console.Write(result.Exception.GetType()); 78 | Console.Write(result.Exception.Message); 79 | } 80 | static string CountSize(double Size) 81 | { 82 | string houzui = "B"; 83 | double FactSize = Size; 84 | if (FactSize >= 1024) 85 | { 86 | houzui = "K"; 87 | FactSize = (FactSize / 1024.00); 88 | } 89 | if (FactSize >= 1024) 90 | { 91 | houzui = "M"; 92 | FactSize = (FactSize / 1024.00); 93 | } 94 | if (FactSize >= 1024) 95 | { 96 | houzui = "G"; 97 | FactSize = (FactSize / 1024.00); 98 | } 99 | if (FactSize >= 1024) 100 | { 101 | houzui = "T"; 102 | FactSize = (FactSize / 1024.00); 103 | } 104 | return string.Format("{0} {1}", Math.Round(FactSize, 2), houzui); 105 | } -------------------------------------------------------------------------------- /src/Core/HttpOption.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Text; 5 | 6 | namespace HttpLib 7 | { 8 | public class HttpOption 9 | { 10 | public HttpOption(string url) { uri = new Uri(url); } 11 | public HttpOption(string url, HttpMethod _method) 12 | { 13 | uri = new Uri(url); 14 | method = _method; 15 | } 16 | public HttpOption(Uri _uri, HttpMethod _method) 17 | { 18 | uri = _uri; 19 | method = _method; 20 | } 21 | 22 | public Uri uri { get; } 23 | public HttpMethod method { get; set; } = HttpMethod.Get; 24 | 25 | #region 参数 26 | 27 | /// 28 | /// Get参数 29 | /// 30 | public List? query { get; set; } 31 | /// 32 | /// 参数 33 | /// 34 | public List? data { get; set; } 35 | /// 36 | /// body参数 37 | /// 38 | public string? datastr { get; set; } 39 | 40 | /// 41 | /// 上传文件 42 | /// 43 | public List? file { get; set; } 44 | 45 | #endregion 46 | 47 | #region 头 48 | 49 | /// 50 | /// 头 51 | /// 52 | public List? header { get; set; } 53 | 54 | #endregion 55 | 56 | /// 57 | /// 代理 58 | /// 59 | public IWebProxy? proxy { get; set; } 60 | 61 | /// 62 | /// 编码 63 | /// 64 | public Encoding? encoding { get; set; } 65 | 66 | /// 67 | /// 自动编码 68 | /// 69 | public bool autoencode { get; set; } 70 | 71 | /// 72 | /// 请求重定向 73 | /// 74 | public bool redirect { get; set; } 75 | 76 | /// 77 | /// 请求超时时长 78 | /// 79 | public int timeout { get; set; } 80 | 81 | /// 82 | /// 获取域名IP 83 | /// 84 | public string? IP 85 | { 86 | get 87 | { 88 | try 89 | { 90 | if (IPAddress.TryParse(uri.Host, out IPAddress? ip)) return ip.ToString(); 91 | else 92 | { 93 | var hostEntry = Dns.GetHostEntry(uri.Host); 94 | var ipEndPoint = new IPEndPoint(hostEntry.AddressList[0], 0); 95 | string _ip = ipEndPoint.Address.ToString(); 96 | if (_ip.StartsWith("::")) return "127.0.0.1"; 97 | else return _ip; 98 | } 99 | } 100 | catch { } 101 | return null; 102 | } 103 | } 104 | 105 | /// 106 | /// 请求URL 107 | /// 108 | public Uri Url 109 | { 110 | get 111 | { 112 | #region 合并参数 113 | 114 | var data = new List(); 115 | if (query != null && query.Count > 0) foreach (var it in query) data.Add(it.ToStringEscape()); 116 | 117 | if (method == HttpMethod.Get && this.data != null && this.data.Count > 0) foreach (var it in this.data) data.Add(it.ToStringEscape()); 118 | 119 | #endregion 120 | 121 | if (data.Count > 0) 122 | { 123 | if (string.IsNullOrEmpty(uri.Query)) return new Uri(uri.AbsoluteUri + "?" + string.Join("&", data)); 124 | else return new Uri(uri.AbsoluteUri + "&" + string.Join("&", data)); 125 | } 126 | 127 | return uri; 128 | } 129 | } 130 | 131 | public string FileName(ResultResponse _web) 132 | { 133 | if (_web.Header.ContainsKey("Content-Disposition")) 134 | { 135 | string val = _web.Header["Content-Disposition"]; 136 | if (!string.IsNullOrEmpty(val)) return val.FileNameDisposition() ?? uri.FileName(); 137 | } 138 | return uri.FileName(); 139 | } 140 | 141 | public override string ToString() => uri.AbsoluteUri; 142 | } 143 | } -------------------------------------------------------------------------------- /src/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | 5 | namespace HttpLib 6 | { 7 | /// 8 | /// 全局配置 9 | /// 10 | public class Config 11 | { 12 | public static string? CacheFolder = null; 13 | 14 | #region 请求头 15 | 16 | public static List? headers = null; 17 | 18 | /// 19 | /// 请求头 20 | /// 21 | /// 多个参数 22 | public static void header(params Val[] vals) 23 | { 24 | headers ??= new List(vals.Length); 25 | headers.AddRange(vals); 26 | } 27 | 28 | /// 29 | /// 请求头 30 | /// 31 | /// 多个参数 32 | public static void header(IList vals) 33 | { 34 | headers ??= new List(vals.Count); 35 | headers.AddRange(vals); 36 | } 37 | 38 | /// 39 | /// 请求头 40 | /// 41 | /// 键 42 | /// 值 43 | public static void header(string key, string? val) 44 | { 45 | if (headers == null) headers = new List { new Val(key, val) }; 46 | else headers.Add(new Val(key, val)); 47 | } 48 | 49 | /// 50 | /// 请求头 51 | /// 52 | /// 多个参数 53 | public static void header(IDictionary vals) 54 | { 55 | headers ??= new List(vals.Count); 56 | foreach (var it in vals) headers.Add(new Val(it.Key, it.Value)); 57 | } 58 | 59 | #endregion 60 | 61 | /// 62 | /// 用户标识 63 | /// 64 | public static string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"; 65 | 66 | /// 67 | /// 表示文件压缩和解压缩编码格式,该格式将用来压缩在 System.Net.HttpWebRequest 的响应中收到的数据 68 | /// 69 | public static DecompressionMethods DecompressionMethod = DecompressionMethods.GZip; 70 | 71 | /// 72 | /// 全局是否自动重定向 73 | /// 74 | public static bool Redirect = false; 75 | 76 | #region 全局错误 77 | 78 | public delegate void ErrEventHandler(HttpCore core, ResultResponse result); 79 | 80 | /// 81 | /// 接口调用失败的回调函数(带响应头) 82 | /// 83 | public static event ErrEventHandler? fail; 84 | 85 | public static void OnFail(HttpCore core, ResultResponse result) => fail?.Invoke(core, result); 86 | 87 | #endregion 88 | 89 | #region 代理 90 | 91 | public static IWebProxy? _proxy = null; 92 | 93 | /// 94 | /// 全局代理 95 | /// 96 | /// 代理服务器的 URI 97 | public static void proxy(string address) => _proxy = new WebProxy(address); 98 | /// 99 | /// 全局代理 100 | /// 101 | /// 代理服务器的 URI 102 | public static void proxy(Uri address) => _proxy = new WebProxy(address); 103 | 104 | /// 105 | /// 全局代理 106 | /// 107 | /// 代理主机的名称 108 | /// 要使用的 Host 上的端口号 109 | public static void proxy(string host, int port) => _proxy = new WebProxy(host, port); 110 | 111 | /// 112 | /// 全局代理 113 | /// 114 | /// 代理主机的名称 115 | /// 要使用的 Host 上的端口号 116 | /// 用户名 117 | /// 密码 118 | public static void proxy(string host, int port, string username, string password) 119 | { 120 | _proxy = new WebProxy(host, port); 121 | if (!string.IsNullOrEmpty(username)) _proxy.Credentials = new NetworkCredential(username, password); 122 | } 123 | 124 | #endregion 125 | 126 | public static int CacheSize = 4096; 127 | 128 | /// 129 | /// 重试次数 130 | /// 131 | public static int RetryCount = 3; 132 | /// 133 | /// 超时时长 134 | /// 135 | public static int TimeOut = 10000; 136 | } 137 | } -------------------------------------------------------------------------------- /src/Core/Api.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net.Mime; 5 | using System.Threading; 6 | 7 | namespace HttpLib 8 | { 9 | internal static class Api 10 | { 11 | #region 文件处理 12 | 13 | public static bool DeleteFile(this string dir) 14 | { 15 | if (File.Exists(dir)) 16 | { 17 | try 18 | { 19 | File.Delete(dir); 20 | } 21 | catch { return false; } 22 | } 23 | return true; 24 | } 25 | 26 | #region 删除文件夹 27 | 28 | /// 29 | /// 删除文件夹以及文件夹内所有文件 30 | /// 31 | public static bool DeleteDirectory(this string dir) 32 | { 33 | if (Directory.Exists(dir)) 34 | { 35 | try 36 | { 37 | DeleteDirectory(new DirectoryInfo(dir)); 38 | Directory.Delete(dir, true); 39 | } 40 | catch { return false; } 41 | } 42 | return true; 43 | } 44 | 45 | /// 46 | /// 清除文件夹内容,但是保留文件夹 47 | /// 48 | public static bool ClearDirectory(this string dir) 49 | { 50 | if (Directory.Exists(dir)) 51 | { 52 | try 53 | { 54 | DeleteDirectory(new DirectoryInfo(dir)); 55 | } 56 | catch { return false; } 57 | } 58 | else Directory.CreateDirectory(dir); 59 | return true; 60 | } 61 | public static void CreateDirectory(this string dir) 62 | { 63 | if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); 64 | } 65 | static void DeleteDirectory(DirectoryInfo dir) 66 | { 67 | if (dir.Exists) 68 | { 69 | foreach (FileInfo item in dir.GetFiles()) 70 | { 71 | try 72 | { 73 | item.Delete(); 74 | } 75 | catch { } 76 | } 77 | foreach (DirectoryInfo item in dir.GetDirectories()) DeleteDirectory(item); 78 | } 79 | } 80 | 81 | #endregion 82 | 83 | #endregion 84 | 85 | public static string RandomString(this int length) 86 | { 87 | string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"; 88 | char[] chars = new char[length]; 89 | var rd = new Random(); 90 | for (int i = 0; i < length; i++) chars[i] = allowedChars[rd.Next(0, allowedChars.Length)]; 91 | return new string(chars); 92 | } 93 | 94 | public static string? FileNameDisposition(this string disposition) 95 | { 96 | if (disposition.Contains("filename*=UTF-8")) 97 | { 98 | try 99 | { 100 | return Uri.UnescapeDataString(disposition.Substring(disposition.IndexOf("filename*=UTF-8") + 15).Trim('\'')); 101 | } 102 | catch { } 103 | } 104 | return new ContentDisposition(disposition).FileName; 105 | } 106 | public static string FileName(this Uri uri) 107 | { 108 | if (uri.Query.Length > 0) return Path.GetFileName(uri.AbsoluteUri.Substring(0, uri.AbsoluteUri.Length - uri.Query.Length)); 109 | return Path.GetFileName(uri.AbsoluteUri); 110 | } 111 | 112 | public static string FileName(this Uri uri, string? disposition) 113 | { 114 | if (disposition != null && !string.IsNullOrEmpty(disposition)) return disposition.FileNameDisposition() ?? uri.FileName(); 115 | return uri.FileName(); 116 | } 117 | 118 | /// 119 | /// 文件合并函数, 120 | /// 可将任意个子文件合并为一个,为fileSplit()的逆过程 121 | /// delet标识是否删除原文件, change对data的首字节进行解密 122 | /// 123 | public static string CombineMultipleFilesIntoSingleFile(this List files, string filePath, string WorkPath) 124 | { 125 | if (files.Count == 1) 126 | { 127 | if (File.Exists(filePath)) File.Delete(filePath); 128 | File.Move(files[0], filePath); 129 | //删除临时文件夹 130 | WorkPath.DeleteDirectory(); 131 | return filePath; 132 | } 133 | using (var MergeFile = new FileStream(filePath, FileMode.Create)) 134 | { 135 | using (var AddWriter = new BinaryWriter(MergeFile)) 136 | { 137 | //按序号排序 138 | int i = 0; 139 | foreach (string file in files) 140 | { 141 | i++; 142 | using (var fs = new FileStream(file, FileMode.Open)) 143 | { 144 | using (var tmp = new BinaryReader(fs)) 145 | { 146 | if (i == files.Count) AddWriter.Write(tmp.ReadBytes((int)fs.Length)); 147 | else AddWriter.Write(tmp.ReadBytes((int)fs.Length - 1)); 148 | } 149 | } 150 | } 151 | } 152 | //删除临时文件夹 153 | WorkPath.DeleteDirectory(); 154 | } 155 | return filePath; 156 | } 157 | 158 | public static bool Wait(this WaitHandle handle) 159 | { 160 | try 161 | { 162 | handle.WaitOne(); 163 | return false; 164 | } 165 | catch 166 | { 167 | return true; 168 | } 169 | } 170 | public static bool Wait(this CancellationTokenSource? token) 171 | { 172 | try 173 | { 174 | if (token == null || token.IsCancellationRequested) return true; 175 | return false; 176 | } 177 | catch 178 | { 179 | return true; 180 | } 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HttpLib 便捷的Http库 | 多线程下载库 2 | 3 | 如果你喜欢 HttpLib 项目,请为本项点亮一颗星 ⭐! 4 | 5 | 6 | [![NuGet](https://img.shields.io/nuget/vpre/tom.httpLib?style=flat-square&logo=nuget&label=HttpLib)](https://www.nuget.org/packages/Tom.HttpLib) 7 | [![Download](https://img.shields.io/nuget/dt/Tom.HttpLib?style=flat-square)](https://www.nuget.org/packages/Tom.HttpLib) 8 | 9 | ## 🖥支持环境 10 | - .NET 6.0及以上。 11 | - .NET Core3.1及以上。 12 | - .NET Standard2.0及以上。 13 | 14 | ## 🌴支持 15 | 16 | #### multipart/form-data 17 | 18 | 既可以上传文件等二进制数据,也可以上传表单键值对 19 | 20 | #### 上传与下载进度回调 21 | 22 | 上传与下载的进度监控 23 | 24 | #### 支持缓存 25 | 26 | 类似图片加载场景,同一个id的图片通过磁盘存储减少网络开支 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 | * [实例获取域名IP](#实例获取域名IP) 53 | * [实例全局错误捕获](#实例全局错误捕获) 54 | * [ResultResponse介绍](#ResultResponse介绍) 55 | 56 | 57 | # 示例 58 | ## 创建请求 59 | ``` csharp 60 | Http.Get("https://www.baidu.com") 61 | ``` 62 | ``` csharp 63 | Http.Post("https://www.baidu.com") 64 | ``` 65 | ``` csharp 66 | Http.Put("https://www.baidu.com") 67 | ``` 68 | ``` csharp 69 | Http.Delete("https://www.baidu.com") 70 | ``` 71 | ### 添加参数 72 | 73 | > GET请求参数会自动注入到地址 74 | 75 | ``` csharp 76 | data("wd", "随便搜一下") 77 | data(new { test1 = "测试1", test2 = "测试2" }) 78 | data(new { params_ = "关键字参数" }) 79 | data(new { wd = new string[] { "GitHub - Haku-Men HttpLib", "POST数组参数" } }) 80 | ``` 81 | 82 | > URL参数(除了GET请求) 83 | 84 | ``` csharp 85 | query("键", "值对") 86 | query(new { test = "POST下继续传递URL参数" }) 87 | ``` 88 | 89 | ``` csharp 90 | data(new Val("test1", "测试1"), new Val("test2", "测试2")) 91 | ``` 92 | ``` csharp 93 | data(new List { 94 | new Val("test1","测试1"), 95 | new Val("test2","测试2") 96 | }) 97 | ``` 98 | 99 | > 上传字符串 默认 `text/plain` 100 | 101 | ``` csharp 102 | string json = "{\"JSON\":\"json data\"}"; 103 | datastr(json, "application/json") 104 | ``` 105 | 106 | > 上传文件 107 | 108 | ``` csharp 109 | data(new Files("文件地址")) 110 | ``` 111 | ``` csharp 112 | file(@"文件地址") 113 | ``` 114 | 115 | ### 添加请求头 116 | ``` csharp 117 | header("Authorization", "abc") 118 | ``` 119 | ``` csharp 120 | header(new { accept = "*/*", userAgent = "Chrome" }) 121 | ``` 122 | ``` csharp 123 | header(new Val("accept","*/*"), new Val("user-agent","Chrome")) 124 | ``` 125 | 126 | ### 设置代理 127 | ``` csharp 128 | proxy("127.0.0.1",1000) 129 | ``` 130 | 131 | ### 启用重定向 132 | >默认禁止 133 | ``` csharp 134 | redirect() 135 | ``` 136 | 137 | ### 设置超时时长 138 | >`毫秒`(默认不超时) 139 | ``` csharp 140 | timeout(3000) 141 | ``` 142 | 143 | ### 设置编码 144 | >默认`utf-8` 145 | ``` csharp 146 | encoding('utf-8') 147 | ``` 148 | 149 | ## 设置缓存 150 | >先配置`Config.CacheFolder`缓存文件夹 151 | ``` csharp 152 | cache("缓存id") 153 | ``` 154 | >或者设定有效期 1分钟 155 | ``` csharp 156 | cache("缓存id",1) 157 | ``` 158 | 159 | ### 请求之前处理 160 | ``` csharp 161 | before((HttpWebResponse response, ResultResponse result) => 162 | { 163 | return true; //继续请求 164 | }) 165 | ``` 166 | 167 | ### 注入回调获取进度 168 | >字节大小 169 | #### 上传 170 | ``` csharp 171 | requestProgres((bytesSent, totalBytes) => { 172 | double prog = (bytesSent * 1.0) / (totalBytes * 1.0); 173 | Console.Write("{0}% 上传", Math.Round(prog * 100.0, 1).ToString("N1")); 174 | }) 175 | ``` 176 | #### 下载 177 | ``` csharp 178 | responseProgres((bytesSent, totalBytes) => { 179 | if (totalBytes > 0) 180 | { 181 | double prog = (bytesSent * 1.0) / (totalBytes * 1.0); 182 | Console.Write("{0}% 下载", Math.Round(prog * 100.0, 1).ToString("N1")); 183 | } 184 | }) 185 | ``` 186 | 187 | ## 请求 188 | ### 异步错误 189 | ``` csharp 190 | fail((ResultResponse result) => { 191 | }) 192 | ``` 193 | ### 同步获取 194 | ``` csharp 195 | requestNone();//不下载流 196 | request();//返回字符串 197 | requestData();//返回字节 198 | download("保存目录", "保存文件名称(为空自动获取)");//下载文件 199 | ``` 200 | 201 | # 实例 202 | 203 | ``` csharp 204 | string result = Http.Get("https://www.baidu.com/s") 205 | .data(new { wd = "GitHub - Haku-Men HttpLib", params_ = "关键字参数" }) 206 | .redirect() 207 | .request(); 208 | Console.Write(result); 209 | ``` 210 | 211 | # 实例下载文件 212 | ``` csharp 213 | var savapath = Http.Get("https://dldir1.qq.com/qqfile/qq/QQNT/Windows/QQ_9.9.9_240422_x64_01.exe") 214 | .redirect() 215 | .responseProgres((bytesSent, totalBytes) => 216 | { 217 | Console.SetCursorPosition(0, 0); 218 | if (totalBytes > 0) 219 | { 220 | double prog = (bytesSent * 1.0) / (totalBytes * 1.0); 221 | Console.Write("{0}% 下载 {1}/{2} ", Math.Round(prog * 100.0, 1).ToString("N1"), CountSize(bytesSent), CountSize(totalBytes)); 222 | } 223 | else 224 | { 225 | Console.Write("{0} 下载 ", CountSize(bytesSent)); 226 | } 227 | }).download(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "qq.exe"); 228 | if (savapath != null) Console.WriteLine("下载成功保存至:" + savapath); 229 | else Console.WriteLine("下载失败"); 230 | ``` 231 | 232 | # 实例流式传输 233 | ``` csharp 234 | Http.Get("https://test.chatgpt.com/local_doc_chat").request(msg => 235 | { 236 | if (msg == null) return; 237 | if (msg.StartsWith("data: ")) msg = msg.Substring(5).Trim(); 238 | System.Diagnostics.Debug.WriteLine(msg); 239 | }); 240 | ``` 241 | 242 | # 实例获取域名IP 243 | ``` csharp 244 | Http.Get("https://www.baidu.com").IP 245 | ``` 246 | 247 | # 实例全局错误捕获 248 | ``` csharp 249 | Config.fail += (HttpCore core, ResultResponse result)=> 250 | { 251 | if (result.Exception == null) return; 252 | Console.Write(err.GetType()); 253 | Console.Write(err.Message); 254 | }; 255 | ``` 256 | 257 | # ResultResponse介绍 258 | 259 | |代码|类型|解释|说明| 260 | |:--|:--|:--:|:--| 261 | |StatusCode|int|状态代码|`200` 为正常 常见的有`404`未找到、`302`重定向、`502`网址报错| 262 | |IsSuccessStatusCode|bool|响应是否成功|range `200`-`299`| 263 | |ServerHeader|string`?`|服务头|HTTP 200 OK BWS/1.1 Ver:1.1| 264 | |Uri|Uri|最终的地址|| 265 | |Type|string`?`|服务指示类型|`Content-Type`| 266 | |Header|Dictionary|响应头|| 267 | |Cookie|Dictionary|Cookie|| 268 | |OriginalSize|long|流原始大小|动态压缩| 269 | |Size|long|流大小|| 270 | |Exception|Exception`?`|异常信息|| -------------------------------------------------------------------------------- /examples/TDown/Form1.cs: -------------------------------------------------------------------------------- 1 | using HttpLib; 2 | using System; 3 | using System.Text; 4 | using System.Windows.Forms; 5 | 6 | namespace TDown 7 | { 8 | public partial class Form1 : AntdUI.BaseForm 9 | { 10 | public Form1() 11 | { 12 | InitializeComponent(); 13 | } 14 | HttpDown down; 15 | private void btn_Click(object sender, EventArgs e) 16 | { 17 | progress.Value = 0; 18 | txt_state.State = AntdUI.TState.Default; 19 | txt_state.Text = "待下载"; 20 | txt_speed.Text = txt_time.Text = txt_max.Text = txt_value.Text = txt_start_time.Text = txt_end_time.Text = null; 21 | int maxThreads = Environment.ProcessorCount; 22 | string downUrl = txt_uri.Text; 23 | if (int.TryParse(textBox2.Text, out int taskCount)) 24 | { 25 | progress.Loading = true; 26 | DateTime start_time = DateTime.Now; 27 | txt_start_time.Text = start_time.ToString("HH:mm:ss"); 28 | down = Http.Get(downUrl).redirect().downLoad(Program.BasePath, MD5Encrypt16(downUrl)); 29 | btn.Enabled = btn_resume.Enabled = false; 30 | btn_suspend.Enabled = btn_stop.Enabled = true; 31 | down.ValueChange(t => 32 | { 33 | double prog = (t * 1.0) / down.MaxValue; 34 | if (down.MaxValue > t) 35 | { 36 | progress.Value = (float)prog; 37 | txt_prog.Text = Math.Round(prog * 100.0, 1) + "%"; 38 | } 39 | txt_value.Text = ByteUnit(t); 40 | }); 41 | down.MaxValueChange(t => 42 | { 43 | if (t > 0) 44 | { 45 | txt_prog.Text = Math.Round(down.Value / t * 100.0, 1) + "%"; 46 | txt_max.Text = ByteUnit(t); 47 | } 48 | else 49 | { 50 | txt_prog.Text = "∞"; 51 | txt_max.Text = "未知"; 52 | } 53 | }); 54 | down.StateChange((t, err) => 55 | { 56 | switch (t) 57 | { 58 | case DownState.Complete: 59 | txt_state.State = AntdUI.TState.Success; 60 | txt_state.Text = "完成 " + err; 61 | break; 62 | case DownState.Downloading: 63 | txt_state.State = AntdUI.TState.Processing; 64 | txt_state.Text = "下载中"; 65 | break; 66 | case DownState.Fail: 67 | txt_state.State = AntdUI.TState.Error; 68 | txt_state.Text = "异常"; 69 | break; 70 | case DownState.Stop: 71 | txt_state.State = AntdUI.TState.Warn; 72 | txt_state.Text = "已停止 " + err; 73 | break; 74 | } 75 | }); 76 | down.TimeChange(t => 77 | { 78 | txt_time.Text = t; 79 | }); 80 | down.SpeedChange(t => 81 | { 82 | txt_start_time.Text = start_time.ToString("HH:mm:ss") + " | 耗时 " + Math.Round((DateTime.Now - start_time).TotalSeconds) + "秒"; 83 | txt_speed.Text = ByteUnit(t); 84 | }); 85 | down.Go(taskCount).ContinueWith((action => 86 | { 87 | progress.Loading = false; 88 | DateTime end_time = DateTime.Now; 89 | txt_start_time.Text = start_time.ToString("HH:mm:ss"); 90 | txt_end_time.Text = end_time.ToString("HH:mm:ss") + " | 耗时 " + Math.Round((end_time - start_time).TotalSeconds) + "秒"; 91 | Invoke(new Action(() => 92 | { 93 | btn_stop.Enabled = btn_suspend.Enabled = btn_resume.Enabled = false; 94 | btn.Enabled = true; 95 | })); 96 | System.Diagnostics.Debug.WriteLine("保存至:" + action.Result); 97 | if (action.Result != null) 98 | MessageBox.Show("保存至:" + action.Result); 99 | })); 100 | } 101 | } 102 | 103 | private void btn_suspend_Click(object sender, EventArgs e) 104 | { 105 | btn_suspend.Enabled = false; 106 | btn_resume.Enabled = true; 107 | down.Suspend(); 108 | } 109 | 110 | private void btn_resume_Click(object sender, EventArgs e) 111 | { 112 | btn_resume.Enabled = false; 113 | btn_suspend.Enabled = true; 114 | down.Resume(); 115 | } 116 | 117 | private void btn_stop_Click(object sender, EventArgs e) 118 | { 119 | down.Dispose(); 120 | btn_stop.Enabled = btn_suspend.Enabled = btn_resume.Enabled = false; 121 | btn.Enabled = true; 122 | } 123 | 124 | #region 转换 125 | 126 | public static string ByteUnit(long val, int d = 1, string nul = "0B") 127 | { 128 | return ByteUnit(val * 1.0, d, nul); 129 | } 130 | public static string ByteUnit(double val, int d = 1, string nul = "0B") 131 | { 132 | if (val == 0) return nul; 133 | var _val = val; 134 | int unit = 0; 135 | while (_val > 1024) 136 | { 137 | _val /= 1024; 138 | unit++; 139 | if (unit > 5) 140 | { 141 | break; 142 | } 143 | } 144 | return Math.Round(_val, d) + CountSizeUnit(unit); 145 | } 146 | 147 | static string CountSizeUnit(int val) 148 | { 149 | switch (val) 150 | { 151 | case 4: return "T"; 152 | case 3: return "G"; 153 | case 2: return "M"; 154 | case 1: return "K"; 155 | case 5: return "P"; 156 | case 6: return "E"; 157 | //case 7: return "Z"; 158 | //case 8: return "Y"; 159 | default: return "B"; 160 | } 161 | } 162 | 163 | #endregion 164 | 165 | public static string MD5Encrypt16(string str) 166 | { 167 | using (var md5 = System.Security.Cryptography.MD5.Create()) 168 | { 169 | return BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(str)), 4, 8).Replace("-", "").ToUpper(); 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/HttpDown/HttpDown.Option.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading; 5 | 6 | namespace HttpLib 7 | { 8 | partial class HttpDown 9 | { 10 | #region 参数 11 | 12 | /// 13 | /// 自定义ID 14 | /// 15 | public string? ID { get; set; } 16 | 17 | /// 18 | /// 下载地址 19 | /// 20 | public string Url { get; } 21 | 22 | /// 23 | /// 保存路径 24 | /// 25 | public string SavePath { get; } 26 | 27 | /// 28 | /// 超时时长 29 | /// 30 | public int TimeOut = Config.TimeOut; 31 | 32 | /// 33 | /// 重试次数 34 | /// 35 | public int RetryCount = Config.RetryCount; 36 | 37 | /// 38 | /// 下载缓存大小 39 | /// 40 | public int CacheSize = Config.CacheSize; 41 | 42 | /// 43 | /// 下载速度 44 | /// 45 | public double Speed { get => _Speed; } 46 | 47 | /// 48 | /// 剩余时间 49 | /// 50 | public string? Time { get => _Time; } 51 | 52 | /// 53 | /// 当前下载值 54 | /// 55 | public long Value { get => _Value; } 56 | 57 | /// 58 | /// 文件大小 59 | /// 60 | public long MaxValue { get => _MaxValue; } 61 | 62 | /// 63 | /// 任务状态 64 | /// 65 | public DownState State { get => _State; } 66 | 67 | 68 | #region 非公开 69 | 70 | DownState _State = DownState.Ready; 71 | long _MaxValue, _Value, _Speed; 72 | string? _Time; 73 | 74 | long[] ValTmp = new long[0]; 75 | long[] MaxTmp = new long[0]; 76 | internal ManualResetEvent resetState = new ManualResetEvent(true); 77 | 78 | #endregion 79 | 80 | #endregion 81 | 82 | #region 任务事件 83 | 84 | public delegate void StateHandler(DownState e, string err); 85 | 86 | #region 文件大小 87 | 88 | int DownCount = 0, TotalCount = 0; 89 | 90 | Action? _MaxValueChange; 91 | /// 92 | /// 文件大小改变 93 | /// 94 | public HttpDown MaxValueChange(Action action) 95 | { 96 | _MaxValueChange = action; 97 | return this; 98 | } 99 | 100 | void SetMaxValue(long value) 101 | { 102 | if (_MaxValue == value) return; 103 | _MaxValue = value; 104 | _MaxValueChange?.Invoke(value); 105 | } 106 | 107 | void SetMaxValue() 108 | { 109 | var val = MaxTmp.Sum(); 110 | if (TotalCount > 0) SetMaxValue(val * TotalCount / DownCount); 111 | else SetMaxValue(val); 112 | } 113 | 114 | internal void SetMaxValue(int i, long value) 115 | { 116 | if (MaxTmp[i] == value) return; 117 | MaxTmp[i] = value; 118 | var val = MaxTmp.Sum(); 119 | if (TotalCount > 0) SetMaxValue(val * TotalCount / DownCount); 120 | else SetMaxValue(val); 121 | } 122 | 123 | #endregion 124 | 125 | #region 当前下载值 126 | 127 | Action? _ValueChange; 128 | /// 129 | /// 当前下载值改变 130 | /// 131 | public HttpDown ValueChange(Action action) 132 | { 133 | _ValueChange = action; 134 | return this; 135 | } 136 | 137 | void SetValue(long value) 138 | { 139 | if (_Value == value) return; 140 | _Value = value; 141 | _ValueChange?.Invoke(value); 142 | } 143 | 144 | internal void SetValue(int i, long value) 145 | { 146 | ValTmp[i] = value; 147 | SetValue(ValTmp.Sum()); 148 | } 149 | 150 | #endregion 151 | 152 | #region 下载速度 153 | 154 | Action? _SpeedChange; 155 | /// 156 | /// 下载速度改变 157 | /// 158 | public HttpDown SpeedChange(Action action) 159 | { 160 | _SpeedChange = action; 161 | return this; 162 | } 163 | 164 | internal bool CanSpeed = true; 165 | internal void SetSpeed(long value) 166 | { 167 | if (!CanSpeed) return; 168 | if (_Speed == value) return; 169 | _Speed = value; 170 | _SpeedChange?.Invoke(value); 171 | } 172 | 173 | #endregion 174 | 175 | #region 剩余时间 176 | 177 | Action? _TimeChange; 178 | /// 179 | /// 剩余时间改变 180 | /// 181 | public HttpDown TimeChange(Action action) 182 | { 183 | _TimeChange = action; 184 | return this; 185 | } 186 | 187 | internal void SetTime(string? value) 188 | { 189 | if (!CanSpeed) return; 190 | if (_Time == value) return; 191 | _Time = value; 192 | _TimeChange?.Invoke(value); 193 | } 194 | 195 | #endregion 196 | 197 | #region 任务状态 198 | 199 | Action? _StateChange; 200 | /// 201 | /// 下载状态改变 202 | /// 203 | public HttpDown StateChange(Action action) 204 | { 205 | _StateChange = action; 206 | return this; 207 | } 208 | 209 | internal void SetState(DownState value, string? err = null) 210 | { 211 | if (_State == value) return; 212 | _State = value; 213 | _StateChange?.Invoke(value, err); 214 | } 215 | 216 | #endregion 217 | 218 | #endregion 219 | 220 | #region 初始化 221 | 222 | public Uri Uri; 223 | public HttpCore core; 224 | public HttpDown(HttpCore _core, string _savePath, string? id = null) 225 | { 226 | core = _core; 227 | Uri = core.option.uri; 228 | ID = id; 229 | SavePath = _savePath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; 230 | Url = core.option.uri.AbsoluteUri; 231 | } 232 | 233 | #endregion 234 | } 235 | 236 | public enum DownState 237 | { 238 | /// 239 | /// 准备中 240 | /// 241 | Ready, 242 | /// 243 | /// 下载中 244 | /// 245 | Downloading, 246 | /// 247 | /// 已停止 248 | /// 249 | Stop, 250 | /// 251 | /// 完成 252 | /// 253 | Complete, 254 | /// 255 | /// 异常 256 | /// 257 | Fail 258 | } 259 | } -------------------------------------------------------------------------------- /src/Core/Http.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | 5 | namespace HttpLib 6 | { 7 | /// 8 | /// 快捷的HTTP请求 9 | /// 10 | public static class Http 11 | { 12 | #region SSL 13 | 14 | static Http() 15 | { 16 | #if NET40 17 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls; 18 | #elif NET45 || NET46 || NETSTANDARD2_0 || NETSTANDARD2_1 19 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; 20 | #elif NET48 21 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; 22 | #else 23 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13; 24 | #endif 25 | } 26 | 27 | #endregion 28 | 29 | /// 30 | /// GET 请求 31 | /// 32 | /// 地址 33 | public static HttpCore Get(this string url) => Core(url, HttpMethod.Get); 34 | 35 | /// 36 | /// GET 请求 37 | /// 38 | /// 地址 39 | public static HttpCore Get(this Uri url) => Core(url, HttpMethod.Get); 40 | 41 | /// 42 | /// POST 请求 43 | /// 44 | /// 地址 45 | public static HttpCore Post(this string url) => Core(url, HttpMethod.Post); 46 | 47 | /// 48 | /// POST 请求 49 | /// 50 | /// 地址 51 | public static HttpCore Post(this Uri url) => Core(url, HttpMethod.Post); 52 | 53 | /// 54 | /// Put 请求 55 | /// 56 | /// 地址 57 | public static HttpCore Put(this string url) => Core(url, HttpMethod.Put); 58 | 59 | /// 60 | /// Put 请求 61 | /// 62 | /// 地址 63 | public static HttpCore Put(this Uri url) => Core(url, HttpMethod.Put); 64 | 65 | /// 66 | /// Delete 请求 67 | /// 68 | /// 地址 69 | public static HttpCore Delete(this string url) => Core(url, HttpMethod.Delete); 70 | 71 | /// 72 | /// Delete 请求 73 | /// 74 | /// 地址 75 | public static HttpCore Delete(this Uri url) => Core(url, HttpMethod.Delete); 76 | 77 | public static HttpCore Core(this string url, HttpMethod method) => new HttpCore(url, method); 78 | public static HttpCore Core(this Uri url, HttpMethod method) => new HttpCore(url, method); 79 | 80 | #region 缓存 81 | 82 | public static string? Cache(this string ID, int time = 0) 83 | { 84 | var file = Config.CacheFolder + ID; 85 | if (System.IO.File.Exists(Config.CacheFolder + ID)) 86 | { 87 | if (time > 0) 88 | { 89 | var t = System.IO.File.GetCreationTime(file); 90 | var elapsedTicks = DateTime.Now.Ticks - t.Ticks; 91 | var elapsedSpan = new TimeSpan(elapsedTicks); 92 | if (elapsedSpan.TotalMinutes < time) return System.IO.File.ReadAllText(file); 93 | } 94 | else return System.IO.File.ReadAllText(file); 95 | } 96 | return null; 97 | } 98 | public static byte[]? CacheData(this string ID, int time = 0) 99 | { 100 | var file = Config.CacheFolder + ID; 101 | if (System.IO.File.Exists(Config.CacheFolder + ID)) 102 | { 103 | if (time > 0) 104 | { 105 | var t = System.IO.File.GetCreationTime(file); 106 | var elapsedTicks = DateTime.Now.Ticks - t.Ticks; 107 | var elapsedSpan = new TimeSpan(elapsedTicks); 108 | if (elapsedSpan.TotalDays < time) return System.IO.File.ReadAllBytes(file); 109 | } 110 | else return System.IO.File.ReadAllBytes(file); 111 | } 112 | return null; 113 | } 114 | 115 | #endregion 116 | } 117 | 118 | /// 119 | /// 响应结果 120 | /// 121 | public class ResultResponse 122 | { 123 | public ResultResponse(Uri uri) 124 | { 125 | Uri = uri; 126 | OriginalSize = Size = -1; 127 | Header = Cookie = new Dictionary(0); 128 | } 129 | public ResultResponse(Uri uri, Exception ex) : this(uri) { Exception = ex; } 130 | public ResultResponse(HttpWebResponse data, Exception ex) : this(data) { Exception = ex; } 131 | public ResultResponse(HttpWebResponse data) 132 | { 133 | OriginalSize = Size = data.ContentLength; 134 | StatusCode = (int)data.StatusCode; 135 | Type = data.ContentType; 136 | ServerHeader = string.Join(" ", new string[] { 137 | data.ResponseUri.Scheme.ToUpper(), 138 | StatusCode.ToString(), 139 | data.StatusCode.ToString(), 140 | data.Server, 141 | "Ver:" + data.ProtocolVersion.ToString() 142 | }); 143 | Uri = data.ResponseUri; 144 | if (data.Headers.Count > 0 || data.Cookies.Count > 0) 145 | { 146 | if (data.Headers.Count > 0) 147 | { 148 | var header = new Dictionary>(data.Headers.AllKeys.Length); 149 | foreach (string it in data.Headers.AllKeys) 150 | { 151 | var val = data.Headers[it]; 152 | if (val == null) continue; 153 | if (header.ContainsKey(it)) header[it].Add(val); 154 | else header.Add(it, new List { val }); 155 | } 156 | Header = new Dictionary(header.Count); 157 | foreach (var it in header) Header.Add(it.Key, string.Join("; ", it.Value)); 158 | } 159 | else Header = new Dictionary(0); 160 | 161 | if (data.Cookies.Count > 0) 162 | { 163 | var cookie = new Dictionary>(data.Cookies.Count); 164 | foreach (Cookie it in data.Cookies) 165 | { 166 | if (cookie.ContainsKey(it.Name)) cookie[it.Name].Add(it.Value); 167 | else cookie.Add(it.Name, new List { it.Value }); 168 | } 169 | Cookie = new Dictionary(cookie.Count); 170 | foreach (var it in cookie) Cookie.Add(it.Key, string.Join(";", it.Value)); 171 | } 172 | else Cookie = new Dictionary(0); 173 | } 174 | else Header = Cookie = new Dictionary(0); 175 | } 176 | 177 | /// 178 | /// 指示HTTP响应是否成功 range 200-299 179 | /// 180 | public bool IsSuccessStatusCode { get => StatusCode >= 200 && StatusCode <= 299; } 181 | 182 | /// 183 | /// 状态代码 184 | /// 185 | public int StatusCode { get; set; } 186 | 187 | /// 188 | /// 服务头 189 | /// 190 | public string? ServerHeader { get; set; } 191 | 192 | /// 193 | /// 地址 194 | /// 195 | public Uri Uri { get; set; } 196 | 197 | /// 198 | /// 响应指示类型 199 | /// 200 | public string? Type { get; set; } 201 | 202 | /// 203 | /// 响应头 204 | /// 205 | public Dictionary Header { get; set; } 206 | public Dictionary Cookie { get; set; } 207 | 208 | /// 209 | /// 流原始大小 210 | /// 211 | public long OriginalSize { get; set; } 212 | 213 | /// 214 | /// 流大小 215 | /// 216 | public long Size { get; set; } 217 | 218 | /// 219 | /// 错误异常 220 | /// 221 | public Exception? Exception { get; set; } 222 | } 223 | } -------------------------------------------------------------------------------- /src/HttpDown/HttpDown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace HttpLib 10 | { 11 | public partial class HttpDown : IDisposable 12 | { 13 | #region 下载 14 | 15 | #region 多样下载 16 | 17 | /// 18 | /// 下载-自动 19 | /// 20 | public Task Go() => Go(Environment.ProcessorCount, null); 21 | 22 | /// 23 | /// 下载-自定义下载线程数 24 | /// 25 | public Task Go(int ThreadCount) => Go(ThreadCount, null); 26 | 27 | /// 28 | /// 下载-自定义保存文件名称 29 | /// 30 | /// 31 | public Task Go(string FileName) => Go(Environment.ProcessorCount, FileName); 32 | 33 | #endregion 34 | 35 | /// 36 | /// 下载 37 | /// 38 | /// 线程数 39 | /// 文件名称 40 | public Task Go(int ThreadCount, string? FileName) 41 | { 42 | CanSpeed = true; 43 | #if NET40 44 | return Task.Factory.StartNew(() => 45 | { 46 | return DownLoad(ThreadCount, FileName); 47 | }); 48 | #else 49 | return Task.Run(() => 50 | { 51 | return DownLoad(ThreadCount, FileName); 52 | }); 53 | #endif 54 | } 55 | 56 | #endregion 57 | 58 | #region 功能 59 | 60 | /// 61 | /// 暂停下载 62 | /// 63 | public void Suspend() 64 | { 65 | resetState.Reset(); 66 | SetState(DownState.Stop); 67 | } 68 | /// 69 | /// 恢复下载 70 | /// 71 | public void Resume() 72 | { 73 | SetState(DownState.Downloading); 74 | resetState.Set(); 75 | } 76 | 77 | #endregion 78 | 79 | public override string ToString() => Url; 80 | 81 | /// 82 | /// 停止下载 83 | /// 84 | public void Dispose() 85 | { 86 | //终止task线程 87 | resetState.Set(); 88 | resetState.Dispose(); 89 | } 90 | 91 | #region 核心 92 | 93 | string? DownLoad(int ThreadCount, string? FileName) 94 | { 95 | string WorkPath = SavePath + (ID ?? Guid.NewGuid().ToString()) + Path.DirectorySeparatorChar; 96 | WorkPath.CreateDirectory(); 97 | long Length = HttpDownLib.PreRequest(this, ThreadCount, out bool can_range, out var disposition); 98 | FileName ??= Uri.FileName(disposition); 99 | TotalCount = DownCount = 0; 100 | 101 | #region 任务分配 102 | 103 | long SingleFileLength = Length; 104 | int TaskCount = 1; 105 | if (can_range && Length > 2097152) 106 | { 107 | SingleFileLength = 2097152;//大于2MB才做切片 108 | TaskCount = (int)Math.Ceiling(Length / (SingleFileLength * 1.0));//任务分块 109 | } 110 | 111 | var files = new List(TaskCount); 112 | List valTmp = new List(TaskCount), maxTmp = new List(TaskCount); 113 | for (int i = 0; i < TaskCount; i++) 114 | { 115 | long byte_start = SingleFileLength * i, byte_end = SingleFileLength; 116 | if ((byte_start + SingleFileLength) > Length) byte_end = SingleFileLength - ((byte_start + SingleFileLength) - Length); 117 | 118 | string filename_tmp = $"{i}_{byte_start}_{byte_start + byte_end}.temp"; 119 | files.Add(new FilesResult(i, WorkPath + filename_tmp, byte_start, byte_end)); 120 | valTmp.Add(0); 121 | maxTmp.Add(byte_end); 122 | } 123 | ValTmp = valTmp.ToArray(); 124 | MaxTmp = maxTmp.ToArray(); 125 | 126 | SetMaxValue(); 127 | 128 | #endregion 129 | 130 | var option = new HttpDownOption(this, ThreadCount, FileName, SavePath, WorkPath, can_range); 131 | return HttpDownLib.DownLoad(option, files.ToArray()); 132 | } 133 | 134 | #endregion 135 | } 136 | 137 | internal class HttpDownLib 138 | { 139 | #region 核心 140 | 141 | public static string? DownLoad(HttpDownOption option, FilesResult[] fileRS) 142 | { 143 | option.core.core.range(); 144 | option.core.SetState(DownState.Downloading, null); 145 | option.core.CanSpeed = true; 146 | TestTime(option); 147 | 148 | bool is_stop = false; 149 | Parallel.ForEach(fileRS, new ParallelOptions { MaxDegreeOfParallelism = option.ThreadCount }, it => DownLoadSingleRetry(option, it, ref is_stop)); 150 | option.core.CanSpeed = false; 151 | if (is_stop) option.core.SetState(DownState.Complete, "主动停止"); 152 | else 153 | { 154 | var errors = new List(1); 155 | int errorcount = 0; 156 | foreach (var it in fileRS) 157 | { 158 | if (it.error) 159 | { 160 | errorcount++; 161 | if (it.errmsg != null && !errors.Contains(it.errmsg)) errors.Add(it.errmsg); 162 | } 163 | } 164 | if (errorcount > 0) 165 | { 166 | if (errors.Count > 0) option.core.SetState(DownState.Fail, string.Join(" ", errors)); 167 | else option.core.SetState(DownState.Fail, "下载不完全"); 168 | return null; 169 | } 170 | var files = new List(fileRS.Length); 171 | foreach (var it in fileRS) files.Add(it.path); 172 | try 173 | { 174 | var path = files.CombineMultipleFilesIntoSingleFile(option.SaveFullName, option.WorkPath); 175 | option.core.SetState(DownState.Complete, null); 176 | return path; 177 | } 178 | catch (Exception ez) { option.core.SetState(DownState.Fail, ez.Message); return null; } 179 | } 180 | return null; 181 | } 182 | 183 | static bool DownLoadSingleRetry(HttpDownOption option, FilesResult it, ref bool is_stop) 184 | { 185 | int ErrCount = 0; 186 | while (true) 187 | { 188 | if (option.core.resetState.Wait()) 189 | { 190 | is_stop = true; 191 | option.core.SetState(DownState.Complete, "主动停止"); 192 | return false; 193 | } 194 | if (DownLoadSingle(option, it, ref is_stop, out var err)) 195 | { 196 | it.error = false; 197 | it.errmsg = null; 198 | return true; 199 | } 200 | else 201 | { 202 | ErrCount++; 203 | it.errmsg = err; 204 | it.error = true; 205 | if (ErrCount > option.core.RetryCount) return false; 206 | else Thread.Sleep(1000); 207 | } 208 | } 209 | } 210 | static bool DownLoadSingle(HttpDownOption option, FilesResult item, ref bool is_stop, out string? error) 211 | { 212 | error = null; 213 | try 214 | { 215 | long PreFileLength = 0; 216 | using (var file = new FileStream(item.path, FileMode.OpenOrCreate)) 217 | { 218 | if (item.end_position > 0 && file.Length >= item.end_position) 219 | { 220 | PreFileLength = item.end_position; 221 | option.core.SetMaxValue(item.i, PreFileLength); 222 | option.core.SetValue(item.i, PreFileLength); 223 | return true; 224 | } 225 | else if (option.CanRange) PreFileLength = file.Length; 226 | else 227 | { 228 | file.Close(); 229 | File.Delete(item.path); 230 | } 231 | } 232 | 233 | using (var file = new FileStream(item.path, FileMode.OpenOrCreate)) 234 | { 235 | var request = option.core.core.CreateRequest(); 236 | if (option.CanRange) request.AddRange(item.start_position + PreFileLength, item.start_position + item.end_position); 237 | using (var response = (HttpWebResponse)request.GetResponse()) 238 | { 239 | if (response.ContentLength > 0) option.core.SetMaxValue(item.i, PreFileLength + response.ContentLength); 240 | using (var stream = response.GetResponseStream()) 241 | { 242 | long DownValue = 0; 243 | if (PreFileLength > 0) 244 | { 245 | file.Seek(PreFileLength, SeekOrigin.Begin); 246 | DownValue += PreFileLength; 247 | option.core.SetValue(item.i, DownValue); 248 | } 249 | byte[] cache = new byte[option.core.CacheSize]; 250 | int osize = stream.Read(cache, 0, cache.Length); 251 | while (osize > 0) 252 | { 253 | DownValue += osize; 254 | option.core.SetValue(item.i, DownValue); 255 | if (option.core.resetState.Wait()) 256 | { 257 | is_stop = true; 258 | return false; 259 | } 260 | file.Write(cache, 0, osize); 261 | osize = stream.Read(cache, 0, cache.Length); 262 | } 263 | option.core.SetValue(item.i, DownValue); 264 | } 265 | } 266 | } 267 | return true; 268 | } 269 | catch (Exception err) 270 | { 271 | error = err.Message; 272 | } 273 | return false; 274 | } 275 | 276 | /// 277 | /// 计算速度 278 | /// 279 | static void TestTime(HttpDownOption option) 280 | { 281 | var tmp = new List(); 282 | long oldsize = 0; 283 | ITask.Run(() => 284 | { 285 | while (option.core.State == DownState.Downloading || option.core.State == DownState.Stop) 286 | { 287 | Thread.Sleep(1000); 288 | long _downsize = option.core.Value - oldsize; 289 | oldsize = option.core.Value; 290 | 291 | if (_downsize > 0) 292 | { 293 | int se = (int)((option.core.MaxValue - oldsize) / _downsize); 294 | if (se < 1) 295 | { 296 | option.core.SetTime(null); 297 | option.core.SetSpeed(_downsize); 298 | } 299 | else 300 | { 301 | tmp.Add(se); 302 | 303 | if (tmp.Count > 4) 304 | { 305 | int AVE = (int)Math.Ceiling(tmp.Average()); 306 | tmp.Clear(); 307 | TimeSpan timeSpan = new TimeSpan(0, 0, 0, AVE); 308 | string time_txt = $"{timeSpan.Hours.ToString().PadLeft(2, '0')}:{timeSpan.Minutes.ToString().PadLeft(2, '0')}:{timeSpan.Seconds.ToString().PadLeft(2, '0')}"; 309 | if (time_txt.StartsWith("00:")) time_txt = time_txt.Substring(3); 310 | option.core.SetTime(time_txt); 311 | } 312 | option.core.SetSpeed(_downsize); 313 | } 314 | } 315 | else option.core.SetSpeed(0); 316 | } 317 | }); 318 | } 319 | 320 | /// 321 | /// 预请求 322 | /// 323 | /// 324 | /// 是否可以分段 325 | /// 326 | /// 真实长度 327 | public static long PreRequest(HttpDown core, int ThreadCount, out bool can_range, out string? disposition) 328 | { 329 | disposition = null; 330 | try 331 | { 332 | core.core.range(); 333 | var request = core.core.requestNone(); 334 | if (request.Header.ContainsKey("Content-Disposition")) disposition = request.Header["Content-Disposition"]; 335 | var ReadLength = request.Size; 336 | if (ThreadCount > 1 && ReadLength > 0) 337 | { 338 | try 339 | { 340 | core.core.range(1, ReadLength - 1); 341 | var request2 = core.core.requestNone(); 342 | long length = request2.Size; 343 | can_range = length == ReadLength - 1; 344 | } 345 | catch 346 | { 347 | can_range = false; 348 | } 349 | } 350 | else can_range = false; 351 | return ReadLength; 352 | } 353 | catch 354 | { 355 | can_range = false; 356 | return 0; 357 | } 358 | } 359 | 360 | #endregion 361 | } 362 | 363 | internal class HttpDownOption 364 | { 365 | public HttpDownOption(HttpDown c, int threadCount, string fileName, string savePath, string workPath, bool can_range) 366 | { 367 | core = c; 368 | ThreadCount = threadCount; 369 | SaveFullName = savePath + fileName; 370 | WorkPath = workPath; 371 | CanRange = can_range; 372 | } 373 | 374 | public HttpDown core { get; set; } 375 | 376 | /// 377 | /// 线程数量 378 | /// 379 | public int ThreadCount { get; set; } 380 | 381 | public bool CanRange { get; set; } 382 | 383 | public string SaveFullName { get; set; } 384 | public string WorkPath { get; set; } 385 | } 386 | 387 | internal class FilesResult 388 | { 389 | public FilesResult(int _i, string _path, long s, long e) 390 | { 391 | i = _i; 392 | path = _path; 393 | start_position = s; 394 | end_position = e; 395 | } 396 | public int i { get; set; } 397 | 398 | /// 399 | /// 文件保存地址 400 | /// 401 | public string path { get; set; } 402 | 403 | /// 404 | /// 文件开始位置 405 | /// 406 | public long start_position { get; set; } 407 | 408 | /// 409 | /// 文件结束位置 410 | /// 411 | public long end_position { get; set; } 412 | 413 | public bool error { get; set; } 414 | public string? errmsg { get; set; } 415 | } 416 | } -------------------------------------------------------------------------------- /src/HttpCore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Text; 5 | 6 | namespace HttpLib 7 | { 8 | public partial class HttpCore 9 | { 10 | public HttpOption option; 11 | public HttpCore(string uri, HttpMethod method) 12 | { 13 | option = new HttpOption(uri, method); 14 | } 15 | public HttpCore(Uri uri, HttpMethod method) 16 | { 17 | option = new HttpOption(uri, method); 18 | } 19 | public HttpCore(HttpOption _option) 20 | { 21 | option = _option; 22 | } 23 | 24 | #region 请求参(GET) 25 | 26 | /// 27 | /// 请求参(GET) 28 | /// 29 | /// 多个参数 30 | public HttpCore query(params Val[] vals) 31 | { 32 | option.query ??= new List(vals.Length); 33 | option.query.AddRange(vals); 34 | return this; 35 | } 36 | 37 | /// 38 | /// 请求参(GET) 39 | /// 40 | /// 多个参数 41 | public HttpCore query(IList vals) 42 | { 43 | option.query ??= new List(vals.Count); 44 | option.query.AddRange(vals); 45 | return this; 46 | } 47 | 48 | /// 49 | /// 请求参(GET) 50 | /// 51 | /// 键 52 | /// 值 53 | public HttpCore query(string key, string? val) 54 | { 55 | if (option.query == null) option.query = new List { new Val(key, val) }; 56 | else option.query.Add(new Val(key, val)); 57 | return this; 58 | } 59 | 60 | /// 61 | /// 请求参(GET) 62 | /// 63 | /// 多个参数 64 | public HttpCore query(IDictionary vals) 65 | { 66 | option.query ??= new List(vals.Count); 67 | foreach (var it in vals) option.query.Add(new Val(it.Key, it.Value)); 68 | return this; 69 | } 70 | 71 | /// 72 | /// 请求参(GET) 73 | /// 74 | /// 多个参数 75 | public HttpCore query(object data) 76 | { 77 | var list = data.GetType().GetProperties(); 78 | var vals = new List(list.Length); 79 | foreach (var it in list) 80 | { 81 | string key = it.Name; 82 | if (key != "_" && key.EndsWith("_")) key = key.TrimEnd('_'); 83 | var valO = it.GetValue(data, null); 84 | if (valO != null) vals.Add(new Val(key, valO.ToString())); 85 | } 86 | if (vals.Count > 0) return query(vals); 87 | return this; 88 | } 89 | 90 | #endregion 91 | 92 | #region 请求参 93 | 94 | /// 95 | /// 请求参 96 | /// 97 | /// 多个参数 98 | public HttpCore data(params Val[] vals) 99 | { 100 | option.data ??= new List(vals.Length); 101 | option.data.AddRange(vals); 102 | return this; 103 | } 104 | 105 | /// 106 | /// 请求参 107 | /// 108 | /// 多个参数 109 | public HttpCore data(IList vals) 110 | { 111 | option.data ??= new List(vals.Count); 112 | option.data.AddRange(vals); 113 | return this; 114 | } 115 | 116 | /// 117 | /// 请求参 118 | /// 119 | /// 键 120 | /// 值 121 | public HttpCore data(string key, string? val) 122 | { 123 | if (option.data == null) option.data = new List { new Val(key, val) }; 124 | else option.data.Add(new Val(key, val)); 125 | return this; 126 | } 127 | 128 | /// 129 | /// 请求参 130 | /// 131 | /// 多个参数 132 | public HttpCore data(IDictionary vals) 133 | { 134 | option.data ??= new List(vals.Count); 135 | foreach (var it in vals) option.data.Add(new Val(it.Key, it.Value)); 136 | return this; 137 | } 138 | 139 | /// 140 | /// 请求参 141 | /// 142 | /// 多个参数 143 | public HttpCore data(object _data) 144 | { 145 | var list = _data.GetType().GetProperties(); 146 | var vals = new List(list.Length); 147 | foreach (var it in list) 148 | { 149 | string key = it.Name; 150 | if (key != "_" && key.EndsWith("_")) key = key.TrimEnd('_'); 151 | var valO = it.GetValue(_data, null); 152 | if (valO != null) vals.Add(new Val(key, valO.ToString())); 153 | } 154 | if (vals.Count > 0) return data(vals); 155 | return this; 156 | } 157 | 158 | /// 159 | /// 请求参(数组) 160 | /// 161 | /// 键 162 | /// 值 163 | public HttpCore data(string key, IList val) 164 | { 165 | if (option.method == HttpMethod.Get) throw new Exception("Get不支持数组"); 166 | option.data ??= new List(val.Count); 167 | foreach (var it in val) option.data.Add(new Val(key + "[]", it)); 168 | return this; 169 | } 170 | 171 | /// 172 | /// 请求参 173 | /// 174 | /// text/plain 175 | /// 类型 176 | public HttpCore datastr(string val, string contentType = "text/plain") 177 | { 178 | option.datastr = val; 179 | header("Content-Type", contentType); 180 | return this; 181 | } 182 | 183 | #region 文件 184 | 185 | /// 186 | /// 请求参(文件) 187 | /// 188 | /// 多个文件 189 | public HttpCore data(params Files[] files) 190 | { 191 | option.file ??= new List(files.Length); 192 | option.file.AddRange(files); 193 | return this; 194 | } 195 | 196 | /// 197 | /// 请求参(文件) 198 | /// 199 | /// 多个文件 200 | public HttpCore data(IList files) 201 | { 202 | option.file ??= new List(files.Count); 203 | option.file.AddRange(files); 204 | return this; 205 | } 206 | 207 | /// 208 | /// 请求参(文件) 209 | /// 210 | /// 多个文件 211 | public HttpCore file(params string[] vals) 212 | { 213 | option.file ??= new List(vals.Length); 214 | foreach (var item in vals) option.file.Add(new Files(item)); 215 | return this; 216 | } 217 | 218 | /// 219 | /// 请求参(文件) 220 | /// 221 | /// 多个文件 222 | public HttpCore file(params Files[] vals) 223 | { 224 | option.file ??= new List(vals.Length); 225 | option.file.AddRange(vals); 226 | return this; 227 | } 228 | 229 | /// 230 | /// 请求参(文件) 231 | /// 232 | /// 多个文件 233 | public HttpCore file(IList vals) 234 | { 235 | option.file ??= new List(vals.Count); 236 | option.file.AddRange(vals); 237 | return this; 238 | } 239 | 240 | #endregion 241 | 242 | #endregion 243 | 244 | #region 请求头 245 | 246 | /// 247 | /// 请求头 248 | /// 249 | /// 多个参数 250 | public HttpCore header(params Val[] vals) 251 | { 252 | option.header ??= new List(vals.Length); 253 | option.header.AddRange(vals); 254 | return this; 255 | } 256 | 257 | /// 258 | /// 请求头 259 | /// 260 | /// 多个参数 261 | public HttpCore header(IList vals) 262 | { 263 | option.header ??= new List(vals.Count); 264 | option.header.AddRange(vals); 265 | return this; 266 | } 267 | 268 | /// 269 | /// 请求头 270 | /// 271 | /// 键 272 | /// 值 273 | public HttpCore header(string key, string? val) 274 | { 275 | if (option.header == null) option.header = new List { new Val(key, val) }; 276 | else option.header.Add(new Val(key, val)); 277 | return this; 278 | } 279 | 280 | /// 281 | /// 请求头 282 | /// 283 | /// 键 284 | /// 值 285 | public HttpCore header(string key, long val) 286 | { 287 | if (option.header == null) option.header = new List { new Val(key, val) }; 288 | else option.header.Add(new Val(key, val)); 289 | return this; 290 | } 291 | 292 | /// 293 | /// 请求头 294 | /// 295 | /// 多个参数 296 | public HttpCore header(IDictionary vals) 297 | { 298 | option.header ??= new List(vals.Count); 299 | foreach (var it in vals) option.header.Add(new Val(it.Key, it.Value)); 300 | return this; 301 | } 302 | 303 | /// 304 | /// 请求头 305 | /// 306 | /// 多个参数 307 | public HttpCore header(object data) 308 | { 309 | var list = data.GetType().GetProperties(); 310 | var vals = new List(list.Length); 311 | foreach (var it in list) 312 | { 313 | string key = GetTFName(it.Name).TrimStart('-'); 314 | if (key != "_" && key.EndsWith("_")) key = key.TrimEnd('_'); 315 | var valO = it.GetValue(data, null); 316 | if (valO != null) vals.Add(new Val(key, valO.ToString())); 317 | } 318 | if (vals.Count > 0) return header(vals); 319 | return this; 320 | } 321 | 322 | #endregion 323 | 324 | #region 代理 325 | 326 | /// 327 | /// 代理 328 | /// 329 | /// 服务器URI 330 | public HttpCore proxy(string address) 331 | { 332 | option.proxy = new WebProxy(address); 333 | return this; 334 | } 335 | 336 | /// 337 | /// 代理 338 | /// 339 | /// 服务器URI 340 | public HttpCore proxy(Uri address) 341 | { 342 | option.proxy = new WebProxy(address); 343 | return this; 344 | } 345 | 346 | /// 347 | /// 代理 348 | /// 349 | /// 主机名称 350 | /// 端口 351 | public HttpCore proxy(string host, int port) 352 | { 353 | option.proxy = new WebProxy(host, port); 354 | return this; 355 | } 356 | 357 | /// 358 | /// 代理 359 | /// 360 | /// 主机名称 361 | /// 端口 362 | /// 用户名 363 | /// 密码 364 | public HttpCore proxy(string host, int port, string? username, string? password) 365 | { 366 | option.proxy = new WebProxy(host, port); 367 | if (!string.IsNullOrEmpty(username)) option.proxy.Credentials = new NetworkCredential(username, password); 368 | return this; 369 | } 370 | 371 | #endregion 372 | 373 | #region 编码 374 | 375 | /// 376 | /// 编码 377 | /// 378 | /// 编码 379 | public HttpCore encoding(string encoding) 380 | { 381 | option.encoding = Encoding.GetEncoding(encoding); 382 | return this; 383 | } 384 | 385 | /// 386 | /// 编码 387 | /// 388 | /// 编码 389 | public HttpCore encoding(Encoding encoding) 390 | { 391 | option.encoding = encoding; 392 | return this; 393 | } 394 | 395 | /// 396 | /// 自动编码(识别html编码格式) 397 | /// 398 | public HttpCore autoencode(bool auto = true) 399 | { 400 | option.autoencode = auto; 401 | return this; 402 | } 403 | 404 | #endregion 405 | 406 | #region 重定向 407 | 408 | /// 409 | /// 重定向 410 | /// 411 | public HttpCore redirect(bool val = true) 412 | { 413 | option.redirect = val; 414 | return this; 415 | } 416 | 417 | #endregion 418 | 419 | #region 超时 420 | 421 | /// 422 | /// 超时 423 | /// 424 | /// 毫秒 425 | public HttpCore timeout(int time) 426 | { 427 | option.timeout = time; 428 | return this; 429 | } 430 | 431 | #endregion 432 | 433 | #region 缓存 434 | 435 | CacheModel? _cache = null; 436 | /// 437 | /// 设置缓存 438 | /// 439 | /// 缓存id 440 | public HttpCore cache(string id) 441 | { 442 | _cache = new CacheModel(id); 443 | return this; 444 | } 445 | 446 | /// 447 | /// 设置缓存 448 | /// 449 | /// 缓存id 450 | /// 有效期 分钟 451 | public HttpCore cache(string id, int time) 452 | { 453 | _cache = new CacheModel(id) { t = time }; 454 | return this; 455 | } 456 | 457 | class CacheModel 458 | { 459 | public CacheModel(string _id) 460 | { 461 | if (Config.CacheFolder == null) throw new Exception("先配置 \"Config.CachePath\""); 462 | path = Config.CacheFolder; 463 | id = _id; 464 | file = path + _id; 465 | } 466 | public string id { get; set; } 467 | public int t = 0; 468 | public string path { get; set; } 469 | public string file { get; set; } 470 | } 471 | 472 | #endregion 473 | 474 | #region 分块 475 | 476 | /// 477 | /// 清空字节范围 478 | /// 479 | public HttpCore range() 480 | { 481 | _range = null; 482 | return this; 483 | } 484 | 485 | long[]? _range = null; 486 | /// 487 | /// 字节范围 488 | /// 489 | /// 开始发送数据的位置 490 | /// 停止发送数据的位置 491 | public HttpCore range(long from, long to) 492 | { 493 | _range = new long[] { from, to }; 494 | return this; 495 | } 496 | 497 | /// 498 | /// 字节范围 499 | /// 500 | /// 开始发送数据的位置 501 | /// 停止发送数据的位置 502 | public HttpCore range(int from, int to) 503 | { 504 | _range = new long[] { from, to }; 505 | return this; 506 | } 507 | 508 | #endregion 509 | } 510 | 511 | public enum HttpMethod 512 | { 513 | Get, 514 | Post, 515 | Put, 516 | Delete, 517 | Head, 518 | Patch, 519 | Options 520 | } 521 | } -------------------------------------------------------------------------------- /examples/TDown/Form1.Designer.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace TDown 3 | { 4 | partial class Form1 5 | { 6 | /// 7 | /// 必需的设计器变量。 8 | /// 9 | private System.ComponentModel.IContainer components = null; 10 | 11 | /// 12 | /// 清理所有正在使用的资源。 13 | /// 14 | /// 如果应释放托管资源,为 true;否则为 false。 15 | protected override void Dispose(bool disposing) 16 | { 17 | if (disposing && (components != null)) 18 | { 19 | components.Dispose(); 20 | } 21 | base.Dispose(disposing); 22 | } 23 | 24 | #region Windows 窗体设计器生成的代码 25 | 26 | /// 27 | /// 设计器支持所需的方法 - 不要修改 28 | /// 使用代码编辑器修改此方法的内容。 29 | /// 30 | private void InitializeComponent() 31 | { 32 | btn = new AntdUI.Button(); 33 | txt_uri = new AntdUI.Input(); 34 | textBox2 = new AntdUI.InputNumber(); 35 | label1 = new AntdUI.Label(); 36 | btn_suspend = new AntdUI.Button(); 37 | btn_resume = new AntdUI.Button(); 38 | btn_stop = new AntdUI.Button(); 39 | label_prog = new AntdUI.Label(); 40 | txt_max = new AntdUI.Label(); 41 | txt_value = new AntdUI.Label(); 42 | label_time = new AntdUI.Label(); 43 | txt_speed = new AntdUI.Label(); 44 | txt_state = new AntdUI.Badge(); 45 | panel_url = new System.Windows.Forms.TableLayoutPanel(); 46 | progress = new AntdUI.Progress(); 47 | panel_info = new System.Windows.Forms.TableLayoutPanel(); 48 | label_state = new AntdUI.Label(); 49 | txt_prog = new AntdUI.Label(); 50 | label_speed = new AntdUI.Label(); 51 | txt_time = new AntdUI.Label(); 52 | label_start_time = new AntdUI.Label(); 53 | txt_start_time = new AntdUI.Label(); 54 | txt_end_time = new AntdUI.Label(); 55 | label_end_time = new AntdUI.Label(); 56 | label_max = new AntdUI.Label(); 57 | label_value = new AntdUI.Label(); 58 | panel_btns = new System.Windows.Forms.TableLayoutPanel(); 59 | panel_url.SuspendLayout(); 60 | progress.SuspendLayout(); 61 | panel_info.SuspendLayout(); 62 | panel_btns.SuspendLayout(); 63 | SuspendLayout(); 64 | // 65 | // btn 66 | // 67 | btn.Dock = System.Windows.Forms.DockStyle.Fill; 68 | btn.Location = new System.Drawing.Point(739, 3); 69 | btn.Name = "btn"; 70 | btn.Size = new System.Drawing.Size(80, 38); 71 | btn.TabIndex = 0; 72 | btn.Text = "下载"; 73 | btn.Type = AntdUI.TTypeMini.Primary; 74 | btn.Click += btn_Click; 75 | // 76 | // txt_uri 77 | // 78 | txt_uri.BackColor = System.Drawing.Color.Transparent; 79 | txt_uri.BorderWidth = 0F; 80 | txt_uri.Dock = System.Windows.Forms.DockStyle.Fill; 81 | txt_uri.ForeColor = System.Drawing.Color.White; 82 | txt_uri.Location = new System.Drawing.Point(0, 0); 83 | txt_uri.Name = "txt_uri"; 84 | txt_uri.Size = new System.Drawing.Size(730, 38); 85 | txt_uri.TabIndex = 1; 86 | txt_uri.Text = "https://codeload.github.com/EVA-SS/HttpLib/zip/refs/heads/main"; 87 | // 88 | // textBox2 89 | // 90 | textBox2.Dock = System.Windows.Forms.DockStyle.Fill; 91 | textBox2.Location = new System.Drawing.Point(103, 3); 92 | textBox2.Name = "textBox2"; 93 | textBox2.Size = new System.Drawing.Size(476, 38); 94 | textBox2.TabIndex = 2; 95 | textBox2.Text = "2"; 96 | textBox2.Value = new decimal(new int[] { 2, 0, 0, 0 }); 97 | // 98 | // label1 99 | // 100 | label1.Dock = System.Windows.Forms.DockStyle.Fill; 101 | label1.Location = new System.Drawing.Point(0, 0); 102 | label1.Margin = new System.Windows.Forms.Padding(0); 103 | label1.Name = "label1"; 104 | label1.Size = new System.Drawing.Size(100, 44); 105 | label1.TabIndex = 0; 106 | label1.Text = "核心数:"; 107 | label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 108 | // 109 | // btn_suspend 110 | // 111 | btn_suspend.Dock = System.Windows.Forms.DockStyle.Fill; 112 | btn_suspend.Enabled = false; 113 | btn_suspend.Location = new System.Drawing.Point(585, 3); 114 | btn_suspend.Name = "btn_suspend"; 115 | btn_suspend.Size = new System.Drawing.Size(74, 38); 116 | btn_suspend.TabIndex = 3; 117 | btn_suspend.Text = "暂停"; 118 | btn_suspend.Type = AntdUI.TTypeMini.Warn; 119 | btn_suspend.Click += btn_suspend_Click; 120 | // 121 | // btn_resume 122 | // 123 | btn_resume.Dock = System.Windows.Forms.DockStyle.Fill; 124 | btn_resume.Enabled = false; 125 | btn_resume.Location = new System.Drawing.Point(665, 3); 126 | btn_resume.Name = "btn_resume"; 127 | btn_resume.Size = new System.Drawing.Size(74, 38); 128 | btn_resume.TabIndex = 3; 129 | btn_resume.Text = "继续"; 130 | btn_resume.Type = AntdUI.TTypeMini.Success; 131 | btn_resume.Click += btn_resume_Click; 132 | // 133 | // btn_stop 134 | // 135 | btn_stop.Dock = System.Windows.Forms.DockStyle.Fill; 136 | btn_stop.Enabled = false; 137 | btn_stop.Location = new System.Drawing.Point(745, 3); 138 | btn_stop.Name = "btn_stop"; 139 | btn_stop.Size = new System.Drawing.Size(74, 38); 140 | btn_stop.TabIndex = 3; 141 | btn_stop.Text = "终止"; 142 | btn_stop.Type = AntdUI.TTypeMini.Error; 143 | btn_stop.Click += btn_stop_Click; 144 | // 145 | // label_prog 146 | // 147 | label_prog.Dock = System.Windows.Forms.DockStyle.Fill; 148 | label_prog.Location = new System.Drawing.Point(411, 0); 149 | label_prog.Margin = new System.Windows.Forms.Padding(0); 150 | label_prog.Name = "label_prog"; 151 | label_prog.Size = new System.Drawing.Size(140, 42); 152 | label_prog.TabIndex = 0; 153 | label_prog.Text = "下载进度:"; 154 | label_prog.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 155 | // 156 | // txt_max 157 | // 158 | txt_max.Dock = System.Windows.Forms.DockStyle.Fill; 159 | txt_max.Location = new System.Drawing.Point(140, 126); 160 | txt_max.Margin = new System.Windows.Forms.Padding(0); 161 | txt_max.Name = "txt_max"; 162 | txt_max.Size = new System.Drawing.Size(271, 43); 163 | txt_max.TabIndex = 0; 164 | // 165 | // txt_value 166 | // 167 | txt_value.Dock = System.Windows.Forms.DockStyle.Fill; 168 | txt_value.Location = new System.Drawing.Point(551, 126); 169 | txt_value.Margin = new System.Windows.Forms.Padding(0); 170 | txt_value.Name = "txt_value"; 171 | txt_value.Size = new System.Drawing.Size(271, 43); 172 | txt_value.TabIndex = 0; 173 | // 174 | // label_time 175 | // 176 | label_time.Dock = System.Windows.Forms.DockStyle.Fill; 177 | label_time.Location = new System.Drawing.Point(411, 42); 178 | label_time.Margin = new System.Windows.Forms.Padding(0); 179 | label_time.Name = "label_time"; 180 | label_time.Size = new System.Drawing.Size(140, 42); 181 | label_time.TabIndex = 0; 182 | label_time.Text = "剩余时间:"; 183 | label_time.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 184 | // 185 | // txt_speed 186 | // 187 | txt_speed.Dock = System.Windows.Forms.DockStyle.Fill; 188 | txt_speed.Location = new System.Drawing.Point(140, 42); 189 | txt_speed.Margin = new System.Windows.Forms.Padding(0); 190 | txt_speed.Name = "txt_speed"; 191 | txt_speed.Size = new System.Drawing.Size(271, 42); 192 | txt_speed.TabIndex = 0; 193 | // 194 | // txt_state 195 | // 196 | txt_state.Dock = System.Windows.Forms.DockStyle.Fill; 197 | txt_state.Location = new System.Drawing.Point(140, 0); 198 | txt_state.Margin = new System.Windows.Forms.Padding(0); 199 | txt_state.Name = "txt_state"; 200 | txt_state.Size = new System.Drawing.Size(271, 42); 201 | txt_state.TabIndex = 0; 202 | txt_state.Text = "待下载"; 203 | // 204 | // panel_url 205 | // 206 | panel_url.ColumnCount = 2; 207 | panel_url.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 208 | panel_url.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 86F)); 209 | panel_url.Controls.Add(progress, 0, 0); 210 | panel_url.Controls.Add(btn, 1, 0); 211 | panel_url.Dock = System.Windows.Forms.DockStyle.Top; 212 | panel_url.Location = new System.Drawing.Point(0, 0); 213 | panel_url.Margin = new System.Windows.Forms.Padding(4); 214 | panel_url.Name = "panel_url"; 215 | panel_url.RowCount = 1; 216 | panel_url.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 217 | panel_url.Size = new System.Drawing.Size(822, 44); 218 | panel_url.TabIndex = 0; 219 | // 220 | // progress 221 | // 222 | progress.Back = System.Drawing.Color.Gray; 223 | progress.Controls.Add(txt_uri); 224 | progress.Dock = System.Windows.Forms.DockStyle.Fill; 225 | progress.Location = new System.Drawing.Point(3, 3); 226 | progress.Name = "progress"; 227 | progress.Shape = AntdUI.TShape.Default; 228 | progress.Size = new System.Drawing.Size(730, 38); 229 | progress.TabIndex = 0; 230 | // 231 | // panel_info 232 | // 233 | panel_info.ColumnCount = 4; 234 | panel_info.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 140F)); 235 | panel_info.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); 236 | panel_info.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 140F)); 237 | panel_info.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); 238 | panel_info.Controls.Add(txt_state, 1, 0); 239 | panel_info.Controls.Add(label_state, 0, 0); 240 | panel_info.Controls.Add(label_prog, 2, 0); 241 | panel_info.Controls.Add(txt_prog, 3, 0); 242 | panel_info.Controls.Add(label_speed, 0, 1); 243 | panel_info.Controls.Add(txt_speed, 1, 1); 244 | panel_info.Controls.Add(label_time, 2, 1); 245 | panel_info.Controls.Add(txt_time, 3, 1); 246 | panel_info.Controls.Add(label_start_time, 0, 2); 247 | panel_info.Controls.Add(txt_start_time, 1, 2); 248 | panel_info.Controls.Add(txt_end_time, 3, 2); 249 | panel_info.Controls.Add(label_end_time, 2, 2); 250 | panel_info.Controls.Add(label_max, 0, 3); 251 | panel_info.Controls.Add(txt_max, 1, 3); 252 | panel_info.Controls.Add(label_value, 2, 3); 253 | panel_info.Controls.Add(txt_value, 3, 3); 254 | panel_info.Dock = System.Windows.Forms.DockStyle.Fill; 255 | panel_info.Location = new System.Drawing.Point(0, 88); 256 | panel_info.Name = "panel_info"; 257 | panel_info.RowCount = 4; 258 | panel_info.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); 259 | panel_info.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); 260 | panel_info.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); 261 | panel_info.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F)); 262 | panel_info.Size = new System.Drawing.Size(822, 169); 263 | panel_info.TabIndex = 0; 264 | // 265 | // label_state 266 | // 267 | label_state.Dock = System.Windows.Forms.DockStyle.Fill; 268 | label_state.Location = new System.Drawing.Point(0, 0); 269 | label_state.Margin = new System.Windows.Forms.Padding(0); 270 | label_state.Name = "label_state"; 271 | label_state.Size = new System.Drawing.Size(140, 42); 272 | label_state.TabIndex = 0; 273 | label_state.Text = "状态:"; 274 | label_state.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 275 | // 276 | // txt_prog 277 | // 278 | txt_prog.Dock = System.Windows.Forms.DockStyle.Fill; 279 | txt_prog.Font = new System.Drawing.Font("Microsoft YaHei UI", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); 280 | txt_prog.Location = new System.Drawing.Point(551, 0); 281 | txt_prog.Margin = new System.Windows.Forms.Padding(0); 282 | txt_prog.Name = "txt_prog"; 283 | txt_prog.Size = new System.Drawing.Size(271, 42); 284 | txt_prog.TabIndex = 0; 285 | // 286 | // label_speed 287 | // 288 | label_speed.Dock = System.Windows.Forms.DockStyle.Fill; 289 | label_speed.Location = new System.Drawing.Point(0, 42); 290 | label_speed.Margin = new System.Windows.Forms.Padding(0); 291 | label_speed.Name = "label_speed"; 292 | label_speed.Size = new System.Drawing.Size(140, 42); 293 | label_speed.TabIndex = 0; 294 | label_speed.Text = "下载速度:"; 295 | label_speed.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 296 | // 297 | // txt_time 298 | // 299 | txt_time.Dock = System.Windows.Forms.DockStyle.Fill; 300 | txt_time.Location = new System.Drawing.Point(551, 42); 301 | txt_time.Margin = new System.Windows.Forms.Padding(0); 302 | txt_time.Name = "txt_time"; 303 | txt_time.Size = new System.Drawing.Size(271, 42); 304 | txt_time.TabIndex = 0; 305 | // 306 | // label_start_time 307 | // 308 | label_start_time.Dock = System.Windows.Forms.DockStyle.Fill; 309 | label_start_time.Location = new System.Drawing.Point(0, 84); 310 | label_start_time.Margin = new System.Windows.Forms.Padding(0); 311 | label_start_time.Name = "label_start_time"; 312 | label_start_time.Size = new System.Drawing.Size(140, 42); 313 | label_start_time.TabIndex = 0; 314 | label_start_time.Text = "开始时间:"; 315 | label_start_time.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 316 | // 317 | // txt_start_time 318 | // 319 | txt_start_time.Dock = System.Windows.Forms.DockStyle.Fill; 320 | txt_start_time.Location = new System.Drawing.Point(140, 84); 321 | txt_start_time.Margin = new System.Windows.Forms.Padding(0); 322 | txt_start_time.Name = "txt_start_time"; 323 | txt_start_time.Size = new System.Drawing.Size(271, 42); 324 | txt_start_time.TabIndex = 0; 325 | // 326 | // txt_end_time 327 | // 328 | txt_end_time.Dock = System.Windows.Forms.DockStyle.Fill; 329 | txt_end_time.Location = new System.Drawing.Point(551, 84); 330 | txt_end_time.Margin = new System.Windows.Forms.Padding(0); 331 | txt_end_time.Name = "txt_end_time"; 332 | txt_end_time.Size = new System.Drawing.Size(271, 42); 333 | txt_end_time.TabIndex = 0; 334 | // 335 | // label_end_time 336 | // 337 | label_end_time.Dock = System.Windows.Forms.DockStyle.Fill; 338 | label_end_time.Location = new System.Drawing.Point(411, 84); 339 | label_end_time.Margin = new System.Windows.Forms.Padding(0); 340 | label_end_time.Name = "label_end_time"; 341 | label_end_time.Size = new System.Drawing.Size(140, 42); 342 | label_end_time.TabIndex = 0; 343 | label_end_time.Text = "完成时间:"; 344 | label_end_time.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 345 | // 346 | // label_max 347 | // 348 | label_max.Dock = System.Windows.Forms.DockStyle.Fill; 349 | label_max.Location = new System.Drawing.Point(0, 126); 350 | label_max.Margin = new System.Windows.Forms.Padding(0); 351 | label_max.Name = "label_max"; 352 | label_max.Size = new System.Drawing.Size(140, 43); 353 | label_max.TabIndex = 0; 354 | label_max.Text = "Max:"; 355 | label_max.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 356 | // 357 | // label_value 358 | // 359 | label_value.Dock = System.Windows.Forms.DockStyle.Fill; 360 | label_value.Location = new System.Drawing.Point(411, 126); 361 | label_value.Margin = new System.Windows.Forms.Padding(0); 362 | label_value.Name = "label_value"; 363 | label_value.Size = new System.Drawing.Size(140, 43); 364 | label_value.TabIndex = 0; 365 | label_value.Text = "Value:"; 366 | label_value.TextAlign = System.Drawing.ContentAlignment.MiddleRight; 367 | // 368 | // panel_btns 369 | // 370 | panel_btns.ColumnCount = 5; 371 | panel_btns.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100F)); 372 | panel_btns.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 373 | panel_btns.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); 374 | panel_btns.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); 375 | panel_btns.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); 376 | panel_btns.Controls.Add(textBox2, 1, 0); 377 | panel_btns.Controls.Add(btn_stop, 4, 0); 378 | panel_btns.Controls.Add(label1, 0, 0); 379 | panel_btns.Controls.Add(btn_resume, 3, 0); 380 | panel_btns.Controls.Add(btn_suspend, 2, 0); 381 | panel_btns.Dock = System.Windows.Forms.DockStyle.Top; 382 | panel_btns.Location = new System.Drawing.Point(0, 44); 383 | panel_btns.Name = "panel_btns"; 384 | panel_btns.RowCount = 1; 385 | panel_btns.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 386 | panel_btns.Size = new System.Drawing.Size(822, 44); 387 | panel_btns.TabIndex = 1; 388 | // 389 | // Form1 390 | // 391 | AutoScaleDimensions = new System.Drawing.SizeF(10F, 21F); 392 | AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 393 | ClientSize = new System.Drawing.Size(822, 257); 394 | Controls.Add(panel_info); 395 | Controls.Add(panel_btns); 396 | Controls.Add(panel_url); 397 | Font = new System.Drawing.Font("Microsoft YaHei UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); 398 | Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); 399 | MinimumSize = new System.Drawing.Size(838, 296); 400 | Name = "Form1"; 401 | StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 402 | Text = "下载文件DEMO"; 403 | panel_url.ResumeLayout(false); 404 | progress.ResumeLayout(false); 405 | panel_info.ResumeLayout(false); 406 | panel_btns.ResumeLayout(false); 407 | ResumeLayout(false); 408 | } 409 | 410 | #endregion 411 | 412 | private AntdUI.Button btn; 413 | private AntdUI.Input txt_uri; 414 | private AntdUI.InputNumber textBox2; 415 | private AntdUI.Label label1; 416 | private AntdUI.Button btn_suspend; 417 | private AntdUI.Button btn_resume; 418 | private AntdUI.Button btn_stop; 419 | private AntdUI.Label label_prog; 420 | private AntdUI.Label txt_max; 421 | private AntdUI.Label txt_value; 422 | private AntdUI.Label label_time; 423 | private AntdUI.Label txt_speed; 424 | private AntdUI.Badge txt_state; 425 | private System.Windows.Forms.TableLayoutPanel panel_url; 426 | private AntdUI.Progress progress; 427 | private System.Windows.Forms.TableLayoutPanel panel_info; 428 | private AntdUI.Label label_state; 429 | private AntdUI.Label txt_prog; 430 | private AntdUI.Label txt_time; 431 | private AntdUI.Label label_speed; 432 | private AntdUI.Label label_max; 433 | private AntdUI.Label label_value; 434 | private System.Windows.Forms.TableLayoutPanel panel_btns; 435 | private AntdUI.Label label_start_time; 436 | private AntdUI.Label txt_start_time; 437 | private AntdUI.Label txt_end_time; 438 | private AntdUI.Label label_end_time; 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /src/Core/MimeMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace HttpLib 6 | { 7 | public static class MimeMapping 8 | { 9 | abstract class MimeMappingDictionaryBase 10 | { 11 | readonly Dictionary _mappings = new Dictionary(StringComparer.OrdinalIgnoreCase); 12 | 13 | static readonly char[] _pathSeparatorChars = new char[3] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, Path.VolumeSeparatorChar }; 14 | 15 | bool _isInitialized; 16 | 17 | protected void AddMapping(string fileExtension, string mimeType) => _mappings.Add(fileExtension, mimeType); 18 | 19 | void AddWildcardIfNotPresent() 20 | { 21 | if (!_mappings.ContainsKey(".*")) AddMapping(".*", "application/octet-stream"); 22 | } 23 | 24 | void EnsureMapping() 25 | { 26 | if (!_isInitialized) 27 | { 28 | lock (this) 29 | { 30 | if (!_isInitialized) 31 | { 32 | PopulateMappings(); 33 | AddWildcardIfNotPresent(); 34 | _isInitialized = true; 35 | } 36 | } 37 | } 38 | } 39 | 40 | protected abstract void PopulateMappings(); 41 | 42 | private static string GetFileName(string path) 43 | { 44 | int num = path.LastIndexOfAny(_pathSeparatorChars); 45 | if (num < 0) return path; 46 | return path.Substring(num); 47 | } 48 | 49 | public string GetMimeMapping(string fileName) 50 | { 51 | EnsureMapping(); 52 | fileName = GetFileName(fileName); 53 | for (int i = 0; i < fileName.Length; i++) 54 | { 55 | if (fileName[i] == '.' && _mappings.TryGetValue(fileName.Substring(i), out var result) && result != null) return result; 56 | } 57 | return _mappings[".*"]; 58 | } 59 | } 60 | 61 | private sealed class MimeMappingDictionaryClassic : MimeMappingDictionaryBase 62 | { 63 | protected override void PopulateMappings() 64 | { 65 | AddMapping(".323", "text/h323"); 66 | AddMapping(".aaf", "application/octet-stream"); 67 | AddMapping(".aca", "application/octet-stream"); 68 | AddMapping(".accdb", "application/msaccess"); 69 | AddMapping(".accde", "application/msaccess"); 70 | AddMapping(".accdt", "application/msaccess"); 71 | AddMapping(".acx", "application/internet-property-stream"); 72 | AddMapping(".afm", "application/octet-stream"); 73 | AddMapping(".ai", "application/postscript"); 74 | AddMapping(".aif", "audio/x-aiff"); 75 | AddMapping(".aifc", "audio/aiff"); 76 | AddMapping(".aiff", "audio/aiff"); 77 | AddMapping(".application", "application/x-ms-application"); 78 | AddMapping(".art", "image/x-jg"); 79 | AddMapping(".asd", "application/octet-stream"); 80 | AddMapping(".asf", "video/x-ms-asf"); 81 | AddMapping(".asi", "application/octet-stream"); 82 | AddMapping(".asm", "text/plain"); 83 | AddMapping(".asr", "video/x-ms-asf"); 84 | AddMapping(".asx", "video/x-ms-asf"); 85 | AddMapping(".atom", "application/atom+xml"); 86 | AddMapping(".au", "audio/basic"); 87 | AddMapping(".avi", "video/x-msvideo"); 88 | AddMapping(".axs", "application/olescript"); 89 | AddMapping(".bas", "text/plain"); 90 | AddMapping(".bcpio", "application/x-bcpio"); 91 | AddMapping(".bin", "application/octet-stream"); 92 | AddMapping(".bmp", "image/bmp"); 93 | AddMapping(".c", "text/plain"); 94 | AddMapping(".cab", "application/octet-stream"); 95 | AddMapping(".calx", "application/vnd.ms-office.calx"); 96 | AddMapping(".cat", "application/vnd.ms-pki.seccat"); 97 | AddMapping(".cdf", "application/x-cdf"); 98 | AddMapping(".chm", "application/octet-stream"); 99 | AddMapping(".class", "application/x-java-applet"); 100 | AddMapping(".clp", "application/x-msclip"); 101 | AddMapping(".cmx", "image/x-cmx"); 102 | AddMapping(".cnf", "text/plain"); 103 | AddMapping(".cod", "image/cis-cod"); 104 | AddMapping(".cpio", "application/x-cpio"); 105 | AddMapping(".cpp", "text/plain"); 106 | AddMapping(".crd", "application/x-mscardfile"); 107 | AddMapping(".crl", "application/pkix-crl"); 108 | AddMapping(".crt", "application/x-x509-ca-cert"); 109 | AddMapping(".csh", "application/x-csh"); 110 | AddMapping(".css", "text/css"); 111 | AddMapping(".csv", "application/octet-stream"); 112 | AddMapping(".cur", "application/octet-stream"); 113 | AddMapping(".dcr", "application/x-director"); 114 | AddMapping(".deploy", "application/octet-stream"); 115 | AddMapping(".der", "application/x-x509-ca-cert"); 116 | AddMapping(".dib", "image/bmp"); 117 | AddMapping(".dir", "application/x-director"); 118 | AddMapping(".disco", "text/xml"); 119 | AddMapping(".dll", "application/x-msdownload"); 120 | AddMapping(".dll.config", "text/xml"); 121 | AddMapping(".dlm", "text/dlm"); 122 | AddMapping(".doc", "application/msword"); 123 | AddMapping(".docm", "application/vnd.ms-word.document.macroEnabled.12"); 124 | AddMapping(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); 125 | AddMapping(".dot", "application/msword"); 126 | AddMapping(".dotm", "application/vnd.ms-word.template.macroEnabled.12"); 127 | AddMapping(".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"); 128 | AddMapping(".dsp", "application/octet-stream"); 129 | AddMapping(".dtd", "text/xml"); 130 | AddMapping(".dvi", "application/x-dvi"); 131 | AddMapping(".dwf", "drawing/x-dwf"); 132 | AddMapping(".dwp", "application/octet-stream"); 133 | AddMapping(".dxr", "application/x-director"); 134 | AddMapping(".eml", "message/rfc822"); 135 | AddMapping(".emz", "application/octet-stream"); 136 | AddMapping(".eot", "application/octet-stream"); 137 | AddMapping(".eps", "application/postscript"); 138 | AddMapping(".etx", "text/x-setext"); 139 | AddMapping(".evy", "application/envoy"); 140 | AddMapping(".exe", "application/octet-stream"); 141 | AddMapping(".exe.config", "text/xml"); 142 | AddMapping(".fdf", "application/vnd.fdf"); 143 | AddMapping(".fif", "application/fractals"); 144 | AddMapping(".fla", "application/octet-stream"); 145 | AddMapping(".flr", "x-world/x-vrml"); 146 | AddMapping(".flv", "video/x-flv"); 147 | AddMapping(".gif", "image/gif"); 148 | AddMapping(".gtar", "application/x-gtar"); 149 | AddMapping(".gz", "application/x-gzip"); 150 | AddMapping(".h", "text/plain"); 151 | AddMapping(".hdf", "application/x-hdf"); 152 | AddMapping(".hdml", "text/x-hdml"); 153 | AddMapping(".hhc", "application/x-oleobject"); 154 | AddMapping(".hhk", "application/octet-stream"); 155 | AddMapping(".hhp", "application/octet-stream"); 156 | AddMapping(".hlp", "application/winhlp"); 157 | AddMapping(".hqx", "application/mac-binhex40"); 158 | AddMapping(".hta", "application/hta"); 159 | AddMapping(".htc", "text/x-component"); 160 | AddMapping(".htm", "text/html"); 161 | AddMapping(".html", "text/html"); 162 | AddMapping(".htt", "text/webviewhtml"); 163 | AddMapping(".hxt", "text/html"); 164 | AddMapping(".ico", "image/x-icon"); 165 | AddMapping(".ics", "application/octet-stream"); 166 | AddMapping(".ief", "image/ief"); 167 | AddMapping(".iii", "application/x-iphone"); 168 | AddMapping(".inf", "application/octet-stream"); 169 | AddMapping(".ins", "application/x-internet-signup"); 170 | AddMapping(".isp", "application/x-internet-signup"); 171 | AddMapping(".IVF", "video/x-ivf"); 172 | AddMapping(".jar", "application/java-archive"); 173 | AddMapping(".java", "application/octet-stream"); 174 | AddMapping(".jck", "application/liquidmotion"); 175 | AddMapping(".jcz", "application/liquidmotion"); 176 | AddMapping(".jfif", "image/pjpeg"); 177 | AddMapping(".jpb", "application/octet-stream"); 178 | AddMapping(".jpe", "image/jpeg"); 179 | AddMapping(".jpeg", "image/jpeg"); 180 | AddMapping(".jpg", "image/jpeg"); 181 | AddMapping(".js", "application/x-javascript"); 182 | AddMapping(".jsx", "text/jscript"); 183 | AddMapping(".latex", "application/x-latex"); 184 | AddMapping(".lit", "application/x-ms-reader"); 185 | AddMapping(".lpk", "application/octet-stream"); 186 | AddMapping(".lsf", "video/x-la-asf"); 187 | AddMapping(".lsx", "video/x-la-asf"); 188 | AddMapping(".lzh", "application/octet-stream"); 189 | AddMapping(".m13", "application/x-msmediaview"); 190 | AddMapping(".m14", "application/x-msmediaview"); 191 | AddMapping(".m1v", "video/mpeg"); 192 | AddMapping(".m3u", "audio/x-mpegurl"); 193 | AddMapping(".man", "application/x-troff-man"); 194 | AddMapping(".manifest", "application/x-ms-manifest"); 195 | AddMapping(".map", "text/plain"); 196 | AddMapping(".mdb", "application/x-msaccess"); 197 | AddMapping(".mdp", "application/octet-stream"); 198 | AddMapping(".me", "application/x-troff-me"); 199 | AddMapping(".mht", "message/rfc822"); 200 | AddMapping(".mhtml", "message/rfc822"); 201 | AddMapping(".mid", "audio/mid"); 202 | AddMapping(".midi", "audio/mid"); 203 | AddMapping(".mix", "application/octet-stream"); 204 | AddMapping(".mmf", "application/x-smaf"); 205 | AddMapping(".mno", "text/xml"); 206 | AddMapping(".mny", "application/x-msmoney"); 207 | AddMapping(".mov", "video/quicktime"); 208 | AddMapping(".movie", "video/x-sgi-movie"); 209 | AddMapping(".mp2", "video/mpeg"); 210 | AddMapping(".mp3", "audio/mpeg"); 211 | AddMapping(".mpa", "video/mpeg"); 212 | AddMapping(".mpe", "video/mpeg"); 213 | AddMapping(".mpeg", "video/mpeg"); 214 | AddMapping(".mpg", "video/mpeg"); 215 | AddMapping(".mpp", "application/vnd.ms-project"); 216 | AddMapping(".mpv2", "video/mpeg"); 217 | AddMapping(".ms", "application/x-troff-ms"); 218 | AddMapping(".msi", "application/octet-stream"); 219 | AddMapping(".mso", "application/octet-stream"); 220 | AddMapping(".mvb", "application/x-msmediaview"); 221 | AddMapping(".mvc", "application/x-miva-compiled"); 222 | AddMapping(".nc", "application/x-netcdf"); 223 | AddMapping(".nsc", "video/x-ms-asf"); 224 | AddMapping(".nws", "message/rfc822"); 225 | AddMapping(".ocx", "application/octet-stream"); 226 | AddMapping(".oda", "application/oda"); 227 | AddMapping(".odc", "text/x-ms-odc"); 228 | AddMapping(".ods", "application/oleobject"); 229 | AddMapping(".one", "application/onenote"); 230 | AddMapping(".onea", "application/onenote"); 231 | AddMapping(".onetoc", "application/onenote"); 232 | AddMapping(".onetoc2", "application/onenote"); 233 | AddMapping(".onetmp", "application/onenote"); 234 | AddMapping(".onepkg", "application/onenote"); 235 | AddMapping(".osdx", "application/opensearchdescription+xml"); 236 | AddMapping(".p10", "application/pkcs10"); 237 | AddMapping(".p12", "application/x-pkcs12"); 238 | AddMapping(".p7b", "application/x-pkcs7-certificates"); 239 | AddMapping(".p7c", "application/pkcs7-mime"); 240 | AddMapping(".p7m", "application/pkcs7-mime"); 241 | AddMapping(".p7r", "application/x-pkcs7-certreqresp"); 242 | AddMapping(".p7s", "application/pkcs7-signature"); 243 | AddMapping(".pbm", "image/x-portable-bitmap"); 244 | AddMapping(".pcx", "application/octet-stream"); 245 | AddMapping(".pcz", "application/octet-stream"); 246 | AddMapping(".pdf", "application/pdf"); 247 | AddMapping(".pfb", "application/octet-stream"); 248 | AddMapping(".pfm", "application/octet-stream"); 249 | AddMapping(".pfx", "application/x-pkcs12"); 250 | AddMapping(".pgm", "image/x-portable-graymap"); 251 | AddMapping(".pko", "application/vnd.ms-pki.pko"); 252 | AddMapping(".pma", "application/x-perfmon"); 253 | AddMapping(".pmc", "application/x-perfmon"); 254 | AddMapping(".pml", "application/x-perfmon"); 255 | AddMapping(".pmr", "application/x-perfmon"); 256 | AddMapping(".pmw", "application/x-perfmon"); 257 | AddMapping(".png", "image/png"); 258 | AddMapping(".pnm", "image/x-portable-anymap"); 259 | AddMapping(".pnz", "image/png"); 260 | AddMapping(".pot", "application/vnd.ms-powerpoint"); 261 | AddMapping(".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"); 262 | AddMapping(".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"); 263 | AddMapping(".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"); 264 | AddMapping(".ppm", "image/x-portable-pixmap"); 265 | AddMapping(".pps", "application/vnd.ms-powerpoint"); 266 | AddMapping(".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"); 267 | AddMapping(".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"); 268 | AddMapping(".ppt", "application/vnd.ms-powerpoint"); 269 | AddMapping(".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"); 270 | AddMapping(".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); 271 | AddMapping(".prf", "application/pics-rules"); 272 | AddMapping(".prm", "application/octet-stream"); 273 | AddMapping(".prx", "application/octet-stream"); 274 | AddMapping(".ps", "application/postscript"); 275 | AddMapping(".psd", "application/octet-stream"); 276 | AddMapping(".psm", "application/octet-stream"); 277 | AddMapping(".psp", "application/octet-stream"); 278 | AddMapping(".pub", "application/x-mspublisher"); 279 | AddMapping(".qt", "video/quicktime"); 280 | AddMapping(".qtl", "application/x-quicktimeplayer"); 281 | AddMapping(".qxd", "application/octet-stream"); 282 | AddMapping(".ra", "audio/x-pn-realaudio"); 283 | AddMapping(".ram", "audio/x-pn-realaudio"); 284 | AddMapping(".rar", "application/octet-stream"); 285 | AddMapping(".ras", "image/x-cmu-raster"); 286 | AddMapping(".rf", "image/vnd.rn-realflash"); 287 | AddMapping(".rgb", "image/x-rgb"); 288 | AddMapping(".rm", "application/vnd.rn-realmedia"); 289 | AddMapping(".rmi", "audio/mid"); 290 | AddMapping(".roff", "application/x-troff"); 291 | AddMapping(".rpm", "audio/x-pn-realaudio-plugin"); 292 | AddMapping(".rtf", "application/rtf"); 293 | AddMapping(".rtx", "text/richtext"); 294 | AddMapping(".scd", "application/x-msschedule"); 295 | AddMapping(".sct", "text/scriptlet"); 296 | AddMapping(".sea", "application/octet-stream"); 297 | AddMapping(".setpay", "application/set-payment-initiation"); 298 | AddMapping(".setreg", "application/set-registration-initiation"); 299 | AddMapping(".sgml", "text/sgml"); 300 | AddMapping(".sh", "application/x-sh"); 301 | AddMapping(".shar", "application/x-shar"); 302 | AddMapping(".sit", "application/x-stuffit"); 303 | AddMapping(".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"); 304 | AddMapping(".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"); 305 | AddMapping(".smd", "audio/x-smd"); 306 | AddMapping(".smi", "application/octet-stream"); 307 | AddMapping(".smx", "audio/x-smd"); 308 | AddMapping(".smz", "audio/x-smd"); 309 | AddMapping(".snd", "audio/basic"); 310 | AddMapping(".snp", "application/octet-stream"); 311 | AddMapping(".spc", "application/x-pkcs7-certificates"); 312 | AddMapping(".spl", "application/futuresplash"); 313 | AddMapping(".src", "application/x-wais-source"); 314 | AddMapping(".ssm", "application/streamingmedia"); 315 | AddMapping(".sst", "application/vnd.ms-pki.certstore"); 316 | AddMapping(".stl", "application/vnd.ms-pki.stl"); 317 | AddMapping(".sv4cpio", "application/x-sv4cpio"); 318 | AddMapping(".sv4crc", "application/x-sv4crc"); 319 | AddMapping(".swf", "application/x-shockwave-flash"); 320 | AddMapping(".t", "application/x-troff"); 321 | AddMapping(".tar", "application/x-tar"); 322 | AddMapping(".tcl", "application/x-tcl"); 323 | AddMapping(".tex", "application/x-tex"); 324 | AddMapping(".texi", "application/x-texinfo"); 325 | AddMapping(".texinfo", "application/x-texinfo"); 326 | AddMapping(".tgz", "application/x-compressed"); 327 | AddMapping(".thmx", "application/vnd.ms-officetheme"); 328 | AddMapping(".thn", "application/octet-stream"); 329 | AddMapping(".tif", "image/tiff"); 330 | AddMapping(".tiff", "image/tiff"); 331 | AddMapping(".toc", "application/octet-stream"); 332 | AddMapping(".tr", "application/x-troff"); 333 | AddMapping(".trm", "application/x-msterminal"); 334 | AddMapping(".tsv", "text/tab-separated-values"); 335 | AddMapping(".ttf", "application/octet-stream"); 336 | AddMapping(".txt", "text/plain"); 337 | AddMapping(".u32", "application/octet-stream"); 338 | AddMapping(".uls", "text/iuls"); 339 | AddMapping(".ustar", "application/x-ustar"); 340 | AddMapping(".vbs", "text/vbscript"); 341 | AddMapping(".vcf", "text/x-vcard"); 342 | AddMapping(".vcs", "text/plain"); 343 | AddMapping(".vdx", "application/vnd.ms-visio.viewer"); 344 | AddMapping(".vml", "text/xml"); 345 | AddMapping(".vsd", "application/vnd.visio"); 346 | AddMapping(".vss", "application/vnd.visio"); 347 | AddMapping(".vst", "application/vnd.visio"); 348 | AddMapping(".vsto", "application/x-ms-vsto"); 349 | AddMapping(".vsw", "application/vnd.visio"); 350 | AddMapping(".vsx", "application/vnd.visio"); 351 | AddMapping(".vtx", "application/vnd.visio"); 352 | AddMapping(".wav", "audio/wav"); 353 | AddMapping(".wax", "audio/x-ms-wax"); 354 | AddMapping(".wbmp", "image/vnd.wap.wbmp"); 355 | AddMapping(".wcm", "application/vnd.ms-works"); 356 | AddMapping(".wdb", "application/vnd.ms-works"); 357 | AddMapping(".wks", "application/vnd.ms-works"); 358 | AddMapping(".wm", "video/x-ms-wm"); 359 | AddMapping(".wma", "audio/x-ms-wma"); 360 | AddMapping(".wmd", "application/x-ms-wmd"); 361 | AddMapping(".wmf", "application/x-msmetafile"); 362 | AddMapping(".wml", "text/vnd.wap.wml"); 363 | AddMapping(".wmlc", "application/vnd.wap.wmlc"); 364 | AddMapping(".wmls", "text/vnd.wap.wmlscript"); 365 | AddMapping(".wmlsc", "application/vnd.wap.wmlscriptc"); 366 | AddMapping(".wmp", "video/x-ms-wmp"); 367 | AddMapping(".wmv", "video/x-ms-wmv"); 368 | AddMapping(".wmx", "video/x-ms-wmx"); 369 | AddMapping(".wmz", "application/x-ms-wmz"); 370 | AddMapping(".wps", "application/vnd.ms-works"); 371 | AddMapping(".wri", "application/x-mswrite"); 372 | AddMapping(".wrl", "x-world/x-vrml"); 373 | AddMapping(".wrz", "x-world/x-vrml"); 374 | AddMapping(".wsdl", "text/xml"); 375 | AddMapping(".wvx", "video/x-ms-wvx"); 376 | AddMapping(".x", "application/directx"); 377 | AddMapping(".xaf", "x-world/x-vrml"); 378 | AddMapping(".xaml", "application/xaml+xml"); 379 | AddMapping(".xap", "application/x-silverlight-app"); 380 | AddMapping(".xbap", "application/x-ms-xbap"); 381 | AddMapping(".xbm", "image/x-xbitmap"); 382 | AddMapping(".xdr", "text/plain"); 383 | AddMapping(".xla", "application/vnd.ms-excel"); 384 | AddMapping(".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"); 385 | AddMapping(".xlc", "application/vnd.ms-excel"); 386 | AddMapping(".xlm", "application/vnd.ms-excel"); 387 | AddMapping(".xls", "application/vnd.ms-excel"); 388 | AddMapping(".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"); 389 | AddMapping(".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"); 390 | AddMapping(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); 391 | AddMapping(".xlt", "application/vnd.ms-excel"); 392 | AddMapping(".xltm", "application/vnd.ms-excel.template.macroEnabled.12"); 393 | AddMapping(".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"); 394 | AddMapping(".xlw", "application/vnd.ms-excel"); 395 | AddMapping(".xml", "text/xml"); 396 | AddMapping(".xof", "x-world/x-vrml"); 397 | AddMapping(".xpm", "image/x-xpixmap"); 398 | AddMapping(".xps", "application/vnd.ms-xpsdocument"); 399 | AddMapping(".xsd", "text/xml"); 400 | AddMapping(".xsf", "text/xml"); 401 | AddMapping(".xsl", "text/xml"); 402 | AddMapping(".xslt", "text/xml"); 403 | AddMapping(".xsn", "application/octet-stream"); 404 | AddMapping(".xtp", "application/octet-stream"); 405 | AddMapping(".xwd", "image/x-xwindowdump"); 406 | AddMapping(".z", "application/x-compress"); 407 | AddMapping(".zip", "application/x-zip-compressed"); 408 | } 409 | } 410 | static readonly MimeMappingDictionaryBase _mappingDictionary = new MimeMappingDictionaryClassic(); 411 | 412 | public static string GetMimeMapping(string fileName) => _mappingDictionary.GetMimeMapping(fileName); 413 | } 414 | } -------------------------------------------------------------------------------- /src/HttpCore.Request.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace HttpLib 11 | { 12 | partial class HttpCore 13 | { 14 | /// 15 | /// 请求(不下载流) 16 | /// 17 | public ResultResponse requestNone() 18 | { 19 | var val = Go(0); 20 | return val.web; 21 | } 22 | 23 | /// 24 | /// 下载文件 25 | /// 26 | /// 保存目录 27 | /// 保存文件名称(为空自动获取) 28 | /// 返回保存文件路径,为空下载失败 29 | public string? download(string savePath, string? saveName = null) 30 | { 31 | var val = Go(3, savePath, saveName); 32 | return val.str; 33 | } 34 | 35 | /// 36 | /// 下载文件 37 | /// 38 | /// 保存目录 39 | /// 保存文件名称(为空自动获取) 40 | /// 返回保存文件路径,为空下载失败 41 | public string? download(out ResultResponse result, string savePath, string? saveName = null) 42 | { 43 | var val = Go(3, savePath, saveName); 44 | result = val.web; 45 | return val.str; 46 | } 47 | 48 | /// 49 | /// 请求(返回字节) 50 | /// 51 | /// 响应结果 52 | public byte[]? requestData(out ResultResponse result) 53 | { 54 | var val = Go(2); 55 | result = val.web; 56 | return val.data; 57 | } 58 | 59 | /// 60 | /// 请求(返回字节) 61 | /// 62 | public byte[]? requestData() 63 | { 64 | var val = Go(2); 65 | return val.data; 66 | } 67 | 68 | /// 69 | /// 请求(返回字符串) 70 | /// 71 | public string? request() 72 | { 73 | action_eventstream = null; 74 | var val = Go(1); 75 | return val.str; 76 | } 77 | 78 | /// 79 | /// 请求(返回字符串) 80 | /// 81 | /// 响应结果 82 | public string? request(out ResultResponse result) 83 | { 84 | action_eventstream = null; 85 | var val = Go(1); 86 | result = val.web; 87 | return val.str; 88 | } 89 | 90 | Action? action_eventstream = null; 91 | /// 92 | /// 流式请求(返回字符串) 93 | /// 94 | public string? request(Action eventstream) 95 | { 96 | action_eventstream = eventstream; 97 | var val = Go(1); 98 | return val.str; 99 | } 100 | 101 | /// 102 | /// 流式请求(返回字符串) 103 | /// 104 | /// 响应结果 105 | public string? request(Action eventstream, out ResultResponse result) 106 | { 107 | action_eventstream = eventstream; 108 | var val = Go(1); 109 | result = val.web; 110 | return val.str; 111 | } 112 | 113 | #region 对象 114 | 115 | HttpWebRequest? req; 116 | HttpWebResponse? response = null; 117 | 118 | #endregion 119 | 120 | TaskResult Go(int resultMode, string? savePath = null, string? saveName = null) 121 | { 122 | var r = GoCore(resultMode, savePath, saveName); 123 | req = null; 124 | response = null; 125 | return r; 126 | } 127 | TaskResult GoCore(int resultMode, string? savePath = null, string? saveName = null) 128 | { 129 | try 130 | { 131 | if (_cache != null) 132 | { 133 | if (File.Exists(_cache.file)) 134 | { 135 | if (_cache.t > 0) 136 | { 137 | var t = File.GetCreationTime(_cache.file); 138 | var elapsedTicks = DateTime.Now.Ticks - t.Ticks; 139 | var elapsedSpan = new TimeSpan(elapsedTicks); 140 | if (elapsedSpan.TotalMinutes < _cache.t) 141 | { 142 | switch (resultMode) 143 | { 144 | case 1: 145 | return new TaskResult(new ResultResponse(option.Url) 146 | { 147 | StatusCode = 200 148 | }, File.ReadAllText(_cache.file)); 149 | case 2: 150 | return new TaskResult(new ResultResponse(option.Url) 151 | { 152 | StatusCode = 200 153 | }, File.ReadAllBytes(_cache.file)); 154 | } 155 | } 156 | } 157 | else 158 | { 159 | switch (resultMode) 160 | { 161 | case 1: 162 | return new TaskResult(new ResultResponse(option.Url) 163 | { 164 | StatusCode = 200 165 | }, File.ReadAllText(_cache.file)); 166 | case 2: 167 | return new TaskResult(new ResultResponse(option.Url) 168 | { 169 | StatusCode = 200 170 | }, File.ReadAllBytes(_cache.file)); 171 | } 172 | } 173 | } 174 | } 175 | abort(); 176 | req = CreateRequest(); 177 | action_Request?.Invoke(req); 178 | using (response = (HttpWebResponse)req.GetResponse()) 179 | { 180 | var response_max = response.ContentLength; 181 | Max = response_max; 182 | _responseProgresMax?.Invoke(response_max); 183 | 184 | var _web = new ResultResponse(response); 185 | if (action_before != null && !action_before(response, _web)) return new TaskResult(_web); 186 | 187 | switch (resultMode) 188 | { 189 | case 3: 190 | using (var stream = response.GetResponseStream()) 191 | { 192 | DownStream(response_max, _web, stream, out var outfile, savePath, saveName); 193 | return new TaskResult(_web, outfile); 194 | } 195 | case 1: 196 | if (action_eventstream == null) 197 | { 198 | using (var stream = response.GetResponseStream()) 199 | { 200 | var data = DownStream(response_max, _web, stream, out _); 201 | if (data == null) return new TaskResult(_web); 202 | else 203 | { 204 | var encodings = option.encoding; 205 | if (encodings == null || option.autoencode) encodings = GetEncoding(response, data); 206 | 207 | var result = encodings.GetString(data); 208 | if (_cache != null) 209 | { 210 | _cache.path.CreateDirectory(); 211 | File.WriteAllText(_cache.file, result); 212 | } 213 | return new TaskResult(_web, result); 214 | } 215 | } 216 | } 217 | else 218 | { 219 | using (var reader = new StreamReader(response.GetResponseStream())) 220 | { 221 | while (!reader.EndOfStream) action_eventstream(reader.ReadLine()); 222 | return new TaskResult(_web); 223 | } 224 | } 225 | case 2: 226 | using (var stream = response.GetResponseStream()) 227 | { 228 | var result = DownStream(response_max, _web, stream, out _); 229 | if (result != null && _cache != null) 230 | { 231 | _cache.path.CreateDirectory(); 232 | File.WriteAllBytes(_cache.file, result); 233 | } 234 | return new TaskResult(_web, result); 235 | } 236 | case 0: 237 | default: 238 | using (var stream = response.GetResponseStream()) 239 | { 240 | return new TaskResult(_web); 241 | } 242 | } 243 | } 244 | } 245 | catch (Exception err) 246 | { 247 | if (err is WebException err_web && err_web.Response is HttpWebResponse response) 248 | { 249 | var web = new ResultResponse(response, err); 250 | Config.OnFail(this, web); 251 | action_fail?.Invoke(web); 252 | if (response.ContentLength > 0) 253 | { 254 | switch (resultMode) 255 | { 256 | case 1: 257 | using (var stream = response.GetResponseStream()) 258 | { 259 | var data = DownStream(response.ContentLength, web, stream, out _); 260 | if (data == null) return new TaskResult(web); 261 | else 262 | { 263 | var encodings = option.encoding; 264 | if (encodings == null || option.autoencode) encodings = GetEncoding(response, data); 265 | var result = encodings.GetString(data); 266 | return new TaskResult(web, result); 267 | } 268 | } 269 | } 270 | } 271 | return new TaskResult(web); 272 | } 273 | else 274 | { 275 | var web = new ResultResponse(option.Url, err); 276 | Config.OnFail(this, web); 277 | action_fail?.Invoke(web); 278 | return new TaskResult(web); 279 | } 280 | } 281 | } 282 | 283 | class TaskResult 284 | { 285 | public TaskResult(ResultResponse _web) 286 | { 287 | type = 0; 288 | web = _web; 289 | } 290 | public TaskResult(ResultResponse _web, byte[]? _data) 291 | { 292 | type = 2; 293 | web = _web; 294 | data = _data; 295 | } 296 | public TaskResult(ResultResponse _web, string? _str) 297 | { 298 | type = 1; 299 | web = _web; 300 | str = _str; 301 | } 302 | public ResultResponse web { get; set; } 303 | public int type { get; set; } 304 | public string? str { get; set; } 305 | public byte[]? data { get; set; } 306 | } 307 | 308 | #region 请求头-帮助 309 | 310 | string GetTFName(string strItem, string replace = "-") 311 | { 312 | string strItemTarget = ""; //目标字符串 313 | for (int i = 0; i < strItem.Length; i++) //strItem是原始字符串 314 | { 315 | string temp = strItem[i].ToString(); 316 | if (Regex.IsMatch(temp, "[A-Z]")) temp = replace + temp.ToLower(); 317 | strItemTarget += temp; 318 | } 319 | return strItemTarget; 320 | } 321 | void SetHeader(out bool isContentType, HttpWebRequest req, List headers, CookieContainer cookies) 322 | { 323 | isContentType = true; 324 | foreach (var it in headers) 325 | { 326 | if (it.Value != null) 327 | { 328 | switch (it.Key.ToLower()) 329 | { 330 | case "host": 331 | req.Host = it.Value; 332 | break; 333 | case "accept": 334 | req.Accept = it.Value; 335 | break; 336 | case "user-agent": 337 | req.UserAgent = it.Value; 338 | break; 339 | case "referer": 340 | req.Referer = it.Value; 341 | break; 342 | case "content-type": 343 | isContentType = false; 344 | req.ContentType = it.Value; 345 | break; 346 | case "cookie": 347 | if (it.Value != null) 348 | { 349 | if (it.Value.IndexOf(";") >= 0) 350 | { 351 | var Cookies = it.Value.Split(';'); 352 | foreach (string cook in Cookies) 353 | { 354 | if (string.IsNullOrEmpty(cook)) continue; 355 | if (cook.IndexOf("expires") > 0) continue; 356 | cookies.SetCookies(req.RequestUri, cook); 357 | } 358 | } 359 | else cookies.SetCookies(req.RequestUri, it.Value); 360 | } 361 | break; 362 | default: 363 | req.Headers.Add(it.Key, it.Value); 364 | break; 365 | } 366 | } 367 | } 368 | } 369 | 370 | #endregion 371 | 372 | #region 响应流-帮助 373 | 374 | /// 375 | /// 获取域名IP 376 | /// 377 | public string? IP { get => option.IP; } 378 | 379 | public long Val = 0, Max = 0; 380 | 381 | byte[]? DownStream(long response_max, ResultResponse _web, Stream stream, out string? outfile, string? savePath = null, string? saveName = null) 382 | { 383 | Max = response_max; 384 | long response_val = 0; 385 | Val = response_val; 386 | Stream stream_read; 387 | if (savePath == null) 388 | { 389 | outfile = null; 390 | stream_read = new MemoryStream(); 391 | } 392 | else 393 | { 394 | savePath = savePath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; 395 | savePath.CreateDirectory(); 396 | saveName ??= option.FileName(_web); 397 | outfile = savePath + saveName; 398 | stream_read = new FileStream(outfile, FileMode.Create); 399 | } 400 | 401 | using (stream_read) 402 | { 403 | byte[] buffer = new byte[Config.CacheSize]; 404 | if (_responseProgres == null) 405 | { 406 | int rsize = 0; 407 | while ((rsize = stream.Read(buffer, 0, buffer.Length)) > 0) 408 | { 409 | stream_read.Write(buffer, 0, rsize); 410 | response_val += rsize; 411 | Val = response_val; 412 | } 413 | } 414 | else 415 | { 416 | _responseProgres(response_val, response_max); 417 | int rsize = 0; 418 | while ((rsize = stream.Read(buffer, 0, buffer.Length)) > 0) 419 | { 420 | stream_read.Write(buffer, 0, rsize); 421 | response_val += rsize; 422 | _responseProgres(response_val, response_max); 423 | Val = response_val; 424 | } 425 | } 426 | _web.OriginalSize = _web.Size = response_val; 427 | if (response_val > 0) 428 | { 429 | if (savePath == null) return GetByStream(_web, stream_read); 430 | else return null; 431 | } 432 | else 433 | { 434 | stream_read.Close(); 435 | if (outfile != null) File.Delete(outfile); 436 | outfile = null; 437 | return null; 438 | } 439 | } 440 | } 441 | 442 | byte[] GetByStream(ResultResponse _web, Stream stream) 443 | { 444 | stream.Position = 0; 445 | using (var ms = new MemoryStream()) 446 | { 447 | stream.CopyTo(ms); 448 | ms.Position = stream.Position = 0; 449 | string fileclass = ""; 450 | try 451 | { 452 | using (var r = new BinaryReader(stream)) 453 | { 454 | byte buffer = r.ReadByte(); 455 | fileclass = buffer.ToString(); 456 | buffer = r.ReadByte(); 457 | fileclass += buffer.ToString(); 458 | } 459 | } 460 | catch { } 461 | ms.Position = 0; 462 | if (fileclass == "31139") 463 | { 464 | try 465 | { 466 | byte[] data = Decompress(ms); 467 | _web.Size = data.Length; 468 | return data; 469 | } 470 | catch { } 471 | } 472 | ms.Position = 0; 473 | return ms.ToArray(); 474 | } 475 | } 476 | 477 | Encoding GetEncoding(HttpWebResponse response, byte[] data) 478 | { 479 | var meta = Regex.Match(Encoding.Default.GetString(data), " 0) 481 | { 482 | var code = meta.Groups[1].Value.ToLower().Trim(); 483 | if (code.Length > 2) 484 | { 485 | try 486 | { 487 | return Encoding.GetEncoding(code.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); 488 | } 489 | catch { } 490 | } 491 | } 492 | try 493 | { 494 | if (string.IsNullOrEmpty(response.CharacterSet)) return Encoding.UTF8; 495 | else return Encoding.GetEncoding(response.CharacterSet); 496 | } 497 | catch { return Encoding.UTF8; } 498 | } 499 | 500 | /// 501 | /// 解压字符串 502 | /// 503 | /// 504 | /// 505 | byte[] Decompress(MemoryStream ms) 506 | { 507 | using (var zip = new GZipStream(ms, CompressionMode.Decompress)) 508 | { 509 | using (var msreader = new MemoryStream()) 510 | { 511 | var buffer = new byte[Config.CacheSize]; 512 | while (true) 513 | { 514 | var reader = zip.Read(buffer, 0, buffer.Length); 515 | if (reader <= 0) break; 516 | msreader.Write(buffer, 0, reader); 517 | } 518 | msreader.Position = 0; 519 | buffer = msreader.ToArray(); 520 | return buffer; 521 | } 522 | } 523 | } 524 | 525 | internal HttpWebRequest CreateRequest() 526 | { 527 | var uri = option.Url; 528 | 529 | var cookies = new CookieContainer(); 530 | 531 | #region SSL 532 | 533 | ServicePointManager.ServerCertificateValidationCallback ??= (_s, certificate, chain, sslPolicyErrors) => { return true; }; 534 | 535 | #endregion 536 | 537 | var req = (HttpWebRequest)WebRequest.Create(uri); 538 | 539 | if (option.proxy != null) req.Proxy = option.proxy; 540 | else req.Proxy = Config._proxy; 541 | req.Method = option.method.ToString().ToUpper(); 542 | req.AutomaticDecompression = Config.DecompressionMethod; 543 | req.CookieContainer = cookies; 544 | req.Host = uri.Host; 545 | if (_range != null) req.AddRange(_range[0], _range[1]); 546 | 547 | if (option.redirect) req.AllowAutoRedirect = option.redirect; 548 | else req.AllowAutoRedirect = Config.Redirect; 549 | var encoding = option.encoding ?? Encoding.UTF8; 550 | if (option.timeout > 0) req.Timeout = option.timeout; 551 | 552 | req.Credentials = CredentialCache.DefaultCredentials; 553 | req.UserAgent = Config.UserAgent; 554 | 555 | bool setContentType = true; 556 | if (Config.headers != null && Config.headers.Count > 0) SetHeader(out setContentType, req, Config.headers, cookies); 557 | if (option.header != null && option.header.Count > 0) SetHeader(out setContentType, req, option.header, cookies); 558 | 559 | #region 准备上传数据 560 | 561 | if (option.method != HttpMethod.Get && option.method != HttpMethod.Head) 562 | { 563 | if (!string.IsNullOrEmpty(option.datastr)) 564 | { 565 | if (setContentType) req.ContentType = "text/plain"; 566 | 567 | var bs = encoding.GetBytes(option.datastr); 568 | req.ContentLength = bs.Length; 569 | using (var stream = req.GetRequestStream()) 570 | { 571 | stream.Write(bs, 0, bs.Length); 572 | } 573 | } 574 | else if (option.file != null && option.file.Count > 0) 575 | { 576 | string boundary = 8.RandomString(); 577 | req.ContentType = "multipart/form-data; boundary=" + boundary; 578 | 579 | byte[] startbyteOnes = encoding.GetBytes("--" + boundary + "\r\n"), 580 | startbytes = encoding.GetBytes("\r\n--" + boundary + "\r\n"), 581 | endbytes = encoding.GetBytes("\r\n--" + boundary + "--\r\n"); 582 | 583 | var writeDATA = new List((option.data == null ? 0 : option.data.Count * 2) + option.file.Count * 3 + 1); 584 | 585 | int countB = 0; 586 | 587 | #region 规划文件大小 588 | 589 | if (option.data != null && option.data.Count > 0) 590 | { 591 | foreach (var it in option.data) 592 | { 593 | if (it.Value == null) continue; 594 | if (countB == 0) writeDATA.Add(startbyteOnes); 595 | else writeDATA.Add(startbytes); 596 | countB++; 597 | string separator = string.Format("Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}", it.Key, it.Value); 598 | writeDATA.Add(encoding.GetBytes(separator)); 599 | } 600 | } 601 | 602 | foreach (var file in option.file) 603 | { 604 | if (countB == 0) writeDATA.Add(startbyteOnes); 605 | else writeDATA.Add(startbytes); 606 | countB++; 607 | string separator = string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n", file.Name, file.FileName, file.ContentType); 608 | writeDATA.Add(encoding.GetBytes(separator)); 609 | writeDATA.Add(null); 610 | } 611 | writeDATA.Add(endbytes); 612 | 613 | #endregion 614 | 615 | long size = writeDATA.Sum(it => it != null ? it.Length : 0) + option.file.Sum(it => it.Size); 616 | req.ContentLength = size; 617 | 618 | #region 注入进度 619 | 620 | _requestProgresMax?.Invoke(size); 621 | if (_requestProgres == null) 622 | { 623 | using (var stream = req.GetRequestStream()) 624 | { 625 | int fileIndex = 0; 626 | foreach (var it in writeDATA) 627 | { 628 | if (it == null) 629 | { 630 | var file = option.file[fileIndex]; 631 | fileIndex++; 632 | using (file.Stream) 633 | { 634 | file.Stream.Position = 0; 635 | int bytesRead = 0; 636 | byte[] buffer = new byte[Config.CacheSize]; 637 | while ((bytesRead = file.Stream.Read(buffer, 0, buffer.Length)) > 0) 638 | { 639 | stream.Write(buffer, 0, bytesRead); 640 | } 641 | } 642 | } 643 | else stream.Write(it, 0, it.Length); 644 | } 645 | } 646 | } 647 | else 648 | { 649 | long request_value = 0; 650 | _requestProgres(request_value, size); 651 | using (var stream = req.GetRequestStream()) 652 | { 653 | int fileIndex = 0; 654 | foreach (var it in writeDATA) 655 | { 656 | if (it == null) 657 | { 658 | var file = option.file[fileIndex]; 659 | fileIndex++; 660 | using (file.Stream) 661 | { 662 | file.Stream.Position = 0; 663 | int bytesRead = 0; 664 | byte[] buffer = new byte[Config.CacheSize]; 665 | while ((bytesRead = file.Stream.Read(buffer, 0, buffer.Length)) > 0) 666 | { 667 | stream.Write(buffer, 0, bytesRead); 668 | request_value += bytesRead; 669 | _requestProgres(request_value, size); 670 | } 671 | } 672 | } 673 | else 674 | { 675 | stream.Write(it, 0, it.Length); 676 | request_value += it.Length; 677 | _requestProgres(request_value, size); 678 | } 679 | } 680 | } 681 | } 682 | 683 | #endregion 684 | } 685 | else if (option.data != null && option.data.Count > 0) 686 | { 687 | if (setContentType) req.ContentType = "application/x-www-form-urlencoded"; 688 | 689 | var param_ = new List(option.data.Count); 690 | foreach (var it in option.data) 691 | { 692 | if (it.Value == null) continue; 693 | param_.Add(it.ToStringEscape()); 694 | } 695 | 696 | var bs = encoding.GetBytes(string.Join("&", param_)); 697 | req.ContentLength = bs.Length; 698 | using (var stream = req.GetRequestStream()) 699 | { 700 | stream.Write(bs, 0, bs.Length); 701 | } 702 | } 703 | } 704 | 705 | #endregion 706 | 707 | return req; 708 | } 709 | 710 | #endregion 711 | } 712 | } --------------------------------------------------------------------------------