違うユーザのリモートリポジトリをPull・Push時、エラー対処

状況

まず、Eclipseのバージョンは古い(Mars, 2015年版)です。
EclipseのPackage Explorer上に複数のプロジェクトがあり、
それぞれのプロジェクトが属するGitHubのリモートリポジトリのGitHubユーザが違う場合、
(個人用と会社用を分けて使うなど)
PullやPushなどのリモート操作時、以下のエラー(Invalid remote: origin)が発生します。
f:id:jeongman7:20170701184341j:plain

原因

対処方法からの推測ですが、全てのプロジェクトが属するリモートリポジトリのGitHubユーザ情報が
最後に設定したリモートリポジトリのGitHubユーザ情報に上書きされるからだと思います。
今のところ、それぞれのプロジェクトに違うGitHubユーザ情報を設定できる方法はなさそうです。
最新バージョンのEclipseなら、可能かも知れないですね。

対処方法

不便ですが、今のところ、これが最善です。
Git Repositoriesビュー > 作業対象のプロジェクトが属するリポジトリ > Remotes > originを選択し、
右ボタン > Configure Push > Changeすると、Destination Git Repositoryダイアログが表示されます。
ここでAuthenticationにあるUser・Passwordに該当GitHubユーザ情報を入力し、
Store in Secure Storeにチェックし、Finish > Saveします(以下の画像)。
f:id:jeongman7:20170701185718j:plain
以上でPush・Pullする時、エラーは出なくなります。
但し、作業対象のプロジェクトが変わるたびに行う必要があります。
近いうちにEclipseのバージョンを変えてやってみます。

awkについて簡単整理

Linuxのテキスト処理ツールであるawkについて簡単に整理します。
awkはポイントさえ掴めば分かりやすいと思います。

awkは何者?

テキストデータから行を読み込み、文字列操作を行うLinuxのツールです。
特定条件にマッチする行から列を抽出し、ある処理を実行するといったバッチ処理に向いています。
個人的にはsedより文法(C like)が分かりやすい印象です。
ただ、簡単な処理の場合、sedより記述量が多くなるので、場合によって使い分けた方がいいでしょう。

コマンド

# テストファイル生成
echo a 1 >> test.txt
echo b 2 >> test.txt

# awk '処理内容' テキストファイル
awk '{ print $0 }' test.txt
# 結果)
# a 1
# b 2

# テキストファイルの代わりにパイプで入力
cat test.txt | awk '{ print $1 }'
# 結果)
# a
# b

# 処理内容をファイル(awk)に入れて実行。
echo '
    /a/{
        print "bingo! -> ", $0
    }
' > test.awk
awk -f test.awk test.txt
# 結果)
# bingo! ->  a 1

プログラム構造

# テキスト行を読み込む前に一回のみ、実行されるブロック。初期化などを記述します。
BEGIN {
    # コメントです。
    # ... 処理内容 ...
}

# パターンに一致する行ごとに実行されるブロック。
/パターン/ {
    # ... 処理内容 ...

    # 変数はブロック内で宣言なしで使用できる。
    # グローバルスコープなので、前の行で使用した変数を次の行でも参照・更新できる。
    cnt = cnt + 1
    
    # for, whileなどの繰り返し文も使用可能です。
    for (i = 0; i < NF; i++) {
        ...
    }
}

# 論理条件(例 : a == 1 || NF == 3)に一致する行ごとに実行されるブロック。
論理条件 {
    # ... 処理内容 ...
}

# 全ての行ごとに実行されるブロック。
{ 
    # ... 処理内容 ...
}

# 最後の行の処理が終わって一回のみ、実行されるブロック。
END {
    # ... 処理内容 ...
}

行内変数

  • $0 : 現在の行の文字列
  • $1~$x : 現在の行でx番目の列の文字列

他にも色々あります。参考のリンクを参照して下さい。

組込変数

  • NR : 現在の行の番号
  • NF : 現在の行の列数
  • FS : 入力時の区切り文字。デフォルトは半角スペース。BEGINブロックでFS = "¥t"のように設定できる。
  • OFS : 出力時の区切り文字。デフォルトは半角スペース。BEGINブロックでOFS = "¥t"のように設定できる。

他にも色々あります。参考のリンクを参照して下さい。

組込コマンド

  • print : 文字列を出力する
