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; }
可能原因分析
- CURL请求参数错误:
curlRequest2方法中存在变量错误($ch应为$cURLConnection),且未根据请求方法设置对应CURL选项(如POST/PUT等方法未处理),导致非GET请求无法正确转发。 - Header重复与格式问题:代码中重复添加
Accept:application/json,同时混用X-CSRFToken和X-CSRF-Token,可能触发Superset的验证逻辑;且未转发原始请求的必要Header(如User-Agent),部分请求因缺少Header被中断。 - 响应处理不完整:仅替换了部分静态资源路径,未处理API接口等其他绝对路径,导致后续请求无法通过代理访问;同时未转发Superset返回的响应Header(如Content-Length),浏览器因Header不完整中断请求。
- 超时设置缺失:CURL未配置超时时间,若Superset响应较慢,PHP执行超时会中断请求。
调试技巧
- 开启CURL verbose日志:在
curlRequest2中添加以下代码,记录CURL请求的详细过程:
查看日志可获取请求Header、响应状态码、错误信息等关键内容。$verboseLog = fopen('/tmp/curl_superset_proxy.log', 'a'); curl_setopt($cURLConnection, CURLOPT_VERBOSE, true); curl_setopt($cURLConnection, CURLOPT_STDERR, $verboseLog); - 记录请求与响应详情:在控制器中注入
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) // 记录部分内容避免日志过大 ]); - 浏览器网络面板对比:打开浏览器DevTools的Network面板,对比直接请求和代理请求的状态码、响应Header、请求是否被标记为
canceled。 - 检查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
相关产品推荐
相关产品推荐

