まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

Github API を使って簡易アプリケーションを作ってみました。その成果物を紹介すると同時に、Github API そのものについても少しずつ紹介する内容を書きたいと思ってブログエントリを書き始めています。アプリケーションの紹介までは長くなりそうなのでテーマを分割して、今回はとりあえず準備段階を含めた OAuth ログインを紹介します。


【もともとやりたかったこと】
もともとは「ファイルサーバー的なものを作りたい」と思っていました。ファイルサーバー自体はアプリケーション開発テーマとしては(わざわざ新たに作る必要があるものとも思わず、Box とかを使えばいいので)既に枯れたテーマだと思っていますが、ファイルのバージョン管理機能など細かな使い勝手を意識して実装しようとすると既存のものだけでは難しそうだと判断しました。その技術検討の中で(本来の目的とは違う使い方であることを理解した上で)Git のリポジトリをファイルサーバーとみなすとバージョン管理ははじめからついているし、個人ごとのフォルダも個人ごとにブランチを作ればいけそうだし、管理者は管理者用ブランチにマージすればまとめて見れるし、自分の希望に近いことを実現できるのではないか? と思いついたのでした。

要するにファイルサーバーのバックエンドとして Git を使い、そのバックエンド部分を API で読み書きするようなフロントエンドをアプリケーションとして実装すればよい、ということになります。実現するための Git の API として上述の Github API の存在を知ったことでなんかできそうな目処がたったので、実際に作って検証してみた、という経緯です。


【Github API を使う準備】
これから作るアプリケーションでは Github API を使うため、まずは Github に API を利用するアプリケーションを登録して、OAuth 認証用の各種 ID を取得したり、コールバック URL を設定しておく、という準備段階を済ませておく必要があります。以下、その手順を紹介します。

ウェブブラウザで Github にログインし、右上のアイコンメニューから Settings を選択します:
2021051601


Settings 画面の左で Developer settings を選択します:
2021051602


今回作るアプリケーションは Github に外部ログインするウェブアプリケーションです。この外部ログインを実現するため OAuth App として登録する必要があります。左メニューで OAuth Apps を選択して、"New OAuth App" ボタン(初めて登録する場合は "Register a new application" ボタン)をクリックします:
2021051603


