带请求正文的HTTP GET

时间:2009-06-10 20:47:24

标签: rest http http-get

我正在为我们的应用程序开发一个新的RESTful Web服务。

在某些实体上执行GET时,客户端可以请求实体的内容。 如果他们想要添加一些参数(例如排序列表),他们可以在查询字符串中添加这些参数。

或者,我希望人们能够在请求正文中指定这些参数。 HTTP/1.1似乎没有明确禁止这一点。这将允许他们指定更多信息,可以更容易地指定复杂的XML请求。

我的问题:

  • 这是一个好主意吗?
  • HTTP客户端在GET请求中是否存在使用请求主体的问题?

http://tools.ietf.org/html/rfc2616

21 个答案:

答案 0 :(得分:1421)

Roy Fielding's comment about including a body with a GET request

  

是。换句话说,允许包含任何HTTP请求消息   消息体,因此必须解析消息。   但是,GET的服务器语义被限制为一个正文,   如果有的话,对请求没有语义含义。要求   解析与方法语义的要求是分开的。

     

所以,是的,你可以发送一个GET的主体,不,它永远不会有用   这样做。

     

这是HTTP / 1.1分层设计的一部分   一旦规范被分区(正在进行中),就再次清除。

     

...罗伊

是的,您可以使用GET发送请求正文,但它不应该有任何意义。如果您通过在服务器上解析它并根据其内容更改您的响应来赋予它意义,那么您忽略了the HTTP/1.1 spec, section 4.3中的此建议:

  

[...]如果请求方法      不包含实体主体的定义语义,然后是      处理请求时,将忽略message-body SHOULD

the HTTP/1.1 spec, section 9.3中的GET方法说明:

  

GET方法意味着检索Request-URI标识的任何信息([...])。

表明请求主体不是GET请求中资源标识的一部分,只是请求URI。

<强>更新 引用为“HTTP / 1.1规范”的RFC2616现已过时。 2014年,它被RFC 7230-7237取代。引用“处理请求时应该忽略消息体”已被删除。它现在只是“请求消息框架独立于方法语义,即使该方法没有定义消息体的任何用途”第二个引用“GET方法意味着检索任何信息......由Request-URI标识”被删除了。 - 来自评论

答案 1 :(得分:255)

虽然可以这样做,但在HTTP规范没有明确排除的范围内,我建议避免使用它只是因为人们不希望事情以这种方式工作。 HTTP请求链中有许多阶段,虽然它们“大部分”符合HTTP规范,但您唯一可以肯定的是它们的行为与Web浏览器一样。 (我正在考虑透明代理,加速器,A / V工具包等等)。

这是Robustness Principle背后的精神,“你接受的是自由主义,你发送的内容是保守的”,你不想在没有充分理由的情况下突破规范的界限。

但是,如果你有充分的理由,那就去吧。

答案 2 :(得分:128)

如果您尝试利用缓存,则可能会遇到问题。代理人不会在GET主体中查看参数是否会对响应产生影响。

答案 3 :(得分:65)

restclientREST console都不支持此功能,但curl支持此功能。

HTTP specification在第4.3节中说明

  

如果请求方法的规范(第5.1.1节)不允许在请求中发送实体主体,则消息体不得包含在请求中。

Section 5.1.1将我们重定向到第9.x节的各种方法。他们都没有明确禁止包含消息体。然而...

Section 5.2

  

Internet请求标识的确切资源是通过检查Request-URI和Host头字段来确定的。

Section 9.3

  

GET方法意味着检索Request-URI标识的任何信息(以实体的形式)。

这一起表明,当处理GET请求时,服务器不是 required 来检查Request-URI和Host头字段之外的任何其他内容。

总之,HTTP规范并不会阻止您使用GET发送消息体,但是如果所有服务器都不支持它,那么它就不会让我感到惊讶。

答案 4 :(得分:43)

Elasticsearch接受带有正文的GET请求。这似乎是首选方式:http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/common-options.html#_request_body_in_query_string

某些客户端库(如Ruby驱动程序)可以在开发模式下将cry命令记录到stdout,并且它正在广泛使用此语法。

答案 5 :(得分:28)

您尝试实现的目标已经使用更常见的方法完成了很长时间,并且不依赖于使用GET的有效负载。

