C# 使用Proxy代理请求资源的方法步骤
作者:BUTTERAPPLE 发布时间:2022-07-16 22:20:37
前言
这是上周在开发 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 端口的代理服务器,确认是没问题的,把问题想得简单的结果就是发了一个新版本后,还没有下载,然而已经发了新版本说,用户您好,我们又有新版本了。尴尬。需要以此为鉴啊。
后记
来源:104.116.116.112.58.47.47.119.119.119.46.99.110.98.108.111.103.115.46.99.111.109.47.120.105.121.105.110.47.112.47.49.48.53.56.51.55.56.55.46.104.116.109.108.
猜你喜欢
- 本文实例为大家分享了Android实现手势密码功能的具体代码,供大家参考,具体内容如下首先声明一下,九宫格布局是从网上扒了一个大神写好的,大
- 几个月前写过一篇博客《xUtils3.0框架学习笔记》 ,上面也有记录通过xUtils实现文件上传的使用方法,代码如下:private vo
- 先附上图片上传的代码jsp代码如下:<form action="${path}/upload/uploadPic.do&qu
- springboot初始化器新建项目项目结构idea工具类中初始化本地git仓库选择当前项目目录即可工具类由VCS变成了Gitadd 到缓存
- 掌握内存操作流输入和输出都是从文件中来的,当然,也可将输出的位置设置在内存上,这就需要ByteArrayInputStream和ByteAr
- 一. 为什么需要比较对象上一节介绍了优先级队列,在优先级队列中插入的元素必须能比较大小,如果不能比较大小,如插入两个学生类型的元素,会报Cl
- 复合语句Java的复合语句是以整个区块为单位的语句,由{}以及{}内包含的内容组成对于复合语句来说,复合语句创建了一个局部变量的作用域,该作
- 本文实例讲述了C#访问SqlServer设置链接超时的方法。分享给大家供大家参考。具体实现方法如下:下面这段代码设置超时时间为60秒,默认为
- 一、 进程 简单来说,进程是对资源的抽象,是资源的容器,
- 这篇文章主要介绍了Java import导入及访问控制权限修饰符过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考
- Mybatis-Plus是一个优秀的Mybatis增强工具,目前更新到3.1.1。Mybatis-Plus原生提供了很多单表操作的方法,极大
- 为什么要重复造轮子你可能会问,Spring已经自带了全局异常拦截,为什么还要重复造轮子呢?这是个好问题,我觉得有以下几个原因装逼Spring
- 目录前言实践部分测试部分总结前言今天跟小伙伴们分享一个实战内容,使用Spring Boot+Shiro实现一个简单的Http认证。场景是这样
- 在 C# 以二进制形式读取数据时使用的是 BinaryReader 类。BinaryReader 类中提供的构造方法有 3 种,具体的语法形
- SpringBoot自带Tomcat,所以我们的项目可以单独部署,不需要依赖Window、Linux系统中的服务器,所以打包出来的Jar包是
- 网上有很多人探讨Java中异常捕获机制try...catch...finally块中的finally语句是不是一定会被执行?很多人都说不是,
- 1. 前言在Java开发中接触的开发者大多数不太注重对接口的测试,结果在联调对接中出现各种问题。也有的使用Postman等工具进行测试,虽然
- mybatis一直加载xml,找到错误我们在写springmvc+mybatis项目,启动项目的时候,mapper配置文件一直刷,一直加载。
- Interceptor 介绍 * (Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程—&
- 传值(by value)与传址(by reference)分别为普通传递参数方式与ref声明方式,传址方式在使用前需要ref关键词修饰;ou