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

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

最近のブログでは独自ドメインを扱うネタが多くなっています(これとか、これとか)。この背景としては旧 Google Apps(現 Google Workspace)の無料版が廃止になって、猶予期間の終了が迫り、これまで取得したドメインの運用を Google Apps に任せていたことに起因して、無料または安価な代替運用先を探していることにありました。ただ(自分的に優先順位の高かった)独自ドメインのメールサーバーについてはある程度対応できたと思っています。

上述のように独自ドメインのメール環境についてはある程度解決しました。次は独自ドメインウェブサイトの問題です。独自ドメインのウェブサイトやウェブサービスを Google Apps を使わずに用意する方法です。まあ、ホスト名だけであれば DNS の CNAME 設定だけでなんとかなるのですが、問題になるのは SSL を使いたいケース(つまり https で始まる URL で独自ドメインのウェブサイトを公開したいケース)です。

これも手間を惜しまないのであれば、Let's Encrypt などで独自ドメインの証明書を無料で取得・更新できます。その証明書を使ってサービスを公開すれば https 対応はできます。ただ、これはこれで(アプリケーション側で証明書を意識するのも)面倒だし、もし証明書の更新を忘れてしまうとサービスが見えなくなってしまったり、証明書を更新した際の再起動なども行う必要があるので、手順としても(コマンドラインで作業するなど)面倒だったりします。独自ドメインのウェブサイトを運用する上でここを(できれば無料、または安価で)どうにかできないだろうか、という課題があります。

そして本ブログエントリはこの部分を
 ・アプリケーションは無料の Heroku で公開して、
 ・無料の Cloudflare で SSL 対応した独自ドメイン名でアクセスできるようにする
という構成を実現するための手順を紹介します。


【アプリケーションを Heroku で公開する】
対象のウェブアプリケーションを Heroku で公開します。アカウントを所有していない場合はサインアップします。

ここでは特別な設定はほぼ不要ですが、1点だけ。Heroku で独自ドメインを使うには、有料版を利用するか、クレジットカードを登録する形で無料枠を使う必要がある点に注意してください。Heroku 自体はクレジットカードを登録せずに無料範囲内で利用することができ、その範囲内でウェブアプリケーションを公開することもできますが、この後独自ドメインを使う場合は無料で使う場合であってもクレジットカードを登録する必要がある、という意味です。

Heroku は GitHub と連携してアプリケーションをデプロイしますが、ここを CLI で手動で行ってもいいし、Git コミットをハンドリングして自動デプロイにしても構いません。いずれかの方法でアプリケーションを Heroku にデプロイしてください。なお後者の方法については過去にこのブログでも紹介したことがあるので、よかったら参考にしてください:
Heroku のパイプラインで GitHub コミット時に自動デプロイする


以下では dotnsf-www.herokuapp.com というホスト名で Heroku 上でアプリケーションが稼働しているものとして説明を続けます。Heroku 上で独自ドメイン向けの設定を行う必要もありますが、 Cloudflare 側での設定をまず先に済ませてから Heroku 上での設定を行います。


【Cloudflare で独自ドメインの設定をする】
次に Cloudflare 側の設定を行います。アカウントを取得していない場合は Cloudflare の無料プランでサインアップし、所有している独自ドメインを登録します。なお以下では独自ドメイン(pi314.jp というドメインを使う例を紹介します)は取得済みで、そのドメインの DNS 管理を Cloudflare にさせる前提で紹介します。

独自ドメインを登録した後、まずは "SSL" のメニューを選択して、「暗号化モード」が「フル」に設定されていることを確認します。もし異なる設定になっている場合は「フル」に変更します。この設定にすることで利用者のブラウザから、Cloudflare を経由して(今回の場合は Heroku 上の)アプリケーション・サーバーまでの全ての経路の通信を暗号化することができるようになります。また独自ドメインの SSL 証明書の更新も自動化されます:
2022040801


そして "DNS" メニューを選択して DNS の設定を行います。今回は上述の dotnsf-www.herokuapp.com というサーバーを www.pi314.jp という名前で公開できるよう設定します。「レコードを追加」と書かれたボタンをクリックします:
2022040802


追加するレコードは以下のようにして「保存」ボタンをクリックします:
 タイプ: CNAME
 名前: www (www.pi314.jp という名前で公開する、という設定です)
 ターゲット: dotnsf-www.herokuapp.com
 プロキシ: ON(プロキシ済み、と表示されます)
