如何处理映射到同一响应的不同请求?

时间:2010-04-30 13:53:54

标签: http caching

我正在设计一个Web服务。该请求是幂等的,因此我选择了GET方法。响应的计算成本相对较高,而且不小,所以我希望得到缓存(在协议级别)。 (不要担心我的备忘录,我已经覆盖了;我的问题实际上也是关注整个网络。)

如果缺少,只有一个必需参数和一些带有默认值的可选参数。例如,以下两个映射到响应的相同表示。 (如果这是一个愚蠢的方式来解决它的界面,提出更好的建议。)

GET /service?mandatory_parameter=some_data HTTP/1.1
GET /service?mandatory_parameter=some_data;optional_parameter=default1;another_optional_parameter=default2;yet_another_optional_parameter=default3 HTTP/1.1

但是,我想客户不知道这一点,并将它们分开处理,因此浪费缓存存储。我该怎么做才能避免违反golden rule of caching

  1. 制作规范表格,记录下来(例如,毕竟需要所有参数,需要按特定顺序排序)并返回client error,除非符合要求的表格?
  2. 而不是错误,永久重定向到请求的规范形式?
  3. 或者是否足以不介意请求的外观,并且仅针对相同的回复使用相同的ETag进行回复?

4 个答案:

答案 0 :(得分:4)

首先,不要在查询字符串中使用分号作为分隔符。您应该使用?开始查询字符串,使用&来分隔变量/值对。 RFC 3986没有明确表示您必须使用&,但由于application/x-www-form-urlencoded先例,绝大多数现有代码都使用此分隔符。

