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

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

2018年04月

自分メモ兼情報緩募なブログエントリです。

SSH でサーバーにリモートログインして作業している途中で通信が途切れてしまう(プロセスが死んでしまうわけではなく、通信が切れてしまう)ケースがあります。自分が比較的頻度高くやっちゃうのは、電車内からテザリングを使ってリモートログインした先で Node.js のサーバーを動かしていて、地下鉄に入って通信が遮断しちゃうケースです。

これをやってしまって困るのは、例えば Node.js のサーバーを 8000 番ポートで動かしていたとすると、8000 番ポートへの listen が生きたままの状態で通信が切れてしまうことです。繋がっていれば Ctrl+C でサーバーを落とすことができるのですが、繋がっていないので Ctrl+C を入力する端末がありません。ということは再度リモートログインしてプログラムを修正して再度実行・・・しようとしても「そのポートは使われています」エラーになってしまいます。なんとかして元のプロセスを止めなければなりません。


※余談ですが、本来はこうならないように(途中で通信が切れてしまうことを想定して) screentmux などの端末のマルチプレクサーを使って作業するべきです。ただ今回はマルチプレクサーを使っていない時にこうなってしまった場合の対処方法についての話です。


以下で紹介する方法が正解かどうかはわからないのですが、自分の対処方法を紹介します。考え方としては「プロセスが残っているはずの SSH を探し出して息の根を止める」というアプローチです。

というわけで、まずは再度リモートログインした先で ps コマンドを実行して、自分が所有しているプロセスを一覧表示します(ちなみに実行コマンド内で `whoami` を実行していますが、自分の環境だとこの結果は dotnsf となり、dotnsf という文字を含むプロセスの一覧を表示しています):
$ ps -aux | grep `whoami`

root      4335  0.0  0.0  94924  7092 ?        Ss   08:19   0:00 sshd: dotnsf [priv]
dotnsf    4410  0.0  0.0  94924  4428 ?        S    08:20   0:00 sshd: dotnsf@pts/8
dotnsf    4411  0.0  0.0  23912  5620 pts/8    Ss   08:20   0:00 -bash
dotnsf    4654  0.0  0.5 935364 41736 pts/8    Sl+  08:37   0:00 node app
root      5167  0.0  0.0  94924  6964 ?        Ss   09:20   0:00 sshd: dotnsf [priv]
dotnsf    5243  0.0  0.0  94924  3408 ?        S    09:20   0:00 sshd: dotnsf@pts/9
dotnsf    5244  0.1  0.0  23800  5392 pts/9    Ss   09:20   0:00 -bash
dotnsf    5270  0.0  0.0  38376  3392 pts/9    R+   09:21   0:00 ps -aux
dotnsf    5271  0.0  0.0  15256  1012 pts/9    S+   09:21   0:00 grep --color=auto dotnsf
dotnsf   23891  0.9  1.4 1661748 118288 ?      Sl    2月23 651:40 /opt/couchdb/bin/../erts-7.3/bin/beam.smp -K true -A 16 -Bd -- -root /opt/couchdb/bin/.. -progname couchdb -- -home /opt/couchdb -- -boot /opt/couchdb/bin/../releases/2.0.0/couchdb -kernel inet_dist_listen_min 9100 -kernel inet_dist_listen_max 9100 -kernel error_logger silent -sasl sasl_error_logger false -noshell -noinput -config /opt/couchdb/bin/../releases/2.0.0/sys.config
dotnsf   23940  0.0  0.0   4512   856 ?        Ss    2月23   0:00 sh -s disksup
dotnsf   23941  0.0  0.0   4232    72 ?        Ss    2月23   0:05 /opt/couchdb/bin/../lib/os_mon-2.4/priv/bin/memsup
dotnsf   23942  0.0  0.0   4364    76 ?        Ss    2月23   0:00 /opt/couchdb/bin/../lib/os_mon-2.4/priv/bin/cpu_sup
dotnsf   26173  0.0  0.0  45276  3140 ?        Ss    2月15   0:00 /lib/systemd/systemd --user
dotnsf   26174  0.0  0.0 210940  2152 ?        S     2月15   0:00 (sd-pam)
dotnsf   31713  0.0  0.0  11140   312 ?        Ss    3月30   0:00 ssh-agent
  (↑青字が実行コマンドです)


結果の中で注目すべきは赤字にした2行です。dotnsf が sshd から pts(仮想端末)を使っているプロセスが(左から2列目の数値でいうと) 4410 と 5243 の2つあります(実際にコマンドを実行した結果は3つ以上みつかるかもしれません)。この2つのうち、どちらかは今自分がリモートログインに使っている仮想端末のプロセスで、もう1つは今回のターゲットとなる行方不明のプロセスです。このどちらか正しい方のプロセスを kill することで目的を達成することができます。

というわけで、「どちらかは正解、間違えると自分が死ぬ」という下図のような状況なわけです(笑):
jigenbakudan_kaitai_shifuku




