2016年9月24日土曜日

Windowsでopensslを使う

Windowsでopensslコマンドを使った時に嵌ったので、今後のためにメモ。

本家のOpenSSLではソースコードのみを配布しているためちょっと使おうと思ったときには敷居が高い。 バイナリ版はいくつかあるようだが Win32 OpenSSLがx64版もありよさそうな感じである。 ここから "Win64 OpenSSL v1.1.0a" を落としてきてインストール。コマンドラインから実行すると以下のエラーが出た。

C:\> c:\OpenSSL-Win64\bin\openssl.exe req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem
Can't open C:\Program Files\Common Files\SSL/openssl.cnf for reading, No such file or directory
756:error:02001003:system library:fopen:No such process:crypto\bio\bss_file.c:74:fopen('C:\Program Files\Common Files\SSL/openssl.cnf','r')
756:error:2006D080:BIO routines:BIO_new_file:no such file:crypto\bio\bss_file.c:81:
Generating a 2048 bit RSA private key
..................+++
..........................................................+++
writing new private key to 'server.pem'
-----
unable to find 'distinguished_name' in config
problems making Certificate Request
756:error:0E06D06A:configuration file routines:NCONF_get_string:no conf or environment variable:crypto\conf\conf_lib.c:272:

openssl.cnf というファイルが見つからないというエラーが出ている。 しかも、インストール先とは全く関係ない C:\Program Files\Common Files\SSL というフォルダを探しに行っている。

調査したところOPENSSL_CONFという環境変数にopenssl.cnfファイルの場所を指定すればよいらしいとのこと。 このファイルはOpenSSLインストール先のbin\snfフォルダにある、試しに以下のように設定して実行してみた。

C:\>set OPENSSL_CONF=C:\OpenSSL-Win64\bin\cnf\openssl.cnf

C:\> c:\OpenSSL-Win64\bin\openssl.exe req -newkey rsa:2048 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem

---
Generating a 2048 bit RSA private key
..........................................................+++
..........................................................+++
writing new private key to 'server.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:Setagaya-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Company K.K.
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.my-company.com
Email Address []:
---

うまくいったようだ、

2016年9月2日金曜日

WindowsでUUIDを作る

プログラムを作っているとテストデータとしてUUIDを作りたいことがたまにある。 Windowsの場合、VisualStudioをインストールしていればguidgen.exeとツールが付属しているのでそれを使うことができるが、インストールしていない環境でもPowerShellを使って出力できる。PowerShellから以下のコマンドをたたく。
[System.GUID]::NewGuid().ToString()
コマンドプロンプト派の人は、PowerShellを起動するまでもなく以下で。
@powershell "[System.GUID]::NewGuid().ToString()"
ほかにはWSHを使ってもできると思うけどコマンドラインから直接できるかどうかは不明。

2016年9月1日木曜日

Bluemix CFアプリ よく使うコマンド

BluemixへのCFアプリ配備でよく使うコマンドのまとめ。

Bluemixへのログイン

IDとパスワードを入れ、スペースを選択する。そのあとスペースを選択。
cf login -a https://api.ng.bluemix.net

組織(org)-スペース(space)-アプリ(app)

のような関係になっているらしい。

スペース中のアプリ一覧

cf apps

アプリ配備

アプリの置いてあるフォルダで以下を実行。メモリは128MB割り当て。
cf push {アプリ名} -m 128M

アプリとめる

cf stop {アプリ名}

アプリの環境変数一覧

CFアプリには環境変数が設定できコードに埋め込みたくない引数などはそこに定義できる。以下で一覧を表示。
cf env {アプリ名}

環境変数を設定

以下はLINEボットを作った時の設定例。 チャネルIDなどはコードに埋め込みたくないので環境変数で設定しプログラムで読み込むようにした。
cf set-env {アプリ名} BOTAPI_CHANNEL_ID {LINEチャネルID}
cf set-env {アプリ名} BOTAPI_CHANNEL_SECRET {LINEチャネルシークレット}
cf set-env {アプリ名} BOTAPI_MID {LINE MID}

