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

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

2021/04

このブログエントリの続きのような、関係ないような内容です:
IBM COBOL for Linux 1.1 を使ってみた

もともとは「過去の COBOL 資産をウェブやクラウド環境で活用するにはどうすればいい?」かを考えていて、最初は CGI 化してウェブからうまく呼び出して使えないか、、、と検討していたのですが、「活用」と呼ぶにはそのための準備がなかなかに面倒そうで頓挫していました。要は COBOL エンジニアが少なくなってメンテナンスも難しくなっている昨今、COBOL 資産をどうやって今の環境で活用するか、というテーマは実際に検討してもキレイな答を見つけるのが難しいことを再認識する結果となっています。

といいつつ、「難しいですね~」で終わらせるのもシャクなので自分なりの答を用意しました。それがブログタイトルでもある Node.js の child_process を使う方法です。


Node.js + Express で普通にウェブアプリを作りつつ、COBOL 資産をサブルーチンのように(子プロセスとして)呼び出して動かし、その実行結果をウェブのレスポンスとする、という方法です。その子プロセスとしてコマンドを動かす際に child_process を利用します。ぶっちゃけ COBOL 資産でなくても応用の効く泥臭い方法ですが、コマンドラインパラメータを受け取って動き、結果を stdout へ出力する一般的なタイプのバイナリであれば再コンパイル無しで使えるという大きなメリットのあるウェブ化手法といえます。

言葉で説明するよりも実際のコードを見ていただくのが早いと思っています。まず COBOL 資産(資産といえる代物じゃないけど)として先日紹介した COBOL 版 Hello World を使います。特にこのファイルは x86_64 Linux 向けの実行可能バイナリになっているので、ダウンロードして `$ chmod +x hello` するだけで動きます(`$ ./hello` で "Hello World!" と出力するだけですが)。とりあえず、これを COBOL 資産とみなすことにします。


そしてこのコマンドを呼び出してレスポンスを返すウェブアプリケーションを Node.js + Express で作ります:
https://github.com/dotnsf/hello-cobol-web


アプリケーションの実体となる app.js の中は以下のようになっています:
//. app.js
var express = require( 'express' ),
    { exec } = require( 'child_process' ),
    app = express();
var command = '/home/dotnsf/src/hello/hello';

app.use( express.Router() );

app.get( '/', function( req, res ){
  res.contentType( 'text/plain; charset=utf-8' );
  
  exec( command, function( err, result, stderr ){
    if( err ){
      res.status( 400 );
      res.write( err.message );
      res.end();
    }else if( stderr ){
      res.status( 400 );
      res.write( stderr );
      res.end();
    }else{
      res.write( result );
      res.end();
    }
  });
});

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

まず赤字部分で child_process ライブラリを呼び出し、シェルコマンドを実行する準備をしています。次に緑字部分で実際に呼び出すコマンド(この場合は上記 hello コマンド)をフルパスで指定します(コマンドラインパラメータが必要な場合はこのコマンド文字列に含めておきます)。

そして実際にコマンドを呼び出しているのが青字部分です。GET / の HTTP リクエストを受けたら hello コマンドを(JavaScript らしく)非同期に実行し、その実行時や実行結果にエラーがあった場合は HTTP ステータスコード 400 で、なかった場合は(デフォルトの)HTTP ステータスコード 200 でそれぞれの結果を text/plain で返すコードになっています。

上述のコードを Node.js で実行し、GET / を実行すると(hello コマンドのフルパスやパーミッションを間違えていなければ)COBOL 資産の実行結果が HTTP の結果として "Hello World!" と表示されるはずです:
2021042601


専用コマンドのインストール部分が、特に有償ツールなどの場合だとコンテナ化までは難しいかもしれません。ただ COBOL 資産に限らず、形態素解析エンジンのような専用インストールが必要なコマンド・アプリケーションをウェブ化するようなケースでも応用できる便利な方法の紹介でした。

