You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony反向代理Apache Superset嵌入式请求中途中断求助

Symfony反向代理Apache Superset时请求中途中断问题

问题描述

作为Apache Superset(3.1.2版本)反向代理的Symfony控制器会中途停止处理请求,直接向Superset发起请求一切正常,但通过Symfony代理时请求无法完成。

已尝试操作

  • 直接请求:浏览器直接访问Superset完全正常
  • Symfony反向代理:通过Symfony代理的请求中途中断
  • 日志排查:检查Symfony日志(var/log/dev.log、var/log/prod.log),未找到明确说明请求中断原因的错误信息

需求

  • 可能原因:分析请求中途中断的潜在原因
  • 调试技巧:Symfony中有效调试该问题的方法
  • 配置调整:Symfony或Superset中可解决该问题的配置方案

相关截图

  • 直接请求Apache Superset的正常请求截图
  • Symfony反向代理请求中断的节点及控制台错误截图(2张)

前端代码

supersetEmbeddedSdk.embedDashboard({
    id: data.embeddedCode, // given by the Superset embedding UI
    supersetDomain: "{{path('superset_reverse_proxy')}}",
    mountPoint: containerDashboard, // any html element that can contain an iframe
    fetchGuestToken: () => data.guest_token,
    dashboardUiConfig: {}, // dashboard UI config: hideTitle, hideTab, hideChartControls (optional)
})

Symfony控制器代码

/**
 * @Route("/superset/proxy/{any}", defaults={"any" = null}, name="superset_reverse_proxy", requirements={"any"=".+"})
 */
public function supersetProxy(Request $request, $any): Response
{
    $supersetUrl = 'supersetDomain'; // URL de votre instance Superset
    $targetUrl = $supersetUrl . $request->getRequestUri();
    $guestToken = $request->getSession()->get('guest');
    $csrfToken = $request->getSession()->get('csrf');

    $targetUrl = str_replace('/superset/proxy', '', $targetUrl);

    $headers2[] = 'Accept:application/json';
    $headers2[] = 'Content-Type:application/json';
    $headers2[] = 'X-CSRFToken: ' . $csrfToken;
    $headers2[] = 'Authorization: Bearer ' . $guestToken;
    $headers2[] = 'Accept:application/json';

    $headers2[] = 'X-CSRF-Token: ' . $csrfToken;

    $headers2[] = 'X-GuestToken: ' . $guestToken;

    $headers2[] = 'X-Guest-Token: ' . $guestToken;

    $error = null;
    $info = null;
    $requestMethod = $request->getMethod();
    $responseContent =  $this->curlRequest2($targetUrl, $headers2, $requestMethod, [], $error, $info, $request);

    $responseContent = str_replace(array('href="/static/appbuilder/', 'href="/static/assets/', 'src="/static/appbuilder/', 'src="/static/assets/'), array('href="/superset/proxy/static/appbuilder/', 'href="/superset/proxy/static/assets/', 'src="/superset/proxy/static/appbuilder/', 'src="/superset/proxy/static/assets/'),$responseContent);
    $finalResponse = new Response($responseContent);

    $finalResponse->headers->set('Content-Security-Policy', "frame-ancestors 'self' $supersetUrl");

    return $finalResponse;
}

