モーグルとカバとパウダーの日記 このページをアンテナに追加 RSSフィード Twitter

モーグルやカバ(EXカービングスキー)、山スキー(BC)の山行記録などがメインの日記です。
いろんな条件のいろんなところを、その時々の条件にあった滑り方で楽しむ、フリースキーをして遊んでいます。

検索で来られた方は、上の検索窓から再度検索していただくか、右サイドバーのカテゴリーやトピックスの項目で絞り込んでみてください。
仕事柄、コンピュータ系のネタも多いので、スキー関連ネタだけ読みたい方は[ski]、コンピュータ関連ネタは[pc]、スパム関連ネタは[spam]で絞り込んでください。

2015-07-28 (Tue)

[]vagrant+ansibleなどでテスト環境の構築自動化する場合のsshキーペアの扱い vagrant+ansibleなどでテスト環境の構築自動化する場合のsshキーペアの扱いを含むブックマーク vagrant+ansibleなどでテスト環境の構築自動化する場合のsshキーペアの扱いのブックマークコメント

について、リプライいただいたことをまとめました。


CMSで動いているproduction環境に、機能追加やテスト等を行うために、vagrant+ansibleで開発環境を構築する設定を書いていました。

ホストPC上にvagrantでVMが生成され、ansibleでapachetomcatCMS等の組み込みと設定を行い、production環境から現在のデータやファイルをコピーしてきて、ほとんどミラーに近い環境を構築します。


このとき、データベースや追加されたファイルなどを取ってくる処理を、production環境上でダンプやtarしたものをfetchで一度ホストPCに持ってきて、それをVM上に展開するという処理にしていました。

しかし、転送が二度手間になっていて時間もかかるため、例えばファイルについてはsynchronize(rsync)など使ってVM上から直接productionサーバの内容を最新のものに合わせたいと思いました。

ただそうするには、VMからproduction環境に接続するためのsshキーペアの設定が必要になります。

そこでVMにホストPCの秘密鍵をansibleで突っ込んでやればいいだろう、と思ったのですが、それはセキュリティ的にはやっぱ良くないよな…と思って書いたのが冒頭のツイートでした。


それでこの件に対し @_hito_ さんから色々アドバイスをいただきました。

で、後から見直すと下記2ツイートにまとまっちゃうのですが、最初はイマイチピンと理解できず…

何件か質問して噛み砕いて説明していただきました _o_


元々自分が考えていたのは、ホストPCとproduction環境の間で通常使うための鍵ペアをそのまま使い、VM秘密鍵を送り込んで接続するイメージでした。

しかしこれだと、その開発環境のVMが侵害された場合、ホストの秘密鍵が漏れてしまいます。


そこで、データコピー専用の鍵ペアを作って、それの公開鍵をproduction環境に入れておきます。

vagrantで生成したVMには、そのデータコピー専用の秘密鍵を入れてやります。

この下記ペアはデータコピー専用のものなので、もしどこかのVMが侵害されて秘密鍵が漏れても、入れる場所は結局コピー元のところだけなので、それは使いまわしても良いわけです。

トラックバック - http://d.hatena.ne.jp/stealthinu/20150728

2015-07-02 (Thu)

[]MSYS2のgitgithubからcloneしようとしてSSL証明書の問題でエラーになる MSYS2のgitでgithubからcloneしようとしてSSL証明書の問題でエラーになるを含むブックマーク MSYS2のgitでgithubからcloneしようとしてSSL証明書の問題でエラーになるのブックマークコメント

とある原稿を共同で書くために、githubのプライベートリポジトリを使って、そこにcommitしてこうということになりました。

んで、作ってもらった場所からやっとこさcloneしようとしたのですが。

fatal: unable to access 'https://github.com/…': error setting certificate verify locations:

CAfile: /usr/ssl/certs/ca-bundle.crt

CApath: none

とか言われまして。


自分はWindows8.1でSourceTreeを利用していますが、内蔵のgitではなくMSYS2を入れてそのgitを使っています。

この問題は結構よくある話しらしく、色々と出てくるのですがこちらが参考になりました。


Githubの証明書問題をまっとうに解決する - Limitの日記

http://d.hatena.ne.jp/limitusus/20120529/1338306372


