Featured

Many REST Calls At Once

A Client Per Thread

GPAL.RESTClient is a fluent builder, which means the client is the call under construction: WithEndpoint, WithParameters, WithHttpMethod and the rest all settle onto the object and stay there until Execute. Two threads chaining onto the same client are two threads writing the same call, and what goes out is neither of them. So take a client per thread. They are cheap, each one names itself, and each is a separate conversation with the same API.

Parallel.For(0, workers, worker =>

{

// a client of this thread's own, built inside the loop

IRESTClient client = GPAL.RESTClient

.WithAPIBase("http://localhost:8477/")

.ToGPALObject();


for (int seq = 0; seq < callsEach; seq++)

{

EchoReply reply = client

.WithEndpoint("echo")

.WithParameters(new { worker = worker, seq = seq })

.WithHttpMethod("GET")

.Execute<EchoReply>();

}

});

NOTE

When you are proving a fan-out works, put the sender into the request and check the reply says the same thing back. A reply that belongs to another call is the signature of state shared where it should not be, and it is invisible if every call looks alike.

Reading Results Back Out of a Chain

WithResultName labels the result of the call that follows it, and GetExecutionResults hands back a ResultCollection holding every named result the chain has produced so far. That collection belongs to its own client, so parallel workers each accumulate their own and nothing has to be merged or locked. AndThen executes the call you just built and hands back the same client so the next one continues in the same chain; Execute ends the chain and hands you the result directly, which is the cleaner choice when nothing follows it.

IRESTClient client = browser.OttoMagicClient;


int page = 0;


client

.WithWorkflow(c => c

.GoTo($"https://example.com/search?page={++page}")

.AndThen()

.CheckNetworkIdle()

.AndThen()

.WithResultName("pageUrl")

.GetCurrentUrl()

.Execute<string>())

.Until(() => page >= 5);


foreach (var url in client.GetExecutionResults().GetAllResults("pageUrl"))

GPAL.PublishSimpleEvent(GPALEventType.NOTICE, $"Page reached [{url}]");

The One Client You Must Not Fan Out On

browser.OttoMagicClient is not a factory. It is the single live client that browser already built for itself, pointed at that browser own REST API, and every caller that asks gets the same object back. That is exactly what you want for driving one browser, and exactly what you must not spread across threads. For parallel REST work against OttoMagic, give each workflow its own browser and take that browser client, which is what GPAL.Workflow already encourages by handing each workflow nothing and making it build its own.

// wrong: two threads writing the same call on the same live client

Parallel.For(0, 2, i =>

browser.OttoMagicClient.GoTo($"https://example.com/{i}").Execute());


// right: a browser each, and each workflow takes its own browser's client

GPAL.Workflow

.WithWorkflow(() => Drive("https://example.com/0"))

.WithWorkflow(() => Drive("https://example.com/1"))

.WithMaxAtOnce(2)

.Run();

WARNING

browser.OttoMagicClient is a RESTClient underneath, the same class GPAL.RESTClient builds. The difference is ownership: one you made, one the browser made. Anything you can do to the general client you can do to the browser one, so calling WithName or WithAPIBase on browser.OttoMagicClient changes the client that browser is using for everything else. Build your own client when you want one of your own.

💬 Ask GPAL