VPN経由でBacklogにアクセスできない時の対処法

よくある環境だけど、お客さんとこの開発サーバにアクセスしたいのだが、IP制限がかかっていて特定のIPからしかアクセスできないってことがあると思います。そんな時、お客さんに「このIPアドレスは自社サーバですので、このIPからのアクセスは許可してください」って申請して設定してもらいます。その後、自社サーバにVPN接続してお客さんのサーバにアクセスできるようになる、と。

そこで問題になるのが、VPN接続した際のデフォルトゲートウェイVPNサーバにする(大抵デフォルトはこうなると思うけど)と、ある特定のサイトに上手くアクセスできないことがあります。一般的に言われてる原因はMTUのサイズが経路の途中で変化するため、パケットが途中でロストしちゃうからみたいです。こちらのサイトが参考になりました。
http://jehupc.exblog.jp/21558500/

ただ、今回私が遭遇した現象はこれとはちょっと違ってて、MTUのサイズを小さくしてもダメで、MTUのサイズを+4すればアクセスできるようになる、という奇々怪々な方法で解決に至りました。どなたかお詳しい方、これでどうして接続できるようになるのか教えて欲しいです・・。こちらのサイトが参考になりました。
https://wiki.archlinuxjp.org/index.php/PPTP_%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC#ppp0:_ppp:_compressor_dropped_pkt

原因は正確にはわからんけど、とりあえずBacklogにアクセスできるようになったのでよしとしよう。

IOのまとめ

  • たぶんruby2.0から IO.read、IO.foreach、IO.readlines、IO.write などが思ったように使えるようになった
  • IO.readはエンコーディングを指定しなければ、「Encoding.default_external」がセットされる
  • IO.foreach、IO.readlines などはヘルプと少し食い違ってるけど、エンコーディングなどのオプション引数を指定できる
  • IO.write はエンコーディングを指定しなかったら、文字列のエンコーディングのままで書き込まれる。
# coding: utf-8

s = "--- エンコーディングはUTF-8です ---"
puts "リテラル = #{s.encoding}"
puts s

puts "Encoding.default_external => #{Encoding.default_external}" # => Windowsだと「Windows-31J」
# エンコーディングを指定しない場合は↑これが指定される

puts %q|■=== IO.read("cp932.txt") ===|
s = IO.read("cp932.txt")
puts "cp932.txt = #{s.encoding}" # => Windows-31J
puts s

puts %q|■=== IO.read("cp932.txt", :mode => "r:cp932") ===|
s = IO.read("cp932.txt", :mode => "r:cp932")
puts "cp932.txt = #{s.encoding}" # => Windows-31J
puts s

puts %q|■=== IO.read("utf8.txt") ===|
s = IO.read("utf8.txt")
puts "utf8.txt = #{s.encoding}" # => Windows-31J
puts s # 文字化け

puts %q|■=== IO.read("utf8.txt", :mode => "r:utf-8") ===|
s = IO.read("utf8.txt", :mode => "r:utf-8")
puts "utf8.txt = #{s.encoding}" # => UTF-8
puts s

puts %q|■=== IO.foreach("cp932.txt") ===|
i = 1
IO.foreach("cp932.txt"){|line|
  print "#{i}行目:"
  puts line
}

puts %q|■=== IO.foreach("utf8.txt") ===|
i = 1
IO.foreach("utf8.txt"){|line|
  print "#{i}行目:"
  puts line # 文字化け
}

# ヘルプには opts 引数について書かれていないが、やってみたらできた
puts %q|■=== IO.foreach("utf8.txt", :mode => "r:utf-8") ===|
i = 1
IO.foreach("utf8.txt", :mode => "r:utf-8"){|line|
  print "#{i}行目:"
  puts line
}

# これもヘルプには readlines(path, opts={}) というメソッドは無いけど、やってみたらできた
puts %q|■=== IO.readlines("utf8.txt", :mode => "r:utf-8") ===|
s = IO.readlines("utf8.txt", :mode => "r:utf-8")
puts s.join(",")
# ↑行末に \n が付加されているので注意