変更したら以下コマンドで反映。

cf restage {アプリ名}

動作確認

以下のURLで動作確認できる

https://{アプリ名}.mybluemix.net/

ログを見る

cf logs {アプリ名}

メモリ量変更

メモリは割り当てを64MBに変更。
cf scale {アプリ名} -m 64M

ダッシュボード

https://new-console.ng.bluemix.net/

2016年8月27日土曜日

Bluemixコンテナ よく使うコマンド

IBMのBluemix、他のサービスより制限のゆるい無料枠が多く設定されており実験レベルでは全然使えるので重宝している。 (いつまでこれが続くのかわからないが・・・) 以下よく利用するコマンドのまとめ。

Bluemixログイン

cf login -a https://api.ng.bluemix.net

IBM Containers にログイン

cf ic login

名前空間の確認方法

cf ic namespace get

利用できるイメージ一覧を表示

cf ic images

イメージを起動

cf ic run -p 9080 registry.ng.bluemix.net/ibmliberty:latest

実行中のコンテナを表示

cf ic ps

コンテナ一覧

cf ic ps -a

コンテナのPublic IPを要求

2つまで無料で取れる。
cf ic ip request

Public IP一覧

cf ic ip list

Public IPをコンテナに結び付け

cf ic ip bind  {IPアドレス} {コンテナのID}

IPの結び付け解除

cf ic ip unbind {IPアドレス} {コンテナのID}

コンテナを停止

cf ic stop {コンテナのID}

コンテナ削除

cf ic rm {コンテナのID}

Publicイメージを実行

Publicイメージを実行するには一旦privateリポジトリにコピーする。
cf ic cpi nginx registry.ng.bluemix.net/myapp/nginx
cf ic run -d -p 80 registry.ng.bluemix.net/myapp/nginx

2016年8月26日金曜日

mysqldump

mysqldumpで日本語名のテーブルをエクスポートしようと思ったが、指定したものとは別のテーブルがエクスポートされてしまう。 コマンドプロンプトからテーブル名を指定したので文字コードが中でおかしくなっているのかもしれない。 環境はWindows8.1のコマンドプロンプトから、DBはUTF-8で作成している。 今のところ解決方法が見つからず・・・・

2016年8月23日火曜日

JavaからJavaScriptを実行したときのパラメータ受け渡し

JavaでJavaScriptを実行するにはjavax.script.ScriptEngineを使用する。 実行の際にはパラメータを渡したり計算結果を受け取ったりするがそのための方法がリファレンスを読んでもわかりにくかったのでどう受け渡しができるのかを試してみた。

パラメータをひとつづつ渡す
パラメータを渡すにはScriptEngineのputメソッドにJavaScriptでの変数名を指定する。 JavaScript側では通常の変数として利用できる。

try {
  ScriptEngineManager factory = new ScriptEngineManager();
  ScriptEngine e = factory.getEngineByName("js");
  e.put("a", "hello");
  e.put("b", "world");
  
  String script = "ab=a + ' ' +b;";  
  Object ret = e.eval(script);
  Bindings b = e.getBindings(ScriptContext.ENGINE_SCOPE);
  for(String key : b.keySet()) {
    Object v= b.get(key);
    System.out.println("key=" + key + "; value=" + v + " (" + v.getClass() + ")");
  }
} catch (ScriptException ex) {
  ex.printStackTrace();
}
実行結果
key=a; value=hello (class java.lang.String)
key=b; value=world (class java.lang.String)
key=ab; value=hello world (class java.lang.String)