public function curlRequest2($url, $header, $requestMethod = 'GET', $payload = [], &$error = null, &$info = null, $request = null)
{
    $cURLConnection = curl_init();

    curl_setopt($cURLConnection, CURLOPT_URL, $url);
    curl_setopt($cURLConnection, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($cURLConnection, CURLOPT_HTTPHEADER, $header);

    if($request != null){
        $content = $request->getContent();
        if (!empty($content)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
        }
    }

    $response = curl_exec($cURLConnection);
    curl_close($cURLConnection);

    return $response;
}

可能原因分析

  1. CURL请求参数错误:curlRequest2方法中存在变量错误($ch应为$cURLConnection),且未根据请求方法设置对应CURL选项(如POST/PUT等方法未处理),导致非GET请求无法正确转发。
  2. Header重复与格式问题:代码中重复添加Accept:application/json,同时混用X-CSRFToken和X-CSRF-Token,可能触发Superset的验证逻辑;且未转发原始请求的必要Header(如User-Agent),部分请求因缺少Header被中断。
  3. 响应处理不完整:仅替换了部分静态资源路径,未处理API接口等其他绝对路径,导致后续请求无法通过代理访问;同时未转发Superset返回的响应Header(如Content-Length),浏览器因Header不完整中断请求。
  4. 超时设置缺失:CURL未配置超时时间,若Superset响应较慢,PHP执行超时会中断请求。

调试技巧

  1. 开启CURL verbose日志:在curlRequest2中添加以下代码,记录CURL请求的详细过程:
    $verboseLog = fopen('/tmp/curl_superset_proxy.log', 'a');
    curl_setopt($cURLConnection, CURLOPT_VERBOSE, true);
    curl_setopt($cURLConnection, CURLOPT_STDERR, $verboseLog);
    
    查看日志可获取请求Header、响应状态码、错误信息等关键内容。
  2. 记录请求与响应详情:在控制器中注入LoggerInterface,添加日志记录追踪转发前后的请求数据:
    $this->logger->info('Forwarded request details', [
        'method' => $request->getMethod(),
        'target_url' => $targetUrl,
        'request_headers' => $request->headers->all(),
        'content' => $request->getContent()
    ]);
    // 在curl_exec后记录响应
    $this->logger->info('Superset response details', [
        'http_code' => curl_getinfo($cURLConnection, CURLINFO_HTTP_CODE),
        'curl_error' => curl_error($cURLConnection),
        'response_content' => substr($responseContent, 0, 500) // 记录部分内容避免日志过大
    ]);
    
  3. 浏览器网络面板对比:打开浏览器DevTools的Network面板,对比直接请求和代理请求的状态码、响应Header、请求是否被标记为canceled。
  4. 检查PHP与Symfony超时配置:查看php.ini的max_execution_time、memory_limit,以及Symfonyframework.yaml中的framework.http.request_timeout设置。

配置调整与代码修复

1. 修复CURL方法逻辑

修改curlRequest2,完善请求方法处理、超时设置和变量错误:

public function curlRequest2($url, $header, $requestMethod = 'GET', $payload = [], &$error = null, &$info = null, $request = null)
{
    $cURLConnection = curl_init();

    curl_setopt($cURLConnection, CURLOPT_URL, $url);
    curl_setopt($cURLConnection, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($cURLConnection, CURLOPT_HTTPHEADER, $header);
    // 设置超时时间
    curl_setopt($cURLConnection, CURLOPT_TIMEOUT, 30);
    curl_setopt($cURLConnection, CURLOPT_CONNECTTIMEOUT, 10);

    // 处理不同请求方法
    $method = strtoupper($requestMethod);
    if ($method !== 'GET') {
        curl_setopt($cURLConnection, CURLOPT_CUSTOMREQUEST, $method);
        if ($request && !empty($request->getContent())) {
            curl_setopt($cURLConnection, CURLOPT_POSTFIELDS, $request->getContent());
        }
    }

    $response = curl_exec($cURLConnection);
    $error = curl_error($cURLConnection);
    $info = curl_getinfo($cURLConnection);
    curl_close($cURLConnection);

    return $response;
}

2. 优化Header处理

移除重复Header,统一认证Header,并转发原始请求的必要Header:

// 替换原Header构建代码
$headers2 = [];
// 转发原始请求的Accept和Content-Type
if ($request->headers->has('Accept')) {
    $headers2[] = 'Accept: ' . $request->headers->get('Accept');
}
if ($request->headers->has('Content-Type')) {
    $headers2[] = 'Content-Type: ' . $request->headers->get('Content-Type');
}
// 添加认证相关Header(Superset使用X-CSRFToken和X-Guest-Token)
$headers2[] = 'X-CSRFToken: ' . $csrfToken;
$headers2[] = 'X-Guest-Token: ' . $guestToken;

3. 完整处理响应与路径替换

  • 转发Superset的响应Header:
// 在curl_exec后获取响应Header并设置到最终响应
$responseHeaders = curl_getinfo($cURLConnection, CURLINFO_HTTPHEADER);
if ($responseHeaders) {
    foreach ($responseHeaders as $header) {
        list($name, $value) = explode(': ', $header, 2);
        // 跳过不需要转发的Header
        if (!in_array(strtolower($name), ['content-length', 'transfer-encoding', 'connection'])) {
            $finalResponse->headers->set($name, $value);
        }
    }
}
  • 使用正则替换所有绝对路径:
$responseContent = preg_replace('/(href|src)="\/(?!superset\/proxy)/', '$1="/superset/proxy/', $responseContent);

4. Symfony配置调整

在config/packages/framework.yaml中增加请求超时:

framework:
    http:
        request_timeout: 300 # 5分钟,根据实际情况调整

5. Superset配置调整

修改superset_config.py,允许Symfony域名的CORS请求:

ENABLE_CORS = True
CORS_OPTIONS = {
    'origins': ['https://your-symfony-domain.com'], # 替换为你的Symfony域名
    'supports_credentials': True
}

内容的提问来源于stack exchange,提问作者El Simple

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 13:04:50