您可以简单地构建特定的搜索媒体类型,或者如果您想要更加RESTful,请使用类似OpenSearch的内容,并将请求POST到服务器指示的URI,比如说/ search。然后,服务器可以生成搜索结果或构建最终URI并使用303重定向。

这样做的好处是可以遵循传统的PRG方法,帮助缓存中介缓存结果等等。

也就是说,对于任何非ASCII的URI,URI都是编码的,因此application / x-www-form-urlencoded和multipart / form-data也是如此。如果您的目的是支持ReSTful场景,我建议使用此而不是创建另一种自定义json格式。

答案 6 :(得分:21)

你可以发送一个GET与一个正文或发送一个POST并放弃RESTish宗教信仰(它不是那么糟糕,5年前只有一个信仰的成员 - 他的评论在上面联系)。

两者都没有很好的决定,但发送GET机构可以防止某些客户端和某些服务器出现问题。

执行POST可能会遇到一些RESTish框架的障碍。

Julian Reschke建议上面使用像“SEARCH”这样的非标准HTTP标头,这可能是一个优雅的解决方案,除了它更不可能得到支持。

列出能够和不能完成上述各项工作的客户可能最有成效。

无法向身体发送GET的客户(我知道):

  • XmlHTTPRequest Fiddler

可以使用正文发送GET的客户端:

  • 大多数浏览器

服务器&amp;可以从GET中检索主体的库:

  • 的Apache
  • PHP

从GET中剥离正文的服务器(和代理):

答案 7 :(得分:21)

  

哪个服务器会忽略它? - fijiaaron 2012年8月30日21:27

例如,

Google 比忽略它更糟糕,它会认为是错误

使用简单的netcat自己尝试:

$ netcat www.google.com 80
GET / HTTP/1.1
Host: www.google.com
Content-length: 6

1234

(1234内容后跟CR-LF,因此总共6个字节)

你会得到:

HTTP/1.1 400 Bad Request
Server: GFE/2.0
(....)
Error 400 (Bad Request)
400. That’s an error.
Your client has issued a malformed or illegal request. That’s all we know.

你也可以从Bing,Apple等获得400个不良请求,这些请求由AkamaiGhost提供。

所以我不建议将GET请求与正文实体一起使用。

答案 8 :(得分:16)

来自RFC 2616, section 4.3,“讯息正文”:

  

服务器应该在任何请求中读取和转发消息体;如果   请求方法不包含实体主体的定义语义,   那么在处理请求时应该忽略message-body。

也就是说,服务器应始终从网络中读取任何提供的请求主体(检查Content-Length或读取分块的主体等)。此外,代理应转发他们收到的任何此类请求正文。然后,如果RFC为给定方法定义了主体的语义,则服务器实际上可以使用请求主体来生成响应。但是,如果RFC 没有定义正文的语义,那么服务器应该忽略它。

这与上面Fielding的引用一致。

Section 9.3,“GET”,描述了GET方法的语义,并没有提到请求体。因此,服务器应该忽略它在GET请求上收到的任何请求主体。

答案 9 :(得分:14)

我把这个问题提交给了IETF HTTP WG。 Roy Fielding(1998年http / 1.1文件的作者)的评论是

  

&#34; ......除了解析和丢弃该机构以外,还可以执行任何其他操作&#34;

RFC 7213(HTTPbis)声明:

  

&#34; GET请求消息中的有效负载没有定义的语义;&#34;

现在似乎很清楚,目的是禁止GET请求机构的语义,这意味着请求体不能用于影响结果。

如果您在GET中包含正文,那么会有肯定以各种方式破坏您的请求的代理。

总而言之,不要这样做。

答案 10 :(得分:7)

如果您确实希望将可缓存的JSON / XML正文发送到Web应用程序,那么放置数据的唯一合理位置是使用RFC4648: Base 64 Encoding with URL and Filename Safe Alphabet编码的查询字符串。当然你可以只是urlencode JSON并将put放在URL param的值中,但Base64会给出较小的结果。请注意,有网址大小限制,请参阅What is the maximum length of a URL in different browsers?

您可能认为Base64的填充=字符可能对URL的参数值有害,但似乎没有 - 请参阅此讨论:http://mail.python.org/pipermail/python-bugs-list/2007-February/037195.html。但是,您不应该将编码数据放在没有参数名称的情况下,因为带有填充的编码字符串将被解释为具有空值的参数键。 我会使用像?_b64=<encodeddata>这样的东西。

