タグ別アーカイブ: Java

Tomcat の PUTメソッドが息をしていない問題

| コメントをどうぞ

あけましておめでとうございます。
本年も有為無為に関わらず書き散らかしていく所存にございます。
生暖かい眼差しでお付き合いいただければと思います。
どうぞよろしくお願いいたします。
DiceK Mikamiです。

さて突然なのですが、アプリケーションサーバにTomcatを利用していると、たまにとんでもない大穴にはまったりします。
今回はそんなお話です。
続きを読む

java.util.Scannerクラスで構文解析を行ってみた。

| コメントをどうぞ

とある作業でREST APIを作成していたのですが、リクエストからいくつかの条件を受けてDBの値を検索するような仕組みが必要となりました。
ごく単純に実装するのであれば、以下の例のような感じで実装すれば良いかと思います。

例: ユーザーを名前で検索するような場合のエンドポイントとパラメータの場合
GET http://www.example.com/user?name=xxxx

・・・が。

しかし、上記の例では名前の完全一致で検索する場合はよいですが、

  • 前方一致、中間一致、後方一致についてはどうする?
  • 検索の条件値がnullや空文字列の場合は?
  • 複数の条件を ANDまたはOR条件で結合する場合は?
    などなど、アプリケーションでの要求仕様によりますがいかにも汎用性がありません。

そこでもっと汎用性を持たせたるためにGoogle AppsのAPIよろしく、

type=user and name like '%abc%' or name like '%def%'

というようなクエリ文字列をリクエストパラメータとして利用することにしました。

ただ、このような場合、送信されてきたクエリ文字列を解析し、検索対象パラメータ、演算子および検索対象値を正確に抽出する必要があります。
Javaの場合、高度な解析が必要なのであればBNFで構文を定義し、JavaCCにより解析プログラムを作成することになるのでしょうが、今回はより簡易的に実装を行うためjava.util.Scannerクラスを利用した実装例を紹介します。

続きを読む

ダブルMVCルーティングを追え!

| コメントをどうぞ

クライアントサイドのフレームワークが使いたい。
とくに、AngularJSなどは素敵だ。
使おうとなってみたところで、おい待てよと。
クライアントにもルーティングがあるよと。
サーバでルーティングしている場合どうなるんですかと。

よし、今日はレッドオクトーバーダブルMVCのルーティングを追ってみることにしよう。
どうも、DiceK Mikamiです。

 

0. 検証理由