自分の場合、MSYS2のgitの証明書を新しくすればよいので、MSYS2のターミナルから

% cd /usr/ssl/certs
% curl http://curl.haxx.se/ca/cacert.pem -o ca-bundle.crt

として新しいCA証明書を入れてやることで解決しました。

トラックバック - http://d.hatena.ne.jp/stealthinu/20150702

2015-06-02 (Tue)

[]JavaでWebフォームに入力された内容を文字化けせずにメールで送る JavaでWebフォームに入力された内容を文字化けせずにメールで送るを含むブックマーク JavaでWebフォームに入力された内容を文字化けせずにメールで送るのブックマークコメント

Webでフォームに入力された内容をメールでどこかに送る、というのはすごくありふれた内容だと思います。

が、Javaで普通に書くと、いろんな理由で文字化けが発生してしまいます。

一番よくあるのがUTF-8→JIS(ISO-2022-JP)に変換するときに、変換できなくて「?」になってしまうというやつです。


そこで、文字化けが起きないように事前にいくつかフィルタを掛けてやる必要があるのですが、あまりまとまった情報がなくて困ったのでまとめてみました。


大きく下記3種のフィルタを掛けることで対応しました。

  • 波ダッシュやハイフン
  • IBM-UnicodeMS-Unicodeの違い(「¢」や「£」など)
  • HTMLエンティティ(「<」のようなもの)

波ダッシュ問題はとても有名ですが、その上UTF-8には色んな波ダッシュやハイフンがあり、一つに対応してあっても他ので化けたりします。

どうしたもんかと思っていたら、下記ページに波ダッシュやハイフンの各文字コードでの対応状況がまとめられており、大変に参考になりました。

また、変換コードもありましたので、そこに追加する形で他の化ける文字の対応をおこないました。

Unicodeの似た文字を整理してみた - y-kawazの日記


他にもIBM-UnicodeMS-Unicodeの違いにより化けてしまうという問題があります。

これで化けるものは、波ダッシュの他に「¢」や「£」などがあります。下記のページを参考にしました。

UTF-8での文字化け - mokkouyou2001の日記

perlでメール送信時の いわゆる波ダッシュ「〜」問題 | clicktx::Tech::Memo


また、接続環境なのかクライアントによってなのか、フォームに入力された記号のいくつかが、例えば「-」が「−」のように、HTMLエンティティに変換されて送られてくる場合があるようです。

これはどういった場合に、どの記号が変換されるのかということが調べきれなかったため、全てのHTMLエンティティを変換することにしました。

これには StringEscapeUtils クラスの unescapeHtml4 を利用しました。


これらをまとめたものが以下のコードです。

/**
 * HTMLエスケープ文字、UTF8→JISへの変換で化ける文字、を変換する
 * @param str
 * @return 変換された文字列を全て戻した文字列
 */
public String unescapeForMail(String str) {
  String result = str;
  result = StringEscapeUtils.unescapeHtml4(result);
  result = normalizeSimilarCharacter(result, "iso-2022-jp");
  return result;
}

/**
 * 文字化けの原因になる文字を、文字化けない文字に置換します。
 * http://d.hatena.ne.jp/y-kawaz/20101112/1289554290
 * http://perl.no-tubo.net/2011/01/12/perl%E3%81%A7%E3%83%A1%E3%83%BC%E3%83%AB%E9%80%81%E4%BF%A1%E6%99%82%E3%81%AE-%E3%81%84%E3%82%8F%E3%82%86%E3%82%8B%E6%B3%A2%E3%83%80%E3%83%83%E3%82%B7%E3%83%A5%E3%80%8C%E3%80%9C%E3%80%8D%E5%95%8F/
 * @param str
 * @param encoding 外部出力予定の文字コード(この値により置換テーブルが代わります)
 * @return
 */