アプリケーション名(適当)とアプリケーション URL (適当、http://localhost:8080/ など)を指定後、コールバック URL を指定します。この値は OAuth 認証後にリダイレクトする先の URL となり、アプリケーション側ではこの URL にアクセスされた際のパラメータを見て(API 実行時に必要な)アクセストークンを取得・保管する処理が必要になります。なので、アプリケーション毎にこの処理を行う URL を定義する必要がありますが、後述のサンプルアプリケーションでは "http://localhost:8080/api/callback" に GET リクエストがあった時にこの処理を行う想定で作っています。そのためアプリケーション URL には http://localhost:8080/api/callback と入力してください。他の値はオプションなので適当に入力し、最後に "Register Application" ボタンをクリックします:
2021051604


※注意点として、Github API ではこのコールバック URL は OAuth アプリケーションごとに1つだけしか登録できないようです。今回は localhost 環境で動かす想定でコールバック URL を http://localhost:8080/api/callback と登録しましたが、実際に公開するサービスとして利用する場合は、その本番サーバーのホスト名やポート番号を使って指定する必要があります。本番利用時にはこの値を書き換えるか、あるいは本番サーバー用の OAuth アプリケーションを新たに登録し、そこで取得した値(後述)を使ってアプリケーションを動かしてください。


アプリケーションの登録が完了した直後の画面に、外部アプリケーションからの認証時に指定する必要のある各種情報が表示されます(client_secret は "Generate a new client secret" ボタンをクリックすることで表示されます)。このうちの client_idclient_secret の値を(後で使うので)メモしておきます。なお、client_id の値は忘れてしまった場合でも改めて OAuth Apps 一覧から選択することで再び参照することができますが、client_secret の値はこの画面を閉じてしまうと2度と参照することができません(client_id ごとリセットして取得し直す必要があります)。間違えないように正しく記録を残しておくように気をつけてください:
2021051605


これで Github API を使うための、OAuth App を登録する手順が完了しました。次はこの OAuth App を実際に作って動かす段階となりますが、その前に Github API を使って操作する Github リポジトリを用意しておきます。


【Github API で操作するリポジトリを用意する】
後述のサンプルアプリケーションでは「指定した特定の Github リポジトリ内の main ブランチに属しているファイルの一覧を取得する」ことが可能な実装をしています。そのための「特定の Github リポジトリ」を用意します。

単にファイル一覧を読み取るだけなので(変更を加えるわけではないので)、既存の Github リポジトリがあればそれを使ってもいいし、新規に Github リポジトリを作成した上で指定しても構いません。今回は dotnsf/my_githubapi_test という動作確認用リポジトリを作り、この中の main ブランチのファイル一覧を取得するようなアプリケーションとして以下の説明を続けます(実際にはみなさんも独自のリポジトリを作って、README.md 他を main ブランチに入れておいてください):
2021051606


ただ一点注意が必要です。上述の client_id / client_secret を取得した時に使った Github ユーザー ID と同じ ID でログインする場合はこのままでいいのですが、別の Github ユーザー ID で使いたい場合や、友人など別の Github ユーザー ID からも同アプリケーションを使わせたい場合など、(client_id / client_secret を取得した時とは)別の Github ユーザーにもこのアプリケーションを使わせたい場合、そのユーザーが対象のリポジトリを読み取るための Collaborator 権限設定が必要になります。 その場合は同リポジトリのオーナーでブラウザログインし、対象リポジトリを開いてから Settings メニューを選択します:
2021051607


そして画面左から Manage Access メニューを選択し(パスワードを聞かれます)、画面右の Manage Access 内の "Invite a collaborator" ボタンをクリックします:
2021051608


そして後述のアプリケーションを使わせたい Github ユーザーを指定して "Add **** to this repository" ボタンをクリックします:
2021051609


すると指定されたユーザーに招待メールが送信され、メール内の "View invitations" リンクから遷移して accept することで該当リポジトリに対する Collaborator 権限が付与され、リポジトリのオーナー以外のユーザーでも操作できるようになります:
2021051601



ここまでの作業で外部アプリケーションからログインするための設定と、操作対象リポジトリの準備ができました。ではサンプルのアプリケーションを使って、実際に Github API が動作する様子を体験してみます。


【Github の OAuth を使って外部アプリケーションから Github にログインする】
Node.js を使って Github API を実際に動かすサンプルアプリケーションを用意しました。Node.js 導入環境を使って、こちらから git clone するかダウンロード&展開してください:
https://github.com/dotnsf/github_oauth_sample


ソースコード内の settings.js ファイルを編集して、上述の準備段階で集めた情報を指定します。git clone 後かダウンロード後、ローカルシステムにある同ファイルをテキストエディタで開き、以下のように値を入力します(青字はコメント):
//. settings.js
exports.client_id = 'xxxxxxx'; OAuth App 作成時に取得した client_id の値
exports.client_secret = 'xxxxxxx'; OAuth App 作成時に取得した client_secret の値
exports.callback_url = 'http://localhost:8080/api/callback'; OAuth App 作成時に指定したコールバック URL の値
exports.repo_name = 'dotnsf/my_githubapi_test';  操作対象リポジトリ名

この状態で npm install を指定して依存ライブラリを導入してから node app でアプリケーションを起動します:
$ npm install

$ node app

起動に成功すると、このサンプルアプリケーションは 8080 番ポートで HTTP リクエストを待ち受けます。ウェブブラウザで http://localhost:8080/ にアクセスします:
2021051601


最初はログイン前なので "login" ボタンが表示されています。このボタンをクリックすると Github API を使った OAuth ログインの処理がスタートします。一度もログインしたことがない場合は以下のような同意画面が表示されるので、ユーザー名を確認後に "Authorize ***" をクリックしてください:
E1ewH_vVkAAwHHE


すると再度 http://localhost:8080/ に転送されますが、今度はログイン後なのでユーザー情報を取得することができ、ログインした Github ユーザーのアバターアイコンやユーザー ID が画面右上に表示されます。このアイコンが自分の Github ユーザーアイコンであることを確認してください。このアイコンをクリックしてログアウトすることもでき、ログアウトすると再度 login ボタンが表示されます:
2021051602


とりあえず、Github API を使ったログイン処理を実装することができました。以下、アプリケーションのログイン部分の仕組みを解説します。


【Github の OAuth ログインの仕組み】
詳しくはサンプルアプリケーションのソースコード内 api/github.js を見ていただきたいのですが、正確には「ログイン認証の仕組み」というよりは「ログイン認証してアクセストークンを取得する仕組み」です。

まずログイン認証の仕組みは OAuth を使っています。アプリケーションで "login" ボタンをクリックすると、https://github.com/login/oauth/authorize にリダイレクトしています。その際に URL パラメータに付与する形で settings.js 内に記載した client_id の値と callback_url の値を指定しています:
router.get( '/login', function( req, res ){
  //. GitHub API V3
  //. https://docs.github.com/en/developers/apps/authorizing-oauth-apps
  res.redirect( 'https://github.com/login/oauth/authorize?client_id=' + settings.client_id + '&redirect_uri=' + settings.callback_url + '&scope=repo' );
});


このリダイレクト先で上述の Github の認証を行います:
E1ewH_vVkAAwHHE


ここで Authorize するとコールバック URL に転送されます。その際に一時的なアクセストークンが code パラメータに指定される形で送付されてくるので、これを取り出した上で client_id や client_secrent などの値と一緒に https://github.com/login/oauth/access_token に POST リクエストを発行します。その実行結果(form encoded形式)から access_token 変数としてアクセストークンを取り出すことができるので、(本サンプルアプリケーションではセッション内に記録する形で)保存して、この後の Github API 実行時に指定できるようにしています。その上で改めて GetMyInfo() 関数を実行してログイン情報を取得し(詳しくは次回に)トップページにリダイレクトしています:
router.get( '/callback', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );
  var code = req.query.code;
  var option = {
    url: 'https://github.com/login/oauth/access_token',
    form: { client_id: settings.client_id, client_secret: settings.client_secret, code: code, redirect_uri: settings.callback_url },
    method: 'POST'
  };
  request( option, async function( err, res0, body ){
    if( err ){
      console.log( { err } );
    }else{
      //. body = 'access_token=XXXXX&scope=YYYY&token_type=ZZZZ';
      var tmp1 = body.split( '&' );
      for( var i = 0; i < tmp1.length; i ++ ){
        var tmp2 = tmp1[i].split( '=' );
        if( tmp2.length == 2 && tmp2[0] == 'access_token' ){
          var access_token = tmp2[1];

          req.session.oauth = {};
          req.session.oauth.token = access_token;

          var r = await GetMyInfo( access_token );
          if( r ){
            req.session.oauth.id = r.id;
            req.session.oauth.login = r.login;
            req.session.oauth.name = r.name;
            req.session.oauth.email = r.email;
            req.session.oauth.avatar_url = r.avatar_url;
          }
        }
      }
    }
    //console.log( 'redirecting...' );
    res.redirect( '/' );
  });
});

