GitHub APIを使ったWeb API利用手順の学習
以前書評を書いた「Software Design 2018年3月号」のp. 18-31「第1章 そもそもWeb APIとは何か」の特集記事を参考に,GitHub APIを例にWeb APIの利用手順を学習する。
Web APIの使い方自体は,Web APIごとにほとんど違いはなく,以下の3の手順で行う。
- アクセストークンの取得
- アクセス方法の学習
- コーディング
Webアプリケーションの場合,先日サンプルを投稿したJavaScriptのXMLHttpRequestを使ったり,HTTP通信を行うライブラリーが使われる。
アクセストークンの取得
GitHubではOAuth2を自前で実装して取得する他に,簡単にOAuth2トークンを生成できるアクセストークンを提供している。自分のトークンをその場で生成できるので簡単だ。
個人の設定ページで作成できる。ページにアクセス後,[Generate new token] を選択する。
アクセス権の選択画面が表示される。[Note] に [Bookmarklet] と入力し,[Select scopes]>[☑gist] を選択して,[Generate 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
要請のパラメーターも書かれている。
Name | Type | Description |
---|---|---|
files | object | 必須。ファイル名と内容。filesオブジェクト内のキーがstring型でファイル名を表す。なお,Gistが内部で自動的に使うため,ファイル名にgistfile[0-9]+ (gistfileに数字の接尾辞) を指定してはいけない。 |
description | string | gistの説明。 |
public | boolean | trueにすると一般公開される。既定はfalse。 |
fileオブジェクトは以下のキーを値に持つ。
Name | Type | Description |
---|---|---|
content | string | ファイルの内容。 |
POSTメソッドのbodyとその応答の事例が以下のように掲載されている。
{
"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"
}
}
}
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にファイルがアップロードされている。
これでWeb APIのアクセス方法の学習が完了となる。
コーディング
アクセストークンを入手し,アクセス方法を学習したので,これらの情報を使って実際のコーディングに移る。
プログラミング言語によって細かい文法は異なるものの,基本的な流れは以下となる。
- アクセストークンやエンドポイントの変数への格納
- 送信JSONデータの作成
- HTTP要請の作成・送信
サンプルコードは,Webブラウザーで選択範囲をGistへ登録するブックマークレットの実装例を以下に記す。サンプルコードはGitHubでも公開している。
"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をもう少し試して扱い方に慣れていきたい。