Delphi XE5中TRestClient对UTF-8编码字符串的支持

4
我希望使用 Delphi XE5 中的新 TRest 组件发送推文。我正在寻找一种方法来UTF8编码我的推文,其中包含IS0-8859-1字符。下面的代码可以工作,但涉及到代码页转换等。是否有更好的方式?有人知道吗?
procedure TTwitterApi.Send(Tweet: string);
begin
  Reset;

  // Encode as UTF8 within (UTF-16 Delphi) string
  Tweet := EncodeAsUTF8(Tweet);

  FRestRequest.Resource := '1.1/statuses/update.json';
  FRestRequest.Method := rmPOST;
  FRestRequest.Params.AddItem('status', Tweet, pkGETorPOST);
  FRestRequest.Execute;
end;


function TTwitterApi.EncodeAsUTF8(UnicodeStr: string): string;
var
  UTF8Str: AnsiString;
  TempStr: RawByteString;
begin
  TempStr := UTF8Encode(UnicodeStr);
  SetLength(UTF8Str, Length(TempStr));
  Move(TempStr[1], UTF8Str[1], Length(UTF8Str));
  Result := UTF8Str;
end;
3个回答

6
Twitter的1.1/statuses/update.json URL期望数据以application/x-www-form-urlencoded格式进行编码,因此您需要将TRESTClient.ContentType属性设置为ctAPPLICATION_X_WWW_FORM_URLENCODED(默认情况下设置为ctNone)。
至于UTF-8,TRESTClient在内部使用Indy,并支持使用用户指定的字符集对出站数据进行编码,但似乎Embarcadero没有将这个功能添加到其TRESTClient接口中(尽管它确实处理响应中的字符集)。我不知道Embarcadero为什么会省略这样一个重要的特性。仅仅将字符串数据编码为UTF-8是不够的(顺便说一下,您并没有正确地执行此操作),您还需要告诉Twitter数据已经被UTF-8编码(通过Content-Type REST头的charset属性),而TRESTClient似乎不允许您这样做。我不知道TRESTClient是否发送带有默认字符集的REST请求,但是从其源代码来看,我认为它不会,但我没有尝试过。
最起码,您需要修复您的EncodeAsUTF8()函数。它不能产生像您想象的那样保存UTF-8编码八位组的UnicodeString。它产生一个UTF-8编码的AnsiString,然后使用RTL的默认Ansi代码页将其转换为UTF-16编码的UniodeString,因此您正在调用一个丢失UTF-8数据的数据转换。请改用以下方法:
function TTwitterApi.EncodeAsUTF8(UnicodeStr: string): string;
var
  UTF8Str: UTF8String;
  I: Integer;
begin
  UTF8Str := UTF8String(UnicodeStr);
  SetLength(Result, Length(UTF8Str));
  for I := 1 to Length(UTF8Str) do
    Result[I] := Char(Ord(UTF8Str[I]));
end;

那应该允许 TRESTClient 至少在其POST数据中对正确的UTF-8数据进行URL编码。但你仍然需要处理请求头中缺少 charset 属性的问题(除非Twitter在未指定 charset 时默认使用UTF-8)。
现在,说了这么多,如果你发现解决 TRESTClient 的问题行不通,那我建议改用Indy的 TIdHTTP 组件代替(它比 TRESTClient 更准确地实现了 application/x-www-form-urlencoded),例如:
procedure TTwitterApi.Send(Tweet: string);
var
  Params: TStringList;
begin
  Reset;

  Params := TStringList.Create;
  try
    FParams.Add('status=' + Tweet);
    FIdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
    FIdHTTP.Request.Charset := 'utf-8';
    FIdHTTP.Post('https://api.twitter.com/1.1/statuses/update.json', Params, IndyTextEncoding_UTF8);
  finally
    Params.Free;
  end;
end;

我已经查看了REST.Client模块中的ContentType。使用POST时,ContentType是从请求参数中获取的。(请参见第2569行: LContentType := ContentType;) 如果contenttype为ctNone,它实际上会变成APPLICATION_X_WWW_FORM_URLENCODED。所以ContentType似乎是正确的。 - user3012503
正如您所说,无法将UTF-8设置为字符集,但由于Twitter仅接受UTF-8,因此这是有效的。 - user3012503
感谢您建议直接使用TIdHttp组件。我唯一能看到的缺点是需要解决OAuth调用问题。 - user3012503
Indy目前尚未原生支持OAuth(但已列入待办事项),但我看到一些第三方OAuth实现正在使用Indy,包括一些与Twitter相关的实现。 - Remy Lebeau
函数 TTwitterApi.EncodeAsUTF8 的代码是错误的。它不能编译,因为 TempStr 变量不存在。我检查了一下,应该是 UTF8Str,对吗? - Rodrigo Farias Rezino

0
我已经通过另一个API提供商(不是Twitter)以以下方式解决了这个问题:
function EncodeAsUTF8(UnicodeStr: string): AnsiString; // <-- Note the Ansi
var
  UTF8Str: UTF8String;
  I: Integer;
begin
  UTF8Str := UTF8String(UnicodeStr);
  SetLength(Result, Length(UTF8Str));
  for I := 1 to Length(UTF8Str) do
    Result[I] := AnsiChar(Ord(UTF8Str[I])); // <-- Note the Ansi
end;

...

fRESTClient1 := TRESTClient.Create(nil);
fRESTClient1.Accept := 'application/json';
fRESTClient1.AcceptCharset := 'UTF-8';
fRESTClient1.AcceptEncoding := 'identity';
fRESTClient1.ContentType := 'application/x-www-form-urlencoded';

...

rrOrder := TRESTRequest.Create(nil);
rrOrder.Accept := 'application/json';
rrOrder.AcceptCharset := 'UTF-8';
rrOrder.Client := fRESTClient1; {}
rrOrder.Method := rmPOST;
rrOrder.Resource := 'xxxxxx';
rrOrder.Params.AddItem('', EncodeAsUTF8(aJson), pkREQUESTBODY, [poDoNotEncode]);

rrOrder.Execute;

0

TRestRequest在Android上无法正常工作,会引起许多问题,特别是与UTF8有关的问题,我无法解决。而IdHttp Indy则可以很好地工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接