使用 TRESTClient 与 TRESTRequest 作为 HTTP Client(转)
使用 TRESTClient 与 TRESTRequest 作为 HTTP Client
转自:http://www.cnblogs.com/dennieschang/p/6966403.html
在 Delphi XE 推出以前的年代,Delphi的发展方向是笔直朝向资料库连结Windows 应用程式这个目标不断前进的,从Delphi 1开始,到Delphi 7,Delphi奠定了VB Killer的外号,主要依靠的就是与资料库的连接功能超越其他开发工具,而且超越的距离不只一个世代。
在 .NET开始发展,Delphi 8, Delphi .NET 不断延迟的时候,与资料库连接功能的方便性,仍然让许多ERP厂商、软硬体厂商持续爱用 Delphi.
直到 Web 开发与 App 开发超越了 Windows 应用程式的需求,VC, VB, Delphi 也开始随着这波潮流,渐渐不再像 1990年代那么广受爱戴了。
在1990到 2010年之间,Delphi的网路连线功能,主要是借由第三方元件来提供的,其中知名度最高,全球使用人数也最多的,应该就是 Indy 这套元件了。
这套元件在 2000 年前,叫做 WinShose,从第八版之后才改名为Indy,全球投入这套元件开发的开发人员,前后超过40人,从最基础的 TCP/IP 功能到各种协定的Client与Server 端元件,笔者从中得益非常多,也开发了当中的DNS Server元件,对通讯协定的深入了解,Indy团队可说是我不可或缺的师长。
随着Delphi 工具走入了多平台开发的领域,Indy的局限性也在这两三年凸显了出来,主要是在各个作业系统上面对于SSL与加密功能的支援无法紧密结合到作业系统内建的功能所致。
由于这个局限性,Delphi XE6开始,REST Client系列元件渐渐开始成为 Delphi 团队的重点开发项目之一,所以我们从 Delphi XE6, Delphi XE7 之后的版本,可以发现到,使用 TRESTClient, TRESTRequest, TRESTResponse 系列组合的应用程式越来越多了,原厂也不断鼓励大家使用这套元件来提供 REST API 的连线功能。
REST API 的基础是 HTTP 协定,大多以 HTTP 的 POST 方法把 JSON 编码形式的参数传递到 Server,而 Server 再以 JSON 形式的参数回传。
有时作法也会稍有变化,例如以 POST 方法把 Web-Form 编码形式的参数传递给 Server,Server 再以 JSON 形式把资料回传。
形式不一而足,但相同的是 HTTP 协定,最常用的也是以 POST 方法把参数传给 Server 端。
今天要跟大家分享的主题,则是如何『使用 TRESTClient 与 TRESTRequest 作为 HTTP Client』。
前面已经提到过,在没有 TRESTClient 整组元件以前,我们通常用的是 Indy 系列的元件来提供网路传输的功能,而现在有了 TRESTClient 整组元件,我们在行动平台上面就可以不需要另外配置函式库,也能够直接使用 https 与 server 连线了,在勒索病毒泛滥的今天,使用 https 会让使用者比较安心。
POST作业说明
在 HTTP 的 POST 作业当中,参数跟 GET 作业一样,Client端需要以 name=value&name2=value2 这种形式进行字串连接,再传送到 Server 端去。Get 跟 POST的差异,在于 Get 方法是把所有参数当做 URL 的一部分,发送 HTTP GET 指令的时候,参数连同 URL 一起传送。
而 POST 作业则是发送完 POST 指令后,把所有的参数与资料随之传送。依照 HTTP 型定的规范,GET 作业的 URL 是无法加密的,而且长度也有限制。因此,当需要传递的资料比较多,或者有机敏性,透过 HTTPS 传送,就是最直接,也最方便,更是目前最通用的资料保护方法。
透过 POST 传递的参数,除了字串以外,还常常包含了档案传递。我们很常看到在网页上面以按钮提供使用者选择要上传的档案,也常看见提供以拖拉的方式把档案上传到远端系统,尤其网页邮件系统最常见到这种作法。
过去以 TIdHTTP 元件的 POST 方法发送参数时,呼叫方式如下:

