West Wind Internet and Client Tools
Suggestion request for user feedback during HTTPGet() calls
Gravatar is a globally recognized avatar based on your email address. Suggestion request for user feedback during HTTPGet() calls
  Robert
  All
  Feb 27, 2020 @ 03:14pm

In my application I make a number of webrequests to get information from an external server. A typical call takes only 5-10 seconds but during busy times it could take 20-30 seconds. I am struggling with providing user feedback during these calls to let the user know we are still processing. I have tried timers on the form. I have tried external timers (cpptimer.fll) as well. But, once the httpRequest fires I get no UI updates.

oHttp = CREATEOBJECT("wwHTTP")
lcHTML = oHTTP.HTTPGet(this._transactionRequest, '', '', cHTMLResult)
*- 5-30 seconds here where I get no UI updates.
oHttp.HTTPClose()   && Close the connection
this._transactionResponse = ALLTRIM(lcHTML)

Is there a better way to handle this so that I can update the user? Even a simple count down timer would be great. Thanks!

Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Rick Strahl
  Robert
  Feb 27, 2020 @ 03:24pm

Look at the OnHttpBufferUpdate() event you can hook.

wwHttp::OnHttpBufferUpdate

You can use BindEvent or subclass wwHttp to implement this progress method and provide UI feedback.

+++ RIck ---

Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Robert
  Rick Strahl
  Feb 28, 2020 @ 01:27pm

Thank you Rick. I used the BindEvent method and just for testing purposes just ended up writing a log message and I added a DoEvents in the "httpBufferUpdateHandler" method I created.

As I somewhat expected. I got my log entry only after the data started receiving. And this makes sense. Its the 20 some second after the HttpRequest has been received on the server and it is processing that my client is not getting any updates and I am not able to get a screen refresh. once the server is done processing all the data comes back in one read. About 1100 bytes.

I guess that is the nature of a synchronous webrequest call.

Looks like the only way I will be able to update the user is if I spawn a new thread where I then handle the httpGet()?

Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Rick Strahl
  Robert
  Feb 28, 2020 @ 02:11pm

Yup unfortunately that's what you have to do and it's not super straight forward to do with FoxPro.

If I had to do this I would probably use wwDotnetBridge.InvokeMethodAsync() to write a small wrapper HTTP request in .NET and let that run asynchronously and return a value.

Actually come to think of it the Client Tools ship with an internal HTTP client that makes it easy to make simple .NET HTTP calls:

CLEAR
do wwDotNetBridge
LOCAL loBridge as wwDotNetBridge
loBridge = GetwwDotnetBridge()

*** HttpClient is part of wwDotnetBridge.dll for full Client Tools/Web Connection
loHttp = loBridge.CreateInstance("Westwind.WebConnection.HttpClient")

loCallback = CREATEOBJECT("HttpCallback")
loBridge.InvokeMethodAsync(loCallBack, loHttp,"GetUrl","https://west-wind.com/files/wwclient.zip")


RETURN


DEFINE CLASS HttpCallback as AsyncCallbackEvents

*** Returns the result of the method and the name of the method name
FUNCTION OnCompleted(lvResult,lcMethod)

WAIT WINDOW "File received. Size: " + TRANSFORM(LEN(lvResult))

ENDFUNC


* Returns an error message, a .NET Exception and the method name
FUNCTION OnError(lcMessage,loException,lcMethod)
? "Error: " + lcMethod,lcMessage
ENDFUNC

ENDDEFINE

This works...

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Robert
  Rick Strahl
  Feb 28, 2020 @ 03:05pm

Rick, that works indeed. I can now process UI feedback. Thank you!

Follow up question... with regards to exceptions. I see the call back object must have at least two methods: oncompleted and onerror. Is there anything I can provide on the call back object that would be read by InvokeMethodAsync to cancel the request and return? Or, is this even necessary?