retには最後に実行した式の結果が入る。ここではabの値と同じ。 パラメータをMapで渡す
Mapで渡したパラメータはJavaScript側ではオブジェクトのプロパティとして使用できる。
try {
  ScriptEngineManager factory = new ScriptEngineManager();
  ScriptEngine e = factory.getEngineByName("js");
  Map<String, Object> map = new HashMap();
  map.put("x", "hello");
  map.put("y", "world");
  e.put("map", map);
  
  String script = "x = map.x; xy=map.x+' '+map.y;";  
  Object ret = e.eval(script);
  Bindings b = e.getBindings(ScriptContext.ENGINE_SCOPE);
  for(String key : b.keySet()) {
    Object v= b.get(key);
    System.out.println("key=" + key + "; value=" + v + " (" + v.getClass() + ")");
  }
} catch (ScriptException ex) {
  ex.printStackTrace();
}
実行結果
key=map; value={x=hello, y=world} (class java.util.HashMap)
key=x; value=hello (class java.lang.String)
key=xy; value=hello world (class java.lang.String)

パラメータをListで渡す
Listで渡したパラメータはJavaScript側では配列として使用できる。
try {
  ScriptEngineManager factory = new ScriptEngineManager();
  ScriptEngine e = factory.getEngineByName("js");
  List<String> list = new ArrayList();
  list.add("Tokyo");
  list.add("Nagoya");
  list.add("Osaka");
  e.put("list", list);
  
  String script = "list0=list[0]; list1=list[1]; list2=list[2]; length=list.size();";  
  Object ret = e.eval(script);
  Bindings b = e.getBindings(ScriptContext.ENGINE_SCOPE);
  for(String key : b.keySet()) {
    Object v= b.get(key);
    System.out.println("key=" + key + "; value=" + v + " (" + v.getClass() + ")");
  }
} catch (ScriptException ex) {
  ex.printStackTrace();
}
実行結果
key=list; value=[Tokyo, Nagoya, Osaka] (class java.util.ArrayList)
key=list0; value=Tokyo (class java.lang.String)
key=list1; value=Nagoya (class java.lang.String)
key=list2; value=Osaka (class java.lang.String)
key=length; value=3 (class java.lang.Integer)

Listの要素にMapを渡すこともできる。
try {
  ScriptEngineManager factory = new ScriptEngineManager();
  ScriptEngine e = factory.getEngineByName("js");
  List<Map<String, Object>> list = new ArrayList();
  Map<String, Object> map1 = new HashMap();
  map1.put("v1", 100);
  map1.put("v2", 200);
  map1.put("v3", 300);
  lista.add(map1);
  Map map2 = new HashMap();
  map2.put("v1", 110);
  map2.put("v2", 210);
  map2.put("v3", 310);
  lista.add(map2);
  e.put("list", list);
  
  String script = "list0_v1=lista[0].v1; list1_v1=lista[1].v1;";  
  Object ret = e.eval(script);
  Bindings b = e.getBindings(ScriptContext.ENGINE_SCOPE);
  for(String key : b.keySet()) {
    Object v= b.get(key);
    System.out.println("key=" + key + "; value=" + v + " (" + v.getClass() + ")");
  }
} catch (ScriptException ex) {
  ex.printStackTrace();
}
実行結果
key=list; value=[{v1=100, v2=200, v3=300}, {v1=110, v2=210, v3=310}] (class java.util.ArrayList)
key=list0_v1; value=100 (class java.lang.Integer)
key=list1_v1; value=110 (class java.lang.Integer)

2016年7月22日金曜日

BAT バッチファイルで今日の日付を取得

Windowsのバッチファイルで今日の日付を取得する方法。 Windows XPから8.1の環境だと、%DATE%という変数で今日の日付がYYYY/MM/DD形式で取得できる。
C:> echo %DATE%
2016/07/20
たとえばバッチファイル中で今日の日付のファイル名やフォルダを作りたいときはYYYYMMDD形式のほうが望ましいため、以下のように整形をすることができる。
C:> set TODAY=%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%
C:> echo %TODAY%
20160720
今はもう使う機会もほとんどないが、Windows 2000以前だと%DATE%の書式が違っており同じ値を取得するには以下のようにする。
set TODAY=%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%
どの環境でもYYYYMMDD形式で取得できるようにするには次のようにOSのバージョンで場合分けする。
for /f "tokens=1-3" %%i in ('ver') do set OSVER=%%k
if "%OSVER%"=="2000" (
    set TODAY=%date:~2,4%%date:~7,2%%date:~10,2%
) else (
    set TODAY=%date:~0,4%%date:~5,2%%date:~8,2%
)

