38

我有一些我正在编写的 Web 服务,并且我正在尝试尽可能地 RESTful。我使用在 IIS/ASP.NET/SharePoint 内运行的 HTTPHandler 托管这些 Web 服务。

我的大多数服务都需要 HTTP GET。我有两个只是返回一些数据(即查询)并且将是Idempotent,但参数可能有些复杂。它们都可以在服务的参数中包含至少在 URL 的 PATH 部分中不允许使用的字符。

使用 IIS、ASP.NET 和 SharePoint,我发现 URL 路径中的以下字符甚至无法到达我的 HttpHandler,即使 Url 编码(请求爆炸了,我对此没有任何简单的控制) :

  • % (%25)
  • & (%26)
  • * (%2a, 但没有 Url 编码)
  • + (%2b)
  • : (%3a)
  • < (%3c)
  • (%3e)

以下字符进入了我的 HttpHandler,但即使 Url 编码,UriTemplate 也无法正确处理它们:

  • (%23)

  • . (%2e,但没有 Url 编码;UriTemplate 删除了“。”如果是 / 之前的最后一个字符)
  • ? (%3f)
  • /(%2f - UriTemplate 因明显原因而失败,即使 UrlEncoded 也是如此)
  • \ (%5c)

所以,我已经有些彻底,但我需要在查询字符串中测试这些 url 编码的字符。看来这将在那里大部分工作。

在我的一项服务中,作为参数的特殊字符在语义上是查询/过滤器的一部分(实际上是搜索服务的搜索词),但在另一项服务中,它们并不是查询/过滤器的一部分,因此理想情况下它们是路径而不是查询字符串。

我的问题是,什么选择是最好的?以下是我知道的一些:

  1. 使用 HTTP GET 和查询字符串。 任何可能使用特殊字符的内容都应该在查询字符串上并进行 Url 编码。 这是我倾向于的地方,但我担心查询字符串过长(IE 有 2083 个限制)

  2. 在路径中使用 HTTP GET 和 base64 编码。 对于可能使用特殊字符的任何参数,请使用修改后的 Base64 作为 URL,并在首选时将它们保留为路径的一部分。 我试过这个并且它有效,但它有点难看。仍然担心极长的查询字符串。

  3. 使用 HTTP POST 和消息正文。 任何可能使用特殊字符的内容都应该在请求的正文中。 似乎是一个不错的解决方案,但是帖子被理解为不是的,并且(我认为)通常是为了更改(而这里没有发生更改)。

  4. 使用 HTTP GET 和消息正文。 任何可能使用特殊字符的内容都应该在请求的正文中。根据SO:HTTP GET with request bodyRoy Fielding这似乎是个坏主意。

  5. 根据请求的大小,使用 #3 和上面的 #1 或 #2 的组合。

  6. 其他???

请注意,在某些情况下,我可能能够改变一些东西以防止特殊字符(我可能会这样做),但我无法在所有情况下都这样做。


关于 URI 长度,RFC2616 Sec3.2.1说明如下:

HTTP 协议对 URI 的长度没有任何先验限制。服务器必须能够处理它们所服务的任何资源的 URI,并且如果它们提供可以生成此类 URI 的基于 GET 的表单,则应该能够处理无限长度的 URI。如果 URI 比服务器可以处理的长(参见第 10.4.15 节),服务器应该返回 414(Request-URI Too Long)状态。

  Note: Servers ought to be cautious about depending on URI lengths
  above 255 bytes, because some older client or proxy
  implementations might not properly support these lengths.

此外,Internet Explorer 中的最大 URL 长度为 2,083 个字符

相关:如何在 REST 中传递复杂的查询?

4

11 回答 11

34

没有完美的方法可以做到这一点。

正确的 HTTP/REST 方法是使用 GET 并将 URL 中的所有参数作为查询参数。您已经确定了这种方法的两个实际问题

  1. 即使 URL 编码,您的服务器软件也没有正确地向您传递某些字符。实际上,这让我感到惊讶,您应该更仔细地查看发生了什么,您甚至无法通过 URL 获得 %。您的框架是否允许您对 PATH_INFO 或其他未处理的字符进行原始访问?这可能会给你一个解决方法。
  2. 您的查询字符串可能太长。您提到了 MSIE 中的 2083 字节限制。这对您来说可能是也可能不是实际问题,具体取决于 MSIE 是否是您的 API 的客户端。(即:通过 Javascript 调用 JSON API)。但根据我的经验,很长的 URL 最终会在几个地方神秘地中断;沿路径的代理缓存,甚至是状态防火墙。如果您对客户端和网络路径有绝对控制权,您可能会忍受长 URL 的危险。如果它是公共 API,请忽略它。

希望您可以使简单的 GET 在您的环境中工作。您甚至可能需要考虑重构您的 API 以使查询数据更小。

但是如果你不能让 GET 工作怎么办?您提出了几种替代方案。我会立即解雇其中两个。不要将内容放在 GET 请求正文中;如果您尝试这样做,太多的软件会崩溃,而且无论如何它违反了您试图捕捉的 REST 精神。而且我不会使用 base64 编码。它可以帮助您解决问题 1,您的服务器没有正确处理 URL 中的某些字符。但是,如果应用错误,它实际上会使您的 URL 更长,而不是更短,从而使问题 2 更加复杂。即使您正确执行 base64 并包含一些压缩,它也不会使 URL 显着缩短,并且会使客户端更加复杂。

