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/gistsOAUTH_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をもう少し試して扱い方に慣れていきたい。
