1 Client-Server Mechanisms {#estserver}
2 ==========================
8 mechanisms required
for simple client-server applications. It is
9 currently used to implement the
11 program and client-server mechanisms
for SIOD. It is planned to
12 use it to re-implement the festival client-server mechanism.
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.
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.
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.
35 # The Services Table {#estserverservicetable}
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
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
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.
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
63 A more secure `challange-responce
' authorisation scheme
64 should be implemented.
68 The in-file format of the services table is based on the
69 Java properties file format. A typical file might look as
75 fringe.host=foo.bar.com
76 fringe.cookie=511341634
78 fringe.address=123.456.789.654
82 siod.address=123.456.789.654
85 labeling.host=foo.bar.com
86 labeling.cookie=511341634
88 labeling.address=123.456.789.654
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`.
98 The programing interface to the services table is provided by
99 the \ref EST_ServiceTable class.
102 # Writing Clients and Servers {#estserverwritingclientserver}
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.
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
117 First we read the default service table.
120 EST_ServiceTable::read();
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.
131 EST_SiodServer *server
132 = new EST_SiodServer(EST_Server::sm_sequential,
137 Write the table back out so clients can find us.
140 EST_ServiceTable::write();
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.
150 EST_SiodServer::RequestHandler handler;
153 Finally, start the service. This call never returns.
156 server->run(handler);
159 ## A Simple Client {#simple-client}
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'.
165 EST_ServiceTable::read();
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.
178 if (server->connect() != connect_ok)
179 EST_sys_error("Error Connecting");
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
188 LISP expression = get_sexp();
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
198 EST_SiodServer::Args args;
199 args.set_val("sexp", est_val(expression));
202 As in the server, the behaviour of the client is defined by
203 a `handler' object. The handler
205 nothing with the result, leaving it
for us to deal with in
207 `handler.res`. Again
this is good enough
214 Finally we are ready to send the request to the server. The
215 siod server provides only one operation, called
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
224 if (!server->execute("scheme", "eval", args, handler))
225 EST_error("error from siod server '%s'",
226 (const char *)handler.res.String("ERROR"));
229 Now we can
get the result of the evaluation, it is returned
230 as the value of `
"sexp"`.
233 LISP result = scheme(handler.res.Val(
"sexp"));
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:
243 args.set_val(
"sexp", est_val(expression));
244 if (!server->execute(
"scheme",
"eval", args, handler))
246 LISP result = scheme(handler.res.Val(
"sexp"));
249 ## A Specialised Server {#estserverspecializedserver}
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.
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).
267 First we define the handler class. It is a sub-class of the
268 default handler for siod servers.
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
287 if (operation ==
"print")
290 LISP sexp = scheme(args.Val(
"sexp"));
293 LISP result = leval(sexp, current_env);
299 res.set_val(
"sexp", est_val(result));
304 return EST_SiodServer::RequestHandler::process();
308 And now we can start a server which understands the
new
312 MyRequestHandler handler;
313 server->run(handler);
316 ## A Client Which Handles Multiple Results {#estserverclientmultiplereq}
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.
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
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().
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.
349 virtual void process(
void);
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.
358 EST_String MyResultHandler::process(void)
361 LISP result = scheme(handler.res.Val("sexp"));
364 deal_with_result(result);
368 With this definition in place we can make requests to the
372 MyResultHandler handler;
373 if (!server->execute("scheme", "multi-eval", args, handler))
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.
383 # Creating a new Service
395 # The Network Protocol