Sunday, February 10, 2008

Setting timeouts in Delphi SOAP

Source: http://shenoyatwork.blogspot.com/2006/11/setting-timeouts-in-delphi-soap.html

If you want to timeout your webservice call - either connecting, sending or receiving, you may be tempted to use the HttpRio.HttpWebNode properties: ConnectTimeout, SendTimeout and ReceiveTimeout. You will then face this error:

"The data area passed to a system call is too small. - URL:{some url here} - SOAPAction:{some other text here}"

The problem here is that calls made to InternetSetOption (in SOAPHTTPTrans.pas in the THTTPReqResp.Send procedure) is throwing an error. This is weird because although it returns an error code it does exactly what you want! There are calls to check the return code in the above file, and those checks fail.

Well, I'm not going to ask you to change the Borland source code this time. There's a way around it.

The way for you to do this is:
1) Do not set the ReceiveTimeout or the ConnectTimeout or other timeout properties of HTTPRio.HTTPWebNode.

2) Handle HTTPRio.HttpWebNode.BeforePost event and do this:

procedure TForm2.HTTPRIO1HTTPWebNode1BeforePost(
const HTTPReqResp: THTTPReqResp; Data: Pointer);
var TimeOut : integer;
begin
TimeOut := 2000; // in milleseconds.
InternetSetOption(Data,
INTERNET_OPTION_RECEIVE_TIMEOUT,
Pointer(@TimeOut),
SizeOf(TimeOut));
end;


(Do not check the return code of InternetSetOption above. It returns an error although everything's fine)

Note: This only works for INTERNET_OPTION_RECEIVE_TIMEOUT. The connect timeout (INTERNET_OPTION_CONNECT_TIMEOUT) and Send Timeout (INTERNET_OPTION_SEND_TIMEOUT) are broken with WinInet in synchronous mode (as HTTPRio uses) - check this Microsoft Support article. That means if you want to configure Send or Connect timeouts, you must use a different thread or use WinInet asynchronously (which is not at all an easy thing with HttpRio).

Wednesday, October 18, 2006

Showing Download Progress with .NET webservice clients

Let's say you were interested in displaying a progress bar in your .NET application form while a BIG SOAP message was coming down the line. There's an MSDN Article by Matt Powell explaining how you can do exactly that.

I managed to finally get it to twist and turn and
actually work with my Delphi (Win32) "Showing SOAP Download progress" article code.

The Microsoft article expects your client application to already know the transfer size, which honestly is not usually available beforehand. You would expect that the size of the content would vary and your progress bar should work for all transfer sizes.

But then I see why Matt has done that. You don't get any knowledge of the transfer size in your SoapExtension class (which gets you a non-seekable stream meaning you can't get Stream.Length from it)

So how do you get to the length then? Response.ContentLength is your
answer; since the data you are deserializing is the entire Response stream. But where can you see the Response that is actually received?

Answer: NOWHERE.

I had to use Reflector to back up the call stack a little bit and find a reasonable place, and that's another overridden method in the original article. Heck, let me not bore you with all of this. Here's the entire Extension code in one class:

namespace DotNetProgress
{
internal class ProgressClient :
DotNetProgress.localhost.IIDownloaderservice
{
public ProgressBar Progress;
public int TransferSize;
public Form1.UpdateDelegate ProgressDelegate;
public WebResponse _Response = null;

protected override WebResponse GetWebResponse(WebRequest
request)
{
_Response = base.GetWebResponse(request);
return _Response;
}
}

public class ProgressExtension : SoapExtension
{
// Holds the original stream
private Stream m_oldStream;
// The new stream
private Stream m_newStream;
// The buffer for reading from the old stream
// and writing to the new stream
private byte[] m_bufferIn;
// The progress bar we will be incrementing
private ProgressBar m_Progress;
// The size of each read
private int m_readSize;
private int m_totalSize;
// The delegate we will invoke for updating the
// progress bar.
private Form1.UpdateDelegate m_progressDelegate;
// Used to keep track of which stream we are trying
// to chain into
private bool m_isAfterSerialization;
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.AfterSerialize:
// To let us know that the next ChainStream call
// will let us hook in where we want.
m_isAfterSerialization = true;
break;
case SoapMessageStage.BeforeDeserialize:
// This is where we stream through the data
SoapClientMessage clientMessage
= (SoapClientMessage)message;
if (clientMessage.Client is ProgressClient)
{
ProgressClient proxy
= (ProgressClient)clientMessage.Client;
m_Progress = proxy.Progress;
// Read 1/100th of the request at a time.
// This will give the progress bar 100
// notifications.
//m_readSize = proxy.TransferSize / 100;
m_readSize = 8192;
m_totalSize = ((int)
proxy._Response.ContentLength) / 100;
m_progressDelegate = proxy.ProgressDelegate;
}
int CurRead = 0;
while (true)
{
try
{
int bytesRead
= m_oldStream.Read(m_bufferIn,
0,
m_readSize);
if (bytesRead == 0)
{
// end of message...rewind the
// memory stream so it is ready
// to be read during deserial.
m_newStream.Seek(0,
System.IO.SeekOrigin.Begin);
return;
}

m_newStream.Write(m_bufferIn,
0,
bytesRead);

// Update the progress bar
CurRead += bytesRead;
m_Progress.Invoke(m_progressDelegate, new
object[] { CurRead, m_totalSize });
}
catch
{
// rewind the memory stream
m_newStream.Seek(0,
System.IO.SeekOrigin.Begin);
return;
}
}
}
}

public override Stream ChainStream(Stream stream)
{
if (m_isAfterSerialization)
{
m_oldStream = stream;
m_newStream = new MemoryStream();
m_bufferIn = new Byte[8192];
return m_newStream;
}
return stream;
}
// We don't have an initializer to be shared across streams
public override object GetInitializer(Type serviceType)
{
return null;
}

public override object GetInitializer(
LogicalMethodInfo methodInfo,
SoapExtensionAttribute attribute)
{
return null;
}

public override void Initialize(object initializer)
{ m_isAfterSerialization = false; }
}
}

and in the Main Form, where I have a button and progress bar:


private void btnDownload_Click(
object sender, EventArgs e)
{
ProgressClient srv = new ProgressClient();
srv.Progress = this.progressBar1;
srv.TransferSize = 8192 * 100;
srv.ProgressDelegate = new
UpdateDelegate(ProgressBarUpdate);
srv.DownloadFile(edtFileName.Text);
}

public delegate void UpdateDelegate(
int CurRead, int TotalSize);

private void ProgressBarUpdate(
int CurRead, int TotalSize)
{
progressBar1.Value = CurRead / TotalSize;
Application.DoEvents();
}


That works for responses of all sizes, and works with the Delphi Win32 server I'd created in the earlier mentioned article.

Note: If you're using the Delphi server code, you'll have to comment out the Delphi TSoapAttachment based code, because MIME attachments aren't supported by .NET. Commenting it out in the interface should work just fine.


Never miss a thing. Make Yahoo your homepage.

0 Comments: