兵どもが、夢のあとさき

JavaScript 系の言語に興味を惹かれ、まったりと更新しております

Reactって何だー(環境構築編)

Scenery from the airplane
  • Reactのインストール
  • エディタの選択
  • React のインストール(というか、プロジェクトの作成)
  • "Hello World!!" の表記

 

 私はこれまでjQueryを使用してきましたが、ここ近年「React」がjQueryにとって代わろうとしています。

 Reactとはjavascriptの派生言語であり、javascriptと比較して格段にコーディングやデバッグがしやすくなっています。但し、Reactと言ってもブラウザ上で直接稼働する言語ではなく、コンパイラを経て最終的にはjavascriptに変換され、ブラウザはあくまでコンパイルされたjavascriptを読み込み、それを実行します。

 ここではまず React の開発環境のセッティングを行い、ローカル環境でReactの簡単なプログラム(c言語で言う printf("Hello World!!"); みたいなもの)を起動させてみましょう。

 

 開発環境設定というもの一度行ってしまえばそうそう再度行うことではありませんが、ここでは自分自身の覚書という意味合いでなるべく簡素に記載していきます。また開発環境というものは様々な手法があり、ここではあくまで私が行った手法を紹介しています。

 

 それでは、Get Started!! (なお、私の開発環境はWindowsです、MacOSLinuxでは異なりますことを御承知下さい。そちらの開発環境の設定には良い動画が公開されていますので、後ほどご紹介させていただきます。)

 

Reactのインストール

 まず、React 本体のインストールを行います

こちらから「Node.js」というソフトウェアをダウンロードしてください。

このとき必ず「推奨版」を選択してください。「最新版」はどんなバグがあるか分かりませんからね。

node-v16.16.0-x64.msi をダウンロードしました(2022/07/07現在)。

自分は React をやりたいんであって、Node.js などというわけわからんものを入れこまなにゃならんのだ? という方。ごもっとも。Node.js については追々説明する予定ですので、とにかく騙されたと思ってインストールして下さい。

インストールします。node-v16.16.0-x64.msi を叩きます。

Node.jsの最初のインストール画面

上記の画面が表示されます。「Next」をクリック

Node.jsの最初のインストール画面2

ライセンス条項です。英語の読める方はよく読んでおいて下さい。「Next」ボタンがデフォルトでは活性化されていませんが、左下のライセンス条項にチェックを入れれば活性化します

Node.jsの最初のインストール画面3

「Next」ボタンが活性化されました。ここでまた「Next」をクリック

インストールフォルダを選択します。Program FIles フォルダを汚したくない方は適当なフォルダを作成、入力して下さい。ここでまた「Next」をクリック

ここはデフォルトでいいでしょう。「Next」をクリック

npm(後ほど説明します)モジュールの一部は、c言語などでコンパイルする必要があります。それを行うかのチェックですね。空でも問題ありませんが、今後 React でがんがんソフトウェアを開発するぜ!という方はチェックボタンを選択してください。ここでは空のままでインストールします。「Next」をクリック。

さてようやくインストール画面です。ここではWindowsの管理者権限が必要となります。「Install」をクリック

この画面からしばらく画面がスタックしたかなと思った瞬間!

画面がスパッと切り替わって、「このアプリがデバイスに変更を加えることを許可しますか」という画面が表示されるので、「はい」をクリック

ようやくインストールが完了画面です。「Finish」をクリックすればNode.jsのインストール完了です。お疲れ様でした。

と、その前に。Node.jsが正確にインストールされたのか確認しましょう。
まず、Windowsコマンドプロンプト(ターミナル)を起動してください。

node -v と入力してください。v16.16.0と表示されていれば正常にインストールされています。とりあえずお疲れ様でした。

エディタの選択

 たった今ファイルの修正を行いましたが、ソフトウェアの開発にはエディタは必須です。現在有償無償様々なエディタが世に放たれていますが、私の選択は「Visual   Studio Code」一択ですね。
理由:①様々な言語(当然 React も含む)のデバッガとして利用できる ②エメットが優秀 ③ターミナルをエディタ内で起動できる 等々、これほどのものが無償でいいのか!と感銘を受けたエディタです。以下、Visual Studio Code(以下 VSCode と表記) を使用する条件で記載1 していきます。

 まずはVSCodeのインストールを行います
 こちらからダウンロードして下さい。VSCodeUserSetup-x64-1.69.0.exeが落ちてまいりました。さっそく実行。

同意するのチェックをいれ、次へをクリック

次へをクリック(デスクトップ上にアイコンいらないやという方は、チェックを外してください)

インストール をクリック

