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

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

タグ:java

Java で byte 型配列に格納した画像データの情報に対して、幅や高さを調べたり、少し手を加えたりする目的でいったん BufferedImage 型に変換したり、その逆の変換を行う機会があったので、その時の作業メモです。

byte 型配列から BufferedImage 型への変換
byte[] img = new byte[];
    :
  (img にデータを格納する処理(省略))
    :
try{
  BufferedImage image = ImageIO.read( new ByteArrayInputStream( img ) );
    :
}catch( Exception e ){
}

BufferedImage 型から byte 型配列への変換
byte[] img = new byte[];
BufferedImage image = null;
    :
  (image にデータを格納する処理(省略))
    :
try{
  ByteArrayOutputStream bos = new ByteArrayOutputStream();
  BufferedOutputStream os = new BufferedOutputStream( bos );
  image.flush();
  ImageIO.write( image, "png", os ); //. png 型
  img = bos.toByteArray();
    :
}catch( Exception e ){
}

byte 型配列から BufferedImage 型への変換は ImageIO クラスを使って一発でできますが、逆の BufferedImage 型から byte 型配列へはフォーマット(上記の場合だと PNG)を指定する必要がある、という点に注意です。


Java で(Web)アプリケーションから REST API を実行する時など、HTTP のクライアント機能を java.net.* から作るのは面倒です。現実的にはなんらかのライブラリを使うことになると思います。

そんな場合によく使われるのが Apache HTTP Client だと思ってます。2017/Mar/16 現在の最新バージョンは 4.5.3 でした。モジュールはこちらからダウンロードできます:
https://hc.apache.org/downloads.cgi

2017031501


上記サイトの HttpClient カテゴリから 4.5.3.zip と書かれたリンクをクリックすると 4.5.3 のバイナリが zip アーカイブとして取得できます(ファイル名は httpcomponents-client-4.5.3-bin.zip)。ダウンロードした zip ファイルを展開し、lib フォルダから jar ファイル群を取り出します(今回のサンプルで最低限必要なのは以下の6ファイルです):
  • commons-codec-1.4.jar
  • commons-logging-1.2.jar
  • httpclient-4.5.3.jar
  • httpclient-cache-4.5.3.jar
  • httpcore-4.4.6.jar
  • httpmime-4.5.3.jar

Eclipse 等で Java のウェブアプリケーションプロジェクトを作成し、lib フォルダ(Webcontent/WEB-INF/lib など)に上記作業で取り出した jar ファイル群をまとめてコピーしておきます。これで準備完了:
2017031502


では実際にこれらのモジュールを使って HTTP アクセスを実現するプログラムを書いて実行してみます。今回はスタンドアロンに HTTP GET を実行する、こんなプログラムにしてみます:
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient1 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://www.ibm.com/developerworks/jp/"; //. HTTP GET する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpGet get = new HttpGet( url );
      CloseableHttpResponse response = client.execute( get );
      int sc = response.getStatusLine().getStatusCode(); //. 200 の想定
      HttpEntity entity = response.getEntity();
      String html = EntityUtils.toString( entity, "UTF-8" );
      System.out.println( html ); //. 取得結果をコンソールへ
      client.close();
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

