2010.01.25 추가
오늘자로 아래와 같이 코드에 추가된 내용이 있습니다. (아래 코드에는 반영함)

먼저, IdHTTP1.Request.ContentType에 'application/x-www-form-urlencoded' 값을 넣어주는 라인이 추가되었구요. (이 라인이 없으면 제대로 HTML 폼에 POST로 전송된 것으로 인식되지 못하는데, IdHTTP에서 왜 추가해주지 않았는지 이해가 안되네요)

다음으로, POST를 한 후 결과 값을 받아와서 문자열의 길이를 스트림의 크기만큼으로 설정해준 부분이 추가되었습니다. (이게 없어서 문자열 끝 뒤에 쓰레기 값이 넘어오는 경우가 있더군요)

-------------------------------------------------------------------

저번 글에 이어, 2009/2010 버전에 번들된 Indy의 한글 깨짐 문제에 대해 계속 알아보겠습니다. 이번에 알아볼 것은, idHTTP에서 Post를 했을 때 웹 서버로 전달된 컨텐트 내용의 한글 부분이 깨지는 문제입니다.

다음은 2009 버전에 번들된 Indy의 idHTTP 소스에서 오버로드된 여러 Post 메소드들입니다.
    function Post(AURL: string; ASource: TStrings): string; overload;
    function Post(AURL: string; ASource: TStream): string; overload;
    function Post(AURL: string; ASource: TIdMultiPartFormDataStream): string; overload;
    procedure Post(AURL: string; ASource: TIdMultiPartFormDataStream; AResponseContent: TStream); overload;
    procedure Post(AURL: string; ASource: TStrings; AResponseContent: TStream); overload;
    procedure Post(AURL: string; ASource, AResponseContent: TStream); overload;

여러가지로 테스트를 해봤는데, 이 중에서 idHTTP에서 Post로 전송한 데이터의 한글이 깨지는 문제는 Post 데이터인 ASource 파라미터로 TStrings 타입을 넘기는 경우에만 발생하는 것으로 보입니다. 다시 말해 ASource로 TStream 타입을 넘기는 경우에는 발생하지 않습니다.
역시 charset, 즉 인코딩의 처리 문제이구요.

실제 idHTTP의 소스를 보면, 2009에 번들된 Indy 버전에서는 인코딩 처리가 아주 잘못되어 있고, 2010 번들 버전은 많이 개선되었으나 아직 문제가 있습니다.

어쨌든, TStringList 등 TStrings 클래스로 넘기던 코드에서 한글 문제가 발생했다면, 그것을 TStringStream으로 다시 쓴 다음 Post를 하면 됩니다.

그런데, Post로 데이터를 넘긴 후에도 데이터를 다시 받아야 하는 경우가 종종 있습니다. 따라서 앞서의 Get 방법을 다룬 포스트의 코드와 같은 방식으로 처리해야 합니다. 코드는 아래와 같습니다.

Delphi 코드
var
  rbstr: RawByteString;
  HTML: String;
  MemoryStream: TMemoryStream;
  StringStream: TStringStream;
  slPost: TStringList;
begin
  slPost := TStringList.Create;
  slPost.Add('student_info=박지훈');
  slPost.Add('reserve_purpose=임프');
  StringStream := TStringStream.Create(slPost.Text);
  slPost.Free;

  MemoryStream := TMemoryStream.Create;
  IdHTTP1.Request.ContentType := 'application/x-www-form-urlencoded';
  IdHTTP1.Post('http://주소', StringStream, MemoryStream);
  StringStream.Free;
  SetLength(rbstr, MemoryStream.Size);
  StrLCopy(PAnsiChar(rbstr), PAnsiChar(MemoryStream.Memory), MemoryStream.Size);
  MemoryStream.Free;

  if Pos('utf-8', IdHTTP1.Response.ContentType)=0 then
    SetCodePage(rbstr, 949, false)
  else
    SetCodePage(rbstr, 65001, false);
  Memo1.Lines.Text := rbstr;
end;

C++Builder 코드
  TStringList *slPost = new TStringList;
  slPost->Add("student_info=박지훈");
  slPost->Add("reserve_purpose=임프");
  TStringStream *StringStream = new TStringStream(slPost->Text);
  delete slPost;

  TMemoryStream *MemoryStream = new TMemoryStream;
  IdHTTP1->Request->ContentType = "application/x-www-form-urlencoded";
  IdHTTP1->Post("http://주소", StringStream, MemoryStream);
  delete StringStream;

  RawByteString rbstr;
  rbstr.SetLength(MemoryStream.Size);
  strncpy(rbstr.c_str(), (char *)(MemoryStream->Memory), MemoryStream->Size);
  delete MemoryStream;

  if(Pos("utf-8", IdHTTP1->Response->ContentType)==0)
    SetCodePage(rbstr, 949, false);
  else
    SetCodePage(rbstr, 65001, false);
  String HTML = rbstr;

저번에도 말씀드렸다시피 2009/2010 버전에 번들된 Indy의 한글 깨짐 문제는 여기저기 꽤 복잡하고 원인도 몇군데 되기 때문에, 이걸로 끝난 것이 아닙니다. 다음에는 idTCP 컴포넌트들과 idFTP 컴포넌트 쪽으로도 살펴보고, 그에 대한 해결책도 알아볼 거구요. 근본적으로는, 이 모든 회피 방법들을 종합하여 번들된 Indy 자체를 완전히 패치하는 방법도 알려드릴 것입니다.
2009/09/10 07:48 2009/09/10 07:48

