GitHub APIを使ったWeb API利用手順の学習

以前書評を書いた「Software Design 2018年3月号」のp. 18-31「第1章 そもそもWeb APIとは何か」の特集記事を参考に,GitHub APIを例にWeb APIの利用手順を学習する。

Web APIの使い方自体は,Web APIごとにほとんど違いはなく,以下の3の手順で行う。

  1. アクセストークンの取得
  2. アクセス方法の学習
  3. コーディング

Webアプリケーションの場合,先日サンプルを投稿したJavaScriptのXMLHttpRequestを使ったり,HTTP通信を行うライブラリーが使われる。

アクセストークンの取得

GitHubではOAuth2を自前で実装して取得する他に,簡単にOAuth2トークンを生成できるアクセストークンを提供している。自分のトークンをその場で生成できるので簡単だ。

個人の設定ページで作成できる。ページにアクセス後,[Generate new token] を選択する。

[Personal access tokens] 画面

アクセス権の選択画面が表示される。[Note] に [Bookmarklet] と入力し,[Select scopes]>[☑gist] を選択して,[Generate token] を選択する。

[New personal access token] 画面

画面が変わってアクセストークンが表示される。

作成したアクセストークンの表示

アクセストークンは二度と表示されないので,コピーして控えておく。

万が一見失ったり忘れたりしても,削除してまた作成すれば問題ない。上記画面のアクセストークンは削除済みなので問題ない。

アクセス方法の学習

