Follow-up from PR #282 (review comment #18, P3)
PR #282 added the packages/azure-functions-durable compat package (npm durable-functions v4) on the @microsoft/durabletask-js gRPC core. As part of it, context.df.callHttp(...) was made to throw, documented as a breaking change in the CHANGELOG (review comment #18). This issue tracks whether callHttp can be restored as a worker-side durable activity rather than left unsupported.
Short answer from investigation: yes, it is supportable — but as a scoped feature with behavioral deltas, not a byte-for-byte drop-in. Filing so the design + tradeoffs get a real decision.
Why it is supportable
callHttp was never an engine/backend primitive. In v3 the orchestrator emitted a CallHttp action (ActionType.CallHttp = 8) and the Functions host ran the raw HTTP send as a built-in WebJobs activity:
- host chain:
OutOfProcOrchestrationShim.cs:177 -> context.CallHttpAsync -> TaskHttpActivityShim : TaskActivity -> httpClient.SendAsync (extension Listener/TaskHttpActivityShim.cs L20/49/58)
Confirmed absent from the gRPC backend surface: proto (0 HTTP messages), durabletask-dotnet (0 CallHttp), durabletask-python core (0). So there is no host-managed durable-HTTP to inherit over gRPC.
Because it is "make an HTTP request from an orchestration, replay-safely", any backend with activities can provide it: register an auto-injected fetch-based durable activity and call it from the orchestrator. The orchestrator never does I/O; the activity does; the result is journaled -> replay-safe.
Helpful nuance re: the async (202) polling pattern
In v3 the polling loop is already SDK-orchestrated, not host-magic: DurableOrchestrationContext.callHttp returns a CallHttpWithPollingTask that drives a durable timer + re-poll loop (defaultHttpAsyncRequestSleepTimeMillseconds) when asynchronousPatternEnabled/enablePolling is true (default). Only each individual HTTP send went to the host activity. That means a worker-side port can reuse the same structure: core createTimer + repeated activity calls in orchestrator space, with the activity replacing TaskHttpActivityShim.
v3 API surface to match:
callHttp(options: CallHttpOptions): Task<DurableHttpResponse>
CallHttpOptions: method, url, body, headers, tokenSource, enablePolling (alias asynchronousPatternEnabled, default true)
- types:
DurableHttpRequest, DurableHttpResponse, ManagedIdentityTokenSource / TokenSource
What must be (re)built, and the real tradeoffs
- HTTP-send activity (replaces host
TaskHttpActivityShim) — auto-registered fetch-based durable activity; single request/response; replay-safe. (cheap, core of the feature)
- 202 async-polling loop — reimplement
CallHttpWithPollingTask semantics with core createTimer + repeat activity (follow Location/Retry-After until terminal). (moderate; structure exists in v3 to copy)
- Managed-identity auth — reimplement
tokenSource / ManagedIdentityTokenSource token acquisition inside the activity. (needs an identity/token dependency decision)
- Trust / network boundary shift (design call, not code) — HTTP now originates from the app process, not the Functions host. Different egress, identity, and security story than v3. Must be documented; may matter for some users.
- Timeout / retry / error-shape parity with
DurableHttpResponse.
Suggested phasing
- Phase 1: basic
callHttp (single request/response via fetch activity, no polling, no managed identity). Restores the common case; keep throwing only for the unsupported options.
- Phase 2: 202 async-polling parity.
- Phase 3: managed-identity
tokenSource parity.
Acceptance
Links
Follow-up to PR #282 review comment #18.
Follow-up from PR #282 (review comment #18, P3)
PR #282 added the
packages/azure-functions-durablecompat package (npmdurable-functionsv4) on the@microsoft/durabletask-jsgRPC core. As part of it,context.df.callHttp(...)was made to throw, documented as a breaking change in the CHANGELOG (review comment #18). This issue tracks whethercallHttpcan be restored as a worker-side durable activity rather than left unsupported.Short answer from investigation: yes, it is supportable — but as a scoped feature with behavioral deltas, not a byte-for-byte drop-in. Filing so the design + tradeoffs get a real decision.
Why it is supportable
callHttpwas never an engine/backend primitive. In v3 the orchestrator emitted aCallHttpaction (ActionType.CallHttp = 8) and the Functions host ran the raw HTTP send as a built-in WebJobs activity:OutOfProcOrchestrationShim.cs:177->context.CallHttpAsync->TaskHttpActivityShim : TaskActivity->httpClient.SendAsync(extensionListener/TaskHttpActivityShim.csL20/49/58)Confirmed absent from the gRPC backend surface: proto (0 HTTP messages), durabletask-dotnet (0
CallHttp), durabletask-python core (0). So there is no host-managed durable-HTTP to inherit over gRPC.Because it is "make an HTTP request from an orchestration, replay-safely", any backend with activities can provide it: register an auto-injected
fetch-based durable activity and call it from the orchestrator. The orchestrator never does I/O; the activity does; the result is journaled -> replay-safe.Helpful nuance re: the async (202) polling pattern
In v3 the polling loop is already SDK-orchestrated, not host-magic:
DurableOrchestrationContext.callHttpreturns aCallHttpWithPollingTaskthat drives a durable timer + re-poll loop (defaultHttpAsyncRequestSleepTimeMillseconds) whenasynchronousPatternEnabled/enablePollingis true (default). Only each individual HTTP send went to the host activity. That means a worker-side port can reuse the same structure: corecreateTimer+ repeated activity calls in orchestrator space, with the activity replacingTaskHttpActivityShim.v3 API surface to match:
callHttp(options: CallHttpOptions): Task<DurableHttpResponse>CallHttpOptions:method,url,body,headers,tokenSource,enablePolling(aliasasynchronousPatternEnabled, defaulttrue)DurableHttpRequest,DurableHttpResponse,ManagedIdentityTokenSource/TokenSourceWhat must be (re)built, and the real tradeoffs
TaskHttpActivityShim) — auto-registeredfetch-based durable activity; single request/response; replay-safe. (cheap, core of the feature)CallHttpWithPollingTasksemantics with corecreateTimer+ repeat activity (followLocation/Retry-Afteruntil terminal). (moderate; structure exists in v3 to copy)tokenSource/ManagedIdentityTokenSourcetoken acquisition inside the activity. (needs an identity/token dependency decision)DurableHttpResponse.Suggested phasing
callHttp(single request/response via fetch activity, no polling, no managed identity). Restores the common case; keep throwing only for the unsupported options.tokenSourceparity.Acceptance
callHttp(phased) or keep it throwing (current Adddurable-functions@4.0.0— Azure Functions Durable provider on the gRPC core (+ core host helpers, E2E CI, and release pipeline) #282 behavior).DurableHttpRequest/DurableHttpResponsere-exports; Phases 2-3 gated on demand.Links
durable-functions@4.0.0— Azure Functions Durable provider on the gRPC core (+ core host helpers, E2E CI, and release pipeline) #282azure-functions-durable-jssrc/orchestrations/DurableOrchestrationContext.ts(callHttp,CallHttpWithPollingTask)azure-functions-durable-extension.../Listener/TaskHttpActivityShim.cs,OutOfProcOrchestrationShim.cs:177packages/azure-functions-durable/CHANGELOG.md(context.df.callHttp(...) now throws ...)Follow-up to PR #282 review comment #18.