2022040803


入力した内容が DNS 画面に表示されるようになります。これで https://www.pi314.jp/ という URL にアクセスがあると、https://dotnsf-www.herokuapp.com/ に(利用者から見えない所で)転送されてページが表示できるようになります。また Cloudflare を使っているので、セキュリティプロキシも有効になっています:
2022040804



【heroku 側のアプリケーションを再設定する】
これでアプリケーションの独自ドメイン利用ができる・・・わけではありません。最後に SSL を使えるように実際には dotnsf-www.herokuapp.com 上で動いているアプリケーションが www.pi314.jp というホスト名でアクセスされる可能性があることを予め認識させておく必要があります。そのためには Heroku の CLI を使って(CLI でログイン後に)以下のコマンドを実行※します:
$ heroku domains:add www.pi314.jp --app dotnsf-www

※このコマンドを実行するには heroku を有料サービスで使うか、または無料利用であってもクレジットカードを登録しておく必要があります。


このコマンドを実行後、しばらく(数分)待ってからウェブブラウザで https://www.pi314.jp/ にアクセスすると、https://dotnsf-www.herokuapp.com/ の画面が https://www.pi314.jp/ というホスト名で表示できることが確認できます。


以上、Heroku も Cloudflare も有償サービスを使わないのであれば、この方法でも無料の範囲内で独自ドメインでウェブアプリケーションを公開することができそうです。



タイトルの通りです。例えば以下のような独自定義関数があったとします:
// n 未満の素数を配列で取得する関数
function pnum( n ){
  var results = [];

  for( var i = 2; i < n; i ++ ){
    var b = true;
    for( var j = 2; j < i && b; j ++ ){
      b = i % j;
    }
    if( b ){
      results.push( i );
    }
  }

  return results;
}

関数名は pnum() 、パラメータを1つ受け取り、その値未満の素数配列を返す、という内容です。関数の処理内容はもっと複雑でも構いません。

例えば上記内容を pnum.js というファイルで保存した場合、同じフォルダにある HTML ファイルから以下のように使うことができます:
<script src="./pnum.js"></script>
<script>
var results = pnum( 1000 );  // 1000 未満の素数の配列
</script>

では同じファイルの同じ関数を Node.js からも使いたくなった場合はどうすればいいでしょう? あるいは逆に Node.js から require や import で使える JavaScript ファイルをブラウザから読み込んで使いたい場合はどうすればいいでしょう? これが今日のブログテーマです。


答えはシンプルなんですが、まず Node.js から使う場合は、このようにクラス化して、クラスを export するのが定番的なやり方だと思っています:
class MyClass {
  constructor(){
  }

  pnum( n ){
    var results = [];

    for( var i = 2; i < n; i ++ ){
      var b = true;
      for( var j = 2; j < i && b; j ++ ){
        b = i % j;
      }
      if( b ){
        results.push( i );
      }
    }

    return results;
  }
}

module.exports = MyClass;

この上の内容を myclass.js という名前で保存したとすると、Node.js の呼び出し元からは以下のようにして利用することができます:
var MyClass = require( './myclass' );
var myClass = new MyClass(); var results = myClass.pnum( 1000 );

では同じ myclass.js をブラウザの JavaScript から <script> タグで使えばいいのでは・・・ と思いますが、その場合は myclass.js の最終行である module.exports = MyClass; の所でエラーになります(module が定義されていないからです)。

そこでこのエラーを回避するために以下のようにします。module が object として定義済みである場合のみ最終行を実行するようにします:
class MyClass {
  constructor(){
  }

  pnum( n ){
    var results = [];

    for( var i = 2; i < n; i ++ ){
      var b = true;
      for( var j = 2; j < i && b; j ++ ){
        b = i % j;
      }
      if( b ){
        results.push( i );
      }
    }

    return results;
  }
}

if( typeof module === 'object' ){
  module.exports = MyClass;
}

こうしておくとブラウザ側も Node.js と同様に実行することができます:
<script src="./myclass.js"></script>
<script>
var myClass = new MyClass();
var results = myClass.pnum( 1000 );
</script>

要は「クラス化して、module が object として定義済みであればクラスをエクスポート指定」することで Node.js からもブラウザ JavaScript からも共通利用できるようになります。




 