public static String normalizeSimilarCharacter(String str, String encoding) {
    if(str == null || encoding == null) {
        return str;
    }
    encoding = encoding.toLowerCase();
    if("windows-31j".equals(encoding)) {
        return StringUtils.replaceEach(str, SIMILAR_CHARS_W31J_FROM, SIMILAR_CHARS_W31J_TO);
    } else if("shift_jis".equals(encoding)) {
        return StringUtils.replaceEach(str, SIMILAR_CHARS_SJIS_FROM, SIMILAR_CHARS_SJIS_TO);
    } else if("euc-jp".equals(encoding)) {
        return StringUtils.replaceEach(str, SIMILAR_CHARS_EUCJP_FROM, SIMILAR_CHARS_EUCJP_TO);
    } else if("iso-2022-jp".equals(encoding)) {
        return StringUtils.replaceEach(str, SIMILAR_CHARS_ISO2022JP_FROM, SIMILAR_CHARS_ISO2022JP_TO);
    }
    return str;
}

//共通置換テーブル
private static final String[] SIMILAR_CHARS_COMMON_FROM = new String[]{
    "\u00AD", "\u2011", "\u2012", "\u2013", "\u2043", "\uFE63", //半角ハイフン
    "\u223C", "\u223E", //半角波線→半角チルダ
    "\u22EF", //3点
    "\u00B7", "\u2022", "\u2219", "\u22C5", //半角中点
    "\u2225", "\uffe0", "\uffe1", "\uffe2"  // ‖ ¢ £ ¬
};
private static final String[] SIMILAR_CHARS_COMMON_TO = new String[]{
    "\u002D", "\u002D", "\u002D", "\u002D", "\u002D", "\u002D", //半角ハイフン
    "\u007E", "\u007E", //半角波線→半角チルダ
    "\u2026", //3点
    "\uFF65", "\uFF65", "\uFF65", "\uFF65", //半角中点
    "\u2016", "\u00a2", "\u00a3", "\u00ac"  // ‖ ¢ £ ¬
};
//エンコーディング別置換テーブル
private static final String[] SIMILAR_CHARS_SJIS_FROM;
private static final String[] SIMILAR_CHARS_SJIS_TO;
private static final String[] SIMILAR_CHARS_W31J_FROM;
private static final String[] SIMILAR_CHARS_W31J_TO;
private static final String[] SIMILAR_CHARS_EUCJP_FROM;
private static final String[] SIMILAR_CHARS_EUCJP_TO;
private static final String[] SIMILAR_CHARS_ISO2022JP_FROM;
private static final String[] SIMILAR_CHARS_ISO2022JP_TO;
static {
    SIMILAR_CHARS_SJIS_FROM = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_FROM, new String[]{
        "\uFF0D"/*全角マイナス*/, "\u00AF"/*長音符号*/, "\u2015"/*強調引用*/, "\u3030", "\uFF5E"/*波線*/
    });
    SIMILAR_CHARS_SJIS_TO = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_TO, new String[]{
        "\u2212"/*全角マイナス*/, "\uFFE3"/*長音符号*/, "\u2014"/*強調引用*/, "\u301C", "\u301C"/*波線*/
    });
    SIMILAR_CHARS_W31J_FROM = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_FROM, new String[]{
        "\u2212"/*全角マイナス*/, "\u2014"/*強調引用*/, "\u3030", "\u301C"/*波線*/
    });
    SIMILAR_CHARS_W31J_TO = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_TO, new String[]{
        "\uFF0D"/*全角マイナス*/, "\u2015"/*強調引用*/, "\uFF5E", "\uFF5E"/*波線*/
    });
    SIMILAR_CHARS_EUCJP_FROM = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_FROM, new String[]{
        "\uFF0D"/*全角マイナス*/, "\u2015"/*強調引用*/, "\u3030"/*波線*/
    });
    SIMILAR_CHARS_EUCJP_TO = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_TO, new String[]{
        "\u2212"/*全角マイナス*/, "\u2014"/*強調引用*/, "\uFF5E"/*波線*/
    });
    SIMILAR_CHARS_ISO2022JP_FROM = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_FROM, new String[]{
        "\uFF0D"/*全角マイナス*/, "\u00AF"/*長音符号*/, "\u2015"/*強調引用*/, "\u3030", "\uFF5E"/*波線*/
    });
    SIMILAR_CHARS_ISO2022JP_TO = (String[]) ArrayUtils.addAll(SIMILAR_CHARS_COMMON_TO, new String[]{
        "\u2212"/*全角マイナス*/, "\uFFE3"/*長音符号*/, "\u2014"/*強調引用*/, "\u301C", "\u301C"/*波線*/
    });
}
トラックバック - http://d.hatena.ne.jp/stealthinu/20150602