GitHubのドキュメントは [https://developer.github.com/v3/] にある。最新はGraphQLを扱ったv4だが,GraphQLはよくわからないので,ひとまずREST APIのv3を参照する。

まず冒頭でhttps://api.github.comが要請のエンドポイントであることが書いてある。そして,[Authnentication] に,以下のコマンドのようにHTTPヘッダーのAuthorizationヘッダーに取得したトークンを指定すればよいと書いてある。

curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com

さらに,Gistについては [Create a gist] にGistへの登録方法が書いてある。以下のようにHTTPメソッドのPOSTを使う。

POST /gists

要請のパラメーターも書かれている。

/gistsへのHTTP要請のパラメーター
NameTypeDescription
filesobject必須。ファイル名と内容。filesオブジェクト内のキーがstring型でファイル名を表す。なお,Gistが内部で自動的に使うため,ファイル名にgistfile[0-9]+ (gistfileに数字の接尾辞) を指定してはいけない。
descriptionstringgistの説明。
publicbooleantrueにすると一般公開される。既定はfalse。

fileオブジェクトは以下のキーを値に持つ。

fileオブジェクトの中身
NameType Description
contentstringファイルの内容。

POSTメソッドのbodyとその応答の事例が以下のように掲載されている。

Example
{
  "description": "Hello World Examples",
  "public": true,
  "files": {
    "hello_world.rb": {
      "content": "class HelloWorld\n   def initialize(name)\n      @name = name.capitalize\n   end\n   def sayHi\n      puts \"Hello !\"\n   end\nend\n\nhello = HelloWorld.new(\"World\")\nhello.sayHi"
    },
    "hello_world.py": {
      "content": "class HelloWorld:\n\n    def __init__(self, name):\n        self.name = name.capitalize()\n       \n    def sayHi(self):\n        print \"Hello \" + self.name + \"!\"\n\nhello = HelloWorld(\"world\")\nhello.sayHi()"
    },
    "hello_world_ruby.txt": {
      "content": "Run `ruby hello_world.rb` to print Hello World"
    },
    "hello_world_python.txt": {
      "content": "Run `python hello_world.py` to print Hello World"
    }
  }
}
Response
Status: 201 Created
Location: https://api.github.com/gists/aa5a315d61ae9438b18d

{
  "url": "https://api.github.com/gists/aa5a315d61ae9438b18d",
  "forks_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/forks",
  "commits_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/commits",
  "id": "aa5a315d61ae9438b18d",
  "node_id": "MDQ6R2lzdGFhNWEzMTVkNjFhZTk0MzhiMThk",
  "git_pull_url": "https://gist.github.com/aa5a315d61ae9438b18d.git",
  "git_push_url": "https://gist.github.com/aa5a315d61ae9438b18d.git",
  "html_url": "https://gist.github.com/aa5a315d61ae9438b18d",
  "files": {
    "hello_world.rb": {
      "filename": "hello_world.rb",
      "type": "application/x-ruby",
      "language": "Ruby",
      "raw_url": "https://gist.githubusercontent.com/octocat/6cad326836d38bd3a7ae/raw/db9c55113504e46fa076e7df3a04ce592e2e86d8/hello_world.rb",
      "size": 167,
      "truncated": false,
      "content": "class HelloWorld\n   def initialize(name)\n      @name = name.capitalize\n   end\n   def sayHi\n      puts \"Hello !\"\n   end\nend\n\nhello = HelloWorld.new(\"World\")\nhello.sayHi"
    },
    "hello_world.py": {
      "filename": "hello_world.py",
      "type": "application/x-python",
      "language": "Python",
      "raw_url": "https://gist.githubusercontent.com/octocat/e29f3839074953e1cc2934867fa5f2d2/raw/99c1bf3a345505c2e6195198d5f8c36267de570b/hello_world.py",
      "size": 199,
      "truncated": false,
      "content": "class HelloWorld:\n\n    def __init__(self, name):\n        self.name = name.capitalize()\n       \n    def sayHi(self):\n        print \"Hello \" + self.name + \"!\"\n\nhello = HelloWorld(\"world\")\nhello.sayHi()"
    },
    "hello_world_ruby.txt": {
      "filename": "hello_world_ruby.txt",
      "type": "text/plain",
      "language": "Text",
      "raw_url": "https://gist.githubusercontent.com/octocat/e29f3839074953e1cc2934867fa5f2d2/raw/9e4544db60e01a261aac098592b11333704e9082/hello_world_ruby.txt",
      "size": 46,
      "truncated": false,
      "content": "Run `ruby hello_world.rb` to print Hello World"
    },
    "hello_world_python.txt": {
      "filename": "hello_world_python.txt",
      "type": "text/plain",
      "language": "Text",
      "raw_url": "https://gist.githubusercontent.com/octocat/e29f3839074953e1cc2934867fa5f2d2/raw/076b4b78c10c9b7e1e0b73ffb99631bfc948de3b/hello_world_python.txt",
      "size": 48,
      "truncated": false,
      "content": "Run `python hello_world.py` to print Hello World"
    }
  },
  "public": true,
  "created_at": "2010-04-14T02:15:15Z",
  "updated_at": "2011-06-20T11:34:15Z",
  "description": "Hello World Examples",
  "comments": 0,
  "user": null,
  "comments_url": "https://api.github.com/gists/aa5a315d61ae9438b18d/comments/",
  "owner": {
    "login": "octocat",
    "id": 1,
    "node_id": "MDQ6VXNlcjE=",
    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
    "gravatar_id": "",
    "url": "https://api.github.com/users/octocat",
    "html_url": "https://github.com/octocat",
    "followers_url": "https://api.github.com/users/octocat/followers",
    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
    "organizations_url": "https://api.github.com/users/octocat/orgs",
    "repos_url": "https://api.github.com/users/octocat/repos",
    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
    "received_events_url": "https://api.github.com/users/octocat/received_events",
    "type": "User",
    "site_admin": false
  },
  "truncated": false,
  "forks": [
    {
      "user": {
        "login": "octocat",
        "id": 1,
        "node_id": "MDQ6VXNlcjE=",
        "avatar_url": "https://github.com/images/error/octocat_happy.gif",
        "gravatar_id": "",
        "url": "https://api.github.com/users/octocat",
        "html_url": "https://github.com/octocat",
        "followers_url": "https://api.github.com/users/octocat/followers",
        "following_url": "https://api.github.com/users/octocat/following{/other_user}",
        "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
        "organizations_url": "https://api.github.com/users/octocat/orgs",
        "repos_url": "https://api.github.com/users/octocat/repos",
        "events_url": "https://api.github.com/users/octocat/events{/privacy}",
        "received_events_url": "https://api.github.com/users/octocat/received_events",
        "type": "User",
        "site_admin": false
      },
      "url": "https://api.github.com/gists/dee9c42e4998ce2ea439",
      "id": "dee9c42e4998ce2ea439",
      "created_at": "2011-04-14T16:00:49Z",
      "updated_at": "2011-04-14T16:00:49Z"
    }
  ],
  "history": [
    {
      "url": "https://api.github.com/gists/aa5a315d61ae9438b18d/57a7f021a713b1c5a6a199b54cc514735d2d462f",
      "version": "57a7f021a713b1c5a6a199b54cc514735d2d462f",
      "user": {
        "login": "octocat",
        "id": 1,
        "node_id": "MDQ6VXNlcjE=",
        "avatar_url": "https://github.com/images/error/octocat_happy.gif",
        "gravatar_id": "",
        "url": "https://api.github.com/users/octocat",
        "html_url": "https://github.com/octocat",
        "followers_url": "https://api.github.com/users/octocat/followers",
        "following_url": "https://api.github.com/users/octocat/following{/other_user}",
        "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
        "organizations_url": "https://api.github.com/users/octocat/orgs",
        "repos_url": "https://api.github.com/users/octocat/repos",
        "events_url": "https://api.github.com/users/octocat/events{/privacy}",
        "received_events_url": "https://api.github.com/users/octocat/received_events",
        "type": "User",
        "site_admin": false
      },
      "change_status": {
        "deletions": 0,
        "additions": 180,
        "total": 180
      },
      "committed_at": "2010-04-14T02:15:15Z"
    }
  ]
}

試しに上記のサンプルコードをcurlコマンドで試してみる。

curl -H "Authorization: token $OAUTH_TOKEN" -d '{"files": {"hello.txt": {"content": "hello."}}}' https://api.github.com/gists

OAUTH_TOKEN変数に入手した認可コードを格納している。-dのコマンド引数ではなく,ファイルを指定する場合,以下のコマンドを実行する。

curl -H "Authorization: token $OAUTH_TOKEN" -d @file.json https://api.github.com/gists

file.jsonには-dで指定していたJSONを記しておく。

実行すると以下の通り,自分のGistにファイルがアップロードされている。

Gistへの登録結果

これでWeb APIのアクセス方法の学習が完了となる。

コーディング

アクセストークンを入手し,アクセス方法を学習したので,これらの情報を使って実際のコーディングに移る。

プログラミング言語によって細かい文法は異なるものの,基本的な流れは以下となる。

  1. アクセストークンやエンドポイントの変数への格納
  2. 送信JSONデータの作成
  3. HTTP要請の作成・送信

サンプルコードは,Webブラウザーで選択範囲をGistへ登録するブックマークレットの実装例を以下に記す。サンプルコードはGitHubでも公開している。

ブラウザーでの選択範囲をGistに登録
"use strict";

// API info.
let token = 'fba5bdae4a80b6ef91b446a926002031455ab5aa';
let url = 'https://api.github.com/gists';

// Target data.
let content = window.getSelection().toString();
let fileName = prompt('File name?', 'index.js');

// JSON.
let json = {
  "description": 'Code that was on ' + location.href + '.',
  'public': true,
  'files': {}
};
json.files[fileName] = {
  'content': content
};

// HTTP request.
let request = new XMLHttpRequest();
request.open('POST', url);
request.setRequestHeader('Authorization', 'token ' + token);
request.send(JSON.stringify(json));

request.onload = function() {
  if ((200 <= request.status) && (request.status < 400)) {
    var json = JSON.parse(request.responseText);
    if (confirm('Open URL?')) {
      window.open(json.html_url, '_blank');
    }
  } else {
    alert('Error occurred' + request.responseText);
  }
};

YOUT_TOKENに自分のトークンを記入して使う。このコードでは,Webブラウザーで現在選択中のテキストをGistに登録する。

最後に,このJavaScriptのコードをブックマークレットにする。元々の記事では,ブックマークレットの作成に「WDF – Software : ブックマークレット作成スクリプト」を使っていた。ただし,こちらはコメントを自分で削除しておく必要がある。

コメントの削除も自動で行ってくれる「ブックマークレット作成」を使う。先頭1行目に以下の内容を記入してから貼り付ける。

// title:(gist)

[ブックマークレット作成] を選択すると,[ブックマークレット] 欄に と書かれたリンクができる。これをブックマークツールバーにドラッグ・ドロップで登録する。

これにより,ボタンを押すだけでいつでもGistに登録できる。

結論

GitHub APIを例に,Web APIの利用手順を学習した。

curlやJavaScriptのXMLHttpRequestを使ったWeb APIの大まかな利用手順がわかり,今までよくわからなかったWeb APIというものの扱い方が具体的にわかってきた。

実際にアクセス可能なWeb APIをもう少し試して扱い方に慣れていきたい。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です