トップページでは画面ロード直後に GET /api/isLoggedIn という API が実行されます。この API はセッション内にアクセストークンがあるかどうかを調べ、含まれていた場合はその情報(ログイン時に GetMyInfo() 関数によって取り出したユーザー情報)をレスポンスの値として返します。つまりログインしていない場合は false 値が、ログインしている場合はユーザー情報を返すという関数です。これによって、未ログイン時のトップページでは login ボタンを、ログイン時のトップページでは logout ボタンをそれぞれ表示するようにしています:
router.get( '/isLoggedIn', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );
  var status = false;
  if( req.session && req.session.oauth && req.session.oauth.token ){
    status = JSON.parse( JSON.stringify( ( req.session.oauth ) ) );
  }

  if( !status ){
    res.status( 400 );
    res.write( JSON.stringify( { status: false }, null, 2 ) );
  }else{
    res.write( JSON.stringify( { status: true, user: status }, null, 2 ) );
  }
  res.end();
});

なお、logout ボタンが押された場合はセッションの中身を空にする、という処理が実行されます。これによって取得したアクセストークンは無効になり、(再ログインによって)再びアクセストークンを取得するまで Github API は実行できなくなります:
router.post( '/logout', function( req, res ){
  if( req.session.oauth ){
    req.session.oauth = {};
  }
  res.contentType( 'application/json; charset=utf-8' );
  res.write( JSON.stringify( { status: true }, null, 2 ) );
  res.end();
});

