ut.code(); 学習カリキュラム #10

クライアントがデータを送信する仕組み

前回の記事で、サーバー上でプログラムを動作させる仕組みを学習しました。しかしながら、前回学習した内容では、サーバーは決まった動作を繰り返すのみでした。ユーザーの入力内容に応じて処理を変更するにはどうしたら良いのでしょうか。

もっとも単純な方法は、URLにクエリ文字列と呼ばれる追加情報を付加することです。東京大学の学務システムUTASを例にとってみましょう。ブラウザでUTASにログインし、トップページを表示させると、そのURLは

https://utas.adm.u-tokyo.ac.jp/campusweb/campusportal.do?page=main&tabId=home

になっています。「https://utas.adm.u-tokyo.ac.jp/campusweb/campusportal.do」まではよく見る形式ですが、「?page=main&tabId=home」は何者なのでしょうか。実は、これこそがクエリ文字列となっており、サーバー側に付加的な情報を伝える役割を担っています。形式は、見てわかる通り「パラメータ名=値」の組み合わせを「&」記号でつなぎ合わせた形になっており、先頭に「?」を付与することでクエリ文字列であることを明示しています。

プロパティ名や値に特殊な文字(主にアルファベット以外の文字を指します)が含まれる場合は注意が必要です。最も使用頻度の高いものとして、日本語の文字はURLとして使える文字に含まれません。したがって、これらの文字を使用する場合、URLとして使用可能な形式に変換してやる必要があります。

例えば、日本語の「あ」はUTF-8(現代のWebで使用される文字の符号化のための標準規格です)において「E38182(6桁の16進数)」で表されます。この時、「あ」をURLで使用可能な形式に変換する(URLエンコード)場合、「%E3%81%82」となります。

クエリ文字列を用いてPHPにユーザー入力を渡す

HTMLにおいてmethod属性をgetに指定したformタグを使用してinput要素を囲むと、input要素に入力された値をクエリ文字列を使用してサーバー側に送信することができます。各input要素のname属性に指定された値が、クエリ文字列におけるパラメータ名として使用されます。データの送信先のURLはformタグのaction属性によって指定できます。早速例を見てみましょう。

<form method="get" action="members.php">
    <p>名前: <input type="text" name="user-name"></p>
    <p><input type="submit" value="送信"></p>
</form>
ようこそ!<?php print($_GET['user-name']); ?>さん!
実行結果
実行結果

フォームの送信先の画面のアドレスバーに注目しましょう。index.htmlで入力された値が、URLのクエリパラメータとして引き渡されているのが分かります(なお、昨今のブラウザはURLエンコードされた文字列をデコードしてアドレスバーに表示する機能を持っているため、アドレスバーにエンコード済みの文字列が表示されていません)。

PHPでは、クエリパラメータによって渡されたデータは、「$_GET」変数に連想配列の形式で自動的に格納されます。この変数はプログラム中のどこからでもアクセスすることが可能となっています。

HTTP POSTメソッドを用いたデータの受け渡し

URLにクエリ文字列を付与することで、サーバー側にデータを送信することができることが分かりました。しかしながら、この方法では不十分な場合があります。もっともイメージしやすいのが、送信すべきデータが非常に大きい場合です。本来、規格上はURLの長さに上限は設けられていません。しかしながら、サーバーやブラウザによっては、使用できるURLの長さに上限を設けている場合があります。

第3回のカリキュラムで、サーバーとクライアントが、HTTPプロトコルを用いてどのように通信をしているのかを軽く学習しました。この通信内容について、もう少し詳細に眺めてみましょう。

リクエスト

以下は、HTTPリクエストの一例です。ブラウザから、「http://example.com/path/to/index.php」にアクセスすることを想定しています。ブラウザは、先にドメイン名からIPアドレスを取得した後、そのアドレスに向けて、以下のようなリクエストを発行します。

