C# 使用 Proxy 代理请求资源
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 代理请求资源的更多相关文章
- java中Proxy(代理与动态代理)
转自: https://blog.csdn.net/pangqiandou/article/details/52964066 一.代理的概念 动态代理技术是整个java技术中最重要的一个技术,它是学习 ...
- C++设计模式-Proxy代理模式
Proxy代理模式 作用:为其他对象提供一种代理以控制对这个对象的访问. 代理的种类: 如果按照使用目的来划分,代理有以下几种: 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代 ...
- php设计模式之Proxy(代理模式)和Facade(外观)设计模式
Proxy(代理模式)和Facade(外观)设计模式它们均为更复杂的功能提供抽象化的概念,但这两种实现抽象化的过程大不相同 Proxy案例中,所有的方法和成员变量都来自于目标对象,必要时,该代理能够对 ...
- java设计模式之Proxy(代理模式)
java设计模式之Proxy(代理模式) 2008-03-25 20:30 227人阅读 评论(0) 收藏 举报 设计模式javaauthorizationpermissionsstringclass ...
- 豌豆夹Redis解决方式Codis源代码剖析:Proxy代理
豌豆夹Redis解决方式Codis源代码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描写叙述: Codis is a proxy b ...
- 豌豆夹Redis解决方案Codis源码剖析:Proxy代理
豌豆夹Redis解决方案Codis源码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描述: Codis is a proxy base ...
- requests使用“proxy”代理访问接口
在requests中使用proxy代理访问 使用前先更新requests版本为支持socks的版本. 先pip安装对应库: >> pip install -U requests[so ...
- python爬虫scrapy之downloader_middleware设置proxy代理
一.背景: 小编在爬虫的时候肯定会遇到被封杀的情况,昨天爬了一个网站,刚开始是可以了,在settings的设置DEFAULT_REQUEST_HEADERS伪装自己是chrome浏览器,刚开始是可以的 ...
- python + seleinum +phantomjs 设置headers和proxy代理
python + seleinum +phantomjs 设置headers和proxy代理 最近因为工作需要使用selenium+phantomjs无头浏览器,其中遇到了一些坑,记录一下,尤 ...
随机推荐
- Confluence 6 通过 SSL 或 HTTPS 运行
Atlassian 应用可以通过 HTTPS 进行访问,但是 Atlassian 并不提供有关访问的支持服务,同时 Atlassian 不能保证能够提供所有的支持. 如果你的 assistance 在 ...
- LeetCode(81): 搜索旋转排序数组 II
Medium! 题目描述: 假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] ). 编写一个函数来判断给 ...
- WireShark 实例分析笔记(概念)
1.嗅探器工作原理(中文版) 安装软件的附带WinPcap(实现从网卡接受数据可视化) 数据嗅探器工作原理, 第一:收集数据(数据包嗅探器从网络线缆上收集原始二进制数据,选定特定网卡设置混杂模式来完 ...
- Imperial roads 非严格次小生成树
cf测评姬比uva快了五倍... /* 不管这条边是不是在mst上,直接跑lca求出路径上的最大边w即可 ans=mst-w+dist(u,v) */ #include<bits/stdc++. ...
- 重新认识python
为什么这样说呢,我几个月前就开始学python,但是一直都没有进步,还就只是会一些其它语言的共性的问题,也就是新学习的约等于0. 后来一直找一些适合自己的教材,通过同学找到了一个学长的教程. 开始了新 ...
- BZOJ 2818 Gcd(欧拉函数+质数筛选)
2818: Gcd Time Limit: 10 Sec Memory Limit: 256 MB Submit: 9108 Solved: 4066 [Submit][Status][Discu ...
- CentOS6 YUM安装MariaDB10.3.10
1.先新增加一个MariaDB.repo vi /etc/yum.repos.d/MariaDB.repo [mariadb] name = MariaDB baseurl = http://mirr ...
- Sway
啥是Sway: http://livesino.net/archives/7520.live 地址:http://sway.com 现在又可以申请了 试试效果
- Multi-Fiber Networks for Video Recognition (MFNet)
Motivation:减少时空网络的计算量,保持视频分类精度的基础上,使速度尽可能接近对应网络的2D版本. 为此提出 Multi-Fiber 网络,将复杂网络拆分成轻量网络的集成,利用 fibers ...
- dedecms首页入口的详细注释
今天闲来无事,就拿来dede首页的文件给大家详细解释一遍,以便于新手学习,注释过程非常非常非常的详细,里面解释到dede表前缀#@__代替的原理.解释到dede很多自定义函数的具体位置和具体作用等等疑 ...


