HTTP Request Node β Runtime Behavior#
The HTTP Request node lets workflows make arbitrary outbound HTTP calls and act on the response. Its runtime implementation lives in the external graphon package (imported as graphon.nodes.http_request), not in Dify's own codebase. Dify wires it into the workflow engine through DifyNodeFactory, which injects the SSRF-aware HTTP client, file managers, and a compiled HttpRequestNodeConfig (timeouts, size limits, SSL, retry ceiling) at node creation time .
All outbound HTTP from this node is routed through Dify's SSRF proxy layer (api/core/helper/ssrf_proxy.py) before reaching the network.
Output Structure and Non-2xx Status Codes#
A completed HTTP call β regardless of status code β populates the node's outputs dict with four fields:
| Field | Type | Description |
|---|---|---|
status_code | int | Raw HTTP response status code |
body | string | Response body text (empty when files are extracted) |
headers | dict | Response headers |
files | ArrayFileSegment | Extracted file content (empty array if none) |
Non-2xx responses are not failures by default. A 404, for example, returns with result.status == SUCCEEDED and outputs["status_code"] == 404 . Downstream nodes or conditions must inspect status_code explicitly if branching on HTTP errors is needed.
body vs. files mutual exclusivity: body is set to response.text only when files.value is empty; otherwise it is "". PR #27610 fixed a bug where checking if not files (the object) instead of if not files.value (the value) caused body to always be empty, making failed-request debugging impossible.
The raw request log is available in result.process_data["request"] for all executions .
Error Cases That Fail the Node#
The following conditions set result.status = WorkflowNodeExecutionStatus.FAILED and populate result.error / result.error_type:
| Error type | When raised | error_type value |
|---|---|---|
AuthorizationConfigError | API key is empty or whitespace-only at Executor init β before any request is sent | "AuthorizationConfigError" |
ResponseSizeError | Response body exceeds configured limits (default: binary 10 MB, text 1 MB) | "ResponseSizeError" |
HttpRequestNodeError (network) | Connection failure, timeout, or MaxRetriesExceededError exhausted all SSRF-layer retries | varies |
ToolSSRFError | SSRF proxy (Squid) blocked the request and returned 401/403 with a squid Server/Via header | "ToolSSRFError" |
For all failure cases the workflow engine receives a NodeRunResult with :
status:FAILEDerror: human-readable error stringerror_type: the exception class name as a stringprocess_data: request log captured up to the point of failure
Fail Branches and Graph-Level Retry#
Fail-branch routing is controlled by graphon's graph runtime, not by the node itself. When the node returns FAILED, the graph engine follows any configured error edges (fail branches) in the workflow DAG.
Retry coordination: The SSRF transport layer (ssrf_proxy.make_request) has its own exponential-backoff retry loop for status codes [429, 500, 502, 503, 504] (default: 3 retries, backoff 0.5 Γ 2^(retry-1) seconds) . When a workflow also has graph-level retry enabled on the HTTP Request node, both layers previously retried independently, multiplying total attempts: (SSRF_retries + 1) Γ (graph_retries + 1). PR #33689 fixed this β the node now passes max_retries=0 to the SSRF client when graph-level retry is active, giving the graph handler sole control .
After exhausting SSRF retries, a MaxRetriesExceededError is raised, which propagates as a NodeRunResult with FAILED status and surfaces the message "Reached maximum retries for URL {url}" .
SSRF Proxy, Blocking, and Version 1.15.0 Behavior Changes#
All HTTP Request node calls pass through a Squid-based SSRF proxy. Dify 1.15.0 changed this to a deny-by-default model β only .marketplace.dify.ai is whitelisted; all other domains and private IPs are blocked . This is a breaking change from 1.10.x.
Common post-upgrade symptoms:
- HTTP Request nodes receive
403 Forbiddenfrom the SSRF proxy β surfaced asToolSSRFError. "Reached maximum retries for URL"errors when backends are slow: Squid times out β returns 502 β triggers SSRF retry loop βMaxRetriesExceededError.
Remediation via environment variables:
# Allow specific domains or private IPs
SSRF_PROXY_ALLOW_PRIVATE_DOMAINS=.example.com
SSRF_PROXY_ALLOW_PRIVATE_IPS=10.x.x.x
# Increase timeouts for slow backends
SSRF_REQUEST_TIMEOUT=1200
SSRF_READ_TIMEOUT=1200
# Reduce SSRF-layer retries (default: 3)
SSRF_DEFAULT_MAX_RETRIES=1
Note: The pre-1.15.0 env var HTTP_REQUEST_MAX_READ_TIMEOUT no longer controls network timeouts; that is now managed by the SSRF proxy layer .
Key Configuration Reference#
All values are injected at node creation via build_http_request_config() . Defaults shown are from the unit test config :
| Config key | Default | Description |
|---|---|---|
HTTP_REQUEST_MAX_CONNECT_TIMEOUT | 10 s | TCP connection timeout |
HTTP_REQUEST_MAX_READ_TIMEOUT | 600 s | Response read timeout |
HTTP_REQUEST_MAX_WRITE_TIMEOUT | 600 s | Request body write timeout |
HTTP_REQUEST_NODE_MAX_BINARY_SIZE | 10 MB | Max binary response size |
HTTP_REQUEST_NODE_MAX_TEXT_SIZE | 1 MB | Max text response size |
HTTP_REQUEST_NODE_SSL_VERIFY | true | SSL certificate verification |
SSRF_DEFAULT_MAX_RETRIES | 3 | SSRF transport retry ceiling |
Primary source files:
api/core/workflow/node_factory.pyβDifyNodeFactorywires HTTP dependencies into nodesapi/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.pyβ node-level tests including timeout and SSL propagationapi/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.pyβ executor-level tests including auth error casesapi/tests/integration_tests/workflow/nodes/test_http.pyβ end-to-end tests including 404 capture and error outputsapi/core/helper/ssrf_proxy.pyβ SSRF transport layer with retry/backoff logic