GET /path/to/index.php HTTP/1.1
Accept: image/gif, image/jpeg, */* Accept-Language: ja
User-Agent: Mozilla/4.0
Host: example.com

1行目の「GET」の部分をリクエストメソッドといいます。通常、ブラウザから適当なページを開くと、このリクエストメソッドを用いて通信が行われます。

スペースに続く文字列「/path/to/index.php」がパスです。URLのドメインより後ろの文字列がそのまま渡されます(クエリ文字列も含みます)。

さらに続く「HTTP/1.1」はHTTPプロトコルのバージョンを示しています。こちらに関してはあまり意識する必要はありません。

2行目以降は、リクエストヘッダと呼ばれる領域になっており、「ヘッダ名: 値」の形式で、1行につき1ヘッダのペースで記述されます。主に使用されるリクエストヘッダに、Hostリクエストヘッダがあります(上の例では4行目ですね)。

Hostリクエストヘッダには、アクセスしようとするサーバーのホスト(=ドメイン名)を指定します。昨今では、物理的なサーバーの台数を減らして多くのWebサービスを稼働させるため、複数のドメインが同じIPアドレスに名前解決される場合が多くあります。その際、サーバー側ではどのドメインへのリクエストか判断できないため、このHostヘッダを確認することにより、適切な応答をすることができるようになります。

レスポンス

サーバーは、クライアントからのリクエストを受け、適切なレスポンスを返す必要があります。以下は、レスポンスの一例です。前項のリクエストに対する応答を想定しています。

HTTP/1.1 200 OK
Content-Length: 7360
Content-Type: text/html; charset=UTF-8
Date: Tue, 22 Oct 2019 14:28:50 GMT
Server: nginx/1.17.4
Vary: Accept-Encoding

<html><body>Hello World!</body></html>

まず確認すべきなのが、一行目の「200」という整数になります。この数値は、ステータスコードと呼ばれ、HTTPリクエストの成否を端的に示すための値です。ステータスコード200は、リクエストが成功したことを示します。200番台以外のステータスコードは、リクエストが失敗したことを示します。

2行目以降は、レスポンスヘッダと呼ばれる領域になっています。リクエストヘッダと対になる概念で、様々な情報が「ヘッダー名: 値」の形式で1行毎に記述されます。

レスポンスヘッダから空行を一行あけて続く部分が、レスポンスボディです。リクエストされたリソースの内容が入ります。レスポンスヘッダのContent-Typeヘッダは、レスポンスボディの形式を表しています。ここで指定できる形式を、MIMEタイプと呼び、Webの世界ではWindowsでいう拡張子と同じような役割を果たしています。一般的に使われるものとして、以下のようなものがあります。

  • HTML … text/html
  • CSS … text/css
  • JavaScript … text/javascript、application/javascript等

他にも、画像として「image/jpeg」「image/png」、動画として「video/mp4」、さらに「application/pdf」など、様々なMIMEタイプが定義されています。

POSTリクエスト

一般的に使用されるGETメソッドの代わりに、POSTメソッドを使用することで、大規模なデータをサーバーに送信することができます。GETメソッドとPOSTメソッドの最大の違いは、リクエストボディが存在するかどうかになります。リクエストボディが存在するということで、POSTメソッドを用いたリクエストの場合は、リクエストボディの種類をContent-Typeリクエストヘッダで指定します。サーバーは、リクエストボディの内容に応じて、適切に処理を行うことができます。

GETメソッドとPOSTメソッド
GETメソッドとPOSTメソッド

以下は、冒頭のサンプルプログラムを、POSTメソッドを用いて書き直した例です。

<form method="post" action="members.php">
    <p>名前: <input type="text" name="user-name"></p>
    <p><input type="submit" value="送信"></p>
</form>
ようこそ!<?php print($_POST['user-name']); ?>さん!
実行結果
実行結果

ご覧の通り、HTML側のformタグのmethod属性がgetからpostに変わり、PHP側の$_GETが$_POSTに変わった以外に目立った変化はありません。

なお、formタグを用いてPOSTリクエストを送信する場合、通常Content-Typeリクエストヘッダはapplication/x-www-form-urlencodedになります。このとき、リクエストボディはURLのクエリ文字列と同じ形式でエンコードされます。以上の例では、HTTPリクエストは以下のようになっていることでしょう。

POST /members.php HTTP/1.1
Host: 167.179.73.35
Content-Length: 28
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3

user-name=%E7%94%B0%E4%B8%AD

課題

商品検索システムを作ってみましょう。入力画面でプロダクトIDを入力すると、値段が表示されるようにしてください。データは

$data = [
    ['product_id' => 'A101', 'price' => 100],
    ['product_id' => 'A102', 'price' => 300],
    ['product_id' => 'B321', 'price' => 230],
    ['product_id' => 'B334', 'price' => 360]
];

を使ってください。

ヒント

  • $dataはproduct_idキーとpriceキーを持つ連想配列の一次元配列になっています。foreachループを回して、product_idが入力値と一致したらその時のpriceを出力しましょう。