trackback :: http://blog.devgear.co.kr/imp/trackback/80

그저께부터 Delphi/C++Builder 2009/2010 버전에서 Indy가 한글과 관련하여 오작동하는 문제에 대해 파헤치고 있습니다. 2009/2010 Indy의 한글 버그는 2009와 2010이 양상이 좀 다르고, 또 idHTTP와 idFTP, IdTCPClient에서 각각 원인도 좀 다르고 수정해야 할 포인트도 좀 다른 것 같습니다. 따라서 완벽한 해결책을 마련해서 공개해드리려면 1~2주 정도가 더 걸릴 것 같고요. 하지만 당장 답답한 분들이 많을 것이므로 작업 중간중간에 상태를 알려드리려고 합니다.

당장은, 일단 idHTTP와 관련하여 라이브러리 소스들을 수정하지 않고 피해가는 회피책, 즉 workaround 방법을 먼저 알려드리겠습니다. 간단히 말하면, idHTTP에서 Get을 호출해서 한글이 포함된 html 페이지를 한글 깨짐 없이 받아오려면, 다음과 같은 코드를 사용하면 됩니다.

Delphi 코드

var
  rbstr: RawByteString;
  HTML: String;
  MemoryStream: TMemoryStream;
begin
  MemoryStream := TMemoryStream.Create;
  IdHTTP1.Get('http://blog.devgear.co.kr/imp', MemoryStream);
  rbstr := PAnsiChar(MemoryStream.Memory);
  MemoryStream.Free;
  if (Pos('utf-8', IdHTTP1.Response.ContentType)=0) and (AnsiPos('charset=utf-8', rbstr)=0) then
SetCodePage(rbstr, 949, false) else SetCodePage(rbstr, 65001, false); HTML := rbstr; end;

C++Builder 코드
  TMemoryStream *MemoryStream = new TMemoryStream();
  IdHTTP1->Get("http://blog.devgear.co.kr/imp", MemoryStream);
  RawByteString rbstr = (char *)(MemoryStream->Memory);
  delete MemoryStream;
  if(Pos("utf-8", IdHTTP1->Response->ContentType)==0 && (Pos("charset=utf-8", rbstr)==0))
    SetCodePage(rbstr, 949, false);
  else
    SetCodePage(rbstr, 65001, false);
  String HTML = rbstr;


여기서 한가지 짚고 넘어갈 것이 있습니다. idHTTP를 사용했을 때 한글이 항상 깨지는 것은 아니라는 것입니다. 2009 버전에 번들된 Indy 버전에서는 받아오려는 HTML 페이지의 charset이 'utf-8'일 경우 깨지지 않고 'euc-kr'이나 'ks_c_5601-1987'인 경우에만 깨집니다. 2010 버전에 번들된 Indy의 경우, HTTP 헤더에서 charset을 지정된 경우에는 'utf-8'이 아닌 'euc-kr'이나 'ks_c_5601-1987'인 경우라도 깨지지 않으며 charset 지정이 없는 경우에만 깨집니다. (물론 위 코드를 사용하면 어떤 경우에든 안깨집니다)

한글이 깨어졌던 이유를 간단히 말씀드리자면, Indy 코드가 html 텍스트를 받아오는 과정에서 charset(인코딩)을 제대로 인식하지 못해서 엉뚱한 인코딩으로 풀어버린 것입니다. 그래서 메모리스트림으로 받아서 Indy 라이브러리가 인코딩을 직접 해석하지 않도록 강제한 후, 다시 스트림의 메모리 포인터를 코드페이지를 가지지 않는 무변환 스트링 타입인 RawByteString 타입으로 받아서 강제로 코드페이지를 한글인 949(euc-kr)로 지정합니다.

Delphi/C++Builder 2009 버전에 번들된 Indy에서는 이 charset에 대한 고려가 제대로 되어 있지 않습니다. 반면 2010 버전에서는 charset에 대한 처리가 아주 훌륭하게 되어 있는데요, 그럼에도 한글이 깨어지는 것은, HTTP 헤더에서 charset이 지정되지 않은 경우 기본 디폴트 charset 값의 처리 때문입니다. 그래서 2010 버전에서는 charset을 제대로 지정하면 한글 페이지라도 깨지지 않는 것이구요.

또 한가지 중요한 것. idHTTP로 받아오는 html 페이지가 'utf-8'로 되어 있는 경우, 코드페이지 949로 강제해버리면 당연히 깨져버립니다. 그래서 charset이 'utf-8'로 지정된 경우에는 'utf-8'의 코드페이지인 65001을 지정합니다.

참고로, 이 블로그의 charset은 'utf-8' 이고, 볼랜드포럼의 경우 charset이 'ks_c_5601-1987' 이지만 HTTP 헤더에서 넘겨주지 않구요. 델마당의 경우 charset이 'euc-kr' 이면서 역시 HTTP 헤더에서 charset을 넘겨주지 않습니다. (따라서 위와 같은 회피책을 사용하지 않고 idHTTP를 써서 볼랜드포럼과 델마당의 페이지를 Get 하면 깨지지만 이 블로그의 페이지들은 깨지지 않을 것입니다)

오늘 저녁쯤에는, idHTTP의 Post를 사용했을 때의 문제를 해결하는 코드를 보여드리도록 하겠습니다.

2009/09/09 11:49 2009/09/09 11:49

trackback :: http://blog.devgear.co.kr/imp/trackback/79