Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » Eclipse Titan » Chat with STOMP over WebSocket part2 (STOMP/IPL4 translation port)
Chat with STOMP over WebSocket part2 [message #1772145] Wed, 06 September 2017 06:42
Elemer Lelik is currently offline Elemer LelikFriend
Messages: 1120
Registered: January 2015
Senior Member
Dear all,

let's continue from where we left off: we have a functional code to act as a STOMP/Websocket client, but, due to the usage of the IPL4 test port in TCP/TLS mode,
we need to juggle with three protocol layers when sending/receiving a STOMP message. It would be preferable to have a test port that would accept STOMP messages, so
WebSocket and what's underneath would be hidden from the user.

For that, we need to declare a translation from STOMP to TCP/TLS via WebSocket:

type port STOMP_IPL4_PT message map to IPL4asp_PT //Translation port
{
 out 
     STOMPFrame    to ASP_Send   with f_enc_STOMP_Translation(),
     WebSocket_PDU to ASP_Send   with f_enc_WS_Translation(),
     Composite     to ASP_Send   with f_enc_HTTP_Translation()
 in      
     STOMPFrame     from ASP_RecvFrom with f_dec_STOMP_Translation(),
     HTTP_Message   from ASP_RecvFrom with f_dec_HTTP_Translation(),
     WebSocket_PDU  from ASP_RecvFrom with f_dec_WS_Translation(),
     ASP_Event  

 var  integer v_connId
          
} with {extension "internal" }



A few clarifications:

-when declaring the translation port, the user has two choices:
either add the test port skeleton generated (with compiler -t ...) for the translation port type (STOMP_IPL_PT)
or declare the port internal (with ' with {extension "internal" } ' ) , thus stating that it does not have the intention to use the port in native (non-translation ) mode.


-besides the obvious STOMP messages, we have to account for WebSocket PDUs ( as e.g. we will have to send-receive PING messages)
and also for HTTP messages for the initial connection upgrade; however we will deal with these in parts of the code separate from the main STOMP message exchange.

The below structure
type  record Composite
{
HTTP_Message  hTTP_Message,
integer       connId
}

as you can see, contains an HTTP message and a connection Id.
This is needed due to particularities of the IPL4 test port:

the conection Id to be referred to on TCP level is obtained when a TCP/TLS connection is established by calling f_IPL4_connect:
  if(tsp_use_ssl) {
    vl_result :=STOMP_User_CtrlFunct.f_IPL4_connect(STOMP_PCO, tsp_hostname, tsp_remPort_s,"",0, -1, {ssl := {} }, {}  );  //61624-wss
  }
  else {

    vl_result :=STOMP_User_CtrlFunct.f_IPL4_connect( STOMP_PCO, tsp_hostname, tsp_remPort,"",0, -1, {tcp := {} }, {} ); //61623-ws
  }
  :
  v_cid:=vl_result.connId;   <----------------
  


However, this has to be communicated to the translation port; so when the first HTTP message requesting connection upgrade is sent, it is
packed together with a connection Id in a composite structure:
template Composite  t_http_message (in integer cid, in integer portNr) :={ hTTP_Message:= {
    msg := {{request_line :={GET,tsp_url,1,1}},{
        host := tsp_hostname&":"&int2str(portNr),  
        connection := {{"Upgrade"}},
        upgrade := {{"websocket"}},
        origin := "null",
        sec_websocket_key := "dGhlIHNhbXBsZSBub25jZQ==",
        sec_websocket_version := "13",
        sec_websocket_protocol := "v12.stomp",
        sec_websocket_extensions:= "permessage-deflate"
      },omit}},
  connId:=cid  <----------------

}with { optional "implicit omit" } 

The connection Id is then saved as a port variable value
function f_enc_HTTP_Translation
//---------------------------------------------------------------------
( in Composite pl_in,
  out ASP_Send pl_out) //return integer
  port STOMP_IPL4_PT
{

v_connId:=pl_in.connId; //save connection id  <----------------

  pl_out.connId := v_connId;
  pl_out.msg := ef_HTTP_Encode(pl_in.hTTP_Message);         
  pl_out.proto := omit;

  port.setstate(0); 
}with {extension "prototype(fast)" }
 


and reused in subsequent transactions.



To extend the IPL4 test port with external functions having the above-declared STOMP_IPL4_PT translation port as reference,
two files containing extension declarations have to be added:
  STOMP_User_CtrlFunct.ttcn
  STOMP_User_CtrlFuncDef.cc
  


(see IPL4 user guide 3.3 Extending the port); the templates for these files can be found in the IPL4 port source ocde, and have to be edited accordingly.

Now, if we think in terms of test cases to be executed in a sequence, there are some principles that have to be observed, and the first such principle says that these test cases
have to be independent, meaning that their execution order should not affect the outcome.
All test cases have to contain a preparation phase, establishing a connection, logging into the SUT etc. else the above principle would not be respected.
(Say if only the first test case would contain a login sequence, in absence of the first test case all other test cases would fail).

The practice is to extract these preparation steps common to all test cases into a separate function called preamble.
Similarly, to closing steps that will take back the system to its initial status are collected into a postamble.

Our preamble (f_preamble) contains:

-establishment of the TCP/TLS connection
-connection upgrade from HTTP 1.1 to WebSocket

and the postamble (f_postamble):

-closing the websocket
-tearing down the TCP/TLS connection


The TTCN-3 client will also have to reply to WebSocket PONG requests from the broker; again, this is something the user should not be bothered with, so
I have added a default altstep , perfect for such tasks:
 altstep as_default() runs on WS_CT  system System_CT
{

  []STOMP_PCO.receive(t_websocket_pong) -> value v_WebSocket_PDU	{     
    log("Websocket response received : ",v_WebSocket_PDU)
    STOMP_PCO.send(t_websocket_ping); 
    repeat;
  }
  [] t.timeout{log("Bye")}
}


The default is activated at the beginning of the test case:

testcase TC_websocket() runs on WS_CT  system System_CT  {  

  f_preamble();
  var default v_def := activate(as_default())
:
:


The preamble/postamble and the use of default moves handling of HTTP and Websocket messages out of sight;
the test case itself is simpified and focused on STOMP:
//------------------------------------------------------------------------
testcase TC_websocket() runs on WS_CT  system System_CT  {  
//------------------------------------------------------------------------
   f_preamble();

  var default v_def := activate(as_default())

  //send CONNECT

  STOMP_PCO.send(t_connect);
  t.start(1.0) 

  alt 
  { 
    []STOMP_PCO.receive(t_connected) -> value v_stomp	{ 
      log("STOMP  CONNECTED received : ",v_stomp);
    }

    []STOMP_PCO.receive(STOMPFrame:?) -> value v_stomp	{    
      log("unexpected STOMP response received instead of CONNECTED : ",v_stomp);
    }
    [] t.timeout{log("Bye")}

  }  

  //send SUBSCRIBE
  STOMP_PCO.send(t_subscribe);
  //send SEND 
  STOMP_PCO.send(t_send(char2oct("Haha, charade you are")));
  t.start(1.0) 

  alt 
  { 

    []STOMP_PCO.receive(t_message) -> value v_stomp	{ 
      log("STOMP MESSAGE received : ",v_stomp);
      log("match:>>>>", match(v_stomp,t_message) )
    }

    []STOMP_PCO.receive(STOMPFrame:?) -> value v_stomp	{    
      log("unexpected STOMP response received  instead of MESSAGE: ",v_stomp);
    }

    [] t.timeout{log("Bye")}

  } 

  //Wait for a specific message

  t.start(180.0) 
  alt 
  { 

    []STOMP_PCO.receive(t_message_r) -> value v_stomp	{ 
      log("STOMP MESSAGE received : ",v_stomp);
    }

    []STOMP_PCO.receive(STOMPFrame:?) -> value v_stomp	{    
      log("unexpected STOMP response received  instead of MESSAGE: ",v_stomp);
      //  log("match:>>>>", match(v_stomp,t_message_r) )

      repeat;
    }

    [] t.timeout{log("Bye")}

  } 


  f_postamble();

  setverdict(pass);
}

In addition to the STOMP sequence presented in the previous part, after sending a message to the chat queue,
the TTCN-3 client waits for a specific string and then terminates execution; PONG messages received while waiting are handled by the default
and responded automatically.

In summary , I have introduced three changes to streamline the code:
-translation port
-preamble postamble for common parts of the test cases
-default for automatic handling of periodic messages




To run the code, we need to:
- Install if not already installed and start the Apache Apollo message broker (see previous part)
-start the example chat application which connect as chat client to the broker ( see also the previous part)

"Start a browser with websocket support and navigate to:

file:///home/userxxx/Apache_Apollo/examples/stomp/websocket/index.html

or wherever the chat example you have downloaded is deposited on your machine,
and login with the following parameters:

Websocket URL : ws://localhost:61623
User, Password as configured (default login id and password is "admin" and "password"- don't forget to change this in aproduction environment).
Destination: /topic/chat.general

Now the code executed in the browser will connect to the message broker as a chat client; in the Debug Log window in the right the message exchange
can be monitored. All connected and subscribed clients will be dispatched the messages sent to this topic.
The TTCN-3 code will connect and subscribe in a similar manner."


After executing the TTCN-3 code, the message sent to the broker is distributed to the chat clients, hence it will appear in the chat app running in the browser.
To stop execution , we need to type in the expected string in the chat box.

index.php/fa/30618/0/

 
 ttcn3_start ./stompWS StompWS.cfg
ttcn3_start: Starting the test suite                                             
spawn /proj/TTCN/Releases/TTCNv3_daily_LMWP3.1/bin/mctr_cli StompWS.cfg          

*************************************************************************
* TTCN-3 Test Executor - Main Controller 2                              *
* Version: CRL 113 200/6 R2A                                            *
* Copyright (c) 2000-2017 Ericsson Telecom AB                           *
* All rights reserved. This program and the accompanying materials      *
* are made available under the terms of the Eclipse Public License v1.0 *
* which accompanies this distribution, and is available at              *
* http://www.eclipse.org/legal/epl-v10.html                             *
*************************************************************************

Using configuration file: StompWS.cfg
MC@esekilxxen1842: Unix server socket created successfully.
MC@esekilxxen1842: Listening on TCP port 8035.             
MC2> esekilxxen1842.rnd.ericsson.se is the default         
spawn ././stompWS esekilxxen1842.rnd.ericsson.se 8035      
TTCN-3 Host Controller (parallel mode), version CRL 113 200/6 R2A
MC@esekilxxen1842: New HC connected from esekilxxen1842.rnd.ericsson.se [147.214.13.99]. esekilxxen1842: Linux 2.6.32.45-0.3-xen on x86_64.
cmtc                                                                                                                                       
MC@esekilxxen1842: Downloading configuration file to all HCs.                                                                              
MC@esekilxxen1842: Configuration file was processed on all HCs.                                                                            
MC@esekilxxen1842: Creating MTC on host esekilxxen1842.rnd.ericsson.se.                                                                    
MC@esekilxxen1842: MTC is created.                                                                                                         
MC2> smtc                                                                                                                                  
Executing all items of [EXECUTE] section.                                                                                                  
MC2> MTC@esekilxxen1842: connect result{ errorCode := omit, connId := 1, os_error_code := omit, os_error_text := omit }                    
MTC@esekilxxen1842: v_connId:  1                                                                                                           
MTC@esekilxxen1842: STOMP  CONNECTED received : { command := CONNECTED (9), headers := { { header_name := "version", header_value := "1.2" }, { header_name := "server", header_value := "apache-apollo/1.7.1" }, { header_name := "host-id", header_value := "mybroker" }, { header_name := "session", header_value := "mybroker-90" }, { header_name := "heart-beat", header_value := "100,10000" }, { header_name := "user-id", header_value := "admin" } }, payload := omit }                                                                                                                                               
MTC@esekilxxen1842: v_connId:  1                                                                                                                        
MTC@esekilxxen1842: v_connId:  1                                                                                                                        
MTC@esekilxxen1842: STOMP MESSAGE received : { command := MESSAGE (12), headers := { { header_name := "subscription", header_value := "sub-0" }, { header_name := "ack", header_value := "2" }, { header_name := "message-id", header_value := "mybroker-901" }, { header_name := "destination", header_value := "/topic/chat.general" }, { header_name := "content-length", header_value := "21" } }, payload := '486168612C206368617261646520796F7520617265'O ("Haha, charade you are") }                                                                                                                                     
MTC@esekilxxen1842: match:>>>> matched
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Binary_frame (2), mask_bit := '0'B, payload_len := 1, masking_key := omit, payload_data := { data := '0A'O ("\n") } }
MTC@esekilxxen1842: STOMP MESSAGE received : { command := MESSAGE (12), headers := { { header_name := "subscription", header_value := "sub-0" }, { header_name := "ack", header_value := "3" }, { header_name := "message-id", header_value := "mybroker-8d4" }, { header_name := "destination", header_value := "/topic/chat.general" }, { header_name := "content-length", header_value := "15" } }, payload := '426967206D616E20706967206D616E'O ("Big man pig man") }
MTC@esekilxxen1842: Websocket response received : { fin_bit := '1'B, rsv1_bit := '0'B, rsv2_bit := '0'B, rsv3_bit := '0'B, opcode := Connection_Close (8), mask_bit := '0'B, payload_len := 2, masking_key := omit, payload_data := { close_data := { status_code := 1000, data := omit } } }
MTC@esekilxxen1842: close result{ errorCode := omit, connId := 1, os_error_code := omit, os_error_text := omit }
MC@esekilxxen1842: Test execution finished.
Execution of [EXECUTE] section finished.
emtc
MC@esekilxxen1842: Terminating MTC.
MC@esekilxxen1842: MTC terminated.
MC2> exit
MC@esekilxxen1842: Shutting down session.
MC@esekilxxen1842: Shutdown complete.



Code and logs are attached as usual.

Note: The translation port support is not part of any released binary yet; one has to build Titan from the latest source to use this feature.




Best regards
Elemer
Previous Topic:Chat with STOMP over WebSocket in Titan part 1
Next Topic:Test port for serial communication
Goto Forum:
  


Current Time: Fri Apr 26 12:38:32 GMT 2024

Powered by FUDForum. Page generated in 0.02742 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top