几个星期前我已经下载了Privoxy,为了好玩,我很想知道如何完成它的简单版本。
我知道我需要配置浏览器(客户端)以向代理发送请求。代理将请求发送到Web(假设它是一个http代理)。代理将收到答案......但代理如何将请求发送回浏览器(客户端)?
我在网上搜索C#和http代理,但没有找到让我理解它在场景背后是如何工作的东西。 (我相信我不想要反向代理,但我不确定)。
你们有没有一些解释或一些信息让我继续这个小项目?
这是我所理解的(见下图)。
步骤1 我在Proxy侦听端口为所有请求发送到127.0.0.1配置客户端(浏览器)。这样,请求将不会直接发送到Internet,而是由代理处理。
Step2 代理看到一个新连接,读取HTTP标头并查看他必须执行的请求。他执行请求。
Step3 代理会从请求中收到答案。现在他必须将网上的答案发送给客户,但是如何???
Mentalis Proxy:我发现这个项目是代理(但我想要的更多)。我可能会查看来源,但我真的想要一些基本的东西来理解这个概念。
ASP Proxy:我也可以在这里获得一些信息。
Request reflector:这是一个简单的例子。
答案 0 :(得分:85)
我不会使用HttpListener或类似的东西,这样你会遇到很多问题。
最重要的是,支持将是一件巨大的痛苦:
您需要做的是:
我在.NET中用不同的要求编写了2个不同的HTTP代理,我可以告诉你这是最好的方法。
Mentalis这样做,但他们的代码是“委托意大利面条”,比GoTo更糟糕:)
答案 1 :(得分:33)
您可以使用HttpListener
类构建一个用于侦听传入请求,并使用HttpWebRequest
类来传递请求。
答案 2 :(得分:21)
我最近使用TcpListener和TcpClient在c#.net中编写了一个轻量级代理。
https://github.com/titanium007/Titanium-Web-Proxy
它以正确的方式支持安全HTTP,客户端机器需要信任代理使用的根证书。还支持WebSockets中继。除流水线外,支持HTTP 1.1的所有功能。大多数现代浏览器都不使用流水线技术。还支持Windows身份验证(普通,摘要)。
您可以通过引用项目来连接应用程序,然后查看和修改所有流量。 (请求和回复)。
就性能而言,我已经在我的机器上进行了测试,并且没有任何明显的延迟。
答案 3 :(得分:19)
代理可以按以下方式工作。
步骤1,配置客户端使用proxyHost:proxyPort。
Proxy是一个正在侦听proxyHost:proxyPort的TCP服务器。 浏览器打开与Proxy的连接并发送Http请求。 代理解析此请求并尝试检测“主机”标头。此标头将告知Proxy在何处打开连接。
步骤2:代理打开与“主机”标头中指定的地址的连接。然后它将HTTP请求发送到该远程服务器。阅读回复。
步骤3:从远程HTTP服务器读取响应后,Proxy通过先前打开的TCP连接与浏览器发送响应。
示意图如下:
Browser Proxy HTTP server
Open TCP connection
Send HTTP request ----------->
Read HTTP header
detect Host header
Send request to HTTP ----------->
Server
<-----------
Read response and send
<----------- it back to the browser
Render content
答案 4 :(得分:13)
如果您只想拦截流量,可以使用fiddler核心创建代理......
http://fiddler.wikidot.com/fiddlercore
首先使用UI运行fiddler以查看它的功能,它是一个允许您调试http / https流量的代理。它是用c#编写的,并且有一个可以构建到自己的应用程序中的核心。
请记住,FiddlerCore不适用于商业应用程序。
答案 5 :(得分:5)
同意邪恶的博士 如果您使用HTTPListener,您将遇到很多问题,您必须解析请求并参与标题和...
你看到你甚至不需要知道浏览器请求中的内容并解析它,只从第一行获取目标站点地址 第一行通常喜欢这个 获取http://google.com HTTP1.1 要么 CONNECT facebook.com:443(这是针对ssl请求)
答案 6 :(得分:4)
Socks4是一个非常简单的协议。您侦听初始连接,连接到客户端请求的主机/端口,将成功代码发送到客户端,然后通过套接字转发传出和传入流。
如果您使用HTTP,则必须阅读并可能设置/删除一些HTTP标头,以便更多工作。
如果我没记错,SSL将在HTTP和Socks代理中运行。对于HTTP代理,您实现了CONNECT谓词,它的工作原理与上面描述的socks4非常相似,然后客户端在代理的tcp流中打开SSL连接。
答案 7 :(得分:2)
浏览器已连接到代理,因此代理从Web服务器获取的数据仅通过浏览器启动的代理连接发送给代理。
答案 8 :(得分:1)
对于它的价值,这是一个基于HttpListener和HttpClient的C#示例异步实现(我使用它可以将Android设备中的Chrome连接到IIS Express,这是我唯一的方法找到...)。
如果您需要HTTPS支持,则不需要更多代码,只需配置证书:Httplistener with HTTPS support
// define http://localhost:5000 and http://127.0.0.1:5000/ to be proxies for http://localhost:53068
using (var server = new ProxyServer("http://localhost:53068", "http://localhost:5000/", "http://127.0.0.1:5000/"))
{
server.Start();
Console.WriteLine("Press ESC to stop server.");
while (true)
{
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.Escape)
break;
}
server.Stop();
}
....
public class ProxyServer : IDisposable
{
private readonly HttpListener _listener;
private readonly int _targetPort;
private readonly string _targetHost;
private static readonly HttpClient _client = new HttpClient();
public ProxyServer(string targetUrl, params string[] prefixes)
: this(new Uri(targetUrl), prefixes)
{
}
public ProxyServer(Uri targetUrl, params string[] prefixes)
{
if (targetUrl == null)
throw new ArgumentNullException(nameof(targetUrl));
if (prefixes == null)
throw new ArgumentNullException(nameof(prefixes));
if (prefixes.Length == 0)
throw new ArgumentException(null, nameof(prefixes));
RewriteTargetInText = true;
RewriteHost = true;
RewriteReferer = true;
TargetUrl = targetUrl;
_targetHost = targetUrl.Host;
_targetPort = targetUrl.Port;
Prefixes = prefixes;
_listener = new HttpListener();
foreach (var prefix in prefixes)
{
_listener.Prefixes.Add(prefix);
}
}
public Uri TargetUrl { get; }
public string[] Prefixes { get; }
public bool RewriteTargetInText { get; set; }
public bool RewriteHost { get; set; }
public bool RewriteReferer { get; set; } // this can have performance impact...
public void Start()
{
_listener.Start();
_listener.BeginGetContext(ProcessRequest, null);
}
private async void ProcessRequest(IAsyncResult result)
{
if (!_listener.IsListening)
return;
var ctx = _listener.EndGetContext(result);
_listener.BeginGetContext(ProcessRequest, null);
await ProcessRequest(ctx).ConfigureAwait(false);
}
protected virtual async Task ProcessRequest(HttpListenerContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var url = TargetUrl.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
using (var msg = new HttpRequestMessage(new HttpMethod(context.Request.HttpMethod), url + context.Request.RawUrl))
{
msg.Version = context.Request.ProtocolVersion;
if (context.Request.HasEntityBody)
{
msg.Content = new StreamContent(context.Request.InputStream); // disposed with msg
}
string host = null;
foreach (string headerName in context.Request.Headers)
{
var headerValue = context.Request.Headers[headerName];
if (headerName == "Content-Length" && headerValue == "0") // useless plus don't send if we have no entity body
continue;
bool contentHeader = false;
switch (headerName)
{
// some headers go to content...
case "Allow":
case "Content-Disposition":
case "Content-Encoding":
case "Content-Language":
case "Content-Length":
case "Content-Location":
case "Content-MD5":
case "Content-Range":
case "Content-Type":
case "Expires":
case "Last-Modified":
contentHeader = true;
break;
case "Referer":
if (RewriteReferer && Uri.TryCreate(headerValue, UriKind.Absolute, out var referer)) // if relative, don't handle
{
var builder = new UriBuilder(referer);
builder.Host = TargetUrl.Host;
builder.Port = TargetUrl.Port;
headerValue = builder.ToString();
}
break;
case "Host":
host = headerValue;
if (RewriteHost)
{
headerValue = TargetUrl.Host + ":" + TargetUrl.Port;
}
break;
}
if (contentHeader)
{
msg.Content.Headers.Add(headerName, headerValue);
}
else
{
msg.Headers.Add(headerName, headerValue);
}
}
using (var response = await _client.SendAsync(msg).ConfigureAwait(false))
{
using (var os = context.Response.OutputStream)
{
context.Response.ProtocolVersion = response.Version;
context.Response.StatusCode = (int)response.StatusCode;
context.Response.StatusDescription = response.ReasonPhrase;
foreach (var header in response.Headers)
{
context.Response.Headers.Add(header.Key, string.Join(", ", header.Value));
}
foreach (var header in response.Content.Headers)
{
if (header.Key == "Content-Length") // this will be set automatically at dispose time
continue;
context.Response.Headers.Add(header.Key, string.Join(", ", header.Value));
}
var ct = context.Response.ContentType;
if (RewriteTargetInText && host != null && ct != null &&
(ct.IndexOf("text/html", StringComparison.OrdinalIgnoreCase) >= 0 ||
ct.IndexOf("application/json", StringComparison.OrdinalIgnoreCase) >= 0))
{
using (var ms = new MemoryStream())
{
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
await stream.CopyToAsync(ms).ConfigureAwait(false);
var enc = context.Response.ContentEncoding ?? Encoding.UTF8;
var html = enc.GetString(ms.ToArray());
if (TryReplace(html, "//" + _targetHost + ":" + _targetPort + "/", "//" + host + "/", out var replaced))
{
var bytes = enc.GetBytes(replaced);
using (var ms2 = new MemoryStream(bytes))
{
ms2.Position = 0;
await ms2.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false);
}
}
else
{
ms.Position = 0;
await ms.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false);
}
}
}
}
else
{
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
await stream.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false);
}
}
}
}
}
}
public void Stop() => _listener.Stop();
public override string ToString() => string.Join(", ", Prefixes) + " => " + TargetUrl;
public void Dispose() => ((IDisposable)_listener)?.Dispose();
// out-of-the-box replace doesn't tell if something *was* replaced or not
private static bool TryReplace(string input, string oldValue, string newValue, out string result)
{
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(oldValue))
{
result = input;
return false;
}
var oldLen = oldValue.Length;
var sb = new StringBuilder(input.Length);
bool changed = false;
var offset = 0;
for (int i = 0; i < input.Length; i++)
{
var c = input[i];
if (offset > 0)
{
if (c == oldValue[offset])
{
offset++;
if (oldLen == offset)
{
changed = true;
sb.Append(newValue);
offset = 0;
}
continue;
}
for (int j = 0; j < offset; j++)
{
sb.Append(input[i - offset + j]);
}
sb.Append(c);
offset = 0;
}
else
{
if (c == oldValue[0])
{
if (oldLen == 1)
{
changed = true;
sb.Append(newValue);
}
else
{
offset = 1;
}
continue;
}
sb.Append(c);
}
}
if (changed)
{
result = sb.ToString();
return true;
}
result = input;
return false;
}
}