In other words, the request is made and processing. But, it goes off into the weeds. (this is beyond my control) I would like for the user to be able to cancel the request. When all the object go out of scope or are released and then finally the httprequest returns (but at this point I don't care anymore and want to disregards any information that might have been returned) is it "safe" to assume the windows garbage collector will get rid of the thread? I am trying this and not seeing any errors. But I wonder if I am creating a memory hole or something?

Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Rick Strahl
  Robert
  Feb 28, 2020 @ 03:20pm

You can't cancel, but you can certainly ignore the result by adding and then setting a property value on the loCallback object.

The way this works is that wwDotnetBridge internally starts a new thread and makes the call, waits for completion then returns. Since the actual HTTP call in this case is not actually (I'm running sync on a separate thread) there's no way to cancel.

If you're just making a plain HTTP GET request, you can also use the native .NET WebClient object and call WebClient.DownloadStringAsync() directly in the framework. That gives you back a Task instance on which you can cancel.

There's more info on how to use Task based .NET APIs with wwDotnetBridge here:

Calling async/await methods with wwDotnetBridge

In fact the example in that post uses WebClient.DownloadStringAsync() as an example:

do wwDotNetBridge
LOCAL loBridge as wwDotNetBridge
loBridge = CreateObject("wwDotNetBridge","V4")

loClient = loBridge.CreateInstance("System.Net.WebClient")

*** execute and returns immediately
loTask = loBridge.InvokeMethod(loClient,"DownloadStringTaskAsync","https://west-wind.com")
? loTask  && object


*** Do other stuff...

*** Check and Wait for Result dynamically - non-blocking
llDone = loBridge.GetProperty(loTask,"IsCompleted")
DO WHILE (!llDone)
    WAIT WINDOW TIMEOUT 0.1
    llDone = loBridge.GetProperty(loTask,"IsCompleted")
    ? "waiting..."
ENDDO

*** Waits for completion if not finished yet - blocks
lcHtml = loBridge.GetProperty(loTask,"Result")
? SUBSTR(lcHtml,1,1000)

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Rick Strahl
  Robert
  Feb 28, 2020 @ 08:13pm

As another follow up I ended up adding a new method to wwDotnetBridge to make it easier to use .NET Async methods using the same callback mechanism used with threads, but get true async behavior from .NET with a FoxPro callback:

wwDotnetBridge.InvokeTaskMethodAsync

Here's an example:

do wwDotNetBridge
LOCAL loBridge as wwDotNetBridge
loBridge = CreateObject("wwDotNetBridge","V4")

loClient = loBridge.CreateInstance("System.Net.WebClient")

loCallback = CREATEOBJECT("HttpCallback")

*** execute and returns immediately
loBridge.InvokeTaskMethodAsync(loCallback, loClient,"DownloadStringTaskAsync","https://west-wind.com")

*** Hit immediately
? "Done..."

RETURN


DEFINE CLASS HttpCallback as AsyncCallbackEvents

*** Returns the result of the method and the name of the method name
FUNCTION OnCompleted(lvResult,lcMethod)

? "File received. Size: " + TRANSFORM(LEN(lvResult))
? SUBSTR(lvResult,1,1500)

ENDFUNC

* Returns an error message, a .NET Exception and the method name
FUNCTION OnError(lcMessage,loException,lcMethod)
? "Error: " + lcMethod,lcMessage
ENDFUNC

ENDDEFINE

Given that there are now more and more async only APIs in .NET this makes it a little easier to consume these methods truly asynchronously as opposed to waiting for completion.

Note: This is not in the current version but will in 7.10+

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Robert
  Rick Strahl
  Apr 20, 2020 @ 02:54pm

Rick, I was out of this project for a bit. But I am now back and trying out your suggestions. I am using the client tools and I believe the latest version I have is 7.07. I implemented the logic very similar to your example and when I run this in the VFP environment it works great. However, when I compile the code and run an exe it just hangs... I never seem to get the OnCompleted event from the callback object. Any ideas?

loBridge = GetwwDotnetBridge()
loHttp = loBridge.CreateInstance("Westwind.WebConnection.HttpClient")

loCallback = CREATEOBJECT("HttpCallback")
loCallback._stillwaiting = 1
loCallback._TransactionResponse = ''	

loBridge.InvokeMethodAsync(loCallback, loHttp, "GetUrl", objPayment._transactionRequest)

DO WHILE loCallback._stillwaiting > 0 
 *note, I never see this wait window when compiled in EXE.
 *this wait window shows fine when running in VFP enviro
 WAIT WINDOW  'waiting on response..' NOWAIT
ENDDO 

*-This returns fine and almost immediately when run in VFP enviro
*-This never gets this far when compiled into EXE
? ALLTRIM(loCallback._TransactionResponse)


DEFINE CLASS HttpCallback as AsyncCallbackEvents
_stillwaiting = 0
_TransactionResponse = ''
 
FUNCTION OnCompleted(lvResult,lcMethod)
this._TransactionResponse = ALLTRIM(lvResult)
this._stillwaiting = 0
ENDFUNC

FUNCTION OnError(lcMessage,loException,lcMethod)		
this._stillwaiting = 0
this._TransactionResponse = lcMessage
ENDFUNC

ENDDEFINE
Gravatar is a globally recognized avatar based on your email address. re: Suggestion request for user feedback during HTTPGet() calls
  Rick Strahl
  Robert
  Apr 20, 2020 @ 03:13pm

Put some error checking into that code if it fails.

It looks to me like perhaps you're not using the same version of the wwDotnetBridge.dll?

No idea why otherwise it would hang and not work unless the URL is hanging. Note that you should make sure that the Callback object sticks around. Don't make it LOCAL or PRIVATE but attach to some object that will still be in scope. If it releases the callback will never fire.

The reason that might be happening with the EXE vs. the IDE is speed. The EXE might be running faster and the timing is such that it worked in the IDE (perhaps with debugging on) where the object in the EXE goes away faster.

Also check your MyApp.exe.config for the EXE vs. what you have in vfp9.exe.config (if anything).

+++ Rick ---

© 1996-2024