Jump to content
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr ×
과거의 기술자료(읽기 전용): https://tech.devgear.co.kr

RAD 서버의 REST API 개발 시 리소스와 엔드포인트 구현 규칙 안내


Recommended Posts

이 글에서는 RAD 서버에 REST API를 추가할 수 있는 RAD 서버 패키지 프로젝트의 구조와 리소스/엔드포인트 개발 규칙을 설명합니다.

(이 글을 읽기 전에) RAD 서버에 대한 기본적인 기술 사항을 알고 나서, 이 글을 읽어야 보다 명확히 이해할 수 있습니다.

목차


RAD 서버 리소스와 엔드포인트 개발

image.png

리소스와 엔드포인트

REST API에서는 리소스로 접근할 대상을 지정하고, HTTP 메소드를 더해 해당 대상에 어떤 작업을 할지 정해진다. 리소스와 HTTP 메소드를 조합한 것을 엔드포인트라 한다. (자세한 내용은 REST API 이해하기 참고)

RAD 서버 패키지

RAD 서버 엔진은 REST API 서비스가 기본 내장되어 있으며, RAD 서버 패키지를 통해 사용자 정의 REST API(리소스와 엔드포인트)를 추가(확장)할 수 있다.

RAD 서버 패키지는 RAD 서버 엔진에 등록할 수 있는 패키지 파일의 종류이며, 델파이와 C++빌더에서 "RAD Server Package" 프로젝트로 개발해야 한다.

RAD 서버 패키지에는 리소스 및 엔드포인트가 정의 및 구현되어 있어야 한다.

다음과 같이 RAD 서버 엔진은 RAD 서버 패키지에 정의된 리소스와 엔드포인트를 실행한다.

  • RAD 서버 엔진은 구동 시 (RAD 서버 환경파일에 등록된)RAD 서버 패키지 파일(들)을 로드한다.
  • RAD 서버 패키지는 구동 시 리소스를 RAD 서버 엔진에 등록하고, RAD 서버 엔진은 리소스 정보를 관리한다.
  • RAD 서버 엔진이 (클라이언트의)HTTP 요청을 받으면 리소스가 포함된 RAD 서버 패키지의 엔드포인트 메소드를 호출한다.
  • RAD 서버 패키지는 엔드포인트 메소드에 구현된 코드(비지니스 로직)을 실행해 응답한다.

다음은 RAD 서버 로그로, RAD 서버 패키지가 로딩되고, 리소스 및 엔드포인트가 등록된 것을 확인할 수 있다.

image.png

  1. (붉은색 박스) 로딩된 RAD 서버 패키지 경로(HelloPackage.bpl)
  2. (파란색 박스) 해당 패키지에서 제공하는 리소스의 이름(hello)
  3. (주황색 박스) 리소스에서 제공하는 엔드포인트 목록

RAD 서버 패키지 프로젝트 구조

(이 글에서는, 리소스 이름을 "hello"로 지정한 RAD 서버 패키지를 생성했다는 가정으로 설명한다.)

RAD Server Package 프로젝트를 생성하면 다음과 같은 파일들이 생성된다.

image.png

파일의 내용들은 일반 패키지 프로젝트와 비슷하다.

프로젝트 파일(*.dpr)에는 requires에 emsserverapi 패키지가 포함된 것 외에는 일반 패키지 프로젝트와 동일하다.
(emsserverapi 패키지에 RAD 서버 엔진과 연동하는 기능들이 구현된 것을 예상할 수 있다.)

package Project1;

{$R *.res}
{... 일부생략 ...}
{$ENDIF IMPLICITBUILDING}
{$RUNONLY}
{$IMPLICITBUILD ON}

requires
  rtl,
  emsserverapi;

contains
  Unit1 in 'Unit1.pas' {HelloResource1: TDataModule};

end.

RAD 서버 패키지의 주요 로직은, 유닛파일(*.pas)에 구현해야 한다.

