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

StackOverFlow上也有挺多关于如何传入 Credential 到Proxy中,基本上用的也是这个方法,按理说这样就完事了,直到我做了测试,我发现微软这个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-auth 的node.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 MySQL 创建数据库和数据库用户

    一旦你成功的安装和配置了 MySQL 数据库服务器,你需要为你的 Confluence 创建数据库和数据库用户: 在 MySQL 中以超级用户运行 'mysql' .默认的用户为 'root' 同时密 ...

  2. 【JS】中ajax的URL中包含中文,后台接收乱码

    [问题]ajax提交get请求,url中参数包含中文,后台接收到显示乱码. [解决方案]前台: function getSiteInfoByName(siteName){ var res; $.aja ...

  3. 用mybatis做数据库处理 代码中的字段大小写 要和mapper映射设置的大小写一致(这TM不废话么,原谅我渣!~~)

    简单描述情况:其实这个问题怎么说呢,有人给你说,你肯定能意识到,必须大小写对应的嘛.emmmm我现在才意识到是因为:自己在下边敲代码,配的mapper文件并没有把属性变量,和数据库里的段单独提出来做映 ...

  4. tensorflow(4):神经网络框架总结

    #总结神经网络框架 #1,搭建网络设计结构(前向传播) 文件forward.py def forward(x,regularizer): # 输入x 和正则化权重 w= b= y= return y ...

  5. python编程实现十进制的转换(十进制到任何进制不用系统函数实现)

    # 用户给定一个十进制数据,以及进制n, 完成n进制的转换,输出结果给用户(不要借助已有函数) # 用户的输入进制数据Decdata = int(input("请输入一个十进制数据:&quo ...

  6. python虚拟环境搭建

    1.安装python环境 2.检查pip 3.pip install virtualenv 4.创建测试:virtualenv  testvir 5.pip install virtualenvwra ...

  7. SQL Server索引误区使用建议

    常见的误区: 1.数据库不需要索引 2.主键总是聚集的 3.联机索引操作不引起阻塞 4.复合索引下列的顺序不重要 5.聚集索引以物理顺序存储 6.填充因子可以应用在索引的插入过程中 7.每个表应该有聚 ...

  8. Oracle数据库表索引失效,解决办法:修改Oracle数据库优化器模式

    ALTER SYSTEM SET OPTIMIZER_MODE=RULE scope=both; 其他可以选择的模式还有ALL_ROWS/CHOOSE/FIRST_ROWS/ALL_ROWS. 应用系 ...

  9. [转]搭建Hadoop伪分布式环境

    https://my.oschina.net/MyHeaven1987/blog/1821509 http://hadoop.apache.org/docs/current/hadoop-projec ...

  10. [转]GitHub for Windows 安装失败,An error occurred attempting to install github 的解决办法

    解决办法: 只需要将 http://github-windows.s3.amazonaws.com/GitHub.application http改为https,然后在IE上打开,安装即可 问题如下 ...