其次,你是对的,因为查询字符串中的参数会产生不同的URI,因此,就缓存而言,它是一个不同的资源。假设您需要最佳缓存性能,如果您知道已指定了可选参数,并且其包含是不必要的并且不会影响将要传输的表示,那么您应该重定向到省略该参数的规范表示。 (即,给出一个可选参数,其值设置为默认值。例如,如果您有http://example.com:80/,则可以标准化为http://example.com/,因为80是默认值对于具有HTTP的端口。您可以对查询参数执行相同的操作,因为您控制了URI空间。)如果您包含的参数(可选或其他)出现在规范顺序以外的顺序中,您也应该重定向。如果您知道URI之间的关系将是稳定的,则首选301重定向。否则,请根据需要执行302/307重定向。我建议使用与OAuth相同的方式定义规范表单:按字母顺序排序每个参数,首先按键,然后按值排序。其他规范化操作也会有所帮助。 RFC 3986在URI normalization上有一整段与您相关的内容。这种技术实际上只适用于GET,并且通常不建议在PUT / POST / DELETE上重定向。

第三,ETag非常棒,如果客户端和服务器都能很好地实现,它们可以提供巨大的性能提升。但是,不幸的是,双方都很难做到这一点。同上最后修改。您应该追求这些,因为CPU和带宽节省在工作时非常重要,但它们本身并不足够。其他标头如Cache-Control也经常是必需的。如果您打算详细了解这些内容,请务必熟悉Section 13 of RFC 2616

最后,提醒一句 - 您需要注意这些重定向的问题:尝试访问您的资源的客户端可能经常被重定向到其他位置。这引入了开销,如果客户端针对相同资源发出后续请求,则维护状态以避免后续重定向,从而仅为您节省总体成本。除非您开源了一个利用缓存优化的参考客户端实现,否则您可能永远不会从这些调整中受益。

答案 1 :(得分:1)

我会在你的列表中选择选项(2) - 我会将请求设为RESTful,而不是RPC。

即。在这种情况下,如果您创建请求路径的所有参数部分:

/服务/ mandatory_parameter / some_data / optional_parameter /缺省1 / another_optional_parameter /缺省2 / yet_another_optional_parameter / default3

如果未指定所有可选参数,请将301(永久重定向)返回到填写了默认值的完整资源名称。这将(或应该)由客户端和Web缓存进行适当缓存,并且即使它到达你的后端,然后制作301应该对你来说非常便宜。

此时,您有一个URI的规范形式,缓存将正常/预期。

这确实意味着每个参数组合将被单独缓存(作为301),但是这很好,因为非规范请求将对完整请求和担心额外轮次的客户端具有独立的缓存策略旅行可以自己填写所有参数。

您的选项(3)无法按预期工作 - 每个表单都将独立缓存,因为它们是不同的URI。

还应该注意的是,由于查询参数的原因,许多下游缓存/软件都不会缓存您的响应 ,这就是我建议将其转换为“正确”资源的原因..

答案 2 :(得分:0)

首先,你选择GET是一件好事,因为其他方法没有那么好的缓存支持。据我所知,浏览器会根据参数缓存URI,因此我不认为使用规范形式是个好主意。
您在此处未说明的一件事是如何使用此服务。如果这些请求是从浏览器发出的(并且我认为这些请求可能是从脚本发出的)请求可能看起来相同,即使它们被要求不止一次。因此,请确保生成URI的任何内容都以相同的URI结束,以获得相同的输入数据(删除默认参数或始终包含它们) 谈到ETag,我建议你有这个,虽然我想澄清它是如何工作的;您收到请求,处理所有“昂贵的计算”,然后如果有一个If-None-Match标头与处理的响应具有相同的散列(ETag),则可以返回304 Not-Modified。因此ETag用于避免在客户端已经拥有响应时传输响应。 (当然你可以在服务器端实现缓存,但最好根据输入参数来实现) 要进一步改善客户端的缓存命中,您可能需要在响应中设置正确的缓存标头。

答案 3 :(得分:0)

几个月前,我几乎向我提出了同样的问题。我的回答是我对实现的一个例子所描述的。

在服务器端,我有WFC服务,它以下列表格之一接收请求

GET /Service/RequestedData?param1=data1&param2=data2…
GET /Service/RequestedData/IdOfData?param1=data1&param2=data2…
PUT /Service/RequestedData/IdOfData // with param1=data1&param2=data2… in body
POST /Service/RequestedData/IdOfData // with param1=data1&param2=data2… in body
DELETE /Service/RequestedData/IdOfData

因此请求在REST中,但GET请求有一些可选参数。特别是这部分是你感兴趣的港口。

由于WFC支持URL模板,因此回复客户端请求的函数原型如

[WebGet (UriTemplate = "RequestedData?param1={myParam1}&param2={myParam2}",
         ResponseFormat = WebMessageFormat.Json)]
[OperationContract]
MyResult GetData (string myParam1, int myParam2);

所有请求,例如

GET /Service/RequestedData?param1=&param2=data2
GET /Service/RequestedData?param2=data2&param1=
GET /Service/RequestedData?param2=data2

将映射到我的WCF服务侧的同一个呼叫。所以我少了一个问题。

现在,在每个响应HTTP GET请求的方法的实现开始时,我在HTTP标头“ Cache-Control:max-age = 0 ”中设置。这意味着客户端始终尝试验证客户端浏览器缓存,并且不会像本地缓存那样轻松响应任何ajax请求,就像它可以执行Internet Explorer一样。

接下来,我根据我的数据总是计算ETag。确切的算法是单独讨论的主题,但重要的是,在HTTP GET的所有响应中,HTTP标头中存在ETag

因此客户端每次都会验证其本地缓存并向服务器发送GET请求。他们在“ETag”HTTP标头内发送来自本地缓存的If-None-Match。服务器计算具有数据的ETag,该数据将发送回此GET请求。它的ETag数据与客户端请求服务器发回的响应一样,空主体和代码“304 Not Modified”返回。在这种情况下,浏览器从本地缓存中提供数据。

如果来自未知原因的同一客户端创建了一个新版本的URL请求(将从Web浏览器解释为 URL),那么Web浏览器将无法找到旧版本的服务器响应本地缓存并再次向服务器发送相同的请求。这是一个真正的问题吗?服务器再次发送数据。如果您有服务器端缓存,则可以进行更多优化。在大多数情况下,GET请求的URL将由客户端JavaScript生成,因此您将没有时间遇到这种情况。

ETag的计算以及“Cache-Control: max-age=0”和Etag标头的设置以及设置“304 Not Modified”代码都应该进行WFC服务,但这很容易。

最重要的是,我对ETag计算的实现并不像从数据库服务器获取整个数据并从那里计算MD5缓存那样广泛。我在SQL Server数据库的每一行数据中使用永久rowversion数据类型。这个rowversion不是数据库中变化的计数器。如果更改一行数据rowversion,则相应行中的值将递增。因此,如果从SELECT值的最大值生成rowversion语句,并且与先前的请求相比,此值不会更改,则可以确保数据在该时间段内未更改。 ETa g的计算算法应该只对从表中删除数据敏感。但它也是一个解决的问题。关于这方面的更多信息,您可以阅读Concurrency handling of Sql transactrion

我不想建议将ETag计算作为最佳选择,我只想说,ETag的计算可以比整个数据计算MD5便宜得多。

如果出现错误,服务器会抛出一个异常,该异常将映射到我在throw语句中定义的HTTP代码。作为正文WFC发送标准JSON对象{"description":"My error text"}。也可以使用自定义错误对象(请参阅Is WebProtocolException included in .net 4.0?)。在客户端,我使用jQuery并在错误事件处理程序的相应jQuery.ajax内部,错误消息将被解码并显示给用户。

所以我的建议是:对所有HTTP ETag请求使用Cache-Control: max-age=0和“GET”。对于所有其他请求,我建议您实施 RESTfull 服务。对于错误实现,您应该查看用于服务器和客户端实现的软件支持的最本机方式并使用它。

更新:要清除网址结构,我应该添加以下内容。在我的服务中,像GET /Service/RequestedData/IdOfData这样的主要部分描述了所请求的数据对象。参数param1=data1&param2=data2主要对应于有关排序分页过滤数据的信息。我为jQuery使用活动jqGrid插件,如果最终用户在网格中滚动到下一页,单击列标题(数据排序)或者如果他设置了关于搜索功能的过滤器,所有这些都遵循不同的可选参数附加主URL。