Files
sam-manage/app/Services/FlowTester/HttpClient.php
hskwon fe902472c1 feat: [flow-tester] 의존성 검사, Docker 지원, 인증 자동 주입 기능 추가
FlowExecutor 개선:
- 의존성 스텝 실패 시 후속 스텝 자동 스킵 로직 추가
- Docker 환경 자동 감지 및 내부 URL 변환 (api.sam.kr → nginx)
- SSL 검증 비활성화 및 Host 헤더 설정 지원
- .env에서 API Key/Bearer Token 자동 주입

VariableBinder 개선:
- 임의 stepId 패턴 지원 (page_create_1.tempPageId 등)
- {{$env.VAR_NAME}} 환경변수 플레이스홀더 추가
- {{$auth.token}}, {{$auth.apiKey}} 인증 플레이스홀더 추가

UI 개선:
- SKIPPED 상태 스타일링 (노란색 배경/테두리)
- 행 클릭 시 스텝 상세 확장 기능
- 실행 결과 실시간 표시 개선
2025-11-27 22:20:36 +09:00

261 lines
6.4 KiB
PHP

<?php
namespace App\Services\FlowTester;
use Exception;
use Illuminate\Support\Facades\Http;
/**
* HTTP 클라이언트 래퍼
*
* Laravel HTTP Client를 래핑하여 플로우 테스터에 맞게 제공합니다.
*/
class HttpClient
{
private string $baseUrl = '';
private int $timeout = 30;
private array $defaultHeaders = [];
private ?string $apiKey = null;
private ?string $bearerToken = null;
private bool $verifySsl = true;
private ?string $hostHeader = null;
/**
* SSL 검증 비활성화 (Docker 내부 통신용)
*/
public function withoutVerifying(): self
{
$this->verifySsl = false;
return $this;
}
/**
* Host 헤더 설정 (Docker 내부 통신용)
*/
public function setHostHeader(string $host): self
{
$this->hostHeader = $host;
return $this;
}
/**
* 기본 URL 설정
*/
public function setBaseUrl(string $url): self
{
$this->baseUrl = rtrim($url, '/');
return $this;
}
/**
* 타임아웃 설정 (초)
*/
public function setTimeout(int $seconds): self
{
$this->timeout = $seconds;
return $this;
}
/**
* 기본 헤더 설정
*/
public function setDefaultHeaders(array $headers): self
{
$this->defaultHeaders = $headers;
return $this;
}
/**
* API 키 설정 (X-API-KEY 헤더)
*/
public function setApiKey(string $apiKey): self
{
$this->apiKey = $apiKey;
return $this;
}
/**
* Bearer 토큰 설정
*/
public function setBearerToken(string $token): self
{
$this->bearerToken = $token;
return $this;
}
/**
* HTTP 요청 실행
*
* @param string $method HTTP 메서드
* @param string $endpoint 엔드포인트
* @param array $options 옵션 ['headers' => [], 'body' => [], 'query' => []]
* @return array ['status' => int, 'headers' => array, 'body' => array, 'duration' => int]
*/
public function request(string $method, string $endpoint, array $options = []): array
{
$startTime = microtime(true);
$url = $this->buildUrl($endpoint);
$headers = $this->buildHeaders($options['headers'] ?? []);
try {
$request = Http::timeout($this->timeout)
->withHeaders($headers);
// SSL 검증 비활성화 (Docker 내부 통신용)
if (! $this->verifySsl) {
$request = $request->withoutVerifying();
}
// Host 헤더 추가 (Docker 내부 통신용)
if ($this->hostHeader) {
$request = $request->withHeaders(['Host' => $this->hostHeader]);
}
// API 키 추가
if ($this->apiKey) {
$request = $request->withHeaders(['X-API-KEY' => $this->apiKey]);
}
// Bearer 토큰 추가
if ($this->bearerToken) {
$request = $request->withToken($this->bearerToken);
}
// 쿼리 파라미터
if (! empty($options['query'])) {
$url .= '?'.http_build_query($options['query']);
}
// 요청 실행
$response = match (strtoupper($method)) {
'GET' => $request->get($url),
'POST' => $request->post($url, $options['body'] ?? []),
'PUT' => $request->put($url, $options['body'] ?? []),
'PATCH' => $request->patch($url, $options['body'] ?? []),
'DELETE' => $request->delete($url, $options['body'] ?? []),
default => throw new Exception("Unsupported HTTP method: {$method}"),
};
$duration = (int) ((microtime(true) - $startTime) * 1000);
return [
'status' => $response->status(),
'headers' => $response->headers(),
'body' => $response->json() ?? [],
'duration' => $duration,
'success' => $response->successful(),
'error' => null,
];
} catch (Exception $e) {
$duration = (int) ((microtime(true) - $startTime) * 1000);
return [
'status' => 0,
'headers' => [],
'body' => [],
'duration' => $duration,
'success' => false,
'error' => $e->getMessage(),
];
}
}
/**
* GET 요청
*/
public function get(string $endpoint, array $query = [], array $headers = []): array
{
return $this->request('GET', $endpoint, [
'query' => $query,
'headers' => $headers,
]);
}
/**
* POST 요청
*/
public function post(string $endpoint, array $body = [], array $headers = []): array
{
return $this->request('POST', $endpoint, [
'body' => $body,
'headers' => $headers,
]);
}
/**
* PUT 요청
*/
public function put(string $endpoint, array $body = [], array $headers = []): array
{
return $this->request('PUT', $endpoint, [
'body' => $body,
'headers' => $headers,
]);
}
/**
* PATCH 요청
*/
public function patch(string $endpoint, array $body = [], array $headers = []): array
{
return $this->request('PATCH', $endpoint, [
'body' => $body,
'headers' => $headers,
]);
}
/**
* DELETE 요청
*/
public function delete(string $endpoint, array $body = [], array $headers = []): array
{
return $this->request('DELETE', $endpoint, [
'body' => $body,
'headers' => $headers,
]);
}
/**
* 전체 URL 구성
*/
private function buildUrl(string $endpoint): string
{
$endpoint = ltrim($endpoint, '/');
if (empty($this->baseUrl)) {
return $endpoint;
}
return $this->baseUrl.'/'.$endpoint;
}
/**
* 헤더 병합
*/
private function buildHeaders(array $additionalHeaders): array
{
return array_merge(
[
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
$this->defaultHeaders,
$additionalHeaders
);
}
}