Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/sim/app/api/proxy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import { getTool, validateRequiredParametersAfterMerge } from '@/tools/utils'

const logger = createLogger('ProxyAPI')

/**
* Next.js route configuration for long-running API requests
* maxDuration: 10 minutes to support user-configurable timeouts up to 10 min
*/
export const runtime = 'nodejs'
export const maxDuration = 600

const proxyPostSchema = z.object({
toolId: z.string().min(1, 'toolId is required'),
params: z.record(z.any()).optional().default({}),
Expand Down Expand Up @@ -212,6 +219,8 @@ export async function GET(request: Request) {

try {
const pinnedUrl = createPinnedUrl(targetUrl, urlValidation.resolvedIP!)
// timeout: false disables Bun/Node.js default 5-minute timeout
// maxDuration handles the overall route timeout
const response = await fetch(pinnedUrl, {
method: method,
headers: {
Expand All @@ -220,6 +229,8 @@ export async function GET(request: Request) {
Host: urlValidation.originalHostname!,
},
body: body || undefined,
// @ts-expect-error - Bun-specific option to disable default 5-minute timeout
timeout: false,
})

const contentType = response.headers.get('content-type') || ''
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/api/workflows/[id]/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const ExecuteWorkflowSchema = z.object({

export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
// Allow up to 10 minutes for workflow execution with long-running API calls
export const maxDuration = 600

function resolveOutputIds(
selectedOutputs: string[] | undefined,
Expand Down
6 changes: 6 additions & 0 deletions apps/sim/tools/http/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export interface RequestParams {
params?: TableRow[]
pathParams?: Record<string, string>
formData?: Record<string, string | Blob>
/**
* Request timeout in milliseconds.
* Default: 120000 (2 minutes)
* Maximum: 600000 (10 minutes)
*/
timeout?: number
}

export interface RequestResponse extends ToolResponse {
Expand Down
50 changes: 48 additions & 2 deletions apps/sim/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,34 @@ function normalizeToolId(toolId: string): string {
*/
const MAX_REQUEST_BODY_SIZE_BYTES = 10 * 1024 * 1024 // 10MB

/**
* Default timeout for HTTP requests in milliseconds (2 minutes)
*/
const DEFAULT_TIMEOUT_MS = 120000

/**
* Maximum allowed timeout for HTTP requests in milliseconds (10 minutes)
*/
const MAX_TIMEOUT_MS = 600000

/**
* Parses and validates a timeout value, ensuring it falls within acceptable bounds.
* @param timeout - The timeout value to parse (number or string in ms)
* @returns The parsed timeout in milliseconds, clamped to MAX_TIMEOUT_MS
*/
function parseTimeout(timeout: number | string | undefined): number {
if (typeof timeout === 'number' && timeout > 0) {
return Math.min(timeout, MAX_TIMEOUT_MS)
}
if (typeof timeout === 'string') {
const parsed = Number.parseInt(timeout, 10)
if (!Number.isNaN(parsed) && parsed > 0) {
return Math.min(parsed, MAX_TIMEOUT_MS)
}
}
return DEFAULT_TIMEOUT_MS
}

/**
* User-friendly error message for body size limit exceeded
*/
Expand Down Expand Up @@ -650,11 +678,20 @@ async function handleInternalRequest(
// Check request body size before sending to detect potential size limit issues
validateRequestBodySize(requestParams.body, requestId, toolId)

// Prepare request options
const requestOptions = {
// Parse timeout from params (user-configurable via API block)
const timeoutMs = parseTimeout(params.timeout)
logger.info(`[${requestId}] Request timeout for ${toolId}: ${timeoutMs}ms`)

// Prepare request options with timeout support
// Note: timeout: false disables Bun/Node.js default 5-minute timeout
// AbortSignal.timeout provides user-configurable timeout control
const requestOptions: RequestInit & { timeout?: boolean } = {
method: requestParams.method,
headers: headers,
body: requestParams.body,
// @ts-ignore - Bun-specific option to disable default 5-minute timeout (not in standard RequestInit types)
timeout: false,
signal: AbortSignal.timeout(timeoutMs),
}

const response = await fetch(fullUrl, requestOptions)
Expand Down Expand Up @@ -870,10 +907,19 @@ async function handleProxyRequest(
// Check request body size before sending
validateRequestBodySize(body, requestId, `proxy:${toolId}`)

// Parse timeout from params (user-configurable via API block)
const timeoutMs = parseTimeout(params.timeout)
logger.info(`[${requestId}] Proxy request timeout for ${toolId}: ${timeoutMs}ms`)

// Note: timeout: false disables Bun/Node.js default 5-minute timeout
// AbortSignal.timeout provides user-configurable timeout control
const response = await fetch(proxyUrl, {
method: 'POST',
headers,
body,
// @ts-ignore - Bun-specific option to disable default 5-minute timeout (not in standard RequestInit types)
timeout: false,
signal: AbortSignal.timeout(timeoutMs),
})

if (!response.ok) {
Expand Down