と、Github API を使ったログイン時の処理内容を中心に紹介しました。次回は GetMyInfo() 関数でユーザー情報を取り出す仕組みと、ログイン後に main ブランチ内のファイル一覧を取り出す仕組みを紹介する予定です。


久しぶりの MySQL ネタです。

MySQL のプライベート DBaaS 的なものを作ろうとしています。要件としてはこんな感じ:
- 1回の操作で MySQL のデータベースと、そのデータベースを操作するユーザー(ID とパスワード)を新規作成する
- 1回の操作で上記データベースとユーザーを削除する

要は「データベースを使いたい」と思ったときに何か(スクリプトや API )を1度実行するとデータベースを作り、そのデータベースにアクセスできるユーザーIDとパスワードを生成する、というのが最初の要件です。2つ目の要件は同様にしてそのデータベースやユーザーを1回の操作でまとめて削除する、というものです。これができると稼働中の MySQL サーバーに対してリクエストイベントベースでデータベースやユーザーを作成/削除できるようになり、MySQL のプライベート DBaaS を安価に構築できるかな、と思っています。

で、それを実現するシェルスクリプトを作ってみたので公開します:
https://github.com/dotnsf/scripts_for_mysql

2021051101



【準備】
利用するには git clone などでスクリプトをダウンロードし、拡張子 .sh のファイルに実行権限をつけておきます:
$ git clone https://github.com/dotnsf/scripts_for_mysql

$ cd scripts_for_mysql

$ chmod +x *.sh

README.md にも記載していますが、(特に MySQL 5.7 以降の場合は)事前要件としてパスワードポリシーを LOW に設定しておく必要があります。今回作成したシェルスクリプトではデータベースとアクセスユーザーを同時に作成するのですが、データベース名とユーザー名は同じランダムな文字列を、パスワードは独自に生成する別のランダムな文字列を使って生成します。この組み合わせはランダムに生成されるのですが、場合によってはパスワード文字列が MySQL の(5.7 以降で厳しくなった)パスワードポリシーに合わないことが原因によるエラーが発生する可能性があります。このエラーを回避するため、標準状態のパスワードポリシー(MEDIUM)を LOW に変更しておく必要があるのでした。その手順は以下になります(MySQL の再起動は不要です):
$ mysql -u root -p

> show variables like 'validate_password%';

> set global validate_password.policy=LOW;

> quit

ここまでの準備ができていれば後述のシェルスクリプトを使ってデータベース/ユーザーの作成および削除がそれぞれ1回ずつのスクリプト実行で実現できます。


【実行方法】
データベースおよびユーザーを作成する場合は create_db.sh スクリプトを実行します。実行時に環境変数 MYSQL_ROOT_PASSWORD に MySQL の root ユーザーのパスワードを設定する必要があります。以下は同パスワードが P@ssw0rd である場合の設定例ですが、方法は以下3つのいずれでも:

