# 【Delphi】REST クライアントライブラリを使う --- tags: Delphi programming Pascal embarcadero objectpascal created_at: 2021-10-03 updated_at: 2021-11-01 --- # はじめに Delphi は XE5 (2013 年) において **REST** (Representational State Transfer) クライアントライブラリが実装されたのですが、 ![image.png](./images/c0d58087-bc15-feb0-464f-96bf20b51f01.png) `DataSnap` という文字列を見て**「あぁ、Professional Edition では使えないのか」**と勝手に思い込んでいました。 - [Representational State Transfer (Wikipedia)](https://ja.wikipedia.org/wiki/Representational_State_Transfer) - [REST クライアントライブラリ (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/REST_%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88_%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA) ![image.png](./images/5ba53b91-1a99-47b0-048d-92e9b95638f6.png) **そんなことはなかったぜ!** (今更感) # REST クライアントライブラリの使い方 概要は米澤さんの記事をどうぞ。 - [Delphi Community Edition Meet up ! - REST 編 (Qiita: @CYonezawa)](https://qiita.com/CYonezawa/items/e20c3018c6eb6e1200a0) ## Qiita の記事を取得するコードの生成 Qiita には REST API が用意されていますので、ライブラリの使用例として、私が書いた Qiita の記事を取得してみます。 使用する Qiita の API は `GET /api/v2/users/:user_id/items` です。記事一覧を取得する API になります。`:user_id` はプレースホルダーで、実際には Qiita のユーザーID が入ります。 - [GET /api/v2/users/:user_id/items (Qiita Developer)](https://qiita.com/api/v2/docs#get-apiv2usersuser_iditems) REST クライアントライブラリのドキュメントが若干分かりにくいので、まずは **REST Debugger** を使ってみます。Delphi IDE の `[ツール | REST デバッガ] ` または、`$(BDS)\BIN\` にある **RESTDebugger.exe** を直接起動します。 ![image.png](./images/811f85fc-75ac-8388-3313-8fa5cbfc4ad6.png) ### ・[要求] タブ 次のように指定します。 ![image.png](./images/d732b8ae-3269-676a-5877-4e1dd1a08ce3.png) | 項目 | 値 | |:---|:---| | メソッド | GET | | URL | https://qiita.com | | コンテンツタイプ | application/json | ### ・[パラメータ] タブ 次のように指定します。 ![image.png](./images/e4f1c7ae-a22a-981f-4664-4f5122dcc85e.png) | 項目 | 値 | 備考 | |:---|:---|:---| | リソース | /api/v2/users/ht_deko/items | ht_deko は私の Qiita ユーザーID| | 要求パラメータ | [GET/POST /E] page=1
[GET/POST /E] per_page=10| とりあえず 10 件取得してみる設定| 要求パラメータは [追加] ボタンを押して追加します。 ![image.png](./images/bcef7263-f662-3845-c29d-342d3adc1fcf.png) ![image.png](./images/ed6a38c3-f556-6221-778f-cb5948242042.png) ### ・実行 `[要求の送信]` ボタンを押すと応答が返ります。 ![image.png](./images/93fd5a6f-9502-34b5-0d7e-7f701535a728.png) `[コンポーネントのコピー]` ボタンを押すとこの接続のコンポーネントがクリップボードにコピーされます。 ![image.png](./images/1b218c7b-6265-336e-d80c-20575662c9a2.png) ![image.png](./images/f853f984-cdf4-4da4-3db3-1b85a2f7a8fa.png) ```pascal object RESTClient1: TRESTClient BaseURL = 'https://qiita.com' Params = <> end object RESTRequest1: TRESTRequest AssignedValues = [rvConnectTimeout, rvReadTimeout] Client = RESTClient1 Params = < item Name = 'page' Value = '1' end item Name = 'per_page' Value = '10' end> Resource = 'api/v2/users/ht_deko/items' Response = RESTResponse1 end object RESTResponse1: TRESTResponse end ``` 普通はこれを VCL フォームや FMX フォームに貼り付けて使う事になります。 ![image.png](./images/295c4a61-8ef2-5276-4475-779d567ee74b.png) **See also:** - [[REST デバッガ]の利用 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%EF%BC%BBREST_%E3%83%87%E3%83%90%E3%83%83%E3%82%AC%EF%BC%BD%E3%81%AE%E5%88%A9%E7%94%A8) ## REST クライアントの最小限のコード 要は TRESTClient / TRESTRequest / TRESTResponse があればいいので、 ```pascal uses ..., REST.Types, REST.Client; ... var Request := TRESTRequest.Create(nil); try Request.Client := TRESTClient.Create(Request); Request.Response := TRESTResponse.Create(Request); { 要求 (リクエスト) に関するパラメータ等の処理 } ... Request.Execute; { 応答 (レスポンス) の処理 } ... finally Request.Free; end; ``` こんな感じのコードで REST API を扱える事になります。コンポーネントクラス (TComponent) の派生なので、TRESTRequest / TRESTResponse の破棄を TRESTRequest に任せる事ができます。 **See also:** - [REST.Client.TRESTClient (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/REST.Client.TRESTResponse) - [REST.Client.TRESTRequest (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/REST.Client.TRESTRequest) - [REST.Client.TRESTResponse (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/REST.Client.TRESTResponse) ## 具体的な使用例 以前、Qiita の記事をバックアップするコードを書いた事があります。 - [Delphi で Qiita の記事をバックアップしてみる (Qiita)](./f89774c5670952d52404.md) 記事内のコードを REST クライアントライブラリ対応に書き換えてみました。 ```pascal:GetQiitaItems.dpr program GetQiitaItems; {$APPTYPE CONSOLE} {$WARN GARBAGE OFF} uses System.SysUtils, System.Classes, System.Rtti, System.IOUtils, System.RegularExpressions, System.Net.HttpClientComponent, REST.Client, REST.Types, System.JSON.Readers, System.JSON.Builders; const EXP1 = 'https://qiita-image-store\.s3\..*amazonaws\.com/0/.+/(?.+\.(png|gif|jpg))'; EXP2 = 'https://qiita\.com/%s/(items|private)/(?[0-9a-f]+)'; PER_PAGE = 100; type { TReplaceMethodClass } TReplaceMethodClass = class function NewPath(const AMatch: TMatch): String; function RelativePath(const AMatch: TMatch): String; end; function TReplaceMethodClass.NewPath(const AMatch: TMatch): String; begin result := './images/' + AMatch.Groups.Item['filename'].Value; end; { NewPath } function TReplaceMethodClass.RelativePath(const AMatch: TMatch): String; begin result := './' + AMatch.Groups.Item['filename'].Value + '.md'; end; { RelativePath } begin if ParamCount < 1 then begin Writeln('Usage:'); Writeln(TPath.GetFileNameWithoutExtension(ParamStr(0)), ' '); Writeln; Writeln('UserID not specified.'); Exit; end; // 格納先の作成 var Dir := TPath.GetDirectoryName(ParamStr(0)); var SrcDir := TPath.Combine(Dir, 'source'); var ImgDir := TPath.Combine(SrcDir, 'images'); TDirectory.CreateDirectory(ImgDir); // パラメータ var User_ID := ParamStr(1); // パラメータで与えられた文字列をユーザIDとして扱う var Page := 1; // カレントページ var Request := TRESTRequest.Create(nil); var FileRequest := TNetHTTPRequest.Create(nil); var ResponseData := TMemoryStream.Create; var Body := TStringList.Create; var ReplaceMethod := TReplaceMethodClass.Create; try Request.Method := rmGET; Request.Client := TRESTClient.Create(Request); Request.Client.BaseURL := 'https://qiita.com'; Request.Client.ContentType := 'application/json'; Request.Response := TRESTResponse.Create(Request); Request.Resource := '/api/v2/users/{:user_id}/items'; FileRequest.Client := TNetHTTPClient.Create(FileRequest); while True do begin // GET /api/v2/users/:user_id/items // https://qiita.com/api/v2/docs#get-apiv2usersuser_iditems Request.Params.Clear; Request.Params.AddItem(':user_id' , User_ID , TRESTRequestParameterKind.pkURLSEGMENT); Request.Params.AddItem('page' , Page.ToString ); Request.Params.AddItem('per_page' , PER_PAGE.ToString); Request.Execute; if (Request.Response.StatusCode <> 200) or // HTTP 応答ステータスが 200 OK 以外 または (Request.Response.Content.Length < 100) then // 100 文字以下の応答 はエラーとする Break; // JSON データの読み込み var Iterator := TJSONIterator.Create(Request.Response.JSONReader); try Iterator.Recurse; while Iterator.Next do begin // データの取得 Iterator.Recurse; (* BODY *) Iterator.Next('body'); Body.Text := Iterator.AsString; (* ID *) Iterator.Next('id'); var Id := Iterator.AsString; (* TAGS *) Iterator.Next('tags'); var Tags := ''; Iterator.Recurse; while Iterator.Next do begin Iterator.Recurse; Iterator.Next('name'); Tags := Tags + Iterator.AsString + ' '; Iterator.Return; end; Iterator.Return; (* TITLE *) Iterator.Next('title'); var Title := Iterator.AsString; (* URL *) Iterator.Next('url'); var Url := Iterator.AsString; // TITLE と URL と Tags を標準出力 Writeln('Title: ', Title ); Writeln('URL: ' , Url ); Writeln('Tags: ' , Tags.Trim); // 画像ファイルの取得 for var Match in TRegEx.Matches(Body.Text, EXP1) do begin var ImageURL := Match.Groups.Item[0].Value; // 画像 URL を標準出力 Writeln(' - ', ImageURL); // 画像ファイルの取得と保存 ResponseData.Clear; FileRequest.Get(ImageURL, ResponseData); ResponseData.SaveToFile(TPath.Combine(ImgDir, TPath.GetFileName(ImageURL))); end; // // Qiita 互換のヘッダ (任意) // Body.WriteBOM := False; // BOM なし // Body.LineBreak := #$0A; // LF 改行 // var Header := ''; // Header := Header + '---' + Body.LineBreak; // Header := Header + 'title: ' + Title + Body.LineBreak; // Header := Header + 'tags: ' + Tags.Trim + Body.LineBreak; // Header := Header + 'author: '+ User_ID + Body.LineBreak; // Header := Header + 'slide: ' + 'false' + Body.LineBreak; // Header := Header + '---' + Body.LineBreak; // Body.Text := Header + Body.Text; // 画像ファイルのパス変更 (任意) Body.Text := TRegEx.Replace(Body.Text, EXP1, ReplaceMethod.NewPath); // 自身の投稿を相対パスへ (任意) Body.Text := TRegEx.Replace(Body.Text, Format(EXP2, [User_ID]), ReplaceMethod.RelativePath); // Markdown ファイルを出力 Body.SaveToFile(TPath.Combine(SrcDir, Id + '.md'), TEncoding.UTF8); Writeln; Iterator.Return; end; finally Iterator.Free; end; Inc(Page); // 次のページへ end; finally ReplaceMethod.Free; Body.Free; ResponseData.Free; FileRequest.Free; Request.Free; end; end. { Main } ``` ### アクセストークンを使った記事の取得 アクセストークンを使った記事の取得に使用する Qiita の API は `GET api/v2/authenticated_user/items` です。 - [GET /api/v2/authenticated_user/items (Qiita Developer)](https://qiita.com/api/v2/docs#get-apiv2authenticated_useritems) 先述のコードを次のように書き換えます。 ```pascal:GetQiitaItems.dpr ... try Request.Method := rmGET; Request.Client := TRESTClient.Create(Request); Request.Client.BaseURL := 'https://qiita.com'; Request.Client.ContentType := 'application/json'; Request.AddAuthParameter('Authorization', 'Bearer ' + '[ここにアクセストークン]', TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]); // 追加 Request.Response := TRESTResponse.Create(Request); // Request.Resource := '/api/v2/users/{:user_id}/items'; // 変更 Request.Resource := '/api/v2/authenticated_user/items'; // 変更 FileRequest.Client := TNetHTTPClient.Create(FileRequest); while True do begin // GET /api/v2/authenticated_user/items // https://qiita.com/api/v2/docs#get-apiv2authenticated_useritems Request.Params.Clear; // Request.Params.AddItem(':user_id' , User_ID , TRESTRequestParameterKind.pkURLSEGMENT); // 削除 Request.Params.AddItem('page' , Page.ToString ); Request.Params.AddItem('per_page' , PER_PAGE.ToString); Request.Execute; ... ``` # おわりに 思い込みはよくないですね。 **REST Debugger** は Embarcadero 社のフリーツールとしても公開されており、RAD Studio / Delphi / C++ Builder をインストールしなくても使う事ができます。 - [REST Debugger (Embarcadero)](https://www.embarcadero.com/jp/free-tools/rest-debugger) また、**Windows 10** (1803) 以降では `curl` が標準装備になっており、こちらを使って REST API のテストを行う事もできます。 ```bat curl "https://qiita.com/api/v2/authenticated_user/items?page=1&per_page=100" -H "Content-Type: application/json" -H "Authorization: Bearer [ここにアクセストークン]" ``` **See also:** - [Delphi で Rest クライアント (シンクソフト Delphi 開発ブログ)](http://thinksoft.sblo.jp/article/188184801.html) - [Delphi で Slack の投稿ボットを作る (日本語問題) (WorkToolSmith)](https://worktoolsmith.com/delphi-slack-start/) - [Delphi 10.2 Tokyo Enterprise と RAD Server で REST JSON な Web API を作る (Qiita: @kazinoue)](https://qiita.com/kazinoue/items/48cf6abd0076fbfe646b) - [REST+JSON はコンポーネントを使ってかんたんに取得★テーブル化 (Qiita: @kazaiso)](https://qiita.com/kazaiso/items/44bdcb9982530ab3830f) - [Delphi / C++Builder で REST API をカンタンに実装できる RAD Server で JSON を返す方法を3種類作ってみる (Qiita: @kazinoue)](https://qiita.com/kazinoue/items/56226634f242b1cc82c8) - [FDBatchMove を使って RESTで取得した JSON データをデータベースに書き込む (Qiita: @CYonezawa)](https://qiita.com/CYonezawa/items/acf9e9dbafe1d21b4946) - [Delphi 10 で Twilio api を叩いて SMS メッセージを送る (Qiita: @kenken2go)](https://qiita.com/kenken2go/items/86094dc2d4c2f5a4f4ed) - [cURL (Wikipedia)](https://ja.wikipedia.org/wiki/CURL) - [(RESTful) API でアプリをパワーアップする 20 の方法 (LearnDelphi.org)](https://learndelphi.org/ja/20-ways-to-supercharge-your-apps-with-apis/)