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

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

2016/09

Microsoft が .Net アプリケーションを動かすためのフレームワークとしてオープンソース化を進めていた ".Net Core" の正式バージョン 1.0 がリリースされました:
https://www.microsoft.com/net/core

この .Net Core が .Net アプリケーションを動かすための基盤であり、同時に公開された ASP.Net Core は Web アプリケーションの開発基盤となる部分になります。従って .Net Framework のうちの GUI を除いた開発環境のオープンソース化が実現され、Linux や MacOS 上で動かすことができるようになった、ということになります。

というわけで、早速自分の CentOS 環境に導入して使ってみました。なお、.Net Core は CentOS 7 以降に対応しているので、CentOS 7.x 環境を用意した上で以下を説明します。

まずは .Net Core 1.0 をインストールします。CentOS 7 環境で(SSH などで)ターミナルログインし、以下のコマンドを実行します(この例では実体を /opt/dotnet/ 以下にインストールし、/usr/local/bin へシンボリックリンクしています):
$ sudo yum install libunwind libicu
$ curl -sSL -o dotnet.tar.gz https://go.microsoft.com/fwlink/?LinkID=827529
$ sudo mkdir -p /opt/dotnet
$ sudo tar zxf dotnet.tar.gz -C /opt/dotnet $ sudo ln -s /opt/dotnet/dotnet /usr/local/bin

インストールはこれで完了です。では実際にアプリを作ってみましょう。雛形として用意されているアプリがいわゆる "Hello World" なので、"helloworldapp" というフォルダを作り、その中にアプリを作ることにします:
$ mkdir helloworldapp
$ cd helloworldapp
$ dotnet new

上記の最後に入力している "dotnet new" コマンドで雛形作成を含めた初期化を行います。この初期化によって、Program.cs ファイルと project.json ファイルという2つのファイルが作成されます:
2016092901


このうちの Program.cs ファイルがソースコードに相当するファイルです。デフォルト状態では "Hello World!" という文字列をコンソールに表示(WriteLine)するだけの内容になっています:
2016092902


これを vi などのエディタで適当に書き換えてみましょう。以下の例では "ハロー .Net ワールド!!" という日本語文字列に変えてみました:
2016092903


もう1つの proejct.json ファイルは依存関係などのパッケージ情報が記述されています。こちらは今回は変更が不要です:
2016092904

ではいよいよ CentOS 上でこの .Net アプリを動かしてみましょう。"dotnet run" と入力します:
$ dotnet run

成功すると以下のようにコンパイルされ、実行されて、書き換えた文字列が表示されるはずです:
2016092906


なお実行時にコンパイルされるので、このコマンド実行後には bin と obj フォルダがそれぞれ生成されているはずです:
2016092901


今回は CentOS 7.x 上でのインストールから実行までを紹介しました。実行手順は全てで共通ですが、インストール方法はシステムによって若干異なります(Docker イメージも公開されてるんですね・・・)。詳しくは公式ドキュメントを参照ください:
https://www.microsoft.com/net/core


メール送信(送信だけじゃないけど)サービスの最大手と思われる SendGrid を使う機会がありました。SDK や API を使って、メールを送信できるサービスです。
2016090601


メール送信そのものは(sendmail などを使えば)単純に実現できますが、一度に大量のメールを送る場合や迷惑メール対策など、本格的に使おうとすると色々な面倒が待っています。そういう面倒な所を一手に引き受けて、心配なくメール送信が実現できる、というサービスです。

この SendGrid によるメール送信ですが、ダッシュボード画面から目的の相手にインタラクティブに送信するだけではなく、プログラミングのためのインターフェースが公開されているので、自分の作るアプリケーションから利用することもできます。ただ実際に送信するサンプルを Java でググると、多くの場合 SDK を使ったものが見つかります。自分は SDK ではなく Web API(REST API) での実現を考えていたので挑戦してみました。ウェブ上にあまり資料がなかったこともあって、その手順を以下に紹介します。