유닛 파일에는 다음 기능이 구현되어 있어야 한다.

  1. 리소스 클래스 정의
  2. 리소스 클래스 내에 엔드포인트 정의
  3. 리소스 (RAD 서버 엔진에)등록

위 기능을 구현하는 방법(규칙)을 자세히 살펴보자.

리소스 선언 및 등록 규칙

리소스 클래스 선언

리소스는 반드시 클래스로 정의 해야 한다. 데이터 모듈등 다른 클래스를 상속받아도 된다.
(참고로, 데이터 모듈을 상속 받으면, 논비주얼 컴포넌트를 올려 사용할 수 있어 편리하다.)

상속 받지 않는 경우, {$METHODINFO ON}과 {$METHODINFO OFF}를 클래스 전후에 추가해야 한다.
(참고: $METHODINFO 지시어는 클래스의 RTTI를 활성화하기 위함이다.)

// 데이터 모듈 상속
THelloResource1 = class(TDataModule)

// 상속하지 않는 경우
{$METHODINFO ON}
THelloResource = class
... 생략
end;
{$METHODINFO OFF}

리소스 이름 지정

리소스 이름은 URI의 경로 일부로 사용되는 중요한 요소이다.
(아래 그림과 같이 "hello"라는 리소스이름은 /hello, /hello/{item}과 같이 URI 경로 일부로 사용된다.)

image.png

리소스 이름 지정은 리소스 클래스 상단에 ResourceName 애트리뷰트를 추가해 명시적으로 지정 가능하다.

  [ResourceName('hello')]
  THelloResource1 = class(TDataModule)

만약, ResourceName 애트리뷰트를 지정하지 않았다면, 클래스 이름에서 제일 앞의 T를 제외한 키워드가 리소스이름으로 사용된다. 위의 경우 helloresource1이 사용 된다.
image.png

리소스 등록

정의한 리소스 클래스는  RegisterResource 메소드(EMS.ResourceTypes에 정의됨)를 호출해 RAD 서버 엔진에 등록해야 한다.

procedure Register;
begin
  RegisterResource(TypeInfo(THelloResource1));
end;

initialization
  Register;

RAD 서버 패키지 프로젝트에서 여러개의 리소스를 구현 및 등록 할 수 있다.

  • 하나의 유닛파일에 여러개의 리소스 클래스 정의 후 각 리소스 등록
  • 여러개의 유닛파일에 리소스 클래스 정의 후 각 유닛에서 리소스 등록

하나의 패키지 안에 여러개의 리소스를 구현 및 등록한 경우 아래와 같이 하나의 패키지(Project1.bpl)안에 2개의 리소스(hello, bye)가 등록된 로그를 확인할 수 있다.

image.png

엔드포인트 선언 및 연결 규칙

RAD 서버 엔진은 클라이언트 HTTP 요청 시, 리소스와 HTTP 메소드에 따라 리소스 클래스에 구현된 엔드포인트 메소드(코드)를 호출한다.

엔드포인트 메소드 선언

published
  procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  [ResourceSuffix('{item}')]
  procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  procedure Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

엔드포인트 메소드는 다음 규칙으로 선인 해야 한다.

  • public 또는 published 등 공개된 영역에 선언할 것
  • 프로시저(procedure)로 다음 3가지 타입의 매개변수를 가질 것
    • TEndpointContext - 종합 적인 정보(Host, 사용자, 인증 등)가 담긴 객체
    • TEndpointRequest - 요청에 대한 정보가 담긴 객체
    • TEndpointResponse - 응답할 정보를 담는 객체
  • 리소스 이름은 HTTP 메소드로 시작하거나, EndpointMethod 애트리뷰트를 상단에 지정할 것
  • 리소스 이름 뒤에 추가적인 경로를 지정할 경우 ResourceSuffix 애트리뷰트를 상단에 지정할 것

HTTP 메소드와 연결(Mapping)