IBM COBOL for Linux 1.1 がリリースされたので、入手できる立場を利用して(笑)使ってみました。


このソフトウェアを使える権利をお持ちの方は IBM のダウンロードページで "IBM COBOL for Linux" を検索すると見つかります。ちなみに x86_64 の RHEL 7 or 8 または Ubuntu 16 or 18 or 20 の LTS リリースがサポートプラットフォームとされています。インストーラーを含む実行バイナリは CC7XYML で検索して見つかるものです:
2021042000


実行バイナリ(COBOL_LINUX86_6.1.tar.gz)を展開すると以下のようなファイルが入手できます。この中の install というファイルがインストーラーです:
2021042001


この install を実行すると実行環境に合わせた標準インストールが実行されます。インストール先フォルダの指定などはできないのですが、インストール時の前提ライブラリなども合わせて導入されるため非常に簡単にインストールできます:
2021042002


RHEL で実行した場合は足りない前提ライブラリが yum でインストールされます:
2021042003


インストールが成功すると "Installation and configuration successful" というメッセージが表示されます。標準インストールの場合、/opt/ibm/cobol/1.1.0/ 以下にモジュールが格納されます:
2021042004


実際に COBOL のプログラムを作ってコンパイルしてみます。とりあえずは Hello World をみようみまねで・・・
2021042005


そしてコンパイラ(/opt/ibm/cobol/1.1.0/usr/bin/cob2)でコンパイル・・・・
2021042006


・・・エラー!?
2021042007


(知らなかったのですが)COBOL は行のどの位置に記述するかによってコードの意味が変わるらしいです。この例では DISPLAY 命令を書く位置を(「タブ=スペース2」派の僕が勝手にインデント数を減らして)変えてしまったので、その意味合いが変わってしまったことが原因のようでした。というわけで、正しい位置に変更:
2021042008


そして再コンパイル、今度は成功しました:
2021042009


そして実行。ちと躓きましたが(苦笑)、無事に Hello World を表示できました:
2021042010


では今度はこいつを CGI 化してウェブブラウザから・・・と考えていたのですが、調べていて不毛な気がしてきて(苦笑)、もう少し別の形でウェブ対応する方向を考えます。できたらここで紹介する予定です。

(2021/04/26 追記)
上述の COBOL 版 Hello World を公開しました:
https://github.com/dotnsf/hello-cobol



タイトルそのままです。なんらかの画像をコピーしてクリップボードに格納された状態から、ブラウザ画面内の <canvas> 要素内に画像データをペーストして表示する、という処理が実現できないか試してみました。

結論としてはなんとなく実現できていると思います。サンプルを公開しているのでまずは挙動を試してみてください。


PC でもスマホでも、まず対象の画像をコピーします。今回は「いらすとや」さまのこの画像を使って試してみることにします:
https://www.irasutoya.com/2021/04/blog-post_12.html

2021041402


まずは画像をコピーします。PC の場合は右クリックから「コピー」、スマホの場合は対象画像を長押しするとコピーできると思います:
2021041403


その後、こちらのページを開きます:
https://dotnsf.github.io/web_image_paste/

2021041401



表示された画面内の「ペースト」と書かれた箇所にカーソルを移動した上でペースト(貼り付け)してください(スマホの場合は「ペースト」と書かれた箇所を長押ししてペーストを選択してください):
2021041404


うまくいくと最初にコピーした画像がブラウザ画面内の矩形部分(<canvas>)のサイズに合わせてペーストされます:
2021041405


このサンプルのソースコードはこちらで公開しています:
https://github.com/dotnsf/web_image_paste

このソースコードの中で特に該当の機能を実現しているファイルが index.html です。以下、解説を加えながらこのファイルの内容を紹介します。

まず該当部分の HTML は以下のようになっています:
<div class="container">
  <div id="canvas_div">
    <div id="cdiv">
      <div id="box" contenteditable="true">
        ペースト
      </div>
      <canvas width="80%" height="60%" id="mycanvas"></canvas>
    </div>
  </div>
</div>

画面内の「ペースト」と書かれている部分の <div id="box"> 要素に contenteditable="true" という属性がついています。これによって、この要素部分はペースト可能(Ctrl+V や右クリックメニューでペーストできる)として扱うことができるようになります。単にこの部分に画像をペーストして表示できるようにするだけであれば、この HTML だけで実現できます。

問題はここでペースト処理された画像を、この <div id="box"> 内に表示するのではなく(そのままだと <div id="box"> 内に画像がペーストされて表示されてしまうので、表示しないような処理を加えた上で)代わりにすぐ下の <canvas> 要素内に表示したい、という点です。

今回のサンプルではそういった処理は JavaScript で実現しています。まず「ペースト」と書かれた <div id="box"> 要素にペースト処理が実行されたイベントをフックして、imagePaste() 関数(後述)を実行するように指示しています。加えて false を返すことでフックしたイベントをキャンセルし、通常処理(この場合は <div id="box"> へのペースト処理)が実行されないようにしています(false を返さないと、<canvas> に画像をペーストした後でも <div id="box"> 内にも画像が残ってしまうので、それを避けるための処理です):
$(function(){
  $('#box').on( "paste", function( e ){
    imagePaste( e );
    return false;
  });

  :
  :

そして imagePaste() 関数の実装がこちらです:
function imagePaste( event ){
  var blobimg = null;
  var items = ( event.clipboardData || event.originalEvent.clipboardData ).items;
  for( var i = 0; i < items.length; i ++ ){
    if( items[i].type.indexOf( "image" ) == 0 ){
      blobimg = items[i].getAsFile();
    }
  }

  if( blobimg != null ){
    var bloburl = URL.createObjectURL( blobimg );

    var canvas = document.getElementById( "mycanvas" );
    var ctx = canvas.getContext( '2d' );

    var img = new Image();
    img.src = bloburl;
    img.onload = function(){
      var sw = img.naturalWidth;
      var sh = img.naturalHeight;
      var dw = canvas.width;
      var dh = canvas.height;
      ctx.drawImage( img, 0, 0, sw, sh, 0, 0, dw, dh );
    };
  }
}

まずクリップボード内に登録されているデータを配列で取得し、その中に画像("image")が含まれているかどうかを調べます。存在している場合はそのバイナリデータを取得します。

このバイナリデータが見つかった場合は URL.createObjectURL で画像データの URL を生成して画像化し、<canvas> 内に drawImage() 関数を使って描画します。その際に画像全体の高さや幅を <canvas> 全体の高さや幅に調整して表示するので、それらの情報を取得した上でパラメータ指定しています。

これによって「ペースト」のエリアにペーストした画像データを <canvas> 内に描画し、もとの「ペースト」のエリアには描画しない、という処理が実現できました。


本当は「ペースト」のためのエリアを使わずに <canvas> だけでここに画像を直接ペーストできるようになるのが理想なんですが、実現の可否含めてその方法がわかっていません。実現方法のヒントがありましたら教えていただけると嬉しいです。

 

Github を頻繁に使っていると、この図を頻繁に目にすることになります:
2021041001


"Contributions" というタイトルが付けられたこの図は Github への貢献を可視化したもので、自分のプロファイルページに表示されます。何をもって「貢献」とみなすか?についてはこちらのページを参照していただきたいのですが、簡単に言うと「コミット」のことのようです。自分のリポジトリに対するコミットの回数が日毎にカウントされ、コミットがなかった日はグレーで、コミットがあった日はその回数に応じて緑が濃くなって、およそ過去1年分が表示されているようでした:
2021041002
(↑自分は 2020/11/05 に 12 回コミットしていたらしい様子。。)


ちなみに縦1列が1週間を意味しています。一番上が日曜日、一番下が6日後の土曜日で、縦7データです。これが一年分あるので横幅は50強のデータがあります。この画面をキャンバスとみなして絵や文字列メッセージを表示できたら・・・というのが当初のアイデアでしたが、1年もの間、コミットの回数をこのためだけに調整するのは難しい。。

コミットの回数はコミットの粒度にも関係しているので、短絡的に「回数が多い=より貢献している」とは言い切れない部分もありますが、一方でこういったゲーミフィケーション的な視覚化はモチベーションにも繋がると思っていて、視覚化の UI 含めてよく考えられていると思っています。


・・・で、自分でもよくわからないのですが、たまに狂気的なアプリアイデアが浮かぶことがあります。今回もそれに近い感じで、「この Github Contributions 図を自分でも作ってみたい」「自由な位置に色を配置してデジタルサインのように見えたら面白そう」といった不思議な欲求が生まれて、そこから味付けしたサービスを作って公開してみたのでした。


作ってみたのがこちらです。申し訳ないですがスマホからの利用を想定しておらず(理由は後述)、PC ブラウザからアクセスしてください:
https://github-commit.mybluemix.net/

2021041101


(↓2021/04/12 追記)
バージョンアップして、画面右上に "Message" ボタンを付けました。このボタンを使うと下で紹介するように1件ずつコミットしなくても、メッセージを直接入力してまとめてコミットすることができるようにしました。
(↑2021/04/12 追記)

画面上部に Github Contributions に似た図が表示され、直近1年分のコミット結果が視覚化されます。ちなみにこの部分は(一部わざと変えている部分もありますが)本物同様の動的な SVG を生成して表示を実現しています。

ここでの「コミット結果」とは Github へのコミットではなく、あくまで「このサービス内でのコミット」を対象にしています。なので最初にアクセスした時点ではコミットは1つも登録されていないはずなので、Contributions は全てグレーで表示されているはずです。

コミットは画面下部で行います。右側のカレンダーをクリックしてコミットする対象の日をカレンダーから指定することもできます(指定しない場合は「今日」のコミットとして扱われます)※。

※実際の Git にはこのような日付を調整してコミットする機能はありません。
2021041102


仮に 2021/02/14 を選択してみました。Contributions 図も 2021/02/14 だけが赤枠で表示され、これから行うコミットの対象となる日が明示されます(繰り返しますが、実際の Git にはこのような機能はありません):
2021041103


対象日が決まったらコミットします。画面左下のコマンドライン($ に続く部分)はテキストが入力できるので、まずはここを選択します:
2021041104


選択後に git のコミット命令("git commit")を入力します。-m オプション後にコミットコメントを指定することもできます(指定しない場合はコメントなしとしてコミットされます)。最後に Enter キーを押します(上で書いた件の補足ですが、この部分のインターフェースが PC での作業を想定しており、スマホではなく PC でのアクセスでないと色々面倒だと思っています):
2021041105


するとカレンダーで指定した日(指定されなかった場合は今日)に1回コミットが記録されます:
2021041106


これを繰り返して色を濃くしたり、別の日にコミットを記録することができます。なお画面右上の "Reset" で全てコミットを取り消してやり直すことができます。またクッキーでブラウザを登録するので、同じブラウザからであれば次回アクセス時に続きを行うことができるようになっています。注意点として、Contributions は直近1年分を対象としているので、画面内に表示できそうでも1年以上前の日付は登録できません。また翌週(翌日曜)になるとデータが横に1列ずれてしまい、一番左にあった列のデータは表示されなくなることに注意が必要です。

で、気長に頑張るとこんなこともできます。というか、これをやってみたかったがためのサービス開発&ブログでした:
2021041107


自分でやってみてわかりましたが、このハローワールド程度でもかなり根気のいる作業です(苦笑)。ただ "Github Art" みたいな感じで面白くもあるなと。4段階のグラデーションも付けられるので、がんばれば絵の一部も描けるかも。。と思ってますが、実際はかなり大変だと思ってます。


Github を使ったことのない人はぜひコミットの練習に使ってみてください(実際の手順とはちと異なりますけど (^^;)。もしかしたら「Github で仕事してる風に見える」かも。

元ネタはこの記事です:
Cloudflare の ngrok的なサービスがあるらしい ⇒ $ cloudflared tunnel --url localhost:8080 といった使い方

(特に開発中のサービスなど)ローカルホスト内で動いている状態のウェブサービスを試験的・一時的にインターネットに公開して外部から利用できるようにするものです。この手のツールとしては ngrok が有名ですが、CDN で有名な Cloudflare 社も同様のツールを公開していたんですね。。

上記ページはその導入手順を含めて紹介しています。また公式のインストールページもリンクされています:
https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation


が、残念ながらラズベリーパイ向けのインストール方法はここで書かれている方法だけではわかりにくい内容でした(要はラズパイの場合、ソースコードからビルドする必要があるのですが、ビルドの前提となる Go 言語のインストールがまた面倒で・・・)。というわけで、ラズベリーパイ環境向けの cloudflared のインストール手順を以下に紹介します。


【ラズベリーパイ向け cloudflared のインストール手順】
(1) Go 言語のインストール

この後の手順で Go 言語を使って cloudflared をビルドします。そのため Go 言語をインストールしておく必要があります。

以前に紹介した以下のサイトを参照して、ラズベリーパイに Go 言語をインストールしてください:
ラズベリーパイに Go 言語をインストールする


(2) cloudflared のビルド

Go 言語用のソースコードをダウンロードして、Go 言語でビルドしてツールを作成します。

まず GitHub からソースコードをダウンロードします:
$ git clone https://github.com/cloudflare/cloudflared.git

ダウンロードしたソースコードを Go 言語でビルド&インストールします:
$ cd cloudflared

$ make cloudflared

$ go install github.com/cloudflare/cloudflared/cmd/cloudflared

ここまでの手順で ~/go/bin/cloudflared に実行バイナリが作られています。このままだと利用が不便なので、パスの通ったフォルダ(例えば /usr/local/bin/ 以下)にこのファイルをコピーします:
$ sudo cp ~/go/bin/cloudclared /usr/local/bin/

これでインストール完了です。


【ラズベリーパイで cloudflared を使ってみる】

ではインストールした cloudflared を実際に使ってみます。そのためには何でもいいのですが、何らかのウェブアプリケーションをラズベリーパイ上で動かす必要があります。docker で nginx を動かすとか、適当なウェブアプリを動かすとかでも構いません。こちらの方法を参照いただいて Node-RED をインストールして動かす、とかでも構いません(以下は最後の方法を実行し、 $ node-red コマンドまでを実行して、Node-RED が 1880 番ポートで稼働しているという前提で説明を続けます):
2021040801
(↑プライベートアドレスの 1880 番ポートで待受け準備完了した様子)


ラズベリーパイ上で何かウェブアプリが動いていたら、ラズベリーパイの端末(既に利用中の場合は別のターミナルの画面)から cloudflared コマンドを実行します:
$ cloudflared tunnel --url localhost:1880

※上述コマンドの 1880 部分は実際にリクエストの待受けをしているポート番号を指定します

すると実行結果の画面に以下のようなメッセージが表示されます:
  :
  :
2021-04-08T05:01:22Z INF +-------------------------------------------------------+
2021-04-08T05:01:22Z INF |  Your free tunnel has started! Visit it:              |
2021-04-08T05:01:22Z INF |    https://xxxxxx-xx-xxxxxx-xxxxxx.trycloudflare.com  |
2021-04-08T05:01:22Z INF +-------------------------------------------------------+
  :
  :

この青字で表示された URL が cloudflared のトンネリングによってインターネットに公開されたアドレスです。自分の PC はもちろん、全く別のネットワークに接続された別の PC からでもウェブブラウザでこのアドレスにアクセスすると、ラズベリーパイの localhost:1880 で稼働しているアプリケーションをパブリックなインターネットから見ることができるようになります:
2021040802
(↑パブリックアドレスで https で待受け準備完了した様子)



このページのトップヘ