# 1番目の列を出力する。
cat test.txt | awk '{ print $1 }'
# 結果: 
# a
# b

# 1番目の列と2番目の列をOFS(出力時の区切り文字)で区切って出力する。
cat test.txt | awk '{ print $1, $2 }'
# 結果: 
# a 1
# b 2

# 1番目の列と2番目の列を/で区切って出力する。
cat test.txt | awk '{ print $1 "/" $2 }'
# 結果: 
# a/1
# b/2

他にも色々なコマンドがあります。参考のリンクを参照して下さい。

サンプル

キューにたまっているメールのうち、宛先メールアドレスのドメインがtest.testに該当するメールのキューID及びメールアドレスを抽出するサンプル。

# 以下はmailqコマンドで出力したキュー内容の例)
# メール一件当たり4行です。
# -Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------          <--- ヘッダー
# ABCDEF1234     7681 Fri Jun 23 18:59:27  hogehoge@test.test                <--- x行目
#           (connect to dukelab.test[192.168.0.15]:25: Connection refused)   <--- x+1行目
#                                         darekasan@test.test                <--- x+2行目
#                                                                            <--- x+3行目
# ... 省略 ...
# x行目の1列目がキューID(ABCDEF1234)です。
# x+2行目の1列目が宛先メールアドレス(darekasan@test.test)です。
# x+3行目はただの改行です。

echo '
# x行目。
# NF == 7(x行目は7つの列で構成)のように列数で判断するのもいいでしょう。
NR % 4 == 2 {
    # x行目なら、キューIDを抽出する。
    queue_id = $1
}
# x+2行目に宛先メールアドレスがある。
# NF == 1(x+2行目は1つの列で構成)のように列数で判断するのもいいでしょう。
NR % 4 == 0 && /@test.test/{
    # キューID メールアドレスを出力
    print queue_id OFS $1
}' > extract_qids.awk
mailq | awk -f extract_qids.awk
# 以下のように変数に入れてfor文で回したら、バッチ処理も可能でしょう。
# queue_ids=`mailq | awk -f extract_qids.awk`
rm extract_qids.awk

# 結果例
# ABCDEF1234 darekasan@test.test
# ... 省略 ...

稼働中のサーブレットインスタンスを取得する。

あまりないと思いますが、
Tomcatでアプリケーション稼働中、あるサーブレットインスタンスが持つ情報を見たり修正したい時があるとします。
それができる画面や仕組みがアプリケーションにあればいいですが、ない時は困りますね。
以下の方法を使えば、稼働中のサーブレットインスタンスを取得し、何らかの操作が可能です。
あくまで裏技ですし、場合によっては事故になるかも知れませんので、自己責任でお願いします。

まず、取得したいサーブレットの名前が以下のweb.xmlの通り、HogeServletとします。

...
<servlet>
      <servlet-name>HogeServlet</servlet-name>
      <servlet-class>xyz.dukelab.HogeServlet</servlet-class>
</servlet>
...

以下のようなJSPを作成し、サーバにアップロードし、ブラウザやcurlなどでJSPにアクセスすればいいです。

<%@ page import="javax.management.*"%>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="org.apache.catalina.Server"%>
<%@ page import="xyz.dukelab.HogeServlet" %>
<%
    MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
    ObjectName name = new ObjectName("Catalina", "type", "Server");
    
    // Tomcat関連インスタンス取得
    Server server = (Server) mBeanServer.getAttribute(name, "managedResource");    
    StandardEngine engine = (StandardEngine) server.findService("Catalina").getContainer();
    StandardContext context = (StandardContext) engine.findChild(engine.getDefaultHost()).findChild(getServletContext().getContextPath());
    
    // HogeServletのインスタンスのラッパーインスタンスを取得。
    StandardWrapper sw = (StandardWrapper) context.findChild("HogeServlet");
    
    // HogeServletを取得。
    HogeServlet hs = (HogeServlet) sw.getServlet();
    
    // HogeServletのメソッドを呼び出したり、リフレクションを使ってフィールドやメソッドにアクセスする。
    ...
%>

WebPushについて簡単整理

WebPushについて私なりに理解したことを最大限簡単に書いてみます。

WebPushとは

