Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

posted at

updated at

【Delphi】REST クライアントライブラリを使う

はじめに

Delphi は XE5 (2013 年) において REST (Representational State Transfer) クライアントライブラリが実装されたのですが、
image.png
DataSnap という文字列を見て「あぁ、Professional Edition では使えないのか」と勝手に思い込んでいました。

image.png
そんなことはなかったぜ! (今更感)

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 を直接起動します。
image.png

・[要求] タブ

次のように指定します。
image.png

項目
メソッド GET
URL https://qiita.com
コンテンツタイプ application/json

・[パラメータ] タブ

次のように指定します。
image.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
image.png

・実行

[要求の送信] ボタンを押すと応答が返ります。
image.png
[コンポーネントのコピー] ボタンを押すとこの接続のコンポーネントがクリップボードにコピーされます。
image.png
image.png

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
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:

具体的な使用例

以前、Qiita の記事をバックアップするコードを書いた事があります。

記事内のコードを REST クライアントライブラリ対応に書き換えてみました。

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/.+/(?<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 です。

先述のコードを次のように書き換えます。

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 をインストールしなくても使う事ができます。

また、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:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?