まず最初に大事なこと。SendGrid Web API の最新バージョンは V3 ですが、V2 を使います。SendGrid の推奨でもあります:
https://support.sendgrid.kke.co.jp/hc/ja/articles/206231901-Web API v2とv3どちらを利用すべきでしょうか?-


というわけで、Web API V2 の Mail API を使って実装することにします。リファレンスはこちらです:
https://sendgrid.kke.co.jp/docs/API_Reference/Web_API/mail.html


これを読むと、「メールを送信して結果を JSON で受け取る」場合は以下の様な REST API を実行することになります(これは curl コマンドで実行する場合の例):
$ curl -X POST https://api.sendgrid.com/api/mail.send.json -H 'Authorization:Bearer SG.*****' -d 'to=user1@recipient.com&subject=abcd&from=info@from.com&html=<b>ハロー</b>、ワールド'

上記コマンドは info@from.com ユーザーから user1@recipient.com ユーザーへ、メールサブジェクトは "abcd" 、本文は HTML で "<b>ハロー</b>、ワールド"(HTML として送信しているので、ハローだけが太字になります)を送信する場合のコマンドになります。 user1@recipient.com は送信先なので実在している必要がありますが、送信アドレスである info@from.com は実在している必要はありません。またヘッダの認証情報として使っている SG. で始まる文字列は SendGrid から取得した api key です。SendGrid のアカウントをお持ちで、まだ api key を取得していない場合、取得方法についてはこちらを参照してください。

上記 curl コマンドは(入力ミスなどがなければ)正しく実行されて、メールも送信されるはずです。つまり正しいコマンドです。これを REST API と見なして、同じ処理を Java で実装しなおせばいいわけですが、これが意外と手間取りました。

まず最初に、普段使っている Jakarta Commons HTTP Client 3.1 (メンテナンスモード)を使って、こんなコードを書いてみました(あらかじめ書いておきますが、以下のコードでは期待通りに動きません):
  :
public int sendMail( String to, String subject, String html ){
  int r = 0;

  try{
    String data = "to=" + to + "&from=info@from.com&subject=" + subject + "&html=" + html;
    PostMethod method = new PostMethod( "https://api.sendgrid.com/api/mail.send.json" );
    method.setRequestHeader( "Authorization", "Bearer SG.*****" );
    method.setRequestBody( data );
    HttpClient client = new HttpClient();
    int sc = client.executeMethod( method );
    String json = method.getResponseBodyAsString();
      :
r = 1; }catch( Exception e ){ mothod.setRequest e.printStackTrace();
r = -1; } return r; } :

オプションで指定している内容は curl コマンドのものと同じです(例えば Content-Type ヘッダを指定していませんが、curl でも指定せずに動いていたので)。ただこの sendMail 関数を to = user1@recipient.com, subject =  abcd, html = <b>ハロー</b>、ワールド というパラメータで実行した場合、ステータスコード(上記コード内の sc 変数)の値は 400 となり、また実行結果である json 変数の値は以下のようなものになりました:
{"errors":["Empty from email address (required)"],"message":"error"}

「必須項目である from 値が空である」というエラーメッセージのように見えます。しかしその値は上記のようにハードコーディングで入力しているつもりでした。

これまでも HTTP Client を Java で実装する場合はこの HTTPClient 3.x を使うことが多かったし、今回のようなポスト時のエラーに遭遇したことはなかったのですが・・・ まずここで躓きました。


次に試したのはポスト方法の変更です。sendMail 関数を以下の様な形に変え、ポストデータをプレーンテキストで送信するのではなく NameValuePair 配列で送信するように変更してみました(これもまだ期待通りには動きません):
  :