2015-05-20 (Wed)

[]GIMPで拡大縮小がうまくできなくなる問題 GIMPで拡大縮小がうまくできなくなる問題を含むブックマーク GIMPで拡大縮小がうまくできなくなる問題のブックマークコメント

嫁がGIMPを使って幼稚園のアルバム作成をしてるのですが、GIMPで拡大縮小の動作がなんかおかしくなるという問題が起きました。


探しても同じ問題にあたっている人がなかなかなかったのですが、この件とたぶん同じと思われます。

自分もこのスレッドと同様、別レイヤーに一旦コピーしてそこから拡大して対応しました。


レイヤーの拡大縮小ができません - GIMP質問掲示板

http://0bbs.jp/gimproject/4502/


実は、A4で印刷可能な領域などを半透明で設定してあるファイルを作ってあり、それを読み込んでから画像を別レイヤーで読み込んで拡大しようとしていました。

が、この元となるファイルで点くらいの大きさで領域選択がされていました

その上に画像を別レイヤーで読み込むと、読み込んだ画像全体が選択されたり新たな領域選択がされず、その小さな領域が拡大、となってしまっていました。

そのため、拡大しようとするとその何ドットかが拡大されるみたいになって、うまく動かない… となっていたのです。


そこで一旦矩形領域選択で別のところを選択して、その小さな領域選択が表示されなくなってから読み込むと普通に動くようになりました。

「選択」メニュー内の「選択を解除」を行ってもいいような気がします。


とりあえず、読み込んだファイルにすでに領域選択がされている状況で、別の画像を「レイヤーとして開く」をすると、領域選択の動きがおかしくなるのではないか、と思われます。

2015-05-08 (Fri)

[]JavaのSimpleDateFormatで多桁のミリ秒読み込みの謎 JavaのSimpleDateFormatで多桁のミリ秒読み込みの謎を含むブックマーク JavaのSimpleDateFormatで多桁のミリ秒読み込みの謎のブックマークコメント

CSVで渡されてくるデータの中に、日時データにミリ秒以下のものが付いているものがあり、そのパースをするとなんか読み込んだ時刻が微妙にずれてしまう、という問題が起こりました。


日付が「2015-05-08 00:00:00.123456789」のような形式でわたされるため、

String dateStr = "2015-05-08 00:00:00.123456789";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSSSS");
Date date = dateFormat.parse(dateStr);

のようにしてパースさせたのですが、9時間ズレるとかではなく中途半端に時間がずれてしまうのです。


なんでこうなるんだろう?と思ってちょっとテストコードを書いてみました。

out.println(testCalendar("2015-05-08 00:00:00",          "yyyy-MM-dd HH:mm:ss"));
out.println(testCalendar("2015-05-08 00:00:00.123",      "yyyy-MM-dd HH:mm:ss.SSS"));
out.println(testCalendar("2015-05-08 00:00:00.1234",     "yyyy-MM-dd HH:mm:ss.SSSS"));
out.println(testCalendar("2015-05-08 00:00:00.12345",    "yyyy-MM-dd HH:mm:ss.SSSSS"));
out.println(testCalendar("2015-05-08 00:00:00.123456",   "yyyy-MM-dd HH:mm:ss.SSSSSS"));
out.println(testCalendar("2015-05-08 00:00:00.1234567",  "yyyy-MM-dd HH:mm:ss.SSSSSSS"));
out.println(testCalendar("2015-05-08 00:00:00.12345678", "yyyy-MM-dd HH:mm:ss.SSSSSSSS"));
out.println(testCalendar("2015-05-08 00:00:00.123456789","yyyy-MM-dd HH:mm:ss.SSSSSSSSS"));

public String testCalendar(String dateStr, String formatStr) throws ParseException {
    String out = "";
    SimpleDateFormat dateFormat = new SimpleDateFormat(formatStr);
    Date date = dateFormat.parse(dateStr);
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    date = cal.getTime();
    out += "parsed date: " + dateFormat.format(date);
    return out;
}

結果

