I love the work of developing simple, reusable, and hopefully long lasting software components. By simple I mean easy to use, not necessarily easy to design or implement. As software engineers we should, in my opinion, work hard to make complicated things look simple, not the other way around. On the other hand, I like distributed computing, which is pretty complicated by nature. I get very excited if my programs can jump from machine to machine, go through firewalls, and eventually retrieve or update data on a legacy system (which usually means mainframe to me :-).
At work we have a lot of existing programs designed initially for internal use. The trend is to move functionalities provided by these programs to web servers so that we can serve more customers over the internet. It is impractical to rewrite a lot of these programs using the latest and greatest technology. Typically we wrap the needed functionalities in various web methods and other programs will invoke these methods. Most of our new web server programs are developed using .NET. To invoke a web method from .NET code, according to the documentation, you need to add a web reference to your project. A proxy class for the web service will be generated for your project. You can also manually generate a proxy class using the wsdl.exe tool. The .NET generated proxy class is hard-coded for a specific web service. You cannot use the same proxy class to access a different web service. If your program needs to use 10 different web services, you will have to generate 10 different proxy classes and compile those classes into your code.
The .NET framework class SoapHttpClientProtocol
is the base class for all generated web service proxies. I have tried without success to use this base class to build a reusable proxy class for general web services. Fortunately, I figured out my own solution by using the
SoapClient
object in the Microsoft Soap Toolkit. The SoapClient
object "provides a client-side, high-level interface whose methods and properties send a Simple Object Access Protocol (SOAP) request to the server and process the response from the server". In order to invoke a web method in a most flexible way, I have constructed a com component which is a general web service proxy. My proxy is a key component in the web service dispatcher program and it is described in a separate article .
The web service dispatcher program
This program itself is a simple web service written in ASP.NET. It simplifies the work of building and running web client/server programs. Here is how it works. First, we register existing web methods on the web service dispatcher to make them available to other programs. If you have a program that needs to access multiple web methods implemented in different web services, instead of calling those methods individually, you can write the code to call my web service dispatcher providing only the name of the web method you want to call and the input arguments. The web service dispatcher will forward your request to the corresponding server program and also return the output to you. Again, your code need only to know the name, the input format, and the output format of the web method you want to invoke. So as long as the name, the input format, and the output format of the web method do not change, the server program that implements this particular web method can be changed or moved to a different physical location without affecting your existing code. In fact, the web method you want to invoke can be implemented in more than one server programs, some may be written in C++ and others in VB (or even in Java).
Here is a summary of the main advantages of using the web service dispatcher.
- The programs that use various web methods need only to access the web service dispatcher program.
- The server programs need not be implemented using .NET and they don't have to be running on the windows platform either. The same goes with client programs. The dispatcher itself is implemented using .NET, however.
- Multiple server programs running on one or more machines can provide the same web method (uniquely identified by its name string) through the web service dispatcher so that the client programs can still function if one of the server programs crashes.
- Multiple instances of the web service dispatcher can be used. The same server program can make its web methods available through multiple dispatchers.
In order to use the web service dispatcher, we need to do the following.
- Install and run an instance of the web service dispatcher program.
- Register existing web methods on the dispatcher.
- The other programs invoke the registered web methods through the dispatcher.
There are some restrictions on using the web service dispatcher. First, web methods registered with the dispatcher can only take string arguments and the return value must also be a string. This is to simplify the implementation of the web service dispatcher. Since it is easy to use XML to represent complicated data types, this restriction is really not a big deal. Secondly, a web method implementation must be uniquely identified by the registered method name. For example, if there are two different server programs each registers a web method called GetPersonInfo
on the same instance of the dispatcher, then they must implement the same thing, you cannot have one implementation returns the birthday while the other returns the social security number. The dispatcher assumes that all implementations of the same method are the same and will randomly pick a server, if there is more than one, to process a user request.
Here are the main public methods of the web service dispatcher.
RegisterMethod
. This method takes a single string argument and returns a boolean flag, true
for success and false
for failure. You can register multiple web methods with only one call to this method. We will cover this method in detail later.
UnRegisterMethod
. This is, of course, used to unregister a web method. Details will be discussed later.
InvokeMethodX
. Here X
is a number ranging from 0
to 5
. The first argument of this method is the name string of the web method you want to invoke. If the web method you want to invoke does not take any argument, then you use the InvokeMethod0
. Otherwise, you can provide the input arguments by using InvokeMethod1
, InvokeMethod2
, ..., and InvokeMethod5
, depending on the number of arguments. The returned value of InvokeMethodX
is the empty string if an error occurs, otherwise it is the output string of the web method you just invoked.
As you can see, we can use the web service dispatcher as some sort of "gateway" and let all other programs register and access web methods through this gateway. This will make the implementations of client and server programs easier and it is likely a more flexible and distributive design. Now consider scalability and reliability, what if there are too many users trying to access the web service dispatcher? Will it become a bottleneck? What if the machine that runs the gateway goes down? The answer is, we do not have to restrict ourselves to a single gateway.
For example, suppose we have 10 instances of server programs running on 10 different machines and each of them registers the same set of 15 web methods on 3 different instances of web service dispatcher. If there are 300 simultaneous users (or client programs) trying to call these 15 web methods, we could, in theory anyway, divide these 300 users into 3 groups of roughly the same workload, and let each group access a separate gateway. Please note that if one of the server instances crashes, most client programs will not be affected because the remaining 9 server instances are still providing the same set of web methods. The dispatcher program will randomly pick a server instance if multiple exist for the same web method. This example is pure imaginary and I have no resource to do any realistic testing.
How to install and use the web service dispatcher
The dispatcher has to be installed on a machine that already has the .NET framework and the Microsoft Soap Toolkit 3.0 (you can still use Microsoft Soap Toolkit 2.0 by modifying the web.config file).
Step 1. First, unzip the file WebServiceDispatcher.zip
into a directory on the target machine, you need to preserve directory structure while unzipping.
Step 2. Create a virtual web directory WebServiceDispatcher
pointing to the above directory.
Then we need to register existing web methods with the dispatcher.
Step 3. Call the RegisterMethod
method of the dispatcher.
The RegisterMethod
method does not have to be called by the server instance that implements the web method, but typically a server calls RegisterMethod
to register its web methods when starting up. The following VB script registers a web method named GetData
.
Option Explicit
Const WSDL_URL = _
"http://localhost/WebServiceDispatcher/ServiceDispatcher.asmx?wsdl"
Const INPUT_XML = _
"_
GetData _
Server11 _
http://Server11.com/MyService/MyService.wsdl _
"
Dim spclt
Set spclt = CreateObject("MSSOAP.SoapClient")
spclt.mssoapinit WSDL_URL
If spclt.RegisterMethod(INPUT_XML) Then
WScript.Echo "Web method registered"
Else
WScript.Echo "Failed to register web method"
End If
set spclt = nothing
Here is an example of the input string to the RegisterMethod
method. As you can see, it is possible to register multiple web methods with one call.
<Root>
<MethodList>
<Method>
<MethodName>GetName</MethodName>
<InternalMethodName>GetName1</InternalMethodName>
<ProviderName>NameProvider1</ProviderName>
<ServiceURL>http://NameProvider1.com/WebService/WebService.wsdl
</ServiceURL>
<ProxyServer>MyProxy.Com</ProxyServer>
<ProxyPort>91</ProxyPort>
<AuthUser>Tester</AuthUser>
<AuthPassword>123456</AuthPassword>
</Method>
<Method>
<MethodName>GetName</MethodName>
<InternalMethodName>GetName2</InternalMethodName>
<ProviderName>NameProvider2</ProviderName>
<ServiceURL>http://NameProvider2.com/WebService/WebService.wsdl
</ServiceURL>
<ProxyServer>MyProxy.Com</ProxyServer>
<ProxyPort>91</ProxyPort>
<AuthUser>Tester</AuthUser>
<AuthPassword>123456</AuthPassword>
<Method>
<Method>
<MethodName>GetAge</MethodName>
<ProviderName>AgeProvider</ProviderName>
<ServiceURL>http://AgeProvider.com/WebService/WebService.wsdl
</ServiceURL>
</Method>
</MethodList>
</Root>
The <MethodName>
string identifies the web method registered on the dispatcher. The <ProviderName>
string identifies a server instance that implements this web method. The <InternalMethodName>
string is the name of the web method on the server instance that implements it, you can register the same web method from two different server instances (two different providers) as demonstrated in the above XML. It is assumed that <MethodName>
and <InternalMethodName>
are the same in case <InternalMethodName>
is not provided. When a request to invoke a web method comes to the dispatcher, the dispatcher will randomly pick a provider if there is more than one. The <ServiceURL>
string is used by the dispatcher to access the provider of a web method on behalf of the clients. The <ProxyServer>
and the <AuthUser>
and the <AuthPassword>
strings are also optional, they are needed only if access to the web method provider are restricted by the given user name and password.
It is ok to register a web method for the same provider more than once, the last registeration will override the previous ones. For example, if the <ServiceURL>
string was wrong when it was first registered, you can correct the information by registering it again.
A server providing a web method through the dispatcher should call the UnRegisterMethod
method before shutting down itself. The format of the input string for UnRegisterMethod
is the same as that of the RegisterMethod
except that you need only to specify <MethodName>
and <ProviderName>
. If you don't specify <ProviderName>
, then the web method identified by <MethodName>
will be unregistered for all providers!
Step 4. Other programs access the registered web methods by calling InvokeMethodX
methods of the web service dispatcher.
Here is a VB script that calls the GetData
web method registered in the above script, assuming it takes only one input argument.
Option Explicit
Const WSDL_URL = _
"http://localhost/WebServiceDispatcher/ServiceDispatcher.asmx?wsdl"
Dim spclt
Set spclt = CreateObject("MSSOAP.SoapClient")
spclt.mssoapinit WSDL_URL
Dim output
output = spclt.InvokeMethod1("GetData", "This is an input string")
If output <> "" Then
WScript.Echo "Output: " & output
Else
WScript.Echo "Failed to invoke web meothod"
End If
set spclt = nothing
When writing real code, you should know what to send as the input arguments and how to process the output. I have included code for a sample web service called MathService
which provides four web methods, Add
, Subtract
, Multiply
, and Divide
. To install MathService
, you need to unzip the MathService.zip
file into a directory on the target machine and create a virtual web directory named MathService
for it. Run the script RegisterMathService.vbs
to register methods of MathService
on the dispatcher (assuming you already installed the dispatcher). Run the script InvokeMathService.vbs
to invoke the methods of MathService
through the dispatcher.
Other features of the web service dispatcher
The dispatcher program provides dynamic tracing capability. If you look at the web.config
file of the dispatcher, you may find the following text.
<appSettings>
<add key="TraceFilePrefix" value="Log\WebServiceDispatcherTrace" />
<add key="TraceLevel" value="40" />
<add key="TraceCleanup" value="7" />
<add key="SoapToolkitVersion" value="3.0" />
<add key="DataFile" value="Data\WebServiceDispatcherData" />
<add key="FailedAttemptLimit" value="3" />
</appSettings>
The value of TraceFilePrefix
specifies where do you want to create trace files. If you set the value to c:\temp\Dispatcher
, then trace files will be created in directory c:\temp
and the file name will be the string Dispatcher
plus a datetime stamp. By the way, a new trace file will be created for each day the dispatcher is being used so that you won't get a gigantic trace file if the dispatcher keeps running for months. The value of TraceLevel
determines how much information you want in the trace file. Level 0
means no tracing will be done. Level 10
will generate error messages in the trace file. Level 20
means error plus warning messages. Level 30
will have additional messages indicating what internal functions have been called. Level 40
provides the most detailed tracing, which includes all input strings and output strings. You can dynamically invoke the SetTrace
method on the dispatcher to change TraceFilePrefix
and TraceLevel
. In order to prevent the hard disk from filling up, old trace files will be cleaned up automatically after 7 days (you can modify this setting by changing the value of TraceCleanup in the web.config file).
Tracing for the dispatcher is implemented in the .NET component Tools.TraceUtility.dll
. This component is ported from a C++ utility described in another codeproject article . The source code for this component is included in the TraceUtility.zip
file.
Suppose you are using the web service dispatcher in a production environment. What happens if you have to restart IIS or reboot the machine? Do all server programs have to re-register their web methods with the dispatcher? The answer is no unless there is a real disaster (hard disk failure, blue screen of death, cannot shutdown IIS, etc.). This is because when the dispatcher program is shutdown normally, the registration information will be saved to a local file, and the information will be restored from the file when the dispatcher is restarted.
As stated earlier, before a server providing a web method through the dispatcher is shutdown, it should call UnRegisterMethod
to clear the information about this provider stored on the dispatcher. What happens if two server instances register the same web method through the dispatcher and one of them becomes unavailable or dies unexpectedly? Will the dispatcher be smart enough to always forward user requests to the server instance that is still alive? The answer is no. The dispatcher just randomly picks a provider for the web method to forward the request, therefore some users may experience problems. However, the FailedAttemptLimit
value in the web.config
file determines how many times a web method provider can fail to respond to user requests. If the value is 10
, then the dispatcher will automatically unregister a provider when it failed 10
times consecutively. If the FailedAttemptLimit value
is not specified in the web.config
file, then the default value will be 3. So if there are five server instances providing the same web method through the dispatcher and four of them died a tragic death, then eventually all user requests for this web method will be processed by the one instance that is still alive!
Internal implementation of the dispatcher
The web service dispatcher program is implemented as a web service using ASP.NET. It consists of the files web.config
, Global.asax
, ServiceDispatcher.asmx
, WebServiceDispatcher.dll
, Tools.TraceUtility.dll
, XYSoapClient.dll
(and the .NET generated Interop.XYSOAPCLIENTLib.dll
).
The XYSoapClient.dll
is a regular com dll built with Visual C++ 6.0 which implements the general proxy object for accessing various web services. It uses the SoapClient
object from the Microsoft Soap Toolkit (either version 3.0 or 2.0). The reason I am using VC++ 6.0 instead of VC++ 7.0 to build this component is that I want to be able to use it on older platforms (machines without the .NET framework, etc.) and with older applications (VB 6.0 and VC++ 6.0 applications, etc.). The source code for this project is in the XYSoapClient.zip
file. I am going to write a separate article to describe this component and give more C++ examples for accessing web services.
Update History
- July 22, 2003: Modified article text. Added the capability to automatically delete old trace files. Modified the implementation of the dispatcher so that it is more efficient.
- May 30, 2003: Updated source code for XYSoapClient.dll. The previous version does not handle array and reference types properly.
- April 2, 2003: Updated source code and binary to set the soap client property
ServerHTTPRequest
to true. There seems to problems for some machines if this property is not set, however my server works fine without the additional code.
No comments:
Post a Comment