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)])) } + ). + +%-------------------------------------------------------------------------% |