答案 11 :(得分:7)

根据XMLHttpRequest,它无效。来自standard

  

4.5.6 send()方法

     
client . send([body = null])
         

发起请求。可选参数提供请求     身体。如果请求方法为GETHEAD,则忽略该参数。

         

如果任何一个状态不是,则引发InvalidStateError异常     已打开或已设置send()标记。

  
     

send(body)方法必须执行以下步骤:

     
      
  1. 如果状态未打开,则抛出InvalidStateError例外。
  2.   
  3. 如果设置了send()标志,则抛出InvalidStateError例外。
  4.   
  5. 如果请求方法为GETHEAD,请将正文设置为空。
  6.   
  7. 如果 body 为null,请转到下一步。
  8.   

虽然,我认为不应该这样,因为GET请求可能需要大量的内容。

因此,如果您依赖浏览器的XMLHttpRequest,它很可能无法正常工作。

答案 12 :(得分:5)

例如,它适用于Curl,Apache和PHP。

PHP文件:

<?php
echo $_SERVER['REQUEST_METHOD'] . PHP_EOL;
echo file_get_contents('php://input') . PHP_EOL;

控制台命令:

$ curl -X GET -H "Content-Type: application/json" -d '{"the": "body"}' 'http://localhost/test/get.php'

输出:

GET
{"the": "body"}

答案 13 :(得分:4)

不合格的base64编码头怎么办? “SOMETHINGAPP-PARAMS:sdfSD45fdg45 / AS”

长度限制hm。你不能让你的POST处理区分意义吗?如果你想要简单的参数,如排序,我不明白为什么这将是一个问题。我想你确实很担心。

答案 14 :(得分:4)

恕我直言,您可以在JSON中发送encodeURIComponent编码(即。URL),这样您就不会违反HTTP规范并获得JSON 1}}到服务器。

答案 15 :(得分:3)

我不建议这样做,这违反了标准做法,并没有提供那么多回报。你想保留身体的内容,而不是选择。

答案 16 :(得分:2)

我很担心REST协议不支持OOP,Get方法就是证据。作为解决方案,您可以将DTO序列化为JSON,然后创建查询字符串。在服务器端,您可以将查询字符串反序列化为DTO。

看看:

基于消息的方法可以帮助您解决Get方法限制。您可以像请求正文一样发送任何DTO

Nelibur web service framework provides functionality which you can use

var client = new JsonServiceClient(Settings.Default.ServiceAddress);
var request = new GetClientRequest
    {
        Id = new Guid("2217239b0e-b35b-4d32-95c7-5db43e2bd573")
    };
var response = client.Get<GetClientRequest, ClientResponse>(request);

as you can see, the GetClientRequest was encoded to the following query string

http://localhost/clients/GetWithResponse?type=GetClientRequest&data=%7B%22Id%22:%2217239b0e-b35b-4d32-95c7-5db43e2bd573%22%7D

答案 17 :(得分:0)

您有一个选项列表,它们比使用带有GET的请求正文要好得多。

让我们假设您具有类别和每个类别的项目。两者都由一个id标识(在此示例中为“ catid” /“ itemid”)。您要根据另一个参数“ sortby”以特定的“顺序”进行排序。您要传递“ sortby”和“ order”的参数:

您可以:

  1. 使用查询字符串,例如 example.com/category/{catid}/item/{itemid}?sortby=itemname&order=asc
  2. 对路径使用mod_rewrite(或类似方法): example.com/category/{catid}/item/{itemid}/{sortby}/{order}
  3. 使用与请求一起传递的单个HTTP标头
  4. 使用其他方法,例如POST,以检索资源。

所有方法都有其缺点,但比使用GET与正文更好。

答案 18 :(得分:0)

即使经常使用的工具使用此功能(如本页上经常提到的那样),我仍然认为这是一个很糟糕的主意,尽管它没有被规范禁止,但过于奇特。

许多中间基础设施可能只是拒绝此类请求。

通过示例,忘记使用网站前面的一些可用CDN,例如one

  

如果查看器GET请求包含主体,则CloudFront会向查看器返回HTTP状态码403(禁止)。

