前言
这是上周在开发 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 端口的代理服务器,确认是没问题的,把问题想得简单的结果就是发了一个新版本后,还没有下载,然而已经发了新版本说,用户您好,我们又有新版本了。尴尬。需要以此为鉴啊。
后记
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
对资源请求的拦截代理是 Service Worker 的重要功能之一。Service Worker 在完成注册并激活之后,对 fetch 事件的监听就会开始生效,我们可以在事件回调里完成对请求的拦截与改写。下面这个简单的例子演示了如何拦截 http://127.0.0.1:8080/data.txt 的资源请求,并返回固定请求响应的过程: self.addEventListener('fetch'
本文向大家介绍C#使用semaphore来管理异步下载请求的方法,包括了C#使用semaphore来管理异步下载请求的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C#使用semaphore来管理异步下载请求的方法。分享给大家供大家参考。具体实现方法如下: 希望本文所述对大家的C#程序设计有所帮助。
本文向大家介绍C#中使用资源的方法分析,包括了C#中使用资源的方法分析的使用技巧和注意事项,需要的朋友参考一下 本文实例分析了C#中使用资源的方法。分享给大家供大家参考。具体如下: 这里总结一个在C#中如何使用资源的方法如下: 方法一、使用本地文件 1、将本地要加入的资源文本(视频,图片,文本或其它)加入项目,比如我们现在加入一个up.bmp的图片到项目中,且放在文件夹Resources下面 2、
需要通过代理执行REST请求。有几个HTTP代理服务器可用,受基本身份验证的保护。使用Java16。 访问HTTP资源时完全工作的请求示例:
我正在考虑一种使用IDisposable模式来同步/协调对共享资源的访问的方法。 以下是我到目前为止的代码(易于使用LinqPad运行): 我试图实现的是在(独占)访问共享资源之前,在方法的顶部(或者中间的某个地方,谁在乎呢)使用C#“using”语句,并让IDisposable机制自动结束独占访问。 在引擎盖下,Monitor类用于此目的。 所需的优点是不需要缩进的{代码块}。只是在使用...行
forward方法和include方法类似。只是forward方法的作用是转发网络资源(HTML、JSP、Servlet、图象等)。下面的例子演示了如何使用forward方法转发网络资源。 例子 : 用forward方法请求转发网络资源 1. 实例说明 本例使用了一个ForwardingServlet类分别转入IncludedServlet、IncludedHtml.html和图像文件。 2. 编