Triệu gọi ứng dụng RMI trong Applet
Giới thiệu:
Trong rất nhiều mail gửi về hỏi những vấn đề liên quan đến RMI thì thường các bạn rất lúng túng trong việc code và thiết lập security để client có thể truy cập/triệu gọi đến các phương thức từ xa, đặc biệt là trên Applet.
Bởi vì lý do đó nên tôi viết bài này nhằm có thể hướng dẫn các bạn cách thức tạo/thực thi/triệu gọi 1 chương trình sử dụng công nghệ RMI, đặc biệt là trên Applet.
Bài này có liên quan đến java security, tuy nhiên tôi cố tình tránh nói đến nhiều mà chỉ nói cách bạn có thể làm và chạy được còn bạn nào muốn tìm hiểu sâu hơn thì ta có lẽ cần những bài khác.
Việc thiết lập biến môi trường để có thể chạy bằng command-line thì tôi đã giới thiệu ở 1 bài khác. Chạy trên 1 IDE nào đó thì dễ dàng hơn nhiều.
Bài này với nội dung là tạo ra 1 server tính toán và đăng ký trên registry. Khi client triệu gọi đến thì sẽ tính toán trên 4 phép tính cơ bản và trả kết quả về cho client
- Cấu trúc tổ chức code:

Trong đó source code của các file như sau:
a. Calculator.java
package vovanhai.wordpress.com;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Calcalutor extends Remote{
public double Cong(double a,double b)throws RemoteException;
public double Tru(double a,double b)throws RemoteException;
public double Nhan(double a,double b)throws RemoteException;
public double Chia(double a,double b)throws RemoteException;
}
b. CalculatorImpl.java
package vovanhai.wordpress.com.server;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import vovanhai.wordpress.com.Calcalutor;
public class CalculatorImpl extends UnicastRemoteObject implements Calcalutor{
public CalculatorImpl()throws RemoteException {
}
@Override
public double Cong(double a, double b) throws RemoteException {
return a+b;
}
@Override
public double Tru(double a, double b) throws RemoteException {
return a-b;
}
@Override
public double Nhan(double a, double b) throws RemoteException {
return a*b;
}
@Override
public double Chia(double a, double b) throws RemoteException {
return a/b;
}
}
c. CalculatorServer.java
package vovanhai.wordpress.com.server;
import java.rmi.registry.LocateRegistry;
import javax.naming.Context;
import javax.naming.InitialContext;
import vovanhai.wordpress.com.Calcalutor;
public class CalculatorServer {
public static void main(String[] args) throws Exception{
LocateRegistry.createRegistry(1099);
System.out.println("Server started...");
Context ctx=new InitialContext();
Calcalutor cal=new CalculatorImpl();
ctx.bind("rmi://localhost:1099/calc", cal);
System.out.println("Object 'cal' bound in registry.");
}
}
d. CalculatorClientGUI.java
package vovanhai.wordpress.com.client;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import vovanhai.wordpress.com.Calcalutor;
public class CalculatorClientGUI extends JFrame implements ActionListener{
private JTextField tfA,tfB,tfKQ,tfSvr;
private JButton btnCong,btnTru,btnNhan,btnChia;
public CalculatorClientGUI() {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(290,190);setResizable(false);
setTitle("Calculator");
Box b=Box.createVerticalBox();
Box b0=Box.createHorizontalBox();
Box b1=Box.createHorizontalBox();
Box b2=Box.createHorizontalBox();
Box b3=Box.createHorizontalBox();
Box b4=Box.createHorizontalBox();
b.add(Box.createVerticalStrut(10));
b.add(b0);b.add(Box.createVerticalStrut(10));
b.add(b1);b.add(Box.createVerticalStrut(10));
b.add(b2);b.add(Box.createVerticalStrut(10));
b.add(b3);b.add(Box.createVerticalStrut(10));
b.add(b4);b.add(Box.createVerticalStrut(10));
JLabel l1,l2,l3,l4;
b0.add(l1=new JLabel("Server Add:"));b0.add(tfSvr=new JTextField(20));
tfSvr.setText("localhost");
b1.add(l2=new JLabel("Số A:"));b1.add(tfA=new JTextField(20));
b2.add(l3=new JLabel("Số B:"));b2.add(tfB=new JTextField(20));
b4.add(l4=new JLabel("Kết quả:"));b4.add(tfKQ=new JTextField(20));
b3.add(btnCong=new JButton("+"));btnCong.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnCong.setCursor(new Cursor(Cursor.HAND_CURSOR));
b3.add(btnTru=new JButton("-"));btnTru.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnTru.setCursor(new Cursor(Cursor.HAND_CURSOR));
b3.add(btnNhan=new JButton("*"));btnNhan.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnNhan.setCursor(new Cursor(Cursor.HAND_CURSOR));
b3.add(btnChia=new JButton("/"));btnChia.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnChia.setCursor(new Cursor(Cursor.HAND_CURSOR));
l2.setPreferredSize(l1.getPreferredSize());
l3.setPreferredSize(l1.getPreferredSize());
l4.setPreferredSize(l1.getPreferredSize());
this.add(b);tfKQ.setEditable(false);
}
@Override
public void actionPerformed(ActionEvent e) {
JButton b=(JButton)e.getSource();
String op=b.getText();
String add=tfSvr.getText();
try {
double x=Double.parseDouble(tfA.getText());
double y=Double.parseDouble(tfB.getText());
double kq=0;
String url="rmi://"+add+":1099/calc";
Context ctx=new InitialContext();
Object o=ctx.lookup(url);
Calcalutor calc=(Calcalutor)o;
if(op.equals("+")){
kq=calc.Cong(x, y);
}
else if(op.equals("-")){
kq=calc.Tru(x, y);
}
else if(op.equals("*")){
kq=calc.Nhan(x, y);
}
else if(op.equals("/")){
kq=calc.Chia(x, y);
}
tfKQ.setText(""+kq);
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(null, "Lỗi chưa nhập liệu, nhập sai hoặc số quá lớn");
}catch (Exception e2) {
JOptionPane.showMessageDialog(null, "Lỗi:"+e2.getMessage());
}
}
public static void main(String[] args) {
new CalculatorClientGUI().setVisible(true);
}
}
e. CalculatorClientApplet
package vovanhai.wordpress.com.client;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.Naming;
import javax.swing.Box;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextField;
import vovanhai.wordpress.com.Calcalutor;
public class CalculatorClientApplet extends JApplet implements ActionListener{
private JTextField tfA,tfB,tfKQ,tfSvr;
private JButton btnCong,btnTru,btnNhan,btnChia;
public void init(){
setSize(290,190);
Box b=Box.createVerticalBox();Box b0=Box.createHorizontalBox();
Box b1=Box.createHorizontalBox();Box b2=Box.createHorizontalBox();
Box b3=Box.createHorizontalBox();Box b4=Box.createHorizontalBox();
b.add(Box.createVerticalStrut(10));
b.add(b0);b.add(Box.createVerticalStrut(10));
b.add(b1);b.add(Box.createVerticalStrut(10));
b.add(b2);b.add(Box.createVerticalStrut(10));
b.add(b3);b.add(Box.createVerticalStrut(10));
b.add(b4);b.add(Box.createVerticalStrut(10));
JLabel l1,l2,l3,l4;
b0.add(l1=new JLabel("Server Add:"));b0.add(tfSvr=new JTextField(20));
tfSvr.setText("localhost");
b1.add(l2=new JLabel("Số A:"));b1.add(tfA=new JTextField(20));
b2.add(l3=new JLabel("Số B:"));b2.add(tfB=new JTextField(20));
b4.add(l4=new JLabel("Kết quả:"));b4.add(tfKQ=new JTextField(20));
b3.add(btnCong=new JButton("+"));btnCong.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnCong.setCursor(new Cursor(Cursor.HAND_CURSOR));
b3.add(btnTru=new JButton("-"));btnTru.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnTru.setCursor(new Cursor(Cursor.HAND_CURSOR));
b3.add(btnNhan=new JButton("*"));btnNhan.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnNhan.setCursor(new Cursor(Cursor.HAND_CURSOR));
b3.add(btnChia=new JButton("/"));btnChia.addActionListener(this);
b3.add(Box.createHorizontalStrut(5));btnChia.setCursor(new Cursor(Cursor.HAND_CURSOR));
l2.setPreferredSize(l1.getPreferredSize());
l3.setPreferredSize(l1.getPreferredSize());
l4.setPreferredSize(l1.getPreferredSize());
this.add(b);tfKQ.setEditable(false);
}
@Override
public void actionPerformed(ActionEvent e) {
JButton b=(JButton)e.getSource();
String op=b.getText();
String add=tfSvr.getText();
try {
double x=Double.parseDouble(tfA.getText());
double y=Double.parseDouble(tfB.getText());
double kq=0;
//String polfile=this.getParameter("policyfile");
//System.setProperty("java.security.policy",polfile);
String url="rmi://"+add+"/calc";
Object o=Naming.lookup(url);
Calcalutor calc=(Calcalutor)o;
if(op.equals("+")){
kq=calc.Cong(x, y);
}
else if(op.equals("-")){
kq=calc.Tru(x, y);
}
else if(op.equals("*")){
kq=calc.Nhan(x, y);
}
else if(op.equals("/")){
kq=calc.Chia(x, y);
}
tfKQ.setText(""+kq);
} catch (Exception e2) {
tfKQ.setText(e2.getMessage());
}
}
}
2. Biên dịch chương trình
Tạo file build.bat có nội dung:
rem tao thu muc chua cac files class md build rem start building... javac -d build -encoding "UTF-8" vovanhai/wordpress/com/*.java javac -d build -encoding "UTF-8" vovanhai/wordpress/com/server/*.java javac -d build -encoding "UTF-8" vovanhai/wordpress/com/client/*.java rem finished pause
Sau khi build ok, ta có kết quả là cây thư mục sau:

3. Thực thi chương trình
a. Chạy server
Trong thư mục build ta tạo file runserver.bat để start RMI server. File này có nội dung như sau:
java vovanhai.wordpress.com.server.CalculatorServer pause
thực thi nó ta có màn hình sau:

b. Chạy GUI client
Trong thư mục build tạo file runClient.bat với nội dung sau:
java vovanhai.wordpress.com.client.CalculatorClientGUI
thực thi nó ta có màn hình sau:

Bạn có thể chạy file runClient.bat vài lần để tạo ra vài cửa sổ rồi test thử
c. Chạy Applet Client
Tạo file CalculatorApplet.html có nội dung sau:
<html> <head> <title>RMI Applet html page</title> </head> <body> <h3> <hr width="100%"/> RMI-Applet demo - <a href="http://vovanhai.wordpress.com">http://vovanhai.wordpress.com</a> <hr width="100%"/> </h3> <applet codebase="." code="vovanhai/wordpress/com/client/CalculatorClientApplet.class" width="450" height="180"/> </body> </html>
Sau đó ta mở nó bằng trình duyệt (browser: IE,FireFox,Chrome,…) kết quả như sau:

Thử nhập số vào và nhấn 1 nút, bạn sẽ bị lỗi ngay. Lỗi không cho bạn kết nối. Có thể bạn nghĩ là thiếu file policy? Cũng đúng nhưng nếu bạn có file policy và đưa nó vào chương trình thì tôi chắc nó vẫn lỗi.

Ta sẽ giải quyết trường hợp này như sau: có 2 cách giải quyết
Cách 1:
Sửa file policy của java: (Cách này mạo hiểm và không an toàn cho hệ thống của bạn – khuyên là không dùng) cách làm như sau:
Mở file JRE\lib\security\java.policy (thường là C:\Program Files\Java\jre6\lib\security\java.policy) lên và thêm dòng sau vào:
grant{
permission java.security.AllPermission;
};
Lưu lại rồi sau đó chạy lại chương trình (server/applet) ta sẽ được phép làm việc.

Cách 2:
Bạn sign (ký – security) file jar của bạn với 1 chữ ký số (khuyên dùng) cách làm như sau:
i. Đóng gói ứng dụng thành file jar
Tạo file createJARfile.bat với nội dung:
jar cvf Calculator.jar vovanhai/wordpress/com/*.class vovanhai/wordpress/com/client/*
ii. Tạo chữ ký số với công cụ keytool
Lưu ý: nếu bạn đã có thì khỏi chạy bước này và chỉ chạy 1 lần
Mở cửa sổ command-line lên, chuyển tới thư mục của bạn, chạy lệnh
keytool -genkey -alias VoVanHai -keyalg RSA
Lưu ý: password mặc định là changeit
Kết quả như hình

Alias bây giờ của bạn là VoVanHai
iii. Sign file jar của bạn với công cụ jarsigner
tạo file signJARfile.bat với nội dung sau, sau đó thực thi nó (passphrase là changeit):
jarsigner Calculator.jar VoVanHai

Ta có thể verify lại file jar vừa mới sign bằng lệnh trong tập tin có tên verifyJAR.bat
jarsigner.exe -verify -verbose Calculator.jar

Tạo file CalculatorSignedApplet.html có nội dung sau:
<html> <head> <title>RMI Applet html page</title> </head> <body> <h3> <hr width="100%"/> RMI-Applet demo - <a href="http://vovanhai.wordpress.com">http://vovanhai.wordpress.com</a> <hr width="100%"/> </h3> <applet archive="Calculator.jar" code="vovanhai/wordpress/com/client/CalculatorClientApplet.class" width="450" height="180"/> </body> </html>
Thực thi nó bạn sẽ thấy 1 cảnh báo:

Chấp nhận secirity cho chữ ký điện tử này, thực thi => OK

Cấu trúc đầy đủ của chương trình như sau:

Nếu bạn muốn để nó lên 1 web server thì mọi việc rất đơn giản không gì khác ngoài copy/paste.