parsed date: 2015-05-08 00:00:00
parsed date: 2015-05-08 00:00:00.123
parsed date: 2015-05-08 00:00:01.0234
parsed date: 2015-05-08 00:00:12.00345
parsed date: 2015-05-08 00:02:03.000456
parsed date: 2015-05-08 00:20:34.0000567
parsed date: 2015-05-08 03:25:45.00000678
parsed date: 2015-05-09 10:17:36.000000789

ということで、3桁までならば正常にパースされているようです。


ぐぐってみると、下記ページを見てなるほど、となりました。

[Seasar-user:8699] Re: Timestamp型の秒以下の桁を取得するには

TimestampConverter は SimpleDateFormat を使っており,

SimpleDateFormat のパターン文字 S は「ミリ秒」なので

4 桁以上指定しても意味はありません.

ミリ秒だから本来なら3桁までのはずなので、それよりでかい桁については律儀にミリ秒を秒や分、日付に計算しなおしてくれてたのですね。


ここで渡されてくる9桁の「ミリ秒」はそういう意図のデータではないし、まあミリ秒以下のデータは不要であったため、前処理でカットしてパースするようにしました。

自分のように、DBから渡されたデータを、そのまま多桁の「S」を使ったフォーマットで読み込むと、この問題にあたりますので気をつけて。

トラックバック - http://d.hatena.ne.jp/stealthinu/20150508

2015-04-28 (Tue)

[]jquery.validationEngine.jsとautoConfirm.jsの併用 jquery.validationEngine.jsとautoConfirm.jsの併用を含むブックマーク jquery.validationEngine.jsとautoConfirm.jsの併用のブックマークコメント

javascriptである程度凝ったバリデーションをしようとすると、やはり既存ライブラリを使ったほうが良いと考え、jquery.validationEngine.jsを導入してみました。

posabsolute/jQuery-Validation-Engine

https://github.com/posabsolute/jQuery-Validation-Engine

見た目もよく、IE6以降対応で、バリデーションの種類も柔軟に設定出来ることなどからこちらにしてみました。


あと、確認画面を出すためにautoConfirm.jsというjqueryプラグインを使っていました。

autoConfirm.js - フォームの確認画面を自動生成

http://blog.material-being.com/portfolio/auto_confirm/

これは確認画面をわざわざ作らなくても、入力フォームを使って確認画面を表示してくれるものです。


で、この2つを一般的な設定で併用して使おうとすると、どちらもsubmitイベントで同時に平行して動いてしまうためうまく動きませんでした。


バリデーションが通ったら確認画面を表示する、という動作にする必要がありました。

元々は、submitするためのボタンにonclickで自前のバリデーションを実行し、trueだったときにautoConfirm.jsに回るようになっていました。

そこでjquery.validationEngine.jsも同様に動かすようにしてみました。


公式ページの使い方や、validationEngineを紹介しているエントリーではだいたい

$(document).ready(function(){
    $("#formID").validationEngine();
});

と、フォームにvalidationEngineをattachしてsubmitで自動的にvalidateが掛かるように設定していますが、「validate」というメソッドを呼ぶことでvalidateを掛けることができます。

$("#formID").validationEngine('validate');

そこで下記のように確認ボタンが押されるとvalidationEngineのvalidateを呼んで、それが通った時だけautoConfirmで確認画面に遷移する、という動作に出来ました。

<form name="editForm" class="autoConfirm">
  <input name="name" type="text" class="validate[required]">

  <button type="submit" onclick="return validate(document.editForm)">確認</button>
  <button type="button" class="autoConfirmBack" style="display:none;">戻る</button>
</form>

<script type="text/javascript" src="/js/jquery.validationEngine.js"></script>
<script type="text/javascript" src="/js/jquery.validationEngine-ja.js"></script>
<script type="text/javascript" src="/js/jquery.ba-hashchange.min.js"></script>
<script type="text/javascript" src="/js/autoConfirm.js"></script>
<script type="text/javascript">
 function validate(form) {
   if (! $(form).validationEngine('validate')) {
     return false;
   }
   return true;
 }
</script>
トラックバック - http://d.hatena.ne.jp/stealthinu/20150428

2015-04-21 (Tue)