ここでの問題は「どちらが目的のプロセスかを判断する方法」なのですが、これって正解あるんでしょうか? (^^; 明確な方法があったら自分も知りたいです。

自分がやっている判断方法としては2つあります:
・時刻(左から9列目)を見る。どちらかは自分がログインしているプロセスということは、そのプロセスの時刻は自分がログインした時刻になっているので、自分が現在ログインしているプロセスをなんとなく判断できる。
・プロセス番号(この例だと 4410 と 5243)は大抵小さい方が古い。現在のログインに使っているプロセスの方が新しいはず。


で、この基準で考えるとプロセス番号 5243 の方が現在使っているプロセスに該当するので、もう1つの 4410 のプロセスが息の根を止めるべきプロセス、と判断できることになります。ただこの考え方は経験的なもので、もしかすると別の確実な方法があるかもしれません(誰か知っていたら教えてください)。


(↓追記)
who コマンドで自分が現在いる TTY を調べることができる、という指摘をいただきました。確かに使えそうです。
$ who
dotnsf   pts/8        2018-04-12 21:00 (192.168.X.X)

(↑追記)


というわけで、4410 を kill することにします:
$ kill 4410

まあ仮に間違えていたとしても自分が強制ログアウトされるだけなので、そしたらもう一度リモートログインしてもう1つの方を kill し直せばいいんですけどね。。

で、これで上手く元のプロセスを消すことができていれば、再度実行した時に「そのポートは使われています。。」エラーは消える、はず。

 

IBM Cloud(Bluemix) から提供されている NoSQL データベースの DBaaS である Cloudant は読み書きのための REST API が公開されています。各種プログラミング言語から HTTP ベースの API を実行してデータを読み書きすることが可能です。

ただ、これらの API には一般的な CORS(Cross-Origin Resource Sharing) の制限がかかっており、ウェブブラウザの JavaScript からは読み書きができないように設定されています。この CORS 制限を無効にする方法が分かったので、ブログで紹介する形で手順等を紹介します。

まず今回ブラウザからアクセスする対象とするデータベースをこちらとします。pouchdb というデータベースで、現在4件のドキュメントが登録されています:
2018040900


このデータベースに簡単にアクセスするため、今回は PouchDB ライブラリを使うことにします。PouchDB は軽量かつ CouchDB(Cloudant) 互換のデータベースです。この PouchDB を CDN(//cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js) からロードして、データベースオブジェクトを作り、その中の全文書を取り出して表示する、という処理を実装すると↓のような感じになります:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>CORS check</title>
<script src="//cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var cloudant_db_url = 'https://USERNAME:PASSWORD@USERNAME.cloudant.com/pouchdb';

var db = new PouchDB( cloudant_db_url );

db.allDocs( { include_docs: true } ).then( function( docs ){
  console.log( docs );
}).catch( function( err ){
  console.log( 'error' );
});
</script>
</head>
<body>
</body>
</html>

主な処理内容を簡単に解説します。まず CDN から PouchDB ライブラリをロードし、Cloudant のデータベース URL を指定して、データベースオブジェクトを作ります(上記の USERNAME は Cloudant のユーザー名、PASSWORD は同パスワードです)。そして allDocs() メソッドで全文書を取り出して結果を console.log() でコンソールに表示する、という内容の JavaScript を含む HTML になっています。


この HTML を HTTP サーバー上に配置してウェブブラウザからアクセスします。取得結果はコンソールに表示されるので、あらかじめコンソール画面を表示(FireFox であれば F12 キー)しておきます。その状態でブラウザから同ページにアクセスすると・・・コンソールには「クロスオリジン要求・・」というエラーが表示されます。これはつまり Cloudant 側でクロスオリジンからのアクセスを許可していないため、アクセスは拒絶され、そのエラーが表示されています。これが Cloudant のデフォルトでの挙動です:
2018040901


では Cloudant の CORS アクセス(クロスオリジンからのアクセス)を有効にしてみます。curl コマンドの使えるターミナルから、以下のコマンドを実行します:
$ curl -i -u 'USERNAME:PASSWORD' -XPUT 'https://USERNAME.cloudant.com/_api/v2/user/config/cors' -H 'Content-type: application/json' -d '{"enable_cors":true,"allow_credentials":true,"origins":["*"]}'

このコマンドでは認証用の ID とパスワード、HTTP ヘッダの Content-Type: application/json を指定し、/_api/v2/user/config/cors パスに対して、CORS アクセスを有効にするようデータを POST して実行しています:

2018040902


上記のように {"ok": true} という結果が返ってくればコマンドは成功し、クロスオリジンからのアクセスも許可されています。試しに再度同じ HTML ページを(リロードするなどして)表示すると、今度は allDocs() メソッドが成功し、期待通りに(4件の)データを取得し、コンソールに表示できているはずです:
2018040903


これで Cloudant の API をブラウザ(の JavaScript )からも直接実行する術が確保できました。これでウェブブラウザの HTML から直接 Cloudant を操作したり、ウェブブラウザ内の PouchDB と連携することもできるようになります。



(参考)
How to use CORS with a Cloudant account

CORS


JavaScript 製オープンソース Web フレームワークである AngularJS は、リッチなフロントエンドを簡単に作ることができて便利です。実は業務でも使っています(個人的にはまあ色々言いたいこともあるけどw)。 一方で、AngularJS やマテリアルデザインである Angular Material は特に日本語での情報があまり多くなく、標準以外の機能を使おうとすると色々苦労することがあります。その辺りを補足する目的もあって、調べたことのメモを公開します。


AngularJS を使うことで普通に作っても見栄えがよくなりますが、サブモジュールである Datepicker を使うと、日付データの入力に便利な UI を簡単に配置できます(↓こんなやつです):
2018040301


この Datepicker を使う方法も簡単で、JavaScript コード側で @angular/material/datepicker をインポートしておいて、
import {MatDatepickerModule} from '@angular/material/datepicker';

その上で以下のような HTML を記述するだけです:
  :
  :

<mat-form-field>
  <input matInput [matDatepicker]="picker" placeholder="Choose a date">
  <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
  <mat-datepicker #picker></mat-datepicker>
</mat-form-field>

  :
  :

すると上記画像のように <input> フィールドそのものに CSS が適用され、カレンダーアイコンをクリックして日付選択ができるようになる、というスグレモノです。モバイルブラウザのタッチイベントにも対応していたりするなど、機能的にも優秀です。日付選択は HTML5 の type="date" を使う方法もありますが、まだ主要ブラウザの実装もまちまちで、対応されていてもちと使いにくさを感じることもあるため、現時点での代用品としてはかなりの需要があると思っています。


一方で、この Datepicker にも問題点はあります。標準機能をそのまま使うと、選択した日付は日本ではあまり馴染みのない M/d/yyyy フォーマットで画面に表示されることです:
2018040302


この表示部分だけでもなんとか日本式の yyyy/M/d フォーマットにできないだろうか・・と調べた結果が以下の方法です。いくつかの対応手段がありそうですが、簡単なのは「日本ロケール(ja-JP)を指定する」以下のやり方でした。

HTML はそのままで、JavaScript を以下のように書き換えます:
//. 赤の2行を追加インポート
import {MatDatepickerModule} from '@angular/material/datepicker';
import {MAT_MOMENT_DATE_FORMATS, MomentDateAdapter} from '@angular/material-moment-adapter';
import {DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE} from '@angular/material/core';

  :
  :

@Component({
  selector: 'datepicker-locale-example',
  templateUrl: 'datepicker-locale-example.html',
  styleUrls: ['datepicker-locale-example.css'],
  providers: [
    //. 'ja-JP' ロケールを指定
    {provide: MAT_DATE_LOCALE, useValue: 'ja-JP'},

    //. MomentDateAdapter と MAT_MOMENT_DATE_FORMATS をインポート
    {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]},
    {provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS},
  ],
})

  :
  :

export class DatepickerLocaleExample {
  //. クラスのコンストラクタに DateAdapter を渡す
  constructor(private adapter: DateAdapter) {}
}

この変更を加えて上で同じ HTML を参照すると、日付は日本ロケールで表示されるようになります:
2018040303



めでたしめでたし。


(参考)
https://material.angular.io/components/datepicker/overview

この「まだプログラマーですが何か?」ブログは 2013 年から定期的に更新しています。扱う内容はオープンソースネタだったり、Kali Linux だったり、LAMP だったり、IBM Cloud だったり、ノーツだったり・・・と色々ですが、ページ毎に月間アクセスランキングを見た場合の月間一位は常にトップページ(http://dotnsf.blog.jp/)でした。が、昨月はその月間アクセスランキングに初めて異変が起こりました:
20180403


これまで5年近く月間アクセス数では常に1位だったトップページが3位となり、個別ページが1位と2位になったのでした。びっくり!

ちなみに1位のページがこちら:
クロスドメイン問題と Access-Control-Allow-Origin ヘッダ

そして2位のページがこちらです:
Linux の cp コマンドで強制上書き


2位のページは 2014 年の記事で、実は月間1位になったことこそなかったものの、平均的にアクセス数が高い状態をキープしている記事でした。内容は Linux の cp コマンドを実行した時のコピー先に同名のファイルが存在していると必ず「上書きしますか?」と確認してくれるのですが、複数のファイルをまとめて上書きする時にいちいち返事するのは不便なので、なんとかする方法を紹介しています。結構この問題で困っている人が多いようで、ずっと一定以上のアクセスを記録している記事でしたが、3月はトップページ参照回数をも上回るアクセスを記録しました。

そして1位のページは 2016 年の記事なので、こちらも特別に目新しい内容ではないのですが、AJAX を使った時などによく問題になるクロスドメイン問題と、この問題をサーバー側の設定(が可能な場合)で解決する方法について紹介したものです。どういった背景があるのか分からないのですが、2月下旬くらいからこの記事のアクセス数が急激に増え、3月通算では1位となったようです。


このページのトップヘ