public int sendMail( String to, String subject, String html ){
  int r = 0;

  try{
    PostMethod method = new PostMethod( "https://api.sendgrid.com/api/mail.send.json" );
    method.setRequestHeader( "Authorization", "Bearer SG.*****" );
    List<namevaluepair> params = new ArrayList<namevaluepair>();
    params.add( new NameValuePair( "to", to ) );
    params.add( new NameValuePair( "from", "info@from.com" ) );
    params.add( new NameValuePair( "subject", subject ) );
    params.add( new NameValuePair( "html", html ) );
    method.setRequestBody( ( NameValuePair[] )params.toArray( new NameValuePair[0] ) );
    HttpClient client = new HttpClient();
    int sc = client.executeMethod( method );
    String json = method.getResponseBodyAsString();
      :
    r = 1;
  }catch( Exception e ){
    mothod.setRequest
    e.printStackTrace();
    r = -1;
  }

  return r;
}
  :

自分としては先程のコードと同じことを記載しているつもりでした。が、このコードは一応動いて、ステータスコードは 200 (成功)を返してくれます。

しかし、実際に送られてくるメールを受け取ると、残念ながら日本語部分が全て ? という文字に化けてしまっていました。。
2016090601
 (↑ ハロー、ワールド という結果を期待していたが文字化け)


文字化けということは文字コードの指定と実際の文字コードが違っていると読み、であれば強制的に UTF-8 で指定すれば・・・ と考えたのですが、この HTTPClient 3.1 には Content-Type で指定する以外の文字コード指定方法はありません( NameValuePair 配列で指定した本文がどのような内部処理をされているのかはわかりません。。) というわけで、この方法も詰み・・・

結論として、HTTPClient のバージョンをより新しいものに上げて対処しました。現在の最新バージョンは 4.5.2 のようです(僕は 4.5.1 を使いました):
https://hc.apache.org/httpcomponents-client-ga/

このライブラリに置き換えた上で、コードも以下のように 4.5.x 仕様に書き換えました(完成版です):
  :
public int sendMail( String to, String subject, String html ){
  int r = 0;

  try{
    PostMethod method = new PostMethod( "https://api.sendgrid.com/api/mail.send.json" );
    method.setRequestHeader( "Authorization", "Bearer SG.*****" );
    List<namevaluepair> params = new ArrayList<namevaluepair>();
    params.add( new BasicNameValuePair( "to", to ) );
    params.add( new BasicNameValuePair( "from", "info@from.com" ) );
    params.add( new BasicNameValuePair( "subject", subject ) );
    params.add( new BasicNameValuePair( "html", html ) );
    method.setEntity( new UrlEncodedFormEntity( params, "UTF-8" ) );
    CloseableHttpClient client = HttpClients.createDefault();
    CloseableHttpResponse response = client.execute( method );
    int sc = response.getStatusLine().getStatusCode();
    HttpEntity entity = response.getEntity();
    String json = EntityUtils.toString( entity, StandardCharsets.UTF_8 );
      :
    r = 1;
  }catch( Exception e ){
    mothod.setRequest
    e.printStackTrace();
    r = -1;
  }

  return r;
}
  :

この関数は期待通りに動き、実行後にメールを受け取ると、期待通りの HTML コンテンツが文字化けなしに表示されました:
2016090602


と、偉そうに書きましたが、実の所、なぜ前の2つのコードで動かないのかわかりません。最新コードでは NameValuePair 配列を UTF-8 指定で追加していて、これは旧バージョンにはない関数なので、その差で文字化けの有無になっているのかもしれません。ただ繰り返しますが、curl では最小限の指定だけで動いていたことと同じ指定をしているのに、Java でうまく動かない理由が説明できないのでした(少なくともエラーメッセージの内容をそのまま信用して対処しようとするとハマりそうな予感・・・)。

まあとりあえずは結果オーライ、ということで。あと SendGrid のメール送信を Java から、それも SDK ではなく Web API 経由で送る例はあまり見かけることがなかったので、後からやる人の助けになれば嬉しいです。


このページのトップヘ