はじめに
Delphi は XE5 (2013 年) において REST (Representational State Transfer) クライアントライブラリが実装されたのですが、
DataSnap
という文字列を見て 「あぁ、Professional Edition では使えないのか」 と勝手に思い込んでいました。
REST クライアントライブラリの使い方
概要は米澤さんの記事をどうぞ。
Qiita の記事を取得するコードの生成
Qiita には REST API が用意されていますので、ライブラリの使用例として、私が書いた Qiita の記事を取得してみます。
使用する Qiita の API は GET /api/v2/users/:user_id/items
です。記事一覧を取得する API になります。:user_id
はプレースホルダーで、実際には Qiita のユーザーID が入ります。
REST クライアントライブラリのドキュメントが若干分かりにくいので、まずは REST Debugger を使ってみます。Delphi IDE の [ツール | REST デバッガ]
または、$(BDS)\BIN\
にある RESTDebugger.exe を直接起動します。
・[要求] タブ
項目 | 値 |
---|---|
メソッド | GET |
URL | https://qiita.com |
コンテンツタイプ | application/json |
・[パラメータ] タブ
項目 | 値 | 備考 |
---|---|---|
リソース | /api/v2/users/ht_deko/items | ht_deko は私の Qiita ユーザーID |
要求パラメータ | [GET/POST /E] page=1 [GET/POST /E] per_page=10 |
とりあえず 10 件取得してみる設定 |
・実行
[要求の送信]
ボタンを押すと応答が返ります。
[コンポーネントのコピー]
ボタンを押すとこの接続のコンポーネントがクリップボードにコピーされます。
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 フォームに貼り付けて使う事になります。
See also:
REST クライアントの最小限のコード
要は TRESTClient / TRESTRequest / TRESTResponse があればいいので、
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)
- REST.Client.TRESTRequest (DocWiki)
- REST.Client.TRESTResponse (DocWiki)
具体的な使用例
以前、Qiita の記事をバックアップするコードを書いた事があります。
記事内のコードを REST クライアントライブラリ対応に書き換えてみました。
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/.+/(?<filename>.+\.(png|gif|jpg))';
EXP2 = 'https://qiita\.com/%s/(items|private)/(?<filename>[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)), ' <UserID>');
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
です。
先述のコードを次のように書き換えます。
...
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 をインストールしなくても使う事ができます。
また、Windows 10 (1803) 以降では curl
が標準装備になっており、こちらを使って REST API のテストを行う事もできます。
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 開発ブログ)
- Delphi で Slack の投稿ボットを作る (日本語問題) (WorkToolSmith)
- Delphi 10.2 Tokyo Enterprise と RAD Server で REST JSON な Web API を作る (Qiita: @kazinoue)
- REST+JSON はコンポーネントを使ってかんたんに取得★テーブル化 (Qiita: @kazaiso)
- Delphi / C++Builder で REST API をカンタンに実装できる RAD Server で JSON を返す方法を3種類作ってみる (Qiita: @kazinoue)
- FDBatchMove を使って RESTで取得した JSON データをデータベースに書き込む (Qiita: @CYonezawa)
- Delphi 10 で Twilio api を叩いて SMS メッセージを送る (Qiita: @kenken2go)
- cURL (Wikipedia)
- (RESTful) API でアプリをパワーアップする 20 の方法 (LearnDelphi.org)
- Delphi で Apilayer REST API を使用する (blogs.embarcadero.com)