溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C# 使用Proxy代理請(qǐng)求資源的方法步驟

發(fā)布時(shí)間:2020-09-13 18:47:18 來源:腳本之家 閱讀:378 作者:BUTTERAPPLE 欄目:編程語言

前言

這是上周在開發(fā) C# 中使用 Proxy 代理時(shí)開發(fā)的一些思考和實(shí)踐。主要需求是這樣的,用戶可以配置每次請(qǐng)求是否需要代理,用戶可以配置 HTTP代理,HTTPS代理和代理白名單。

還是太年輕

因?yàn)橐恢庇玫腃# 網(wǎng)絡(luò)庫中的HttpWebRequest,所以自然而然先去找找看這個(gè)網(wǎng)絡(luò)庫有沒有封裝好我所需要的代理呀。果不其然,被我找到了。自從上次發(fā)現(xiàn)某些類對(duì)老版本不兼容后,每次在微軟官方文檔上找到都會(huì)翻到最后,查看一下支持的最低框架。

C# 使用Proxy代理請(qǐng)求資源的方法步驟

我需要的就是這個(gè) Proxy 屬性,也就是說我最終在發(fā)送請(qǐng)求前,設(shè)置好這個(gè) Proxy 屬性就可以了。先去看看 Proxy

The IWebProxy object to use to proxy the request. The default value is set by calling the Select property.

這樣的意思就是說我只要構(gòu)造一個(gè)WebProxy,然后賦值給 HttpWebRequest.Proxy就可以了。

看到了WebProxy 的構(gòu)造器,馬上鎖定了

C# 使用Proxy代理請(qǐng)求資源的方法步驟

因?yàn)槲倚枰脩魝鞯氖?string ,所以直接這樣構(gòu)造就可以了。然后就是測(cè)試了,主管大佬寫的 Node.jsProxy代理 o_o 先來測(cè)試測(cè)試

npm install o_o -g

o_o

這樣就啟動(dòng)全局安裝并啟動(dòng)了代理,在控制臺(tái)上可以看到監(jiān)聽的是 8989 端口

C# 使用Proxy代理請(qǐng)求資源的方法步驟

 [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)過了代理,頭部會(huì)出現(xiàn) "HTTP/1.1 o_o" 字段 ,經(jīng)過FT測(cè)試,是成功的。

本來一切都沒有問題的,除了我自己想的比較簡(jiǎn)單外,直到我 Code Review 了一下組里開發(fā)JAVA 的人實(shí)現(xiàn)這個(gè)功能的 Pull Request ,我才發(fā)現(xiàn)我還真的是想的太簡(jiǎn)單?。?!

開始重構(gòu)

首先發(fā)現(xiàn)的一點(diǎn)是,我連Constructor都用錯(cuò)了,用ILSpy反編譯了一下,發(fā)現(xiàn)WebProxy(string,bool,string[])所作的事。

C# 使用Proxy代理請(qǐng)求資源的方法步驟

// 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)給我分解,然后分Credentialproxy 傳入到網(wǎng)絡(luò)庫中嗎?接下來就是驗(yàn)證的過程。

首先需要了解到的一個(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é)議。

問題來了, HttpWebRequest 中支持Basic Authentication嗎?我們可以看到WebProxy中有一個(gè)構(gòu)造方法最后一個(gè)參數(shù)是 ICredential 的

C# 使用Proxy代理請(qǐng)求資源的方法步驟

是的,就是它,知道前因后果和不足后,我繼續(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 CredentialUri信息,然后分別重新構(gòu)造相應(yīng)的類型傳入到 WebProxy 中。上面也有一個(gè)坑,我之前還想用正則把usernamepassword 分別提取出去了,沒想到 Uri 已經(jīng)封裝好了,直接取里面的userinfo 信息。哈哈,省力了。

StackOverFlow上也有挺多關(guān)于如何傳入 CredentialProxy中,基本上用的也是這個(gè)方法,按理說這樣就完事了,直到我做了測(cè)試,我發(fā)現(xiàn)微軟這個(gè)Credential根本沒有起作用,如果是正確的話,會(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è)頭部里面根本沒有加這個(gè) Authorization 屬性啊,尷尬了,是官方文檔坑還是我使用不正確呢,基于此,想到了之前 主管 開發(fā)的那個(gè) Proxy 代理 o_o ,我又去找了一個(gè)驗(yàn)證 basic-authnode.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)聽 3000端口,我使用剛才那段代碼,果不其然,返回的是 401 ,這不是坑嗎,官方文檔上這樣說可以,然而都不行。

最后只能強(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)過 3000 端口的代理服務(wù)器,確認(rèn)是沒問題的,把問題想得簡(jiǎn)單的結(jié)果就是發(fā)了一個(gè)新版本后,還沒有下載,然而已經(jīng)發(fā)了新版本說,用戶您好,我們又有新版本了。尷尬。需要以此為鑒啊。

后記

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI