|
From: Ina C. <in...@st...> - 2001-02-08 04:38:58
|
Hi,
I've implemented the first part of the client interface. Can someone
please review it? Thanks.
Ina
<in...@st...>
========================================================================
Estimated hours taken: 24
Add 2 modules for the client side.
/server/Mmakefile
modify makefile to accommodate the client side.
/server/client_demo.m
new module showing how to call the soap_interface to send
messages to the SOAP server.
/server/options.m
add new options to handle command line arguments for client.
/server/soap_interface.m
new module - provide an interface for programmers to send messages
to the SOAP server.
========================================================================
Index: Mmakefile
===================================================================
RCS file: /cvsroot/quicksilver/webserver/server/Mmakefile,v
retrieving revision 1.4
diff -u -r1.4 Mmakefile
--- Mmakefile 2001/01/25 06:54:42 1.4
+++ Mmakefile 2001/02/08 04:19:22
@@ -1,4 +1,4 @@
-MAIN_TARGET=server libsoap_test_methods
+MAIN_TARGET=server client_demo libsoap_test_methods
-include ../Mmake.params
@@ -18,6 +18,7 @@
# Link in the '-ldl' library (this may not be needed on some systems)
MLLIBS += -ldl
-depend: server.depend soap_test_methods.depend
+depend: server.depend client_demo.depend soap_test_methods.depend
+
server: $(MLLIBS)
Index: client_demo.m
===================================================================
RCS file: client_demo.m
diff -N client_demo.m
--- /dev/null Mon Dec 11 17:26:27 2000
+++ client_demo.m Wed Feb 7 20:19:22 2001
@@ -0,0 +1,77 @@
+%---------------------------------------------------------------------------%
+% Copyright (C) 2001 The University Of Melbourne
+% This file may only be copied under the terms of the GNU General Public
+% License - see the file COPYING
+%---------------------------------------------------------------------------%
+%
+% This module provide a sample of how to call the SOAP interface to send
+% messages from the client to the SOAP server.
+%---------------------------------------------------------------------------%
+
+:- module client_demo.
+:- interface.
+:- import_module io.
+
+:- pred main(io__state::di, io__state::uo) is cc_multi.
+
+%---------------------------------------------------------------------------%
+
+:- implementation.
+:- import_module http, options.
+
+:- import_module bool, char, exception, getopt.
+:- import_module int, list, require, std_util, string.
+
+:- import_module soap_interface.
+
+main -->
+ io__command_line_arguments(Args0),
+ { OptionOpts = option_ops(short_option, long_option, option_defaults)},
+ { getopt__process_options(OptionOpts, Args0, _Args, OptionsResult) },
+ (
+ { OptionsResult = ok(OptTable) },
+ { getopt__lookup_bool_option(OptTable, help, Help) },
+ (
+ { Help = yes },
+ options_help
+ ;
+ { Help = no },
+ { getopt__lookup_string_option(OptTable, host, Host) },
+ { getopt__lookup_int_option(OptTable, port, Port) },
+ { getopt__lookup_string_option(OptTable, method, Method) },
+ { getopt__lookup_string_option(OptTable, uri, URI) },
+ (
+ { Method = "GetStockPrice" }
+ ->
+ { getopt__lookup_int_option(OptTable, int, Int) },
+ soap_call_mercury_type(Host, Port, Method,
+ URI, [univ(Int)], Responses),
+ display_response("Stockprice = ", Responses)
+ ;
+ { Method = "Hello" }
+ ->
+ soap_call_mercury_type(Host, Port, Method,
+ URI, [], Responses),
+ display_response("", Responses)
+ ;
+ { error("Method not supported") }
+ )
+ )
+ ;
+ { OptionsResult = error(OptionErrorString) },
+ io__write_string(OptionErrorString),
+ io__nl,
+ options_help
+ ).
+
+%---------------------------------------------------------------------------%
+
+:- pred display_response(string::in, list(univ)::in, io__state::di,
+ io__state::uo) is det.
+
+display_response(Message, Responses) -->
+ { list__index1_det(Responses, 1, ResponseAsUniv) },
+ { ResponseAsValue = univ_value(ResponseAsUniv) },
+ io__print(Message),
+ write(ResponseAsValue),
+ nl.
Index: options.m
===================================================================
RCS file: /cvsroot/quicksilver/webserver/server/options.m,v
retrieving revision 1.1
diff -u -r1.1 options.m
--- options.m 2000/11/27 10:02:58 1.1
+++ options.m 2001/02/08 04:19:22
@@ -26,6 +26,12 @@
; port
; root
+ % Client options
+ ; method
+ ; uri
+ ; int
+
+
% Miscellaneous Options
; help.
@@ -63,6 +69,14 @@
option_default(port, int(8080)).
option_default(root, string("/var/www")).
+ % General client options
+option_default(uri, string("\"\"")).
+
+% XXX if the default value is not defined, the option cannot
+% be recognised, but this has no default value
+option_default(method, string("Hello")).
+option_default(int, int(0)).
+
% Miscellaneous Options
option_default(help, bool(no)).
@@ -70,16 +84,22 @@
% short_option('f', config_file).
short_option('h', help).
short_option('H', host).
+short_option('i', int).
+short_option('m', method).
short_option('P', port).
short_option('R', root).
+short_option('u', uri).
short_option('v', verbose).
short_option('V', very_verbose).
% long_option("config-file", config_file).
long_option("help", help).
long_option("host", host).
+long_option("int", int).
+long_option("method", method).
long_option("port", port).
long_option("root", root).
+long_option("uri", uri).
long_option("verbose", verbose).
long_option("very-verbose", very_verbose).
@@ -99,6 +119,14 @@
"\t\tWhich port quicksilver listens on.\n",
"\t-R, --root\n",
"\t\tThe root directory for the html files to be served.\n",
+
+ "\nClient Options:\n",
+ "\t-m, --method\n",
+ "\t\tMethod Name to be called.\n",
+ "\t-u, --uri\n",
+ "\t\tURI of SOAPAction header.\n",
+ "\t-i, --int\n",
+ "\t\tAn integer parameter for the RPC.\n",
"\nVerbosity Options:\n",
"\t-v, --verbose\n",
Index: soap_interface.m
===================================================================
RCS file: soap_interface.m
diff -N soap_interface.m
--- /dev/null Mon Dec 11 17:26:27 2000
+++ soap_interface.m Wed Feb 7 20:19:23 2001
@@ -0,0 +1,268 @@
+%---------------------------------------------------------------------------%
+% Copyright (C) 2001 The University of Melbourne
+% This file may only be copied under the terms of the GNU General Public
+% License - see the file COPYING
+%---------------------------------------------------------------------------%
+%
+% This module provide an interface for programmers to send messages
+% to the SOAP server. It will provide as basic functionality the
+% ability to do RPC over SOAP. The hard-coded XML encoding for
+% Mercury datatypes (the same encoding as the SOAP server currently
+% expects) will be used to encode the RPC calls.
+%
+%---------------------------------------------------------------------------%
+
+:- module soap_interface.
+:- interface.
+:- import_module io, list, std_util, string.
+:- import_module tcp.
+
+:- pred soap_call_mercury_type(host::in, port::in, string::in,
+ string::in, list(univ)::in, list(univ)::out,
+ io__state::di, io__state::uo) is det.
+
+%---------------------------------------------------------------------------%
+
+:- implementation.
+:- import_module int, require.
+:- import_module stream.
+
+
+/* Outline:
+soap_call_mercury_type(uri, method_name, list_of_univ_in,
+ list_of_univ_out) :-
+ generate SOAP message in XML format and SOAP protocol,
+ tcp__connect to the server,
+ service_connection,
+ ( which will handle:
+ send the request to the server using HTTP,
+ wait for the response,
+ read response in XML format,
+ )
+ decode the response to mercury types,
+ return list_of_univ as response.
+
+soap_call_xml_type:
+ tcp__connect to the server,
+ service_connection,
+ ( which will handle:
+ send the request to the server using HTTP,
+ wait for the response,
+ read response in XML format,
+ )
+ return response.
+
+*/
+
+soap_call_mercury_type(Host, Port, Method, SOAPuri, Parameters,
+ Responses) -->
+ (
+ { Method = "GetStockPrice" }
+ ->
+ { generate_SP_request(Host, Method, SOAPuri,
+ Parameters, Request) }
+ ;
+ { Method = "Hello" }
+ ->
+ { generate_Hello_request(Host, Method, SOAPuri,
+ Parameters, Request) }
+ ;
+ { error("Method not supported") }
+ ),
+ tcp__connect(Host, Port, Result),
+ (
+ { Result = ok(Connect) },
+ service_connection(Connect, Request, XMLResponse)
+ ;
+ { Result = error(String) },
+ { error(String) }
+ ),
+ { decode_response(Method, XMLResponse, Responses) }.
+
+
+ % Translates XML response to Mercury types.
+:- pred decode_response(string::in, string::in, list(univ)::out) is det.
+
+decode_response(Method, XMLResponse, Responses) :-
+ (
+ Method = "GetStockPrice"
+ ->
+ decode_SP_response(XMLResponse, Responses)
+ ;
+ Method = "Hello"
+ ->
+ decode_Hello_response(XMLResponse, Responses)
+ ;
+ error("Method not supported")
+ ).
+
+%--------------------------------------------------------------------------%
+% GetStockPrice
+%--------------------------------------------------------------------------%
+
+ % Assume library file to be loaded in server is
+ % libsoap_test_methods.so
+ % client shouldn't need to know the library file name?
+
+ % Generates HTTP header information and SOAP message in the body.
+:- pred generate_SP_request(host, string, string, list(univ), string).
+:- mode generate_SP_request(in, in, in, in, out) is det.
+
+generate_SP_request(Host, Method, SOAPuri, Parameters, Request) :-
+ generate_SP_body(Method, Parameters, Body),
+ string__length(Body, Length),
+ generate_header(Host, Length, SOAPuri, Header),
+ Request = insert_cr(Header) ++ Body.
+
+ % Generates SOAP message.
+ % XXX assume no namespace and client has a copy of the schema
+ % used in the server side to encode mercury types.
+:- pred generate_SP_body(string, list(univ), string).
+:- mode generate_SP_body(in, in, out).
+
+ % schema for GetStockPrice:
+ % <element name="stocknum" type="xsd:int">
+
+generate_SP_body(MethodName, Parameters, Body) :-
+ % since client knows that this function takes in only
+ % one parameter, there is no need to call list_foldl to
+ % translate the parameters.
+ list__index1_det(Parameters, 1, Parameter),
+ generate_SP_param(Parameter, ParamAsString),
+ Body = "<Envelope><Body><" ++ MethodName ++ ">" ++
+ ParamAsString ++ "</" ++ MethodName ++
+ "></Body></Envelope>".
+
+:- pred generate_SP_param(univ, string).
+:- mode generate_SP_param(in, out) is det.
+
+generate_SP_param(ParamAsUniv, Tag) :-
+ det_univ_to_type(ParamAsUniv, ParamAsValue),
+ string__int_to_string(ParamAsValue, ParamAsString),
+ Tag = "<stocknum>" ++ ParamAsString ++ "</stocknum>".
+
+:- pred decode_SP_response(string::in, list(univ)::out) is det.
+
+decode_SP_response(XMLResponse, Responses) :-
+ (
+ string__sub_string_search(XMLResponse, "<price>", Start),
+ string__sub_string_search(XMLResponse, "</price>", End)
+ ->
+ string__left(XMLResponse, End, Response0),
+ TagLength = 7, % <price>
+ string__right(Response0, End-Start-TagLength, Response1),
+ (
+ string__to_int(Response1, ResponseAsInt)
+ ->
+ Response = ResponseAsInt
+ ;
+ error("decode error")
+ ),
+ Responses = [univ(Response)]
+ ;
+ error("decode error")
+ ).
+
+%-------------------------------------------------------------------------%
+% Hello
+%-------------------------------------------------------------------------%
+
+ % Generates HTTP header information and SOAP message in the body.
+:- pred generate_Hello_request(host, string, string, list(univ), string).
+:- mode generate_Hello_request(in, in, in, in, out) is det.
+
+generate_Hello_request(Host, Method, SOAPuri, Parameters, Request) :-
+ generate_Hello_body(Method, Parameters, Body),
+ string__length(Body, Length),
+ generate_header(Host, Length, SOAPuri, Header),
+ Request = insert_cr(Header) ++ Body.
+
+ % Generates SOAP message.
+ % Hello pred has no input.
+:- pred generate_Hello_body(string, list(univ), string).
+:- mode generate_Hello_body(in, in, out).
+
+generate_Hello_body(MethodName, _Parameters, Body) :-
+ Body = "<Envelope><Body><" ++ MethodName ++ ">" ++
+ "</" ++ MethodName ++ "></Body></Envelope>".
+
+:- pred decode_Hello_response(string::in, list(univ)::out) is det.
+
+decode_Hello_response(XMLResponse, Responses) :-
+ (
+ string__sub_string_search(XMLResponse, "<output>", Start),
+ string__sub_string_search(XMLResponse, "</output>", End)
+ ->
+ string__left(XMLResponse, End, Response0),
+ TagLength = 8, % <output>
+ string__right(Response0, End-Start-TagLength, Response1),
+ Responses = [univ(Response1)]
+ ;
+ error("decode error")
+ ).
+
+%-------------------------------------------------------------------------%
+% Shared functions
+%-------------------------------------------------------------------------%
+
+ % Generates HTTP header.
+:- pred generate_header(string::in, int::in, string::in,
+ string::out) is det.
+
+generate_header(Host, Length, SOAPuri, Headers) :-
+ Header1 = "POST /libsoap_test_methods.so HTTP/1.1",
+ Header2 = "Host: " ++ Host,
+ Header3 = "Content-Type: text/xml",
+ string__int_to_string(Length, LengthAsString),
+ Header4 = "Content-Length: " ++ LengthAsString,
+ Header5 = "SOAPAction: \"" ++ SOAPuri,
+ Headers = insert_cr(Header1) ++ insert_cr(Header2) ++
+ insert_cr(Header3) ++ insert_cr(Header4) ++
+ insert_cr(Header5).
+
+ % Insert carriage return into the line.
+:- func insert_cr(string) = string.
+insert_cr(Line) = Line ++ "\r\n".
+
+%------------------------------------------------------------------------%
+
+ % Connects to the server using TCP/IP, sends request to server
+ % using HTTP, receives response from server and terminates
+ % the connection.
+:- pred service_connection(tcp::in, string::in, string::out,
+ % io__state::di, io__state::uo) is cc_multi.
+ io__state::di, io__state::uo) is det.
+
+service_connection(TCP, Request, Response) -->
+ send_request(TCP, Request),
+ read_response(TCP, Response),
+ tcp__shutdown(TCP).
+
+ % Sends the request to the server side.
+:- pred send_request(S, string, io__state, io__state) <= stream__output(S).
+:- mode send_request(in, in, di, uo) is det.
+
+send_request(S, Requests) -->
+ stream__write_string(S, Requests).
+
+ % Reads responses from server.
+:- pred read_response(S, string, io__state, io__state) <= stream__input(S).
+:- mode read_response(in, out, di, uo) is det.
+
+read_response(S, Response) -->
+ stream__read_line(S, LineResult),
+ (
+ { LineResult = ok(Line) },
+ { string__from_char_list(Line, String) },
+ { Response = String ++ Response0 },
+ read_response(S, Response0)
+ ;
+ { LineResult = eof },
+ { Response = "" }
+ ;
+ { LineResult = error(Error) },
+ { error(string__format("read_response: %s.", [s(Error)])) }
+ ).
+
+%-------------------------------------------------------------------------%
|