ブラウザに対してサーバからプッシュ通知ができる技術です。
他のウェブページを見ていても通知が来ます。ブラウザによっては、ブラウザを立ち上げなくても通知が来ます。
専用アプリは不要です。ウェブページ(html, jsp...)とjsだけでプッシュ通知できます。
通知がたくさん来ると、ユーザに迷惑でしょうが、うまく使えば色々な使い方ができそうですね。

プッシュ通知の流れ

プッシュ通知は以下の流れで動作します。
標準のWebPush APIを利用すると仮定します。
AP ServerはApplication ServerでTomcatなどのコンテナーを表します。

ユーザ > Browser

  • ユーザがBrowserでウェブページにアクセスします。
  • Browserからプッシュ通知を許可するかどうかの選択を求めるダイアログが表示されます。
  • ユーザが許可を選択します。

Browser > PushServer > Browser

  • ユーザがBrowserでプッシュ通知の購読ボタンを押します。
  • BrowserからPushServerに購読をリクエストします。
  • PushServerはEndPoint(URL)をBrowserに返します。

Browser > AP Server > DB

  • BrowserはPushServerから受け取ったEndPointとユーザキーなどをAP Serverに送ります。
  • AP ServerはEndPointとユーザキーなどをDBに保存します。

AP Server > PushServer > Browser

  • AP Serverの使用者はEndPoint宛にメッセージを送り、メッセージはPushServerに届きます。
  • PushServerはBrowserにメッセージ送り、Browserはメッセージを通知ダイアログで表示します。

PushServer

ブラウザの利用者にプッシュ通知を送るためにはEndPointというURLにメッセージを送る必要があります。
EndPointはPushServerのURLを表し、ユーザごとにユニークでドメインもブラウザごとに違います。
WebPushを標準的な方法で利用すると、特にEndPointが何者か意識する必要はありません。
Push APIの購読メソッドを呼び出すと、EndPointが返ってきますが、そのEndPoint宛にメッセージを送れば、
該当ブラウザにプッシュ通知が表示されます。

サポートするブラウザ

ChromeFirefoxSafariがWebPushをサポートします。
ブラウザのバージョンは大体2016年7月以降のものなら、WebPushの標準的な使い方ができます。
ChromeFirefoxは検証できましたが、SafariMacがないので、分かりません。
またSafariは少々サポート(アイコン未サポート..)が足りないようです。
MicrosoftのEdgeは開発中だそうです。
従いまして、今のところChromeFirefoxが確実にサポートすると考えればいいと思います。

実装方法の違い

Firefoxでは特に意識する必要はありません。
Chromeの場合、バージョン52未満ではChrome専用で行わなければならないもの(例 : ID取得、manifest.jsonファイル)があります。
今はChrome 52以降なら、Web Pushプロトコル標準をサポートしているため、ブラウザ依存なしで実装できます。

より多くのブラウザをサポートするためにChromeの旧バージョンもサポートすべきか?と悩むところですが、
私は必要ないのでは?と思います。
なぜかというと、IEならまだしも、ChromeFirefoxは知らぬ間に自動更新してくれますし、
ブラウザシェアを見ると、ほとんどのChromeユーザは52以降のバージョンを使用中だからです。
(WebブラウザシェアランキングTOP10(日本国内・世界) – ソフトウェアテスト・第三者検証ならウェブレッジ)

ですので、近々なくなるChrome依存のコードは要らないのでは?と思います。

サンプル

以下のgithubのサンプルが役立ちました。
Javaベースですが、標準的なAPIを使用しているので、他の言語への適用は問題ないと思います。
github.com
WebPushの標準的な実装だけでなく、古いバージョンのChromeにも対応しています。
暗号化する部分が複雑ですが、ただWebPushを利用するだけなら、スキップし、実際通信する部分だけを参照しても問題ないと思います。

実装ポイント

  • プッシュ通知の許可や購読を行うjsは該当ウェブサイトと別のドメインにあってもいい。
  • 通知を行うjsは該当ウェブサイトに配置する必要がある。
  • 通知を行うjsはブラウザでService Workerとして常駐することになる。つまり、該当ウェブサイトを去ってもずっとブラウザのメモリに残って通知が来ることを待つ。
  • Service Workerとして登録された通知を行うjsの更新に注意する必要がある。期限を設定し、うまく更新されるか確認しよう。

SPFとSenderIDで曖昧な部分を整理