しばらく待つ

完了です。ここまで一分かかってません。さっそく起動。

 

こんな画面が出ればインストール完了です。

VSCodeはマウスでも様々な機能を使用することが可能ですが、ショートカットも多々ありますので、それらを覚えていけば手をあまり移動せずに開発が可能となりますので是非覚えて下さい。

React のインストール(というか、プロジェクトの作成)

 エディタで話がそれてしまいましたが、React のインストールはまだ完了していません。現在のままでは React のソフトウェアどころか、当初の目的であった(Hello World!!)すら記述することもできません。そこで、まずプロジェクト名(基本、名称はなんでもいいです)を決め、その専用フォルダを作成し、そこへ React の開発環境を生成します。

まず React 用のフォルダを生成します。

react フォルダへ移動

ここで謎のコマンド "npx create-react-app プロジェクト名" を実行してください。上記の場合 "test" がプロジェクト名となります。このとき "test" フォルダは、npx コマンドが生成しますので、先に作成する必要はありません。

実行中。あれ? Warning がまた出てるな。

完了しました。"cd test"、"npm start" を実行してみろと書いてありますね。では素直に実行してみましょう。

コマンド上では表記の様に正常に React プログラムを起動したようです。さて結果は?

こんな画面がブラウザ上に表記されれば成功です。では次に、ここに "Hello World!!" と表記してみましょう。

注釈: npm start というコマンドは、React プログラムをコンパイル(この場合 javascriptへ変換)し、簡易なHTTPサーバも起動します。その結果、localhost上のポート3000番にアクセスするとブラウザでそれを見ることができるわけですね。

 

"Hello World!!" の表記

 さて、一応の React プログラムは起動したわけですが、これを早速編集してみたいと思います。まずはシンプルに先ほど生成されたプログラムの "Edit src/App.js and save to reload." の個所を "Hello World!!" に置き換えてみます。
ではVSCodeを立ち上げて、今回作成した test プロジェクトのソースを手繰ってみましょう。

VSCodeの立ち上げ画面です。ここで、"ファイルを開く" => "フォルダを開く" で(ctrl + k + oでもOK)プロジェクトフォルダ(今回は /react/test/)を選択して下さい。"フォルダーの選択"をクリックすると…

このような画面が表示されます。ここで "src" の ">" をクリックすると

このように、このプロジェクトの各ファイル群が表示されます。これが React のソースの部分になります。では、実際にHTTPサーバが処理している個所はというと、"public" になります。こちらも ">" で内容を確認することが可能です。
React では、"src" 内のコードをコンパイルして "public" へ移動し、公開モードにしている訳です。これらは単純なフォルダ構成になっており、エクスプローラー等でも内容を確認することができます。ここで注意点は "public" はコンパイルの結果であり、編集すべきは "src" であり、"public" は編集すべきではないということです。コンパイルすれば上書きされてしまいますからね) index.html ファイルは例外です。これはコンパイルされた結果生成されたファイルではないので<title>タグなどは、プロジェクト名に即した内容に変更すべきです。

では、"Hello World!!" の世界へ!