チームで開発すると明確なローカル規約や仕様書が存在しない場合、ソース構成が驚きの汚さになります。
また、内容も属人的になってしまい、「これ作った奴、ちょっと俺に謝れ」となります。(なりませんか?
こうした問題を解決するため、構成の見通しの良さからサーバサイド、クライアントサイドとフレームワークを使いたくなるのも人情と言うもの。
で、その前段階として今回の検証をしてみようと思った次第であります。
 

1. 準備

今回は、サーバをSpring Bootで作り、クライアントをAngularJSで作ります。
サーバをRESTサーバにして、クライアントと完全に分離すると検証の意味がないので、サーバに内包する形でAngularJSを使います。
(というか、それが今回の肝。サーバのVがクライアントに繋がっていないとルーティングの謎を追えない)
イメージとしては、Spring BootのView部分がAngularJSで出来ていると言う感じ。

2. 仮説

仮説として、以下のようなフローになると考えています。

  1. ブラウザからの要求からサーバコントローラーが起動する。
  2. サーバコントローラーに紐づいているサーバビューが返却される。
  3. サーバビューがJSP(HTML)とした場合、まずビューが起動する。
  4. ビューに紐づいているクライアントコントローラーが動く。
  5. クライアントコントローラーによって計算された値が、クライアントビューに返却される。
  6. クライアントビューが表示が完了する。

ポイントは、サーバビューとはクライアント側の基底HTML(JSP)であること。
また、クライアントビューは、この基底HTMLの上にインジェクトされるHTMLであることです。
ちょっと煩雑ですね。
 

3. 実装

まずは、Spring Bootでサーバ側をさくっと作ります。

doublemvc02

できました。
Sample Controllerがサーバコントローラーで、index.jspがサーバコントローラーに紐づいているビューです。
上図は、まだAngularJSを導入していない状態です。
これにAngularJSのクライアントを投入します。

doublemvc03

できました。
webapp/sample 配下が、AngularJSでできています。
また、WEB-INF/jsp/sample/dist 配下のindex.jspがAngularJS用のサーバビューになります。
ここでポイントは、「 /sample/dist/index.jsp = サーバビュー 」であり、「 /sample/dist/index.jsp = AngularJSの基底HTML 」だということです。
ですので、サーバコントローラーからは、このindex.jspを呼び出すことになります。
 

4. 実験

さて、いよいよ実験です。

doublemvc04

最終的な表示画面に各所の通過タイム(new Date()によるミリ秒表示)を表示してみました。
仮説の通りになりました。
client viewの時間は、AngularJSの基底HTMLを通過した時間です。
時間を見る限りやはりここがエントリーポイントのようです。
 

5. さいごに

時間検証を行うことで、ダブルMVCであってもそれぞれの干渉することなく動作させることが出来ることがわかりました。
しかし、クライアントは基底HTMLによって閉じることになりますので、例えば、サーバ側で複数のルーティングを持つ場合、クライアントではそれぞれの関連を持つことが出来ないことになります。
これが開発を行っていく上で余計な手間を増やさないかが気になるところです。
反面、接合点さえ決めておけば、パラレルに作業を進めることができ、またそれぞれでテストを行うことができるのは魅力的ではないでしょうか?
実際に適用してみた事例がもっと出てきてくれれば、こうした事例におけるベストプラクティスが見つかるかもしれませんね。

これにて任務完了。
 

おまけ

今回、検証に利用したソースはGitHubにあげておきました。
corestrike/RoutingSample

AWS EC2上のTomcatにJMX設定をしよう!

| コメントをどうぞ

はじめての方は初めまして、そうでない方はお久しぶりでございます。
DiceK Mikamiです。
諸事により長らくブログから遠ざかっておりましたが、戻って参りました。
決して嫌になったとかそういうんじゃないだからねっ!!
心機一転よろしくお願いいたします。
 
閑話休題
 
さて、4月になり春も真っ盛りと言う感じですが、
日本の社会人である私たちにとって4月とは新年度であり、
バタバタと忙しい日々かと思います。
新年度と言えば、、、そうですね新環境構築ですね。
今回は縁あって、EC2上に設置したTomcatに対してJMX設定を行いましたので、
その設定方法などを共有したいと思います。
これでリモート監視ができる!
 

検証環境

  • OS: Amazon Linux AMI release 2014.03
  • Java: JDK1.7.0
  • Tomcat: 7.0.5

 

リモートホスト(EC2側)の設定手順

1. Tomcatのserver.xmlにリスナーを登録する

素敵なことにJMX用のリスナーが用意されていますので、server.xmlにリスナーを追記します。

<Tomcat Home>/conf/server.xml

<Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener" rmiRegistryPortPlatform="10080" rmiServerPortPlatform="10081"/>

server.xmlの最初の方にリスナーが固まって記述されていますので、そのあたりに追加すれば良いです。
 

2. catalina-jmx-remote.jarをlibフォルダに追加する

リスナー登録だけでは動作してくれませんので、JMX用のライブラリ( catalina-jmx-remote.jar )をTomcatのlibフォルダに追加します。
最初から同梱されていることはほぼないと思いますので、以下から取得してください。

http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.50/bin/extras

バージョン配下にある「 bin/extras 」にjarファイルはありますので、利用するTomcatのバージョンにあわせてライブラリを取得してください。
ライブラリを取得しましたら「 $Tomcatホーム/lib 」配下に置きます。
 

3. Tomcat起動オプションを設定する

次にTomcatの起動オプションを追加します。
setenv.shを作成し、以下のオプションを追加します。

<Tomcat Home>/bin/setenv.sh

export CATALINA_OPTS="-Dcom.sun.management.jmxremote=true
                      -Dcom.sun.management.jmxremote.ssl=false
                      -Dcom.sun.management.jmxremote.authenticate=false
                      -Djava.rmi.server.hostname=<ホスト名>"

bin配下にsetenv.shがある場合、起動時にデフォルトで呼ばれますので、別個に用意しています。
検証時には、設定するホスト名をEC2インスタンスを立ち上げた時のデフォルトホスト名を設定しました。
 

4. Outboundポートを開放する

今回の目的としてEC2上のTomcatをリモート監視すると言うことになりますので、外部から接続するためのポートを開放します。
これはAWSコンソールからの設定になります。
検証では10080ポートと10081ポートを利用しましたので、その2つのポートを開放しました。

EC2側での設定は以上になります。
 

クライアントからの接続手順

いよいよクライアントから接続します。
今回はjdkに付属しているjconsoleから接続してみます。(VisualVMでも接続できます)

$ jconsole service:jmx:rmi://localhost:10081/jndi/rmi://localhost:10080/jmxrmi

これで接続完了!
 

…….
……….. 
 

こ、こんなはずでは。。。

こ、こんなはずでは。。。


!!!!!
なん、、、だと、、、???

はいそうですね。
いくらポートを開放したところでセキュリティがバッチリきいているため接続はできなくて当然ですよね。
みなさんを試したんですよ。
リモートで接続するためにSSHトンネリングを利用しましょう。

$ ssh -i  @ -L 10080:localhost:10080 -L 10081:localhost:10081

これを実行した後、再度jconsoleを起動します。

$ jconsole service:jmx:rmi://localhost:10081/jndi/rmi://localhost:10080/jmxrmi

jmxsuccess
おお、ちゃんとグラフが見えるではないか。
 
ともあれ、これでリモートでTomcatを監視し放題になりました。
今回はコマンドラインと既存ツールを使って行いましたが、
SSHトンネリングとJMXのプロトコルを利用したアプリケーションを利用すればより良い監視ライフが送れることかと思います。

Amazon Elastic Beanstalkで複数のwarファイルをアップロードする方法

| コメントをどうぞ

Amazon Elastic Beanstalk (以下 Beanstalk)では、javaアプリケーションを動かす時、warファイルをアップロードしてそのwarファイルをBeanstalkが自動的に展開してtomcatにデプロイしてくれます。 ただし、現在のBeanstalkではwarファイルは1つしかアップロードできません。 複数warファイルがあるアプリケーションをBeanstalk上で動かすのは無理なのかなーと思って色々調べたら、どうにか複数warファイルでもアプリケーションが動作する環境ができたので、その方法をまとめてみます。

ただし、公式の方法ではないので実際に試す時は注意して行なって下さい。

1.Beanstalkで利用可能なEC2インスタンスをAMIから作成する。

まずは、Beanstalkで使用するためのAMI(Amazon Machine Image)のベースになるインスタンスを作成します。

EC2のインスタンス作成時に下記画像のようにCommunity AMIsから「beanstalk」を検索し、beanstalk用のAMIからインスタンスを作成します。

2.Beanstalkで利用しているEC2インスタンスのRubyファイルを編集する。

インスタンスが作成されたら、ssh等でサーバーへログインします。

sshでログイン後に、下記コマンドでrubyファイルを編集します。このrubyファイルが、beanstalkでインスタンスを作成時に実行されて、インスタンス内のtomcatにアプリケーションをデプロイしています。

vi /opt/elasticbeanstalk/srv/hostmanager/lib/elasticbeanstalk/hostmanager/applications/tomcatapplication.rb

編集箇所1

/usr/bin/sudo /usr/bin/unzip -o -qq #{TomcatApplication.tomcat_deploy_dir}/application.war -d #{TomcatApplication.tomcat_webapps_dir}/ROOT


/usr/bin/sudo /usr/bin/unzip -o -qq #{TomcatApplication.tomcat_deploy_dir}/application.war -d #{TomcatApplication.tomcat_webapps_dir}/

編集箇所2

/usr/bin/sudo /bin/chown -R tomcat:elasticbeanstalk #{TomcatApplication.tomcat_webapps_dir}/ROOT

/usr/bin/sudo /bin/chown -R tomcat:elasticbeanstalk #{TomcatApplication.tomcat_webapps_dir}/*

編集箇所3

/usr/bin/sudo mv #{TomcatApplication.tomcat_deploy_dir}/application.war #{TomcatApplication.tomcat_webapps_dir}/ROOT.war

/usr/bin/sudo /usr/bin/unzip -o -qq #{TomcatApplication.tomcat_deploy_dir}/application.war -d #{TomcatApplication.tomcat_webapps_dir}/

編集後に、インスタンスをAMIとして保存します。

3.Warファイルを作成しBeanstalkでアプリケーションを作成する。

Beanstalkに複数のwarファイルをアップロードして複数のアプリケーションを立てる場合は、次のようなwarファイルを用意します。

application.war
├─appA.war
├─appB.war
└─appC.war

また、application.warに含まれている各warファイルのMETA-INFフォルダには、context.xmlを含めるようにして下さい。

次に、Beanstalkでアプリケーションを作成し、用意したwarファイルをbeanstalkのアプリケーションにアップロードします。

Beanstalkでのアプリケーション作成方法は下記URLなどを参考にして下さい。

http://www.atmarkit.co.jp/fjava/rensai4/devtool23/devtool23_1.html

アプリケーションを作成後しばらくすると、EC2のインスタンスが立ち上がりますが、この時点ではアップロードしたwarファイルは正常にデプロイされません。

Beanstalkでアプリケーションを作成する際、一番初めのデプロイではAMIを指定できずにデフォルトとインスタンスでアプリケーションを作成してしまうためです。

複数のwarを含んだアプリケーションをデプロイする場合は、一旦デフォルトのインスタンスで作成された後に、Beanstalk上の設定でAMIを指定する必要があります。

4.BeanstalkでAMIを指定する。

最後に、Beanstalkの「Edit Configuration」をクリックして設定画面を開き、

2で作成したAMIのIDを「Custom AMI ID」の欄に入力して「Apply Changes」で保存します。

しばらくすると3の最後で作成されたインスタンスが破棄され、設定したAMIで新しいインスタンスが起動します。

インスタンス起動後に

http://アプリケーション名.elasticbeanstalk.com/コンテキスト名

のコンテキスト名を変えると、各コンテキストへアクセスできます。

参考:https://forums.aws.amazon.com/thread.jspa?messageID=229121

Tomcatのクラスタ環境でSession Replicationする方法

| コメントをどうぞ

Tomcatを複数台立てて、各Tomcat上に上がっている同じアプリケーションのセッションを共有する方法です。

この設定をしておくと、Tomcatが1台落ちた時でも他のTomcatが落ちたTomcatで持っているセッションを引き継いでくれるため、
アプリケーションのユーザーは落ちたTomcatを使っていたとしても、セッションが切れずに他のTomcatでアプリケーションを引き続き利用することができます。

上記のような図の構成で、ユーザーのセッションがTomcat Bにあり、Tomcat B が落ちてしまった場合、Tomcat Aが生きていてもユーザーのセッションは切れてしまいユーザーは再度ログインなどをしてセッションを作成しなければならなくなります。

Session Replicationを設定していると、このような状況になってもTomcat B にあるセッションをTomcat Aに複製してくれるので、ユーザーのセッションは切れずにそのままアプリケーションを利用することが可能です。

今回はinfoScoop OSS 3.1.0 quick startでバンドルされているTomcatで設定してみます。

infoScoop OSS 3.1.0 quick startは下記URLからダウンロードできます。
http://www.infoscoop.org/index.php/ja/download/file/65-infoscoop-3.1.0-quickstart.zip.html

infoScoop OSS 3.1.0 quick startのインストール方法は下記のURLをご参照下さい。
http://www.infoscoop.org/index.php/document-310/installation-guide/quickstart-setup.html

セッションレプリケーションにはいくつか方法があるようですが、今回はJDBCを使ってデータベースにセッション情報を保存する方法を試しました。

設定手順は大きく分けると下記のような手順になります。

  1. データベースにセッション情報保存用のテーブルを作成する
  2. コンテキストファイルにデータベース情報を記述する
  3. tomcatのシステムプロパティに宣言を記述する
  4. データベースのドライバーを<TOMCAT_HOME>/libに追加する

これらの手順はinfoScoop OSS quick startをインストール後に行なって下さい。

各手順の詳細を説明します。

1.データベースにセッション情報保存用のテーブルを作成する

今回はquickstartで利用する「iscoop」というデータベースにセッション情報保存ようのテーブルを作成します。

下記sqlを対象のデータベースに対して実行します。

create table tomcat_sessions (
session_id varchar(100) not null primary key,
valid_session char(1) not null,
max_inactive int not null,
last_access bigint not null,
app_name varchar(255),
session_data mediumblob,
KEY kapp_name(app_name)
);

2.コンテキストファイルにデータベース情報を記述する

quick startのtomcat内にあるinfoscoop.xmlにデータベース情報を記述します。

apache-tomcat-6.0.28/conf/Catalina/localhost/infoscoop.xml

上記ファイルのcontext要素内に下記の情報を追記します。

<Manager className='org.apache.catalina.session.PersistentManager'
 saveOnRestart='false' minIdelSwap='0' maxIdleSwap='0' maxIdleBackup='1'>
<Store className="org.apache.catalina.session.JDBCStore"
driverName="com.mysql.jdbc.Driver"
checkInterval="1"
connectionURL="jdbc:mysql://DBサーバー名:3306/iscoop?useUnicode=true&amp;characterEncoding=UTF-8&amp;user=DB接続ユーザー名&amp;password=DB接続ユーザーパスワード"
sessionTable="tomcat_sessions"
sessionIdCol="session_id"
sessionDataCol="session_data"
sessionValidCol="valid_session"
sessionMaxInactiveCol="max_inactive"
sessionLastAccessedCol="last_access"
sessionAppCol='app_name' />
 </Manager>

connectionURLに記述するDBへの接続情報はお使いの環境に合わせて編集して下さい。

3.tomcatのシステムプロパティに宣言を記述する

tomcatのシステムプロパティファイルは下記の場所にあります。

apache-tomcat-6.0.28/conf/catalina.properties

上記ファイルの一番下に下記の内容を追記します。

org.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true

4.データベースのドライバーを<TOMCAT_HOME>/libに追加する

quick start内のtomcatにはすでに追加されているので、特に作業は必要ありません。

quick startを利用しない場合は、データベースのドライバーを追加して下さい。

以上でセッションレプリケーションの設定ができます。

参考:http://d.hatena.ne.jp/sato-shi/20110122/p3

JAX-RSでファイルダウンロード

| 1件のフィードバック

全国に寒波がきていたようで、ここしばらくは大分寒かったですね。
バイク乗りの私としては早々に暖かくなっていただきたいものです。
こんにちわ、DiceK Mikamiです。

今回は軽めのネタとして、JAX-RSを利用した際にバイナリを返却する方法を紹介したいと思います。
百聞は一見に如かず。
と言うことで、まずはサンプルコードを御覧ください。

import java.io.File;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

@Path("/image")
public class SampleResource {
 @GET
 @Produces("image/png")   //ポイント1
 @Path("{id}")
 public Response getImage(@PathParam("id") Integer imageId){
     File file = new File("<ファイルの場所>"); //ポイント2

     ResponseBuilder response = Response.ok((Object)file); //ポイント3
     String headerVal = "attachment; filename="+imageId.toString()+".png";
     response.header("Content-Disposition",headerVal);
     return response.build();
 }
}

全く簡単だ。
「今回はこれまで」といきたいところですが、ポイントをいくつか下記します。

  • ポイント1
    返却するバイナリのMIMEタイプを指定します。
  • ポイント2
    バイナリをjava.io.File形式で取得します。
  • ポイント3
    javax.ws.rs.core.Response.ResponseBuilderを使用して、ヘッダを追加します。

バイナリファイルをダウンロードさせたいという要求があった場合などに便利ですね。
サンプルでは画像ファイルを返却するようにしておりますが、
@ProducesにてダウンロードさせたいバイナリのMIMEタイプを指定すれば、切り替えることができます。