C# 使用Proxy代理請(qǐng)求資源的方法步驟
前言
這是上周在開(kāi)發(fā) C# 中使用 Proxy
代理時(shí)開(kāi)發(fā)的一些思考和實(shí)踐。主要需求是這樣的,用戶可以配置每次請(qǐng)求是否需要代理,用戶可以配置 HTTP
代理,HTTPS
代理和代理白名單。
還是太年輕
因?yàn)橐恢庇玫腃# 網(wǎng)絡(luò)庫(kù)中的HttpWebRequest,所以自然而然先去找找看這個(gè)網(wǎng)絡(luò)庫(kù)有沒(méi)有封裝好我所需要的代理呀。果不其然,被我找到了。自從上次發(fā)現(xiàn)某些類(lèi)對(duì)老版本不兼容后,每次在微軟官方文檔上找到都會(huì)翻到最后,查看一下支持的最低框架。
我需要的就是這個(gè) Proxy
屬性,也就是說(shuō)我最終在發(fā)送請(qǐng)求前,設(shè)置好這個(gè) Proxy
屬性就可以了。先去看看 Proxy
類(lèi)
The IWebProxy object to use to proxy the request. The default value is set by calling the Select property.
這樣的意思就是說(shuō)我只要構(gòu)造一個(gè)WebProxy
,然后賦值給 HttpWebRequest.Proxy
就可以了。
看到了WebProxy
的構(gòu)造器,馬上鎖定了
因?yàn)槲倚枰脩魝鞯氖?string
,所以直接這樣構(gòu)造就可以了。然后就是測(cè)試了,主管大佬寫(xiě)的 Node.js
的Proxy
代理 o_o 先來(lái)測(cè)試測(cè)試
npm install o_o -g o_o
這樣就啟動(dòng)全局安裝并啟動(dòng)了代理,在控制臺(tái)上可以看到監(jiān)聽(tīng)的是 8989 端口
[Fact] public void HttpProxy() { var request = new DescribeAccessPointsRequest(); client.SetHttpProxy("http://localhost:8989"); var response = client.GetAcsResponse(request); Assert.NotNull(response.HttpResponse.Content); var expectValue = "HTTP/1.1 o_o"; string actualValue; response.HttpResponse.Headers.TryGetValue("Via", out actualValue); Assert.Equal(expectValue, actualValue); }
如果經(jīng)過(guò)了代理,頭部會(huì)出現(xiàn) "HTTP/1.1 o_o"
字段 ,經(jīng)過(guò)FT
測(cè)試,是成功的。
本來(lái)一切都沒(méi)有問(wèn)題的,除了我自己想的比較簡(jiǎn)單外,直到我 Code Review 了一下組里開(kāi)發(fā)JAVA 的人實(shí)現(xiàn)這個(gè)功能的 Pull Request ,我才發(fā)現(xiàn)我還真的是想的太簡(jiǎn)單?。?!
開(kāi)始重構(gòu)
首先發(fā)現(xiàn)的一點(diǎn)是,我連Constructor
都用錯(cuò)了,用ILSpy
反編譯了一下,發(fā)現(xiàn)WebProxy(string,bool,string[])
所作的事。
// System.Net.WebProxy private static Uri CreateProxyUri(string address) { if (address == null) { return null; } if (address.IndexOf("://") == -1) { address = "http://" + address; } return new Uri(address); }
即使傳進(jìn)去的是string,最后也是構(gòu)造成 Uri, 為什么會(huì)關(guān)注的這個(gè)呢?因?yàn)槲野l(fā)現(xiàn)有些Proxy地址是
http://username:password@localhost:8989
長(zhǎng)這樣的,那么我如果直接以這種形式傳入到CreateProxy
里面,它會(huì)自動(dòng)給我分解,然后分Credential
和 proxy
傳入到網(wǎng)絡(luò)庫(kù)中嗎?接下來(lái)就是驗(yàn)證的過(guò)程。
首先需要了解到的一個(gè)概念:Basic access authentication
In the context of an HTTP transaction, basic access authentication is a method for an HTTP user agent (e.g. a web browser) to provide a user name and password when making a request. In basic HTTP authentication, a request contains a header field of the form Authorization: Basic <credentials>
, where credentials is the base64 encoding of id and password joined by a colon.
It is specified in RFC 7617 from 2015, which obsoletes RFC 2617 from 1999.
由于其不安全性,已在 RFC 中棄用了,轉(zhuǎn)而代之的是 TLS SSL 那些協(xié)議。
問(wèn)題來(lái)了, HttpWebRequest
中支持Basic Authentication
嗎?我們可以看到WebProxy
中有一個(gè)構(gòu)造方法最后一個(gè)參數(shù)是 ICredential 的
是的,就是它,知道前因后果和不足后,我繼續(xù)去重構(gòu) Http Proxy
的代碼:
originProxyUri = new Uri(proxy); if (!String.IsNullOrEmpty(originProxyUri.UserInfo)) { authorization = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(originProxyUri.UserInfo)); finalProxyUri = new Uri(originProxyUri.Scheme + "://" + originProxyUri.Authority); var userInfoArray = originProxyUri.UserInfo.Split(':'); credential = new NetworkCredential(userInfoArray[0], userInfoArray[1]); httpRequest.WebProxy = new WebProxy(finalProxyUri, false, noProxy, credential); }
先拆分出 UserInfo Credential
和 Uri
信息,然后分別重新構(gòu)造相應(yīng)的類(lèi)型傳入到 WebProxy
中。上面也有一個(gè)坑,我之前還想用正則把username
和password
分別提取出去了,沒(méi)想到 Uri 已經(jīng)封裝好了,直接取里面的userinfo
信息。哈哈,省力了。
StackOverFlow
上也有挺多關(guān)于如何傳入 Credential
到Proxy
中,基本上用的也是這個(gè)方法,按理說(shuō)這樣就完事了,直到我做了測(cè)試,我發(fā)現(xiàn)微軟這個(gè)Credential
根本沒(méi)有起作用,如果是正確的話,會(huì)在 HEADER
中添加
Authorization: Basic <credentials>
,和上面那段測(cè)試代碼一樣,
[Fact] public void HttpProxyWithCredential() { DescribeAccessPointsRequest request = new DescribeAccessPointsRequest(); client.SetHttpProxy("http://username:password@localhost:8989"); var response = client.GetAcsResponse(request); var expectValue = "HTTP/1.1 o_o"; string actualValue; response.HttpResponse.Headers.TryGetValue("Via", out actualValue); Assert.Equal(expectValue, actualValue); Assert.NotNull(response.HttpResponse.Content); }
我去測(cè)試了發(fā)現(xiàn),這個(gè)頭部里面根本沒(méi)有加這個(gè) Authorization
屬性啊,尷尬了,是官方文檔坑還是我使用不正確呢,基于此,想到了之前 主管 開(kāi)發(fā)的那個(gè) Proxy
代理 o_o
,我又去找了一個(gè)驗(yàn)證 basic-auth
的node.js
代理服務(wù)器 basic-auth
npm install basic-auth
var http = require('http') var auth = require('basic-auth') var compare = require('tsscmp') // Create server var server = http.createServer(function (req, res) { var credentials = auth(req) // Check credentials // The "check" function will typically be against your user store if (!credentials || !check(credentials.name, credentials.pass)) { res.statusCode = 401 res.setHeader('WWW-Authenticate', 'Basic realm="example"') res.end('Access denied') } else { res.end('Access granted') } }) // Basic function to validate credentials for example function check (name, pass) { var valid = true // Simple method to prevent short-circut and use timing-safe compare valid = compare(name, 'john') && valid valid = compare(pass, 'secret') && valid return valid } // Listen server.listen(3000)
將上面那段 Js代碼打包成一個(gè) js文件,然后執(zhí)行
node tets.js
該代理服務(wù)器監(jiān)聽(tīng) 3000端口,我使用剛才那段代碼,果不其然,返回的是 401 ,這不是坑嗎,官方文檔上這樣說(shuō)可以,然而都不行。
最后只能強(qiáng)制加上這個(gè) Authorization
代碼
originProxyUri = new Uri(proxy); if (!String.IsNullOrEmpty(originProxyUri.UserInfo)) { authorization = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(originProxyUri.UserInfo)); finalProxyUri = new Uri(originProxyUri.Scheme + "://" + originProxyUri.Authority); var userInfoArray = originProxyUri.UserInfo.Split(':'); credential = new NetworkCredential(userInfoArray[0], userInfoArray[1]); httpRequest.WebProxy = new WebProxy(finalProxyUri, false, noProxy, credential); httpRequest.Headers.Add("Authorization", "Basic " + authorization); }
最后在測(cè)試經(jīng)過(guò) 3000 端口的代理服務(wù)器,確認(rèn)是沒(méi)問(wèn)題的,把問(wèn)題想得簡(jiǎn)單的結(jié)果就是發(fā)了一個(gè)新版本后,還沒(méi)有下載,然而已經(jīng)發(fā)了新版本說(shuō),用戶您好,我們又有新版本了。尷尬。需要以此為鑒啊。
后記
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Nginx服務(wù)器的反向代理proxy_pass配置方法講解
- Vue中如何實(shí)現(xiàn)proxy代理
- php設(shè)計(jì)模式 Proxy (代理模式)
- 詳解設(shè)計(jì)模式中的proxy代理模式及在Java程序中的實(shí)現(xiàn)
- vue 設(shè)置proxyTable參數(shù)進(jìn)行代理跨域
- 詳解nodejs通過(guò)代理(proxy)發(fā)送http請(qǐng)求(request)
- Python開(kāi)發(fā)中爬蟲(chóng)使用代理proxy抓取網(wǎng)頁(yè)的方法示例
- Java設(shè)計(jì)模式之代理模式(Proxy模式)介紹
相關(guān)文章
C# mysql 插入數(shù)據(jù),中文亂碼的解決方法
用C#操作mysql時(shí), 插入數(shù)據(jù)中文都是亂碼,只顯示問(wèn)號(hào),數(shù)據(jù)庫(kù)本身使用的是utf-8字符2013-10-10百度人臉識(shí)別之人臉識(shí)別FaceIdentify(簽到考勤)
這篇文章主要為大家詳細(xì)介紹了百度人臉識(shí)別之人臉識(shí)別FaceIdentify,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08C# Form自定義光標(biāo)的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了C# Form自定義光標(biāo)的簡(jiǎn)單實(shí)現(xiàn),有需要的朋友可以參考一下2014-01-01說(shuō)說(shuō)C#的async和await的具體用法
本篇文章主要介紹了說(shuō)說(shuō)C#的async和await的具體用法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09C#實(shí)現(xiàn)合并多個(gè)word文檔的方法
這篇文章主要介紹了C#實(shí)現(xiàn)合并多個(gè)word文檔的方法,是C#針對(duì)Word文檔操作的一個(gè)非常重要的技巧,需要的朋友可以參考下2014-09-09C#使用DateAndTime.DateDiff實(shí)現(xiàn)計(jì)算年齡
這篇文章主要為大家詳細(xì)介紹了C#如何使用DateAndTime.DateDiff實(shí)現(xiàn)根據(jù)生日計(jì)算年齡,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2024-01-01C#使用CefSharp控件實(shí)現(xiàn)爬蟲(chóng)
這篇文章介紹了C#使用CefSharp控件實(shí)現(xiàn)爬蟲(chóng)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06