TsgcHTTPServer 위에 REST API를 구축하는 일은 언제나 가능했지만, 매번 손으로 작성해야 하는 것들이 몇 가지 있었습니다. CORS 프리플라이트 응답, 로드 밸런서를 위한 헬스 엔드포인트, 모니터링 스택을 위한 메트릭 엔드포인트, 그리고 어떤 고객의 데이터인지 구분하는 방법입니다. TsgcHTTPRESTServer는 이 모든 것을 기본으로 제공하는 새로운 컴포넌트입니다.
이 컴포넌트는 TsgcHTTPServer를 직접 상속하므로 이미 알고 있는 모든 것이 그대로 적용됩니다. 같은 Port, 같은 SSLOptions, 같은 Authentication, 같은 OnCommandGet 핸들러입니다. TsgcHTTPServer는 그대로 평범한 HTTP 서버로 남으며 변하지 않습니다. 추가 기능은 자손 클래스에 들어 있고, 각각은 선택적으로 활성화합니다.
시작하기
이 컴포넌트는 sgcHTTP_REST_Server 유닛에 있으며, SGC REST 팔레트 페이지에 TsgcHTTPRESTServer로 등록됩니다. 폼에 올려놓고 Active를 설정하면 동작하는 HTTP 서버가 됩니다. 흥미로운 부분은 요청 핸들러입니다.
uses
sgcHTTP_REST_Server;
var
oServer: TsgcHTTPRESTServer;
begin
oServer := TsgcHTTPRESTServer.Create(nil);
oServer.Port := 8080;
oServer.OnCommandGet := OnServerCommandGet;
oServer.Active := True;
end;
핸들러는 표준 Indy 요청/응답 객체를 사용하므로, JSON 응답은 세 줄의 할당이면 됩니다.
procedure TForm1.OnServerCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Document = '/api/status' then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := '{"status":"running"}';
end
else
begin
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentType := 'application/json';
AResponseInfo.ContentText := '{"error":"not found"}';
end;
end;
OnCommandGet은 GET과 POST를 받습니다. PUT, PATCH, DELETE 같은 메서드는 시그니처가 완전히 동일한 OnCommandOther로 들어오므로, 전체 메서드를 지원하는 REST 리소스는 보통 두 이벤트 모두에서 호출하는 하나의 디스패치 루틴으로 작성합니다.
procedure TForm1.OnServerCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if ARequestInfo.Command = 'DELETE' then
begin
AResponseInfo.ResponseNo := 204;
AResponseInfo.ContentText := '';
end;
end;
의도적으로 기본 비활성화된 CORS
다른 오리진에서 API를 호출하는 브라우저는 Access-Control-* 헤더가 필요하고, 실제 요청을 보내기 전에 OPTIONS 프리플라이트에 대한 응답도 받아야 합니다. CORSOptions가 둘 다 처리합니다.
한 가지 강조할 점은 Enabled가 기본적으로 False이며, 실제로 필요하지 않다면 그대로 두는 것이 의도라는 것입니다. 업그레이드 이후 서버가 조용히 Access-Control-Allow-Origin: *로 응답하기 시작한다면 보안 퇴행이 되므로, CORS는 엄격하게 선택적으로 활성화합니다.
oServer.CORSOptions.Enabled := True;
oServer.CORSOptions.AllowOrigins := 'https://app.example.com';
oServer.CORSOptions.AllowMethods := 'GET, POST, PUT, DELETE, OPTIONS';
oServer.CORSOptions.AllowHeaders := 'Content-Type, Authorization';
이렇게 설정하면 프리플라이트는 204와 세 개의 헤더로 자동 응답되고, 모든 일반 응답에도 헤더가 추가됩니다. 핸들러에 OPTIONS 분기를 작성할 필요가 없습니다.
API에 인증이 있다면 *보다 명시적인 오리진을 선호하세요. 와일드카드 오리진과 자격 증명의 조합은 어차피 브라우저가 거부하며, 실제로 서비스하는 오리진을 나열하는 것이 더 안전한 기본값입니다.
직접 작성하지 않는 헬스와 메트릭
TsgcHTTPServerStats 컴포넌트를 ServerStats 속성에 연결하면 서버가 두 개의 운영 엔드포인트에 응답할 수 있습니다. 둘 다 개별적으로 활성화하기 전까지는 비활성 상태입니다.
oStats := TsgcHTTPServerStats.Create(nil);
oStats.Endpoints.Health.Enabled := True;
oStats.Endpoints.Metrics.Enabled := True;
oServer.ServerStats := oStats;
/health는 로드 밸런서 프로브에 적합한 작은 JSON 문서로 응답합니다.
{"status":"ok","uptime":3600,"connections":12,"requests":48120,
"responses":{"1xx":0,"2xx":47800,"3xx":10,"4xx":300,"5xx":10},
"latency":{"min":0,"avg":4,"max":180}}
/metrics는 Prometheus 텍스트 노출 형식으로 응답하므로, 중간에 어댑터 없이 바로 스크래핑할 수 있습니다.
# HELP sgc_server_requests_total Total requests served
# TYPE sgc_server_requests_total counter
sgc_server_requests_total 48120
# HELP sgc_server_request_duration_ms_avg Average request duration
# TYPE sgc_server_request_duration_ms_avg gauge
sgc_server_request_duration_ms_avg 4
/metrics와 /health가 여러분의 라우트와 충돌한다면 두 경로 모두 변경할 수 있습니다.
oStats.Endpoints.Metrics.Path := '/internal/metrics';
oStats.Endpoints.Health.Path := '/internal/health';
이 엔드포인트들은 인증 관문 이후에 제공되므로, 서버가 이미 적용하고 있는 인증을 그대로 상속합니다. 서버 자체가 공개되지 않는 한 이 엔드포인트도 절대 공개되지 않습니다. 이 점은 양쪽 방향 모두에서 알아둘 만합니다. 기본적으로 비공개로 유지된다는 뜻이며, 동시에 서버가 자격 증명을 요구한다면 모니터링 스크래퍼도 자격 증명이 필요하다는 뜻입니다.
API와 함께 정적 콘텐츠 제공하기
DocumentRoot는 TsgcHTTPServer에서 상속되어 그대로 동작하므로, 하나의 서버가 작은 프런트엔드와 그것이 호출하는 API를 함께 호스팅할 수 있습니다. 핸들러가 응답하지 않는 요청은 모두 문서 루트로 넘어갑니다.
oServer.DocumentRoot := 'C:\www\app';
oServer.HTTPCompression.Enabled := True;
oServer.HTTPCompression.MinSize := 1024;
TLS
기본 서버와 달라지는 것이 없습니다. 평소처럼 SSL을 설정하고 SSLOptions를 채우면 됩니다.
oServer.SSL := True;
oServer.SSLOptions.Port := 443;
oServer.Port := 443;
다음 이야기
이 컴포넌트는 하나의 서버를 다중 고객용으로 바꿔주는 Tenancy 속성과 읽기 전용 Tenant 속성도 공개하며, Authentication 옵션은 사용자 저장소 컴포넌트를 받아들여 자격 증명을 TStringList에 보관하지 않아도 됩니다. 이 내용은 두 번째 글에서 다루고, 세 번째 글에서는 전체 앞단에 OpenAPI 계약을 두어 라우트와 검증, 문서가 모두 하나의 파일에서 나오도록 하는 방법을 보여줍니다.
TsgcHTTPRESTServer는 지금 사용할 수 있습니다. sgcWebSockets 다운로드 페이지에서 최신 빌드를 받으세요.