JavaScript で乱数を扱う際の話です。

「乱数」はその名前の通り「事前に予測できないランダムな値」のことや、その生成の仕組みのことを呼びます。JavaScript にも乱数を生成する機能は標準で用意されており、Math.random() という関数を実行することで 0 から 1 の間のランダムな小数を取得することができます。

もし0から1の間の小数の乱数ではなく、「10以上20未満のランダムな整数の乱数」が必要な場合は以下のように Math.floor() (小数部分を切り捨てて整数化する関数)と組み合わせることで目的の乱数を得ることができます:
Math.floor( Math.random() * 10 ) + 10

この JavaScript の乱数は標準機能として備わっていて、すぐに使えるという点では便利です。しかしシード(seed)に対応していない、という、場合によっては困る点があります。

シードは乱数を初期化する時に指定する値です。同じシードで初期化した乱数システムは同じ乱数を返すようになります。例えば1という値をシードに指定して乱数を初期化して3回乱数を発生させたとします(発生した値を a1, a2, a3 とします)。別の機会に同じ1をシードに指定して乱数を初期化してから3回乱数を発生させて、それらの値をそれぞれ b1, b2, b3 とします。この時、同じシードを指定してから実行しているので、a1 = b1, a2 = b2, a3 = b3 が成立します。a1 も a2 も a3 も乱数なので事前に予測することはできませんが、同じシードを指定して実行したのであれば b1, b2, b3 がそれぞれ a1, a2, a3 と同じ値が取得できることが事前に保障されます。このように再現性のある乱数を発生させる仕組みが必要になることがある※のですが、JavaScript の Math.random() はこのシードには対応していない、という問題があります。

※例えば「ウェブページ内に1日ごとにランダムな画像を表示する(同じ日にアクセスした場合は同じ画像が表示される)」という仕組みを作ろうとした場合、本当にランダムな関数を使って表示画像を選ぼうとすると、アクセスするたびに異なる画像が選ばれてしまうことになります。 一方、シードに対応した乱数であれば、例えば日付からタイムスタンプ値を取るなどして数値化し、その数値をシードに指定して乱数を1個取得すれば、その値は事前に予測はできませんが(同じシードで初期化しているので)同じ日に実行していれば同じ値になります。ということは同じ画像を選んで表示することができるようになる、というものです。

で、このような再現性のある乱数を JavaScript で実現するにはどうすればよいか? という問題です。答としては「自分で用意する」ことになります。例えば以下のような感じ(こちらの記事を参考にしています):

class Random {
  constructor(seed = 19681106) {
    this.x = 31415926535;
    this.y = 8979323846;
    this.z = 2643383279;
    this.w = seed;
  }
  
  // XorShift
  next() {
    let t;
 
    t = this.x ^ (this.x << 11);
    this.x = this.y; this.y = this.z; this.z = this.w;
    return this.w = (this.w ^ (this.w >>> 19)) ^ (t ^ (t >>> 8)); 
  }
  
  // min以上max以下の乱数を生成する
  nextInt(min, max) {
    const r = Math.abs(this.next());
    return min + (r % (max + 1 - min));
  }
}

上述のようなクラスを事前に定義しておきます(x, y, z の値は自由に変更してかまいません。ちなみに↑の例は円周率の最初の31桁を使っています)。その上で以下のように使います:
  //. 今日の日付
  var dt = new Date();
  var y = dt.getFullYear();
  var m = dt.getMonth() + 1;
  m = ( ( m < 10 ) ? '0' : '' ) + m;
  var d = dt.getDate();
  d = ( ( d < 10 ) ? '0' : '' ) + d;

  //. 今日の午前零時のタイムスタンプをシードとして取得
  dt = new Date( y + '-' + m + '-' + d + ' 00:00:00' );
  var seed = dt.getTime();

  //. 今日の午前零時のタイムスタンプをシードに関数を初期化
  var random = new Random( seed );

  //. 0以上100未満の乱数を発生させる
  var value = random.nextInt( 0, 100 );

(かなり無理やり感ありますが・・)このように JavaScript を記述すると日が変わるまでの間は同じシード値を使って初期化することになります。したがって最後の行で乱数を発生させていますが、ここで取得する乱数値は同じ日に実行する間は同じ結果になる、というものです。

このページのトップヘ