HTTP 메소드와 리소스 클래스의 엔드포인트 메소드 연결은 다음 규칙으로 연결된다.

  1. 엔드포인트 메소드(함수) 이름으로 연결
  2. EndpointMethod 애트리뷰트로 지정된 메소드와 연결
  3. 같은 메소드의 경우 경로를 지정 해 경로에 따른 메소드와 연결

엔드포인트 메소드 이름으로 연결

엔드포인트 메소드는 HTTP 메소드(Get, Post, Put, Delete)와 일치하는 문자열로 시작하는 메소드와 연결된다.

샘플 엔드포인트는 다음 이름으로 엔드포인트 메소드가 생성된다.

  • Get, GetItem, Post, PutItem, DeleteItem

GetItem과 PutItem, PostData 등 HTTP 메소드가 접두사로 시작되면 유효한 이름이다.(대소문자는 구분하지 않는다.)

EndpointMehtod 애트리뷰트 지정

명시적으로 EndpointMethod 애트리뷰트를 사용해 메소드를 지정할 수 있다.

    [EndpointMethod(TEndpointRequest.TMethod.Get)]
    procedure Print(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

위와 같이 Print 메소드에 엔드포인트 메소드를 지정한 경우, RAD 서버의 로그에 아래 그림과 같이 Get 메소드에 Print 프로시저가 연결된다.(실제 GET 요청 시 Print 프로시저에 구현된 코드가 실행된다.)

image.png

경로에 따른 메소드 연결

Get과 GetItem은 동일한 GET으로 요청하더라도 ResourceSuffix(리소스 접미사)에 따라 다른 엔드포인트 메소드를 호출한다. 이 내용은 URL 경로 및 파라메터 취득 단락에서 자세히 설명한다.

인용하기

만약 동일한 메소드가 중복 선언된 경우 메소드 호출 시 다음과 같은 에러가 발생한다.(구동시에는 오류가 발생하지 않는다.)
image.png

위 오류는 동일한 메소드에 대해 경로로 구분하려 했으나, 경로 지정이 되지 않았거나 잘못 지정한 경우 자주 발생한다.
그리고, 중복된 메소드가 있는지도 검토해봐야 한다.

 

리소스 접미사 및 파라미터 취득

리소스 접미사 선언

인용하기

리소스 접미사는?

리소스 접미사는 HTTP 요청의 URI에서 리소스 이름 뒤에 추가된 경로를 의미한다.

예를 들어, 이름이 sports인 리소스의 경우, /sports/soccer는 soccer가 리소스 접미사이며, 통상적으로 스포츠 중 축구라는 의미로 리소스 접미사가 사용된다.

RAD 서버 엔진은 HTTP 요청시 리소스 접미사가 있는 경우, ResourceSuffix에 정의된 경로와 매핑되는 엔드포인트 메소드를 찾아 실행한다.

procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
[ResourceSuffix('{item}')]
procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
[ResourceSuffix('categories/{item}')]
procedure GetICategoryItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
[ResourceSuffix('categories/{item}/types/{type}')]
procedure GetICateAndTypeItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

ResourceSuffix는

  • 중괄호({})를 이용해 매개변수를 사용할 수 있다.
  • 경로를 포함할 수 있다.
  • 이름이 다른 여러개의 매개변수를 사용할 수 있다.

위 코드의 경우 URI에 따라 호출되는 엔드포인트 메소드는 다음과 같다.(굵게 표시한 부분은 매개변수이다.)

  • GET /hello/10 -> GetItem
  • GET /hello/categories/delphi -> GetCategoryItem
  • GET /hello/categories/delphi/types/example -> GetCateAndTypeItem

GetItem, PutItem, DeleteItem등 특정 대상으로 조회/수정/삭제 시 대상을 지정해야 하는 경우 리소스 접미사를 사용해야 한다.

[ResourceSuffix('{item}')]
procedure PutItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
[ResourceSuffix('{item}')]
procedure DeleteItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

리소스 접미사 매개변수를 구현에서 사용하기

리소스 접미사 선언 시 매개변수를 사용했다면, 구현 시 ARequest.Params.Values[매개변수이름]으로 리소스 접미사의 매개변수를 취득할 수 있다.

procedure THelloResource1.GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  LItem: string;
begin
  LItem := ARequest.Params.Values['item'];
  AResponse.Body.SetValue(TJSONString.Create('hello ' + LItem), True);
end;

URL 파라미터 취득

ARequest.Params.Values를 이용해 리소스 접미사 매개변수 외에도 URL 파라미터(URI에서 ? 뒤에 선언)를 가져올 수 있다.

procedure THelloResource1.GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  LItem: string;
  LQry: string;
begin
  LItem := ARequest.Params.Values['item'];
  LQry := ARequest.Params.Values['query'];
  AResponse.Body.SetValue(TJSONString.Create('hello ' + LItem + '/q=' + LQry), True);
end;

query 파라미터 값을 취득해 화면에 출력한 결과

image.png

 

엔드포인트 메소드 응답

엔드포인트 메소드에서 응답은 응답 객체(TEndpointResponse)를 이용한다.

REST API에서 응답 메시지는 HTTP 바디(응답 데이터), 컨텐트타입(응답 데이터 종류), 상태코드(결과) 3가지를 제공한다.

HTTP 바디

HTTP 바디는 일반적으로 JSON 형식으로 데이터를 제공한다.(JSON 형식이 필수사항은 아니며 XML 형식 또는 사용자 정의 문자열 포맷을 사용해도 된다.)

AResponse.Body.SetValue(TJSONString.Create('hello'), True);

위 코드는 HTTP 바디에 JSON 문자열 hello를 응답한 것이며, SetValue 메소드 내부에서 ContentType(application/json)과 상태코드(200: OK)를 포함한다.

인용하기

델파이 코드로 JSON을 다루는 방법 참고

https://welcome.devgear.co.kr/topic/192-델파이에서-json-데이터-다루기/

컨텐트 타입(ContentType)

응답 데이터의 종류를 지정할 수 있다. 컨텐트 타입은 응답 해더에 추가되며, 아래와 같은 AResponse.Body에 데이터를 설정하는 메소드의 매개변수로 컨텐트 타입을 전달하면 내부적으로 해더에 컨텐트 타입을 추가해 응답한다.(SetValue 메소드는 JSON 데이터로 자동으로 application/json으로 설정된다.)

컨텐트 타입은 문자열로 입력해야 하며, REST API 이해하기의 HTTP 해더 섹션의 '자주사용하는 컨텐츠 타입'을 참고하기 바란다.

procedure SetValue(const AJSONValue: TJSONValue; AOwnsValue: Boolean); virtual; abstract;
procedure SetBytes(const ABytes: TBytes; const AContentType: string); virtual; abstract;
procedure SetStream(const AStream: TStream; const AContentType: string; AOwnsValue: Boolean); virtual; abstract;
procedure SetString(const AString: string; const AContentType: string = ''); virtual;

예를 들어, 이미지 파일을 SetStream으로 응답한 경우 ContentType은 'image/jpeg'을 지정하는 것이 좋다.

상태코드

요청에 대한 응답의 결과를 상태코드로 제공하면, 클라이언트에서 상태코드로 결과를 판단할 수 있다.

상태코드는 기본으로 200(OK)가 설정되어 있으며, 예외가 발생한 경우 다음 메소드를 사용해 오류 내용과 상태코드를 반환할 수 있다. 만약, 해당하는 예외 메소드가 없는 경우 RaiseError로 명시적으로 코드 및 에러 내용을 지정해 응답할 수 있다.

procedure RaiseNotFound(const AError: string = ''; const ADescription: string = ''); // 404
procedure RaiseBadRequest(const AError: string = ''; const ADescription: string = ''); // 400
procedure RaiseDuplicate(const AError: string = ''; const ADescription: string = ''); // 409
procedure RaiseForbidden(const AError: string = ''; const ADescription: string = ''); // 403
procedure RaiseUnauthorized(const AError: string = ''; const ADescription: string = ''); // 401
procedure RaiseNotAcceptable(const AError: string = ''; const ADescription: string = ''); // 406
procedure RaiseUnsupportedMedia(const AError: string = ''; const ADescription: string = ''); // 415

procedure RaiseError(ACode: Integer; const AError, ADescription: string); // 그외

예를 들어 리소스를 찾을 수 없는 경우, 다음과 같이 NotFound(404 에러) 에러를 발생할 수 있다.

  if Qry.RecordCount = 0 then
    AResponse.RaiseNotFound('에러 내용', '상세설명');

404 에러코드외에 에러에 대한 내용도 (바디를 통해)응답된다.

image.png

요청 처리 위임

리소스 클래스는 리소스 구현을 다른 리소스 모듈(클래스 또는 컴포넌트)에 위임할 수 있다. 위임받는 클래스는 IEMSEndpointPublisher 인터베이스를 구현해야 한다.

주의할 점은 위임한 경우 리소스 이름은 위임을 요청한 리소스 클래스의 리소스 이름과 위임받은 리소스 클래스의 리소스 이름이 모두 표시된다.
(예를 들면, hello 리소스에서 emps 리소스로 위임한 경우 리소스 이름은 /hello/emps 로 정의 된다.)

클래스에 위임

다음 코드와 같이 IEMSEndpointPublisher 인터페이스를 구현한 클래스를 선언할 수 있다. 클래스도 위에서 작성한 규칙을 이용해 구현해야 한다.

  TEmpsProvider = class(TComponent, IEMSEndpointPublisher)
  private
    class var FArray: TJSONArray;
  private
    function GetItemByID(ARequest: TEndpointRequest): Integer;
    class constructor Create;
    class destructor Destroy;
  public
    procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); overload;
    [ResourceSuffix('./{id}')]
    procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); overload;
  end;

