兵どもが、夢のあとさき

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

Reactって何だー(JSX、props編)

photo at Rebun island

JSXとは

 JSX とはなんでしょうか。JSX とは、JavaScript + XML の略で、JavaScript の拡張構文です。UI がどのような見た目かを記述するために、React で JSX を使用することは必須と言って良いでしょう。基本的な形式は、

function コンポーネント名(引数) {
 /*
  JavaScript

 */

 return (
  HTML文
 )
}

となります。

 それでは、簡単な JSX 構文のプログラムを作成してみましょう。プロジェクト名は jsx としましょう。

 ターミナルを立ち上げ、react フォルダに戻ります。ここで、\react> npx create-react-app jsx でプロジェクトを作成します。しばらく、コーヒータイムです。

VSCode で、「フォルダを開く」で jsx フォルダを選択して開きます。下記の画面になります。(publicフォルダと、srcフォルダは展開しました)

ここで App.js を開きます。

 これが、JSX の雛型です。
 コンポーネントを作成します。(6~8行目、呼び出しは25行目)

結果は、

の様に、’Hello!!’ がコンソールに表示されました。

props を使用して引数をコンポーネントに渡す

 次の様に、プログラムを変更します。(6 及び 25行目)

 props というオブジェクトで引数を受け取っています。25行目で name='Hello!!' を渡していますので、受け側は、props.name という形でそのデータを受け取ることができます。props に渡す変数は、文字列、数字、関数、コンポーネント、オブジェクト何でも引き渡すことが可能です。複数の引数を渡したい場合には、下記の様に呼び出し側をスペースで区切ってください。

 一点注意ですが、props は親コンポーネントから、子コンポーネントへ渡される引数であり、逆はありません。また、props という名称は、特に決まった名称ではなく、hikisuu でも hennsuu でも何でも構いませんが、ただ、React では props という名称を使うことが慣例となっていますので、可読性を考慮した場合、props を使用することが良いでしょう(なお、props は properties の略で、財産、資産という意味です)。

 また、今更になりますがコンポーネントの名称の頭文字は必ず大文字にして下さい。<hello /> では認識されません。function hello() も同様です。

 

 

Reactって何だー(ソフトウェア開発編 その2)

photo at Moiwa mountain

 

 おはようございます。暑い日が続いてますね。皆さま熱中症には十分注意してください。それでは早速続きと参りましょう。

Axiosの使い方

 前ページで、Axios の使い方が間違っていると記載しましたが、本来の Axios は以下の様な記述をする必要があります(今回は get メソッド)。

 

axios.get(URLを記述)
 .then(function(response) {
        // getが成功した時の処理
        console.log(response)
    })
   .catch(function(error) {
       // getが失敗した時の処理
       console.log(error)
    })
    .finally(function() {
      // axiosの処理結果によらずいつも実行させたい処理を記述
    })

 

 それでは実際に実装してみましょう。そして成功時、失敗時の時の処理の動きを確認しましょう。

15~26行目を書き換えました。実行してみると、

 以前同様、宗谷地方の気象情報を得ることができました。それでは、次はエラーを発生させてみましょう。URL を書き換えます。

 存在しないエリア番号を指定しましょう。結果は、

 エラーコード 404 (Not Found)が出力されています。つまり存在しないファイルということですね。これをデバッガではなく UI 上に出力させるためコードを下記の様に書き換えました。

 
import logo from './logo.svg';
import './App.css';
import axios from "axios";  // npm install axios を実施済

