C# 使用 Proxy 请求资源,基于 HttpWebRequest 类

前言

这是上周在开发 C# 中使用 Proxy 代理时开发的一些思考和实践。主要需求是这样的,用户可以配置每次请求是否需要代理,用户可以配置 HTTP代理,HTTPS代理和代理白名单。

还是太年轻

因为一直用的C# 网络库中的HttpWebRequest,所以自然而然先去找找看这个网络库有没有封装好我所需要的代理呀。果不其然,被我找到了。自从上次发现某些类对老版本不兼容后,每次在微软官方文档上找到都会翻到最后,查看一下支持的最低框架。

我需要的就是这个 Proxy 属性,也就是说我最终在发送请求前,设置好这个 Proxy 属性就可以了。先去看看 Proxy

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

这样的意思就是说我只要构造一个WebProxy,然后赋值给 HttpWebRequest.Proxy 就可以了。

看到了 WebProxy 的构造器,马上锁定了

因为我需要用户传的是 string ,所以直接这样构造就可以了。然后就是测试了,主管大佬写的 Node.js Proxy代理 o_o 先来测试测试

npm install o_o -g

o_o

这样就启动全局安装并启动了代理,在控制台上可以看到监听的是 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);
}

如果经过了代理,头部会出现 "HTTP/1.1 o_o" 字段 ,经过FT测试,是成功的。

本来一切都没有问题的,除了我自己想的比较简单外,直到我 Code Review 了一下组里开发JAVA 的人实现这个功能的 Pull Request ,我才发现我还真的是想的太简单!!!

开始重构

首先发现的一点是,我连Constructor都用错了,用ILSpy 反编译了一下,发现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);
}

即使传进去的是string,最后也是构造成 Uri, 为什么会关注的这个呢?因为我发现有些Proxy地址是

http://username:password@localhost:8989 长这样的,那么我如果直接以这种形式传入到CreateProxy 里面,它会自动给我分解,然后分Credential proxy 传入到网络库中吗?接下来就是验证的过程。

首先需要了解到的一个概念: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 中弃用了,转而代之的是 TLS SSL 那些协议。

问题来了, HttpWebRequest 中支持 Basic Authentication吗?我们可以看到WebProxy中有一个构造方法最后一个参数是 ICredential 的

是的,就是它,知道前因后果和不足后,我继续去重构 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 信息,然后分别重新构造相应的类型传入到 WebProxy 中。上面也有一个坑,我之前还想用正则把username password 分别提取出去了,没想到 Uri 已经封装好了,直接取里面的userinfo 信息。哈哈,省力了。

StackOverFlow上也有挺多关于如何传入 CredentialProxy中,基本上用的也是这个方法,按理说这样就完事了,直到我做了测试,我发现微软这个Credential根本没有起作用,如果是正确的话,会在 HEADER 中添加

Authorization: Basic <credentials> ,和上面那段测试代码一样,

[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);
}

我去测试了发现,这个头部里面根本没有加这个 Authorization 属性啊,尴尬了,是官方文档坑还是我使用不正确呢,基于此,想到了之前 主管 开发的那个 Proxy 代理 o_o ,我又去找了一个验证 basic-authnode.js 代理服务器 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代码打包成一个 js文件,然后执行

node tets.js

该代理服务器监听 3000端口,我使用刚才那段代码,果不其然,返回的是 401 ,这不是坑吗,官方文档上这样说可以,然而都不行。

最后只能强制加上这个 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);
}

最后在测试经过 3000 端口的代理服务器,确认是没问题的,把问题想得简单的结果就是发了一个新版本后,还没有下载,然而已经发了新版本说,用户您好,我们又有新版本了。尴尬。需要以此为鉴啊。

后记

姜还是老的辣,多看看别人的代码,来发现自己的不足。勤加练习!