2016年7月19日火曜日

JavaMail と AmazonSESでメールを送る

Amazon の提供するクラウドサービスに Amazon Simple Email Service(Amazon SES)というバルクメール送信サービスがある。SMTPのインターフェースが使えるとのことなので今回 JavaMail を使いJavaプログラムからメールを送信してみた。
Amazon SES を使うためには Amazon Web Service(AWS) のアカウントを持っている必要がある。 (アカウントの取り方は省略する)
EC2をすでに利用していれたので同じアカウントで利用することができた。Amazon SES のためには最低限、以下の設定が必要である。

  1. メールアドレスの登録
    送信元(From)およびテスト用の送信先(To)のメールアドレスの設定および承認をする。
  2. SMTP設定
    SMTPサーバーにアクセスするための認証情報などを設定する。
設定はどちらも Management Console から行う。 現時点でのそれぞれの設定方法は次のようになる。

1. メールアドレスの登録

送信元(From)とするメールアドレスの設定および承認をする。 また、サービス利用直後はsandboxモードというあらかじめ登録されたメールアドレスにしか送れないモードとなっているが、この送信先(To)のメールアドレスも同じように登録する。

メールアドレスは以下の手順で登録する。送信元(From)、送信先(To)のアドレスをそれぞれ登録する。

  1. Management Consoleのメニュー"Email Addresses"を選択
  2. [Verify a New Email Address]を押す。ダイアログボックスに送信元とするメールアドレスを入れる。
  3. 入力したメールアドレスに "Amazon SES Address Verification Request"というメールが来る。その中のリンクをクリック。
  4. 「Amazon Simple Email Service(Amazon SES)でのメールアドレスの検証が完了しました。このアドレスからのメール送信を開始できます。」というページが開く。
  5. 登録完了

2. SMTP設定

SMTPサーバーにアクセスするための認証情報などを設定する。

  1. Management Consoleのメニュー"SMTP Settings"を選択
  2. [Create My SMTP Credentials]を押す
  3. "IAM User Name" を要求される。既定の値をそのまま使い [Create] を押す。
  4. 認証情報が生成される。[Show User SMTP Security Credentials]を押すと以下のような内容が表示される。
ses-smtp-user.xxxx-xxxx
SMTP Username: xxxxxxxxxxxxxxx
SMTP Password: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

SMTP Username と SMTP Password は後で使うのでメモしておく。

ここまでできたらAmazon SESでメール送信することができるすることができる。 早速、JavaMailを使いJavaプログラムからメールを送信してみよう。 SMTPサーバーはポート25か465か587が使えるので、自分の環境にあったものを選べばよいかと思う。 自分のローカルPCから送る場合は、プロバイダの方でポート25を制限していることが多いと思うのでそれ以外を選ぶのがいいだろう。 また、Transport Layer Security (TLS)が必要なのでJavaMailのほうでその設定をする。

以下のサンプルプログラムはAmazon SESのリファレンスガイドに載っていたものとほぼ同じである。 ビルドしてローカルPCで動かすと1で登録したメールアドレスにメールが配信される。

package test;

