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 |
|
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.
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
|
|
|
Goto Forum:
Current Time: Fri Apr 26 12:38:32 GMT 2024
Powered by FUDForum. Page generated in 0.02742 seconds
|