快速、可扩展、流式 AJAX 代理 - 持续跨域提供数据






4.99/5 (68投票s)
一个使用 ASP.NET 构建的 AJAX 代理 HttpHandler,它能持续地将外部域的内容传输到浏览器,因此比任何其他代理服务都更快、更具可扩展性。
引言
由于浏览器禁止跨域 XMLHTTP 调用,所有 AJAX 网站都必须有一个服务器端代理来从 Flickr 或 Digg 等外部域获取内容。从客户端 JavaScript 代码,XMLHTTP 调用会发送到托管在同一域上的服务器端代理,然后代理从外部服务器下载内容并将其发送回浏览器。总的来说,互联网上所有显示来自外部域内容的 AJAX 网站都在遵循这种代理方法,除了少数使用 JSONP 的例外。当网站的许多组件都在下载外部域的内容时,这种代理会获得大量的点击。因此,当代理开始承受数百万次点击时,就会成为一个可扩展性问题。此外,网页的整体加载性能在很大程度上取决于代理的性能,因为它将内容传输到页面。在本文中,我们将探讨如何改进传统的 AJAX 代理,使其更快、异步、持续流式传输内容,从而提高其可扩展性。
AJAX 代理实战
当您访问 Pageflakes.com 时,您可以看到这样的代理。您会看到各种“碎片”(小部件)加载来自许多不同外部域的各种内容,例如天气预报、Flickr 照片、YouTube 视频和 RSS。所有这些都通过内容代理完成。内容代理上个月处理了约 4230 万个 URL,这对我们来说是一项巨大的工程挑战,需要使其既快速又可扩展。有时内容代理会传输兆字节的数据,这带来了更大的工程挑战。因此,代理会收到大量点击;如果我们平均每次调用能节省 100 毫秒,那么每个月就能节省 423 万秒的下载/上传/处理时间。这相当于全世界数百万人盯着浏览器等待内容下载浪费了约 1175 个工时。
这种内容代理以查询参数的形式接收外部服务器的 URL。它从该 URL 下载内容,然后将内容作为响应写回浏览器。
图:内容代理在浏览器和外部域之间作为中间人工作
上面的时间线显示了一个请求如何到达服务器,然后服务器向外部服务器发出请求,下载响应,再将其传输回浏览器。代理到浏览器的响应箭头比外部服务器到代理的响应箭头更大,因为通常情况下,代理服务器的托管环境比用户的互联网连接速度更快。
基本代理
在我开源的 AJAX Web Portal Dropthings.com 中也可以找到这样的内容代理。您可以从 CodePlex 上的代码中了解这种代理是如何实现的。
以下是一个非常简单、同步、非流式、阻塞式代理
[WebMethod]
[ScriptMethod(UseHttpGet=true)]
public string GetString(string url)
{
using (WebClient client = new WebClient())
{
string response = client.DownloadString(url);
return response;
}
}
}
尽管它显示了基本原理,但它远非一个真正的代理,因为
- 它是一个同步代理,因此不可扩展。每次调用此 Web 方法都会导致 ASP.NET 线程等待,直到调用外部 URL 完成。
- 它是非流式的。它首先在服务器上下载全部内容,将其存储在字符串中,然后将全部内容上传到浏览器。如果您传递一个 MSDN feed URL,它将在服务器上下载那个巨大的 220 KB RSS XML,并将其存储在一个 220 KB 的长字符串中(实际上是两倍大小,因为 .NET 字符串都是 Unicode),然后将 220 KB 写入 ASP.NET 响应缓冲区,在内存中消耗另外 220 KB 的 UTF8 字节数组。然后,这 220 KB 将以块的形式传递给 IIS,以便它可以将其传输到浏览器。
- 它不产生适当的响应头来缓存服务器上的响应。它也不会从源头传输诸如Content-Type之类的关键标头。
- 如果外部 URL 提供 gzip 压缩的内容,它会将内容解压缩为字符串表示形式,从而浪费服务器内存。
- 它不会在服务器上缓存内容。因此,在同一秒或一分钟内对同一外部 URL 的重复调用将从外部 URL 下载内容,从而浪费服务器带宽。
我们需要一个异步的流式代理,在从外部域服务器下载内容的同时将其传输到浏览器。因此,它将以小块下载字节,并立即将其传输到浏览器。结果,浏览器将在调用 Web 服务后立即看到连续的字节传输。在服务器上完全下载内容期间不会有延迟。
更好的代理
在向您展示复杂的流式代理代码之前,让我们采取一种演进的方法。让我们构建一个比上面所示的更好的内容代理,它是同步且非流式的,但没有其他问题。我们将构建一个名为RegularProxy.ashx的 HTTP Handler,它将 URL 作为查询参数。它还将缓存作为查询参数,用于生成适当的响应头以在浏览器中缓存内容。这样,它就可以避免浏览器反复下载相同的内容。
using System;
using System.Web;
using System.Web.Caching;
using System.Net;
using ProxyHelpers;
public class RegularProxy : IHttpHandler {
public void ProcessRequest (HttpContext context) {
string url = context.Request["url"];
int cacheDuration = Convert.ToInt32(context.Request["cache"]?? "0");
string contentType = context.Request["type"];
// We don't want to buffer because we want to save memory
context.Response.Buffer = false;
// Serve from cache if available
if (context.Cache[url] != null)
{
context.Response.BinaryWrite(context.Cache[url] as byte[]);
context.Response.Flush();
return;
}
using (WebClient client = new WebClient())
{
if (!string.IsNullOrEmpty(contentType))
client.Headers["Content-Type"] = contentType;
client.Headers["Accept-Encoding"] = "gzip";
client.Headers["Accept"] = "*/*";
client.Headers["Accept-Language"] = "en-US";
client.Headers["User-Agent"] =
"Mozilla/5.0 (Windows; U; Windows NT 6.0; " +
"en-US; rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6";
byte[] data = client.DownloadData(url);
context.Cache.Insert(url, data, null,
Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(cacheDuration),
CacheItemPriority.Normal, null);
if (!context.Response.IsClientConnected) return;
// Deliver content type, encoding and length
// as it is received from the external URL
context.Response.ContentType =
client.ResponseHeaders["Content-Type"];
string contentEncoding =
client.ResponseHeaders["Content-Encoding"];
string contentLength =
client.ResponseHeaders["Content-Length"];
if (!string.IsNullOrEmpty(contentEncoding))
context.Response.AppendHeader("Content-Encoding",
contentEncoding);
if (!string.IsNullOrEmpty(contentLength))
context.Response.AppendHeader("Content-Length",
contentLength);
if (cacheDuration > 0)
HttpHelper.CacheResponse(context, cacheDuration);
// Transmit the exact bytes downloaded
context.Response.BinaryWrite(data);
}
}
public bool IsReusable {
get {
return false;
}
}
}
此代理有两个增强功能
- 它允许服务器端缓存内容。同一 URL 在一段时间内被不同浏览器请求时,服务器将不会再次下载,而是从缓存提供。
- 它生成适当的响应缓存头,以便内容可以在浏览器中缓存。
- 它不会在内存中解压缩下载的内容。它保持原始字节流不变。这节省了内存分配。
- 它以非缓冲方式传输数据,这意味着 ASP.NET
Response
对象不缓冲响应,从而节省内存。
但是,这是一个阻塞代理。
更好的代理 - 流式传输!
为了获得更好的性能,我们需要创建一个流式的异步代理。原因如下:
图:连续流式代理
正如您所见,当数据在服务器下载内容的同时从服务器传输到浏览器时,服务器端下载的延迟就被消除了。因此,如果服务器需要 300 毫秒从外部源下载内容,然后需要 700 毫秒将其发送回浏览器,那么您可以节省多达 300 毫秒的服务器与浏览器之间的网络延迟。当外部服务器提供内容很慢,需要很长时间才能提供内容时,情况会变得更好。外部站点越慢,这种连续流式传输方法节省的就越多。当外部服务器在亚洲或澳大利亚,而您的服务器在美国时,这比阻塞方法要快得多。
连续代理的方法是:
- 从另一个线程(读取器线程)以 8KB 的块从外部服务器读取字节,这样就不会被阻塞。
- 将这些块存储在一个名为 Pipe Stream 的内存队列中。
- 从该队列将块写入 ASP.NET Response。
- 如果队列已空,请等待读取器线程下载更多字节。
Pipe Stream 需要是线程安全的,并且需要支持阻塞读取。阻塞读取意味着,如果一个线程尝试从中读取一个块,而流是空的,它将挂起该线程,直到另一个线程在该流上写入一些内容。一旦发生写入,它将恢复读取器线程并允许其读取。我从 James Kolpack 在 CodeProject 文章中获取了 PipeStream
的代码,并对其进行了扩展,以确保它具有高性能,支持存储字节块而不是单个字节,支持等待超时,等等。
我进行了一些比较,比较了常规代理(阻塞、同步、先下载全部再发送)和流式代理(从外部服务器到浏览器的连续传输)。两种代理都下载 MSDN feed 并将其传输到浏览器。这里显示的时间是浏览器发出请求到代理接收到整个响应的总持续时间。
图:下载 MSDN feed 时,流式代理与常规代理所花费的时间
这不是一个非常科学的图表,响应时间因浏览器与代理服务器之间的链接速度以及代理服务器与外部服务器之间的链接速度而异。但它表明,在大多数情况下,流式代理的性能优于常规代理。
图:用于比较常规代理和流式代理的测试客户端
您还可以通过访问 此链接来测试两个代理的响应时间。输入您的 URL,然后点击 Regular/Stream 按钮,并在“Statistics”文本框中查看总持续时间。您可以打开“Cache response”并从一个浏览器点击一个 URL。然后,转到另一个浏览器并点击该 URL,您将看到响应直接来自服务器缓存。此外,如果您在同一浏览器中再次点击该 URL,您将看到响应立即返回,而无需调用服务器。这就是浏览器缓存的作用。
从我的博客文章中了解更多关于 HTTP 响应缓存的信息:为高性能网站最佳利用缓存。
在负载测试中运行的 Visual Studio Web Test 提供了更好的图景
图:常规代理负载测试结果显示平均每秒请求数为 0.79,平均响应时间为 2.5 秒
图:流式代理负载测试结果显示平均每秒请求数为 1.08,平均响应时间为 1.8 秒。
从上面的负载测试结果来看,流式代理的每秒请求数提高了 26%,平均响应时间提高了 29%。这些数字可能看起来微不足道,但在 Pageflakes,29% 的响应时间提高意味着每月为网站上的所有用户节省了129 万秒。因此,我们实际上每月节省了 353 个工时,这些时间之前浪费在盯着浏览器屏幕等待内容下载。
构建流式代理
构建一个能够超越常规代理性能的流式代理并非易事。我尝试了三种方法,最终找到了能够超越常规代理的最佳组合。
流式代理使用 HttpWebRequest
和 HttpWebResponse
从外部服务器下载数据。它们用于更好地控制数据的读取方式,更具体地说,以 WebClient
不提供的字节块读取。此外,在构建该代理所需的快速可扩展的 HttpWebRequest
方面,还有一些优化。
public class SteamingProxy : IHttpHandler
{
const int BUFFER_SIZE = 8 * 1024;
private Utility.PipeStream _PipeStream;
private Stream _ResponseStream;
public void ProcessRequest (HttpContext context)
{
string url = context.Request["url"];
int cacheDuration = Convert.ToInt32(context.Request["cache"] ?? "0");
string contentType = context.Request["type"];
if (cacheDuration > 0)
{
if (context.Cache[url] != null)
{
CachedContent content = context.Cache[url] as CachedContent;
if (!string.IsNullOrEmpty(content.ContentEncoding))
context.Response.AppendHeader("Content-Encoding",
content.ContentEncoding);
if (!string.IsNullOrEmpty(content.ContentLength))
context.Response.AppendHeader("Content-Length",
content.ContentLength);
context.Response.ContentType = content.ContentType;
content.Content.Position = 0;
content.Content.WriteTo(context.Response.OutputStream);
}
}
HttpWebRequest request =
HttpHelper.CreateScalableHttpWebRequest(url);
// As we will stream the response, don't want
// to automatically decompress the content
// when source sends compressed content
request.AutomaticDecompression = DecompressionMethods.None;
if (!string.IsNullOrEmpty(contentType))
request.ContentType = contentType;
using (new TimedLog("StreamingProxy\tTotal " +
"GetResponse and transmit data"))
using (HttpWebResponse response =
request.GetResponse() as HttpWebResponse)
{
this.DownloadData(request, response, context, cacheDuration);
}
}
DownloadData
方法从响应流(连接到外部服务器)下载数据,然后将其传输到 ASP.NET Response
流。
private void DownloadData(HttpWebRequest request, HttpWebResponse response,
HttpContext context, int cacheDuration)
{
MemoryStream responseBuffer = new MemoryStream();
context.Response.Buffer = false;
try
{
if (response.StatusCode != HttpStatusCode.OK)
{
context.Response.StatusCode = (int)response.StatusCode;
return;
}
using (Stream readStream = response.GetResponseStream())
{
if (context.Response.IsClientConnected)
{
string contentLength = string.Empty;
string contentEncoding = string.Empty;
ProduceResponseHeader(response, context, cacheDuration,
out contentLength, out contentEncoding);
//int totalBytesWritten =
// TransmitDataInChunks(context, readStream, responseBuffer);
//int totalBytesWritten =
// TransmitDataAsync(context, readStream, responseBuffer);
int totalBytesWritten = TransmitDataAsyncOptimized(context,
readStream, responseBuffer);
if (cacheDuration > 0)
{
#region Cache Response in memory
// Cache the content on server for specific duration
CachedContent cache = new CachedContent();
cache.Content = responseBuffer;
cache.ContentEncoding = contentEncoding;
cache.ContentLength = contentLength;
cache.ContentType = response.ContentType;
context.Cache.Insert(request.RequestUri.ToString(),
cache, null, Cache.NoAbsoluteExpiration,
TimeSpan.FromMinutes(cacheDuration),
CacheItemPriority.Normal, null);
#endregion
}
}
context.Response.Flush();
}
}
catch (Exception x)
{
Log.WriteLine(x.ToString());
request.Abort();
}
}
在这里,我尝试了三种不同的方法。现在未注释的那种,称为 TransmitDataAsyncOptimized
,是最佳方法。我很快会解释所有三种方法。DownloadData
函数的目的是在发送数据之前准备 ASP.NET 响应流。然后,它使用三种方法之一发送数据,并将下载的字节缓存在内存流中。
第一种方法是从连接到外部服务器的响应流中读取 8192 字节,然后立即将其写入响应(TransmitDataInChunks
)。
private int TransmitDataInChunks(HttpContext context, Stream readStream,
MemoryStream responseBuffer)
{
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
int totalBytesWritten = 0;
while ((bytesRead = readStream.Read(buffer, 0, BUFFER_SIZE)) > 0)
{
context.Response.OutputStream.Write(buffer, 0, bytesRead);
responseBuffer.Write(buffer, 0, bytesRead);
totalBytesWritten += bytesRead;
}
return totalBytesWritten;
}
这里,readStream
是从 HttpWebResponse.GetResponseStream
调用接收到的响应流。它正在从外部服务器下载。responseBuffer
只是一个内存流,用于在内存中保存整个响应,以便我们可以缓存它。
这种方法甚至比常规代理还要慢。在进行了一些代码级别的性能分析后,看来写入 OutputStream
需要很长时间,因为 IIS 试图将字节发送到浏览器。因此,存在网络延迟加上传输一个块所花费的时间。频繁调用 OutputStream.Write
产生的累积网络延迟大大增加了总操作的延迟。
第二种方法是尝试多线程。从 ASP.NET 线程启动的新线程会持续从 Socket
读取,而无需等待发送字节到浏览器的 Response.OutputStream
。主 ASP.NET 线程等待字节被收集,然后立即将其传输到响应。
private int TransmitDataAsync(HttpContext context, Stream readStream,
MemoryStream responseBuffer)
{
this._ResponseStream = readStream;
_PipeStream = new Utility.PipeStreamBlock(5000);
byte[] buffer = new byte[BUFFER_SIZE];
Thread readerThread = new Thread(new ThreadStart(this.ReadData));
readerThread.Start();
int totalBytesWritten = 0;
int dataReceived;
while ((dataReceived = this._PipeStream.Read(buffer, 0, BUFFER_SIZE)) > 0)
{
context.Response.OutputStream.Write(buffer, 0, dataReceived);
responseBuffer.Write(buffer, 0, dataReceived);
totalBytesWritten += dataReceived;
}
_PipeStream.Dispose();
return totalBytesWritten;
}
在这里,读取是在 PipeStream
上执行的,而不是在 ASP.NET 线程的套接字上。有一个新线程在下载外部站点字节的同时将数据写入 PipeStream
。结果是,ASP.NET 线程不断地将数据写入 OutputStream
,而另一个线程则不间断地从外部服务器下载数据。下面的代码从外部服务器下载数据,然后将其存储在 PipeStream
中。
private void ReadData()
{
byte[] buffer = new byte[BUFFER_SIZE];
int dataReceived;
int totalBytesFromSocket = 0;
try
{
while ((dataReceived = this._ResponseStream.Read(buffer, 0,
BUFFER_SIZE)) > 0)
{
this._PipeStream.Write(buffer, 0, dataReceived);
totalBytesFromSocket += dataReceived;
}
}
catch (Exception x)
{
Log.WriteLine(x.ToString());
}
finally
{
this._ResponseStream.Dispose();
this._PipeStream.Flush();
}
}
这种方法的缺点是,仍然有太多的 Response.OutputStream.Write
调用。外部服务器以可变数量的字节传递内容,有时是 3592 字节,有时是 8192 字节,有时只有 501 字节。这完全取决于您的服务器与外部服务器的连接速度。通常,Microsoft 服务器近在咫尺,因此在从 MSDN feed 读取时调用 _ResponseStream.Read
时,几乎总是可以获得 8192 字节(缓冲区最大大小)字节。但是,当您与一个不可靠的服务器通信时,例如在澳大利亚的服务器,您并不总能每次读取调用都获得 8192 字节。因此,您最终会比应该的多调用 Response.OutputStream.Write
。所以,一个更好也是最终的方法是引入另一个缓冲区,该缓冲区将持有正在写入 ASP.NET Response
的字节,并在准备好传输 8192 字节时立即刷新到 Respose.OutputStream
。这个中间缓冲区将确保始终将 8192 字节传输到 Response.OutputStream
。
private int TransmitDataAsyncOptimized(HttpContext context, Stream readStream,
MemoryStream responseBuffer)
{
this._ResponseStream = readStream;
_PipeStream = new Utility.PipeStreamBlock(10000);
byte[] buffer = new byte[BUFFER_SIZE];
// Asynchronously read content form response stream
Thread readerThread = new Thread(new ThreadStart(this.ReadData));
readerThread.Start();
int totalBytesWritten = 0;
int dataReceived;
byte[] outputBuffer = new byte[BUFFER_SIZE];
int responseBufferPos = 0;
while ((dataReceived = this._PipeStream.Read(buffer, 0, BUFFER_SIZE)) > 0)
{
// if about to overflow, transmit the response buffer and restart
int bufferSpaceLeft = BUFFER_SIZE - responseBufferPos;
if (bufferSpaceLeft < dataReceived)
{
Buffer.BlockCopy(buffer, 0, outputBuffer,
responseBufferPos, bufferSpaceLeft);
context.Response.OutputStream.Write(outputBuffer, 0, BUFFER_SIZE);
responseBuffer.Write(outputBuffer, 0, BUFFER_SIZE);
totalBytesWritten += BUFFER_SIZE;
// Initialize response buffer
// and copy the bytes that were not sent
responseBufferPos = 0;
int bytesLeftOver = dataReceived - bufferSpaceLeft;
Buffer.BlockCopy(buffer, bufferSpaceLeft,
outputBuffer, 0, bytesLeftOver);
responseBufferPos = bytesLeftOver;
}
else
{
Buffer.BlockCopy(buffer, 0, outputBuffer,
responseBufferPos, dataReceived);
responseBufferPos += dataReceived;
}
}
// If some data left in the response buffer, send it
if (responseBufferPos > 0)
{
context.Response.OutputStream.Write(outputBuffer, 0, responseBufferPos);
responseBuffer.Write(outputBuffer, 0, responseBufferPos);
totalBytesWritten += responseBufferPos;
}
_PipeStream.Dispose();
return totalBytesWritten;
}
上述方法确保每次只向 ASP.NET 响应流写入 8192 字节。这样,写入响应的总次数就是(读取的总字节数/8192)。
带异步 HTTP Handler 的流式代理
既然我们正在流式传输字节,我们就需要使代理异步,这样它就不会长时间占用主 ASP.NET 线程。异步意味着它将在调用外部服务器后立即释放 ASP.NET 线程。当外部服务器调用完成并且有可用字节下载时,它将从 ASP.NET 线程池获取一个线程并完成执行。
当代理不是异步运行时,它会一直占用 ASP.NET 线程,直到整个连接和下载操作完成。如果外部服务器响应缓慢,它会不必要地长时间占用 ASP.NET 线程。结果,如果代理收到太多针对慢速服务器的请求,ASP.NET 线程将很快耗尽,您的服务器将停止响应任何新请求。我们在 Pageflakes 遇到了这样的问题。我们正在请求一个股票报价 Web 服务的数据。该 Web 服务花费了超过 60 秒才响应调用。由于当时我们没有异步 Handler,我们的内容代理占用了线程池中的所有 ASP.NET 线程,我们的站点无法响应。我们不得不在两天内每 10 分钟重启一次 IIS 来解决这个问题,直到股票报价 Web 服务自我修复。
创建异步 HTTP Handler 并不容易理解。这篇 MSDN 文章试图解释它,但很难完全从这篇文章中理解这个概念。因此,我在我的书《Building a Web 2.0 portal using ASP.NET 3.5》中专门用了一个章节来解释如何构建异步 Handler。根据我的经验,我发现大多数人都对何时使用它感到困惑。因此,我展示了三种异步 Handler 有用的具体场景。我还解释了这种内容代理的几个其他可扩展性问题,您会觉得阅读它们很有趣。特别是,通过利用内容代理来使网站瘫痪的几种巧妙的攻击尝试,以及如何防御它们。
第一步是实现 IHttpAsyncHandler
并将 ProcessRequest
函数的代码分成两部分 - BeginProcessRequest
和 EndProcessRequest
。Begin 方法将对 HttpWebRequest.BeginGetResponse
进行异步调用,并将线程返回给 ASP.NET 线程池。
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb,
object extraData)
{
string url = context.Request["url"];
int cacheDuration = Convert.ToInt32(context.Request["cache"] ?? "0");
string contentType = context.Request["type"];
if (cacheDuration > 0)
{
if (context.Cache[url] != null)
{
// We have response to this URL already cached
SyncResult result = new SyncResult();
result.Context = context;
result.Content = context.Cache[url] as CachedContent;
return result;
}
}
HttpWebRequest request = HttpHelper.CreateScalableHttpWebRequest(url);
request.AutomaticDecompression = DecompressionMethods.None;
if (!string.IsNullOrEmpty(contentType))
request.ContentType = contentType;
AsyncState state = new AsyncState();
state.Context = context;
state.Url = url;
state.CacheDuration = cacheDuration;
state.Request = request;
return request.BeginGetResponse(cb, state);
}
当 BeginGetResponse
调用完成,外部服务器开始向我们发送响应字节时,ASP.NET 会调用 EndProcessRequest
方法。此方法从外部服务器下载字节,然后将其传回浏览器。
public void EndProcessRequest(IAsyncResult result)
{
if (result.CompletedSynchronously)
{
// Content is already available in the cache
// and can be delivered from cache
SyncResult syncResult = result as SyncResult;
syncResult.Context.Response.ContentType =
syncResult.Content.ContentType;
syncResult.Context.Response.AppendHeader("Content-Encoding",
syncResult.Content.ContentEncoding);
syncResult.Context.Response.AppendHeader("Content-Length",
syncResult.Content.ContentLength);
syncResult.Content.Content.Seek(0, SeekOrigin.Begin);
syncResult.Content.Content.WriteTo(
syncResult.Context.Response.OutputStream);
}
else
{
// Content is not available in cache and needs to be
// downloaded from external source
AsyncState state = result.AsyncState as AsyncState;
state.Context.Response.Buffer = false;
HttpWebRequest request = state.Request;
using (HttpWebResponse response =
request.EndGetResponse(result) as HttpWebResponse)
{
this.DownloadData(request, response, state.Context, state.CacheDuration);
}
}
}
就这样。一个快速、可扩展、连续的 AJAX 流式代理,其性能始终优于网络上任何常规 AJAX 代理。
如果您想知道 HttpHelper
、AsyncState
和 SyncResult
类是什么,它们是一些辅助类。以下是这些辅助类的代码。
public static class HttpHelper
{
public static HttpWebRequest CreateScalableHttpWebRequest(string url)
{
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Headers.Add("Accept-Encoding", "gzip");
request.AutomaticDecompression = DecompressionMethods.GZip;
request.MaximumAutomaticRedirections = 2;
request.ReadWriteTimeout = 5000;
request.Timeout = 3000;
request.Accept = "*/*";
request.Headers.Add("Accept-Language", "en-US");
request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US;" +
" rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6";
return request;
}
public static void CacheResponse(HttpContext context,
int durationInMinutes)
{
TimeSpan duration = TimeSpan.FromMinutes(durationInMinutes);
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now.Add(duration));
context.Response.Cache.
AppendCacheExtension("must-revalidate, proxy-revalidate");
context.Response.Cache.SetMaxAge(duration);
}
public static void DoNotCacheResponse(HttpContext context)
{
context.Response.Cache.SetNoServerCaching();
context.Response.Cache.SetNoStore();
context.Response.Cache.SetMaxAge(TimeSpan.Zero);
context.Response.Cache.
AppendCacheExtension("must-revalidate, proxy-revalidate");
context.Response.Cache.SetExpires(DateTime.Now.AddYears(-1));
}
}
public class CachedContent
{
public string ContentType;
public string ContentEncoding;
public string ContentLength;
public MemoryStream Content;
}
public class AsyncState
{
public HttpContext Context;
public string Url;
public int CacheDuration;
public HttpWebRequest Request;
}
public class SyncResult : IAsyncResult
{
public CachedContent Content;
public HttpContext Context;
#region IAsyncResult Members
object IAsyncResult.AsyncState
{
get { return new object(); }
}
WaitHandle IAsyncResult.AsyncWaitHandle
{
get { return new ManualResetEvent(true); }
}
bool IAsyncResult.CompletedSynchronously
{
get { return true; }
}
bool IAsyncResult.IsCompleted
{
get { return true; }
}
#endregion
}
就这样,各位。
结论
好了,您拥有了一个比其他任何人都更快速、更具可扩展性的 AJAX 代理。所以,感觉非常棒吧!:)