はじめに
この記事について
この記事はWebを支える技術
などの参考文献からWebの仕組みや設計について勉強した際のまとめである。
ポートフォリを作成するにあたって,せっかくなら自分が使いたくなるようなWebサービスを作りたいと思い,色々アイディアを練っていたのだが,それらをいざ作ろうと思うとあまりにもいろんな実装方法があり過ぎて何をどのように設計するのが好ましいのかわからなかった。
WebAPIの設計について色々調べていくとRESTという概念について出会った。
この記事にはその概念を調べ上げた結果得られた知識の再整理を行う。
とはいえ、Web系は初心者なため間違っているところがたくさんあるかも知れません。そこは注意してください。
まだ書き途中のまま投稿するため、もしかしたら随時更新するかもしれないし、やらないかもしれない。
この記事の目的
- Webとは何かについて知る.
- アーキテクチャスタイルのRESTとは何かについて理解する
- URIを設計する上で気を付けるべきことを知る.
- WebAPIの設計におけるベスト・バットプラクティスを知る
想定している読者
当時の自分は次のようなタイプだったので似たような人の助けになればと思う。
- なんらかの言語でコードを書くことができる.
- TCP/IPの知識はぼんやりと記憶にあるが,Webの仕組みについては疎い
- とりあえず動くアプリなら作れるけど, WebAPIとして何が良い・悪い設計なのかについて理解していない
ただし,自分なりの視点や疑問点を中心に記事を進めるので読みにくいところがあるかもしれません。そこはご了承ください。
また,サンプルコードとしてはJavaScriptかPython3を用いるのでそれらの言語が読めるとなお良いかもしれません。
この記事で書かないこと
- インターネットやWebの詳しい歴史
- HTTPやURIの詳しい仕様
- ネットワークのコンポーネントに関する説明
- 通信の暗号化の方法などセキュリティに関する詳しい知識
- HTMLなどといった特定の言語の文法
- ハンズオン的なWebサービスの開発手法
ではさっそく本題に入る。
本題
Webとは何か。
生まれた年代やコンピュータを本格的に触り始めた時期に依存するかもしれないが,10代から20代の一般的な消費者はネットといえば恐らくGoogle検索を使ったネットサーフィンを真っ先に思い浮かべると思う(自分はそうだった)。ましてや現代においてはWebという単語自体、日常でも頻繁に利用されるくらいに当たり前なものになってしまっている。それ故に通信の仕組みについて勉強するまでは「Web」の本来の意味を知らず「Web≒インターネット」という勝手な解釈をとっていたところがある。
まずはこれらの誤解を解消する目的でWebとは何かをざっくりと説明する。
Webを構成するシステム
Webには技術的には二つの側面がある。
-
ハイパーメディア
テキストや動画,写真,文書など(以降は総称してメディアという)をハイパーリンクによって相互に結びつけて得られたシステムのこと。HTMLやXMLなどがハイパーメディアに当たる。 -
分散システム
メディアやデータを一つのコンピュータで管理したり処理するのではなく,ネットワーク上に分散された複数のコンピュータが管理・処理するシステムのこと
ハイパーメディアや分散システムの構想はWebが登場する前からもあったが,Webはそれをインターネット上で実現したのが以前の他のシステムと異なる部分である。
Webとは結局のところネットワークを利用したシステムの一つの形態に過ぎない。
Webの簡単な歴史
すでにお気づきかもしれないが,Webが誕生する以前は時系列にすると
ハイパーメディアや分散システムの構想 $\longrightarrow$ インターネットの登場$\longrightarrow$ Webの誕生
という順を辿っている。詳しい歴史については割愛するが,Webが登場する前のインターネットには他の分散システムも存在してはいた(例えば分散オブジェクトなど)。だが,結果的にWebがあまりにも急速にインターネットを全体に広がった。
「Web≒インターネット」という勝手に解釈してしまっていた原因は,今の若者が物心着いてパソコンを触る頃にはすでにWebが普及していたからであろう。
なぜWebがここまで普及できたのかの詳しい説明に関しては参考文献を参照のこと。
Webの通信プロトコル
ハイパーメディアや分散システム以外にもWebが通信をする際に利用する技術がある。
-
HTTP
ハイパーテキストトランスポートプロトコルの略。TCP/IPにおけるアプリケーション層に位置するプロトコルでWebサービス(サーバー)と利用者のデバイス(クライアント)が通信する際に利用される。クライアントがHTTPリクエストをサーバーに送信し,サーバーがそれを受け取ると適切なHTTPレスポンスをクライアントに返却する。 -
URI
Uniform Resource Identifierの略。ネットワーク上のリソース(ここではデータのことだと考えれば良い)がどこにあるのかを一意的に識別するための文字列。HTTPで通信する際にHTTPメソッドと共にHTTPリクエストの初めの1行(リクエストライン)で必ず指定する必要がある。URLなら聞いたことある人は多いだろうが,それとほぼ同じだと考えて問題ない。
サーバー側がHTTPリクエストを受け取ると、指定されたURIに一致するリソースに対して,指定されたHTTPメソッドを適用することを試みる。そしてその結果をHTTPレスポンスとしてクライアントに返却する。
ざっくりとまとめると,Webはインターネット上にあるハイパーメディアをHTTPでつなげる分散システムといえる。
さまざまなWebサービス
Webが使われているサービスには以下のようなものがある.
-
Webサイト
-
Webアプリケーション
人間がWebを通じて利用することを想定したアプリケーション。デスクトップのアプリとは違い,ChromeやSafariなどのWebブラウザさえあればどの端末でも利用可能。TwitterやYoutube,Amazonなどを想像するのが一番わかりやすいと思う。 -
WebAPI
HTTP通信で呼び出すことができるプログラムや関数,あるいはその仕様のことを指す。自分でなにかプログラムや関数を組む場合,どのような入力値に対してどのような結果を返すのかといった仕様を決める必要がある.その仕様をAPIと呼んだりするのだが,実際にそのプログラムを実行する上では,そのAPIとプログラムはほぼ同じものであるといえる。このような意味でプログラム自体もまたAPIと呼ぶこともある。WebAPIはAPIのWeb版である。リクエスト内のURIで呼び出されたWebAPIは,そのリクエスト内のパラメータとメソッドに応じた返り値をレスポンスボディとして格納し,そのレスポンスをクライアントに返却する。この説明がわかりにくい方は下のWebAPIの例を見て欲しい。なお,Webアプリがクライアントとして人間を想定しているのとは対照的に,WebAPIはクライアントとしてはプログラムを想定している。したがってWebAPIが返すレスポンスボディは,JSON型のようにあらゆるプログラムでも利用しやすいデータ型になっていることが多い。いくつかのWebAPIを組み合わせて,HTML&CSSやJavaSctiptなどで見た目や動きをつけたものがWebアプリだと思えばいいだろう。
以降はこれらを総称してWebサービスと呼ぶことにする。
WebAPIの例
利用するとき
WebAPIの例として郵便番号検索を提供するZipCloudがある.ここではそれをPythonを使って利用する例を提示する。なおPythonでHTTP通信を行うライブラリとしてRequestsを使用している。
ZipCloudのWebAPIをPythonから使う場合は次のようにすれば良い。
import requests
response = requests.get("https://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060")
たったこれだけでWebAPIへのHTTPリクエストの送信とレスポンスの受信が完了する。request.get(uri)
でuriに指定されたリソース(WebAPI)に対してGETメソッドのHTTPリクエストを行い,それに対するWebAPIからのレスポンスをresponse
に格納している。なおuriの https://zipcloud.ibsnet.co.jp/api/search までが郵便番号検索APIのURIである。試しにこのURIをクリックしてみると
{
"message": "必須パラメータが指定されていません。",
"results": null,
"status": 400
}
と表示されると思う。必須パラメータとはZipCloudの場合はzipcode
として指定されている。uri
のsearch
の後ろに?zipcode=7830060
と指定すると,そのリクエストを受け取ったWebAPIは郵便番号が783-0060である住所のJSONデータをレスポンスボディとしたHTTPレスポンスを作成し,クライアントに送信する。試しに https://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060 をクリックするとブラウザ上に次のように表示されると思う。
{
"message": null,
"results": [
{
"address1": "高知県",
"address2": "南国市",
"address3": "蛍が丘",
"kana1": "コウチケン",
"kana2": "ナンコクシ",
"kana3": "ホタルガオカ",
"prefcode": "39",
"zipcode": "7830060"
}
],
"status": 200
}
ブラウザで単純にクリックして表示する場合だと見た目があまりにも簡素であることがわかると思う。これは先ほど説明したようにWebAPIはプログラムが利用することを想定しているからである。郵便番号で検索して得られた住所のデータをPythonの他のプログラムで利用する場合は,先ほどのコードを実行した後
address = response.json()["results"][0]
などとすれば良い。こうして得られたaddressは
>>type(address)
<class 'dict'>
>>print(address)
{'address1': '高知県', 'address2': '南国市', 'address3': '蛍が丘', 'kana1': 'コウチケン', 'kana2': 'ナンコクシ', 'kana3': 'ホタルガオカ', 'prefcode': '39', 'zipcode': '7830060'}
のようにPythonのdictとして扱える。ここまでのPythonのコードを見てみると,WebAPIと通信するのにHTTPを利用しているというだけで通常の関数を呼び出す場合とそこまで変わらないことがわかると思う。
作るとき
PythonでWebAPIを作れるライブラリやフレームワークとしてはDjango REST framework,Flask, FastAPIがある。
コードの詳細は略すが,例えばFlaskでhttp://ドメイン名/hello
にアクセスしたとき{"message": "hello world"}
というJSONを返すWebAPIを作成する場合は次のようなソースコードになる(サーバーをホストする方法とかはここでは省く)。
from flask import Flask
app = Flask(__name__)
@app.route("/hello", methods=["GET"])
def hello_world():
return {
"message": "hello world"
}
なお@app.route("/hello", methods=["GET"])
はデコレータとよばれるもので,app.route("/hello", methods=["GET"])
は関数を引数にとる関数で返り値もまた関数になるのだが(ややこしい),次の二つのコードの結果はいずれも同じになる。
hello_world()
app.route("/hello", methods=["GET"])(hello_world)()
ここでは,FlaskによるWebAPI実際の挙動としてはユーザーがhttp://ドメイン名/hello
にアクセスしたことをトリガーにしてhello_world
が実行される程度に考えておけばよい。
WebAPIの実装方法は言語やフレームワークなどによってさまざまだが,イメージしにくい場合は「結局のところWebAPIはリモートの関数みたいなもの(callable object)やプログラムのことだが,それらを実行するための引数を与えたり,その返り値を受信するためにHTTPを利用している」のだと考えれば良い。実際WebAPIは上記のようにJavaやRuby,Python,Go,PHPなどといったプログラム言語で書かれている。
どの言語でWebAPIを作るのかは作るサービスに依存するが,設計をする際に共通して守る必要のある設計方針がある。次のセクションではこれについて述べる。
REST
実際に上記の仕組みで自分のWebサービスを作ろうと思うと,どのように設計するのかの方針が要になってくる。そこで登場するのがRESTと呼ばれるアーキテクチャスタイルである。
Webのアーキテクチャスタイル
Webシステムを設計する上での指針や流儀(アーキテクチャスタイル)としては色々なものが生まれたが、結果としてRESTと呼ばれるアーキテクチャスタイルが最も広く普及した。
何故このRESTが最も普及したのかや他にどんなものがあったのかなどについて知りたい場合は実際にWebを支える技術を手にとって読んで欲しい。
RESTの詳細
RESTはアーキテクチャスタイルの一つだと述べたが、実はRESTは6つのアーキテクチャスタイルから構成されるものである.
-
クライアント/サーバー
HTTPやURIのところで述べたが,リクエストを送信するクライアントと,リクエストを受け取りレスポンスを返すサーバーで処理が完全に分担されている。したがっていろいろなデバイスからもアクセスが可能であり,サーバー側はクライアントにデータを提供する機能を備えるだけで良い。 -
ステートレス1サーバ
サーバー側がクライアントのアプリケーションの状態を保存しないことを指す。アプリケーションの状態はサーバーから送られてきたハイパーメディア内のリンクをクリックすることによって遷移する。したがってそれをサーバーが保存しないということは,クライアントが同一アプリケーション内でたどったリンクの履歴をサーバー側が記憶しないということになる。これによってサーバー間でのクライアントに関するデータの同期が不要になり,実装が単純になる。ただしログインすることで利用することができるWebサービス(Twitterなど)はユーザのログイン状態をCookieやセッションなどを利用して2サーバー側が保存する必要がある。このような場合はステートレス性を諦めざるを得ない。
-
キャッシュ
サーバーから取得したデータをクライアント側のローカルストレージに一時的に格納し,再利用する仕組みのこと。キャッシュによって保存されたデータもキャッシュと呼ばれる。キャッシュによってネットワークの通信の負荷を下げることができる。デメリットとしてはキャッシュの鮮度が低下し,情報の信頼性が低下することである。 -
統一インターフェース
限定的なインタフェースを用いて,URIで指定したリソースへの操作を行うこと。現在のHTTPではGET,HEAD,POST,PUT,DELETEなどを含む9つのメソッドのみが定義されている3。インタフェースが統一されることにより,サーバーとクライアントの実装における独立性が向上する。
-
階層化システム
負荷分散やアクセス制限をする目的でロードバランサやプロキシなどのネットワークコンポーネントをサーバーの前に置いたりなどするように,システムをいくつかの階層に分離すること。 -
コードオンデマンド
プログラムコードをサーバーからダウンロードし,クライアント側で実行できる仕組みのこと。HTMLのscriptタグ内で指定するJavaScriptコードなどが該当する。クライアントのアプリケーションを拡張することができるのがメリット。
RESTの制約は守るのが好ましいが絶対ではなく,いくつかを除外しても構わない。
URIの設計
URIの仕様
細かな仕様の説明についてはいろいろな文献があるのでそちらに譲る。ここではURIを構成する用語について簡単に解説する。
https://example.com/hoge/path_param?qery=param
上記のURIに対して,それぞれ
- URIスキーム: https
- ホスト名: example.jp
- パス: hoge/path_param
- クエリパラメータ: query=param
という呼び名がある。
URIで使用できる文字としてはASCII文字(大文字小文字のアルファベット,数字,記号-.~:@!$&'() )
ASCII文字以外の文字,例えば日本語をURIに含める場合は%エンコーディングが必要になる。
Cool URI
変わりにくいURIのことをCool URIというが,ここではそれを実現するためのポイントを説明する。
-
実装や言語に依存した文字列を含めない
たとえばhttps://example.com/Flaskapp/login.py
のようにメソッド名や言語の拡張子,使用しているライブラリ名などをURIに含めると,実装の仕方を変えたりリファクタリングしたりしたときにURIをその都度変えなければならない。 -
アクセスごとに変化する値を含めない
例えばhttps://example.com/home?sessionid=10024
のようにセッションIDをURIに含めると,ログインしなおしたりしたときにはURIは変更になってしまう。 -
リソースを表現する適切な名詞にする
例えばhttps://example.com/home/tweet/112/update
のようなURIでhttps://example.com/home/tweet/112
のリソースを更新する処理を実装すると,HTTPメソッド以外のメソッドを適用したように見える。URIはあくまでリソースを特定するだけのものであるから,メソッドはURIではなくHTTPメソッドで指定するのがRESTが要請する統一インタフェースを実現する意味でも好ましい。 -
マトリクスURIを使う
例えばGoogleマップのような地図アプリで使用する緯度と経度のように,階層構造では表現できないものはマトリクスURIをしようするとよい。
http://example.com/map/lat=35.740;lng=139.751
-
URIの不透明性を意識する
クライアントアプリケーション側がURIを勝手に組み立てたり,推測できたりしないようなURIを不透明であるという.例えばhttps://example.com/home.jp
で日本語のページを,https://example.com/home.en
で英語のページを表示できたとすると,https://example.com/home.fr
のページも推測可能であるが,そのページが存在するとは限らない。また,クライアント側のアプリケーションでURIを組み立てるプログラムが実装されているとすると,サーバー側がURIの構造を変更したときにシステムが動かなくなり修正作業も多くなる密結合状態になってしまう。
参考文献
-
その他Webサイト
https://thinkit.co.jp/free/article/0609/8/4
https://developer.mozilla.org/ja/docs/Web/HTTP/Methods
セッション管理の周辺知識まとめ
-
対義語としてステートフルという言葉がある。 ↩
-
Cookieやセッションに関する説明は こちらのサイト や書籍『プロになるためのWeb技術入門』――なぜ,あなたはWebシステムを開発できないのかなどを参考にしてほしい。 ↩
-
こちらを参照https://developer.mozilla.org/ja/docs/Web/HTTP/Methods ↩