import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class AmazonSESTest {

  // 1で登録したメールアドレス
  static final String FROM = "xxx@xxx.jp";
  static final String TO = "yyy@yyy.jp";

  // 2で作成したSMTP Username/Passwordを設定
  static final String SMTP_USERNAME = "xxxxxxxx";
  static final String SMTP_PASSWORD = "xxxxxxxxxxxxxxxxxx";

  // Amazon SES SMTP ホスト名
  static final String HOST = "email-smtp.us-east-1.amazonaws.com";

  // ポートは25,465または587を指定する
  static final int PORT = 587;

  private static Logger logger = Logger.getLogger(AmazonSESTest.class.getName());

  public static void main(String[] args) {
    try {
      AmazonSESTest app = new AmazonSESTest();
      app.start(args);
    } catch (Throwable ex) {
      logger.log(Level.SEVERE, "エラーが発生: " + ex.toString(), ex);
    }
  }

  private void start(String[] args) throws MessagingException {
    // JavaMailの設定
    Properties props = System.getProperties();
    props.put("mail.transport.protocol", "smtp");
    props.put("mail.smtp.port", PORT);
    
    // JavaMailでTransport Layer Security (TLS)を使う設定
    props.put("mail.smtp.auth", "true");
    props.put("mail.smtp.starttls.enable", "true");
    props.put("mail.smtp.starttls.required", "true");

    // JavaMail Session 作成
    Session session = Session.getDefaultInstance(props);

    // 送信メッセージを作成
    MimeMessage msg = new MimeMessage(session);
    msg.setFrom(new InternetAddress(FROM));
    msg.setRecipient(Message.RecipientType.TO, new InternetAddress(TO));
    msg.setSubject("テストメール", "utf-8");
    msg.setContent("AmazonSESから出したメール", "text/plain;charset=utf-8");

    // メール送信
    Transport transport = session.getTransport();
    try {
      logger.info("Amazon SES SMTP で送信中...");
      transport.connect(HOST, SMTP_USERNAME, SMTP_PASSWORD);
      transport.sendMessage(msg, msg.getAllRecipients());
      logger.info("送信完了");
    } finally {
      // 後始末
      transport.close();
    }
  }

}

最後に、Amazon SESでは個別の申請をしない場合には以下のような利用制限があるようである。 後者はproduction版を申請すれば解除されるようだが、それはまた後日試してみることにする。

  • 送信できるメール数、200メール/日、1メール/秒
  • 登録済みのメールアドレスにしか送信できない(sandboxモード)

2016年7月11日月曜日

Android エミュレーターでホストに通信するときのIP

Android エミュレーターでホストマシンのPCに通信するとき、PCのIPは 10.0.2.2

2016年6月15日水曜日

BATファイル CSVの読み込み

WindowsコマンドプロンプトのバッチファイルでCSVファイルを読み込んで逐次処理をするにはfor文を使う。 たとえば、次のようなカンマ区切りのCSVファイルがあったとする。 列は3つあり、1行目は列名のヘッダである。

品目,単価,個数
りんご,98,10
みかん,30,20
キウイ,78,30

このCSVを読み込んで表示するバッチは以下のようになる。

for /f "skip=1 tokens=1,2,3* delims=," %%i in (data.csv) do (
    echo 品目=%%i 単価=%%j 個数=%%k
)
このバッチはCSVファイルdata.csvを開き各行をを順次for文で処理する。 for文にオプションskip=1 を付け1行目のヘッダ行は無視するようにする。 また、列が3つあるのでtokens=1,2,3*を指定している。このとき読み込んだ列はそれぞれ変数%%i, %%j, %%kに入る。for文に指定しているのは%%iだけだが、forの仕様としてi,j,k...のアルファベット順の変数に列の値が自動で入る。 列の区切り文字はdelims=, オプションで指定する。

このバッチで先のCSVファイルを読み込むと次のような感じになる。

C:\Temp>readcsv.bat
品目=りんご 単価=98 個数=10
品目=みかん 単価=30 個数=20
品目=キウイ 単価=78 個数=30

データ中にカンマが入っているとそこが区切りと認識されるのでこの方法ではうまく動かないが、 簡単にCSVファイルを処理したい場合は十分役立つのではと思う。