├── .gitattributes ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── dotnet-core-test.yml │ └── publish_nuget.yml ├── .gitignore ├── LICENSE.md ├── PyLibSharp.Common ├── ConsoleEx.cs ├── PyLibSharp.Common.csproj ├── PyLibSharp.Common.xml └── Range.cs ├── PyLibSharp.Requests ├── PyLibSharp.Requests.csproj ├── PyLibSharp.Requests.xml ├── Requests.cs ├── Utils.cs └── WebRequestExtension.cs ├── PyLibSharp.Win32Friendly ├── PyLibSharp.Win32Friendly.csproj ├── PyLibSharp.Win32Friendly.xml └── WindowApi.cs ├── PyLibSharp.sln ├── README.MD ├── TestCore ├── Program.cs └── TestCore.csproj └── TestProject └── TestProject ├── App.config ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── TestFramework.csproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '29 5 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: windows-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Setup .NET Core 56 | uses: actions/setup-dotnet@v1 57 | with: 58 | dotnet-version: 5.0.100 59 | - name: Setup MSBuild 60 | uses: microsoft/setup-msbuild@v1 61 | - name: Install dependencies 62 | run: dotnet restore 63 | - name: Build 64 | run: dotnet build --configuration Release --no-restore 65 | 66 | # ℹ️ Command-line programs to run using the OS shell. 67 | # 📚 https://git.io/JvXDl 68 | 69 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 70 | # and modify them (or add more) to build your code if your project 71 | # uses a compiled language 72 | 73 | #- run: | 74 | # make bootstrap 75 | # make release 76 | 77 | - name: Perform CodeQL Analysis 78 | uses: github/codeql-action/analyze@v1 79 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core-test.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 5.0.100 20 | - name: Install dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Test 25 | run: dotnet test --no-restore --verbosity normal 26 | -------------------------------------------------------------------------------- /.github/workflows/publish_nuget.yml: -------------------------------------------------------------------------------- 1 | name: publish PyLibSharp to nuget 2 | on: 3 | push: 4 | branches: 5 | - master # Default release branch 6 | jobs: 7 | publish: 8 | name: build, pack & publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Setup dotnet 14 | uses: actions/setup-dotnet@v1 15 | with: 16 | dotnet-version: 5.0.100 17 | 18 | # Publish 19 | - name: publish on version change 20 | id: publish_nuget 21 | uses: brandedoutcast/publish-nuget@v2 22 | with: 23 | # Filepath of the project to be packaged, relative to root of repository 24 | PROJECT_FILE_PATH: PyLibSharp/PyLibSharp.csproj 25 | 26 | # NuGet package id, used for version detection & defaults to project name 27 | PACKAGE_NAME: PyLibSharp 28 | 29 | # Filepath with version info, relative to root of repository & defaults to PROJECT_FILE_PATH 30 | VERSION_FILE_PATH: PyLibSharp/PyLibSharp.csproj 31 | 32 | # Regex pattern to extract version info in a capturing group 33 | VERSION_REGEX: ^\s*(.*)<\/Version>\s*$ 34 | 35 | # Useful with external providers like Nerdbank.GitVersioning, ignores VERSION_FILE_PATH & VERSION_REGEX 36 | # VERSION_STATIC: 1.0.0 37 | 38 | # Flag to toggle git tagging, enabled by default 39 | TAG_COMMIT: true 40 | 41 | # Format of the git tag, [*] gets replaced with actual version 42 | # TAG_FORMAT: v* 43 | 44 | # API key to authenticate with NuGet server 45 | NUGET_KEY: ${{secrets.NUGET_API_KEY}} 46 | 47 | # NuGet server uri hosting the packages, defaults to https://api.nuget.org 48 | # NUGET_SOURCE: https://api.nuget.org 49 | 50 | # Flag to toggle pushing symbols along with nuget package to the server, disabled by default 51 | INCLUDE_SYMBOLS: false 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | *- Backup*.rdl 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush personal settings 299 | .cr/personal 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | 336 | # Local History for Visual Studio 337 | .localhistory/ 338 | 339 | # BeatPulse healthcheck temp database 340 | healthchecksdb -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 XiaoHe321 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 | -------------------------------------------------------------------------------- /PyLibSharp.Common/ConsoleEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace PyLibSharp.Common 7 | { 8 | public static class ConsoleEx 9 | { 10 | private static bool HasToStringMethod(this object obj) 11 | => obj.GetType() 12 | .GetMethods(BindingFlags.Public 13 | | BindingFlags.DeclaredOnly | 14 | BindingFlags.Instance) 15 | .Where(i => i.DeclaringType != typeof(object)) 16 | .Where(i => !i.GetParameters().Any()) 17 | .Any(i => i.Name == "ToString"); 18 | 19 | private static bool PrintWhenHasToString(this object obj) 20 | { 21 | if (obj.HasToStringMethod()) 22 | { 23 | Console.Write(obj.ToString()); 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | public static void Print(object obj) 31 | { 32 | //如果对象已显式实现ToString,则直接调用 33 | if (obj.PrintWhenHasToString()) return; 34 | 35 | if (obj is IEnumerable lst) 36 | { 37 | Console.Write("{"); 38 | IEnumerator enumerator = lst.GetEnumerator(); 39 | object previous = null; 40 | while (enumerator.MoveNext()) 41 | { 42 | if (previous != null) 43 | { 44 | Print(previous); 45 | Console.Write(","); 46 | } 47 | 48 | previous = enumerator.Current; 49 | } 50 | 51 | Print(previous); 52 | Console.Write("}"); 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /PyLibSharp.Common/PyLibSharp.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | true 6 | true 7 | xiaohe321 8 | xiaohe321 9 | 使用 .Net 根据部分 Python 库的设计思想来封装一些常用功能 10 | 公共库部分 11 | 12 | Use. Net to encapsulate some common functions according to the design idea of some Python libraries 13 | LICENSE.md 14 | https://github.com/xh321/PyLibSharp 15 | 修复没有注释 16 | zh-CN 17 | 1.0.1 18 | 19 | 20 | 21 | Auto 22 | C:\Users\xiaohe321\OneDrive\Programming\CSharp\PyLib#\PyLibSharp\PyLibSharp.Common\PyLibSharp.Common.xml 23 | 24 | 25 | 26 | 27 | True 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /PyLibSharp.Common/PyLibSharp.Common.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyLibSharp.Common 5 | 6 | 7 | 8 | 9 | 排序方式 10 | 11 | 12 | 13 | 14 | 升序 15 | 16 | 17 | 18 | 19 | 降序 20 | 21 | 22 | 23 | 24 | 可排序,不可重复的整数范围 25 | 26 | 27 | 28 | 29 | 范围内的数据 30 | 31 | 32 | 33 | 34 | 默认排序方式 35 | 36 | 37 | 38 | 39 | 创建一个范围 40 | 41 | 开始的数字 42 | 终止的数字 43 | 步长 44 | 45 | 46 | 47 | 48 | 用一个可迭代序列来初始化一个范围 49 | 50 | 51 | 52 | 53 | 54 | 用一个整数列表来初始化一个范围 55 | 56 | 57 | 58 | 59 | 60 | 针对范围中每一个数都乘一个数 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 针对范围中每一个数都除一个数(若有小数将直接抹掉) 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 获取指定下标位置的数据 77 | 78 | 下标号(从0开始) 79 | 80 | 81 | 82 | 83 | 获取范围中,从startIndex下标到endIndex下标的数据,默认步长为1,但可调 84 | 85 | 开始的下标(从0开始) 86 | 结束的下标(从0开始) 87 | 步长 88 | 89 | 90 | 91 | 92 | 构造函数 93 | 94 | 从0或1开始生成多少个数字 95 | 96 | 是否从0开始。 97 | 例如,假设count=7,且此参数为true: 98 | 范围是:0~6 99 | 否则: 100 | 范围是:1~7 101 | 102 | 排序方式 103 | 104 | 105 | 106 | 构造函数 107 | 构造一个[start,stop]范围的 108 | 109 | List{int} 110 | 111 | 112 | 范围开始的数字(包含这个数字) 113 | 范围终止的数(包含这个数字) 114 | 范围增加步长(可为负数) 115 | 116 | 117 | 118 | 范围是否包含某一个数字 119 | 120 | 121 | 122 | 123 | 124 | 125 | 某一个范围是否在该范围之内(交集是否为本身) 126 | 127 | 128 | 129 | 130 | 131 | 132 | 范围是否包含不某一个数字 133 | 134 | 135 | 136 | 137 | 138 | 139 | 范围是否不包含某一个范围(交集是否为空) 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /PyLibSharp.Common/Range.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace PyLibSharp.Common 7 | { 8 | /// 9 | /// 排序方式 10 | /// 11 | public enum OrderBy 12 | { 13 | /// 14 | /// 升序 15 | /// 16 | Asc = 0, 17 | 18 | /// 19 | /// 降序 20 | /// 21 | Desc = 1 22 | } 23 | 24 | public class StepEqualsZeroException : Exception 25 | { 26 | public string error = "步长不能为0"; 27 | private Exception innerException; 28 | 29 | public StepEqualsZeroException() 30 | { 31 | } 32 | 33 | public StepEqualsZeroException(string msg) : base(msg) 34 | { 35 | this.error = msg; 36 | } 37 | 38 | public StepEqualsZeroException(string msg, Exception innerException) : base(msg, innerException) 39 | { 40 | this.innerException = innerException; 41 | error = msg; 42 | } 43 | 44 | public string GetError() 45 | { 46 | return error; 47 | } 48 | } 49 | 50 | /// 51 | /// 可排序,不可重复的整数范围 52 | /// 53 | public class Range : IEnumerable, IEquatable 54 | { 55 | private List rangeData; 56 | 57 | /// 58 | /// 范围内的数据 59 | /// 60 | public List data 61 | { 62 | get => rangeData; 63 | set => rangeData = value.Distinct().ToList(); 64 | } 65 | 66 | /// 67 | /// 默认排序方式 68 | /// 69 | public OrderBy orderBy = OrderBy.Asc; 70 | 71 | /// 72 | /// 创建一个范围 73 | /// 74 | /// 开始的数字 75 | /// 终止的数字 76 | /// 步长 77 | /// 78 | public static Range range(int start, int stop, int step = 1) 79 | => new Range(start, stop, step); 80 | 81 | /// 82 | /// 用一个可迭代序列来初始化一个范围 83 | /// 84 | /// 85 | public Range(IEnumerable data) 86 | { 87 | rangeData = data.Distinct().ToList(); 88 | } 89 | 90 | /// 91 | /// 用一个整数列表来初始化一个范围 92 | /// 93 | /// 94 | public Range(List data) 95 | { 96 | rangeData = data.Distinct().ToList(); 97 | } 98 | 99 | public static implicit operator List(Range range) 100 | { 101 | return range.rangeData; 102 | } 103 | 104 | public static implicit operator Range(List intList) 105 | { 106 | return new Range(intList); 107 | } 108 | 109 | public static Range operator +(Range a, Range b) 110 | => a.AddRange(b); 111 | 112 | public static Range operator -(Range a, Range b) 113 | => a.RemoveRange(b); 114 | 115 | public static Range operator +(Range a, int b) 116 | => a.Add(b); 117 | 118 | public static Range operator -(Range a, int b) 119 | => a.Remove(b); 120 | 121 | 122 | /// 123 | /// 针对范围中每一个数都乘一个数 124 | /// 125 | /// 126 | /// 127 | /// 128 | public static Range operator *(Range a, int b) 129 | { 130 | for (var i = 0; i < a.rangeData.Count; i++) 131 | { 132 | if (!a.data.Contains(a[i] * b)) 133 | a[i] *= b; 134 | } 135 | 136 | return a; 137 | } 138 | 139 | /// 140 | /// 针对范围中每一个数都除一个数(若有小数将直接抹掉) 141 | /// 142 | /// 143 | /// 144 | /// 145 | public static Range operator /(Range a, int b) 146 | { 147 | for (var i = 0; i < a.rangeData.Count; i++) 148 | { 149 | if (!a.data.Contains(a[i] / b)) 150 | a[i] /= b; 151 | } 152 | 153 | return a; 154 | } 155 | 156 | /// 157 | /// 获取指定下标位置的数据 158 | /// 159 | /// 下标号(从0开始) 160 | /// 161 | public int this[int index] 162 | { 163 | get => rangeData[index]; 164 | set 165 | { 166 | //不能出现重复项 167 | if (rangeData.All(i => i != value)) 168 | { 169 | rangeData[index] = value; 170 | } 171 | } 172 | } 173 | 174 | /// 175 | /// 获取范围中,从startIndex下标到endIndex下标的数据,默认步长为1,但可调 176 | /// 177 | /// 开始的下标(从0开始) 178 | /// 结束的下标(从0开始) 179 | /// 步长 180 | /// 181 | public Range this[int startIndex, int endIndex, int step = 1] 182 | { 183 | get 184 | { 185 | Range ret = new Range(); 186 | for (var i = Math.Min(startIndex, endIndex); i <= Math.Max(startIndex, endIndex); i += Math.Abs(step)) 187 | { 188 | ret.Add(rangeData[i]); 189 | } 190 | 191 | return ret; 192 | } 193 | } 194 | 195 | public Range() 196 | { 197 | rangeData = new List(); 198 | } 199 | 200 | /// 201 | /// 构造函数 202 | /// 203 | /// 从0或1开始生成多少个数字 204 | /// 205 | /// 是否从0开始。 206 | /// 例如,假设count=7,且此参数为true: 207 | /// 范围是:0~6 208 | /// 否则: 209 | /// 范围是:1~7 210 | /// 211 | /// 排序方式 212 | public Range(int count, bool startAtZero = true, OrderBy orderBy = OrderBy.Asc) 213 | { 214 | var data = new List(); 215 | if (count <= 0) 216 | { 217 | rangeData = data; 218 | return; 219 | } 220 | 221 | for (int i = startAtZero ? 0 : 1; (startAtZero ? i < count : i <= count); i++) 222 | { 223 | data.Add(i); 224 | } 225 | 226 | rangeData = data; 227 | } 228 | 229 | /// 230 | /// 构造函数 231 | /// 构造一个[start,stop]范围的 232 | /// 233 | /// List{int} 234 | /// 235 | /// 236 | /// 范围开始的数字(包含这个数字) 237 | /// 范围终止的数(包含这个数字) 238 | /// 范围增加步长(可为负数) 239 | public Range(int start, int stop, int step = 1) 240 | { 241 | var data = new List(); 242 | if (step == 0) throw new StepEqualsZeroException(); 243 | 244 | if (start > stop || step < 0) 245 | { 246 | orderBy = OrderBy.Desc; 247 | } 248 | 249 | for (int i = Math.Min(start, stop); i <= Math.Max(start, stop); i += Math.Abs(step)) 250 | { 251 | data.Add(i); 252 | } 253 | 254 | rangeData = data; 255 | Sort(); 256 | } 257 | 258 | public Range AddRange(Range toAdd) 259 | { 260 | foreach (int item in toAdd.ToList()) 261 | { 262 | if (!rangeData.Contains(item)) 263 | rangeData.Add(item); 264 | } 265 | 266 | return (rangeData); 267 | } 268 | 269 | public Range RemoveRange(Range toRemove) 270 | { 271 | foreach (int item in toRemove.ToList()) 272 | { 273 | if (rangeData.Contains(item)) 274 | rangeData.Remove(item); 275 | } 276 | 277 | return (rangeData); 278 | } 279 | 280 | public Range Add(int toAdd) 281 | { 282 | if (!rangeData.Contains(toAdd)) 283 | rangeData.Add(toAdd); 284 | 285 | return (rangeData); 286 | } 287 | 288 | public Range Remove(int toRemove) 289 | { 290 | if (rangeData.Contains(toRemove)) 291 | rangeData.Remove(toRemove); 292 | 293 | return (rangeData); 294 | } 295 | 296 | public Range Sort() 297 | { 298 | rangeData.Sort(); 299 | if (orderBy == OrderBy.Desc) 300 | { 301 | rangeData.Reverse(); 302 | } 303 | 304 | return (rangeData); 305 | } 306 | 307 | public Range SortBy(OrderBy order) 308 | { 309 | rangeData.Sort(); 310 | if (order == OrderBy.Desc) 311 | { 312 | rangeData.Reverse(); 313 | } 314 | 315 | return (rangeData); 316 | } 317 | 318 | /// 319 | /// 范围是否包含某一个数字 320 | /// 321 | /// 322 | /// 323 | public bool IsIn(int i) 324 | => rangeData.Contains(i); 325 | 326 | /// 327 | /// 某一个范围是否在该范围之内(交集是否为本身) 328 | /// 329 | /// 330 | /// 331 | public bool IsIn(Range range) 332 | { 333 | if (rangeData.Count < range.Count()) 334 | { 335 | return false; 336 | } 337 | 338 | foreach (int item in range.ToList()) 339 | { 340 | if (!rangeData.Contains(item)) 341 | return false; 342 | } 343 | 344 | return true; 345 | } 346 | 347 | /// 348 | /// 范围是否包含不某一个数字 349 | /// 350 | /// 351 | /// 352 | public bool IsOut(int i) 353 | => !((Range) rangeData).IsIn(i); 354 | 355 | /// 356 | /// 范围是否不包含某一个范围(交集是否为空) 357 | /// 358 | /// 359 | /// 360 | public bool IsOut(Range i) 361 | => !((Range) rangeData).IsIn(i); 362 | 363 | public List AsList() 364 | => rangeData; 365 | 366 | public List ToList() 367 | => rangeData; 368 | 369 | public Range CopyOne() 370 | { 371 | var ret = new Range(); 372 | return ret.AddRange(this); 373 | } 374 | 375 | public IEnumerator GetEnumerator() 376 | { 377 | return ((IEnumerable) rangeData).GetEnumerator(); 378 | } 379 | 380 | IEnumerator IEnumerable.GetEnumerator() 381 | { 382 | return ((IEnumerable) rangeData).GetEnumerator(); 383 | } 384 | 385 | public override bool Equals(object? value) 386 | { 387 | if (!(value is Range)) 388 | { 389 | return false; 390 | } 391 | 392 | return (Range) value == this; 393 | } 394 | 395 | public override int GetHashCode() 396 | { 397 | return HashCode.Combine(rangeData); 398 | } 399 | 400 | public static bool operator ==(Range one, Range two) 401 | => one?.Equals(two) ?? false; 402 | 403 | public static bool operator !=(Range one, Range two) 404 | => !(one == two); 405 | 406 | public bool Equals(Range other) 407 | { 408 | if (this.Count() != other.Count()) return false; 409 | 410 | var a = CopyOne().SortBy(OrderBy.Asc); 411 | var b = other?.CopyOne().SortBy(OrderBy.Asc); 412 | for (int i = 0; i < a?.Count(); i++) 413 | { 414 | if (a[i] != b?[i]) 415 | { 416 | return false; 417 | } 418 | } 419 | 420 | return true; 421 | } 422 | } 423 | } -------------------------------------------------------------------------------- /PyLibSharp.Requests/PyLibSharp.Requests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 使用 .Net 根据部分 Python 库的设计思想来封装一些常用功能 7 | Requests库部分 8 | 9 | Use. Net to encapsulate some common functions according to the design idea of some Python libraries 10 | xiaohe321 11 | 1.2.10 12 | 13 | LICENSE.md 14 | https://github.com/xh321/PyLibSharp 15 | 16 | true 17 | zh-CN 18 | [Requests]解决若HTTP返回头部不包含ContentEncoding情况下报null错误的问题 19 | request network 20 | 1.2.10.0 21 | 1.2.10.0 22 | 23 | 24 | 25 | C:\Users\xiaohe321\OneDrive\Programming\CSharp\PyLib#\PyLibSharp\PyLibSharp.Requests\PyLibSharp.Requests.xml 26 | Auto 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | True 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /PyLibSharp.Requests/PyLibSharp.Requests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyLibSharp.Requests 5 | 6 | 7 | 8 | 9 | 设置 HTTP 请求的基本参数。 10 | 11 | 12 | 13 | 14 | 设置 HTTP 请求中的默认头部。 15 | 若此参数并不包含你想要设置的头部,请改用 参数去设置。 16 | 17 | 18 | 19 | 20 | 设置传输过程中使用的代理。 21 | 22 | 23 | 24 | 25 | 设置传输时采用的 Cookie 容器。 26 | 27 | 28 | 29 | 30 | 当要设置 中的参数所不包含的默认头部或任何自定义头部时,请使用本参数设置。 31 | 32 | 33 | 34 | 35 | 设置要传递给服务器的请求参数。 36 | 当 HTTP 动作为 时,将智能附加至 URL 。 37 | 其他 HTTP 动作时,将写入传输流,此时请设置 以明确如何传输数据。 38 | 其他 HTTP 动作时,此参数优先级不如 高,若设置了 参数,此参数将被忽略。 39 | 40 | 41 | 42 | 43 | (GET 动作中此参数将被忽略) 44 | 设置要 Post 至服务器的 HTTP 内容(如有必要需手动设置 的 MediaType 字符串,如嫌麻烦请继续使用 参数,这些参数会自动设置 ContentType )。 45 | 若同时设置了 ,此参数优先级最高,将使用本参数指定的方式进行 Post ; 46 | 在未设置 参数时,将自动修改 的值。 47 | 48 | 49 | 50 | 51 | (GET 动作中此参数将被忽略) 52 | 建议改用 参数传递 HttpContent; 53 | 设置要 Post 至服务器的原始字节序列。 54 | 若同时设置了 参数优先级最高,将使用 指定的方式进行 Post ; 55 | 在未设置 参数时,将自动修改 的值。 56 | 57 | 58 | 59 | 60 | (GET 动作中此参数将被忽略) 61 | 建议改用 参数传递 HttpContent; 62 | 设置要 Post 至服务器的 Json 数据。 63 | 若同时设置了 参数优先级最高,将使用 指定的方式进行 Post ; 64 | 在未设置 参数时,将自动修改 的值。 65 | 66 | 67 | 68 | 69 | (GET 动作中此参数将被忽略) 70 | 建议改用 参数传递 HttpContent; 71 | 设置要 Post 到服务器的 MultiPart 数据。 72 | 若同时设置了 参数优先级最高,将使用 指定的方式进行 Post ; 73 | 在未设置 参数时,将自动修改 的值。 74 | 75 | 76 | 77 | 78 | (GET 动作中此参数将被忽略) 79 | 若 Post 传输的是字符串,该参数用来决定将采用的编码(对 参数中设置的编码无影响)。 80 | 81 | 82 | 83 | 84 | (GET 动作中此参数将被忽略) 85 | 设置 Post 采用的传输方式。 86 | 87 | 88 | 89 | 90 | 设置是否采用自定义错误捕捉器。 91 | 若设置为true,请务必同时设置 类的 参数。 92 | 93 | 94 | 95 | 96 | 是否自动关闭输出流,如果设置为true,则 属性无效。 97 | 98 | 99 | 100 | 101 | 设置当请求 HTML 时,是否使用 HTML 头部中的 标签自动获取编码。 102 | 如设为 ,将覆盖 HTTP 响应头中的编码设置。 103 | 104 | 105 | 106 | 107 | 设置当 HTTP 响应码不正常时,是否抛出异常。 108 | 109 | 110 | 111 | 112 | 设置当 HTTP 响应超时时,是否抛出异常。 113 | 114 | 115 | 116 | 117 | 是否检查 HTTPS 证书的合法性,默认检查,以避免中间人等不安全因素 118 | 如非必要请不要设为false 119 | 120 | 121 | 122 | 123 | 设置 HTTP 连接等待的超时时间(单位毫秒/ms)。 124 | 125 | 126 | 127 | 128 | 设置并发连接数(建议1024以下) 129 | 130 | 131 | 132 | 133 | 是否自动对返回流进行Gzip解压(若服务器表明其为Gzip压缩后传输的) 134 | 135 | 136 | 137 | 138 | 是否自动对返回流进行Deflate解压(若服务器表明其为Deflate压缩后传输的) 139 | 140 | 141 | 142 | 143 | 设置当 Post 时数据的传输方式。 144 | 145 | 146 | 147 | 148 | 采用 HttpContent 封装 Post 有效载荷。 149 | 150 | 151 | 152 | 153 | 要传输的是 Json 数据(将自动设置 ContentType ) 154 | 155 | 156 | 157 | 158 | 要传输的是 WWW 表单数据(将自动进行 URL 编码以及设置 ContentType ) 159 | 160 | 161 | 162 | 163 | 要传输的是 Multipart FormData 数据(将自动设置 ContentType ) 164 | 165 | 166 | 167 | 168 | 要传输的是原始字节序列数据(建议手动设置 ContentType ) 169 | 170 | 171 | 172 | 173 | 默认(不 Post 数据) 174 | 175 | 176 | 177 | 178 | 若要让字典支持重复的键,请实例化本类并传入到字典的构造函数中。 179 | 180 | 181 | 182 | 183 | 储存 HTTP 响应的基本信息。 184 | 185 | 186 | 187 | 188 | 获取 HTTP 响应转储的原始输出流。 189 | 每次读取都将转储输出流 中所有内容到本属性中,但并不会关闭输出流(你可以调用CloseStream方法手动关闭,或者之后可能有新的内容时再次读取) 190 | 191 | HTTP 响应转储的原始输出流 192 | 193 | 194 | 195 | 获取 HTTP 响应的原始字节流。 196 | 小心使用,可能存在不稳定性 197 | 需要 ReqParams 中的 属性设为 false。 198 | 特别注意:当使用了 属性时,他们会自动读取 199 | HTTP 响应的原始字节流 200 | 201 | 202 | 203 | 204 | 获取 HTTP 响应的 Cookie 容器。 205 | 206 | HTTP 响应的 Cookie 容器 207 | 208 | 209 | 210 | 获取 HTTP 响应的纯文本(将使用 参数所代表的编码(或者当开启根据 HTML 自动判断编码时获取到的编码)进行解码) 211 | 212 | HTTP 响应纯文本 213 | 214 | 215 | 216 | 获取 HTTP 响应的原始字节序列。 217 | 218 | HTTP 响应的原始字节序列 219 | 220 | 221 | 222 | 获取 HTTP 响应的 ContentType。 223 | 224 | HTTP 响应的ContentType 225 | 226 | 227 | 228 | 获取 HTTP 响应的响应长度(响应流声明的自己的长度,也就是响应流的ContentLength属性,仅供参考)。 229 | 若响应未给该参数,则默认-1 230 | 231 | HTTP 响应的响应长度(ContentLength属性) 232 | 233 | 234 | 235 | 获取 HTTP 响应使用的编码; 236 | 或设置当解码 参数或执行 Json() 函数时要采用的编码。 237 | 238 | 239 | 240 | 241 | 获取 HTTP 响应码。 242 | 243 | 244 | 245 | 246 | 服务器响应流是否被Gzip压缩过 247 | 248 | 249 | 250 | 251 | 服务器响应流是否被Deflate压缩过 252 | 253 | 254 | 255 | 256 | RawStream 当前是否被Gzip压缩过 257 | 258 | 259 | 260 | 261 | RawStream 当前是否被Deflate压缩过 262 | 263 | 264 | 265 | 266 | 是否自动对返回流进行Gzip解压(若服务器表明其为Gzip压缩后传输的) 267 | 注意,仅对之后读取的RawStream有效,若已经读取过RawStream,则无效 268 | 269 | 270 | 271 | 272 | 是否自动对返回流进行Deflate解压(若服务器表明其为Deflate压缩后传输的) 273 | 注意,仅对之后读取的RawStream有效,若已经读取过RawStream,则无效 274 | 275 | 276 | 277 | 278 | 将结果中的 Text(将使用 参数所代表的编码进行解码)解析为 的 JObject。 279 | 280 | 解析后的 JObject 对象 281 | 282 | 283 | 284 | 将结果中的 Text(将使用 参数所代表的编码进行解码)解析为指定类型的对象。 285 | 286 | 解析后的对象 287 | 288 | 289 | 290 | 获取 HTTP 响应的纯文本(将使用 参数所代表的编码进行解码) 291 | 292 | HTTP 响应纯文本 293 | 294 | 295 | 296 | 获取返回值文本每一行的迭代器 297 | 298 | 299 | 300 | 301 | 302 | 获取返回值文本每一行的迭代器 303 | 304 | 305 | 306 | 307 | 308 | HTTP 中 URL解析、请求或响应中可能出现的错误类型。 309 | 310 | 311 | 312 | 313 | 根据HTML头部中的meta标记来返回网页编码 314 | 315 | 316 | 317 | 318 | 319 | 320 | 链式获取父错误的每一级 InnerException,并作为 List 返回 321 | 322 | 父级错误 323 | 324 | 325 | 326 | 327 | 链式获取父错误的每一级 InnerException 的错误消息,并拼接为字符串返回 328 | 每一行代表一级的错误消息,从上到下依次深入 329 | 330 | 父级错误 331 | 332 | 333 | 334 | 335 | 通用错误处理函数 336 | 337 | 是否使用捕捉器 338 | 捕捉器 339 | 内部错误 340 | 错误类型 341 | 342 | 343 | 344 | Provides extension methods for asynchronous operations on 345 | objects. 346 | 347 | 348 | 349 | 350 | 351 | 352 | Returns a response to an Internet request as an asynchronous operation. 353 | 354 | 355 | This operation will not block. The returned object will 356 | complete after a response to an Internet request is available. 357 | 358 | The request. 359 | A object which represents the asynchronous operation. 360 | If is . 361 | 362 | 363 | 364 | Returns a response to an Internet request as an asynchronous operation. 365 | 366 | 367 | This operation will not block. The returned object will 368 | complete after a response to an Internet request is available. 369 | 370 | The request. 371 | The that will be assigned to the new . 372 | A object which represents the asynchronous operation. 373 | If is . 374 | 375 | If was previously called. 376 | -or- 377 | If the timeout period for the request expired. 378 | -or- 379 | If an error occurred while processing the request. 380 | 381 | 382 | 383 | 384 | -------------------------------------------------------------------------------- /PyLibSharp.Requests/Requests.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Net.Http; 10 | using System.Text; 11 | using System.Text.RegularExpressions; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | using System.Web; 15 | 16 | namespace PyLibSharp.Requests 17 | { 18 | /// 19 | /// 设置 HTTP 请求的基本参数。 20 | /// 21 | public class ReqParams 22 | { 23 | /// 24 | /// 设置 HTTP 请求中的默认头部。 25 | /// 若此参数并不包含你想要设置的头部,请改用 参数去设置。 26 | /// 27 | public Dictionary Header { get; set; } = new Dictionary(); 28 | 29 | /// 30 | /// 设置传输过程中使用的代理。 31 | /// 32 | public WebProxy ProxyToUse { get; set; } 33 | 34 | /// 35 | /// 设置传输时采用的 Cookie 容器。 36 | /// 37 | public CookieContainer Cookies { get; set; } = new CookieContainer(); 38 | 39 | /// 40 | /// 当要设置 中的参数所不包含的默认头部或任何自定义头部时,请使用本参数设置。 41 | /// 42 | public Dictionary CustomHeader { get; set; } = new Dictionary(); 43 | 44 | /// 45 | /// 设置要传递给服务器的请求参数。 46 | /// 当 HTTP 动作为 时,将智能附加至 URL 。 47 | /// 其他 HTTP 动作时,将写入传输流,此时请设置 以明确如何传输数据。 48 | /// 其他 HTTP 动作时,此参数优先级不如 高,若设置了 参数,此参数将被忽略。 49 | /// 50 | public Dictionary Params { get; set; } = new Dictionary(); 51 | 52 | /// 53 | /// (GET 动作中此参数将被忽略) 54 | /// 设置要 Post 至服务器的 HTTP 内容(如有必要需手动设置 的 MediaType 字符串,如嫌麻烦请继续使用 参数,这些参数会自动设置 ContentType )。 55 | /// 若同时设置了 ,此参数优先级最高,将使用本参数指定的方式进行 Post ; 56 | /// 在未设置 参数时,将自动修改 的值。 57 | /// 58 | public HttpContent PostContent { get; set; } 59 | 60 | /// 61 | /// (GET 动作中此参数将被忽略) 62 | /// 建议改用 参数传递 HttpContent; 63 | /// 设置要 Post 至服务器的原始字节序列。 64 | /// 若同时设置了 参数优先级最高,将使用 指定的方式进行 Post ; 65 | /// 在未设置 参数时,将自动修改 的值。 66 | /// 67 | public byte[] PostRawData { get; set; } 68 | 69 | /// 70 | /// (GET 动作中此参数将被忽略) 71 | /// 建议改用 参数传递 HttpContent; 72 | /// 设置要 Post 至服务器的 Json 数据。 73 | /// 若同时设置了 参数优先级最高,将使用 指定的方式进行 Post ; 74 | /// 在未设置 参数时,将自动修改 的值。 75 | /// 76 | public object PostJson { get; set; } 77 | 78 | /// 79 | /// (GET 动作中此参数将被忽略) 80 | /// 建议改用 参数传递 HttpContent; 81 | /// 设置要 Post 到服务器的 MultiPart 数据。 82 | /// 若同时设置了 参数优先级最高,将使用 指定的方式进行 Post ; 83 | /// 在未设置 参数时,将自动修改 的值。 84 | /// 85 | public MultipartFormDataContent PostMultiPart { get; set; } 86 | 87 | /// 88 | /// (GET 动作中此参数将被忽略) 89 | /// 若 Post 传输的是字符串,该参数用来决定将采用的编码(对 参数中设置的编码无影响)。 90 | /// 91 | public Encoding PostEncoding { get; set; } = new System.Text.UTF8Encoding(false); 92 | 93 | /// 94 | /// (GET 动作中此参数将被忽略) 95 | /// 设置 Post 采用的传输方式。 96 | /// 97 | public PostType PostParamsType { get; set; } = PostType.none; 98 | 99 | /// 100 | /// 设置是否采用自定义错误捕捉器。 101 | /// 若设置为true,请务必同时设置 类的 参数。 102 | /// 103 | public bool UseHandler { get; set; } = false; 104 | 105 | /// 106 | /// 是否自动关闭输出流,如果设置为true,则 属性无效。 107 | /// 108 | public bool IsAutoCloseStream { get; set; } = true; 109 | 110 | /// 111 | /// 设置当请求 HTML 时,是否使用 HTML 头部中的 标签自动获取编码。 112 | /// 如设为 ,将覆盖 HTTP 响应头中的编码设置。 113 | /// 114 | public bool IsUseHtmlMetaEncoding { get; set; } = true; 115 | 116 | /// 117 | /// 设置当 HTTP 响应码不正常时,是否抛出异常。 118 | /// 119 | public bool IsThrowErrorForStatusCode { get; set; } = true; 120 | 121 | /// 122 | /// 设置当 HTTP 响应超时时,是否抛出异常。 123 | /// 124 | public bool IsThrowErrorForTimeout { get; set; } = true; 125 | 126 | /// 127 | /// 是否检查 HTTPS 证书的合法性,默认检查,以避免中间人等不安全因素 128 | /// 如非必要请不要设为false 129 | /// 130 | public bool isCheckSSLCert { get; set; } = true; 131 | 132 | /// 133 | /// 设置 HTTP 连接等待的超时时间(单位毫秒/ms)。 134 | /// 135 | public int Timeout { get; set; } = 1500; 136 | 137 | /// 138 | /// 设置并发连接数(建议1024以下) 139 | /// 140 | public int DefaultConnectionLimit { get; set; } = 512; 141 | 142 | /// 143 | /// 是否自动对返回流进行Gzip解压(若服务器表明其为Gzip压缩后传输的) 144 | /// 145 | public bool IsAutoUnGzip { get; set; } = true; 146 | 147 | /// 148 | /// 是否自动对返回流进行Deflate解压(若服务器表明其为Deflate压缩后传输的) 149 | /// 150 | public bool IsAutoUnDeflate { get; set; } = true; 151 | } 152 | 153 | /// 154 | /// 设置当 Post 时数据的传输方式。 155 | /// 156 | public enum PostType 157 | { 158 | /// 159 | /// 采用 HttpContent 封装 Post 有效载荷。 160 | /// 161 | http_content, 162 | 163 | /// 164 | /// 要传输的是 Json 数据(将自动设置 ContentType ) 165 | /// 166 | json, 167 | 168 | /// 169 | /// 要传输的是 WWW 表单数据(将自动进行 URL 编码以及设置 ContentType ) 170 | /// 171 | x_www_form_urlencoded, 172 | 173 | /// 174 | /// 要传输的是 Multipart FormData 数据(将自动设置 ContentType ) 175 | /// 176 | form_data, 177 | 178 | /// 179 | /// 要传输的是原始字节序列数据(建议手动设置 ContentType ) 180 | /// 181 | raw, 182 | 183 | /// 184 | /// 默认(不 Post 数据) 185 | /// 186 | none 187 | } 188 | 189 | /// 190 | /// 若要让字典支持重复的键,请实例化本类并传入到字典的构造函数中。 191 | /// 192 | public class ReqRepeatable : IEqualityComparer 193 | { 194 | public bool Equals(string x, string y) 195 | { 196 | return x != y; 197 | } 198 | 199 | public int GetHashCode(string obj) 200 | { 201 | return obj.GetHashCode(); 202 | } 203 | } 204 | 205 | /// 206 | /// 储存 HTTP 响应的基本信息。 207 | /// 208 | public class ReqResponse : IEnumerable 209 | { 210 | private readonly MemoryStream _rawStream = new MemoryStream(); 211 | 212 | /// 213 | /// 获取 HTTP 响应转储的原始输出流。 214 | /// 每次读取都将转储输出流 中所有内容到本属性中,但并不会关闭输出流(你可以调用CloseStream方法手动关闭,或者之后可能有新的内容时再次读取) 215 | /// 216 | /// HTTP 响应转储的原始输出流 217 | public MemoryStream RawStream 218 | { 219 | get 220 | { 221 | if (OutputStream?.CanRead ?? false) 222 | { 223 | //读取输出流 224 | MemoryStream bufferStream = new MemoryStream(); 225 | 226 | IsCurrentGzipped = IsGzipped; 227 | IsCurrentDeflated = IsDeflated; 228 | 229 | if (_isAutoUnGzip && IsGzipped) 230 | { 231 | Utils.UnGzipFromStreamToStream(OutputStream, bufferStream); 232 | IsCurrentGzipped = false; 233 | } 234 | else if (_isAutoUnDeflate && IsDeflated) 235 | { 236 | Utils.UnDeflateFromStreamToStream(OutputStream, bufferStream); 237 | IsCurrentDeflated = false; 238 | } 239 | else 240 | { 241 | OutputStream?.CopyTo(_rawStream); 242 | } 243 | 244 | //追加写入 245 | byte[] buffer = bufferStream.GetBuffer(); 246 | _rawStream.Write(buffer, 0, buffer.Length); 247 | bufferStream.Close(); 248 | } 249 | 250 | return _rawStream; 251 | } 252 | } 253 | 254 | /// 255 | /// 获取 HTTP 响应的原始字节流。 256 | /// 小心使用,可能存在不稳定性 257 | /// 需要 ReqParams 中的 属性设为 false。 258 | /// 特别注意:当使用了 属性时,他们会自动读取 259 | /// HTTP 响应的原始字节流 260 | /// 261 | public Stream OutputStream { get; } 262 | 263 | /// 264 | /// 获取 HTTP 响应的 Cookie 容器。 265 | /// 266 | /// HTTP 响应的 Cookie 容器 267 | public CookieContainer Cookies { get; } 268 | 269 | /// 270 | /// 获取 HTTP 响应的纯文本(将使用 参数所代表的编码(或者当开启根据 HTML 自动判断编码时获取到的编码)进行解码) 271 | /// 272 | /// HTTP 响应纯文本 273 | public string Text 274 | { 275 | get 276 | { 277 | string testRead = Encode.GetString(RawStream.ToArray()); 278 | //是否自动判断编码 279 | if (_decodeUsingHtmlMetaTag 280 | && ContentType.ToLower().IndexOf("text/html", StringComparison.OrdinalIgnoreCase) != -1) 281 | { 282 | Encode = Utils.GetHtmlEncodingByMetaHeader(testRead); 283 | } 284 | 285 | return Encode.GetString(RawStream.ToArray()); 286 | } 287 | } 288 | 289 | /// 290 | /// 获取 HTTP 响应的原始字节序列。 291 | /// 292 | /// HTTP 响应的原始字节序列 293 | public byte[] Content => RawStream.ToArray(); 294 | 295 | /// 296 | /// 获取 HTTP 响应的 ContentType。 297 | /// 298 | /// HTTP 响应的ContentType 299 | public string ContentType { get; } 300 | 301 | /// 302 | /// 获取 HTTP 响应的响应长度(响应流声明的自己的长度,也就是响应流的ContentLength属性,仅供参考)。 303 | /// 若响应未给该参数,则默认-1 304 | /// 305 | /// HTTP 响应的响应长度(ContentLength属性) 306 | public long ContentLength { get; } 307 | 308 | /// 309 | /// 获取 HTTP 响应使用的编码; 310 | /// 或设置当解码 参数或执行 Json() 函数时要采用的编码。 311 | /// 312 | public Encoding Encode { get; set; } 313 | 314 | /// 315 | /// 获取 HTTP 响应码。 316 | /// 317 | public HttpStatusCode StatusCode { get; } 318 | 319 | /// 320 | /// 服务器响应流是否被Gzip压缩过 321 | /// 322 | public bool IsGzipped { get; } 323 | 324 | /// 325 | /// 服务器响应流是否被Deflate压缩过 326 | /// 327 | public bool IsDeflated { get; } 328 | 329 | /// 330 | /// RawStream 当前是否被Gzip压缩过 331 | /// 332 | public bool IsCurrentGzipped { get; private set; } 333 | 334 | /// 335 | /// RawStream 当前是否被Deflate压缩过 336 | /// 337 | public bool IsCurrentDeflated { get; private set; } 338 | 339 | /// 340 | /// 是否自动对返回流进行Gzip解压(若服务器表明其为Gzip压缩后传输的) 341 | /// 注意,仅对之后读取的RawStream有效,若已经读取过RawStream,则无效 342 | /// 343 | private bool _isAutoUnGzip { get; set; } = true; 344 | 345 | /// 346 | /// 是否自动对返回流进行Deflate解压(若服务器表明其为Deflate压缩后传输的) 347 | /// 注意,仅对之后读取的RawStream有效,若已经读取过RawStream,则无效 348 | /// 349 | private bool _isAutoUnDeflate { get; set; } = true; 350 | 351 | private readonly bool _decodeUsingHtmlMetaTag = false; 352 | private readonly WebRequest _requestMain; 353 | 354 | public ReqResponse(WebRequest requestMain, Stream outputStream, CookieContainer cookies, string contentType, 355 | long contentLength, 356 | Encoding encode, 357 | HttpStatusCode statusCode, bool decodeUsingHtmlMetaTag, bool autoCloseStream, 358 | bool isGzipped = false, bool isDeflated = false, bool isAutoUnGzip = true, 359 | bool isAutoUnDeflate = true) 360 | { 361 | ContentLength = contentLength; 362 | _requestMain = requestMain; 363 | Cookies = cookies; 364 | OutputStream = outputStream; 365 | ContentType = contentType; 366 | Encode = encode; 367 | StatusCode = statusCode; 368 | _decodeUsingHtmlMetaTag = decodeUsingHtmlMetaTag; 369 | IsGzipped = isGzipped; 370 | IsDeflated = isDeflated; 371 | IsCurrentGzipped = isGzipped; 372 | IsCurrentDeflated = isDeflated; 373 | _isAutoUnGzip = isAutoUnGzip; 374 | _isAutoUnDeflate = isAutoUnDeflate; 375 | if (autoCloseStream) 376 | { 377 | //读取输出流 378 | if (_isAutoUnGzip && isGzipped) 379 | { 380 | Utils.UnGzipFromStreamToStream(OutputStream, _rawStream); 381 | IsCurrentGzipped = false; 382 | } 383 | else if (_isAutoUnDeflate && isDeflated) 384 | { 385 | Utils.UnDeflateFromStreamToStream(OutputStream, _rawStream); 386 | IsCurrentDeflated = false; 387 | } 388 | else 389 | { 390 | OutputStream?.CopyTo(_rawStream); 391 | } 392 | 393 | OutputStream?.Close(); 394 | _requestMain?.Abort(); 395 | } 396 | } 397 | 398 | /// 399 | /// 将结果中的 Text(将使用 参数所代表的编码进行解码)解析为 的 JObject。 400 | /// 401 | /// 解析后的 JObject 对象 402 | public JToken Json() 403 | { 404 | try 405 | { 406 | // if (!ContentType.Contains("application/json")) 407 | // { 408 | // throw new WarningException("HTTP 响应中的 Content-Type 并非 JSON 格式,响应的数据有可能并不是 JSON"); 409 | // } 410 | 411 | return JToken.Parse(Text).ToObject(); 412 | } 413 | catch (Exception ex) 414 | { 415 | throw new ReqResponseParseException("JSON 解析出错,请确保响应为 JSON 格式: " + ex.Message, ex); 416 | } 417 | } 418 | 419 | public void CloseStream() 420 | { 421 | OutputStream?.Close(); 422 | _requestMain?.Abort(); 423 | } 424 | 425 | /// 426 | /// 将结果中的 Text(将使用 参数所代表的编码进行解码)解析为指定类型的对象。 427 | /// 428 | /// 解析后的对象 429 | public T ToObject() 430 | { 431 | try 432 | { 433 | // if (!ContentType.Contains("application/json")) 434 | // { 435 | // throw new WarningException("HTTP 响应中的 Content-Type 并非 JSON 格式,响应的数据有可能并不是 JSON"); 436 | // } 437 | 438 | try 439 | { 440 | return JsonConvert.DeserializeObject(Text); 441 | } 442 | catch 443 | { 444 | return JArray.Parse(Text ?? "[]")[0].ToObject(); 445 | } 446 | } 447 | catch (Exception ex) 448 | { 449 | throw new 450 | ReqResponseParseException("JSON 解析出错,无法解析为类型:" + typeof(T) + "。请确保响应为 JSON 格式及与类型匹配: " + ex.Message, 451 | ex); 452 | } 453 | } 454 | 455 | /// 456 | /// 获取 HTTP 响应的纯文本(将使用 参数所代表的编码进行解码) 457 | /// 458 | /// HTTP 响应纯文本 459 | public override string ToString() 460 | { 461 | return Text; 462 | } 463 | 464 | /// 465 | /// 获取返回值文本每一行的迭代器 466 | /// 467 | /// 468 | public IEnumerator GetEnumerator() 469 | { 470 | bool isCrLf = Text.Contains("\r"); 471 | foreach (string line in Regex.Split(Text, isCrLf ? "\r\n" : "\n")) 472 | { 473 | yield return line; 474 | } 475 | } 476 | 477 | /// 478 | /// 获取返回值文本每一行的迭代器 479 | /// 480 | /// 481 | IEnumerator IEnumerable.GetEnumerator() 482 | { 483 | bool isCrLf = Text.Contains("\r"); 484 | foreach (string line in Regex.Split(Text, isCrLf ? "\r\n" : "\n")) 485 | { 486 | yield return line; 487 | } 488 | } 489 | } 490 | 491 | #region 自定义错误部分 492 | 493 | /// 494 | /// HTTP 中 URL解析、请求或响应中可能出现的错误类型。 495 | /// 496 | public enum ErrorType 497 | { 498 | ArgumentNull, 499 | HTTPStatusCodeError, 500 | HTTPRequestTimeout, 501 | HTTPRequestHeaderError, 502 | UrlParseError, 503 | UserCancelled, 504 | Other, 505 | HTTPRequestError 506 | } 507 | 508 | class ReqRequestException : ApplicationException 509 | { 510 | public string Error; 511 | public ErrorType ErrType; 512 | private Exception innerException; 513 | 514 | public ReqRequestException() 515 | { 516 | } 517 | 518 | public ReqRequestException(string msg, ErrorType errType) : base(msg) 519 | { 520 | this.Error = msg; 521 | this.ErrType = errType; 522 | } 523 | 524 | public ReqRequestException(string msg, Exception innerException) : base(msg, innerException) 525 | { 526 | this.innerException = innerException; 527 | Error = msg; 528 | } 529 | 530 | public string GetError() 531 | { 532 | return Error; 533 | } 534 | } 535 | 536 | class ReqResponseException : ApplicationException 537 | { 538 | public string Error; 539 | public ErrorType ErrType; 540 | private Exception innerException; 541 | 542 | public ReqResponseException() 543 | { 544 | } 545 | 546 | public ReqResponseException(string msg, ErrorType errType) : base(msg) 547 | { 548 | this.Error = msg; 549 | this.ErrType = errType; 550 | } 551 | 552 | public ReqResponseException(string msg, Exception innerException) : base(msg, innerException) 553 | { 554 | this.innerException = innerException; 555 | Error = msg; 556 | } 557 | 558 | public string GetError() 559 | { 560 | return Error; 561 | } 562 | } 563 | 564 | 565 | class ReqResponseParseException : ApplicationException 566 | { 567 | public string Error; 568 | public ErrorType ErrType; 569 | private Exception innerException; 570 | 571 | public ReqResponseParseException() 572 | { 573 | } 574 | 575 | public ReqResponseParseException(string msg, ErrorType errType) : base(msg) 576 | { 577 | this.Error = msg; 578 | this.ErrType = errType; 579 | } 580 | 581 | public ReqResponseParseException(string msg, Exception innerException) : base(msg, innerException) 582 | { 583 | this.innerException = innerException; 584 | Error = msg; 585 | } 586 | 587 | public string GetError() 588 | { 589 | return Error; 590 | } 591 | } 592 | 593 | class ReqUrlException : ApplicationException 594 | { 595 | public string Error; 596 | public ErrorType ErrType; 597 | private Exception innerException; 598 | 599 | public ReqUrlException() 600 | { 601 | } 602 | 603 | public ReqUrlException(string msg, ErrorType errType) : base(msg) 604 | { 605 | this.Error = msg; 606 | this.ErrType = errType; 607 | } 608 | 609 | public ReqUrlException(string msg, Exception innerException) : base(msg, innerException) 610 | { 611 | this.innerException = innerException; 612 | Error = msg; 613 | } 614 | 615 | public string GetError() 616 | { 617 | return Error; 618 | } 619 | } 620 | 621 | class ReqHeaderException : ApplicationException 622 | { 623 | public string Error; 624 | public ErrorType ErrType; 625 | private Exception innerException; 626 | 627 | public ReqHeaderException() 628 | { 629 | } 630 | 631 | public ReqHeaderException(string msg, ErrorType errType) : base(msg) 632 | { 633 | this.Error = msg; 634 | this.ErrType = errType; 635 | } 636 | 637 | public ReqHeaderException(string msg, Exception innerException) : base(msg, innerException) 638 | { 639 | this.innerException = innerException; 640 | Error = msg; 641 | } 642 | 643 | public string GetError() 644 | { 645 | return Error; 646 | } 647 | } 648 | 649 | #endregion 650 | 651 | public class Requests 652 | { 653 | public static event EventHandler ReqExceptionHandler; 654 | 655 | public class AggregateExceptionArgs : EventArgs 656 | { 657 | public AggregateException AggregateException { get; set; } 658 | public ErrorType ErrType { get; set; } 659 | } 660 | 661 | public static ReqResponse XHR(string XHRData) 662 | { 663 | return XHRBase(XHRData, new ReqParams()).Result; 664 | } 665 | 666 | public static async Task XHRAsync(string XHRData) 667 | { 668 | return await XHRBase(XHRData, new ReqParams()); 669 | } 670 | 671 | public static ReqResponse XHR(string XHRData, ReqParams Params) 672 | { 673 | return XHRBase(XHRData, Params).Result; 674 | } 675 | 676 | public static async Task XHRAsync(string XHRData, ReqParams Params) 677 | { 678 | return await XHRBase(XHRData, Params); 679 | } 680 | 681 | private static async Task XHRBase(string XHRData, ReqParams Params) 682 | { 683 | List HeaderAndData = 684 | XHRData.Split(new string[] {"\r\n\r\n"}, 2, StringSplitOptions.RemoveEmptyEntries).ToList(); 685 | List linesOfXHR = 686 | HeaderAndData[0].Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries).ToList(); 687 | if (Params.UseHandler) 688 | { 689 | if (ReqExceptionHandler == null) 690 | throw new ArgumentNullException(nameof(ReqExceptionHandler), 691 | new Exception("若要使用自定义错误处理函数,请先对事件 ReqExceptionHandler 增加处理函数。")); 692 | } 693 | else 694 | { 695 | if (ReqExceptionHandler != null) 696 | Params.UseHandler = true; 697 | } 698 | 699 | if (!linesOfXHR.Any()) 700 | { 701 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 702 | ReqUrlException("XHR 格式有误:应至少有1行", 703 | new Exception()), ErrorType.UrlParseError); 704 | } 705 | 706 | string HTTPFirst = linesOfXHR.First(); 707 | linesOfXHR.RemoveAt(0); 708 | string method = ""; 709 | string URL = ""; 710 | try 711 | { 712 | var firstLine = HTTPFirst.Trim().Replace(" ", "").Split(new[] {' '}); 713 | method = firstLine[0]; 714 | URL = firstLine[1]; 715 | string HTTPProtocal = firstLine[2]; //忽略 716 | } 717 | catch (Exception ex) 718 | { 719 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 720 | ReqUrlException("XHR 格式有误:第一行格式有误", 721 | ex), ErrorType.UrlParseError); 722 | } 723 | 724 | 725 | Dictionary headerAndKey = new Dictionary(); 726 | Dictionary defaultHeaderAndKey = new Dictionary(); 727 | string host = ""; 728 | //解析每一行 XHR 729 | linesOfXHR.ForEach(i => 730 | { 731 | string currLine = (i.EndsWith("\r") ? i.Trim().TrimEnd('\r') : i.Trim()); 732 | string key = currLine; 733 | string value = ""; 734 | if (currLine.Contains(":")) 735 | { 736 | key = currLine.Split(new string[] {":"}, 2, StringSplitOptions.None)[0].Trim(); 737 | value = currLine.Split(new string[] {":"}, 2, StringSplitOptions.None)[1].Trim(); 738 | } 739 | 740 | if (key.ToLower() == "host") 741 | { 742 | host = value; 743 | } 744 | else if (key.ToLower() == "content-length") 745 | { 746 | } 747 | else if (key.ToLower() == "accept-encoding") 748 | { 749 | } 750 | //如果是预先定义的HTTP头部 751 | else if (Enum.IsDefined(typeof(HttpRequestHeader), key.Replace("-", ""))) 752 | { 753 | defaultHeaderAndKey 754 | .Add((HttpRequestHeader) Enum.Parse(typeof(HttpRequestHeader), key.Replace("-", ""), true), 755 | value); 756 | headerAndKey.Remove(key); 757 | } 758 | else 759 | { 760 | //如果是自定义HTTP头部 761 | headerAndKey.Add(key, value); 762 | } 763 | }); 764 | 765 | 766 | Params.Header = defaultHeaderAndKey; 767 | Params.CustomHeader = headerAndKey; 768 | 769 | if (host == "") 770 | { 771 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 772 | ReqUrlException("XHR 格式有误:未指定目标服务器 Host", 773 | new Exception()), ErrorType.UrlParseError); 774 | } 775 | 776 | 777 | URL = "http://" + host + URL; 778 | 779 | if (method.ToUpper() != "GET" && HeaderAndData.Count > 1) 780 | { 781 | if (defaultHeaderAndKey.ContainsKey(HttpRequestHeader.ContentType)) 782 | { 783 | if (defaultHeaderAndKey[HttpRequestHeader.ContentType].Contains("charset=")) 784 | { 785 | Params.PostEncoding = 786 | Encoding.GetEncoding(defaultHeaderAndKey[HttpRequestHeader.ContentType] 787 | .Split(new string[] {"charset="}, StringSplitOptions.None)[1]); 788 | } 789 | 790 | if (Params.PostParamsType == PostType.none) 791 | { 792 | if (defaultHeaderAndKey[HttpRequestHeader.ContentType].ToLower() 793 | .Contains("application/x-www-form-urlencoded") 794 | ) 795 | { 796 | Params.PostParamsType = PostType.x_www_form_urlencoded; 797 | } 798 | 799 | if (defaultHeaderAndKey[HttpRequestHeader.ContentType].ToLower() 800 | .Contains("multipart/form-data") 801 | ) 802 | { 803 | Params.PostParamsType = PostType.form_data; 804 | } 805 | 806 | if (defaultHeaderAndKey[HttpRequestHeader.ContentType].ToLower() 807 | .Contains("application/json") 808 | ) 809 | { 810 | Params.PostParamsType = PostType.json; 811 | } 812 | } 813 | } 814 | 815 | switch (Params.PostParamsType) 816 | { 817 | case PostType.raw: 818 | Params.PostRawData = Params.PostEncoding.GetBytes(HeaderAndData[1]); 819 | break; 820 | case PostType.x_www_form_urlencoded: 821 | defaultHeaderAndKey[HttpRequestHeader.ContentType] = 822 | "application/x-www-form-urlencoded;charset=" + Params.PostEncoding.WebName; 823 | Params.PostRawData = Params.PostEncoding.GetBytes(HeaderAndData[1]); 824 | Params.PostParamsType = PostType.raw; 825 | break; 826 | case PostType.form_data: 827 | Params.PostRawData = Params.PostEncoding.GetBytes(HeaderAndData[1]); 828 | Params.PostParamsType = PostType.raw; 829 | break; 830 | case PostType.json: 831 | defaultHeaderAndKey[HttpRequestHeader.ContentType] = 832 | "application/json;charset=" + Params.PostEncoding.WebName; 833 | Params.PostJson = HeaderAndData[1]; 834 | break; 835 | default: 836 | if (defaultHeaderAndKey.ContainsKey(HttpRequestHeader.ContentType)) 837 | { 838 | if (defaultHeaderAndKey[HttpRequestHeader.ContentType].ToLower().Contains("applition/json")) 839 | { 840 | Params.PostJson = HeaderAndData[1]; 841 | } 842 | else 843 | { 844 | Params.PostRawData = Params.PostEncoding.GetBytes(HeaderAndData[1]); 845 | } 846 | } 847 | else 848 | { 849 | Params.PostRawData = Params.PostEncoding.GetBytes(HeaderAndData[1]); 850 | } 851 | 852 | break; 853 | } 854 | } 855 | 856 | return await RequestBase(URL, new HttpMethod(method.ToUpper()), Params, new CancellationTokenSource()); 857 | } 858 | 859 | public static ReqResponse Get(string Url) 860 | { 861 | return RequestBase(Url, HttpMethod.Get, new ReqParams(), new CancellationTokenSource()).Result; 862 | } 863 | 864 | public static ReqResponse Get(string Url, ReqParams Params) 865 | { 866 | return RequestBase(Url, HttpMethod.Get, Params, new CancellationTokenSource()).Result; 867 | } 868 | 869 | public static ReqResponse Get(string Url, ReqParams Params, CancellationTokenSource CancelFlag) 870 | { 871 | return RequestBase(Url, HttpMethod.Get, Params, CancelFlag).Result; 872 | } 873 | 874 | public static ReqResponse Post(string Url) 875 | { 876 | return RequestBase(Url, HttpMethod.Post, new ReqParams(), new CancellationTokenSource()).Result; 877 | } 878 | 879 | public static ReqResponse Post(string Url, ReqParams Params) 880 | { 881 | return RequestBase(Url, HttpMethod.Post, Params, new CancellationTokenSource()).Result; 882 | } 883 | 884 | public static ReqResponse Post(string Url, ReqParams Params, CancellationTokenSource CancelFlag) 885 | { 886 | return RequestBase(Url, HttpMethod.Post, Params, CancelFlag).Result; 887 | } 888 | 889 | public static async Task GetAsync(string Url) 890 | { 891 | return await RequestBase(Url, HttpMethod.Get, new ReqParams(), new CancellationTokenSource()); 892 | } 893 | 894 | public static async Task GetAsync(string Url, ReqParams Params) 895 | { 896 | return await RequestBase(Url, HttpMethod.Get, Params, new CancellationTokenSource()); 897 | } 898 | 899 | public static async Task GetAsync(string Url, ReqParams Params, CancellationTokenSource CancelFlag) 900 | { 901 | return await RequestBase(Url, HttpMethod.Get, Params, CancelFlag); 902 | } 903 | 904 | public static async Task PostAsync(string Url) 905 | { 906 | return await RequestBase(Url, HttpMethod.Post, new ReqParams(), new CancellationTokenSource()); 907 | } 908 | 909 | public static async Task PostAsync(string Url, ReqParams Params) 910 | { 911 | return await RequestBase(Url, HttpMethod.Post, Params, new CancellationTokenSource()); 912 | } 913 | 914 | public static async Task PostAsync(string Url, ReqParams Params, 915 | CancellationTokenSource CancelFlag) 916 | { 917 | return await RequestBase(Url, HttpMethod.Post, Params, CancelFlag); 918 | } 919 | 920 | public static async Task RequestBase(string Url, HttpMethod Method, ReqParams Params, 921 | CancellationTokenSource CancelFlag) 922 | { 923 | //不能直接使用GB2312,必须先注册 924 | Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); 925 | 926 | if (string.IsNullOrEmpty(Url)) 927 | { 928 | throw new ArgumentNullException(nameof(Url), new Exception("URL 不可为空")); 929 | } 930 | 931 | //检查错误捕捉器合法性 932 | if (Params.UseHandler) 933 | { 934 | if (ReqExceptionHandler == null) 935 | throw new ArgumentNullException(nameof(ReqExceptionHandler), 936 | new Exception("若要使用自定义错误处理函数,请先对事件 ReqExceptionHandler 增加处理函数。")); 937 | } 938 | else 939 | { 940 | if (ReqExceptionHandler != null) 941 | Params.UseHandler = true; 942 | } 943 | 944 | if (Params is null) 945 | { 946 | Params = new ReqParams(); 947 | } 948 | 949 | GC.Collect(); 950 | HttpWebRequest request = null; 951 | 952 | //参数处理部分 953 | string paramStr = 954 | String.Join("&", 955 | Params.Params.Select(i => HttpUtility.UrlEncode(i.Key) + "=" + 956 | HttpUtility.UrlEncode(i.Value))); 957 | //自动判断 Post 类型 958 | if (Params.PostParamsType == PostType.none) 959 | { 960 | if (Params.PostContent != null) 961 | { 962 | Params.PostParamsType = PostType.http_content; 963 | } 964 | else if (!string.IsNullOrEmpty(paramStr)) 965 | { 966 | Params.PostParamsType = PostType.x_www_form_urlencoded; 967 | } 968 | else 969 | { 970 | if (Params.PostJson != null) 971 | { 972 | Params.PostParamsType = PostType.json; 973 | } 974 | 975 | if (Params.PostRawData != null && Params.PostRawData.Length != 0) 976 | { 977 | Params.PostParamsType = PostType.raw; 978 | } 979 | 980 | if (Params.PostMultiPart != null && Params.PostMultiPart.Any()) 981 | { 982 | Params.PostParamsType = PostType.form_data; 983 | } 984 | } 985 | } 986 | 987 | try 988 | { 989 | //解析 Url,产生必要的报错 990 | Uri urlToSend = new Uri(Url); 991 | 992 | 993 | if (Method == HttpMethod.Get || 994 | //只有x_www_form情况下Params才是Post的参数,其他情况下均要转为Get参数 995 | (Params.PostParamsType != PostType.x_www_form_urlencoded && Params.Params.Count > 0)) 996 | { 997 | //如果是 GET 请求,需要拼接参数到 URL 上 998 | var urlParsed = Url.Contains("?") ? Url.Split('?')[0] : Url; 999 | if (paramStr == "") 1000 | { 1001 | if (Url.Contains("?")) 1002 | { 1003 | urlParsed += "?" + Url.Split('?')[1]; 1004 | } 1005 | } 1006 | else 1007 | { 1008 | paramStr = (urlToSend.Query.StartsWith("?") ? urlToSend.Query : "?" + urlToSend.Query) + 1009 | (urlToSend.Query.EndsWith("&") 1010 | ? "" 1011 | : ((urlToSend.Query != "" && urlToSend.Query != "?" && paramStr != "") 1012 | ? "&" 1013 | : "")) 1014 | + paramStr; 1015 | urlParsed += ((urlToSend.AbsolutePath == "/" && !Url.EndsWith("/")) ? "/" : "") + paramStr; 1016 | } 1017 | 1018 | request = (HttpWebRequest) WebRequest.Create(urlParsed); 1019 | } 1020 | else 1021 | { 1022 | request = (HttpWebRequest) WebRequest.Create(urlToSend); 1023 | } 1024 | } 1025 | catch (Exception ex) 1026 | { 1027 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1028 | ReqUrlException("构造 URL 时发生错误,请检查 URL 格式和请求参数", 1029 | ex), ErrorType.UrlParseError); 1030 | } 1031 | 1032 | 1033 | request.Method = Method.Method; 1034 | request.Timeout = Params.Timeout; 1035 | request.Proxy = Params.ProxyToUse; 1036 | request.ServicePoint.Expect100Continue = false; 1037 | ServicePointManager.DefaultConnectionLimit = Params.DefaultConnectionLimit; 1038 | //是否检查 SSL 证书 1039 | if (!Params.isCheckSSLCert) 1040 | { 1041 | ServicePointManager.ServerCertificateValidationCallback = 1042 | (i, j, k, l) => true; 1043 | } 1044 | 1045 | //是否检查 SSL 证书 1046 | if (!Params.isCheckSSLCert) 1047 | { 1048 | ServicePointManager.ServerCertificateValidationCallback = 1049 | (i, j, k, l) => true; 1050 | } 1051 | 1052 | try 1053 | { 1054 | //头部处理部分 1055 | //默认头部添加 1056 | if (!Params.Header.ContainsKey(HttpRequestHeader.AcceptLanguage)) 1057 | { 1058 | Params.Header.Add(HttpRequestHeader.AcceptLanguage, 1059 | "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"); 1060 | } 1061 | 1062 | if (!Params.Header.ContainsKey(HttpRequestHeader.UserAgent)) 1063 | { 1064 | Params.Header.Add(HttpRequestHeader.UserAgent, 1065 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.41"); 1066 | } 1067 | 1068 | if (!Params.Header.ContainsKey(HttpRequestHeader.Accept)) 1069 | { 1070 | Params.Header.Add(HttpRequestHeader.Accept, 1071 | "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"); 1072 | } 1073 | 1074 | if (!Params.Header.ContainsKey(HttpRequestHeader.Connection)) 1075 | { 1076 | Params.Header.Add(HttpRequestHeader.Connection, ""); 1077 | } 1078 | 1079 | if (Method != HttpMethod.Get) 1080 | { 1081 | //附加 Cookies 1082 | var cookieList = Utils.GetAllCookies(Params.Cookies); 1083 | StringBuilder sb = new StringBuilder(); 1084 | //必须手动拼接,否则 POST 等请求带不出 Cookies 1085 | foreach (Cookie o in cookieList) 1086 | { 1087 | sb.Append(o.Name + "=" + o.Value + ";"); 1088 | } 1089 | 1090 | Params.Header.Add(HttpRequestHeader.Cookie, sb.ToString()); 1091 | } 1092 | 1093 | //读取参数指定的头部,正确写入 request 属性 1094 | foreach (KeyValuePair header in Params.Header) 1095 | { 1096 | switch (header.Key) 1097 | { 1098 | case HttpRequestHeader.Accept: 1099 | request.Accept = header.Value; 1100 | break; 1101 | case HttpRequestHeader.Connection: 1102 | //request.Connection = header.Value; 1103 | if (header.Value.ToLower() == "keep-alive") 1104 | { 1105 | request.KeepAlive = true; 1106 | } 1107 | else 1108 | { 1109 | request.KeepAlive = false; 1110 | } 1111 | 1112 | break; 1113 | case HttpRequestHeader.ContentLength: 1114 | //GET忽略此参数 1115 | if (Method == HttpMethod.Get) break; 1116 | if (long.TryParse(header.Value, out long length)) 1117 | { 1118 | request.ContentLength = length; 1119 | } 1120 | 1121 | break; 1122 | case HttpRequestHeader.ContentType: 1123 | request.ContentType = header.Value.Trim().ToLower(); 1124 | break; 1125 | case HttpRequestHeader.Date: 1126 | if (DateTime.TryParse(header.Value, out DateTime date)) 1127 | { 1128 | request.Date = date; 1129 | } 1130 | 1131 | break; 1132 | case HttpRequestHeader.Expect: 1133 | request.Expect = header.Value; 1134 | break; 1135 | case HttpRequestHeader.Host: 1136 | request.Host = header.Value; 1137 | break; 1138 | 1139 | case HttpRequestHeader.Referer: 1140 | request.Referer = header.Value; 1141 | break; 1142 | case HttpRequestHeader.UserAgent: 1143 | request.UserAgent = header.Value; 1144 | break; 1145 | default: 1146 | request.Headers.Add(header.Key, header.Value); 1147 | break; 1148 | } 1149 | } 1150 | 1151 | foreach (KeyValuePair header in Params.CustomHeader) 1152 | { 1153 | request.Headers.Add(HttpUtility.UrlEncode(header.Key), HttpUtility.UrlEncode(header.Value)); 1154 | } 1155 | } 1156 | catch (Exception ex) 1157 | { 1158 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1159 | ReqHeaderException("构造 HTTP 头部时发生错误", 1160 | ex), ErrorType.HTTPRequestHeaderError); 1161 | } 1162 | 1163 | 1164 | Stream myResponseStream = null; 1165 | //StreamReader myStreamReader = null; 1166 | 1167 | long responseContentLength = 0; 1168 | Encoding responseEncoding = Encoding.UTF8; 1169 | HttpStatusCode responseStatusCode = 0; 1170 | string responseContentType = ""; 1171 | CookieContainer responseCookieContainer = Params.Cookies; 1172 | 1173 | //POST 数据写入 1174 | if (Method == HttpMethod.Post || Method == HttpMethod.Put) 1175 | { 1176 | using (Stream stream = request.GetRequestStream()) 1177 | { 1178 | //判断 POST 类型 1179 | switch (Params.PostParamsType) 1180 | { 1181 | case PostType.http_content: 1182 | if (Params.PostContent == null) 1183 | { 1184 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1185 | ReqHeaderException("以 HttpContent 类型 POST 时,HttpContent 参数未设置或为空", 1186 | new ArgumentNullException(nameof(Params))), 1187 | ErrorType.ArgumentNull); 1188 | } 1189 | 1190 | request.ContentType = Params.PostContent.Headers.ContentType + ";charset=" + 1191 | Params.PostContent.Headers.ContentEncoding; 1192 | 1193 | await Params.PostContent.CopyToAsync(stream); 1194 | 1195 | break; 1196 | case PostType.x_www_form_urlencoded: 1197 | if (string.IsNullOrEmpty(paramStr)) 1198 | { 1199 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1200 | ReqHeaderException("以 application/x-www-form-urlencoded 类型 POST 时,Params 参数未设置或为空", 1201 | new ArgumentNullException(nameof(Params))), 1202 | ErrorType.ArgumentNull); 1203 | } 1204 | 1205 | request.ContentType = 1206 | "application/x-www-form-urlencoded;charset=" + Params.PostEncoding.WebName; 1207 | byte[] data = Params.PostEncoding.GetBytes(paramStr.ToString()); 1208 | 1209 | await stream.WriteAsync(data, 0, data.Length); 1210 | 1211 | 1212 | break; 1213 | case PostType.form_data: 1214 | if (Params.PostMultiPart is null) 1215 | { 1216 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1217 | ReqHeaderException("以 multipart/formdata 类型 POST 时,PostMultiPart 参数未设置或为空", 1218 | new ArgumentNullException("PostMultiPart")), 1219 | ErrorType.ArgumentNull); 1220 | } 1221 | 1222 | var dat = Params.PostMultiPart; 1223 | var task = dat.ReadAsByteArrayAsync(); 1224 | request.ContentType = dat.Headers.ContentType.ToString(); 1225 | request.ContentLength = dat.Headers.ContentLength.Value; 1226 | 1227 | 1228 | await stream.WriteAsync(task.Result, 0, task.Result.Length); 1229 | 1230 | 1231 | break; 1232 | case PostType.raw: 1233 | if (Params.PostRawData is null || Params.PostRawData.Length == 0) 1234 | { 1235 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1236 | ReqHeaderException("以 Raw 类型 POST 时,PostRawData 参数未设置或为空", 1237 | new ArgumentNullException("PostRawData")), 1238 | ErrorType.ArgumentNull); 1239 | } 1240 | 1241 | 1242 | await stream.WriteAsync(Params.PostRawData, 0, Params.PostRawData.Length); 1243 | 1244 | 1245 | break; 1246 | case PostType.json: 1247 | if (Params.PostJson == null) 1248 | { 1249 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1250 | ReqHeaderException("以 Json 类型 POST 时,PostJson 参数未设置或为空", 1251 | new ArgumentNullException("PostJson")), 1252 | ErrorType.ArgumentNull); 1253 | } 1254 | 1255 | request.ContentType = "application/json;charset=" + Params.PostEncoding.WebName; 1256 | byte[] jsonData; 1257 | if (Params.PostJson is string json) 1258 | { 1259 | jsonData = Params.PostEncoding.GetBytes(json); 1260 | } 1261 | else 1262 | { 1263 | jsonData = Params.PostEncoding.GetBytes(JsonConvert.SerializeObject(Params.PostJson)); 1264 | } 1265 | 1266 | 1267 | await stream.WriteAsync(jsonData, 0, jsonData.Length); 1268 | 1269 | 1270 | break; 1271 | } 1272 | } 1273 | } 1274 | 1275 | bool _isGzipped = false; 1276 | bool _isDeflated = false; 1277 | try 1278 | { 1279 | request.CookieContainer = Params.Cookies; 1280 | HttpWebResponse response = null; 1281 | try 1282 | { 1283 | //开始异步请求 1284 | Task responseTask = request.GetResponseAsync(CancelFlag.Token); 1285 | //如果取消 1286 | if (CancelFlag.IsCancellationRequested) 1287 | { 1288 | return new ReqResponse(request, new MemoryStream(), Params.Cookies, "", 0, new UTF8Encoding(), 1289 | 0, 1290 | false, Params.IsAutoCloseStream); 1291 | } 1292 | else if (await Task.WhenAny(responseTask, Task.Delay(Params.Timeout)) != responseTask) 1293 | { 1294 | return new ReqResponse(request, new MemoryStream(), Params.Cookies, "", 0, new UTF8Encoding(), 1295 | 0, 1296 | false, Params.IsAutoCloseStream); 1297 | } 1298 | 1299 | //异步请求结果 1300 | if (responseTask.IsFaulted) 1301 | { 1302 | //出错继续抛出,若是WebException则仍可以继续,其他Exception由再外层的try-catch捕获 1303 | throw responseTask.Exception.InnerException; 1304 | } 1305 | 1306 | if (responseTask.IsCanceled) 1307 | { 1308 | // HandleError(Params.UseHandler, ReqExceptionHandler, new 1309 | // ReqRequestException("用户主动取消 HTTP 请求", ErrorType.UserCancelled), ErrorType.UserCancelled); 1310 | 1311 | return new ReqResponse(request, new MemoryStream(), Params.Cookies, "", 0, new UTF8Encoding(), 1312 | 0, 1313 | false, Params.IsAutoCloseStream); 1314 | } 1315 | 1316 | response = (HttpWebResponse) responseTask.Result; 1317 | } 1318 | catch (WebException ex) 1319 | { 1320 | //状态码错误 1321 | if (ex.Status == WebExceptionStatus.ProtocolError) 1322 | { 1323 | if (Params.IsThrowErrorForStatusCode) 1324 | { 1325 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1326 | ReqResponseException("HTTP 状态码指示请求发生错误,状态为:" + 1327 | (int) ((HttpWebResponse) ex 1328 | .Response).StatusCode + 1329 | " " + 1330 | ((HttpWebResponse) ex 1331 | .Response).StatusCode, 1332 | ErrorType 1333 | .HTTPStatusCodeError), 1334 | ErrorType.HTTPStatusCodeError); 1335 | } 1336 | } 1337 | //超时 1338 | else if (ex.Status == WebExceptionStatus.Timeout) 1339 | { 1340 | if (Params.IsThrowErrorForTimeout) 1341 | { 1342 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1343 | ReqResponseException("HTTP 请求超时", 1344 | ErrorType 1345 | .HTTPRequestTimeout), 1346 | ErrorType.HTTPRequestTimeout); 1347 | } 1348 | 1349 | return new ReqResponse(request, new MemoryStream(), Params.Cookies, "", 0, new UTF8Encoding(), 1350 | 0, 1351 | false, Params.IsAutoCloseStream); 1352 | } 1353 | //其他未知错误 1354 | else 1355 | { 1356 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1357 | ReqResponseException("HTTP 请求时发生错误", ex), 1358 | ErrorType.HTTPRequestError); 1359 | } 1360 | 1361 | response = (HttpWebResponse) ex.Response; 1362 | } 1363 | 1364 | //确保报错后有Response 1365 | if (response is null) 1366 | { 1367 | return new ReqResponse(request, new MemoryStream(), Params.Cookies, "", 0, new UTF8Encoding(), 0, 1368 | false, Params.IsAutoCloseStream); 1369 | } 1370 | 1371 | _isGzipped = response?.ContentEncoding?.ToLower().Contains("gzip") ?? false; 1372 | _isDeflated = response?.ContentEncoding?.ToLower().Contains("deflate") ?? false; 1373 | //获取响应流 1374 | myResponseStream = response.GetResponseStream(); 1375 | responseContentLength = response.ContentLength; 1376 | // myStreamReader = 1377 | // new StreamReader(myResponseStream ?? throw new ReqResponseException("请求无响应"), 1378 | // Encoding.GetEncoding(response.CharacterSet ?? 1379 | // throw new ReqResponseException("请求无响应"))); 1380 | // 1381 | 1382 | //流转储 1383 | // byte[] buffer = new byte[bufferSize]; 1384 | // int count = 1385 | // await (myResponseStream ?? throw new ReqResponseException("请求无响应", ErrorType.HTTPRequestTimeout)) 1386 | // .ReadAsync(buffer, 0, bufferSize); 1387 | // 1388 | // while (count > 0) 1389 | // { 1390 | // responseStream.Write(buffer, 0, count); 1391 | // count = await myResponseStream.ReadAsync(buffer, 0, bufferSize); 1392 | // } 1393 | 1394 | //编码自动判断 1395 | if (response.CharacterSet != "" && !(response.CharacterSet is null) && 1396 | response.ContentType.Contains("charset")) 1397 | { 1398 | responseEncoding = Encoding.GetEncoding(response.CharacterSet.ToLower() ?? 1399 | throw new ReqResponseException("请求无响应", 1400 | ErrorType.HTTPRequestTimeout)); 1401 | } 1402 | else 1403 | { 1404 | responseEncoding = Encoding.UTF8; 1405 | } 1406 | 1407 | //属性添加 1408 | responseStatusCode = response.StatusCode; 1409 | responseContentType = response.ContentType; 1410 | responseCookieContainer.Add(response.Cookies); 1411 | } 1412 | catch (Exception ex) 1413 | { 1414 | Utils.HandleError(Params.UseHandler, ReqExceptionHandler, new 1415 | ReqResponseException("HTTP 请求或解析响应时发生未知错误", ex), 1416 | ErrorType.Other); 1417 | } 1418 | 1419 | //使用Finally将会导致不弹出错误 1420 | // finally 1421 | // { 1422 | // //myStreamReader?.Close(); 1423 | // myResponseStream?.Close(); 1424 | // } 1425 | return new ReqResponse(request, myResponseStream, responseCookieContainer, responseContentType, 1426 | responseContentLength, 1427 | responseEncoding, 1428 | responseStatusCode, Params.IsUseHtmlMetaEncoding, Params.IsAutoCloseStream 1429 | , _isGzipped, _isDeflated, Params.IsAutoUnGzip, Params.IsAutoUnDeflate); 1430 | } 1431 | } 1432 | } -------------------------------------------------------------------------------- /PyLibSharp.Requests/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.IO.Compression; 7 | using System.Net; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Text.RegularExpressions; 11 | 12 | namespace PyLibSharp.Requests 13 | { 14 | public static class Utils 15 | { 16 | public static string UnGzipFromStreamToString(Stream inputStream) 17 | { 18 | String ungzipped = ""; 19 | using (GZipStream stream = new GZipStream(inputStream, CompressionMode.Decompress)) 20 | { 21 | using (StreamReader reader = new StreamReader(stream)) 22 | { 23 | ungzipped = reader.ReadToEnd(); 24 | } 25 | } 26 | 27 | return ungzipped; 28 | } 29 | 30 | public static string UnDeflateFromStreamToString(Stream inputStream) 31 | { 32 | String undeflated = ""; 33 | using (DeflateStream stream = new DeflateStream(inputStream, CompressionMode.Decompress)) 34 | { 35 | using (StreamReader reader = new StreamReader(stream)) 36 | { 37 | undeflated = reader.ReadToEnd(); 38 | } 39 | } 40 | 41 | return undeflated; 42 | } 43 | 44 | public static void UnGzipFromStreamToStream(Stream inputStream, Stream outputStream) 45 | { 46 | using (GZipStream stream = new GZipStream(inputStream, CompressionMode.Decompress)) 47 | { 48 | stream.CopyTo(outputStream); 49 | } 50 | } 51 | 52 | public static void UnDeflateFromStreamToStream(Stream inputStream,Stream outputStream) 53 | { 54 | using (DeflateStream stream = new DeflateStream(inputStream, CompressionMode.Decompress)) 55 | { 56 | stream.CopyTo(outputStream); 57 | } 58 | } 59 | 60 | /// 61 | /// 根据HTML头部中的meta标记来返回网页编码 62 | /// 63 | /// 64 | /// 65 | public static Encoding GetHtmlEncodingByMetaHeader(string htmlContent) 66 | { 67 | Encoding responseEncoding = Encoding.UTF8; 68 | //通过HTML头部的Meta tag判断编码 69 | var CharSetMatch = 70 | Regex.Match(htmlContent, @" 1 && CharSetMatch[1].Value != "") 73 | { 74 | string overrideCharset = CharSetMatch[1].Value; 75 | responseEncoding = Encoding.GetEncoding(overrideCharset); 76 | } 77 | 78 | return responseEncoding; 79 | } 80 | 81 | /// 82 | /// 链式获取父错误的每一级 InnerException,并作为 List 返回 83 | /// 84 | /// 父级错误 85 | /// 86 | public static List GetInnerExceptionList(Exception exOuter) 87 | { 88 | List lstRet = new List(); 89 | while (exOuter != null) 90 | { 91 | lstRet.Add(exOuter); 92 | exOuter = exOuter.InnerException; 93 | } 94 | 95 | return lstRet; 96 | } 97 | 98 | /// 99 | /// 链式获取父错误的每一级 InnerException 的错误消息,并拼接为字符串返回 100 | /// 每一行代表一级的错误消息,从上到下依次深入 101 | /// 102 | /// 父级错误 103 | /// 104 | public static string GetInnerExceptionMessages(Exception exOuter) 105 | { 106 | StringBuilder sb = new StringBuilder(); 107 | 108 | GetInnerExceptionList(exOuter).ForEach(i => sb.AppendLine(i.Message)); 109 | 110 | return sb.ToString(); 111 | } 112 | 113 | /// 114 | /// 通用错误处理函数 115 | /// 116 | /// 是否使用捕捉器 117 | /// 捕捉器 118 | /// 内部错误 119 | /// 错误类型 120 | internal static void HandleError(bool useHandler, EventHandler handler, 121 | Exception innerException, ErrorType errType) 122 | { 123 | Debug.Print($"Requests库出现错误:({(useHandler ? "使用" : "未使用")}自定义错误捕捉器)" + 124 | $"\r\n报错信息为:" + 125 | $"\r\n{GetInnerExceptionMessages(innerException)}" + 126 | $"\r\n报错堆栈为:" + 127 | $"\r\n{innerException.StackTrace}"); 128 | 129 | if (useHandler) 130 | { 131 | handler?.Invoke(null, 132 | new Requests.AggregateExceptionArgs() 133 | { 134 | AggregateException = 135 | new AggregateException(innerException), 136 | ErrType = errType 137 | }); 138 | } 139 | else 140 | { 141 | throw innerException; 142 | } 143 | } 144 | 145 | public static CookieCollection GetAllCookies(CookieContainer cookieJar) 146 | { 147 | if (!(cookieJar is null) && cookieJar.Count > 0) 148 | { 149 | CookieCollection cookieCollection = new CookieCollection(); 150 | 151 | Hashtable table = (Hashtable) cookieJar.GetType().InvokeMember("m_domainTable", 152 | BindingFlags.NonPublic | 153 | BindingFlags.GetField | 154 | BindingFlags.Instance, 155 | null, 156 | cookieJar, 157 | new object[] { }); 158 | 159 | foreach (var tableKey in table.Keys) 160 | { 161 | String str_tableKey = (string) tableKey; 162 | 163 | if (str_tableKey[0] == '.') 164 | { 165 | str_tableKey = str_tableKey.Substring(1); 166 | } 167 | 168 | SortedList list = (SortedList) table[tableKey].GetType().InvokeMember("m_list", 169 | BindingFlags.NonPublic | 170 | BindingFlags.GetField | 171 | BindingFlags.Instance, 172 | null, 173 | table[tableKey], 174 | new object[] { }); 175 | 176 | foreach (var listKey in list.Keys) 177 | { 178 | String url = "http://" + str_tableKey + (string) listKey; 179 | cookieCollection.Add(cookieJar.GetCookies(new Uri(url))); 180 | } 181 | } 182 | 183 | return cookieCollection; 184 | } 185 | else 186 | { 187 | return new CookieCollection(); 188 | } 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /PyLibSharp.Requests/WebRequestExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace PyLibSharp.Requests 7 | { 8 | 9 | //From https://github.com/openstacknetsdk/openstack.net/blob/master/src/corelib/Core/WebRequestExtensions.cs 10 | //Under MIT License 11 | /// 12 | /// Provides extension methods for asynchronous operations on 13 | /// objects. 14 | /// 15 | /// 16 | /// 17 | public static class WebRequestExtensions 18 | { 19 | /// 20 | /// Returns a response to an Internet request as an asynchronous operation. 21 | /// 22 | /// 23 | /// This operation will not block. The returned object will 24 | /// complete after a response to an Internet request is available. 25 | /// 26 | /// The request. 27 | /// A object which represents the asynchronous operation. 28 | /// If is . 29 | public static Task GetResponseAsync(this WebRequest request) 30 | { 31 | if (request == null) 32 | throw new ArgumentNullException("request"); 33 | 34 | return GetResponseAsync(request, CancellationToken.None); 35 | } 36 | 37 | /// 38 | /// Returns a response to an Internet request as an asynchronous operation. 39 | /// 40 | /// 41 | /// This operation will not block. The returned object will 42 | /// complete after a response to an Internet request is available. 43 | /// 44 | /// The request. 45 | /// The that will be assigned to the new . 46 | /// A object which represents the asynchronous operation. 47 | /// If is . 48 | /// 49 | /// If was previously called. 50 | /// -or- 51 | /// If the timeout period for the request expired. 52 | /// -or- 53 | /// If an error occurred while processing the request. 54 | /// 55 | public static Task GetResponseAsync(this WebRequest request, CancellationToken cancellationToken) 56 | { 57 | if (request == null) 58 | throw new ArgumentNullException("request"); 59 | 60 | bool timeout = false; 61 | TaskCompletionSource completionSource = new TaskCompletionSource(); 62 | 63 | RegisteredWaitHandle timerRegisteredWaitHandle = null; 64 | RegisteredWaitHandle cancellationRegisteredWaitHandle = null; 65 | AsyncCallback completedCallback = 66 | result => 67 | { 68 | try 69 | { 70 | if (cancellationRegisteredWaitHandle != null) 71 | cancellationRegisteredWaitHandle.Unregister(null); 72 | 73 | if (timerRegisteredWaitHandle != null) 74 | timerRegisteredWaitHandle.Unregister(null); 75 | 76 | completionSource.TrySetResult(request.EndGetResponse(result)); 77 | } 78 | catch (WebException ex) 79 | { 80 | if (timeout) 81 | completionSource.TrySetException(new WebException("No response was received during the time-out period for a request.", WebExceptionStatus.Timeout)); 82 | else if (cancellationToken.IsCancellationRequested) 83 | completionSource.TrySetCanceled(); 84 | else 85 | completionSource.TrySetException(ex); 86 | } 87 | catch (Exception ex) 88 | { 89 | completionSource.TrySetException(ex); 90 | } 91 | }; 92 | 93 | IAsyncResult asyncResult = request.BeginGetResponse(completedCallback, null); 94 | if (!asyncResult.IsCompleted) 95 | { 96 | if (request.Timeout != Timeout.Infinite) 97 | { 98 | WaitOrTimerCallback timedOutCallback = 99 | (object state, bool timedOut) => 100 | { 101 | if (timedOut) 102 | { 103 | timeout = true; 104 | request.Abort(); 105 | } 106 | }; 107 | 108 | timerRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, timedOutCallback, null, request.Timeout, true); 109 | } 110 | 111 | if (cancellationToken.CanBeCanceled) 112 | { 113 | WaitOrTimerCallback cancelledCallback = 114 | (object state, bool timedOut) => 115 | { 116 | if (cancellationToken.IsCancellationRequested) 117 | request.Abort(); 118 | }; 119 | 120 | cancellationRegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(cancellationToken.WaitHandle, cancelledCallback, null, Timeout.Infinite, true); 121 | } 122 | } 123 | 124 | return completionSource.Task; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /PyLibSharp.Win32Friendly/PyLibSharp.Win32Friendly.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | true 6 | true 7 | 使用 .Net 根据部分 Python 库的设计思想来封装一些常用功能 8 | Win32 API封装部分 9 | 10 | Use. Net to encapsulate some common functions according to the design idea of some Python libraries 11 | PyLibSharp.Win32Friendly 12 | xiaohe321 13 | xiaohe321 14 | LICENSE.md 15 | https://github.com/xh321/PyLibSharp 16 | zh-CN 17 | 初始版本 18 | 19 | 20 | 21 | C:\Users\xiaohe321\OneDrive\Programming\CSharp\PyLib#\PyLibSharp\PyLibSharp.Win32Friendly\PyLibSharp.Win32Friendly.xml 22 | 23 | 24 | 25 | 26 | True 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /PyLibSharp.Win32Friendly/PyLibSharp.Win32Friendly.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PyLibSharp.Win32Friendly 5 | 6 | 7 | 8 | 9 | 该窗体的标题文本 10 | 11 | 12 | 13 | 14 | 该窗体的类名字符串 15 | 16 | 17 | 18 | 19 | 该窗体的坐标和大小 20 | 坐标取的是窗体左上角的坐标 21 | 22 | 23 | 24 | 25 | 根据指定窗体属性来获取第一个符合条件的子窗体对象 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 根据指定窗体属性来获取第一个符合条件的窗体对象 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 根据指定窗体属性来遍历符合条件的子窗体对象 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 根据指定窗体属性来遍历符合条件的窗体对象 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /PyLibSharp.Win32Friendly/WindowApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | 9 | namespace PyLibSharp.Win32Friendly 10 | { 11 | public static class WindowApi 12 | { 13 | public static Exception lastError; 14 | 15 | public enum MouseButton 16 | { 17 | Left = 0, 18 | Middle = 1, 19 | Right = 2 20 | } 21 | 22 | public static class InternalConst 23 | { 24 | private const uint SWP_NOSIZE = 0x1; //忽略 cx、cy, 保持大小 25 | private const uint SWP_NOMOVE = 0x2; //忽略 X、Y, 不改变位置 26 | private const uint SWP_NOZORDER = 0x4; //忽略 hWndInsertAfter, 保持 Z 顺序 27 | 28 | public const uint WM_MOUSEMOVE = 0x200; 29 | 30 | public const uint WM_LBUTTONDOWN = 0x201; 31 | public const uint WM_LBUTTONUP = 0x202; 32 | public const uint WM_LBUTTONDBLCLK = 0x203; 33 | 34 | public const uint WM_RBUTTONDOWN = 0x204; 35 | public const uint WM_RBUTTONUP = 0x205; 36 | public const uint WM_RBUTTONDBLCLK = 0x206; 37 | 38 | public const uint WM_MBUTTONDOWN = 0x207; 39 | public const uint WM_MBUTTONUP = 0x208; 40 | public const uint WM_MBUTTONDBLCLK = 0x209; 41 | 42 | public const uint WM_CLOSE = 0x010; 43 | 44 | private const int VK_RETURN = 0x0D; 45 | private const int BM_CLICK = 0xF5; 46 | private const int GW_HWNDFIRST = 0; 47 | private const int GW_HWNDNEXT = 2; 48 | private const int GWL_STYLE = (-16); 49 | private const int WS_VISIBLE = 268435456; 50 | private const int WS_BORDER = 8388608; 51 | } 52 | 53 | 54 | public static class InternalApi 55 | { 56 | public delegate bool WndEnumProc(IntPtr hWnd, int lParam); 57 | 58 | [DllImport("user32.dll", SetLastError = true)] 59 | public static extern bool EnumWindows(WndEnumProc lpEnumFunc, int lParam); 60 | 61 | [DllImport("user32.dll")] 62 | public static extern int EnumChildWindows(IntPtr hWndParent, WndEnumProc lpfn, int lParam); 63 | 64 | [DllImport("user32.dll", SetLastError = true)] 65 | public static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect); 66 | 67 | [DllImport("user32.dll", SetLastError = true, EntryPoint = "FindWindowW", CharSet = CharSet.Unicode)] 68 | public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 69 | 70 | [DllImport("user32.dll", SetLastError = true, EntryPoint = "FindWindowExW", CharSet = CharSet.Unicode)] 71 | public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, 72 | string lpszWindow); 73 | 74 | [DllImport("user32", SetLastError = true)] 75 | public static extern bool PostMessage( 76 | IntPtr hWnd, 77 | uint Msg, 78 | int wParam, 79 | int lParam 80 | ); 81 | 82 | [DllImport("user32.dll", EntryPoint = "SendMessageA", SetLastError = true)] 83 | public static extern int SendMessage(IntPtr hwnd, uint wMsg, int wParam, int lParam); 84 | 85 | [DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowTextW", CharSet = CharSet.Unicode)] 86 | public static extern int GetWindowText(IntPtr hWnd, 87 | [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpString, 88 | int nMaxCount); 89 | 90 | [DllImport("user32.dll", SetLastError = true, EntryPoint = "GetClassNameW", CharSet = CharSet.Unicode)] 91 | public static extern int GetClassName(IntPtr hWnd, 92 | [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpString, 93 | int nMaxCount); 94 | 95 | [DllImport("user32.dll", SetLastError = true)] 96 | public static extern IntPtr GetForegroundWindow(); 97 | 98 | [DllImport("user32.dll", SetLastError = true)] 99 | public static extern int GetWindow(int hWnd, int wCmd); 100 | 101 | [DllImport("user32.dll", SetLastError = true)] 102 | public static extern int GetWindowLongA(int hWnd, int wIndx); 103 | 104 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 105 | public static extern int GetWindowTextLength(IntPtr hWnd); 106 | 107 | [DllImport("user32.dll", SetLastError = true)] 108 | public static extern int GetWindowThreadProcessId(IntPtr hWnd, ref int lpdwProcessId); 109 | 110 | [DllImport("user32.dll", SetLastError = true)] 111 | public static extern bool IsWindowVisible(IntPtr hWnd); 112 | 113 | [DllImport("user32.dll", SetLastError = true)] 114 | public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndlnsertAfter, int X, int Y, int cx, int cy, 115 | uint Flags); 116 | 117 | public struct Rect 118 | { 119 | public int Left; 120 | public int Top; 121 | public int Right; 122 | public int Bottom; 123 | } 124 | 125 | public struct WindowInfo 126 | { 127 | public IntPtr hWnd; 128 | public string szWindowName; 129 | public string szClassName; 130 | public string currStatus; 131 | } 132 | } 133 | 134 | public class Win32Window : IEnumerable 135 | { 136 | private StringBuilder _buffer; 137 | 138 | /// 139 | /// 该窗体的标题文本 140 | /// 141 | public string WindowText { get; set; } 142 | 143 | /// 144 | /// 该窗体的类名字符串 145 | /// 146 | public string ClassName { get; set; } 147 | 148 | /// 149 | /// 该窗体的坐标和大小 150 | /// 坐标取的是窗体左上角的坐标 151 | /// 152 | public Rectangle WindowPosition { get; set; } 153 | 154 | private IntPtr _internalPtr; 155 | 156 | public IntPtr Handle 157 | { 158 | get => _internalPtr; 159 | set 160 | { 161 | if (ApiOptions.IsThrowErrorWhenHandleIsNull && 162 | value == IntPtr.Zero) throw new ArgumentNullException(nameof(value), "该句柄无效或未找到对应窗口"); 163 | 164 | try 165 | { 166 | _internalPtr = value; 167 | bool isSuccGetText = InternalApi.GetWindowText(value, _buffer, 255) > 0; 168 | if (isSuccGetText) 169 | WindowText = _buffer.ToString(); 170 | else 171 | WindowText = ""; 172 | 173 | bool isSuccGetClass = InternalApi.GetClassName(value, _buffer, 255) > 0; 174 | if (isSuccGetClass) 175 | ClassName = _buffer.ToString(); 176 | else 177 | ClassName = ""; 178 | 179 | bool isSuccGetRect = InternalApi.GetWindowRect(value, out InternalApi.Rect rect) > 0; 180 | if (isSuccGetRect) 181 | { 182 | WindowPosition = new Rectangle(new Point(rect.Left, rect.Top), 183 | new Size(rect.Right - rect.Left, rect.Bottom - rect.Top)); 184 | } 185 | else 186 | { 187 | WindowPosition = new Rectangle(); 188 | } 189 | } 190 | catch (Exception eX) 191 | { 192 | lastError = eX; 193 | } 194 | } 195 | } 196 | 197 | private void initBuffer(StringBuilder sbBuffer) 198 | { 199 | if (sbBuffer is null) _buffer = new StringBuilder(256); 200 | else _buffer = sbBuffer; 201 | } 202 | 203 | public Win32Window(IntPtr hWnd, StringBuilder sbBuffer = null) 204 | { 205 | initBuffer(sbBuffer); 206 | Handle = hWnd; 207 | } 208 | 209 | public Win32Window(string sWindowText, StringBuilder sbBuffer = null) 210 | { 211 | initBuffer(sbBuffer); 212 | Handle = InternalApi.FindWindow(null, sWindowText); 213 | } 214 | 215 | public Win32Window(string sWindowText, string sClassName, StringBuilder sbBuffer = null) 216 | { 217 | initBuffer(sbBuffer); 218 | Handle = InternalApi.FindWindow(sClassName, sWindowText); 219 | } 220 | 221 | public void Click(MouseButton btn = MouseButton.Left) 222 | { 223 | uint eMove = InternalConst.WM_MOUSEMOVE; 224 | uint eMDown = 0, eMClk = 0, eMUp = 0; 225 | switch (btn) 226 | { 227 | case MouseButton.Left: 228 | eMDown = InternalConst.WM_LBUTTONDOWN; 229 | eMClk = InternalConst.WM_LBUTTONDBLCLK; 230 | eMUp = InternalConst.WM_LBUTTONUP; 231 | break; 232 | case MouseButton.Middle: 233 | eMDown = InternalConst.WM_MBUTTONDOWN; 234 | eMClk = InternalConst.WM_MBUTTONDBLCLK; 235 | eMUp = InternalConst.WM_MBUTTONUP; 236 | break; 237 | case MouseButton.Right: 238 | eMDown = InternalConst.WM_RBUTTONDOWN; 239 | eMClk = InternalConst.WM_RBUTTONDBLCLK; 240 | eMUp = InternalConst.WM_RBUTTONUP; 241 | break; 242 | } 243 | 244 | InternalApi.PostMessage(Handle, eMDown, 0x2, 1 + 1 * 65536); 245 | InternalApi.PostMessage(Handle, eMClk, 0x1, 1 + 1 * 65536); 246 | InternalApi.PostMessage(Handle, eMUp, 0x0, 1 + 1 * 65536); 247 | } 248 | 249 | public void Close() 250 | { 251 | InternalApi.PostMessage(Handle, InternalConst.WM_CLOSE, 0, 0); 252 | } 253 | 254 | public IEnumerator GetEnumerator() 255 | { 256 | //已经访问过的句柄,不再重复访问 257 | List lstVisitedHandles = new List(); 258 | 259 | //先遍历一遍所有句柄,预筛选 260 | InternalApi.EnumChildWindows(Handle, (h, l) => 261 | { 262 | //窗口不可见 263 | if (ApiOptions.IsSkipInvisibleWindow && 264 | !InternalApi.IsWindowVisible(h)) return true; 265 | 266 | //如果已经访问过,则看下一个 267 | if (lstVisitedHandles.Contains(h)) return true; 268 | lstVisitedHandles.Add(h); 269 | 270 | return true; 271 | }, 0); 272 | 273 | StringBuilder buffer = new StringBuilder(256); 274 | //最后再次筛选,并获取对应属性 275 | foreach (IntPtr hWnd in lstVisitedHandles) 276 | { 277 | Win32Window currentWindow = new Win32Window(hWnd, buffer); 278 | 279 | if (ApiOptions.IsSkipEmptyWindowText && currentWindow.WindowText == "") 280 | continue; 281 | 282 | if (ApiOptions.IsSkipEmptyWindowClass && currentWindow.ClassName == "") 283 | continue; 284 | 285 | Debug.Print(currentWindow.WindowText); 286 | yield return currentWindow; 287 | } 288 | } 289 | 290 | IEnumerator IEnumerable.GetEnumerator() 291 | { 292 | return GetEnumerator(); 293 | } 294 | } 295 | 296 | public static class ApiOptions 297 | { 298 | public static bool IsSkipEmptyWindowClass { get; set; } = true; 299 | public static bool IsSkipEmptyWindowText { get; set; } = true; 300 | public static bool IsSkipInvisibleWindow { get; set; } = true; 301 | public static bool IsThrowErrorWhenHandleIsNull { get; set; } = true; 302 | } 303 | 304 | /// 305 | /// 根据指定窗体属性来获取第一个符合条件的子窗体对象 306 | /// 307 | /// 308 | /// 309 | /// 310 | /// 311 | public static Win32Window GetSubWindowByProps(this Win32Window wParent, string sWindowText = null, 312 | string sClassName = null) 313 | => GetWindowByProps(wParent.Handle, sWindowText, sClassName); 314 | 315 | 316 | /// 317 | /// 根据指定窗体属性来获取第一个符合条件的窗体对象 318 | /// 319 | /// 320 | /// 321 | /// 322 | /// 323 | public static Win32Window GetWindowByProps(IntPtr hParent, string sWindowText = null, 324 | string sClassName = null) 325 | { 326 | IntPtr retPtr = IntPtr.Zero; 327 | retPtr = 328 | InternalApi.FindWindowEx(hParent, IntPtr.Zero, sClassName, sWindowText); 329 | 330 | return new Win32Window(retPtr); 331 | } 332 | 333 | 334 | /// 335 | /// 根据指定窗体属性来遍历符合条件的子窗体对象 336 | /// 337 | /// 338 | /// 339 | /// 340 | /// 341 | public static IEnumerable GetSubWindowsByProps(this Win32Window wParent, string sWindowText = null, 342 | string sClassName = null) 343 | => GetWindowsByProps(wParent.Handle, sWindowText, sClassName); 344 | 345 | 346 | /// 347 | /// 根据指定窗体属性来遍历符合条件的窗体对象 348 | /// 349 | /// 350 | /// 351 | /// 352 | /// 353 | public static IEnumerable GetWindowsByProps(IntPtr hParent, string sWindowText = null, 354 | string sClassName = null) 355 | { 356 | //预留buffer,节约时间和空间 357 | StringBuilder buffer = new StringBuilder(256); 358 | IntPtr lastPtr = IntPtr.Zero; 359 | IntPtr retPtr = IntPtr.Zero; 360 | List visitedPtr = new List(); 361 | do 362 | { 363 | retPtr = 364 | InternalApi.FindWindowEx(hParent, lastPtr, sClassName, sWindowText); 365 | if (visitedPtr.Contains(retPtr)) continue; 366 | visitedPtr.Add(retPtr); 367 | 368 | lastPtr = retPtr; 369 | //窗口文本空 370 | if (ApiOptions.IsSkipEmptyWindowText && sWindowText == "") 371 | { 372 | continue; 373 | } 374 | 375 | //窗口类名空 376 | if (ApiOptions.IsSkipEmptyWindowClass && sClassName == "") 377 | { 378 | continue; 379 | } 380 | 381 | //窗口不可见 382 | if (ApiOptions.IsSkipInvisibleWindow && !InternalApi.IsWindowVisible(retPtr)) 383 | { 384 | continue; 385 | } 386 | 387 | yield return new Win32Window(retPtr, buffer); 388 | } while (retPtr != IntPtr.Zero); 389 | } 390 | } 391 | } -------------------------------------------------------------------------------- /PyLibSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30406.217 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestCore", "TestCore\TestCore.csproj", "{02D4204E-2BE2-4C04-A634-AC5F9333C97B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestFramework", "TestProject\TestProject\TestFramework.csproj", "{96634720-1057-4909-AB79-4618FA6127D4}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PyLibSharp.Requests", "PyLibSharp.Requests\PyLibSharp.Requests.csproj", "{974B9F2B-D0C6-4544-9500-8B0C826AB35F}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PyLibSharp.Common", "PyLibSharp.Common\PyLibSharp.Common.csproj", "{53E16169-2A54-48F9-A3AA-DEF280160B49}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PyLibSharp.Win32Friendly", "PyLibSharp.Win32Friendly\PyLibSharp.Win32Friendly.csproj", "{70FD4BD9-B7A4-41B9-968E-2189C36C7696}" 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 | {02D4204E-2BE2-4C04-A634-AC5F9333C97B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {02D4204E-2BE2-4C04-A634-AC5F9333C97B}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {02D4204E-2BE2-4C04-A634-AC5F9333C97B}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {02D4204E-2BE2-4C04-A634-AC5F9333C97B}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {96634720-1057-4909-AB79-4618FA6127D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {96634720-1057-4909-AB79-4618FA6127D4}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {96634720-1057-4909-AB79-4618FA6127D4}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {96634720-1057-4909-AB79-4618FA6127D4}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {974B9F2B-D0C6-4544-9500-8B0C826AB35F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {974B9F2B-D0C6-4544-9500-8B0C826AB35F}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {974B9F2B-D0C6-4544-9500-8B0C826AB35F}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {974B9F2B-D0C6-4544-9500-8B0C826AB35F}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {53E16169-2A54-48F9-A3AA-DEF280160B49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {53E16169-2A54-48F9-A3AA-DEF280160B49}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {53E16169-2A54-48F9-A3AA-DEF280160B49}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {53E16169-2A54-48F9-A3AA-DEF280160B49}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {70FD4BD9-B7A4-41B9-968E-2189C36C7696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {70FD4BD9-B7A4-41B9-968E-2189C36C7696}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {70FD4BD9-B7A4-41B9-968E-2189C36C7696}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {70FD4BD9-B7A4-41B9-968E-2189C36C7696}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {2F7F1EB0-5706-4C72-953F-8B3789B017DC} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | 2 | 我是README(什么都没有) 3 | -------------------------------------------------------------------------------- /TestCore/Program.cs: -------------------------------------------------------------------------------- 1 | using PyLibSharp.Requests; 2 | using System; 3 | 4 | namespace TestCore6 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | var req = 11 | Requests.Get("https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history",new ReqParams() 12 | { 13 | Params = 14 | { 15 | {"host_uid","123"}, 16 | {"offset_dynamic_id","1"} 17 | } 18 | }); 19 | var res = req; 20 | Console.WriteLine(res); 21 | 22 | // var str = Requests.Get("https://www.baidu.com", new ReqParams() 23 | // { 24 | // Timeout = 10000, 25 | // IsThrowErrorForStatusCode = false, 26 | // IsAutoCloseStream = false 27 | // }); 28 | // byte[] buffer = new byte[50]; 29 | // str.OutputStream.Read(buffer, 0,50); 30 | // Console.WriteLine(Encoding.UTF8.GetString(buffer)); 31 | // 32 | // str.OutputStream.Read(buffer, 0, 50); 33 | // Console.WriteLine(Encoding.UTF8.GetString(buffer)); 34 | // 35 | // Console.WriteLine("结束"); 36 | // Console.WriteLine(str.StatusCode); 37 | // 38 | // foreach (string s in str) 39 | // { 40 | // Console.Write(s); 41 | // } 42 | // 43 | 44 | 45 | Console.ReadKey(); 46 | } 47 | 48 | private static void Requests_ReqExceptionHandler(object sender, Requests.AggregateExceptionArgs e) 49 | { 50 | Console.WriteLine(e.AggregateException); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /TestCore/TestCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /TestProject/TestProject/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TestProject/TestProject/Program.cs: -------------------------------------------------------------------------------- 1 | using PyLibSharp.Requests; 2 | using System; 3 | 4 | namespace TestProject 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | 11 | var str = Requests.XHR(@"GET /baidu?tn=monline_3_dg&ie=utf-8&wd=12321312 HTTP/1.1 12 | Host: www.baidu.com 13 | User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0 14 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 15 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 16 | Accept-Encoding: gzip, deflate, br 17 | Connection: keep-alive 18 | Referer: https://i.g-fox.cn/rd29.html?engine=baidu_web&q=12321312 19 | Cookie: BAIDUID=0D1F081DFFC8FF3C53817A1405D947C6:FG=1; BIDUPSID=0D1F081DFFC8FF3CC49C20C8A2AE788C; PSTM=1597464138; COOKIE_SESSION=0_0_0_0_1_0_0_0_0_0_0_0_0_0_20_0_1598617462_0_1598617442%7C1%230_0_1598617442%7C1 20 | Upgrade-Insecure-Requests: 1 21 | Pragma: no-cache 22 | Cache-Control: no-cache"); 23 | 24 | Console.WriteLine("结束"); 25 | Console.WriteLine(str.Text); 26 | Console.ReadKey(); 27 | } 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /TestProject/TestProject/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的一般信息由以下 6 | // 控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("TestProject")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TestProject")] 13 | [assembly: AssemblyCopyright("Copyright © 2020")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 会使此程序集中的类型 18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 19 | //请将此类型的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("96634720-1057-4909-ab79-4618fa6127d4")] 24 | 25 | // 程序集的版本信息由下列四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 33 | //通过使用 "*",如下所示: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /TestProject/TestProject/TestFramework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {96634720-1057-4909-AB79-4618FA6127D4} 8 | Exe 9 | TestProject 10 | TestProject 11 | v4.8 12 | 512 13 | true 14 | true 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | latest 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | latest 37 | 38 | 39 | 40 | ..\..\PyLibSharp\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | ..\..\PyLibSharp\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll 46 | 47 | 48 | ..\..\PyLibSharp\packages\System.Text.Encoding.CodePages.4.7.1\lib\net461\System.Text.Encoding.CodePages.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {974b9f2b-d0c6-4544-9500-8b0c826ab35f} 68 | PyLibSharp.Requests 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /TestProject/TestProject/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------