極端なことを言えば、"public"フォルダ内のindex.html ファイルを修正すれば事足りてしまいます。しかし、それでは React を使った意味がありません。React はコンポーネントと呼ばれる部品("src" 、最終的には "public" 内の各ファイル)を作成、変更することで最小限の効力で完全かつ最速であるソフトウェアを生成することを目的としています。index.html ファイルを修正することでは、それは成しえないのです。例えば、フッターを考えてみてください。(Copyright © 2022 hoge, Inc.みたいなやつですね)フッターは内容が更新され、かつ西暦など表記する際には毎年変更されるべきですが、このフッター部分を持つ HTML ファイルが100あったとしたら、100回フッター部分を書き換える必要があるわけです。そんな無駄な努力はせずに、仮に Footer.js を作成し、そこへコピーライト等を表記する React を記載しておき、100個の HTML ファイルからコンポーネントを呼び出した方が圧倒的に効率が良いですよね。
そこで、まず各コンポーネント(しつこいようですが、"src" 、最終的には "public" 内の各ファイル)がどのように記載されているかを探索し、"Hello World!!" の世界へ進みましょう。

  1. ァイル群の探索
    ・index.html 内容は下図の様になっています。index.html はデフォルトでは、HTTPサーバが最初に読み込むファイルです。最も重要なファイルと言って良いでしょう。

    このファイルを見てまず気になるのは、

    %PUBLIC_URL%

    の部分ですか。ただこれは、

          Notice the use of %PUBLIC_URL% in the tags above.
          It will be replaced with the URL of the `public` folder during the build.
          Only files inside the `public` folder can be referenced from the HTML.

    とあるように、"%PUBLIC_URL%" は、(プロジェクトフォルダ配下の)"public" フォルダに置き換えられます。

    次に、気になるのは

        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

    ですが、こちらは

        <!--
          manifest.json provides metadata used when your web app is installed on a
          user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
      -->

    とある様に、ユーザ側のモバイル端末やデスクトップにインストールされた場合に提供されるメタデータですね。

      <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root"></div>
      </body>


    index.htmlファイルで一番重要な個所は、ここですね。今、あなたのブラウザでは


    この画面になっているはずです。それでは、index.htmlファイルから、

        <div id="root"></div>

    を削ってみてください。(VSCode では、当該行に移動して、ctrl + x)
    結果、画面が真っ白になり何も表記されなくなります。
    では、戻してみましょう。(VSCode で、ctrl + z)
    元の画面に戻りました。
    要するに HTML 上の id="root" が何らかの処理を行っている訳です(div タグはHTML上では何の意味を為さないので)。さて、では id の "root" の処理は何処で行われているのでしょう。 

    その前に重要なことがあります。"src" フォルダと "public" フォルダの関係です。この依存関係はどのようになっているのでしょうか。論より証拠。"src" の index.js を開いてみてください。

    これは javascript ではなく、JSX と言う React の表記言語です。
    記述10行目に <App /> との表記がありますが、これは App.js を呼び出しています。これを削ってみてください。
    先ほどと同様にブラウザの画面が真っ白になりました。つまり、"src" を変更するとリアルタイムで React のプログラムに反映されることになります。
     
    つまり、/public/index.html => /src/index.js => /src/App.js の依存関係がリアルタイムで成り立ち、"src" のファイル群も /public/index.html へ直接影響を与えることが証明されました。

    話が飛びましたが、結局 id の "root" の処理は何処で行われているのでしょう。 答えは、もう出ていますね。/src/index.js 内に以下の表記があります。

    const root = ReactDOM.createRoot(document.getElementById('root'));

    getElementById('root') とある様に、id(HTML内では唯一無比)が 'root' であるオブジェクトを、変数 root にDOM(HTMLのツリー構造ですね)として取得しています。この構文はやはり JSX で書かれており、ここではふんわりとイメージが掴めればOKです。

    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    次の行の、この部分でレンダリングの処理をおこなっています。これも JSX 構文です。内容は、Appコンポーネントを呼び出しているだけですね。それでは、App.jsの内容を見てみましょう。

    10行目を見てください。そのまんま「Edit src/App.js and save to reload.」と表示をする JSX が書かれているではないですか。
    この部分を "Hello World!!" に置き換えましょう。9から11行目ですね。
            <p>
              Hello World!!
            </p>

    無事、"Hello World!!" と表示されました。変わらない場合には、リロードしてみてください。
    これにて、React の環境設定から簡単なプログラムの変更までが終わりました。お疲れ様でした。
    React は JSX(javascript + XML)で記載する必要がありますが、HTML、javascriptライクな表記であり、ざっと言ってしまうと javascript をよりシンプルに、簡易に書ける言語であると思っています。無論、他の言語同様難解な部分(class コンポーネント、Functionable コンポーネントとは何よ? とか)も多々ありますが、慣れるとUIを作成するうえでこれ以上良い言語は無いと勝手に思っております。


     

    続きを読む

XMLHttpRequest とは何か?

XMLHttpRequest とは

 そもそも XMLHttpRequest とはなんでしょう。

 だらだらと書くと、XMLHttpRequest とは、javascript から HTTP アクセスを行う際に使用される API です。ブラウザから直接 HTTP アクセスする方式に比べ、Ajax 等の技術を使用すれば、Google Map のように画面遷移を行わなくとも情報を更新したりすることができます。但し、基本的にクロスドメイン(プロトコルやアドレスやポートが異なる)環境では情報の取得を行うことはできません。

 XMLHttpRequest と難解な名称がついていますが、基本は HTTP です。また XML 以外のデータ(JSONなど)を扱うことも可能です。

 