指定した URL(上記の場合は https://www.ibm.com/developerworks/jp/")に HTTP でアクセスして、GET した結果をコンソールに出力する、というものです。この内容を記述したファイル(HttpClient1.java)を Eclipse から実行します:
2017031503


で、指定した URL  の HTML が取得できることを確認します。HTTP GET は呼び出すだけなのでシンプルですね:
2017031504


アクセス先として HTML のようなテキストではなく、画像のようなバイナリデータの場合は以下のように byte 配列として結果を取得します(HTTP リクエストヘッダを設定する例も加えています):
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient2 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://dw1.s81c.com/developerworks/i/f-ii-ibmbluemix.png"; //. HTTP GET する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpGet get = new HttpGet( url );
      get.addHeader( "User-Agent", "MyBot/1.0" );  //. HTTP リクエストヘッダの設定
      CloseableHttpResponse response = client.execute( get );
      int sc = response.getStatusLine().getStatusCode(); //. 200 の想定
      HttpEntity entity = response.getEntity();
      byte[] img = EntityUtils.toByteArray( entity );
      System.out.println( "" + img.length ); //. 取得結果をコンソールへ
      client.close();
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

一方、HTTP POST の場合も同様ですが、GET の時との違いとしてポストデータを送信する必要もあります。以下はテキスト情報とファイルのアップロードを同時に(Multipart で)送信する場合の例です:
import java.io.File;
import java.io.FileInputStream;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient3 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://xxx.com/posturl"; //. HTTP POST する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpPost post = new HttpPost( url );

//. 文字情報2つとファイル1つをポスト MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody( "name", "K.Kimura", ContentType.TEXT_PLAIN ); builder.addTextBody( "email", "dotnsf@jp.ibm.com", ContentType.TEXT_PLAIN ); File f = new File( "./logo.png" ); builder.addBinaryBody( "image_file", new FileInputStream( f ), ContentType.APPLICATION_OCTET_STREAM, f.getName() ); HttpEntity multipart = builder.build(); post.setEntity( multipart ); CloseableHttpResponse response = client.execute( post ); int sc = response.getStatusLine().getStatusCode(); //. 200 の想定 HttpEntity entity = response.getEntity(); String html = EntityUtils.toString( entity, "UTF-8" ); System.out.println( html ); //. 取得結果をコンソールへ client.close(); }catch( Exception e ){ e.printStackTrace(); } } }

PUT や DELETE の場合も同様に。

Tomcat やら Jetty やらといった Java アプリケーションコンテナ(Java アプリケーションサーバー)の種類に依存しない形でユーザー認証を実現するサンプル を作ってみました。実装にはフィルタを使います。また今回は認証の種類に Basic 認証を使っています。


まずは以下のような javax.servlet.Filter インターフェースの実装となるクラス: BasicAuthenticationFilter を作ります:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimeUtility;

public class BasicAuthenticationFilter implements Filter{
  //. レルム名
  private final String realmName = "myRealm";


  //. Filter の実装に必要なメソッド(何もしない)
  public void init( FilterConfig config ) throws ServletException{
  }
  public void destroy(){
  }

  //. フィルタリング処理の実装
  public void doFilter( ServletRequest req, ServletResponse res, FilterChain filterChain ) throws IOException, ServletException{
    ByteArrayInputStream bin = null;
    BufferedReader br = null;

    boolean isAuthorized = false; //. この値で認証の可否を判断する
    try{
      HttpServletRequest httpReq = ( HttpServletRequest )req;
      String basicAuthData = httpReq.getHeader( "authorization" );
      if( basicAuthData != null && basicAuthData.length() > 6 ){
        //. Basic認証から情報を取得
        String basicAuthBody = basicAuthData.substring( 6 ); //. 'Basic dG9tY2F0OnRvbWNhdA== ' 

        //. BASE64 デコード
        bin = new ByteArrayInputStream( basicAuthBody.getBytes() ); 
        br = new BufferedReader( new InputStreamReader( MimeUtility.decode( bin, "base64" ) ) );
        StringBuilder buf = new StringBuilder();
        String line = null;
        while ( ( line = br.readLine() )!=null ) {
          buf.append( line );
        }

        //. 入力された username と password を取り出す
        String[] loginInfo = buf.toString().split( ":" );
        String username = loginInfo[0];
        String password = loginInfo[1];
//.     System.out.println( "Basic " + username + ":" + password );

        //. 取り出した username と password で認証可否を判断する

        //. 実際にはここで LDAP やユーザー情報データベースと比較して判断することになる
        isAuthorized = true; //. 今回の例ではとりあえず何かが入力されていれば認証 OK とする
      }

      if( !isAuthorized ){
        //. (認証に何も指定されていなかった場合も含めて)認証 NG だった場合はブラウザに UnAuthorized エラー(401)を返す
        HttpServletResponse httpRes = ( HttpServletResponse )res;
        httpRes.setHeader( "WWW-Authenticate", "Basic realm=" + this.realmName );
        httpRes.setContentType( "text/html" );
        httpRes.sendError( HttpServletResponse.SC_UNAUTHORIZED ); //. 401

        //. 最初に認証なしでアクセスした場合はここを通るので、その結果ブラウザが認証ダイアログを出す、という流れ
      }else{
    	//. 認証 OK だった場合はそのまま処理を続ける
        filterChain.doFilter( req, res );
      }
    }catch( Exception e ){
      throw new ServletException( e );
    }finally{
      //. ストリームのクローズ
      try{
        if( bin!=null ) bin.close();
        if( br !=null ) br.close();
      }catch( Exception e ){
      }
    }
  }
}



また、web.xml の 内に以下の <filter> と <filter-mapping> の記述(青字部分)を追加します。この例では全ての URL (/*) に対して認証をかけるよう指定して います:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" 

xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 

http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>BasicAuth</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

  :
  :

  <!-- Filter Configuration -->
  <filter>
    <filter-name>basicAuthFilter</filter-name>
    <filter-class>me.juge.basicauth.BasicAuthenticationFilter</filter-class>
  </filter>

  <!-- Filter Mapping -->
  <filter-mapping>
    <filter-name>basicAuthFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

</web-app>



後は確認用の index.html として適当な内容のものを用意します:
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>ハローワールド!</h1>
</body>
</html>
↑超適当!


こうして作成した Java アプリケーションを動かして、ウェブブラウザからコンテキストルート(/) にアクセスすると、(初回は認証情報を付けずにアクセスする ので)作成したフィルタから 401 が返され、結果以下のような認証ダイアログが表示されるはずです:
2017031101


ここに適当な内容の文字列を入力して再度アクセスすると、(今度は何かが入っていたことになるので)上記サンプルでは認証 OK という判断になり、用意した index.html が表示される、という流れが実現できます:
2017031102



今回のサンプルはこちらに公開します:
https://github.com/dotnsf/BasicAuth












 

IBM Notes 9.0.1 の FP(Feature Pack) 8 がリリースされました。有効なサポート契約をお持ちであれば、以下の FixCentral サイトからダウンロードして適用いただくことが可能です:
http://www-01.ibm.com/support/docview.wss?uid=swg24037141


適用後にメニューから ヘルプ - IBM Notes について を実行すると、リリースバージョンが変更できていることが確認できます。自分の環境にも早速適用してみました:
2017030901


実は以前にこのブログの中で、「ノーツでワトソン」というエントリを紹介したことがありました(比較的アクセス数の多い、人気エントリの1つです):
http://dotnsf.blog.jp/archives/1062359514.html

↑この中でも触れているのですが、Watson API のセキュリティ仕様変更があり、JDK 1.8 未満の環境から Watson の REST API へ https リクエストを実行するとエラーが返されるようになってしまっていました(ノーツは JDK 1.6 を使っていたのでエラーが発生していました。上記エントリはその回避方法も含めて紹介しているものです)。

が、今回の 9.0.1 FP8 では、内蔵 JDK バージョンが 1.8 に変更される、という大きな変更が含まれていることになっています。これによって Watson API のセキュリティ要件を満たすことになるので上記エントリのような回避策も不要になることが期待できます。

というわけで、IBM Notes 9.0.1 FP8 の実際の JDK バージョンを確かめるためのプログラムを作って実行してみることにします。まずは Domino Designer で適当なデータベースアプリケーションを作り、その中に Java のエージェントを新規に作成します(エージェントの名称は FP8 としました):
2017030902


Java エージェントの中身を記述します:
2017030903


具体的には以下のようなコードを記述しています(初期状態の内容と比較して、青字部分を追記しています):
import java.util.Properties;

import lotus.domino.AgentBase;
import lotus.domino.AgentContext;
import lotus.domino.Session;

public class JavaAgent extends AgentBase {
  public void NotesMain() {

    try {
      Session session = getSession();
      AgentContext agentContext = session.getAgentContext();

      // (Your code goes here)
      Properties props = System.getProperties();
      props.list(System.out);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
}

↑システム内の全プロパティを取得して、標準出力(System.out)にプロパティ名とその値を表示する、という内容です。Java のバージョンもプロパティの1つなので、この出力結果の中に含まれているはずです。

このエージェントをメニューから実行できるよう、基本プロパティのトリガーはイベントで「アクションメニューの選択」、対象は「データベースの全ての文書」とした上で、このエージェントを保存します:
2017030904


では作成したエージェントを実行してみます。結果は標準出力に出されるので、あらかじめメニューのツールから、Java デバッグコンソールの表示 を選択して、出力画面を表示しておきます:
2017030905


改めてメニューから アクション - FP8 を実行します:
2017030906


するとエージェントの実行結果が Java デバッグコンソールに追加記載される様子が確認できます:
2017030907


画面を少しずつ下にスクロールしていくと、"java.version" というプロパティの値が "1.8.0" になっていることが確認できました。ちゃんと(?) 1.8 が使われているようです:
2017030908


 というわけで、IBM Notes 9.0.1 FP8 からは JRE/JDK 1.8 が使えるようになりました。これで Java プログラミング時に互換性を心配しながらコーディングする必要はなくなりました。


IBM ワトソンなどの各種コグニティブエンジンを使う際に、重要なのは学習データだと思っています。

要はコグニティブエンジンを使って問い合わせを行うわけですが、その問い合わせする前に何らかの学習をする必要があり、そこで何をどれだけ学習させたかによって問い合わせの精度が変わってくるからです。

で、先日こんなツイートをしました:
2017022601

https://twitter.com/dotnsf/status/834959690803007488


↑は、その学習データを何らかの方法で集める際に、(非同期処理で集めるのではなく)マルチスレッド処理がいいのではないか、と思ってつぶやいたのでした。このことをもう少し詳しく紹介しようと思います。なお、あくまで特定条件下での個人見解なので他の人の意見も聞いてみたいと思ってます。

やろうと思っているのは、大まかにはこんな内容です:
  1. あらかじめ用意したインターネット上の URL のリストから、その HTML を取得して学習データにしたい
  2. この「リスト」が大量にあるケースを想定する
  3. なお、リストの中には実在しない URL が存在している可能性がある(つまりリストそのものが間違っている可能性がある)
  4. また URL は実在しているのだが、ネットワーク障害や DNS の設定ミスなど何らかの原因でアクセスする際に非常に長い時間がかかる(長い時間がかかった結果、タイムアウトになったり成功したりする)場合もある

1と2はごく普通の条件ですよね。ここではインターネット上の URL のリストは配列変数で用意されているものとしましょう。配列から1つずつ URL を取り出して、その URL の HTML を取得する、という処理を施すことになります。

問題は3と4の条件です(でも現実的に想定すべき条件だと思ってます)。3は用意されたリストそのものが間違っているというケース、つまり与えられた URL は "Unknown Host" になったり、404 などのエラーが返ってくることを想定しないといけない、ということです。

また4は更にややこしい条件です。成功するかしないかではなく、こちらからは手が出せない箇所のなんらかの障害によって目的の URL へのアクセスに非常に時間がかかってしまうケースです。時間がかかった結果、目的の HTML が取得できればいいのですが、最終的にタイムアウトエラーが発生することもあり得る、というケースを想定する必要があります。


要するにこれらを想定したエラー対策が必要になるのですが、まずは3と4を無視して(エラーが発生しない前提で)普通にアルゴリズムを考えるとこんな感じになるでしょうか:
//. URL の配列
urls = [
  "http://xxx.com/a1.html",
  "https://xxx.com/a2.html",
  "https://yyy.net/",
  "http://abc.com/xyz.html",
     :
     :
];

//. URL を1つずつ取り出して HTML を取り出す
forall( url in urls ){
  html = getHTML( url );
      :
      :
  (取り出した HTML を使って学習処理を行う)
      :
      :
}


↑の例ですと、getHTML 関数の中で実際に指定した URL にアクセスして HTML を取り出す、という処理をするものとします。そしてこの関数の中で3や4を原因とするエラーや時間がかかるといった現象が発生することを想定してみます。

3のケースは実は単純で、実在しない URL が指定された場合はこの関数の返り値を null などにして、null でなかった場合のみ処理を続ける、という判断を加えることで解決します:
//. URL の配列
urls = [
  "http://xxx.com/a1.html",
  "https://xxx.com/a2.html",
  "https://yyy.net/",
  "http://abc.com/xyz.html",
     :
     :
];

//. URL を1つずつ取り出して HTML を取り出す
forall( url in urls ){
  html = getHTML( url );
  if( html != null ){ //. 3の対策
      :
      :
  (取り出した HTML を使って学習処理を行う)
      :
      :
  }
}


さて問題は 4 のケースです。3 のアルゴリズムを単純に(シーケンシャルに)実行した場合、特定の URL から HTML を取り出す処理に時間がかかってしまうような事態が発生すると、リストの途中で処理が先に進まなくなってしまう、ということになります。このようなケースを想定した上で効率よく処理を実行するにはどう改良すべきでしょうか?

1つの方法として考えたのが非同期処理です。上記のループ部分を非同期に処理して、リスト内の各 URL へのアクセスを非同期に行う、という方法です。アルゴリズムにするとこのような感じになるでしょうか:
//. URL の配列
urls = [
  "http://xxx.com/a1.html",
  "https://xxx.com/a2.html",
  "https://yyy.net/",
  "http://abc.com/xyz.html",
     :
     :
];

//. URL を1つずつ取り出して HTML を取り出す
forall( url in urls ){
  html = getHTML( url, function( err, html ){  //. getHTML を非同期に実行する
    if( err ){
      //. 3. のエラー処理
    }else{
//. 成功した場合
: : (取り出した HTML を使って学習処理を行う) : : } }); }
↑非同期ということで JavaScript っぽくしてみました

このように非同期に処理を行うことで、「各 URL の HTML を取得する」という命令を全て先に実行しておき、(成功にせよエラーにせよ)取得の処理が終わったらそれぞれの続きを行う、というロジックです。こうすることで 4 のような事態が発生しても、正常な URL に対する処理は邪魔されることなく先に終了し、時間のかかる処理だけが後から実行される、という一見きれいな形になります。

しかし、この方法にも問題点がありました。それは URL のリストが膨大だった場合です。上記のコードが非同期に実行されるとまず全て URL に対する HTML 取得のリクエストが発行されます。そしてその処理はシステムのメモリ量や TCP ソケット数上限を超えて実行されてしまう可能性があります。この部分はコーディングというよりもシステムのメモリ管理やソケット数管理などの厄介そうな処理を行う必要がでてきてしまいます。

で、冒頭のマルチスレッドです。上記の非同期で行っていた部分をマルチスレッドに書き換えることで、(スレッド生成間のスリープ時間を調整するなどの)ある程度のスケジュール調整をした上で同時に HTML を取得する処理を行うことができるようになります。例えばこんな感じです(ここは言語依存がありそうなので、Java 丸出しで記述しています):
//. URL の配列
urls = [
  "http://xxx.com/a1.html",
  "https://xxx.com/a2.html",
  "https://yyy.net/",
  "http://abc.com/xyz.html",
     :
     :
];

//. URL を1つずつ取り出して HTML を取り出す
forall( url in urls ){
  //. 子スレッドを生成して、スレッドの中で取得処理を実行
  Download dl = new Download( url );
  Thread t = new Thread( t );
  t.start();

  try{
    Thread.sleep( 1000 ); //. 1秒待ってから次の URL を処理
  }catch( Exception e ){
  }
}


//. マルチスレッドで動くインスタンス
public class Download implements Runnable{
  private String url = "";
  public Download( String url ){
    this.url = url;
  }

  public void run(){
    html = getHTML( url );
    if( html != null ){
      :
      :
  (取り出した HTML を使って学習処理を行う)
      :
      :
    }
  }
}

↑マルチスレッドなので Java 全開の記述法で

この方法であれば、各 URL に対する HTML の取得は別々のスレッドで行われるため、特定の URL で 4 のような遅延現象が発生しても、他の URL に対する処理がその遅延に巻き込まれることはありません。また、この例では1秒(1000ミリ秒)おきにスレッドを生成するようにしています。例えば1スレッドの処理が2秒程度かかるようなケースであっても、3つ目のスレッドが生成されたタイミングで最初のスレッドの処理は終了している可能性が高くなり、同時に実行されるスレッドの数がさほどは増えないようなアルゴリズムになっています。仮に 4 のようなケースが発生したとしても、そのスレッドだけはしばらくの間生き続けることになりますが、他のスレッドは生成しては処理されて消えてゆくという流れになるので、やはりシステムの限界を意識するような処理にはなりにくいアルゴリズムになっているのでした。


という所まで作っての「マルチスレッド処理が効率よい」という冒頭の結論に至ったわけでした。要するにこれなら深く考えずに作って動かしてもややこしい対応が必要になる心配が少ないかな、と。またマルチスレッド処理となると Java が得意とする処理ロジックであり、オッサン脳の出番が増えるかもしれないなあ、と感じたのでした。

このページのトップヘ