Chương 14 : HelloPhone kết hợp với Proxy
Trong phần này, chúng ta sẽ xây dựng 1 Sip proxy server đơn giản , proxy này có các tính năng sau:
- Bao gồm Sip registrar và location service.
- Registrar khá đơn giản, nhiệm vụ nhận REGISTER request để đăng ký và thông tin được cập nhật vào location service.
- Chỉ thực hiện tính năng trao đổi riêng lẽ giữa hai peer.
Chúng ta bổ sung proxy vào HelloPhone project, thực hiện như sau:
- Tạo class SipProxyGUI : giao diện đồ họa của Proxy.
- Tạo class SipProxyListener : code JAIN SIP thực hiện hoạt động của SIP Proxy.
I. Cách hoạt động của Proxy:
Các User Agent(UA) không trao đổi trực tiếp như các ví dụ trước đó mà phải thông qua proxy.
* UA đăng ký thông tin :
UA gởi REGISTER request đến proxy để đăng ký thông tin, proxy thực hiện như sau:
- Tạo 1 ServerTransaction mới dành REGISTER.
- Lấy AddressOfRecord (AOR) từ To header.
- Lấy ContactAddress từ Contact header.
- Xóa thông tin đăng ký trước đó trong Location service.
- Nếu REGISTER có Expires header, proxy sẽ kiểm tra REGISTER đã hết hạn chưa.
+ Nếu đã hết hạn, tạo ra "408 Request Timeout" response gởi đến UA.
+ Chưa hết hạn, lưu AOR và ContactAddress vào Location Service và trả về "200 OK" response đến UA.
Lưu ý: Nếu UA không đăng ký thông tin thì không thể thực hiện cuộc gọi với UA khác.
* UAC thực hiện cuộc gọi :
UAC muốn thực hiện cuộc gọi với UAS : UAC gởi INVITE request đến proxy, proxy thực hiện như sau :
-
Kiểm tra đó là INVITE hay reINVITE, dựa trên phương thức
getServerTransaction() của RequestEvent trả về 1 ServerTransaction có
đang tồn tại không:
+ Nếu ServerTransaction là null, đó là INVITE ,tạo ra 1 ServerTransaction mới dành cho INVITE này.
+ Nếu ServerTransaction khác null, đó là reINVITE.
- Sao chép request để tạo 1 bản sao gọi là copyRequest.
- Xóa Route header trong copyRequest.
-
Thiết lập giá trị mới cho Request-URI trong copyRequest. HelloPhone của
chúng ta chỉ có 1 proxy, nên đích đến kế tiếp là UAS, từ To header của
copyRequest lấy ra AOR, từ AOR lấy ra ContactAddress của UAS trong
location service. Nếu UAS chưa đăng ký với proxy - nghĩa là, proxy không
biết được ContactAddress của UAS - proxy tạo ra "404 not found"
response gởi đến UAC, với ý nghĩa "proxy không biết contactAddress của
UAS"
- Giảm giá trị của MaxForwards header đi 1.
- Thêm địa chỉ Proxy vào Via header.
- Tạo ra client transaction mới để gởi copyRequest đến UAS.
- Tạo ra 1 response context mới, lưu lại các thông tin sau:
+ ServerTransaction dành cho INVITE.
+ ClientTransaction dành cho copyINVITE.
+ Lưu INVITE và copyINVITE.
* UAC hủy cuộc gọi :
UAC muốn hủy cuộc gọi, sẽ gởi CANCEL request đến proxy, proxy thực hiện như sau :
-
CANCEL request dùng để kết thúc 1 INVITE request khi chưa tồn tại ACK
request, nên CANCEL và INVITE đều cùng 1 dialog. Do đó,
ServerTransaction được khởi tạo bởi INVITE cũng là ServerTransaction
nhận CANCEL.
-
Lấy ra INVITE request đầu vào tương ứng, được lưu trong ProxyCore, tạo
"487 Terminated” response để kết thúc INVITE gởi đến UAC.
- Tạo "200 OK" response để kết thúc CANCEL gởi đến UAC.
- Tạo CANCEL request mới gởi đến UAS.
* UAS từ chối cuộc gọi :
- UAS từ chối cuộc gọi, gởi "486 BUSY" response đến proxy, ngay lập tức proxy sẽ chuyển tiếp response này đến UAC.
II. Giao diện của Proxy – SipProxyGUI class:
- Button ON : khởi động Proxy.
- Button OFF : ngưng hoạt động Proxy.
- Domain
: là tên miền ( bạn cứ đặt theo bạn thích), nhưng các UA phải đăng ký
đúng tên miền này (ví dụ, tên miền là niitag.com thì các UA phải là
xxx@niitag.com).
- Tracer
: theo dõi các giao dịch sip message giữa UAC với proxy và giữa proxy
với UAS. Nếu là giao dịch giữa UAC với proxy, sip message hiển thị ở
Server Transaction. Ngược lại là giao dịch giữa proxy với UAS, sip
message hiển thị ở Client Transaction.
- Location Service : hiển thị địa chỉ AddressOfRecord và ContactAddress của mỗi UA đã đăng ký với proxy.
- Proxy Core : hiển thị các Response Context được lưu giữ trên proxy.
a. Các biến của SipProxyGUI class :
- cmdON : button dùng để khởi động proxy.
- cmdOFF : button dùng để ngưng hoạt động proxy.
- lblInit : label hiển thị địa chỉ IP và port của proxy khi proxy khởi động thành công.
- txtClientTransaction : TextArea hiển thị nội dung các sip message khi có sự giao dịch giữa proxy với UAS.
- txtDomain: TextField hiển thị tên domain của proxy.
- tableLocationService : Table hiển thị địa chỉ AddressOfRecord và ContactAddress của mỗi UA.
- txtProxyPort : TextField hiển thị port của proxy.
- txtServerTransaction : TextArea hiển thị nội dung các sip message khi có sự giao dịch giữa UAC với proxy.
- tableProxyCore : Table hiển thị các response context đang tồn tại.
- proxyListener : SipProxyListener, lấy nội dung giao dịch của SIP.
b. Các phương thức của SipProxyGUI class :
- Phương thức cmbONActionPerformed() : khi button ON được nhấn.
- Phương thức cmbOFFActionPerformed() : khi button OFF được nhấn.
- Phương thức getProxyPort() : lấy giá trị Port của proxy do user nhập vào.
- Phương thức setInit() : thiết lập giá trị cho label Init.
- Phương thức appendClientTransaction() : hiển thị nội dung các sip message được thực hiện khi có sự giao dịch giữa proxy với UAS.
- Phương thức appendServerTransaction() : hiển thị nội dung các sip message được thực hiện khi có sự giao dịch giữa UAC với proxy.
- Phương thức getDomain() : lấy giá trị Domain do người dùng đặt tên.
- Phương thức updateLocationService() : cập nhật sự hiển thị AOR và ContactAddress của 1 UA trong table Location Service.
- Phương thức removeLocationService() : xóa bỏ sự hiển thị AOR và ContactAddress của 1 UA trong table Loacation Service.
- Phương thức updateProxyCore() : cập nhật sự hiển thị 1 response context trong table ProxyCore.
- Phương thức removeResponseContext() : xóa bỏ sự hiển thị 1 response context trong table ProxyCore
III. SipProxyListener class :
Đối
với UAC, proxy là UAS. Còn với UAS, proxy là UAC. Do đó, proxy cũng chỉ
là 1 UA, nên SipProxyListener cũng tương tự như các SipListener khác,
cũng phải kế thừa SipListener.
- Tạo các biến khởi động SIP:
-
Đối với các chương trình thực tế, Location Sevice và Proxy Core sử
dụng hệ quản trị cơ sở dữ liệu để quản lý nó. Để các bạn dễ hiểu và đơn
giản hóa, chúng tôi sử dụng đối tượng của HashMap để quản lý Location
Service và ArrayList là ProxyCore.
+ Mỗi phần tử của HashMap locationservice : có key là AOR và dữ liệu là ContactAddress :
+ Mỗi phần tử của ArrayList proxyCore chứa đối tượng ResponseContext :
-
Mỗi response context của proxy chứa các thông tin : Client Transaction ,
Server Transaction, Request đầu vào và Request gởi đi. Do đó, tạo class
ResponseContext như sau:
- Hàm dựng SipProxyListener() : khởi động proxy khi button ON được nhấn
Lưu ý
: Proxy không thể tự động tạo Dialog như UAC và UAS. Mặc định JAIN SIP
tự động tạo Dialog khi UAC gởi INVITE, để tắt tính năng này ở proxy, đặt
giá trị "OFF" cho thuộc tính AUTOMATIC_DIALOG_SUPPORT.
- Phương thức setOFF() : ngưng hoạt động proxy khi button OFF được nhấn
- Phương thức processRequest() : các request gởi đến Proxy do phương thức này bắt và xử lý.
- Phương thức processResponse() : các response gởi đến Proxy do phương thức này bắt và xử lý.
1. Xử lý các tình huống khi proxy nhận request và response:
a. UAC gởi REGISTER request đến proxy để đăng ký thông tin:
Phương
thức processRequest() bắt và xử lý các request khi gởi đến proxy. Do
đó, chúng ta sẽ viết code xử lý khi proxy nhận được REGISTER trong
phương thức này. Chúng ta thực hiện từng bước như sau:
+ Lấy địa chỉ AddressOfRecord từ ToHeader của REGISTER
+ Lấy địa chỉ ContactAddress từ ContactHeader của REGISTER
+ Xóa thông tin đăng ký trước đó trong Location Service dựa trên AOR
+ Kiểm tra REGISTER đã hết hạn chưa dựa trên ExpiresHeader .
i. Nếu đã hết hạn , tạo ra "408 Timeout" response trả về UA.
ii. Nếu chưa hết hạn, lưu thông tin vào location service và tạo “200 OK” response gởi đến UA để thể hiện sự đăng ký thành công.
b. UAC gởi INVITE request đến proxy :
Chúng ta sẽ viết code xử lý khi proxy nhận được INVITE trong phương thức processRequest(). Chúng ta thực hiện từng bước như sau:
-
Kiểm tra đó là INVITE hay reINVITE, dựa trên phương thức
getServerTransaction() của RequestEvent trả về 1 ServerTransaction có
đang tồn tại không:
+ Nếu ServerTransaction là null, đó là INVITE ,tạo ra 1 ServerTransaction mới dành cho INVITE này.
+ Nếu ServerTransaction khác null, đó là reINVITE.
- Sao chép request để tạo 1 bản sao gọi là copyRequest.
- Xóa Route header trong copyRequest.
-
Thiết lập giá trị mới cho Request-URI trong copyRequest. HelloPhone của
chúng ta chỉ có 1 proxy, nên đích đến kế tiếp là UAS, từ To header của
copyRequest lấy ra AOR, dựa vào AOR lấy ra ContactAddress của UAS trong
location service. Nếu UAS chưa đăng ký với proxy - nghĩa là, proxy không
biết được ContactAddress của UAS - proxy tạo ra "404 not found"
response gởi đến UAC, với ý nghĩa "proxy không biết contactAddress của
UAS"
- Giảm giá trị của MaxForwards header đi 1.
- Thêm địa chỉ Proxy vào Via header.
- Tạo ra client transaction mới để gởi copyRequest đến UAS.
- Tạo ra 1 response context mới, lưu lại các thông tin sau:
+ ServerTransaction dành cho INVITE.
+ ClientTransaction dành cho copyINVITE.
+ Lưu INVITE và copyINVITE.
public void processRequest(RequestEvent requestEvent) {
...
// nếu request nhận được là INVITE
if (request.getMethod().equals(Request.INVITE)
|| request.getMethod().equals(Request.MESSAGE)) {
// Kiểm tra đây là INVITE hay reINVITE
ServerTransaction inviteServerTransaction = requestEvent.getServerTransaction();
// nếu inviteServerTransaction là null, đó là INVITE, ngược lại là reINVITE
if (inviteServerTransaction == null) {
// Nếu là INVITE tạo 1 ServerTransaction mới
inviteServerTransaction = sipProvider.getNewServerTransaction(request);
}
// sao chép Request mới nhận được
Request copyRequest = (Request) request.clone();
// xóa bỏ Route Header trong copyRequest
RouteHeader receivedRouteHeader =
(RouteHeader) request.getHeader(RouteHeader.NAME);
SipURI receivedRouteHeaderSipURI =
(SipURI) receivedRouteHeader.getAddress().getURI();
String receivedRouteHeaderDomain = receivedRouteHeaderSipURI.getHost();
if (receivedRouteHeaderDomain.equals(proxyIP)) {
copyRequest.removeFirst(RouteHeader.NAME);
}
// thiết lập giá trị mới cho Request-URI trong copyRequest
ToHeader receivedToHeader = (ToHeader) request.getHeader("To");
SipURI addressOfRecord = (SipURI) receivedToHeader.getAddress().getURI();
if (addressOfRecord.getHost().equals(proxyGUI.getDomain())) {
URI contactAddress = (URI) locationservice.get(addressOfRecord);
if (contactAddress != null) {
copyRequest.setRequestURI(contactAddress);
}else {
// tạo "404 not found" response gởi trở lại UAC
Response responseNoFound = messageFactory.createResponse(404, request);
inviteServerTransaction.sendResponse(responseNoFound);
return;
}
}
// giảm giá trị của MaxForwards đi 1
MaxForwardsHeader maxForwardsHeader =
(MaxForwardsHeader) copyRequest.getHeader(MaxForwardsHeader.NAME);
maxForwardsHeader.decrementMaxForwards();
// Thêm địa chỉ Proxy vào Via header
ViaHeader viaHeader =
headerFactory.createViaHeader(proxyIP, proxyGUI.getProxyPort(), "udp", null);
copyRequest.addFirst(viaHeader);
// tạo ra clienttransaction mới để gởi copyRequest đến UAS
ClientTransaction inviteClientTransaction =
sipProvider.getNewClientTransaction(copyRequest);
inviteClientTransaction.sendRequest();
proxyGUI.appendClientTransaction("send : " + copyRequest.toString());
// tạo 1 response context mới chứa các thông tin
ResponseContext context = new ResponseContext();
context.clientTransaction = inviteClientTransaction;
context.serverTransaction = inviteServerTransaction;
context.requestIn = request;
context.requestOut = copyRequest;
// lưu response context mới này vào proxyCore
proxyCore.add(context);
// cập nhập sự hiển thị proxy core trong proxy GUI
proxyGUI.updateProxyCore(context.requestIn.getMethod(),
context.serverTransaction.toString(), context.clientTransaction.toString());
}
|
c. UAC gởi CANCEL request đến proxy :
Chúng ta sẽ viết code xử lý khi proxy nhận được INVITE trong phương thức processRequest(). Chúng ta thực hiện từng bước như sau:
-
CANCEL request dùng để kết thúc 1 INVITE request khi chưa tồn tại ACK
request, nên CANCEL và INVITE đều cùng 1 dialog. Do đó,
ServerTransaction được khởi tạo bởi INVITE cũng là ServerTransaction
nhận CANCEL.
-
Lấy ra INVITE request đầu vào tương ứng, được lưu trong ProxyCore, tạo
"487 Terminated” response để kết thúc INVITE gởi đến UAC.
- Tạo "200 OK" response để kết thúc CANCEL gởi đến UAC.
- Tạo CANCEL request mới gởi đến UAS.
d. UAS gởi response đến proxy :
Phương thức processResponse() xử lý các response được UAS gởi đến
proxy. Do đó, chúng ta sẽ viết code xử lý các response trong phương thức
này. Chúng ta thực hiện từng bước như sau:
+
Proxy không xử lý các response có là 487 và response phản hồi của
CANCEL request. Bởi vì, trong quá trình xử lý CANCEL request, proxy đã
tự động gởi "487 terminated" đối với INVITE và "200 OK" đối với CANCEL,
gởi đến UAC rồi. Nên những response này được gởi từ UAS không cần thiết
phải gởi đến UAC nữa.
+ Sao chép và loại bỏ giá trị đầu tiên của Via header trong response
+
Nếu response có tồn tại Client Transaction trong response context, thì
Server Transaction của context này sẽ gởi copyResponse đến UAC.
+ Ngược lại, Response không tồn tại ClientTransaction, sử dụng SipProvider để gởi copyResponse này đến UAC.
IV. Bổ sung thêm một số thành phần trên giao diện HelloPhoneGUI :
Chúng ta bổ sung thêm các thành phần sau :
- txtName : là textFiled, tên của 1 UA
- txtAOR : là textFiled, đó là Address of Record - địa chỉ đăng ký với Proxy, bắt buộc:
+ Không được trùng với UA khác
+ Phải có @.
+ Tên domain trùng với proxy.
Trong ví dụ này, chúng tôi đặt Domain của proxy là niitag.com thì AOR phải có dạng như sau: xxx@niitag.c om
- txtProxyServer : là textField, địa chỉ proxy.
- lblStatusRegister : là label, chứa hình ảnh hiển thị tình trạng đăng ký với proxy :
+ : chưa đăng ký (chưa bấm button Connect) , hoặc đăng ký thất bại. Trạng thái này không thể thực hiện cuộc gọi.
+ : đăng ký thành công, sẵn sàng thực hiện cuộc gọi.
- Phương thức getAddressProxy(), getName(), getAOR() là các phương thức lấy giá trị của txtProxyServer, txtName, txtAOR được dùng trong HelloPhoneListener class.
- Phương thức statusRegister() : chứa tham số kiểu boolean:
+
Nếu là true thể hiện đăng ký đã thành công, thiết lập hình ảnh đăng ký
thành công cho lblStatusRegister và cho phép các button thực hiện cuộc
gọi.
+ Nếu là false thể hiện đăng ký không thành công, thiết lập hình ảnh đăng ký không thành công cho lblStatusRegister.
V. Bổ sung thêm code trong HelloPhoneListener class :
a. Bổ sung 1 :
Bắt buộc UA phải gởi REGISTER đến proxy để đăng ký. Do đó, chúng ta
tạo REGISTER này chung với hành động bấm vào button Connect để khởi động
SIP, tức là gọi phương thức tạo REGISTER trong hàm dựng của
HelloPhoneListener class. Bổ sung code như sau:
Tương tự như tạo REGISTER trong các ví dụ trước đây, nhưng chỉ khác là giá trị của Request-URI phải là địa chỉ proxy.
b. Bổ sung 2 :
Trong phương thức processResponse(), chúng ta xử lý trường hợp khi UA nhận được các response trả về từ proxy đối với REGISTER:
c. Bổ sung 3 :
INVITE
request được thay thế địa chỉ Request-URI là địa chỉ proxy và bổ sung
Route header vào INVITE. Hành động này, được thực hiện trong phương thức
sendRequest() :
VI. Chạy chương trình :
- Đầu tiên, class SipProxyGUI phải chạy trước và bấm button ON để khởi động proxy.
- Kế tiếp lần lượt chạy các UA bởi class HelloPhoneGUI.
Lưu ý : bạn có thể chạy nhiều UA nhưng chỉ chạy duy nhất 1 proxy.
Dưới đây, là 1 số hình ảnh về proxy khi thực hiện giao dịch giữa 2 UA.
Hình ảnh 2 UA :
Code đầy đủ:
http://www.mediafire.com/download/oocdimi4c834rh2/HelloPhone_JAINSIP.rar