B4J Question How to call CallSubDelayed from a different Class Module

avalle

Active Member
Licensed User
Longtime User
Hi all,
I have a B4J Server application implementing a REST API service with multiple Handler Class Modules.
In one of these class modules I need to use CallSubDelayed to raise a callback event which is used in a Wait For in a different class module.

Is this possible?
If so, how do I reference in CallSubDelayed the class module I want to call?
What should I use in lieu of the XXXX placeholder below?

Is there a better way to implement an asynchronous callback?

Many thanks in advance!

B4X:
'Handler Class module GetResponse
Sub Handle(req As ServletRequest, resp As ServletResponse)
    ' ...
    'Skipped some code that makes an HTTP call to an external service
    ' ...
    Wait For Callback_Done
    ' ...
    'Skipped some code that reads the data from the DB
    ' ...
End Sub

'Handler Class module Callback
Sub Handle(req As ServletRequest, resp As ServletResponse)
    ' ...
    'Skipped some code that reads JSON data from a callback and writes into a DB
    ' ...
    resp.Status = 200
    resp.Write("")
    CallSubDelayed(XXXX, "Callback_Done")
End Sub
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Remember that the target of CallSubDelayed is not the class module but rather a specific class instance. There can be many instances of the same class.

This means that you need to somehow organize the references. The exact solution depends on your requirements. If for example both handlers instances are related to the same user then you can store the instance inside the session.

You can see an example in WebSocketWithFileUpload:
B4X:
Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
   ws = WebSocket1
   form1.RunMethod("ajaxForm", Null)
   ws.Session.SetAttribute("file_upload_sender", Me)
End Sub
And in FileHelper:
B4X:
Dim callback As Object = req.GetSession.GetAttribute("file_upload_sender")
 
Upvote 0

avalle

Active Member
Licensed User
Longtime User
Ok thanks.
In my case the session option is not applicable.
I'll look for another option to store/retrieve the "Me"
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
In my case the session option is not applicable.
Why?

Youl'll need something to tie the two calls together. The session approach would make it easy (if the same client calls it). You can use a combo of a client id that get's sent to the server via cookie/url and a list on the server side that associates the handler with that client id.
 
Upvote 0

avalle

Active Member
Licensed User
Longtime User
The B4J server provides REST API services to clients using Jersey, cURL or other HTTP library, not to web browsers.
And also the calls are using POST not GET.
Would a session work for this context?
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
How can cURL handle a call back? What is the order of calls for cURL against the two handlers in post #1?
 
Upvote 0

avalle

Active Member
Licensed User
Longtime User
Let me explain. There are three systems:
(A) My B4J server app implementing the REST API service.
(B) A third party REST API service (which I don't control) implementing some asynchronous methods.
(C) A client application calling "A".

Here's the sequence of calls:
1. "C" makes a POST call to the GetResponse API endpoint of "A". An instance of the GetResponse class is invoked.
2. The GetResponse code in "A" makes an HTTP POST call to "B". "B" immediately responds with a 200 OK but without a response as it will make a callback to "A" when it has finished to elaborate. This handler waits for the data to be returned by the callback (=> Wait For Callback_Done)
3. When it has finished to work, "B" makes an HTTP POST call to the Callback API endpoint of "A". The Callback handler reads the data from the payload and writes it into a DB.
4. The Callback handler calls CallSubDelayed(XXXX, "Callback_Done") so that the GetResponse instance can resume from the line after the "Wait For Callback_Done" and read some data written in the DB during the previous step.
5. The GetResponse handler returns a response to "C".

Thanks
Andrea
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
When your GetResponse handler calls the external API and sets up a callback, what info do you give the external API and what info does the external API give your CallBack handler that ties them together? How does your Callback handler know that the external API is a valid call (that you were expecting it)?

For example, do you in your JSON payload include a unique serial type number that the external API server uses in it's JSON payload when it calls your CallBack handler? Note: May also be part of the query string of the URL, etc.
 
Upvote 0

avalle

Active Member
Licensed User
Longtime User
I send a client_id (static value assigned during API key registration).
There's an auth_token parameter that I get in the callback payload. It's a signed JWT containing my client_id, so I can authenticate the callback.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Hypothetical then:

You place two calls to the external API. Call A and B in that order. But due to processing, the API server does the callback on B first. How would you determine that? Or does the API server never do callbacks out of order?

Update: Minor edit correction: to -> two (2nd edit: and then really correct it)
 
Upvote 0

avalle

Active Member
Licensed User
Longtime User
There's a transaction ID contained in the JWT and also returned by "B" in step #2 above.
However, there's no question about the architecture design (which is defined by the "B" system and not under my control).

The question is about how to implement an asynchronous callback which needs to interact with a resumable sub in a server handler.

Thanks
Andrea
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
How do you know that this transaction id that is returned by B is related to the call placed to b? Again, if you call b twice and b calls your callback twice, how do you know which callback belongs to which call? This is very pertinent to what you are trying to accomplish.
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
Depending on your answer(s) I can see three solutions for you
1) You have an transaction ID that you sent to B and B answer comes with that transaction ID
You can use a ThreadSafeMap. In the GetResponse Handler, you do ThreadSafeMap.Put(TransactionID, Me). Once you get a response back from B in your CallBack Handler, you can do a ThreadSafeMap.Get(TransactionID) to get the object reference of the GetResponse Handler that initiated that transaction to do the CallSubDelayed. After CallSubDelayed, do a ThreadSafeMap.Remove(TransactionID)
2) You have no transaction ID to sent to B, but B responds synchronously to your requests (I do calls 1,2 and three, B responds in sequence 1,2 and 3)
You can use a FIFO queue. You can create one with a List object and a long variable. The list object starts out empty and the long variable starts with -1. When you do you call in GetResponse, you a) set the long variable to 0 if it is -1 (otherwise, leave it alone) and add Me to the List object. Once you get a CallBack, you a) see if the List object has entries, if not, you have a spurious callback b) If the List Size <= long variable, you have a spurious call c) Otherwise use the long integer as an index into the List to retrieve the Me of the GetResponse Handler. Set the value of the List at index long integer to null. Increment long integer by 1. Note: You have lots of calls before you overflow a long (and the List object), but may want to build in a reset
3) You call B in 1 2 3 order, but B can answer in 3 2 1 order and you have no clue how to sort it out. At this point you have to serialize your calls in such a way that only one call to B is made and all others are queued until B does its callback. A tad more complicated.

I'm pretty sure the answer is #1, but until you provide me all the information on how you set up a call to B and how you synchronize/know which callback from B is for what call to B, I can't really tell. So yeah, how B works is pertinent (and I do realize you can't change B)

Edit: Some grammar. May make no sense, it's getting late (plus the disclaimer below).
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
Upvote 0

avalle

Active Member
Licensed User
Longtime User
Depending on your answer(s) I can see three solutions for you...
Thanks a lot Oliver for your comprehensive response.

Solution #1 seems appropriate as the Transaction ID is returned by B when A called it first. Later the same ID is sent by B to the callback hosted by A, so A knows which callback belongs to a pending handler instance.

I'll try to implement this and will let you know. Meanwhile, I really appreciate your contribution.
 
Upvote 0

avalle

Active Member
Licensed User
Longtime User
Just wanted to report that after quite a while I've been able to implement the solution #1 based on ThreadSafeMap. It works nicely. Thank you again @OliverA for suggesting this!
 
Upvote 0
Top