文档章节

C# 使用 Proxy 代理请求资源

o
 osc_a22drz29
发布于 2019/03/23 14:10
字数 1471
阅读 3
收藏 0

精选30+云产品,助力企业轻松上云!>>>

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

后记

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

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
[Z] C#程序中设置全局代理(Global Proxy)

https://www.cnblogs.com/Javi/p/7274268.html 1. HttpWebRequest类的Proxy属性,只要设置了该属性就能够使用代理了,如下: 1 //设置代理 2 WebProxy WP = new WebProxy("41.76.44.76", 3128......

osc_t6kfzq66
2018/06/23
3
0
C#程序中设置全局代理(Global Proxy)

HttpWebRequest类的Proxy属性,只要设置了该属性就能够使用代理了,如下: 1 //设置代理2 WebProxy WP = new WebProxy("41.76.44.76", 3128);3 ICredentials jxCredt = new NetworkCredentia......

osc_w5x85e9u
2019/01/04
2
0
C# HTTP系列1 HttpWebRequest类

系列目录 【已更新最新开发文章,点击查看详细】   .NET Framework 中 System.Net 命名空间下提供了 HttpWebRequest 和 HttpWebResponse 2个类,他们是用于发送和接收HTTP数据的最好选择。...

osc_x690pobu
2019/08/28
5
0
《C# 爬虫 破境之道》:第一境 爬虫原理 — 第二节:WebRequest

本节主要来介绍一下,在C#中制造爬虫,最为常见、常用、实用的基础类 ------ WebRequest、WebResponse。 先来看一个示例 [1.2.1]: 1 using System; 2 using System.IO; 3 using System.Net;...

osc_arj2xsvk
2019/12/24
4
0
.Net 连接FTP下载文件报错:System.InvalidOperationException: The requested FTP command is not supported whe...

系统环境: Windows + .Net Framework 4.0 问题描述: C#连接FTP下载文件时,在部分电脑上有异常报错,在一部分电脑上是正常的;异常报错的信息:System.InvalidOperationException: The req...

osc_on5pjexo
04/16
4
0

没有更多内容

加载失败,请刷新页面

加载更多

038. RocketMQ 高性能最佳实践

1. 最佳实践之 Producer 1. 一个应用尽可能用一个 Topic,消息子类型用 tags 来标识,tags 可以由应用自由设置。 只有发送消息设置了 tags,消费方在订阅消息时,才可以利用 tags 在 broker...

华夏紫穹
54分钟前
32
0
QQ音乐Android客户端Web页面通用性能优化实践

QQ音乐 Android 客户端的 Web 页面日均 PV 达到千万量级,然而页面的打开耗时与 Native 页面相距甚远,需要系统性优化。本文将介绍 QQ 音乐 Android 客户端在进行 Web 页面通用性能优化过程中...

腾讯云开发者社区
今天
26
0
rabbitmq+sleuth+zinkip 分布式链路追踪

我们都知道,微服务之间通过feign传递,在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路,在每条链路中任何一个依赖服务出现延迟超时或者错误都有可能引...

良许Linux
今天
16
0
5分钟搭建属于你的视频会议系统

前言 在疫情的推动下视频会议和线上办公大力发展,如果你也想了解视频会议,看看这篇文章吧 准备工作 一台Ubuntu18.04拥有公网IP的服务器 一个域名提前解析到这台服务器上 安全组设置规则tcp...

死磕音视频
今天
17
0
从文本JavaScript中删除HTML - Strip HTML from Text JavaScript

问题: 有没有一种简单的方法可以在JavaScript中获取html字符串并去除html? 解决方案: 参考一: https://stackoom.com/question/3RxM/从文本JavaScript中删除HTML 参考二: https://oldbug...

fyin1314
今天
19
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部