![]() |
License / Documentation home / Help and feedback | ![]() |
Up to this point, we've studied only the simplest type of servers, which we exemplified using the Parser server. As we described in the server basics lesson, the Generator and Backend also are no more complex: all three servers accept messages and return a response. In this lesson, we're going to look at a slighly more complex server, one which doesn't return any responses, but rather sends new messages (and sometimes waits for their replies). This server is the Dialogue server.
# GreetingThe way the Dialogue server is implemented, all of these interactions involve sending a new message to the Hub. In addition, the database query involves sending a new message to the Hub and waiting for the response. We'll examine each of these elements in turn.
System: Welcome to Communicator. How may I help you?
User: I WANT TO FLY TO LOS ANGELES
# Additional information
System: Where are you traveling from?
User: BOSTON
# Database query and response presentation
System: American Airlines flight 115 leaves at 11:44 AM, and United flight 436 leaves at 2:05 PM
Gal_Frame DoGreeting(Gal_Frame frame, void *server_data)The function we use to write new frames is GalSS_EnvWriteFrame. This function takes three arguments: a call environment, a frame to use as the message, and a flag indicating whether to wait for the frame to be written before returning. So when the Dialogue server receives a frame named DoGreeting, it constructs a frame named FromDialogue, writes it to the Hub, frees the frame, and returns NULL, which means that no frame will be returned (unless a reply is required, in which case a dummy will be sent). We can illustrate this with an example.
{
char *s = "{c FromDialogue :output_frame {c greeting } :is_greeting 1 }";
Gal_Frame greeting = Gal_ReadFrameFromString(s);GalSS_EnvWriteFrame((GalSS_Environment *) server_data, greeting, 0);
Gal_FreeFrame(greeting);
return (Gal_Frame) NULL;
}
[New messages exercise 1]In this example, the unit tester will pretend to be the Hub. Start the Dialogue server and then the unit tester. Select "Send new message", choose the DoGreeting frame, and press OK. The interaction history will show a new message:
Unix:
% process_monitor $GC_HOME/tutorial/messages/greeting.config
Windows:
C:\> python %PM_DIR%\process_monitor.py %GC_HOME%\tutorial\messages\greeting.config
[Interaction History pane]Note that we neither asked for a reply to the message we sent, nor did we receive one.[Sending: new message]
{c DoGreeting }
[Received: new message]
{c FromDialogue
:output_frame {c greeting }
:is_greeting 1 }
(If you're curious, you can repeat this exercise with "Reply required" selected, and you can see how the unit tester receives a dummy reply in addition to the new message.)
Select "File --> Quit" to end this exercise.
PROGRAM: DBQueryWhen the program is done, the result is returned to the originating server. This interaction corresponds to the following small subsection of the toy travel demo:RULE: :sql_query --> Backend.Retrieve
IN: :sql_query
OUT: :column_names :nfound :values
[New messages exercise 2]The unit tester will be functioning as a server in this example. Start the Backend server, the Hub, then the unit tester. Select "Send new message", select the first frame named DBQuery, press "Reply required" and then OK. You should see something like this in the unit tester interaction history:
Unix:
% process_monitor $GC_HOME/tutorial/messages/backend.config
Windows:
C:\> python %PM_DIR%\process_monitor.py %GC_HOME%\tutorial\messages\backend.config
[Interaction History pane]If you look at the Hub pane, you'll see that the new message matched the appropriate program, fired the rule to invoke the Backend server, and then terminated normally, returning the token state to the originating server (the unit tester):[Sending: new message]
{c DBQuery
:sql_query "select airline, flight_number, departure_datetime from flight_table where departure_aiport = 'BOS' and arrival_airport = 'LAX'" }
[Received: reply message]
{c DBQuery
:nfound 2
:values ( ( "AA"
"115"
"1144" )
( "UA"
"436"
"1405" ) )
:sql_query "select airline, flight_number, departure_datetime from flight_table where departure_aiport = 'BOS' and arrival_airport = 'LAX'"
:column_names ( "airline"
"flight_number"
"departure_datetime" )
:session_id "Default" }
[Hub pane]Formally, this interaction is identical to our previous interactions with the Parser server: the unit tester sends a new message to the Hub, the appropriate program is matched, the rules are fired and the token state updated, and the token state returned when the program ends.----------------[ 1]----------------------
{c DBQuery
:sql_query "select airline, flight_number, departure_datetime from flight_table where departure_aiport = 'BOS' and arrival_airport = 'LAX'"
:session_id "Default"
:tidx 1 }
--------------------------------------------
Found operation for token 1: Backend.Retrieve
Serving message with token index 1 to provider for Backend @ localhost:13000
---- Serve(Backend@localhost:13000, token 1 op_name Retrieve)
Got reply from provider for Backend @ localhost:13000 : token 1
----------------[ 1]----------------------
{c DBQuery
:sql_query "select airline, flight_number, departure_datetime from flight_table where departure_aiport = 'BOS' and arrival_airport = 'LAX'"
:session_id "Default"
:tidx 1
:column_names ( "airline"
... )
:nfound 2
:values ( ( "AA"
... )
... ) }
--------------------------------------------
Done with token 1 --> returning to owner UI@<remote>:-1
Destroying token 1
If the Backend server raises an error, of course, the error will be forwarded to the originating server, as we saw in the last lesson. (Notice that there's no ERROR: directive in the Hub program rule, so the program won't continue.) We can see this again if we select "Send new message", select the second frame named DBQuery (the one with arrival_airport = 'SFO'), press "Reply required" and then OK. This time, the uniit tester interaction history will show an error reply:
[Interaction History pane]Select "File --> Quit" to end this exercise[Sending: new message]
{c DBQuery
:sql_query "select airline, flight_number, departure_datetime from flight_table where departure_aiport = 'BOS' and arrival_airport = 'SFO'" }
[Received: error message]
{c system_error
:session_id "Default"
:err_description "no DB result"
:errno 0 }
Gal_Frame DoDialogue(Gal_Frame f, void *server_data)Let's look at this a little more closely:
{
Gal_Frame msg_frame = Gal_MakeFrame("DBQuery", GAL_CLAUSE);
Gal_Frame r_f;
GalIO_MsgType reply_type;
char *sql_query;
GalSS_Environment *env = (GalSS_Environment *) server_data;/* ... */
Gal_SetProp(msg_frame, ":sql_query", Gal_StringObject(sql_query);
response_frame = GalSS_EnvDispatchFrame(env, msg_frame, &reply_type);
Gal_FreeFrame(msg_frame);
switch (reply_type) {
case GAL_ERROR_MSG_TYPE:
/* Relay error */
/* ... */
break;
case GAL_REPLY_MSG_TYPE:
/* Construct presentation of database response */
/* ... */
}
}
So when the Dialogue server receives a frame which causes it to consult the Backend server, this is how it gets the result from the Backend server via the Hub.
In this next example, the unit tester is going to act as a server. We're going to use the following program file:
;; Use extended syntax (new in version 3.0).We're going to send a message named DoDialogue, and the Dialogue server will respond with a message named FromDialogue, as in the DoGreeting case. Now, neither of these messages corresponds to a Hub program by that name, but both of them correspond to operations, and the Hub will find the server with the appropriately named operation and send the message to it. The Dialogue server provides DoDialogue, and the UI server (!) provides FromDialogue.PGM_SYNTAX: extended
SERVICE_TYPE: UI
CLIENT_PORT: 14500
OPERATIONS: FromDialogueSERVER: Dialogue
HOST: localhost
PORT: 18500
OPERATIONS: DoDialogue DoGreetingSERVER: Backend
HOST: localhost
PORT: 13000
OPERATIONS: RetrievePROGRAM: DBQuery
RULE: :sql_query --> Backend.Retrieve
IN: :sql_query
OUT: :column_names :nfound :values
This last bit is a little sneaky. The unit tester connects to the Hub, pretending to be the UI. The unit tester itself doesn't implement any dispatch functions, but it is constructed so it can field any message. So we're just relaying this message to that server so that you can see what the result of the interaction is. In effect, we're performing a slightly larger subset of the toy travel demo than just the interaction with the Backend; the unit tester is providing input to the Dialogue server, and digesting output from the Dialogue server:
[New messages exercise 3]Start the Backend, Dialogue, Hub and then the unit tester. Select "Send new message", select the first frame named DoDialogue, and press OK. If you scroll through the output in the Hub pane, you'll see the following steps:
Unix:
% process_monitor $GC_HOME/tutorial/messages/hub_backend.config
Windows:
C:\> python %PM_DIR%\process_monitor.py %GC_HOME%\tutorial\messages\hub_backend.config
[Interaction History pane]Now, let's look at how errors might be handled here. Remember, the DoDialogue dispatch function doesn't send a reply. This means that it can't send an error reply (well, it could, but since the Hub isn't waiting for a reply, it will discard whatever reply it gets). So the Dialogue server will have to send a new message when it gets an error from the Backend server. So select "Send new message", select the second frame named DoDialogue, and press OK. The Hub and servers will follow exactly the same steps, except that the Hub will receive an error reply from the Backend server, and the new message the unit tester receives will describe the error:[Sending: new message]
{c DoDialogue
:frame {c flight
:origin "BOSTON"
:destination "LOS ANGELES" } }
[Received: new message]
{c FromDialogue
:tidx 3
:output_frame {c db_result
:tuples ( ( "AA"
"115"
"1144" )
( "UA"
"436"
"1405" ) )
:column_names ( "airline"
"flight_number"
"departure_datetime" ) }
:session_id "Default" }
[Interaction History pane]Select "File --> Quit" to end this exercise.[Sending: new message]
{c DoDialogue
:frame {c flight
:origin "BOSTON"
:destination "SAN FRANCISCO" } }
[Received: new message]
{c FromDialogue
:tidx 6
:output_frame {c error
:description "error consulting backend" }
:session_id "Default" }
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Let's do one more exercise:
;;
;; MAIN INPUT BODY
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;PROGRAM: UserInput
RULE: :input_string --> Parser.Parse
IN: :input_string
OUT: :frameRULE: :frame --> Dialogue.DoDialogue
IN: :frame
OUT: none!;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; MAIN OUTPUT BODY
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;PROGRAM: FromDialogue
RULE: :output_frame --> Generator.Generate
IN: :output_frame
OUT: :output_stringRULE: :output_string --> UI.ReportIO
IN: :output_string
OUT: none!;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; DB SUBQUERY
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;PROGRAM: DBQuery
RULE: :sql_query --> Backend.Retrieve
IN: :sql_query
OUT: :column_names :nfound :values
[New messages exercise 4]The two available inputs in this exercise correspond to the two inputs in exercise 3. There are a number of processes in this exercise; you should be able to start them all by selecting "Process Control --> Restart all". When the unit tester window appears, select "Send new message", select the first frame named UserInput, and press OK. You'll see the following interaction in the interaction history:
Unix:
% process_monitor $GC_HOME/tutorial/messages/string_io.config
Windows:
C:\> python %PM_DIR%\process_monitor.py %GC_HOME%\tutorial\messages\string_io.config
[Interaction History pane]If you like, you can select the Hub pane in the process monitor and trace the history of this interaction; at this point in the tutorial, you should be able to do that without assistance.[Sending: new message]
{c UserInput
:input_string "I WANT TO FLY FROM BOSTON TO LOS ANGELES" }
[Received: new message]
{c UI.ReportIO
:session_id "Default"
:output_string "American Airlines flight 115 leaves at 11:44 AM, and United flight 436 leaves at 2:05 PM" }
To induce the error, select "Send new message" in the unit tester, select the second frame named UserInput, and press OK, and then look at the interaction history:
[Interaction History pane]Again, inspect the history of the interaction in the Hub pane if you like.[Sending: new message]
{c UserInput
:input_string "I WANT TO FLY FROM BOSTON TO SAN FRANCISCO" }
[Received: new message]
{c UI.ReportIO
:session_id "Default"
:output_string "I'm sorry, but I can't get your answer from the database" }
Select "File --> Quit" to end this exercise.
Next: Setting up a brokered audio connection
![]() |
License / Documentation home / Help and feedback | ![]() |