(1)直接環境変数に指定してからスクリプトを実行する
$ export MYSQL_ROOT_PASSWORD=P@ssw0rd

$ ./create_db.sh

(2)スクリプト実行時に指定する
$ MYSQL_ROOT_PASSWORD=P@ssw0rd ./create_db.sh

(3)スクリプト内の変数に指定してから実行する
$ vi create_db.sh

:
:
# MySQL root Password
MYSQL_ROOT_PASSWORD=P@ssw0rd (行頭のコメント記号 # を削除してパスワードを指定)
:
:
$ ./create_db.sh

※プライベートな環境で使う場合は(3)でもいいと思います。

実行が成功すると以下のような結果が表示されます。上が作成されたデータベース名およびユーザー名、下がユーザーログイン時のパスワードです。パスワードはここで表示されたものを再度確認する手段がないので必ずメモしておきましょう。
$ MYSQL_ROOT_PASSWORD=P@ssw0rd ./create_db.sh

mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
USERNAME = DBNAME = 'f2fd22b01cefbe51'
PASSWORD = 'dY2fBxTmvMaTfuoM'

この例であればデータベース名 f2fd22b01cefbe51 、ユーザー名 f2fd22b01cefbe51 、パスワード dY2fBxTmvMaTfuoM が作成されているので、この情報を使ってアクセスできることを確認します:
$ mysql -u f2fd22b01cefbe51 -pdY2fBxTmvMaTfuoM f2fd22b01cefbe51

mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 8.0.24 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

作成したデータベースおよびユーザーを削除する場合はその名称(上記であれば f2fd22b01cefbe51)を指定して drop_db.sh を実行します:
$ MYSQL_ROOT_PASSWORD=P@ssw0rd ./drop_db.sh f2fd22b01cefbe51

mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
DB & USER: 'f2fd22b01cefbe51' DROPPED.

これでデータベースもユーザーも削除できました。


【応用】
基本的にはこれだけで最低限の機能は実装できていると思いますが、より便利に使うための応用方法をいくつか紹介しておきます。

まず現状はシェルスクリプトとしての実装なので外部システムから実行することはできません。REST API などにしておくと外部からも実行できて、その実行結果を HTTP レスポンスの形で返すこともできるのでより便利に使えるようになります。セキュリティ要件等も考慮した上で、問題ない場合はこちらのブログエントリを参考に REST API 化も検討ください。

また現在のスクリプトではローカルシステムからのみログイン可能なユーザーが作成されます。一方、DBaaS としての利用であれば外部システムからもログイン可能なユーザーを作成したいものです。外部からもログイン可能なユーザーを作成する場合は create_db.sh を以下のように変更してください:
   :
   :
# create database and user
mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "CREATE DATABASE ${USER} default character set utf8"
mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "CREATE USER ${USER}@'%' IDENTIFIED BY '${PASSWD}'"
mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "GRANT ALL PRIVILEGES ON ${USER}.* TO '${USER}'@'%'"
   :
   :

※ create_db.sh 内の 'localhost' と書かれていた青字部分2箇所を '%' に変更して保存してください

これで作成したユーザー情報を使うと、外部システムからでもデータベースへのログインが可能になります。こちらもセキュリティ要件と相談の上で変更してください。



MySQL はしばらくメインで使っていない間にセキュリティ要件が少し面倒になった気がしました。このブログエントリ内でも書いたパスワードポリシー、これを恒久的に無効にする方法って、今はないんですかね。。

簡易ブラウザゲームなど、HTML + 画像 + CSS + (クライアントサイド)JavaScript のみで作成できる静的ページの公開手段として Github ページを使っている人は少なくないと思っています。かくいう自分もその一人で、例えば以下のページはすべて自作した上で Github ページを使って公開している例です:
ブラウザ移植版 "MOLEMOLE"
マッチ棒を1本だけ動かして正しい式にする(を自動解答)
JavaScript + CSS + HTML オンラインプレビューワー


あらためて Github ページを簡単に紹介します。

ベースとなっている Github は無料で(も)使えるソースコードのオンラインバージョン管理システムです。個人的には 10 年前は全く別の Subversion というバージョン管理システムを自分で自宅サーバーにインストールして使っていました(一応まだ残ってますが、ほぼ使わなくなりました)が、この 10 年で状況ががらりと変わり、今ではほぼ Github (というか、Git を使ったシステム)がバージョン管理の主流となりました。 Github ページはこの Github の機能の1つとして提供されているもので、Git プロジェクト(の一部)のフォルダをまるごと HTML のドキュメントルートとして公開するというものです。いわば「Github の安定した HTTP サーバーを使って公開する無料のウェブページ」とも言えるものです。あくまで静的コンテンツしか公開できないため、データベースにデータを登録する機能などは使えませんが、CORS の設定や REST API 、AJAX などを併用することで外部ファイルや外部データベースからデータを取得して表示する、といった程度であれば実現できます。自分自身は上述のような簡易アプリケーションに加えて、ドキュメント類やランディングページを公開する際にも便利に使っています。


このように便利な Github ページですが、Github に登録した後は便利なのですが、登録前のコンテンツ作成時にちょっと使いにくいと感じることもありました。例えば以下のようなケースを考えてみます:
2021042901


↑リポジトリの説明に必要な README.md と、Github ページで表示する index.html を同じフォルダに配置しています(このルートフォルダを Github ページで公開する、という想定です)。

index.html 内では imgs/icon.png ファイルを参照しています。また外部 JavaScript である jQuery(https://code.jquery.com/jquery-2.2.4.min.js)の URL を指定して利用しています:
2021042902


ちなみに img/icon.png は「いらすとや」さまの「静かにしてください」のマークを使わせていただきました:
icon


index.html 自体は上述のように比較的シンプルな内容です。(ローカルファイルをフルパス指定して)ブラウザで表示すると以下のような内容が表示されるものです:
2021042903


ちなみに <body> 部はこれだけ:
<body>

<h1>GHP(GitHub Pages) サンプル</h1>

<img src="./imgs/icon.png"/>

</body>

このプロジェクトを(深く考えずに)Github ページで公開する場合の手順を紹介します。そんなに複雑な話ではなく、まず単純に Github にパブリックなプロジェクトを作り、このフォルダ内の全ファイルを同プロジェクトに git push します(以下は自分のリポジトリに push した際の例です):
$ git init

$ git add .

$ git commit -m 'first commit'

$ git branch -M main

$ git remote add origin https://github.com/dotnsf/ghp.git (ここは実際の URL を指定してください)

$ git push -u origin main


2021042904

 
これでプロジェクトのバージョン管理を Github で行えるようになりましたが、今回の目的はバージョン管理というよりも Github ページによるコンテンツ公開なので、もう1ステップ必要になります。同プロジェクトの "Settings" メニューから "Pages" を選び、公開方法(どのブランチのどのフォルダ以下をページとして公開するか)を指定します。今回の場合は main ブランチのルートディレクトリをそのまま公開するだけなので、以下のように指定して "Save" します:
2021042905


すると以下のような画面になり、このコンテンツが Github ページで公開されます(実際にインターネットからアクセスできるようになるまで1分弱かかるようです):
2021042906


表示されている URL にアクセスすると、ちゃんとコンテンツとして公開できていることを確認できます。(オリジナルドメインで GitHub ページを使う設定をしていなければ)HTTPS にも自動的に対応されていて、非常に便利な機能です:
2021042907


ここまでが Github ページの概要および使い方です。index.html の内容を更新したり、新しいファイル(ページ)を追加した場合でも同様にしてプロジェクトの更新ファイルを Github に登録(git push)するだけで公開されるコンテンツの内容も更新されます。静的コンテンツの公開目的には非常に便利なサービスですが、一方で細かな点ではありますが、この方法だと少し非効率に感じる点がないわけではありません。自分の場合は以下の2点で少し不便に感じています。

まず一点目として、ローカルでの動作確認時にブラウザでローカルファイルを開く必要がある、という点があります。上述でもローカルファイルの内容を参照する際には file:/// から始まる URL で(つまりブラウザのメニューや Ctrl + O で「ファイルを開く」を選択してから、目的のフォルダを探し、その中の index.html を指定して)開いていました。自分の場合は普段 Node.js を使ってウェブアプリケーションを作るのですが、その際の動作確認時は http://localhost:8080/ といったシンプルな URL でアプリケーションを開けるようにしています。この差もあって、動作確認するたびにこの「ファイルを開く」オペレーションをするのは少し面倒に感じてしまいます:
2021042903


もう一点は「外部ファイルのプロトコル」についてです。例えば index.html 内で jQuery を呼び出す部分は以下のように記載しています:
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

気になるのは赤字の部分、つまり目的のファイルを https 指定で呼び出しています。現実問題として https で指定できるファイルを https で呼び出すことにはセキュリティ面での問題はないとも言えます。

一方で、これまで Node.js でウェブアプリケーションを開発している中でこのような外部 JavaScript の呼び出しを行う必要がある際、http か https かを指定せずに記述することがあります。つまり普段は以下のように指定しています:
<script type="text/javascript" src="//code.jquery.com/jquery-2.2.4.min.js"></script>

この記述方法だと表示するページ(今回の例だと index.html)を呼び出す際に使うプロトコルと同じプロトコルで jquery ファイルを呼び出すことになります。つまりローカルでの動作確認時など http://localhost:8080/ で呼び出している場合は http プロトコルで、実際に Github ページとして https://dotnsf.github.io/ghp/ で呼び出している場合は https プロトコルで、それぞれ呼び出すことになり、外部ファイル呼び出しもアクセス時のセキュリティレベルに合わせて行うことができるようになっています。

ただこの方法はローカルファイルを直接呼び出す際の file プロトコルでアクセスした場合は正しく読み込むことができません(file:///code.jquery.com/jquery-2.2.4.min.js は存在しないため)。ローカルファイルを呼び出す場合はプロトコルを省略せずに明記する必要があり、http か https のいずれかを指定して記述する必要があるのでした。

つまり、Github ページで運用する際には index.html 内の外部ファイル呼び出し部でプロトコルまで指定せずに記述することができるのに、動作確認時はプロトコルを指定して呼び出す必要がある。ということになります。これは非常に面倒な話で動作確認が終わったらプロトコル記述を削除する(Github ページを更新した後に、更にまた手元のファイルを変更する必要が生じた場合はもとに戻す)という運用は面倒だし、だからといってそのためだけにプロトコルを固定して記述しなければならないのだとしたら筋の違う話だと思っています。ローカルで常に HTTP サーバーを動かして、対象フォルダをドキュメントルートにしておけば、動作確認時も http://localhost:8080/ のようにアクセスできるようにはなりますが、それも本末転倒な話です。なんとかして Github ページを作る際もローカル動作確認時だけ HTTP サーバーを使って動作させることができないものか・・・


・・・と、長い前振りでしたが、ここからが本題です。アプリケーション・サーバーや HTTP サーバーを併用しないと解決が難しそうで、かつ Github ページ利用時に余計なコードが公開されないようにしたい、という上述の問題を解決する方法を紹介します。以下に紹介する方法は Node.js + Express を使った方法で、どちらかというとサーバーサイド JavaScript のアプリケーションエンジニア向けですが、言語環境によっては同様のことができる別方法(というか別言語環境)があるかもしれません。

答としてはプロジェクトを以下のようなファイル構成に変更します:
2021043001


Github ページで公開していたコンテンツ関連ファイル(今回の例では index.html と imgs/icon.png)を新たに作成した docs/ フォルダ内に移動しました。プロジェクトのルートフォルダには README.md を残した上で、新たに .gitignore, app.js, package.json ファイルを追加しています。以下、追加したファイルの内容を紹介します。

.gitignore ファイルは一般的なもので、Node.js では node_modules/ フォルダを Git 連携の対象外フォルダとすることが一般的です。というわけで、今回は以下の内容の .gitignore ファイルを使います:
node_modules/
package-lock.json
!.gitkeep

package.json ファイルも特殊なものではなく普通の package.json ファイルです。後述の app.js が express パッケージのみ利用するものなので、このような内容にしました:
{
    "name": "ghp",
    "version": "0.0.1",
    "scripts": {
        "start": "node app.js"
    },
    "dependencies": {
        "express": "^4.17.1"
    },
    "repository": {},
    "engines": {
        "node": "10.x"
    }
}

そして Node.js のメインファイルとも言える app.js 、これも実はごくシンプルな内容のものです:
//. app.js
var express = require( 'express' ),
    app = express();

app.use( express.static( __dirname + '/docs' ) );

var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );

特筆というほどのことはないのですが、肝といえるのは赤字部分です。Express を使ってウェブアプリケーション内のスタティックコンテンツが含まれるフォルダを docs/ フォルダに指定しています。これにより docs/ フォルダ以下にあるファイルはアプリケーションのルートパスから相対パスで表示できるようになります。つまりブラウザなどのクライアントから ./index.html と指定すると ./docs/index.html ファイルが、./imgs/icon.png と指定すると ./docs/imgs/icon.png ファイルを参照することができるようになります。つまり Github ページで公開するのとほぼ同じ状態をローカルホストで作ることができるようになります。

実際にローカルホストで動作確認用に動かす場合は、Node.js インストール後に以下のコマンドを実行します。特にオプションを指定しない場合は 8080 番ポートで HTTP サーバーが起動します:
$ npm install

$ node app
server starting on 8080 ...

8080 番以外の番号で待ち受けたい場合は起動時に環境変数 PORT に待受ポート番号を指定します(以下は 8081 番で待ち受ける場合):
$ PORT=8081 node app
server starting on 8081 ...

起動後、localhost の待受ポート番号を指定してブラウザでアクセスすれば(Ctrl + O で index.html ファイルを指定して開かなくても)目的のページを開くことができます:
2021050101


この状態であれば file プロトコルではなく http(s) プロトコルを使っているので、 docs/index.html 内の外部 JavaScript 参照部分でのプロトコルも明示する必要がなく、以下のような記述でも(ローカルでの動作確認時も、Github ページでの利用時も)動作するようになります。ローカルでの開発時と Github ページで公開する時の差を意識する必要もなくなりました:
<script type="text/javascript" src="//code.jquery.com/jquery-2.2.4.min.js"></script>

最後にこの変更を Github ページでも反映する必要があります(ルートディレクトリを Github ページとして公開している設定だと index.html ファイルが開かないので)。まずは更新した内容を Github リポジトリにもコミット&プッシュして反映させます:
$ git add . -A

$ git commit -m 'node.js 対応'

$ git push origin main

次に Github ページの設定を変更し、/(ルートフォルダ)ではなく /docs/ フォルダ以下を Github ページとして公開するように変更して Save します:
2021050102


少し待つとこの変更が Github ページにも反映され、先程と同じ URL で同様のページを参照することができるようになります:
2021042907


この状態ができてしまえば、手元でページを編集するときは docs/ フォルダ以下のファイルを対象に変更を加えて、必要に応じて `$ node app` でローカルの HTTP サーバーでコンテンツの出来を確認し、Github にコミット&プッシュすれば自動的に Github ページにも更新が反映されるようになります。

もうちょっとスマートなやり方があるのかもしれませんが、Node.js + Express であればこのように「Express の静的コンテンツフォルダを docs/ にする」+「Github ページの対象フォルダを docs/ 以下にする」という設定を組み合わせることで面倒からの開放は実現できそうでした。


このページのトップヘ