C# 使用 Proxy 代理请求资源的更多相关文章

  1. java中Proxy(代理与动态代理)

    转自: https://blog.csdn.net/pangqiandou/article/details/52964066 一.代理的概念 动态代理技术是整个java技术中最重要的一个技术,它是学习 ...

  2. C++设计模式-Proxy代理模式

    Proxy代理模式 作用:为其他对象提供一种代理以控制对这个对象的访问. 代理的种类: 如果按照使用目的来划分,代理有以下几种: 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代 ...

  3. php设计模式之Proxy(代理模式)和Facade(外观)设计模式

    Proxy(代理模式)和Facade(外观)设计模式它们均为更复杂的功能提供抽象化的概念,但这两种实现抽象化的过程大不相同 Proxy案例中,所有的方法和成员变量都来自于目标对象,必要时,该代理能够对 ...

  4. java设计模式之Proxy(代理模式)

    java设计模式之Proxy(代理模式) 2008-03-25 20:30 227人阅读 评论(0) 收藏 举报 设计模式javaauthorizationpermissionsstringclass ...

  5. 豌豆夹Redis解决方式Codis源代码剖析:Proxy代理

    豌豆夹Redis解决方式Codis源代码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描写叙述: Codis is a proxy b ...

  6. 豌豆夹Redis解决方案Codis源码剖析:Proxy代理

    豌豆夹Redis解决方案Codis源码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描述: Codis is a proxy base ...

  7. requests使用“proxy”代理访问接口

    在requests中使用proxy代理访问 使用前先更新requests版本为支持socks的版本.   先pip安装对应库:  >> pip install -U requests[so ...

  8. python爬虫scrapy之downloader_middleware设置proxy代理

    一.背景: 小编在爬虫的时候肯定会遇到被封杀的情况,昨天爬了一个网站,刚开始是可以了,在settings的设置DEFAULT_REQUEST_HEADERS伪装自己是chrome浏览器,刚开始是可以的 ...

  9. python + seleinum +phantomjs 设置headers和proxy代理

    python + seleinum +phantomjs 设置headers和proxy代理     最近因为工作需要使用selenium+phantomjs无头浏览器,其中遇到了一些坑,记录一下,尤 ...

随机推荐

  1. Confluence 6 推荐的更新通知设置和禁用

    你可以设置默认的发送选项(发送 / 不发送)和默认的发送时间(每天或每周). 如何配置推荐更新电子邮件通知: 在屏幕的右上角单击 控制台按钮 ,然后选择 General Configuration 链 ...

  2. linux命令中的参数前的一横(-)和两横(--)的区别

    在解释这些区别之前我们先了解一下有关linux的背景知识,这个需要大家先认真看完就会对这些区别有更深入的了解,对linux也有更深的了解. 关于System V和BSD风格以及他们与Linux的关系: ...

  3. day07 元组类型 字典类型 集合

    元组:元组就是一个不可变的列表 1.用途:当我们需要记录多个同种属性的值,并且只有读的需求,没有改的需求,应该用元组. 2.定义方式:在()内用逗号分隔开多个任意类型的元素 t=(‘egon’)#注意 ...

  4. vi 编辑器常用快捷键

    vi 编辑器 vim 编辑器算是vi的进阶版本 所有的unix like 系统都会内建vi编辑器 vi三种模式分别为: 1.一般模式(默认模式或指令模式) 上下左右方向键 移动光标 pageUp pa ...

  5. npm run build 打包后(直接打包白屏),如何运行在本地查看效果(Apache服务)

    转载: https://www.cnblogs.com/qiu-Ann/p/7477593.html 目前,使用vue-cli脚手架写了一个前端项目,之前一直是使用npm run dev 在8080端 ...

  6. Loadrunner11.0 录制手机App脚本的方法二

    lr11安装一个移动补丁以后,就可以支持抓包文件直接转换为http/html协议的脚本.下面简单说一下过程. 1.工作思路:手机连接可上外网wifi,在手机wifi中设置本机ip的网络地址,然后在本机 ...

  7. 如何用TortoiseSVN对文件进行操作

    我们如何用TortoiseSVN修改文件,添加文件,删除文件,以及如何解决冲突等. 添加文件 在检出的工作副本中添加一个Readme.txt文本文件,这时候这个文本文件会显示为没有版本控制的状态,如图 ...

  8. 调试阶段 获取微信小程序openid

    wx.login({ success: function(res) { //首先获取用户code //res.code wx.request({ url: 'https://api.weixin.qq ...

  9. Java+selenium之WebDriver的抛出异常分析(七)

    NoSuchElementException 1.检查元素的定位器是否正确 2.如果定位器正确,增加休眠时间 3.等待了足够的时间依然找不到的话,更换定位器的定位方式 NoSuchWindowExce ...

  10. JAVA,字符串替换

    package l515; //例5.15 //字符串替换 public class NewStr { public static void main(String[] args) { String ...