1 var
2 httpClient : TIdHTTP;
3 url, params, httpResultStr : string;
4 begin
5 url := 'http://mytestURL.com/test.php';
6 params := 'name=我的名字&test=测试';
7
8 httpClient := TIdHTTP.Create(self);
9 try
10 httpResultStr := httpClient.Post(url, params);
11 showMessage(httpResultStr);
12 finally
13 httpClient.Free;
14 end;
15 end;

这样就可以把 params 字串的众参数传到 server 去了。理论上是这样没错,但事情并没有这么简单,在 HTTP 协定当中要传参数给 Server,如果这些字串包含了特殊字元,则必须要先经过编码,而编码,是我们一生都需要与之对抗的繁复程序。
在 HTTP GET 方法当中,所有的参数除了要以 name=value 对每一个参数做描述,以及需要用 & 来连接各组参数,所有的 value 都需要以 url encode 来摆脱 URL 保留字元的纠缠。name 是否需要编码呢?笔者建议,name 就乖乖的用英文吧,可以省下很多问题,以及处理这些可避免的问题所需要的时间!
那么同样的功能,以 TRESTClient 跟 TRESTRequest 要怎么达成呢? 也很容易,作法如下:
1. 在 form 里面放上 TRESTClient 跟 TRESTRequest 元件各一。
2. 把要传递的参数加到 TRESTRequest 实体的 params 属性里面去,这个属性的型别是 TArray,所以可以存放多组参数。
3. 设定 TRESTClient 要传送参数的URL,注意,URL 是设定在TRESTClient 哦!
4. 设定 TRESTRequest 要使用的传输方法,要设定为POST(因为我们正在介绍的是POST方法,请按照您的需求调整)
5. 呼叫 TRESTRequest 实体的 execute 方法,就可以把资料送去 server 了。
写成 Delphi 的程式码,会像以下这样:

self.RESTClient1.BaseURL :=
'http://我的网址/acceptNewCard.php';
self.RESTRequest1.Params.Clear;
self.RESTRequest1.Method := rmPOST; self.RESTRequest1.AddParameter('test', self.EditCardNo.Text);
self.RESTRequest1.AddParameter('name', self.EditName.Text);

是不是很容易呢?的确很容易,里头的问题我们等下再深入探讨,先来看 server 端要怎么接收这些个参数,我们用 PHP 当范例,需要用 C#,JSP的读者朋友们请自行转译喔⋯⋯
PHP Server 端接收 POST 参数的方法
从 1994 年开始,笔者就陆续撰文说明 HTTP POST 方法如何接参数,包含了CGI 用C,perl等语言实作,也包含 ISAPI 以 Delphi 实作,近几年比较流行的是 PHP,JSP,C#,但 PHP 程式码读起来比较简洁易懂,所以我就选择 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 $floorIdx = $_POST["floorIdx"];
10 $name = $_POST["name"];
11 $houseNum = $_POST["houseNum"];
12
13 $params = array();
14 $params['name'] = $name;
15 $params['cardno'] = $cardNum;
16 $params['houseNum'] = $houseNum;
17 $params['floorIdx'] = $floorIdx;
18 $params['picFilename'] = $fileSaveName;
19 $params['created'] = 0;
20
21 $result["resultCode"] = "0";
22 $result["result"] = "成功";
23 $result["sqlcmd"] = $name;
24
25 $jsonStr = json_encode($result);
26 echo $jsonStr;
27 ?>

