질문자 :Evert
우리 애플리케이션을 위한 새로운 RESTful 웹 서비스를 개발 중입니다.
특정 엔터티에 대해 GET을 수행할 때 클라이언트는 엔터티의 내용을 요청할 수 있습니다. 일부 매개변수를 추가하려는 경우(예: 목록 정렬) 쿼리 문자열에 이러한 매개변수를 추가할 수 있습니다.
또는 사람들이 요청 본문에 이러한 매개변수를 지정할 수 있기를 바랍니다. HTTP/1.1 은 이것을 명시적으로 금지하지 않는 것 같습니다. 이렇게 하면 더 많은 정보를 지정할 수 있고 복잡한 XML 요청을 더 쉽게 지정할 수 있습니다.
내 질문:
- 이것은 완전히 좋은 생각입니까?
- HTTP 클라이언트가 GET 요청 내에서 요청 본문을 사용하는 데 문제가 있습니까?
http://tools.ietf.org/html/rfc2616
GET 요청에 본문을 포함하는 것에 대한 Roy Fielding의 의견 .
예. 즉, 모든 HTTP 요청 메시지는 메시지 본문을 포함할 수 있으므로 이를 염두에 두고 메시지를 구문 분석해야 합니다. 그러나 GET에 대한 서버 의미론은 본문이 있는 경우 요청에 의미론적 의미가 없도록 제한됩니다. 구문 분석에 대한 요구 사항은 메서드 의미론에 대한 요구 사항과 별개입니다.
예, GET으로 본문을 보낼 수 있습니다. 아니요, 그렇게 하는 것은 결코 유용하지 않습니다.
이것은 사양이 분할되면(진행 중인 작업) 다시 명확해질 HTTP/1.1의 계층화된 디자인의 일부입니다.
....로이
예, GET으로 요청 본문을 보낼 수 있지만 의미가 없어야 합니다. 서버에서 구문 분석 하고 내용에 따라 응답을 변경하여 의미를 부여하면 HTTP/1.1 사양, 섹션 4.3 에서 이 권장 사항을 무시하는 것입니다.
...요청 방법이 엔티티 본문에 대해 정의된 의미 체계를 포함하지 않는 경우 요청을 처리할 때 메시지 본문을 무시해야 합니다(SHOULD).
그리고 HTTP/1.1 사양, 섹션 9.3 의 GET 메서드에 대한 설명:
GET 방법은 Request-URI에 의해 식별되는 정보([...])를 검색하는 것을 의미합니다.
요청 본문은 GET 요청에서 리소스 식별의 일부가 아니라 요청 URI일 뿐임을 나타냅니다.
업데이트
"HTTP/1.1 사양"으로 참조되는 RFC2616은 이제 더 이상 사용되지 않습니다. 2014년에는 RFC 7230-7237로 대체되었습니다. "요청을 처리할 때 메시지 본문을 무시해야 합니다"라는 인용문이 삭제되었습니다. 이제 "요청 메시지 프레이밍은 메서드가 메시지 본문에 대한 사용을 정의하지 않더라도 메서드 의미론과 무관합니다." 두 번째 인용문 "GET 메서드는 요청-URI에 의해 식별되는 모든 정보를 검색하는 것을 의미합니다." 삭제되었습니다. - 댓글에서
HTTP 1.1 2014 사양에서 :
GET 요청 메시지 내의 페이로드에는 정의된 의미가 없습니다. GET 요청에서 페이로드 본문을 보내면 일부 기존 구현이 요청을 거부할 수 있습니다.
Paul Morgan그렇게 할 수 는 있지만 HTTP 사양에서 명시적으로 금지하지 않는 한 사람들이 그런 식으로 작동할 것으로 기대하지 않기 때문에 피하는 것이 좋습니다. HTTP 요청 체인에는 많은 단계가 있으며 "대부분" HTTP 사양을 준수하지만 웹 브라우저에서 전통적으로 사용하는 것처럼 동작한다는 것만 확신할 수 있습니다. (투명 프록시, 가속기, A/V 툴킷 등을 생각하고 있습니다.)
이것은 대략 "받아들이는 것에 대해 자유로워지고, 보내는 것에 대해 보수적"인 견고성 원칙 의 정신입니다. 정당한 이유 없이 사양의 경계를 밀고 싶지는 않습니다.
그러나 합당한 이유가 있다면 가십시오.
caskey캐싱을 이용하려고 하면 문제가 발생할 수 있습니다. 프록시는 매개변수가 응답에 영향을 미치는지 확인하기 GET
Darrel Millerrestclient 도 REST 콘솔 도 이것을 지원하지 않지만 curl은 지원합니다.
HTTP 사양 은 섹션 4.3에 나와 있습니다.
요청 방법의 사양(섹션 5.1.1)이 요청에 엔티티 본문을 보내는 것을 허용하지 않는 경우 메시지 본문은 요청에 포함되어서는 안 됩니다(MUST NOT).
섹션 5.1.1 은 다양한 방법에 대해 섹션 9.x로 리디렉션합니다. 그들 중 어느 것도 메시지 본문의 포함을 명시적으로 금지하지 않습니다. 하지만...
섹션 5.2 말한다
인터넷 요청에 의해 식별되는 정확한 리소스는 Request-URI와 Host 헤더 필드를 모두 검사하여 결정됩니다.
섹션 9.3 은 말합니다.
GET 방법은 Request-URI에 의해 식별되는 정보(엔티티 형태)를 검색하는 것을 의미합니다.
이는 함께 GET 요청을 처리할 때 서버가 Request-URI 및 Host 헤더 필드 이외의 다른 항목을 검사할 필요가 없음을 시사합니다.
요약하면, HTTP 사양은 GET으로 메시지 본문을 보내는 것을 방지하지 않지만 모든 서버에서 지원하지 않는 경우 놀라지 않을 만큼 모호성이 있습니다.
Dave DurbinElasticsearch는 본문과 함께 GET 요청을 수락합니다. 이것이 선호하는 방법인 것 같습니다: Elasticsearch 가이드
Ruby 드라이버와 같은 일부 클라이언트 라이브러리는 개발 모드에서 cry 명령을 stdout에 기록할 수 있으며 이 구문을 광범위하게 사용합니다.
jlecour본문과 함께 GET을 보내거나 POST를 보내고 RESTish 종교를 포기할 수 있습니다.
둘 다 좋은 결정은 아니지만 GET 본문을 보내는 것은 일부 클라이언트와 일부 서버의 문제를 방지할 수 있습니다.
일부 RESTish 프레임워크에서는 POST를 수행하는 데 장애물이 있을 수 있습니다.
Julian Reschke는 "SEARCH"와 같은 비표준 HTTP 헤더를 사용하여 지원될 가능성이 훨씬 낮다는 점을 제외하고는 우아한 솔루션이 될 수 있다고 제안했습니다.
위의 각 작업을 수행할 수 있는 클라이언트와 수행할 수 없는 클라이언트를 나열하는 것이 가장 생산적일 수 있습니다.
본문과 함께 GET을 보낼 수 없는 클라이언트(내가 알고 있음):
본문과 함께 GET을 보낼 수 있는 클라이언트:
GET에서 본문을 검색할 수 있는 서버 및 라이브러리:
GET에서 본문을 제거하는 서버(및 프록시):
fijiaaron달성하려는 것은 훨씬 더 일반적인 방법과 GET과 함께 페이로드 사용에 의존하지 않는 방법으로 오랫동안 수행되었습니다.
특정 검색 미디어 유형을 간단히 구축하거나 더 RESTful하고 싶다면 OpenSearch와 같은 것을 사용하고 서버가 지시한 URI(예: /search)에 요청을 POST할 수 있습니다. 그런 다음 서버는 검색 결과를 생성하거나 최종 URI를 빌드하고 303을 사용하여 리디렉션할 수 있습니다.
이것은 전통적인 PRG 방법을 따르고 캐시 중개자가 결과를 캐시하는 데 도움이 되는 이점이 있습니다.
즉, URI는 ASCII가 아닌 모든 것에 대해 어쨌든 인코딩되며 application/x-www-form-urlencoded 및 multipart/form-data도 마찬가지입니다. ReSTful 시나리오를 지원하려는 경우 또 다른 사용자 지정 json 형식을 만드는 것보다 이것을 사용하는 것이 좋습니다.
SerialSeb이 질문을 IETF HTTP WG에 올렸습니다. Roy Fielding(1998년 http/1.1 문서 작성자)의 의견은 다음과 같습니다.
"... 수신된 경우 해당 본문을 구문 분석하고 삭제하는 것 외에 다른 작업을 수행하려면 구현이 중단될 것입니다."
RFC 7213(HTTPbis)은 다음과 같이 명시합니다.
"GET 요청 메시지 내의 페이로드에는 정의된 의미 체계가 없습니다."
이제 GET 요청 본문에 대한 의미론적 의미가 금지되어 요청 본문을 사용하여 결과에 영향을 줄 수 없다는 의도가 분명해진 것 같습니다.
GET에 본문을 포함시키면 다양한 방식으로 요청을 확실히 깨뜨릴 프록시가 있습니다.
결론부터 말씀드리면 하지마세요.
Adrien어떤 서버가 이를 무시할까요? – fijiaaron 8월 30 '12 at 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.
AkamaiGhost에서 제공하는 Bing, Apple 등으로부터 400개의 잘못된 요청도 받습니다.
따라서 본문 엔터티와 함께 GET 요청을 사용하는 것은 권장하지 않습니다.
user941239RFC 2616, 섹션 4.3 , "메시지 본문"에서:
서버는 모든 요청에 대해 메시지 본문을 읽고 전달해야 합니다(SHOULD). 요청 방법이 엔티티 본문에 대해 정의된 의미를 포함하지 않는 경우 요청을 처리할 때 메시지 본문을 무시해야 합니다(SHOULD).
즉, 서버는 항상 네트워크에서 제공된 요청 본문을 읽어야 합니다(Content-Length 확인 또는 청크 본문 읽기 등). 또한 프록시는 수신한 요청 본문을 전달해야 합니다. 그런 다음 RFC가 주어진 메소드의 본문에 대한 의미를 정의하면 서버는 실제로 응답을 생성할 때 요청 본문을 사용할 수 있습니다. 그러나 RFC가 본문에 대한 의미 체계를 정의 하지 않으면 서버는 이를 무시해야 합니다.
이것은 위의 Fielding의 인용문과 일치합니다.
9.3절 "GET"에서는 GET 메서드의 의미를 설명하고 요청 본문은 언급하지 않습니다. 따라서 서버는 GET 요청에서 수신하는 모든 요청 본문을 무시해야 합니다.
izrikXMLHttpRequest에 따르면 유효하지 않습니다. 표준에서 :
4.5.6 send()
메서드
client . send([ body = null])
요청을 시작합니다. 선택적 인수는 요청 본문을 제공합니다. GET
또는 HEAD
경우 인수는 무시됩니다.
상태가 열려 있지 않거나 send()
플래그가 설정된 InvalidStateError
예외를 throw합니다.
send( body )
메서드는 다음 단계를 실행해야 합니다.
- 상태가 열려
InvalidStateError
예외가 발생합니다. -
send()
플래그가 설정되면 InvalidStateError
예외가 발생합니다. - 요청 방법이
GET
또는 HEAD
경우 본문 을 null로 설정합니다. - 본문 이 null이면 다음 단계로 이동합니다.
그러나 GET 요청에 큰 본문 내용이 필요할 수 있기 때문에 그렇게 해서는 안 된다고 생각합니다.
따라서 브라우저의 XMLHttpRequest에 의존하는 경우 작동하지 않을 가능성이 높습니다.
Rafael Sales캐시 가능한 JSON/XML 본문을 웹 애플리케이션에 보내려는 경우 데이터를 넣을 수 있는 유일한 합리적인 위치는 RFC4648: Base 64 Encoding with URL and Filename Safe Alphabet 로 인코딩 된 쿼리 문자열입니다. 물론 JSON을 urlencode하고 URL param의 값에 넣을 수 있지만 Base64는 더 작은 결과를 제공합니다. URL 크기 제한이 있음을 기억하십시오 . 다른 브라우저에서 URL의 최대 길이는 얼마입니까?를 참조하십시오. .
Base64의 패딩 =
문자가 URL의 매개변수 값에 좋지 않다고 생각할 수도 있지만 그렇지 않은 것 같습니다. 이 토론을 참조하세요. http://mail.python.org/pipermail/python-bugs-list/2007-February/037195.html . 그러나 패딩이 있는 인코딩된 문자열은 빈 값이 있는 매개변수 키로 해석되기 때문에 매개변수 이름 없이 인코딩된 데이터를 넣으면 안 됩니다. ?_b64=<encodeddata>
와 같은 것을 사용할 것입니다.
gertas나는 이것을 조언하지 않을 것입니다. 그것은 표준 관행에 어긋나며 그 대가로 많은 것을 제공하지 않습니다. 당신은 옵션이 아닌 콘텐츠를 위해 본문을 유지하기를 원합니다.
cloudheadGET과 함께 요청 본문을 사용하는 것보다 훨씬 나은 옵션 목록이 있습니다.
각 범주에 대한 범주와 항목이 있다고 가정해 보겠습니다. 둘 다 id로 식별됩니다(이 예에서는 "catid" / "itemid"). 특정 "순서"에서 다른 매개변수 "sortby"에 따라 정렬하려고 합니다. "sortby" 및 "order"에 대한 매개변수를 전달하려고 합니다.
다음을 수행할 수 있습니다.
- 쿼리 문자열 사용(예:
example.com/category/{catid}/item/{itemid}?sortby=itemname&order=asc
- 경로에 mod_rewrite(또는 이와 유사한 것) 사용:
example.com/category/{catid}/item/{itemid}/{sortby}/{order}
- 요청과 함께 전달하는 개별 HTTP 헤더 사용
- 자원을 검색하려면 POST와 같은 다른 방법을 사용하십시오.
모두 단점이 있지만 본체와 함께 GET을 사용하는 것보다 훨씬 낫습니다.
XenoniteREST 프로토콜이 OOP를 지원하지 않고 Get
메소드가 증거라는 사실에 화가 났습니다. 솔루션으로 DTO를 JSON으로 직렬화한 다음 쿼리 문자열을 생성할 수 있습니다. 서버 측에서는 쿼리 문자열을 DTO로 역직렬화할 수 있습니다.
살펴보세요:
메시지 기반 접근 방식은 Get 메서드 제한을 해결하는 데 도움이 될 수 있습니다. 요청 본문과 마찬가지로 모든 DTO를 보낼 수 있습니다.
Nelibur 웹 서비스 프레임워크는 사용자가 사용할 수 있는 기능을 제공합니다.
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
GSerjo비준수 base64 인코딩 헤더는 어떻습니까? "SOMETHINGAPP-PARAMS:sdfSD45fdg45/aS"
길이 제한 흠. POST 처리로 의미를 구분할 수 없습니까? 정렬과 같은 간단한 매개변수를 원할 경우 이것이 왜 문제인지 모르겠습니다. 걱정하고 계시는 것이 틀림없을 것 같습니다.
chazIMHO를 사용 URL
JSON
(예: encodeURIComponent
)을 HTTP
사양을 위반하지 않고 JSON
을 서버로 가져올 수 있습니다.
EthraZa예를 들어 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"}
Nick내 클라이언트 프로그램에서 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을 완전히 무시합니다! 출력 스트림에 복사되지 않습니다.
결과적으로 내 서버 프로그램은 메시지 본문을 수신하지 않고 해당 예외를 throw했습니다.
Spring 프레임워크의 RestTemplate에 대한 예제입니다. 요점은 메시지 본문이 HTTP 사양에서 더 이상 금지되지 않더라도 일부 클라이언트 또는 서버 라이브러리 또는 프레임워크는 여전히 이전 사양을 준수하고 GET 요청에서 메시지 본문을 거부할 수 있다는 것입니다.
Zhou이 페이지에서 자주 언급되는 것처럼 인기있는 도구에서 이것을 사용하더라도 사양에서 금지하지 않음에도 불구하고 너무 이국적이어서 여전히 꽤 나쁜 생각이라고 생각합니다.
많은 중간 인프라가 그러한 요청을 거부할 수 있습니다.
예를 들어으로이 같은 웹 사이트의 앞에 가능한 CDN의 일부를 사용하여 잊어 일 :
최종 사용자 GET
요청에 본문이 포함된 경우 CloudFront는 최종 사용자에게 HTTP 상태 코드 403(금지됨)을 반환합니다.
예, 클라이언트 라이브러리는 이 주석에 보고된 대로 이러한 요청을 내보내는 것을 지원하지 않을 수도 있습니다.
FrédéricRequestfactory 클래스 만들기
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); } } }
Bhaskara Arani출처 : http:www.stackoverflow.com/questions/978061/http-get-with-request-body