SPFについてブログやドキュメントを読んでも何を言っているのか分からないところを整理しました。
実際に検証したところもあり、英文を探して分かったところもあります。

SPFとSenderIDはどのドメインを確認するか

  • SPFはMAIL FROMコマンド(いわゆるEnvelope From)に指定されるメールアドレスのドメイン部のSPFレコードをチェックします。
  • SenderIDはPRA(Purported Responsible Address)といって、メールヘッダーにある最終的に責任があるメールアドレスのドメイン部のSPFレコードをチェックします。
    • PRAを抽出するアルゴリズムRFC4407の2. Determining the Purported Responsible Addressに書いてあります。

下記はRFC4407でPRAを求めるアルゴリズムのStep 1からStep 6までを疑似コードで書いてみたのです。

def getPRA
  def step2_to_step6
    # step 2 
    if Resent-From.isEmpty
      if Sender.isEmpty
        # step 4
        if From.size == 1
          return From[0]
        else
          throw new Exception
        end
      else
        # step 3
        if Sender.size == 1
          return Sender[0] 
        else
          throw new Exception
        end
      end
    else
      return Resent-From
    end
  end
  # step 1
  if Resent-Sender.isEmpty
    return step2_to_step6()
  else
    if Resent-From.isBefore(Resent-Sender) 
         && (Received.isAfter(Resent-From) && Received.isBefore(Resent-Sender)) 
           || (Return-Path.isAfter(Resent-From) && Return-Path.isBefore(Resent-Sender))
      return step2_to_step6()
    else
      return Resent-Sender
    end
  end
end

step1で条件が複雑ですが、Resent-Sender > Resent-From > Sender > Fromの順にチェックしていて、通常はFromのメールアドレスがPRAになると考えればよさそうです。

includeとredirectの違い

  • includeはincludeの右に指定したメールサーバのSPF設定を参照する上に自分のSPF設定を追加できます
example.org. IN TXT "v=spf1 include:_spf-a.example.com include:_spf-b.example.com include:_spf-c.example.com -all"
  • redirectはredirectの右に指定したメールサーバのSPF設定しか利用できません。自分のSPF設定を追加できません。完全に委任するということです。
example.net. IN TXT "v=spf1 redirect=example.org"
  • まとめると、includeとredirectの違いは自分のSPF設定を追加できるかどうかです。

メールログの時間がずれる時の対処

メール送信と受信のため、PostfixDovecotをインストールしましたが、
メールログ(/var/log/maillog)に出力される時間がサーバ時間と9時間もずれていました。
色々ググって試して失敗したりしましたが、ついに解決できましたので、メモします。

以下のコマンドを実行したら、いけました。rootで行う必要があります。

cd /var/spool/postfix
mkdir etc #既にあれば、スキップ
cd etc
cp /etc/localtime .
service postfix restart
service rsyslog restart

殆どのブログでは/etc/localtimeを/var/spool/postfix/etcにコピーすればいいと言っていましたが、
私が使うサーバではrsyslogの再起動も必要でした。

セッションストアに関してメモ

最近、RoR(Ruby on Rails)を勉強しています。

RoRでセッションデータはデフォルトでブラウザのクッキーに保存されます。
最初はServletのようにセッションIDだけ、ブラウザに送られて、実際のデータはサーバのメモリに保存されると思いました。

しかし、そうではなく、セッションに入れたすべてのデータがブラウザのクッキーに保存されるのでした。
これはまずいんじゃない?と思いました。
なぜなら、たとえ、クッキーのセッションデータが暗号化されていても理論的には復号化できるので、
セキュリティ上、安全ではないからです。

幸い、RoRではセッションストアを変更できます。
セッションデータをDBはもちろんのこと、Redisやmemcachedなどのキャッシュサーバに保存することも可能です。

RoRで本格的にウェブアプリケーションを開発する時にはセッションストアをクッキー以外に変更しましょう。
以下のgemはセッションデータをDBに保存します。
github.com

以下はredisです。
github.com

最後にmemcachedです。
github.com


どのセッションストアもセッションデータを一つのAPサーバのメモリでなく、クッキーやDBなどの共有ストレージに保存するとの共通点がありますね。
こうなると、APサーバの冗長化時にセッションを同期するためにクッキーにサーバのIDを送るとか、セッションレプリケーションなどを行う必要がないから、便利だと思います。