Thứ Tư, 20 tháng 8, 2014

[JAIN SIP] Chương 14 : HelloPhone kết hợp với Proxy

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

Không có nhận xét nào:

Đăng nhận xét