[]MySQL WorkbenchがWin7/8でSSH経由で接続してる時selectが止まる問題 MySQL WorkbenchがWin7/8でSSH経由で接続してる時selectが止まる問題を含むブックマーク MySQL WorkbenchがWin7/8でSSH経由で接続してる時selectが止まる問題のブックマークコメント

他の人のWin7環境にMySQL Workbenchを入れてテーブルを見ようとしたところ、テーブルを見ようとselectしただけで止まってしまう(ハングしてる感じ)問題が起きました。

これ、全てのテーブルがダメなわけではなく、一部のテーブルを見ようとしただけでこの状況が起き、見れるテーブルは全く問題なく見れるのです。

止まってしまうテーブルは、limitの設定を小さくしてもやはり止まってしまいました。

また、自分のWin8.1で同じバージョン、設定で見ても問題なく見れました。ただ、一度同じような問題が発生したことがありました。(その後再現せず)


それで同じ問題にはまってる人がいないかぐぐってみたのですが、日本語の情報はなかなか無く、でも英語だともろそのものな感じの情報が見つかりました。

MySQL Bugs: #73343: Workbench Freezes on Remote SSH Server Query

MySQL Workbench hang on fetching more than some size of data - Stack Overflow


どうも

という条件でのみこの問題が発生するようで、自分の例のように同条件でも発生したりしなかったりするようです。


MySQLのmax_allowed_packet設定を大きくすることで改善したという報告もありましたが、確実ではないようです。

MySQL Workbench 6.0.9にダウングレードすると確実に改善するようです。

MySQL :: Download MySQL Workbench(6.0.9)のダウンロードページ


結局、6.0.9にダウングレードすることで対処しました。

なぜこんな謎現象が発生するのか不思議です…


(追記)

6.3.xなら治ってるかな…?と思って探してみたのですが、まだダメらしいです。

http://bugs.mysql.com/bug.php?id=74557

トラックバック - http://d.hatena.ne.jp/stealthinu/20150421

2015-04-02 (Thu)

[]javascript重複チェックとソート javascriptで重複チェックとソートを含むブックマーク javascriptで重複チェックとソートのブックマークコメント

「grape,apple,orange」のように、カンマ区切りで入力された内容を重複チェックするために、ハッシュにいれることで行いました。

例えばこんな感じです。

  input = "grape,apple,orange,apple";

  var results = {};
  var inputs = input.split(",");
  for (var i = 0; i < inputs.length; i++) {
    results[inputs[i]] = true;
  }

  for (var result in results) {
    console.log(result);
  }
grape
apple
orange

この結果をソートしたいという要望が出たため、ハッシュ results をソートしようとして results.sort() としたのですが、出来ないのですね。

あれ?出来なかったっけ?と思ったんですが、javascriptだと出来ない感じ… perlなら出来るのに。


というわけで、一度配列に突っ込み直してソートしました。

  input = "grape,apple,orange,apple";

  var results = {};
  var inputs = input.split(",");
  for (var i = 0; i < inputs.length; i++) {
    results[inputs[i]] = true;
  }

  var resultsArray = [];
  for (var result in results) {
    resultsArray.push(result);
  }
  resultsArray.sort();

  for (var i = 0; i < resultsArray.length; i++) {
    console.log(resultsArray[i]);
  }
apple
grape
orange

なんかひと手間感ありますね。


これってもっと良い方法あるのかな?

こうするといいよってのありましたら教えてください。


(追記)

@kotyさんから

input.split(',').reduce(function(prev,curr){if(prev.indexOf(curr)===-1)prev.push(curr); return prev;}, []).sort();

通りすがりさんから、ES6ならば

[...new Set(input.split(','))].sort();

といただきました。

関数型の威力をみせつけられましたねえ…

kkotyykkotyy 2015/04/02 16:42 input.split(',').reduce(function(prev,curr){if(prev.indexOf(curr)===-1)prev.push(curr); return prev;}, []).sort();

でいけました。IE8以前だと動きませんが。

stealthinustealthinu 2015/04/02 18:16 おお、すばらしい。なるほどreduceこういう感じに使うといいのか。
これ見て思ったんですが、sortしてからシュリンクすればもっと単純になりそうですね。