是的,您的客户端库也可能不支持发出此类请求,如本comment中所述。

答案 19 :(得分:0)

我在客户端程序中使用Spring框架的RestTemplate,并且在服务器端,我使用Json主体定义了GET请求。我的主要目的与您的主要目的相同:当请求具有多个参数时,将其放入正文中似乎比将其放入延长的URI字符串中更整齐。是吗?

但是,可悲的是,它不起作用!服务器端引发了以下异常:

org.springframework.http.converter.HttpMessageNotReadableException:缺少所需的请求正文...

但是我很确定客户端代码正确地提供了邮件正文,所以怎么了?

我追踪到RestTemplate.exchange()方法并发现以下内容:

// SimpleClientHttpRequestFactory.class
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
    ...
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
        ...
        if (!"POST".equals(httpMethod) && !"PUT".equals(httpMethod) && !"PATCH".equals(httpMethod) && !"DELETE".equals(httpMethod)) {
            connection.setDoOutput(false);
        } else {
            connection.setDoOutput(true);
        }
        ...
    }
}

// SimpleBufferingClientHttpRequest.class
final class SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest {
    ...
    protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
        ...
        if (this.connection.getDoOutput() && this.outputStreaming) {
            this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
        }

        this.connection.connect();
        if (this.connection.getDoOutput()) {
            FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
        } else {
            this.connection.getResponseCode();
        }
        ...
    }
}

请注意,在executeInternal()方法中,输入参数'bufferedOutput'包含我的代码提供的消息正文。我通过调试器看到了它。

但是,由于prepareConnection()的原因,executeInternal()中的getDoOutput()始终返回false,这又使bufferedOutput完全被忽略!它不会复制到输出流中。

因此,我的服务器程序未收到消息正文,并引发了该异常。

这是有关Spring框架的RestTemplate的示例。 重点是,即使HTTP规范不再禁止消息正文,某些客户端或服务器库或框架仍可能符合旧规范并拒绝GET请求中的消息正文。

答案 20 :(得分:-2)

创建一个Requestfactory类

import java.net.URI;

import javax.annotation.PostConstruct;

import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class RequestFactory {
    private RestTemplate restTemplate = new RestTemplate();

    @PostConstruct
    public void init() {
        this.restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestWithBodyFactory());
    }

    private static final class HttpComponentsClientHttpRequestWithBodyFactory extends HttpComponentsClientHttpRequestFactory {
        @Override
        protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
            if (httpMethod == HttpMethod.GET) {
                return new HttpGetRequestWithEntity(uri);
            }
            return super.createHttpUriRequest(httpMethod, uri);
        }
    }

    private static final class HttpGetRequestWithEntity extends HttpEntityEnclosingRequestBase {
        public HttpGetRequestWithEntity(final URI uri) {
            super.setURI(uri);
        }

        @Override
        public String getMethod() {
            return HttpMethod.GET.name();
        }
    }

    public RestTemplate getRestTemplate() {
        return restTemplate;
    }
}

和@Autowired,可在任何需要和使用的地方使用,这里是带有RequestBody的一个示例代码GET请求

 @RestController
 @RequestMapping("/v1/API")
public class APIServiceController {
    
    @Autowired
    private RequestFactory requestFactory;
    

    @RequestMapping(method = RequestMethod.GET, path = "/getData")
    public ResponseEntity<APIResponse> getLicenses(@RequestBody APIRequest2 APIRequest){
        APIResponse response = new APIResponse();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        Gson gson = new Gson();
        try {
            StringBuilder createPartUrl = new StringBuilder(PART_URL).append(PART_URL2);
            
            HttpEntity<String> entity = new HttpEntity<String>(gson.toJson(APIRequest),headers);
            ResponseEntity<APIResponse> storeViewResponse = requestFactory.getRestTemplate().exchange(createPartUrl.toString(), HttpMethod.GET, entity, APIResponse.class); //.getForObject(createLicenseUrl.toString(), APIResponse.class, entity);
    
            if(storeViewResponse.hasBody()) {
                response = storeViewResponse.getBody();
            }
            return new ResponseEntity<APIResponse>(response, HttpStatus.OK);
        }catch (Exception e) {
            e.printStackTrace();
            return new ResponseEntity<APIResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }
        
    }
}