본문 바로가기

Delphi/C++Builder

[RX.11] REST 클라이언트 Post 전송 시 Get으로 파라미터 전송 이슈 해결방안

데브기어 포럼에 등록된 이슈 공유합니다.

https://welcome.devgear.co.kr/topic/227-delphi-11-rest-client-동작-오류-문의드립니다/

 

<질문 요약>

11.0에서 REST 클라이언트로 post로 요청시, 서버단에서 post로 못받고 get으로 인지를 하는 문제입니다. 

개요 : 10.4.2 에서 잘 되던 앱이 11 버전 RestClient POST 방식에서 문제가 되어 여러가지 테스트 해본 결과 POST로 파라미터를 요청을 하면 서버쪽에서 POST로 파라미터 값을 못 받는 현상입니다.

 

<답변>

질문하신 내용을 요약하면 "델파이 11에서 POST로 요청 시 GET으로 메소드를 호출되는 이슈가 있다.로 이해됩니다.

질문의 답변에 앞서,

요구사항을 확인하면 "이미지 데이터를 서버로 전달"하는 것으로 보여 다음 2가지 답변을 드립니다.

1) POST 요청 시 GET으로 호출하는 원인 확인 및 조치사항(질문에 대한 답변)

2) REST 클라이언트로 이미지를 업로드하는 방안(요구사항 솔루션)

 

1) POST 요청 시 GET으로 호출하는 원인 확인 및 조치사항

우선 현상과 원인 파악을 위해 PHP 페이지를 다음과 같이 작성 후 다시 시도해보시기 바랍니다.(정확히 어떤 메소드로 호출하는지 확인할 수 있습니다.)

<?php
$GetId = $_GET['id'];
$PostId = $_POST['id'];

echo $_SERVER["REQUEST_METHOD"]."<br />\n"; // HTTP 메소드(GET / POST)

echo "Get : ".$GetId."<br />\n";
echo "Post : ".$PostId."<br />\n";

echo "body: ".$HTTP_RAW_POST_DATA."<br />\n";
echo "body: ".file_get_contents("php://input")."<br />\n";
?>

제가 테스트한 결과는 다음과 같습니다.

두가지 버전 모두 POST로 요청함을 확인했습니다.
(응답 Body의 첫번째 항목은 HTTP 메소드 종류이며, 둘다 POST로 출력되었습니다.)

하지만 전송한 요청 파라미터(GETorPOST 파라미터)는, 10.4.2에서는 POST($_POST) 데이터로 11.0에서는 GET($_GET)으로 전달됩니다.
결과적으로, 두 버전간 전달하는 방식에 차이가 있는 것을 확인했습니다.

 

두 버전이 다른 결과가 나온 내용을 소스코드에서 분석한 내용을 설명합니다.(간단히 설명하니 참고만 하고, 아래의 결론의 내용을 적용하시기 바랍니다.)
11.0에서 REST 클라이언트의 주요 변경사항 중 ContentType을 문자열로 처리하도록 개선되었습니다.(상당히 많은 양의 코드가 변경되었습니다.)

특히, 파라메터 전송 방식을 판단하는 IsQueryParam은 아래와 같이 변경되었습니다.(좌: 10.4.2, 우: 11.0)

빨간 박스를 보면 파라메터의 컨텐트타입(APram.ContentType)이 ctNone(공백)인 경우 쿼리 파라메터로 인식되어 요청시 Get 형식의 파라메터가 전송됩니다.

위 로직을 피하기 위해서는 다음과 같은 조치를 취해야 합니다.

  1. TRESTRequest.Params 속성 선택
  2. POST 파라메터로 전달하려는 파라메터의 ContentTypeStr을 "multipart/form-data" 지정

위와 같이 지정 후 REQUEST 실행하면 다음과 같이 10.4.2와 동일한 방식으로 POST 파라메터로 전송되는 것을 확인할 수 있습니다.

해당 조치는 REST Debugger에서는 진행할 수 없으니, 소스코드 상에서 조치해야 합니다.

결론: 

델파이 11.0에서 POST 요청 시 파라메터를 POST 파라미터로 전달하려면, 파라메터의 ContentTypeStr을 "multipart/form-data"로 지정해야 합니다.

 

2) REST 클라이언트로 이미지를 업로드하는 방안

이미지를 Base64로 인코딩해 문자열로 전송하셨습니다.  멀티파트 폼데이터(multi-part/formdata)로 전송하는 방식도 검토해 보시기 바랍니다.

멀티파트 폼데이터로 전송 시 Stream을 그대로 파라미터로 설정할 수 있습니다.

다음은 클라이언트에서 멀티파트-폼데이터로 전송하는 샘플 코드입니다.
(파라미터에 파일(pkFILE)이 포함된 경우 multi-part/formdata로 전송됩니다.)

var
  Stream: TMemoryStream;
  Item: TRESTRequestParameter;
begin
  if not OpenDialog1.Execute then
    Exit;

  Stream :=  TMemoryStream.Create;
  Stream.LoadFromFile(OpenDialog1.FileName);

// 파라메터는 디자인타임에 생성되었습니다.
//  Item := RESTRequest1.Params.AddItem;
//  Item.Name := 'img';
//  Item.Kind := pkFILE;
  RESTRequest1.Params.ParameterByName('img').SetStream(Stream);

  RESTRequest1.Method := rmPOST;
  RESTRequest1.Execute;
end;

멀티파트 폼데이터로 전송 시 웹서버에서 파일 전송과 동일한 방식으로 받아 처리할 수 있습니다.

RAD 서버 11.0에서는 다음 코드를 이용해 멀티파트 폼데이터를 처리할 수 있습니다.

procedure TImgResource1.Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TStream;
begin
  Stream := ARequest.Body.GetPart('img', '').GetStream;

  if Stream.Size <= 0 then
    AResponse.RaiseBadRequest('no stream');

  TMemoryStream(Stream).SaveToFile('D:\Temp\test.jpg');
end;

위 샘플코드 프로젝트입니다.

RSX_FileUpload.zip
0.01MB

 

결론: 
이미지 등의 파일 업로드 구현 시 멀티파티 폼데이터로 전송할 수도 있습니다.
(델파이의 일반적인 데이터구조인 Stream을 바로 전송할 수 있어 별도 인코딩 없이 간단히 구현할 수 있습니다.)

 

두가지 방식을 안내드렸습니다.

제가 제안하는 방식은, 

업로드 방식을 변경할 수 있다면 2번항목을 참고해 업로드 방식을 변경하시길 권장드리며, 

서버의 인터페이스를 변경하지 못하는 경우 1번항목을 참고해 적용하시기 바랍니다.