式码读起来比较简洁易懂,所以我就选择 PHP 来做范例了。
在 PHP 里面,透过GET 跟 POST 方法传递的参数,会被分别存放在 $_GET 跟 $_POST 这两个阵列变数里面,如果要偷懒,不想区分 GET 或 POST 方法,也可以从 $_REQUEST 这个变数试着读取,当中有些安全性考量,最好勤劳一点,把它们区分开来。
以刚刚的例子来看,我们传了一个名为 name,以及一个名为 test 的字串,用的是 POST 方法,所以我们得用以下两个变数来存取这两个字串:
? $_POST['name'] 这个变数可以取得 Client 端发送出来的 name
? $_POST['test'] 这个变数可以取得 Client 端发送出来的 test
所以在 server 端,我们可以这样写,来抓到这两个资料:
$name = $_POST["name"];
$test = $_POST["test"];
这样写会不会出问题呢? 答案是不会!如果使用者不输入中文的话!
中文资料的编码处理
Delphi的开发人员绝大多数都是英美语系的人,我推测因此对于亚洲语系的文字显示与传输比较没有办法完整的测试,但对于我们以中文为母语的人来说,从电脑诞生的那个年代,中文的显示在每个操作系统、每种通讯协定的设计都比英文来的困难。
以上面的例子来看,如果我们直接拿这个例子来测试,笔者写的范例程式,执行传输资料时,Server 所抓到的文字并不是正确的中文字,如下图所示:
可以看得出来,传到 server 的时候,server 是读不到资讯的。这是怎么回事呢?笔者属于不认输的好奇宝宝,使出了浑身解数,终于解决了这个问题。
写过 Web 程式的读者们一定可以立刻推测出来,这绝对是文字编码出问题了,然而,是什么地方出问题?可能出问题的点我列出来跟大家分享:
? HTTP Client 的 charset 设定错了
? HTTP Request 里面的文字编码出问题
检查的方向也是从这两个关键点出发,第一点的检测很容易,从Object Inspector检查一下 RESTRequest1的设定:
AcceptCharset 确定是 UTF-8,没错,所以设定不是问题。
接着,就要从 Client 端发出去的资料下手了。有读者或许会问『你怎么不怀疑Server端程式写错了?』这个问题很好,之所以排除了这个问题,是因为同一个 Server 端的 PHP 程式,我用了 Postman 做过比对测试,回传的结果是正确的,因此判定是 Client 端程式的问题。
接着笔者从 TRESTRequest.AddParameter 的各种多载形式来尝试,AddParameter 这个方法有以下几种多载的形式:
procedure AddParameter(const AName, AValue: string); overload;
procedure AddParameter(const AName: string; AJsonObject: TJSONObject; AFreeJson: boolean = true); overload;
procedure AddParameter(const AName, AValue: string; const AKind: TRESTRequestParameterKind); overload;
三种形式我都测试过,从 AddParameter 的执行中 trace 进去看各个可能性,由于 TRESTRequest 的参数中,Get 跟 Post 的加入方法是混用的,在程式码里面编码又会有点不同。
在 REST.Client.pas 里面,我曾经怀疑过编码错误,所以也在执行阶段对各个变数都进行观察,最后,找到了原因与解法,至于过程,就不多说了,花了我两天咧。
原因:编码错误
用HTTP传递中文的时候,务必用UTF-8编码,但一定要记得,中文字在作业系统中,都是UCS32编码,这个现象在Windows里面如此,在Android里面如此,在iOS跟Mac我不确定,但处理方法是一样的。
直接以 AddParameter('name', '中文测试'); 把参数加进 TRESTRequest 的时候,REST.Client.pas 的程式码是把 '中文测试' 这个字串直接抓 Ord 的资料来做编码的,然而,这个作法,是错的!!!!!!!!
在 HTTP 传递 UTF-8 资料的时候,我们要传递的是 UTF-8 文字的二进位资料,但直接把 '中文测试' 这个字串直接拿来转成二进位? 当时编码并不是 UTF-8 啊,当然怎么编码送到 server 都是错的!!!!
解法:AddParameter之前先做 UTF-8 转换
这个解法,笔者第一天就已经想到,只是很想像以前改 Indy 程式一样,直接改好 REST.Client.pas 之后,回馈给原厂使用,所以花了不少时间找方法,最后发现这个方法不用动到 REST.Client.pas,又能正确处理,就直接这么跟大家分享了,写成 Delphi 程式码如下:
var
nameStr : String;
begin
...
nameStr := TIdURI.ParamsEncode(nameStr, IndyTextEncoding_UTF8);
self.RESTRequest1.AddParameter('name', nameStr,
TRESTRequestParameterKind.pkGETorPOST,
[TRESTRequestParameterOption.poDoNotEncode]);
...
end;
在把字串透过 AddParameter 加入参数阵列之前,我先把字串做个 UTF-8 转换,在这里用的是 TIdURI 的类别方法 ParamsEncode,这个方法只有两个参数,第一个参数是字串内容,第二个参数则是要文字编码的种类,在这里我选择了 UTF8,写法就是上面范例程式的第一行。
接着,在呼叫 AddParameter 的时候,我使用了多载形式当中的第三种,要求 AddParameter 处理资料的时候不要再动我的编码,因为我已经处理好了。
这么修改过之后,在各个作业系统当中,执行结果都是正确的,上面两个图以 Windows 作业系统为例,现在我们拿 Android 截图来做为例子:
读者可以看到右边的截图里面,server 回传的资料已经是正确的中文字了。
最后,我把 PHP 程式码也附上来给大家参考:
1 <?php
2 date_default_timezone_set("Asia/Taipei");
3 header('Content-Type: charset=utf-8');
4
5 include("public/DBClassPDO.php");
6 $objDBPDO = new DBClassPDO();
7
8 $cardNum = $_POST["cardNum"];
9 $floor