puts %q|■=== IO.write ===|
s = "ほげほげ\nふがふが"
IO.write("io-write-no-enc.txt", s) # => utf-8
IO.write("io-write-enc-utf8.txt", s, :mode => "w:utf-8") # => utf-8
IO.write("io-write-enc-cp932.txt", s, :mode => "w:cp932") # => cp932

先読み後読み

ずっと勘違いしてた模様・・。いやーもう20年近くプログラマしてるけどきちんと理解できてないことがなんと多いことか

http://d.hatena.ne.jp/a_bicky/20100530/1275195072
http://takahashikzn.root42.jp/entry/20090810/1249906831

Windows10を使いやすくする

Windows10の評判はイマイチだけどこうすれば使いやすくなります(必死に過去のWindowsへ戻してるだけ・・)

  • エクスプローラの行選択
  • デスクトップとかタスクバー、エクスプローラの外観や設定
    • 昔のWindows風にカスタマイズしてくれるClassic Shellというソフトで対応します
  • デスクトップとかウィンドウのフォントや幅などを調整
    • こちらの開発者さんが作成されているフリーソフトでカスタマイズできます。デフォルトだと、ウィンドウの境界が全っっ然わからん。タブレットとか携帯端末が常に全画面でアプリ使う前提ならそれでええけど、普通のデスクトップPCでこの設定ってアホじゃないかと思う、Microsoft
      • Meiryo UIも大っきらい!!
      • Re-Metrics
  • アイコンの配置を戻す
  • 音量ミキサー
    • デフォルトだと、新UIの音量インジケータが表示されるんだけど、俺は20ずつ音量の上げ下げをしたいねん!なので、こちらで紹介されているレジストリ修正を実施して、音量アイコンの動作を変更
      • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\MTCUVC\EnableMtcUvc
      • DWORDで「0」に設定

パフォーマンス確認

簡単にパフォーマンスチェックをしてみます。
id、name を持つテーブルに1万件insertしてみました。
特に意外な結果にはならなかったので、問題ないかなと思います。結果はソース中にコメントで書いておきました

Dao019.java
public interface Dao019 {
	@SqlUpdate("insert into table001 (id, name) values (:id, :name)")
	int insert(@Bind("id") int i, @Bind("name") String name);

	@SqlBatch(value = "insert into table001 (id, name) values (:id, :name)", transactional = false)
	int[] insertBatch(@Bind("id") List<Integer> ids, @Bind("name") List<String> names);

	void close();
}
Sample029.java
private static final int SIZE = 10000;

private static DBI dbi = null;
private static List<Integer> idList = new ArrayList<>();
private static List<String> nameList = new ArrayList<>();

private static Date d = null;
private static String tag = null;
private static void start(String tag)
{
	d = new Date();
	Sample029.tag = tag;
}

private static void stop()
{
	long time = new Date().getTime() - d.getTime();
	System.out.println(
		String.format("%s : %,.2f[s]", tag, time/1000D)
		);
}

private static void truncate()
{
	dbi.withHandle(new HandleCallback<Void>() {
		@Override
		public Void withHandle(Handle handle) throws Exception {
			handle.execute("truncate table table001");
			return null;
		}
	});
}

public static void main(String[] args) {
	String url = "jdbc:postgresql://192.168.52.128/jdbi";
	dbi = new DBI(url, "jdbi_user", "jdbi_pass");

	for( int i=0; i<SIZE; ++i )
	{
		idList.add(i);
		nameList.add("name"+i);
	}

	handleExecute(); // 9.89[s]
	handleInsert(); // 6.59[s]
	handleCreateBatch(); // 0.47[s]
	handlePrepareBatch(); // 0.69[s]
	// daoOnDemandInsert(); // さすがに毎回Open-CloseするonDemandでは計測不能
	daoOpenCloseInsert(); // 7.33[s]
	daoBatch(); // 0.36[s]
}

private static void handleExecute()
{
	truncate();

	dbi.withHandle(new HandleCallback<Void>() {
		@Override
		public Void withHandle(Handle handle) throws Exception {
			start("handleExecute");
			for(int i=0; i<SIZE; ++i )
			{
				String sql = String.format(
					"insert into table001(id, name) values(%d,'%s')",
					idList.get(i), nameList.get(i)
					);
				handle.execute(sql);
			}
			stop();
			return null;
		}
	});
}

