Edinburgh Speech Tools  2.1-release
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
doc/estserver.md
1 Client-Server Mechanisms {#estserver}
2 ==========================
3 
4 [TOC]
5 
6 
7 The C++ class \ref EST_Server provides the core
8 mechanisms required for simple client-server applications. It is
9 currently used to implement the
10 `fringe_client`
11 program and client-server mechanisms for SIOD. It is planned to
12 use it to re-implement the festival client-server mechanism.
13 
14 Servers have types and names. When a server is started it
15 records it's type, name and location in a `services' file. When
16 a client wishes to connect to a server it looks for it's
17 location in that file by giving a name and type.
18 
19 Once connected, a client must present a magic cookie to the
20 server as a simple form of authentication. Once authenticated
21 the client sends requests, consisting of a package name,
22 operation name and a set of arguments, to the server. The server
23 responds with an error report or a sequence of values.
24 
25 An instance of \ref EST_Server embodies each
26 side of the client-server relationship. In the server an
27 instance of \ref EST_Server is created and
28 told how to process requests from clients, a call to the
29 EST_Server::run() method then starts the
30 server. In a client an instance of \ref EST_Server
31 represents the server, and calls to the
32 EST_Server::execute() method send
33 requests to the server.
34 
35 # The Services Table {#estserverservicetable}
36 
37 The first problem which needs to be addressed by any
38 client-server system is how the client finds the
39 server. Servers based on \ref EST_Server
40 handle this problem by writing a record into a file giving
41 their name, type and location. Clients can then look servers
42 up by namd and type.
43 
44 By default the file `.estServices` is used
45 for this purpose, meaning that each user has their own list of
46 servers. An alternative file could be specified to record
47 public services.
48 
49 The services table also provides a simple authorisation
50 mechanism. Each server records a random string in the table,
51 and clients must send this string before making any
52 requests. Thus people who can't read the services table can't
53 make requests of the server, and the file permissions on the
54 services table can be used to control access to the server.
55 \par Important:
56 
57 This `magic cookie' authorisation scheme is not very
58 secure. The cookie is sent as plain text over the
59 network and so anyone who can snoop on the network can
60 break the security.
61 
62 
63 A more secure `challange-responce' authorisation scheme
64 should be implemented.
65 
66 
67 
68 The in-file format of the services table is based on the
69 Java properties file format. A typical file might look as
70 follows:
71 
72 @code
73  #Services
74  fringe.type=fringe
75  fringe.host=foo.bar.com
76  fringe.cookie=511341634
77  fringe.port=56362
78  fringe.address=123.456.789.654
79  siod.type=siod
80  siod.cookie=492588950
81  siod.host=foo.bar.com
82  siod.address=123.456.789.654
83  siod.port=56382
84  labeling.type=fringe
85  labeling.host=foo.bar.com
86  labeling.cookie=511341634
87  labeling.port=56362
88  labeling.address=123.456.789.654
89 @endcode
90 
91 This file lists three services, a
92 `fringe` server with the default
93 name of `fringe`, a scheme interpreter
94 running as a server, also with the default name, and a second
95 `fringe` server named `labeling`.
96 
97 
98 The programing interface to the services table is provided by
99 the \ref EST_ServiceTable class.
100 
101 
102 # Writing Clients and Servers {#estserverwritingclientserver}
103 
104 If a service type (that is a sub-class of
105 \ref EST_Server ) has already been defined
106 for the job you need to do, creating clients and servers is
107 quite straight forward. For this section I will use the
108 \ref EST_SiodServer class, which defines a
109 simple scheme execution service service, as an example.
110 
111 ## A Simple Server
112 
113 To run a siod server we have to read the server table,
114 create the server object and update the table, then start the
115 service running.
116 
117 First we read the default service table.
118 
119 @code{.cpp}
120  EST_ServiceTable::read();
121 @endcode
122 
123 Now we create the new scheme service called "mySiod". The
124 `sm_sequential` parameter to the \ref Mode
125 server constructor tells the server to deal with one client
126 at a time. The `NULL` turns off trace
127 output, replace this with `&cout` to see
128 what the server is doing.
129 
130 @code{.cpp}
131  EST_SiodServer *server
132  = new EST_SiodServer(EST_Server::sm_sequential,
133  "mySiod",
134  NULL);
135 @endcode
136 
137 Write the table back out so clients can find us.
138 
139 @code{.cpp}
140  EST_ServiceTable::write();
141 @endcode
142 
143 Create the object which handles the client requests. The
144 `handler` object actually does the work
145 the client requests. \ref EST_SiodServer
146 provides the obvious default handler (it executes the scheme
147 code and returns the results), so we use that.
148 
149 @code{.cpp}
150  EST_SiodServer::RequestHandler handler;
151 @endcode
152 
153 Finally, start the service. This call never returns.
154 
155 @code{.cpp}
156  server->run(handler);
157 @endcode
158 
159 ## A Simple Client {#simple-client}
160 
161 A client is created by reading the service table, and then
162 asking for a server by name. Again the `NULL` means `no trace output'.
163 
164 @code{.cpp}
165  EST_ServiceTable::read();
166 
167  EST_SiodServer *server
168  = new EST_SiodServer("mySiod", NULL);
169 @endcode
170 
171 Now we have a representation of the server we must connect
172 before we can do anything. We can connect and dissconnect a
173 server object any number of times over it's life. This may
174 or may not have some meaning to the server. The return value
175 of the connect operation tells us if we managed to connect.
176 
177 @code{.cpp}
178  if (server->connect() != connect_ok)
179  EST_sys_error("Error Connecting");
180 @endcode
181 
182 Once we are connected we can send requests to the
183 server. The siod server executes scheme for us, assume that
184 the function \ref get_sexp() returns something
185 we want evaluated.
186 
187 @code{.cpp}
188  LISP expression = get_sexp();
189 @endcode
190 
191 We pass arguments to requests in an \ref Args
192 structure, a special type of
193 \ref EST_Features . The siod server wants
194 the expression to execute as the value of
195 `sexp`.
196 
197 @code{.cpp}
198  EST_SiodServer::Args args;
199  args.set_val("sexp", est_val(expression));
200 @endcode
201 
202 As in the server, the behaviour of the client is defined by
203 a `handler' object. The handler
204 \ref EST_SiodServer defines for us does
205 nothing with the result, leaving it for us to deal with in
206 the \ref EST_Features structure
207 `handler.res`. Again this is good enough
208 for us.
209 
210 @code{.cpp}
211  EST_SiodServer::ResultHandler handler;
212 @endcode
213 
214 Finally we are ready to send the request to the server. The
215 siod server provides only one operation, called
216 `"eval"` in package
217 `"scheme"`, this is the evaluate-expression
218 operation we want. The return value of
219 \ref execute() is true of everything goes
220 OK, false for an error. For an error the message is the
221 value of `"ERROR"`.
222 
223 @code{.cpp}
224  if (!server->execute("scheme", "eval", args, handler))
225  EST_error("error from siod server '%s'",
226  (const char *)handler.res.String("ERROR"));
227 @endcode
228 
229 Now we can get the result of the evaluation, it is returned
230 as the value of `"sexp"`.
231 
232 @code{.cpp}
233  LISP result = scheme(handler.res.Val("sexp"));
234 @endcode
235 
236 Although this may seem a lot of work just to evaluate one
237 expression, once a connection is established, only the three
238 steps set arguments, execute, extract results need to be
239 done for each request. So the following would be the code
240 for a single request:
241 
242 @code{.cpp}
243  args.set_val("sexp", est_val(expression));
244  if (!server->execute("scheme", "eval", args, handler))
245  [handle error]
246  LISP result = scheme(handler.res.Val("sexp"));
247 @endcode
248 
249 ## A Specialised Server {#estserverspecializedserver}
250 
251 If you need to create a server similar to an existing one
252 but which handles requests slightly differently, all you
253 need to do is define your own
254 \ref RequestHandler class. This class has
255 a member function called
256 RequestHandler::process() which does the work.
257 
258 
259 Here is a variant on the siod server which handles a new
260 operation `"print"` which evaluates an
261 expression and prints the result to standard output as well
262 as retruning it. (In this example some details of error
263 catching and so on necessary for dealing with scheme are
264 omitted so as not to obscure the main points).
265 
266 
267 First we define the handler class. It is a sub-class of the
268 default handler for siod servers.
269 
270 @code{.cpp}
271  class MyRequestHandler : public EST_SiodServer::RequestHandler
272  {
273  public:
274  virtual EST_String process(void);
275  };
276 @endcode
277 
278 Now, we define the processing method. For any operation
279 other than `"print"` we call the default
280 siod handler. (\ref leval and
281 \ref lprint are functions provided by the
282 siod interpreter).
283 
284 @code{.cpp}
285  EST_String MyRequestHandler::process(void)
286  {
287  if (operation == "print")
288  {
289  // Get the expression.
290  LISP sexp = scheme(args.Val("sexp"));
291 
292  // Evaluate it.
293  LISP result = leval(sexp, current_env);
294 
295  // Print it.
296  lprint(result);
297 
298  // Return it.
299  res.set_val("sexp", est_val(result));
300  return "";
301  }
302  else
303  // Let the default handler deal with other operations.
304  return EST_SiodServer::RequestHandler::process();
305  }
306 @endcode
307 
308 And now we can start a server which understands the new
309 operation.
310 
311 @code{.cpp}
312  MyRequestHandler handler;
313  server->run(handler);
314 @endcode
315 
316 ## A Client Which Handles Multiple Results {#estserverclientmultiplereq}
317 
318 Servers have the option to return more than one value for a
319 single request. This can be used to return the results of a
320 request a piece at a time as they become available, for
321 instance *festival* returns a waveform for each sentence in
322 a piece of text it is given to synthesise.
323 
324 
325 Clearly a simple client of the kind described
326 \link simple-client above \endlink which gets the
327 result of a request as a result of the call to
328 EST_SiodServer::execute() can't handle
329 multiple results of this kind. This is what the handler
330 object is for.
331 
332 I'll asume we need a client to deal with a variant on the
333 normal siod sever which returns multiple values, say it
334 evaluates the expression in each of a number of environments
335 and returns each result separately. I'll also assume that
336 the work to be done for each result is defined by the fucntion
337 \ref deal_with_result().
338 
339 
340 Most of the client will be the same as for
341 \link simple-client above \endlink,
342 the exception is that we use our own result handler rather
343 than the default one.
344 
345 @code{.cpp}
346  class MyResultHandler : public EST_SiodServer::ResultHandler
347  {
348  public:
349  virtual void process(void);
350  };
351 @endcode
352 
353 As for the server's request handler, the behaviour of the
354 result handler is defined by the
355 process() method of the handler.
356 
357 @code{.cpp}
358  EST_String MyResultHandler::process(void)
359  {
360  // Get the result.
361  LISP result = scheme(handler.res.Val("sexp"));
362 
363  // And deal with it.
364  deal_with_result(result);
365  }
366 @endcode
367 
368 With this definition in place we can make requests to the
369 server as follows.
370 
371 @code{.cpp}
372  MyResultHandler handler;
373  if (!server->execute("scheme", "multi-eval", args, handler))
374  [handle errors]
375 @endcode
376 
377 The \ref deal_with_result() function will be
378 called on each result which is returned. If anything special
379 needs to be done with the final value, it can be done after
380 the call to EST_SiodServer::execute()
381 as in the simple client example.
382 
383 # Creating a new Service
384 
385 Not written
386 
387 ## Commands
388 
389 Not written
390 
391 ## Results
392 
393 Not written
394 
395 # The Network Protocol
396 
397 Not written