这是最基本的传递字串,下次再给大家示范怎么传档案,透过 TRESTRequest来做也是很简单的,TRESTRequest 跟 TRESTClient 的确是取代 TIdHTTP 的好工具。
使用 TRESTClient 与 TRESTRequest 作为 HTTP Client(转)的更多相关文章
- 使用 TRESTClient 与 TRESTRequest 作为 HTTP Client
在 Delphi XE 推出以前的年代,Delphi的发展方向是笔直朝向资料库连结Windows 应用程式这个目标不断前进的,从Delphi 1开始,到Delphi 7,Delphi奠定了VB Kil ...
- 使用 TRESTClient 與 TRESTRequest 作為 HTTP Client 之二 (POST 檔案)
使用 HTML 进行文件上传,已经是很平常的应用了,在手机App里面,也常常会用到这个作业,例如拍照上传,或是从相簿选取照片上传,都是很常见的. 在 HTML 的 Form 里面,要让使用者选择文件上 ...
- Delphi APP 開發入門(十)REST Client 開發
Delphi APP 開發入門(十)REST Client 開發 分享: Share on facebookShare on twitterShare on google_plusone_share ...
- 我的第一个REST客户端程序!
Delphi:XE8 看了好几天的资料了,也没有弄出来一个REST程序,尝试了XE8中带的例子,也都没有搞懂.我在网上不断搜索,看是否能够找到适合自己的文章,希望能够做出来一个REST的小例子,万幸, ...
- delphi 各版本的特性
delphi 各新版本特性收集 Delphi XE6新增了一些特性并增强了原有的功能,主要有以下几个方面: IDE(整合开发环境) Internet XML(扩展标记语言) Compiler( ...
- delphi c++builder JSON 生成与解析 例子
json,System.JSON,REST.JSON JSON有两种数据结构,对象和数组. 对象在js中表示为“{}”括起来的内容,数据结构为 {key:value,key:value,...} 数组 ...
- Delphi调用REST
Delphi调用REST很简单,首先在界面上放上: RESTClient1: TRESTClient; RESTRequest1: TRESTRequest; RESTResponse1: TREST ...
- Delphi XE5中的新增内容
Delphi XE5中的新增内容 Delphi XE5是所有Delphi开发人员的必须备升级,并且是来自Embarcadero的获奖的.多设备应用开发解决方案的最新版本.使用Delphi XE5的新特 ...
- vmware里面的名词 vSphere、vCenter Server、ESXI、vSphere Client
vmware里面的名词 vSphere.vCenter Server.ESXI.vSphere Client vSphere.vCenter Server.ESXI.vSphere Client VS ...
随机推荐
- 异常java.lang.IllegalArgumentException:attempt to create delete event with null entity
异常java.lang.IllegalArgumentException:attempt to create delete event with null entity解决:路径问题,前台jsp和ja ...
- list set接口之间的区别
list接口它的实现类,比如arraylist里面的值有序,并且可以重复.(有序指的是插入进去的顺序) set无序,且不可重复.(这里的无序就是指不是插入进去的顺序,但其实也不是真的无序,它会按照自己 ...
- CentOS系统找不到setup命令工具的解决方法
如果你的CentOS系统中没有setup命令,很有可能是因为你安装CentOS系统时采用了最小化安装(minimal).这时,你执行setup命令时,就会报错: 错误信息: 1[root@localh ...
- Codeforces 1076D Edge Deletion 【最短路+贪心】
<题目链接> 题目大意: n个点,m条边的无向图,现在需要删除一些边,使得剩下的边数不能超过K条.1点为起点,如果1到 i 点的最短距离与删除边之前的最短距离相同,则称 i 为 " ...
- DRF分页组件
为什么要使用分页 其实这个不说大家都知道,大家写项目的时候也是一定会用的, 我们数据库有几千万条数据,这些数据需要展示,我们不可能直接从数据库把数据全部读取出来, 这样会给内存造成特别大的压力,有可能 ...
- 版本控制系统-SVN(1)
1. SVN介绍 1.1. 简介 SVN(subversion),版本管理工具,与CVS一样,SVN是一个可跨平台的开源版本控制系统,Subversion管理随时间变化的数据.这些数据都被放置在 ...
- 第13届景驰-埃森哲杯广东工业大学ACM程序设计大赛.B.跳一跳,很简单的(Hash 倍增)
题目链接 首先变换的周期是\(26\),而所有字符是同时变的,所以一共就只有\(26\)种树,我们对\(26\)棵树分别处理. 求某节点到根路径上的字符串,可以从根往下哈希,\(O(n)\)预处理出. ...
- System.ServiceModel.AddressAccessDeniedException
发生了 System.ServiceModel.AddressAccessDeniedException HResult=0x80131501 Message=HTTP 无法注册 URL ht ...
- C++程序设计方法5:接口设计
一切都在变化,因此程序要适应变化: 变化分层 封装 接口与隐藏: 接口:类暴露出来的部分,是类所提供的功能: 隐藏实现的细节: UML类图 接口在哪儿? 设计思路:从上到下设计 实现类的接口 细化
- servlet 表单加上multipart/form-data后request.getParameter获取NULL(已解决)
先上结论(可能不对,因为这是根据实践猜测而来,欢迎指正) 表单改为multipart/form-data传值后,数据就不能通过普通的request.getParameter获取. 文件和文件名通过Fi ...