stealthinustealthinu 2015/04/02 18:21 …と思ったんだけどあんまりうまくいかないや。
全部と検索するんじゃなくて一つ前のと比べるだけだから単純になると思ったんだけど、別にpushする先がないとだめなのか。

kkotyykkotyy 2015/04/03 08:35 ちなみにTypeScriptだと
input.split(',').reduce((prev,curr) => {if(prev.indexOf(curr)===-1)prev.push(curr); return prev;}, []).sort();
と書けてプチハッピーです。filter/map/reduceはラムダ式と相性が良いです。

通りすがり通りすがり 2015/04/03 16:38 reduceとかを使う必要はありません。ES6なら
[...new Set(input)].sort()
だけで可能です。

stealthinustealthinu 2015/04/06 09:56 うおお、ほんとだ。なにこれすごい!
「...」になに埋めたらいいんだろ?と思ったら、これそのままで動くのですね。SetやMapはいわゆるSetやMapだろうということでわかるのですが、「...」の文法がよくわからんうえにググりにくい…
勉強してきます。

stealthinustealthinu 2015/04/06 10:20 Rest Parameters
http://odetocode.com/blogs/scott/archive/2014/08/18/features-of-es6-part-4-rest-parameters.aspx
というものでいいのでしょうか。
可変長引数をちゃんと渡せるようになる仕組み、という感じなのかな?
この手のことはnode界隈の人がよく扱ってる感じ。nodeだとブラウザのバージョンとか考えずに使えるもんね。うう、いいなあ。

通りすがり通りすがり 2015/04/08 07:15 「...」はRest/Spreadの関係で幾つかの構文が用意されています。
今回のは引数の方ではなく配列の要素として機能する方です。
また、Restは受け取り側なので、
var [a,b, ...c] = hoge;
とする時はRestですが、今回はSpreadです。
ですので「スプレッド要素」とでも呼ぶといいと思います。
全てをまとめて呼ぶときは「スプレッド構文」になると思います。
正式な名前が付いているわけではありません。

stealthinustealthinu 2015/04/08 10:48 ありがとうございます。
なるほど、newしたコレクションを「...」の場所にSpreadしてくれるのですね。
可変長引数のほうだけでイメージしてたのでわかりにくかったのですが、Spreadって言われるとわかりやすいですわ。
大変勉強になりました。ありがとうございます。

kkotyykkotyy 2015/06/29 18:04 今更コメントに気づいたんですが、ES6はすごいですね。
しかしブラウザの実装が追いついてないので、Babelってのを使うことになるんでしょうか。
http://codezine.jp/article/detail/8717

stealthinustealthinu 2015/06/30 09:51 FBとかみたいに通知飛ばないですからね… コメントでtwitterアカウント入ってたら通知飛ぶようになったらいいのに。
しかし、Babelってまた厨二心をくすぐられるネーミングですなw
ES6で書いたのをこれでコンパイルすれば良いのか。Typescriptとか使っている場合だとどうせコンパイルするんだから、それでぜんぜん構わんわけですよね。

kkotyykkotyy 2015/06/30 10:17 どこかで見かけましたが、近頃のJSは「コンパイルするもの」だそうです。なのでgulpやwebpackとかが流行っているのだろうと。

stealthinustealthinu 2015/06/30 14:21 そうなんですねえ。全然世の中の流れについていけてなくて悲しいです…

トラックバック - http://d.hatena.ne.jp/stealthinu/20150402

2015-03-17 (Tue)

[]Gmailで「ソーシャル」タブ等に自動分類されたものを排除して検索 Gmailで「ソーシャル」タブ等に自動分類されたものを排除して検索を含むブックマーク Gmailで「ソーシャル」タブ等に自動分類されたものを排除して検索のブックマークコメント

Gmailの受信トレイに、どうしても未読が5件残ってしまっていて気持ち悪い…という状況になっていました。


こういうときは検索で「in:inbox is:unread」と条件を指定すると、受信トレイにある未読を検索してくれます。

が「ソーシャル」タブに分類されているもの、例えばtwitterの反応とかの未読が大量にソーシャルタブにたまっているため、それを除いた真の未読を探そうとしても見つけきれませんでした。


twitterで嘆いていたところ「category:social」でソーシャルタブを指定出来ることを教えていただきました。