您最实用的解决方案可能是选项 3,即 HTTP POST。这不是 RESTful 的;您应该将 GET 用于只读查询。而且您将失去 REST 方法的一些优势,包括 GET 等的缓存。另一方面,它可以与各种 Internet 基础设施和软件库一起正常且简单地工作。然后,您可以通过 multipart/form-data 编码、JSON 或 XML 在 POST 正文中传递尽可能多的数据。(我已经使用 SOAP 构建了两个主要的公共 Web 服务,它只是 POST 上的 XML。它很丑陋而且不是 RESTful,但它确实可以可靠地工作。)

REST 是一个伟大的设计范式。这是一个指导方针。如果它不适合您的应用程序,请不要觉得您需要坚持使用它。HTTP 不擅长使用 GET 将大量数据传递给服务器。如果您需要巨大的查询参数,请执行其他操作。

于 2009-08-17T20:48:18.193 回答
17

如果查询太大而无法进入 URI,请将您的查询转换为资源(如已保存的搜索)。我为酒店预订系统开发了一个宁静的 API;搜索查询有太多参数(首选项,房间列表...等),所以我把它变成了我发布到服务器的资源。然后服务器回复一个唯一标识搜索的 URI,哪个正文是发布的查询 + 其结果:

POST http://hotels.xyz/searches
body <search><query>...</query></search>

回复

201 Created - Location: http://hotels.xyz/searches/someID
Body <search><query>...</query><results>...</results></search>
于 2010-12-20T19:21:25.403 回答
6

我建议您阅读HTTP 1.1 规范,尤其是3.2 统一资源标识符9.1.1 安全方法部分。这些希望能回答你的问题。


以下是一些附加信息:

于 2009-08-12T16:05:14.783 回答
5

如果没有其他方法,请使用带有 HTTP GET 的自定义 HTTP 标头。几乎所有客户端都可以设置 HTTP 标头。

通常最好在查询字符串中使用 URL 参数。URL参数过多表示需要拆分成更细粒度的服务。

于 2009-08-16T02:01:24.820 回答
2

您应该使用 HTTP GET 请求将参数放在查询字符串中。一些较旧的 Web 浏览器的限制不是问题,因为在 Web 浏览器中通过 API 浏览的唯一人员可能是开发人员(或至少是技术人员)。

请记住,客户端应用程序不应操纵您的 API 提供给它们的 URL。URL 对客户端来说是不透明的标识符,仅用于将它们引导到可以找到特定资源的位置。

如果由于某种原因无法做到这一点,我将使用 POST 请求,并将参数表单编码到正文中。它不会完全是 RESTful,但假设您的资源设计得当,对客户端代码的影响应该是最小的。

于 2009-08-14T23:20:48.717 回答
2

Roy Fielding可能会赞成在这种情况下使用 POST,但你必须问他。

通常,大多数涉及将用户提供的数据提供给服务器的应用程序都是不安全的。 唯一的例外是当信息以通用查询参数的形式出现时,通常需要在 GET 和 POST 之间进行权衡,这通常涉及参数内容的大小GET 仅适用于参数可以表示为有意义的 URI 的情况。

于 2009-08-18T09:21:52.950 回答
2

如果您在服务器上生成这些长 URL,则可以对路径信息使用压缩。

因此,如果您有类似 /?param1=bla-bla¶m2=bla-bla 的内容,您只需压缩该参数并使 url 看起来像 /?query=ASsadcnfAFFASFscnsdlc

当你收到这样的请求时,你只需解压它们并解析参数字符串

于 2009-08-12T15:54:43.697 回答
2

我肯定会从您开始的地方开始:URL 缩短。我会尝试缩短参数名称 (?a=XXX;b=YYY;c=zzz); 将整个查询重新编码为 Base64;GZip Base64;霍夫曼编码 GZip;... 不惜一切代价。一旦我知道缩短不适用于所有情况(您有一些可以无限期添加的动态过滤器创建系统,或者 w/e),那么您必须承认可能会尝试做所有事情在单个请求中可能无法正常工作...

我不建议您使用拆分参数抛出多个 GET 并尝试以这种方式跟踪请求...

我可以建议的唯一“稳健”方法是将请求的查询字符串存储/设置在一个请求(POST)中,并让它返回一个固定大小的 ID(或 guid),用于标识数据存储中的请求参数位置(filterID),然后使用 filterID 令牌而不是完整的过滤器查询字符串值发出实际的 GET 请求。这将允许各种巧妙的事情,例如基于 filterID 缓存响应,因此您可以(理论上)稍后重用相同的过滤器(而不是手动重新输入它们,只需将“标签”与过滤器主体一起保存并从按标签的最后 5 个过滤器),或者至少将它们与您的数据一起存储,以便每次刷新页面时都不会重新发送整个过滤器请求。

于 2009-08-17T20:28:17.080 回答
1

我的目标是 HTTP POST。当它到达 PHP(或您使用的任何一个)时,它被很好地标记化,并且它没有其他人的大小限制。

于 2009-08-12T15:49:18.123 回答
0

base64 应该这样做。其他明智的使用标准的 % 符号。

于 2009-08-17T12:23:09.333 回答
0

Consider supporting :
- GET requests with short query string
- POST requests with long query string into the body and X-HTTP-Method-Override: GET (https://en.wikipedia.org/wiki/List_of_HTTP_header_fields)

Beware not mixing "POST /orders" that is a bulk creation of new orders and "POST /orders" with a "X-HTTP-Method-Override: GET" that is a search of order.

于 2016-03-21T14:50:05.740 回答