function App() {

  const weatherData = 'みんなの天気予報'
  var obj = ''

  const Req = async () => {

    // const area = "011000"   // 宗谷地方
    const area = "000000"   // 何処?

    await axios.get(url + area + ".json")
      .then(function(response) {
        // get処理の成功時
        // console.log(response)
        obj = response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

      // alert(obj)
      document.getElementById('weather').textContent = obj

      })
      .catch(function(error) {
        // 処理の失敗
        console.log(error)
        if(error.response) {
          document.getElementById('weather').textContent = 'エラーが発生しました'
          + ': ' + error.config.url
          + ': ' + error.response.status
        }
      })
      .finally(function() {
        // 成功時も、失敗時も実行される
      })
 
    }

  const buttonAlert = () => {
    // alert(JSON.stringify({weatherData})); // コメントアウトしました
    Req()
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 これでエラーステータス 404 が表示されるかと実行すると、

 エラーステータスが、0 となってしまいます。これでは、エラーハンドリングを行うことができません(デバッガでも、error.response.status は、0 でした)。エラーハンドリングは非常に重要性が高く、トラブルが発生した際にエンドユーザに現在何が発生しているのかを通知(回線障害、サーバ障害、不正処理等々)するために必要なものです。従って、ここの部分(エラーハンドリング)で手を抜くと運用に入った際にトラブルシューティングに非常に時間を要する可能性があります。

 しかし、困ったな… 悩んでいても仕方がないのでここは一度ペンディングします。取得方法が判明したら改めてご報告したいと思います。

 

コンポーネント化について

 これまで App.js にすべてのソースを書いてきました。小さなプログラムですからこれで問題はないのですが、様々な機能を一つのファイルにすべて詰め込むとバグが出た際にデバッグが困難になったり、先だっても述べましたがフッタの様に複数のプログラムで同様の機能を各々作成していてはメンテナンス性も悪くなります。

 React は元々コンポーネント化を考慮された言語であり、部品と呼ばれるコンポーネントを組み合わせることにより、より簡易な構築そしてメンテナンス性の高いプログラムが書けるようになっています。それでは、App.js をコンポーネント化していきます。

import logo from './logo.svg';
import './App.css';
import axios from "axios";  // npm install axios を実施済

function App() {

  const weatherData = 'みんなの天気予報'
  var obj = ''

  const Req = async () => {

    const area = "011000"   // 宗谷地方
    // const area = "000000"   // 何処?

    await axios.get(url + area + ".json")
      .then(function(response) {
        // get処理の成功時
        // console.log(response)
        obj = response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

      // alert(obj)
      document.getElementById('weather').textContent = obj

      })
      .catch(function(error) {
        // 処理の失敗
        console.log(error)
        if(error.response) {
          document.getElementById('weather').textContent = 'エラーが発生しました'
          + ': ' + error.config.url
          + ': ' + error.response.status
        }
      })
      .finally(function() {
        // 成功時も、失敗時も実行される
      })
 
    }

  const buttonAlert = () => {
    // alert(JSON.stringify({weatherData})); // コメントアウトしました
    Req()
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 現在の App.js プログラムです。この中で、Req 関数を呼び出しています。この部分をコンポーネント化してみます。

 まず Req 関数の部分を選択し、切り取ります(ctrl + x)。
 次に、下向き鏃の src を選択し、「新しいファイルの作成」(ctrl + n)を実行。

 この画面で「言語の選択」をクリック。「言語モードの選択」に react と入力します。

 JavaScript React をクリック。

 先ほどのソースをペースト(ctrl + v)

 「ファイル(F)」から、「名前を付けて保存」を選択し、Req と名称をつけて保存してください。

 src フォルダのやや下の方に、Req.jsx*1 というファイルが生成されました。
 生成されたファイルの内容は以下の通りです。(不要なコメントを外したり、obj 変数を関数内に入れてあります)

const Req = async () => {

  const area = "011000"   // 宗谷地方
  var obj = ''

  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      // console.log(response)
      obj = response.data[0].timeSeries[0].areas[0].area.name
      + ": "
      + response.data[0].timeSeries[0].areas[0].weathers[0];

    document.getElementById('weather').textContent = obj

    })
    .catch(function(error) {
      // 処理の失敗
      console.log(error)
      if(error.response) {
        document.getElementById('weather').textContent = 'エラーが発生しました'
        + ': ' + error.config.url
        + ': ' + error.response.status
      }
    })
    .finally(function() {
      // 成功時も、失敗時も実行される
    })
  }

 そして、元の App.js からは Req() に相当する部分を削除して下さい。ソースは下記となります。

import logo from './logo.svg';
import './App.css';
import axios from "axios";  // npm install axios を実施済

function App() {

  const weatherData = 'みんなの天気予報'
  const buttonAlert = () => {
    // alert(JSON.stringify({weatherData})); // コメントアウトしました
    Req()
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 

 さて結果は?

 エラーとなりました。Req() 関数が見つからないようです。

 実はコンポーネントは明示的に自分自身を外へ開示する必要があります。そして読み込む方も、そのコンポーネントを明示的に読み込む必要があります。具体的には、

Req.jsx には

export default Req

と記述する必要があり、読み込む側の App.js には

import Req from './Req';

を記述する必要があります。export は最終行に、import は頭に記述することになっています(単なる慣例ですけど)。

 もう一つ大事な点があります。Axios ライブラリの読み込みに関してです。Req() 関数をコンポーネント化しましたので、Axios は App.js ではなく Req.jsx で読み込む必要があります。そこで、App.js の

 
import axios from "axios";

を削除して、Req.jsx の頭に張り付けてください。

 実行します。と言うか React は勝手にコンパイルされるのでエラーが出ていた場合には、本章を再度読み直して確認してください。

 この画面になっていればOKです。ボタンをクリックすると、

 はい、元の挙動どおりに稼働しました。これがコンポーネント化です。

 

引数の引き渡し

 一応コンポーネント化は行われましたが、これでは何がおいしいのかメリットが見当たりません。そこで次は、Req コンポーネントに引数を渡して(そうですね、宗谷地方以外の地域も表示できるようにして)みましょう。引数を渡すことによって、Req コンポーネントの使いまわしが可能となります。

 まず、親コンポーネントである App.js を下記の様にReq メソッドに引数を指定します。

function App() {

  const weatherData = 'みんなの天気予報'
  var area = ''
  var areaName = ''

  const buttonAlert = () => {
    Req(area = '011000', areaName = '北海道')
  }
 
 

 次に、Req.jsx メソッドが引数を受け取る様に書き換えます。

const Req = async (area, areaName) => {


  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      let obj = areaName + "の" + response.data[0].timeSeries[0].areas[0].area.name
      + ": "
      + response.data[0].timeSeries[0].areas[0].weathers[0];

        document.getElementById('weather').textContent = obj
    })

 結果は、

 この様に引数が引き渡されました。

 これを複数の地域の情報を表示させます。App.js を次のように書き換えてみましょう。(変更部分のみ)

App.js

  const buttonAlert = () => {
    Req(area = '471000', areaName = '沖縄地方')
    Req(area = '350000', areaName = '山口県')
    Req(area = '330000', areaName = '中国地方')
    Req(area = '270000', areaName = '大阪府')
    Req(area = '230000', areaName = '中部地方')
    Req(area = '130000', areaName = '東京都')
    Req(area = '020000', areaName = '青森県')
    Req(area = '016000', areaName = '札幌')
    Req(area = '012000', areaName = '北海道中部')
  }

Req.jsx

  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      let obj = areaName + "の" + response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

        // document.getElementById('weather').textContent = obj

        var c = document.getElementById('weather');
        c.insertAdjacentHTML('afterbegin', "<li align='left'>" + obj + "</li>");

    })

 結果は、

の様になりました。
 ただ、Req.jsx を複数呼び出しているのはみっともないので連想配列を使って、シンプル化します。
 App.js

 この様に、連想配列を使用することにより、ソースの見通しが良くなりました。コメント部分は不要なので削除してしまいましょう。

 App.js

import './App.css';
import Req from './Req';

function App() {

  const weatherData = 'みんなの天気予報'

  let areaData = {'012000': '北海道中部', '016000': '札幌', '020000': '青森県', '070000': '福島県',
                  '130000': '首都', '230000': '中部地方', '270000': '近畿地方', '330000': '中国地方',  
                  '350000': '山口県', '471000': '沖縄地方',
    }

  const buttonAlert = () => {    
   
    Object.keys(areaData).forEach(key => {
      Req(key, areaData[key])
    })
  }

  return (
    <div className="App">
      <header className="App-header">
        <div id='weather'>
          <p>{weatherData}</p>
        </div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 Req.jsx

import axios from "axios";

const Req = async (area, areaName) => {


  await axios.get(url + area + ".json")
    .then(function(response) {
      // get処理の成功時
      let obj = areaName + ": " + response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

        var c = document.getElementById('weather');
        c.insertAdjacentHTML('beforeend', "<li align='left'>" + obj + "</li>");
      })

    .catch(function(error) {
      // 処理の失敗
      console.log(error)
      if(error.response) {
        document.getElementById('weather').textContent = 'エラーが発生しました'
        + ': ' + error.config.url
        + ': ' + error.response.status
      }
    })

    .finally(function() {
      // 成功時も、失敗時も実行される
    })
}

export default Req

 結果は?

 結果は表示されましたが、地方の順序がばらばらです。やはり、北から or 南から表示させたいですね。連想配列は通常の配列と異なり、順不同で格納されています。そのため、取り出す際に登録した順には得ることができないのです。

 連想配列で順番を保ちたい場合には Map オブジェクトを使用するとうまくいきます。

App.js

import './App.css';
import Req from './Req';

function App() {

  const weatherData = 'みんなの天気予報'

  const buttonAlert = () => {
   
    const areaData = new Map([
      ['012000', '北海道中部'], ['016000', '札幌']    , ['020000', '青森県'],
      ['070000', '福島県']    , ['130000', '首都']    , ['230000', '愛知県'],
      ['270000', '近畿地方']  , ['280000', '兵庫県']  , ['330000', '中国地方'],
      ['350000', '山口県']    , ['471000', '沖縄地方'],
    ])
 
    Req(areaData)

 }

  return (
    <div className="App">
      <header className="App-header">
        <div id='weather'></div>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
        </a>
        <div><button onClick={buttonAlert}> {weatherData} </button></div>
      </header>
    </div>
  );
}

export default App

 連想配列を Map で定義し、そのオブジェクトをまるごと Req.jsx に引数として渡します。

Req.jsx

import axios from "axios";

const Req = async (areaData) => {

  for (let [key, areaName] of areaData) {
 
    const obj = url + key + ".json"

    await axios.get(obj)

      .then(function(response) {
      // get処理の成功時
      let obj = areaName + ": " + response.data[0].timeSeries[0].areas[0].area.name
        + ": "
        + response.data[0].timeSeries[0].areas[0].weathers[0];

        let pattern = / /ug
        obj = obj.replace(pattern, '')

        let c = document.getElementById('weather');
        c.insertAdjacentHTML('afterend', "<li align='left'>" + obj + "</li>");
      })

      .catch(function(error) {
      // 処理の失敗
        console.log(error)
        if(error.response) {
          document.getElementById('weather').textContent = 'エラーが発生しました'
          + ': ' + error.config.url
          + ': ' + error.response.status
        }
      })

      .finally(function() {
        // 成功時も、失敗時も実行される
      })
    }
}

export default Req

 あとは、Req.jsx で、そのオブジェクトを受け取り、for で各要素を処理していきます。ついでに、正規表現を使用して

 let pattern = / /ug
 obj = obj.replace(pattern, '')

で余計な全角スペースを削っています。さて結果は、

 見事に、南から北への天気予報が表示されました。余計なスペースも削られて、見やすくなっていますね。
 この章はここまでです。次章では、React の肝である JSX と props を使用したコンポーネントを作成していきたいと思います。

 

 

 

 

 

*2

 デフォルトでは、React DOM は JSX に埋め込まれた値をレンダー前にエスケープします。このため、自分のアプリケーションで明示的に書かれたものではないあらゆるコードは、注入できないことが保証されます。レンダーの前に全てが文字列に変換されます。これは XSS (cross-site-scripting) 攻撃の防止に役立ちます。

 

JSX の導入 – React より引用。これにより、同時にクロスサイトリクエストフォージェリ (CSRF) を防ぐことも可能となります。

 
 

MongoDB などの NoSQL データベースでは JSON と同様のフォーマットのデータをそのまま格納することができます。

*1:Reactは、JavaScriptと明確に異なることを明示するために、.jsx という拡張子を付けることが慣例となっています

*2:Axiosのセキュリティについて

*3:今後使いたいDBについて

Reactって何だー(ソフトウェア開発編 その1)

photo in Kyoto city

 さて、"Hello World!!" の表示は行うことができたので、次に簡単なプログラムを作成してみましょう。お題は、友人のいる地域の天気予報一覧です。
私は、とあるスマホゲームをやっておりオフ会等を行ってきた結果(最近はコロナの影響でできませんが…)全国に友人を増やすことができました。最近(2022/07/11現在)は気温も高くなり、コロナの他にも熱中症にも気を使うことも多く、その話題でチャットが盛り上がったりします。
 そこで、今回は全国各地にいる友人の地域の天気予報(現在の気温、湿度等も含む)を一覧にして表示するプログラムを作成することにしました。

 

 天気予報の情報は、気象庁JSON 形式で提供していますので、こちらを使用させていただきます。(気象庁の情報を利用するためには利用規約があります。使用する場合にはこちらを十分留意してください。)

 

 

 早速とりかかるとします。今回のプロジェクト名は "weather" 、構築環境は VSCode を使用します。まず、コマンドプロンプト/ターミナルでプロジェクトを作成します。
 

例によって数分かかります。今日は暑いのでアイスコーヒーを作って飲んで待ってます

完了しました。では次に VSCode で本プロジェクトを開きます。

 

 

本プロジェクトの流れは、下記のようになります。

 

(1) ユーザがボタンを押す

(2) 気象庁のサイトから気象情報を JSON 形式で取得

(3) 予め設定した地域の情報を選択

(4) HTML 形式で出力

となります。いたってシンプルですね。

 

ここで一点言い訳弁明を。react ではコンポーネントと呼ばれる小さな部品を組み上げてソフトウェアを作成することが推奨されています。これは、前回も述べたように小さな部品を構成することにより、変更点が生じた場合最小限の労力で、修正することができるからです。しかし、ここでは手を抜いて前回の "Hello World!!" で使用した、App() コンポーネントにすべて入れ込むことにしました。初学者は細かいことを考えるよりガンガン手を動かして、ソフトウェアをガンガン作成していきましょう!

 

と言うことで、App() コンポーネントに必要なソースを書いていきます。

App.js を開きます。デフォルトでは下記の様になっています。

ユーザがボタンを押す

 ユーザがボタンを押すためには、そのボタンを表示させなければなりません。では、ボタンを表示させます。20行目に下記を記載して下さい。

        <div><button> みんなの天気予報 </button></div>

 これは JSX でも何でもなく、単なる HTML ですね。しかし、とりあえずボタンらしいものは表記されるようになりました。

このボタンは押しても何も起こりません。それは当然です。ボタンを押したときのイベント処理に対するソースを何も記載していませんから。

 では次に、その処理のソースを記載しましょう。ただし、JSON の取得部分のソースもまだですので、何か別の変化が起こる様にしましょう。例えば、よくあるポップアップにしましょうか。

 

一応の説明をしておくと、JSX は return 文で DOM(HTML)を返します。従って、return 文の中に変数定義を記述することはできません。

例えば、JSX では、変数の定義に const を使用しますが、

 const weatherData = 'みんなの天気予報'

を return 文の中に記載してしまうと(20行目)、

 

下記の結果になります。

ですので、JSX で変数定義等を行う場合には、return 文の外に記載する必要があります。書き換えてみましょう。

6行目の return 文の外に、変数を定義しました。そして13行目に {weatherData} と記述しました。結果は?

の様に、6行目で定義した weatherData という変数の内容が、13行目の {weatherData} によって正常に表記されています。

 ※重要なことを記載していませんでした。JSX で定義した変数を、return 文内で使用する場合には、{  } で括って下さい。そうでないと変数名そのままが表記されてしまいます。

 前置きが長くなりました。それでは、ボタンクリックイベントを作成しましょう。

 イベントが発生した時の関数を作成しましょう。ポップアップでいいので、alert 文を使用します。前述のとおり、return 文の外(今回は前)で定義します。ついでに <button> タグにも、その関数を呼び出す様に JSX を記載します。

8~10、26行目に注目してください。

 関数定義 const buttonAlert = () => {  は独特の表記ですが、今は雰囲気だけ味わっておいて下さい。そのうち () 内に props とか入り始めますから。

8~10行 は関数定義、26行は button タグに、その関数を呼び出す様に記述しました。しつこい様ですが、return 内で JSX の関数名や変数名を使用する場合には、{  } で括ることを忘れないで下さい。b

では実行。ボタンをクリック!

あれ? 何ですかこの [object Object] とは?

ググるとー。JSON.stringify(変数名) で内容が取得できるらしい。では、

おおー。要するに連想配列(react だと何て言うんでしょう? => 連想配列でいいらしい。javascript だから当然ですか)だから key と data に分ければ良いようです。まあここは本質では無いのでこのまま放置します。(JSON.stringify(変数名) 関数はブラウザ依存なのであまり使用しない方が良いですね。今回は chrome を使用)
とりあえず、ボタンイベントが完成したので次へ進みます。

 

気象庁のサイトから気象情報を JSON 形式で取得

 とりあえず、気象庁のサイトをブラウザで叩いてみるとこうなります。

JSON データの羅列が取得できます。でも凄まじい量ですね。これを毎度毎度取得するのもリソースの無駄ですし、何といっても気象庁のサイトに負担をかけてしまいます。そこで、まず東京のみの情報を取得してみたいと思います。

https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json

これをブラウザで叩いてみてください。

ほうほう、東京都の情報が得られました。しかも、天気予報や気温などの情報も含まれているではないですか。

これを JSX から取得するには、Axios というライブラリを使用します。それでは実際に実装してみます。

Axios を使用するには、まず axios ライブラリを import する必要があります。下記の3行目。

すると、

エラーとなります。どうやら、Axios ライブラリはデフォルトでは実装されていないようです。ここで取る手段は2つ。npm を使用してライブラリをインポートするか、CDNを使用して外部からライブラリを引き込むかの二択です。Axios 自体は軽いライブラリですので(94k程度)npm を使用してインポートします。

下記を実行します。

 npm install axios(npm i axios という省略形もあります)

完了しました。

CDN を使用する場合には、/pubic/index.html の <body> タグ内に

<script type="text/babel">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" />
 

の様に 記載すればOKです。

 

さて、では Axios を実装していきましょう。

3行目で、Axios ライブラリをインポートしています。

9~14行目が、気象庁のデータにアクセスし、console.log で結果を出力する Req() 関数定義となります。axios.get() 関数でシンプルに HTTP の GET メソッドで気象庁JSON 情報を取得します。

※ console.log はデバッガに結果を出力するメソッドなので、実行前に console.clear で内容をクリアしています

18行目で Req() を呼び出します。

さて結果は?

ボタンを押しても何も変化は無いと思います。前述のとおり、console.log は画面上ではなく、デバッガに結果を出力するメソッドすので画面上には出力されません。ここで、デバッガを立ち上げます。ctrl + shift + I (i です、愛)すると、

デバッガが立ち上がります。デバッガの、上部にある console タブをクリックして下さい。

status は、HTTP のレスポンス状態を示しています。200 ですから正常に取得できていますね。では次に、取得したデータを見てみましょう。「▶ {data: ~ の ▶ をクリック」=> 「▶ {request: ~ の ▶ をクリック」=> 「」。responseText という項目がありますので、カーソルをそこにフォーカスすると、東京都全域の情報がテキスト表示されます。

これは、つまり気象庁が提供している JSON データを response 変数に格納されたことを意味します。デバッガでは、その内容を確認できた訳ですね。

response 変数に JSON データが格納されたことは確認できましたが、次にこの内容を展開して UI 上に表示する必要があります。それでは response 変数の展開を行っていきます。

 

<余談>

 コンポーネントの呼び出し方ですが、これまで App() の様に関数であるかのような表記を使ってきました。しかし jacascript では様々な呼び出し方があり、例えば下記の様な表記方法もあります。18行目を参照してください。

呼び出し方は様々です。本来は統一した方がデバッグも行いやすいと考えますが、人の書いたコードをメンテしたりする際に、色々な手法を知っておかないと困ることもありますので、「あーこんな表記方法もあるんだな」程度で良いと思いますので学んでおいて損は無いと思います。他にも import と require の関係など枚挙にいとまがありません。

</余談>

予め設定した地域の情報を選択

 まずは単純に気象庁JSON データにどの様な情報が含まれているのか、それをブラウザ上でわかりやすい表記を行えるようにしましょう。と、その前に VSCode 上で成形して見やすくしてみましょう。再度、気象庁のサイトをブラウザで叩いてみてください。JSON データがずらりと表記されます。これをすべてコピーしてください(全選択 ctrl + a して ctrl + c)。ここで VSCode へ戻り、「新規ファイル」=> 「新しいテキスト ファイル」(ctrl + n でもOKです)から新規ファイルを開きます。

テキスト内に、「言語の選択」とありますので、そこをクリック

「言語モードの選択」という入力画面が表示されますので、JSON と入力。

「{ } Json (json)」をクリック。

この画面に戻りますので、先ほどコピーした JSON データを貼り付け(ctrl + v)。

こんな感じで、一行にペーストされました。ここで、shift + alt + f と打ってください。

この様に可読性の良いフォーマットで JSON データが表示されます。ちなみに、このデータの頭は(テキストの適当な部分をクリックして、)、

 この様に北から情報が取得できることが分かると思います。"centers" タグは中央管区の情報を包括(のちほど別のタグも出没します)を表し、"name" 及び "enName" はそのエリア名、"officeName" は各エリアの管区気象台の日本語表記、"childlen" はその名の通り、そのエリアがどこの地域を統括しているかということ(ここでは単なる数字表記ですが)が何となく分かると思います。

fig.1

 さてここで、行数を示す箇所にカーソルを移動させると、下向きの鏃の様なマークが表記されます。2行目もそうですね。下向きということは、ここではすべての情報が展開されていることを意味しています。ここで、2行目の鏃をクリックしてみてください。

fig.2

"centers" タグが折りたたまり、139行目から始まる "offices" タグ情報が表記されました。ここで注視していただきたいのが、fig.1(唐突に図番を入れました)と、fig.2 の比較です。fig.2 では140行目にタグの無い、"011000" 表記があります。そして fig.1 では、8行目に同様に "011000" と記載されています。これはつまり、"centers" の "children" である "011000" の情報が、"offices" に書かれていると言うことを表してみます。つまり、

の配下に、

        "011000": {
            "name": "宗谷地方",
            "enName": "Soya",
            "officeName": "稚内地方気象台",
            "parent": "010100",
            "children": [
                "011000"
            ]
        },

この様に、宗谷地方が含まれていることが分かると思います*1

 

 と、ここまで来ましたが実はこのデータは各地域(行政区とは異なるかな?)のコード(さきほど確認した "011000" のことですね)を確認するために取得したものです。実は天気予報の JSON 情報は別にありまして、

 https://www.jma.go.jp/bosai/forecast/data/forecast/xxxxxx.json(xxxxxx が先ほど取得した地域コード)が実際の天気予報の情報となります。それでは、せっかく宗谷地方のコードが判明しましたので、そちらの天気予報を見てみましょう。

 https://www.jma.go.jp/bosai/forecast/data/forecast/011000.json

をブラウザで叩いてみてください。

 これを先ほどの手順で VSCode に張り付けて成形すると

 この様に、宗谷地方の天気予報らしき情報を得ることができました。これは使えそうですね。(東京の情報を取得するつもりが、宗谷地方になっていますけれど…)

 

 かなり話が外れてしまいましたが、それでは Axois を実装して宗谷地方の情報を取得してみましょう。

11行目から、16行目までが変更点です。実行し、デバッガを立ち上げます。そして適当に展開してみると、

 今日(2022/07/23現在)の宗谷地方は少し天気が悪そうです。
 さて、それでは各個別の情報を取得していきましょう。JSON入れ子になっており、多段化されていることが多いです。気象台のデータもかなり入り組んでいますね。そこで必要な要素のみを取得してみましょう。まず、地域コード "011000" の地名を取得します。下記の要領で展開して(一部展開を戻しました)、name が地域名となりますが相当深い位置にありますね。これを手繰るのは大変なので、name へカーソルを持っていき、右クリックすると下記のメニューが表示されます。ここで「Copy property path」を選択。パスがクリップボードに読み込まれます。これは後ほど使用します。

さて、VSCode に戻ります。

18行目に alert(response) と書き込み、実行してみると、

この様になります。これは以前も発生した現象ですね。つまり、内容が Object なので、そのままでは表示されないわけです。

そこで、コードを下記の様に書き換えて下さい。



18行目の「data[0].timeSeries[0].areas[0].area.name」とは何ぞや、と思われたかと。実はこれが先ほどクリップボードに読み込んでおいた、JSONname のパスに相当します。この様に深いネストでも、デバッガの「Copy property path」から読み取り、貼り付け(ctrl + v)で簡単に対応することができるわけです。実行してみると、

 この様に、地域コード "011000" の地名を取得することができました。天気予報も同時に表示したければ、下記の様に + で連結すれば表記されます。

alert に出力ばかりしても意味がありませんから、画面遷移を使って表示させてみます。

25行目、id 'root' 全体を書き換えました。

一応表示されました。ただ、画面遷移するのは気持ち悪いのでもう少し手を入れます。App.js の46 ~ 48行目に新たに、<div id='weather'>を差し込みます。

そして、

obj 変数内のコンテキストを、先ほどの id = 'weather' に差し込みます。さて結果は?

上図の様に、画面遷移無しに宗谷地方の気象情報が表示されました(2022/07/24現在)。

 

実はこのソースかなり適当に書かれています。悪いお手本の様なソースですね。JSX ライクな書き方ではありませんし、Axiosも正しく使用されていません(エラーハンドリング等)。それでは、次のページでは Axios の正しい使い方や、JSX ライクな記述法、最終目標である友人たちのエリアの天気予報を完成させたいと思っております。では。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

私が参照した、遅延勉強法の動画です。これ、私知らず知らずのうちにやってました。

www.youtube.com

 

*1:JSON に関しては、こちらのサイトの方が非常に分かり易く書かれていますので、ぜひ一度チェックなさってください

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を停止する

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