in:inbox is:unread -category:social -category:promotions


よく見ると、Gmailの上の検索のところで「検索オプションを指定」から「ソーシャル」を選ぶとこの条件指定がわかります。

トラックバック - http://d.hatena.ne.jp/stealthinu/20150317

2015-03-10 (Tue)

[]Javaサーブレットでユーザのセッション数を取得する Javaサーブレットでユーザのセッション数を取得するを含むブックマーク Javaサーブレットでユーザのセッション数を取得するのブックマークコメント

Tomcatで動いているWebアプリケーションで、ユーザアカウント毎のセッション数制限を実装する必要が出ました。


調査したところ、3通りの方法が考えられました。


1. サーブレットにプローブを挿しといてそこから取得する

2. セッション生成削除のリスナーを設定してセッション情報コピーを作る

3. セッション情報をDBに持つように設定してDB経由で見る


このうち、1と2の方法を実際に作って試しました。


1の「サーブレットにプローブを挿しといてそこから取得する」という方法は、下記ページのやり方を真似しました。

DAY2907 セッション情報の一覧を取得する(Tomcat限定) - LHD Peugeot

ContainerServletのsetWrapperメソッドをオーバーライドしておいて、渡されてきた containerWrapper を保持しておき、セッション情報を見たいときにはそれ経由でセッションリストにアクセスするというものです。


取得した後の値のセッション情報の触り方などは、tomcatが標準で提供しているmanagerサーブレットのソースが参考になりました。

GC: ManagerServlet - org.apache.catalina.manager.ManagerServlet (.java) - GrepCode Class Source


ただ、この方法はtomcat依存になってしまう、という問題と、このサーブレット自体をJSPなどに埋め込むことはできない(オブジェクトとして持てない)ため、サーブレットに対してWebAPIなどを通じて情報を取得する必要がありました。



そこで2の「セッション生成削除のリスナーを設定してセッション情報コピーを作る」を試しました。


これは下記のページを参考にしました。

8. リスナー2 | TECHSCORE(テックスコア)

javax.servlet.http.HttpSessionListener は、web.xmlに<listener>で設定しておくと、サーブレットセッションが生成されたときと削除された時に呼び出されるように出来ます。


この時に得られるセッションオブジェクトは、単にその時のセッション状況コピーではなくて、サーブレット内のセッションオブジェクトそのものなので、そのまま保持しておくとセッション生成後に行われた変更も含めて全てのセッション情報を取得することが出来ます。

なので、セッション生成後にログイン処理がされてユーザ名のセットがされても、ちゃんと取得することが出来ます。

(最初その認識がなかったため、この手法は使えないと思っていた)


下記程度の簡単な内容でセッション情報を保持することが出来るようになり、JSPから普通にUserCounterオブジェクトを生成してユーザのセッション数などを得ることが出来ます。

public class UserCounter {
    private static final UserCounter INSTANCE = new UserCounter();
    private final Map<String, HttpSession> sessions = new HashMap<>();

    private UserCounter() {
    }
    
    public static UserCounter getInstance() {
        return INSTANCE;
    }
    
    synchronized public void addSession(HttpSession session) {
        sessions.put(session.getId(), session);
    }

    synchronized public void delSession(HttpSession session) {
        sessions.remove(session.getId());
    }
    
    synchronized public int countAllSessions() {
        return sessions.size();
    }
    
    synchronized public int countUserSessions(String username) {
        int userCounter = 0;
        for (HttpSession session: sessions.values()) {
            String sessionUsername = (String) session.getAttribute("username");
            if (sessionUsername != null && sessionUsername.equals(username)) {
                userCounter ++;
            }
        }
        return userCounter;
    }
}

public class UserCountListner implements HttpSessionListener {
    private final UserCounter counter = UserCounter.getInstance();

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        HttpSession session = event.getSession();
        counter.addSession(session);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        HttpSession session = event.getSession();
        counter.delSession(session);
    }
}

これはtomcat依存ではないため他のWebコンテナでも動くはずです。

ただ、1の方法のように全サーブレットセッション情報を取得するのではなく、listnerの設定をしたサーブレットセッションのみになります。

トラックバック - http://d.hatena.ne.jp/stealthinu/20150310