클래스로 위임하려면 클래스 선언(Employees: TEmpsProvider)만 해도 된다. 아래 코드는 일부러 ResourceName 애트리뷰트를 지정했다.

  [ResourceName('hello')]
  THelloResource1 = class(TDataModule)
  published
    [ResourceName('emps')]
    Employees: TEmpsProvider;

    procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

컴포넌트에 위임

RAD 서버는 리소스 처리를 위임받아 특별한 기능을 수행하는 2가지 컴포넌트를 제공한다.

  • TEMSDataSetResource - 지정한 데이터셋의 데이터를 JSON 형식으로 제공하거나 추가/수정/삭제할 수 있는 리소스를 제공하는 컴포넌트
  • TEMSFileResource - 지정 경로의 파일을 제공하거나 추가/수정/삭제할 수 있는 리소스를 제공하는 컴포넌트

TEMSDataSetResource 사용법은 데이터셋 데이터를 REST API로 바로 서비스할 수 있는TEMSDataSetResource 컴포넌트 안내에서 설명한다.

 

참고 자료

이 댓글 링크
다른 사이트에 공유하기

  • 험프리 changed the title to RAD 서버의 REST API 개발 시 리소스와 엔드포인트 구현 규칙 안내

이 토의에 참여하세요

지금 바로 의견을 남길 수 있습니다. 그리고 나서 가입해도 됩니다. 이미 회원이라면, 지금 로그인하고 본인 계정으로 의견을 남기세요.

Guest
이 토픽(기고/질문)에 답하기

×   서식있는 텍스트로 붙여넣기.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   이전에 작성한 콘텐츠가 복원되었습니다..   편집창 비우기

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

중요한 정보

이용약관 개인정보보호정책 이용규칙 이 사이트가 더 잘 작동하기 위해 방문자의 컴퓨터에 쿠키가 배치됩니다. 쿠키 설정 변경에서 원하는 설정을 할 수 있습니다. 변경하지 않으면 쿠키를 허용하는 것으로 이해합니다.