実際のデータのやりとり

 それでは実際に、サーバデータ(今回は JSON)にアクセスした場合、直接ブラウザでアクセスした場合と、XMLHttpRequest からアクセスした場合の HTTP ヘッダの違いを表示します。まずクロスドメインの場合です。

 

 直接アクセスした場合

  1. Request URL:
  2. Request Method:
    GET
  3. Status Code:
     
    200 OK
  1. Request: Headers
  2. Accept:
    text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
  3. Accept-Encoding:
    gzip,deflate,sdch
  4. Accept-Language:
    ja,en-US;q=0.8,en;q=0.6
  5. Cache-Control:
    max-age=0
  6. Connection:
    keep-alive
  7. Cookie: ...
     
  8. Host:
    weather.livedoor.com
  9. User-Agent:
    Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36
  1. Response: Headers
  2. Content-Type:
    application/json; charset=utf-8
  3. Transfer-Encoding:
    chunked
  4. X-Content-Type-Options:
    nosniff
  5. X-Frame-Options:
    DENY

 取得した JSON データは下記のようになります。

{

  • pinpointLocations
     
    [

     

    ...

    •  
  • title"石川県 金沢 の天気",
  • description
     
    {
    • text"(気圧配置など) 本州付近は高気圧に覆われています。 (天気分布など) 石川県は、晴れとなっています。 (今日の天気 24日) 高気圧に覆われますが、気圧の谷の影響を受けるでしょう。 このため、 石川県では、晴れで夜は曇りとなるでしょう。 (明日の天気 25日) 気圧の谷が通過する見込みです。 このため、 石川県では、曇りで朝から昼前にかけて雨となるでしょう。",
    • publicTime"2014-02-24T10:41:00+0900"
    }

}

 

 次に、XMLHttpRequest からアクセスします

  1. Request: Headers: Provisional headers are shown
  2. Accept:
    application/json, text/javascript, */*; q=0.01
  3. Origin:
  4. User-Agent:
    Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36

  エラーとなります。エラー内容は、

XMLHttpRequest cannot load http://weather.livedoor.com/forecast/webservice/json/v1?city=170010. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

 要するに、相手サーバ側が「Access-Control-Allow-Origin」ヘッダを出力していないため、XMLHttpRequest は、送信されてきたデータにアクセスしない。ということです。前にも記載しましたが、この場合でも JSON データは送信されてきています。あくまで  XMLHttpRequest 側で拒否しているということです。

 サーバ側に、「Access-Control-Allow-Origin」ヘッダの付与をお願いするわけにはいきませんし、これで手詰まりです。これを回避するには、javascript 以外の言語を使用するのみです。

 

 では、同一ドメインではどのような情報がやりとりされているのでしょうか。

 

 ブラウザから直接アクセスした場合 

  こちらは、クロスドメインと同様です。単に、HTTP/GET メソッドでデータを取得するだけですので、そのまま JSON データを受信します。

 

 XMLHttpRequest からアクセスした場合 

 
  1. Request Method: GET
     
  2. Status Code:
     
    200 OK
  3. Request: Headers
  4. Accept:
    application/json, text/javascript, */*; q=0.01
  5. Accept-Encoding:
    gzip,deflate,sdch
  6. Accept-Language:
    ja,en-US;q=0.8,en;q=0.6
  7. Connection:
    keep-alive
  8. Host:
  9. User-Agent:
    Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.117 Safari/537.36
  10. X-Requested-With:
  1. Response: Headers
  2. Connection:
    Keep-Alive
  3. Content-Length:
    277
  4. Content-Type:
    text/javascript; charset=UTF-8
  5. Keep-Alive:
    timeout=5, max=100
  6. X-Content-Type-Options:
    nosniff
  7.  

 こちらも JSON データが正常に取得されます(データは割愛)。 

 「X-Requested-With」ヘッダが付加されています。これは、XMLHttpRequest が、自分が「XMLHTTPRequest」であることを宣言しているわけです。これにより、サーバ側はクライアントが XMLHttpRequest からアクセスされたことを認識できるわけです。ただ、これも繰り返しになりますが、ヘッダ情報はスクリプトなどを使用して疑似ブラウザを作成すれば、いくらでも改編できますので、それを完全に信用するのはセキュリティホールにつながります。

  また、このヘッダ情報はクロスドメインでは送信されません。おそらく、もともとクロスドメインXMLHttpRequest アクセスが許可されていないためであると思われます。

 

 

 

JSONあれこれ(5)

まとめ

 ここまで引っ張ってきましたが、残念ながら「お天気Webサービス」から JSON データを javascriptライブラリで取得する方法は見つかりませんでした。但しサーバ側の設定次第では、取得する方法はありますので、下記にまとめます。

 

サーバとクライアントが同一ドメインの場合

 セキュリティの観点から同一ドメイン間がベストです。その場合は、javascriptライブラリ(jQuery等)からのアクセスであることをチェックします。

・クライアント例 (HTML + jQuery)

<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>

<!-- Internet Explorer 6~8で動作させる場合はバージョン1系を使用してください -->

<!-- <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script> -->

<script>

 $(function () {

  $("#load").on("click", function () {

   $.getJSON("json.php", function (data) {

    $("#food").append(data[0].item);

   })

   .fail(function(jqXHR, textStatus, errorThrown) {

    alert('getJSON request failed! ' + ': ' + textStatus + ': ' + jqXHR.responseText);

  })

 });

});

</script>

</head>

<body><div><input type="button" value="読込" id="load"><p id="food"></p></div></body>

・サーバ側の例 (PHP)

// javascriptライブラリからかチェック

if (! isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') {

 die(json_encode(array('status' => "不正な呼び出しです")));

 }

 

// データを準備します。

$value = array(

  1 => array('item' => '台湾ラーメン', 'price' => 580, 'orders' => 113),

  2 => array('item' => '台湾ラーメン(アメリカン)', 'price' => 580, 'orders' => 72),

  3 => array('item' => 'ニンニクチャーハン', 'price' => 630, 'orders' => 87),

);

 

// Content-Typeを「application/json」に設定します。(text/json ではない)

header("Content-Type: application/json; charset=UTF-8");

// Internet ExplorerがContent-Typeヘッダーを無視しないようにします

header("X-Content-Type-Options: nosniff");

 

// 可能な限りのエスケープを行ない、JSON形式で結果を返します。

echo json_encode(

  $value,

  JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP

);

 

 

 最初の5行を記載することで、JSON データを javascriptライブラリ以外が取得することが不可能となります。但し、他のヘッダにも言えることですが、「X-Request-With」ヘッダを改竄するスクリプトなどではアクセスできてしまいますが。

 

 

クロスドメインの場合

 ① javascriptライブラリから、直接リモートサーバの URL にアクセス

  ① のクライアント例

...

<script>

 $(function () {

  $("#load").on("click", function () {

   $.getJSON("http://127.0.0.1/php-recipe/07/11/json.php", function (data) {

    $("#food").append(data[0].item);

   })

 ...

 ①のサーバ例

  クロスドメインの場合、「X-Requested-With」ヘッダはクライアントから送信されないので、チェックは無意味です。その代わり (ではないですが) Access-Control-Allow-Origin ヘッダを送信することで、クライアントはサーバの許可を得たと認識します。

// javascriptライブラリからかチェックは不可

// if (! isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest') {

// die(json_encode(array('status' => "不正な呼び出しです")));

// }

 

// データを準備します。

$value = array(

  1 => array('item' => '台湾ラーメン', 'price' => 580, 'orders' => 113),

  2 => array('item' => '台湾ラーメン(アメリカン)', 'price' => 580, 'orders' => 72),

  3 => array('item' => 'ニンニクチャーハン', 'price' => 630, 'orders' => 87),

);

 

header("Content-Type: application/json; charset=UTF-8");

header("X-Content-Type-Options: nosniff");

// javascripライブラリのクロスドメインアクセスを許可します。* は適切に

header("Access-Control-Allow-Origin: *");

 

echo json_encode(

  $value,

  JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP

);

 

 

 

JSONP を使用する

 HTMLの<script src="...">タグの仕様の穴をついたHack。セキュリティに十分留意が必要。また、ブラウザ側からサーバ側で設定された「コールバック名」をパラメータで渡す必要があります。

 ②のクライアント例

...

<script>

 $(function () {

  $("#load").on("click", function () {

 // コールバック名を指定する。下記では「__callback」

   $.getJSON("http://127.0.0.1/php-recipe/07/11/json.php?__callback=?", function (data) {

    $("#food").append(data[0].item);

   })

 ...

 ② のサーバ例

...

//header("Content-Type: application/json; charset=UTF-8");

header("Content-Type: text/javascript; charset=UTF-8");  // Content-Typeを「text/javascript」に設定

 

header("X-Content-Type-Options: nosniff");

// JSONPは、javascript ライブラリからのコールではないので無関係。従ってサーバ側で Access-Control-Allow-Origin ヘッダの設定は無意味

// header("Access-Control-Allow-Origin: *");

 

# 可能な限りのエスケープを行ない、JSON形式で結果を返します。

$json = json_encode(

$value, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP

);

 

// クライアントから、GETパラメータでコールバック名が指定された場合の処理。

 

// クライアント側で「__callback=***」パラメータを指定した場合、$jsonp_callback に *** を格納。

$jsonp_callback = isset($_GET['__callback']) ? $_GET['__callback'] : null;

// 「__callback=***」パラメータを指定した GET メソッドの場合 ***( ... )」で括った JSON データを返す

echo ($jsonp_callback ? $jsonp_callback . '(' : '') . $json . ($jsonp_callback ? ')' : '');

 

 

 

 

・サーバ側のチェック

 異なるドメインの場合、javascriptライブラリは、x-request-with ヘッダを送信しない。したがって、同一ドメインの場合の様なチェックは不可。

 

JSONデータへのアクセスの手段と、ヘッダ名 (x-request-with ⇒ ブラウザの javascript ライブラリが送信、Access-Control-Allow-Origin ⇒ サーバ側で設定) の相関

方式 ヘッダ名 送信・影響の有無
1. jQuery(同一ドメイン) x-requested-with 送信される
Access-Control-Allow-Origin 影響されない
2. jQuery(クロスドメイン) x-requested-with 送信されない
Access-Control-Allow-Origin 影響される
3. JSONP x-requested-with 送信されない
Access-Control-Allow-Origin 影響されない(scriptのため)
4. 直接(HTTP/GET)で JSON データを呼び出す x-requested-with 送信されない
Access-Control-Allow-Origin 影響されない

 サーバ側で、Access-Control-Allow-Origin を設定すると、jQueryでリモートアクセスが可能となります。しかし、これは悪意ある第三者からの攻撃を受ける可能性もあるので、ファイアウォールAccess-Control-Allow-Origin の値を設定を適切に行うことが肝要です。同時に認証トークンを使用することも良い手段です。

 それから、XMLHttpRequest は、Lvele1, 2 とあるのですが、上記の情報はすべて Level 2 となります。(Opera ではクロスドメイン通信ができないとのうわさがちらほら見られます) 

 

 参考リンク集

HTTP access control(CORS) https://developer.mozilla.org/ja/docs/HTTP_access_control

https://gist.github.com/cowboy/1200708

機密情報を含むJSONには X-Content-Type-Options: nosniff をつけるべき http://d.hatena.ne.jp/hasegawayosuke/20130517/p1

- Windows - 起動しているサービスを停止する

忘備録

 起動しているポート番号から、そのアプリ名を取得して停止する。Windowsコマンドプロンプトを起動します。以下のコマンドを実行

netstat -nao 当該ポートを使用している PID を確認
tasklist /svc /fi "PID eq 2128" その PID を使用しているアプリ名を確認
kill -f 2128 アプリ名を確認後、そのアプリPIDを停止する

 ポート番号からタスクを停止できるので便利です。

 

 

 

JSONあれこれ(4)

 JSONを出力するプログラム(json.php)はできましたので、次はそのデータにアクセスし、データを取得・表示する受信側を作成します(ajax.php)。これも『PHP逆引きレシピ 第2版』からの引用です。なお、サーバ環境に関しては『PHP逆引きレシピ 第2版』のままとしていますので、そちらを参照願います。良い本ですよ。

 名称は ajax.php ですが、下記の様に PHP ではなく HTML + jQuery で構成されています。(7,8行目にコメント追加修正)

<!DOCTYPE html>

<html lang="ja">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width,initial-scale=1.0">

<title>JSON形式のデータを返すWeb APIを作りたい</title>

<link href="../../css/style.css" rel="stylesheet">   <!-- 7行目 CSS 読み込み -->

<script src="../../js/jquery-2.0.3.min.js"></script>   <!-- 8行目 jQuery ライブラリ読み込み -->

<!-- Internet Explorer 6~8で動作させる場合はバージョン1系を使用してください -->

<!-- <script src="../../js/jquery-1.10.2.min.js"></script> -->

<script>

  $(function(){

// jQueryを使用して、読込ボタンがクリックされたら処理を行ないます。

    $("#load").on("click", function(){

// Web APIjson.php)で生成したjsonデータを取得して処理します。

      $.getJSON("json.php", function(data){

        for (var i in data) {

// 行のオブジェクトを生成します。

          var tr = $("<tr>");

// 列のオブジェクトを生成して行に追加します。

          var td_item = $("<td>").text(data[i].item);

          tr.append(td_item);

          var td_price = $("<td>").text(data[i].price);

          tr.append(td_price);

          var td_orders = $("<td>").text(data[i].orders);

          tr.append(td_orders);

// 行のオブジェクトをテーブルに追加します。

          $("#listbox").append(tr);

// 読込ボタンを非表示化します。

          $("#load").hide();

        }

      });

    });

  });

</script>

</head>

<body>

<div>

<input type="button" value="読込" id="load">

<table id="listbox">

<tr>

<th>品名</th>

<th>価格</th>

<th>注文数</th>

</tr>

</table>

</div>

</body>

</html>

 http://localhost/php-recipe/07/11/ajax.php を実行すると、下記ページが表示されます。

   f:id:shogorobe:20140213143227j:plain

「読込」ボタンをクリックすると、

    f:id:shogorobe:20140213172731j:plain

 と表示され、json.php で定義した JSONデータが ajax.php によって受信、表示されました。 これは何でもないようなことですが、実は ajax.php から HTTPのGETメソッドを発行し json.php を呼出し、JSONデータ(Content-Type: Application/json)の受信、Unicodeデコード (json.phpエンコードされています) 、javascriptオブジェクト(XMLHttpRequest)化を行っています。そして、そのオブジェクトにアクセスし、<table>タブに挿入することで、画面遷移なしにデータの表示を行っているのです。jQuery の $.getJSON メソッド便利です。

 HTTP/GETメソッドを発行しているということは、例えば ajax.php の16行目のjQuery の表記を下記のように JSONP で取得する様に変更すれば、リモートサーバの JSON データを取得することも可能です(かなりトリッキーですし、jQuery 側のクロスドメイン制限、セキュリティ上の留意点など注意すべき点がありますが)。

$.getJSON("http://リモートサーバ/~/json.php?callback=?", function(data){

 * すいません上記の「読込」後のボタンですが本来は表記されません

CDNの使用

 8行目で jQueryライブラリを読み込んでいるのですが、CDNという機能を使用すると、jQuery をWebサーバ上に置くことなく手軽に利用できます。(その他にもメリットがあります)

 最新の jQuery を読み込むように8行目を下記のように書き換えます。

<script  type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script> 

 メリットとしては、

  • 高性能なサーバ(上記の例では、code.jquery.com)を利用でき、高速化が期待できる
  • 同じサーバを指定すればキャッシュ効果も期待できる
  • 何といっても自サーバの jQuery の設定が不要

が挙げられます。

 

フォールバック

 良いことだらけの CDN に見えますが、デメリットもあります。アクセス先のサーバがダウンしたり、ネットワークに異常が発生した場合 jQuery のライブラリが取得できない場合が発生します(私も1回経験しました)。そこで実運用環境では、下記の様に自サーバ上にも jQuery の環境を整備し、CDNが使用できない場合(window.jQuery が undefined となり、|| 以降が実行される)バックアップ機能を持たせるのが良いのかなあと。3番目のメリットが無くなりますが。

<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script>window.jQuery || document.write('<script src="../../js/jquery-2.0.3.min.js"><\/script>')</script>

それでは次回まとめに入ります。

 

追記

 JSONPによるリモートサーバからの JSON データの取得ですが、一般に公開されている Web API のアクセスに関しては思いの外、大変です。http://weather.livedoor.com/weather_hacks/webservice で公開されている、livedoorの「お天気Webサービス」の情報を上記方法で取得しようとしてもエラーとなります。

 Chomeのデバッガを使用しながら、金沢の情報を取得してみるとデータの取得はできています(下記引用参照)。しかし、「XMLHttpRequest cannot load http://weather.livedoor.com/forecast/webservice/json/v1?city=170010. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.」というエラーが発生しています。

  1. copyright: {provider:[{link:http://tenki.jp/, name:日本気象協会}], link:http://weather.livedoor.com/,…}
  2. description: {,…}
  3. forecasts: [,…]
  4. link: "http://weather.livedoor.com/area/forecast/170010"
  5. location: {city:金沢, area:信越・北陸, prefecture:石川県}
  6. pinpointLocations: [{link:http://weather.livedoor.com/area/forecast/1720100, name:金沢市},…]
  7. publicTime: "2014-02-15T11:00:00+0900"
  8. title: "石川県 金沢 の天気"
  9.  

 これは、サーバ側で「Access-Control-Allow-Origin」ヘッダを送信していないため、データの取得はしなかった、というエラーです(エラーというか javascript の仕様)。$.getJSON()では、データは XMLHttpRequest オブジェクトで取得されるのですが、これはクロスドメインではアクセスは許されません。つまり同じドメインのみデータのアクセスが可能なわけです。そのためブラウザ(今回はChrome)は、別のドメインから取得した JSON データの取得を拒否したということです。

 回避方法は、いくつかあるようですが、今回の場合は ajax.php と同一ドメインのサーバ上で、 $.getJSON() を実行するサーバ上に「お天気Webサービス」の JSON データを取得する javacsript 以外のプログラムを別途設置するのが実現的かなと考えます。「お天気サービス」では、JSONPの「コールバック」による取得はできないようですし。

 もちろん、セキュアな情報を扱う Web API を構築するのであれば、同一ドメインで管理すべきでしょう(認証、セッション管理を別途行うのはもちろん)。

 Ajax が出力する「X-Requested-With」ヘッダは、クロスドメイン環境では出力されないようです(私が確認したのは Chrome 32.0.1700.107 m)。そのため、JSONハイジャックを防止する目的で、同ヘッダをチェックする方法は採れないようです。

 まとめると

  • Ajax(XMLHttpRequest)でクロスドメインのデータの取得は不可 
  • JSONPを使用すると取得は行うが、ブラウザの javascript でエラー(jQuery の $getJSON() メソッドで .fail()が呼び出される)となる
  • JSONJSONP は全く異なるものである。JSON は単なるデータであるが、JSONPスクリプトである
  • 従って、クロスドメイン環境でも、<script src="..."></script>の形式で、JSONP を呼び出すことが可能
  • JSONPを使用する場合、サーバ側で設定した「コールバック名」を指定することで、ブラウザの  javascriptライブラリ(jQuery の $.getJSON()など)で JSON データを取得することが可能
  • XMLHttpRequest でデータの取得を行うには、サーバ側で「Access-Control-Allow-Origin: *」ヘッダを出力する必要がある。この設定を行えば、クロスドメインのデータの取得は可。(但し JSONPスクリプトであるため、javascriptライブラリは、この設定に影響されずデータを取得してしまう)
  • クロスドメイン環境では、X-Requested-With ヘッダの有無による、XMLHttpRequest からのアクセスであるかのチェックは不可。同一ドメイン環境でのみ可能

(1) JSONP を使用する ⇒ 

 サーバ側の「コールバック名」を指定すれば JSON データを取得できる

 サーバ側でAccess-Control-Allow-Origin: *」の設定も不要

 サーバとクライアント側で、「コールバック名」を一致させる必要がある

 セキュリティに十分留意する必要がある

(2) AjaxJSON データを取得する ⇒

 サーバ側で「Access-Control-Allow-Origin: *」の設定が必要(ブラウザではデータの受信は行うが、javascript ライブラリでブロックする)

 クロスドメイン環境では、X-Requested-With ヘッダが送信されないので、サーバ側での、Ajax からのアクセスチェックには使用できない。同じドメインでのみ使用可能

 

 

 一部修正しました。まだ試行錯誤中です。間違っていたら追記します。

JSONあれこれ(3)

どのようにアクセスすればよいのでしょうか?

 

と、その前に、このプログラムのセキュリティ等に関してまとめておきます。

 

 まず、下記ですがプログラム内のコメントにもありますように、「JSONハイジャック攻撃」を防止するために、受信HTTPヘッダ(x_request_with)をチェックしています。先ほど直接アクセスしても『不正な呼び出しです』と表示されたのは、それが理由です。

# jQueryなど主要なJavaScriptライブラリを通じてのアクセスである
# (ブラウザからの直接アクセスでない)ことを確認します。
# この方法はJSONハイジャック攻撃など☆レシピ305☆(JSONのセキュリティについて知りたい)に対しても有効です。
if (! isset($_SERVER['HTTP_X_REQUESTED_WITH']) ||
    $_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {
  die(json_encode(array('status' => "不正な呼び出しです")));
}

 

ブラウザ等の受信側に、JSONデータを返すことを明確にヘッダで指定します。

# Content-Typeを「application/json」に設定します。
header("Content-Type: application/json; charset=UTF-8");

 

 受信したデータのMIMEタイプは、上記の Content-Type から決定されるべきです。しかし、IEでは受信データのコンテンツから決定する「仕様」があるようです。コンテンツの内容からMIMEタイプを判断した場合、HTMLでないデータがHTMLと判断され、XSS脆弱性が発生する可能性があります。

 IE8以降では、下記ヘッダを指定することで、この「仕様」を無効にすることが可能となります。

 IEがアクセスするサイト(!)は、上記のMIMEの設定をきちんと行い、下記のヘッダも同時に出力すれば間違いないようです。

# Internet ExplorerがContent-Typeヘッダーを無視しないようにします☆レシピ287☆(XSS対策をしたい)。
header("X-Content-Type-Options: nosniff");

 

 可能な限りのエスケープを行ない、JSON形式で結果を返します。

「<」「>」等のコードをそのままにしておくと、XSSの危険性がありますので、できうる限りエスケープします。

echo json_encode(
  $value,
  JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
);

パラメータの意味は下記の通りです。

JSON_HEX_TAG 「<」「>」を、\u003C及び\u003Eに変換
JSON_HEX_APOS 「'」を、\u0027に変換
JSON_HEX_QUOT 「"」を、\u0022に変換
JSON_HEX_AMP 「&」を、\u0026に変換