private static void handleInsert()
{
	truncate();

	dbi.withHandle(new HandleCallback<Void>() {
		@Override
		public Void withHandle(Handle handle) throws Exception {
			String sql = "insert into table001(id, name) values(?,?)";

			start("handleInsert");
			for(int i=0; i<SIZE; ++i )
			{
				handle.insert(sql, idList.get(i), nameList.get(i));
			}
			stop();
			return null;
		}
	});
}

private static void handleCreateBatch()
{
	truncate();

	dbi.withHandle(new HandleCallback<Void>() {
		@Override
		public Void withHandle(Handle handle) throws Exception {
			Batch batch = handle.createBatch();

			start("handleCreateBatch");
			for(int i=0; i<SIZE; ++i )
			{
				String sql = String.format(
					"insert into table001(id, name) values(%d,'%s')",
					idList.get(i), nameList.get(i)
					);
				batch.add(sql);
			}
			batch.execute();
			stop();
			return null;
		}
	});
}

private static void handlePrepareBatch()
{
	truncate();

	dbi.withHandle(new HandleCallback<Void>() {
		@Override
		public Void withHandle(Handle handle) throws Exception {
			String sql = "insert into table001(id, name) values(?,?)";
			PreparedBatch b = handle.prepareBatch(sql);

			start("handlePrepareBatch");
			for(int i=0; i<SIZE; ++i )
			{
				b.add(idList.get(i), nameList.get(i));
			}
			b.execute();
			stop();
			return null;
		}
	});
}

private static void daoOnDemandInsert()
{
	truncate();

	Dao019 dao = dbi.onDemand(Dao019.class);
	start("daoOnDemandInsert");
	for( int i=0; i<SIZE; ++i )
	{
		dao.insert(idList.get(i), nameList.get(i));
	}
	stop();
}

private static void daoOpenCloseInsert()
{
	truncate();

	Dao019 dao = dbi.open(Dao019.class);
	start("daoOpenCloseInsert");
	for( int i=0; i<SIZE; ++i )
	{
		dao.insert(idList.get(i), nameList.get(i));
	}
	stop();
	dao.close();
}

private static void daoBatch()
{
	truncate();

	Dao019 dao = dbi.onDemand(Dao019.class);
	start("daoBatch");
	dao.insertBatch(idList, nameList);
	stop();
}

ログ出力

log4j で簡単にログ出力できます。
DBIにログ設定したり、Handleにもログ設定できます。
必要なところだけロギングしたければHandleに設定すればよいでしょう。
log4j.jar をクラスパスに加えないとコンパイルできないかもしれませんのでご注意を。

Sample028.java
private static DBI getDBI()
{
	return new DBI("jdbc:postgresql://192.168.52.128/jdbi", "jdbi_user", "jdbi_pass");
}

public static void main(String[] args) {
	BasicConfigurator.configure();

	logAtDBI();
	logAtHandle();
}

private static void logAtDBI()
{
	DBI dbi = getDBI();
	dbi.setSQLLog(new Log4JLog());
	dbi.withHandle(new HandleCallback<Void>()
	{
		@Override
		public Void withHandle(Handle h) throws Exception
		{
			h.execute("truncate table table001");
			h.insert("insert into table001(id, name) values(?,?)", 1, "name1");
			h.select("select * from table001 where id = ?", 1);
			return null;
		}
	});
}

private static void logAtHandle()
{
	DBI dbi = getDBI();
	dbi.withHandle(new HandleCallback<Void>()
	{
		@Override
		public Void withHandle(Handle h) throws Exception
		{
			h.update("update table001 set name = 'name100' where id = 1");
			h.createStatement("select * from table001 where id = :id").bind("id", 1).execute();

			h.setSQLLog(new Log4JLog());

			h.update("delete from table001 where id = ?", 1);
			h.execute("select * from table001");
			return null;
		}
	});
}