简体   繁体   中英

Wait for a third-party API callback

I need to create an REST API that connect to a third party SOAP API. The third party API events are sent by callback to an URL I provide.

The typical steps my API go through is it starts a session with the third party by providing an ID and an callback URL. The third party can now send new events to my API through this URL when, for example, a new participant connects. Now sometimes i need to request specific info, like the list of participants for a given session(ID), and wait for the event containing the info. Note that there may be multiple open sessions at the same time.

An example of what I need:

private string url = "http://myapi/callback";

[HttpGet]
[Route("createSession")]
public async Task<string> CreateSession()
{
    var id = Guid.NewGuid().ToString();
    var result = await ExternAPI.CreateSession(id, this.url);
    return result; //contains the id
}

[HttpGet]
[Route("endSession")]
public async Task<string> EndSession([FromUri] string id)
{
    var result = await ExternAPI.EndSession(id);
    return result;
}

[HttpGet]
[Route("partipants")]
public async Task<string> Partipants([FromUri] string id)
{
    ExternAPI.participants(id); // The results of this method will be sent to the callback function
    results = // Wait for the results for this id
    return results;
}

[HttpPost]
[Route("callback")]
public void Callback(body)
{
    // notify waiting function and pass body
}

I came up with a solution using ReactiveX but I'm not really sure about its reliability in production. What I have in mind is to create a subject that never terminate and handle all the events but it is not a usual lifetime for a subject, what happens on error ? And I don't think I did it the "RX-way" (state concerns).

Here it is (you will need System.Reactive to run this code):

class Data
{
    public int id;
    public string value;
}

class Program
{
    private static Subject<Data> sub;

    static void Main(string[] args)
    {
        sub = new Subject<Data>();
        Task.Run(async () => {
            int id = 1;
            ExternAPI(CallBackHook, id);
            Data result = await sub.Where(data => data.id == id).FirstAsync();
            Console.WriteLine("{0}", result.value);
        });
        Console.ReadLine();
    }

    static void CallBackHook(Data data)
    {
        sub.OnNext(data);
    }

    static String ExternAPI(Action<Data> callback, int id)
    {
        // Third-party API, access via SOAP. callback is normally an url (string)
        Task.Run(() =>
        {
            Thread.Sleep(1000);
            callback(new Data { id = id, value = "test" });
        });
        return "success";
    }
}

An other way will be a dictionary of subjects, one for each session, so I could manage their lifetimes.

it is not a usual lifetime for a subject

what happens on error?

And I don't think I did it the "RX-way"

Yes, these are all perfectly valid concerns with this kind of approach. Personally, I don't much mind the last one, because even though Subjects are frowned-upon, many times they're just plain easier to use than the proper Rx way. With the learning curve of Rx what it is, I tend to optimize for developer maintainability, so I do "cheat" and use Subjects unless the alternative is equally understandable.

Regarding lifetime and errors, the solutions there depend on how you want your application to behave.

For lifetime, it looks like currently you have a WebAPI resource (the SOAP connection) requiring an explicit disconnect call from your client; this raises some red flags. At the very least, you'd want some kind of timeout there where that resource is disposed even if endSession is never called. Otherwise, it'll be all too easy to end up with dangling resources.

Also for errors, you'll need to decide the appropriate approach. You could "cache" the error and report it to each call that tries to use that resource, and "clear" the error when endSession is called. Or, if it's more appropriate, you could let an error take down your ASP.NET process. (ASP.NET will restart a new one for you).

To delay an API until you get some other event, use TaskCompletionSource<T> . When starting the SOAP call (eg, ExternAPI.participants ), you should create a new TCS<T> . The API call should then await the TaskCompletionSource<T>.Task . When the SOAP service responds with an event, it should take that TaskCompletionSource<T> and complete it. Points of note:

  • If you have multiple SOAP calls that are expecting responses over the same event, you'll need a collection of TaskCompletionSource<T> instances, along with some kind of message-identifier to match up which events are for which calls.
  • Be sure to watch your thread safety. Incoming SOAP events are most likely arriving on the thread pool, with (possibly multiple) API requests on other thread pool threads. TaskCompletionSource<T> itself is threadsafe, but you'd need to make your collection threadsafe as well.

You may want to write a Task -based wrapper for your SOAP service first (handling all the TaskCompletionSource<T> stuff), and then consume that from your WebAPI.

As a very broad alternative, instead of bridging SOAP with WebAPI, I would consider bridging SOAP with SignalR. You may find that this is a more natural translation. Among other things, SignalR will give you client-connect and client-disconnect events (complete with built-in timeouts for clients). So that may solve your lifetime issues more naturally. You can use the same Task -based wrapper for your SOAP service as well, or just expose the SOAP events directly as SignalR messages.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM