Search results for '전체'

  1. 2017.06.14 -- "REST API 웹서비스 연동" 교육자료 (1)
  2. 2017.05.25 -- 프로세스 메모리 사용량 로그 기록 - 성능 모니터 이용
  3. 2017.05.24 -- [REST API][실습] 데이터셋 기반 REST API 개발하기
  4. 2017.05.19 -- [REST API][실습] REST API 클라이언트 개발하기(REST Client 이용)
  5. 2017.05.17 -- [REST API][실습] REST API 서버 개발하기(엔드포인트 구현, RAD 서버 이용)
  6. 2017.05.11 -- [REST API] REST API 이해하기
  7. 2017.04.27 -- [RAD서버] EMS 패키지 프로젝트 시작하기
  8. 2017.04.25 -- [디자인(설계)] 팩토리 메소드 패턴과 Class 타입을 활용 객체 생성 시 참조 관계 제거
  9. 2017.04.20 -- [환경설정] 아마존 EC2 이용해 리눅스 서버 환경 구축하기
  10. 2017.04.20 -- 델파이 앱에서 움직이는 GIF 표현하기(소스 포함)
  11. 2017.04.06 -- [환경설정] 아마존 EC2 이용해 윈도우 서버 환경 구축하기
  12. 2017.04.06 -- [RAD서버] EMS 서버 운영환경에 설치하기(독립형 실행파일)
  13. 2017.03.29 -- [10.2 도쿄] 델파이로 리눅스 기반 웹서비스 제작하기(WebBroker 이용)
  14. 2017.03.20 -- 블루투스 바코드 스캐너(BI-07) 안드로이드 라이브러리를 델파이에서 연동하는 방법 (16)
  15. 2017.02.15 -- [10.2 도쿄] 델파이/C++빌더 리눅스 개발환경 설정하기
  16. 2016.12.09 -- [안드로이드] 포토 라이브러리 목록 불러오기
  17. 2016.11.21 -- Out of memory 오류 해소 방안 (1)
  18. 2016.10.04 -- [필립스 휴] API 정리
  19. 2016.09.28 -- 마우스 이벤트 캡쳐 - SetCapture, ReleaseCapture
  20. 2016.09.22 -- 모바일 디바이스 화면 방향전환 이벤트 수신하기

"REST API 웹서비스 연동" 교육자료

2017.06.14 17:39

REST API 웹서비스 연동


여러분들의 프로젝트에 REST API 기반 웹서비스를 구축하고 연동하는 방법을 배울 수 있는 교육과정입니다.



진행 순서

 시간

 내용

 관련링크

 오전

 [이론] REST API 이해

 http://blog.hjf.pe.kr/462

 [이론] JSON 이란?

 엠바카데로 기술문서 바로가기

 [실습] REST API 서버 제작(RAD 서버 이용)

 http://blog.hjf.pe.kr/463

 오후

 [실습] REST API 서버 연동

 http://blog.hjf.pe.kr/464

 [실습] 데이터셋 기반 REST API 연동

 http://blog.hjf.pe.kr/465

 [실습] Open API 연동

 -



JSON 샘플

{
  "books":
  {
    "total":2,
    "book":
    [
      {
        "BOOK_SEQ":15,
        "BOOK_TITLE":"델파이 Begin...End",
        "BOOK_AUTHOR":"김원경"
      },     
      {
        "BOOK_SEQ":16,
        "BOOK_TITLE":"한 번에 개발하는 안드로이드 iOS앱 with 델파이. 1편",
        "BOOK_AUTHOR":"김원경 , 김현수, 오상현"
      }
    ]
  }

} 

http://www.json.org/json-ko.html


Open API 관련링크


OAuth 2.0 참고
https://developers.daum.net/services/apis/docs/oauth2_0/intro


REST API 서버 프로젝트(Books, DataSets)

EMSPackage.zip


클라이언트 프로젝트

EndPointClient.zip


DataSetClient.zip


KakaoOAuth20.zip


저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

험프리.김현수 험프리.김현수 교육, 세미나

  1. Blog Icon
    이재풍

    험프리님.

    볼랜드 포럼에 질문 올렸는데요,,
    TMemo가 제대로 작동 안하는 것에 대해
    바쁘시더라도 도움 주셨으면 합니다. ㅜㅜ

    볼랜드 포럼 델파이 질문에 Nougat 관련 제목으로 최근에 올렸습니다.
    부탁드립니다...

프로세스 메모리 사용량 로그 기록 - 성능 모니터 이용

2017.05.25 11:31

개발한 프로세스에서 메모리 누수(Leak)이 발생된다 예상되면, 메모리 사용량 추적을 통해 메모리 누수 여부를 검증해야 합니다. 성능 모니터를 이용하면 프로세스의 메모리 사용량을 추적 및 로그 기록할 수 있습니다.


만약, 메모리 누수가 확인된다면, 테스트용 실행파일 등으로, 메모리 누수가 발생할 수 있는 코드의 범위를 줄여가며 반복적으로 테스트 해야 합니다.

만약, 특정 기능 수행 시 메모리 누수가 의심된다면 특정 기능을 반복적으로 실행할 수 있는 테스트 프로그램을 만들어 점검할 코드의 범위를 줄여가며 추적하기 바랍니다.


델파이로 개발된 프로젝트라면 메모리 누수 보고 기능을 켜서 메모리 누수 여부를 확인할 수 있습니다.


프로세스 메모리 사용량 추적

윈도우즈 성능 모니터를 이용해 프로세스가 사용 중인 메모리 사용량을 로그파일로 기록하는 방법을 소개합니다.


모니터링 대상 프로세스 실행

메모리 사용량 추적할 프로세스를 실행합니다. 저는 MemoryLeakTest.exe라는 실행파일을 만들어 실행했습니다.


성능 모니터에서 프로세스 메모리 사용량 기록

성능 모니터를 실행하고, 성능 > 데이터 수집기 집합 > 사용자 정의 메뉴를 선택합니다.


목록 창에서 우측 마우스를 누르고, 새로 만들기 > 데이터 수집기 집합 메뉴를 선택합니다.


이름을 지정하고, 수동으로 만들기(고급) 항목 선택 합니다.


성능 카운터 항목을 선택합니다.


[추가] 버튼을 누릅니다.

데이터 수집 가격을 지정합니다.


사용 가능한 카운터에서 Process 항목을 펼치고, Working Set 항목을 선택합니다.

인스턴스로는 추적할 프로세스를 선택합니다.(현재 실행된 프로세스 목록이 표시됩니다.)

[추가] 버튼을 눌러 추가합니다.

(Handle Count, Private Bytes 등 추적하고 싶은 카운터들도 함께 선택해도 됩니다.)

(주요 카운터에 대한 설명은 하단 참고링크 참고)


데이터 저장 경로를 지정합니다.



작업을 마칩니다.


추가한 데이터 수집기 집합의 속성에 들어가 로그 형식을 "쉼표로 구분"으로 변경합니다.(로그를 엑셀로 활용할 수 있습니다.)


성능 모니터링을 시작합니다.(데이터 수집기 집합의 속성에서 예약등을 이용해 자동 시작할 수 있습니다.)


로그파일 확인

지정한 경로에 csv 파일이 생성됩니다.


csv 파일을 엑셀로 열고, 데이터를 분석하거나, 차트를 만들어 시각적으로 메모리 사용량(Working Set) 추이를 관찰 할 수 있습니다.


참고링크


저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

험프리.김현수 험프리.김현수 Delphi/C++Builder

[REST API][실습] 데이터셋 기반 REST API 개발하기

2017.05.24 13:31

이 글에서는 데이터셋 기반으로 일괄 데이터 처리하는 REST API 엔드포인트를 구현하고, 연동하는 내용을 설명합니다.


데이터셋 기반 REST API

데이터셋(TDataSet)은 데이터들의 집합으로, FireDAC의 데이터 셋(TFDDataSet)은 데이터셋의 내용을 JSON 포맷으로 저장하고, 불러오는 기능을 제공합니다. 


이 기능을 활용해 REST API의 JSON 포맷을 손쉽게 개발할 수 있습니다.


데이터셋 기반 REST API의 특징(장/단점)은 다음과 같습니다.

1) (장점) 매우 신속하고, 손쉽게 REST API 서버, 클라이언트를 개발할 수 있습니다.

데이터를 JSON 포맷으로 변환하는 코드가 대단히 짧아 집니다.


2) (단점)JSON 포맷을 직접 설정할 수 없습니다.

FireDAC의 JSON 저장 기능을 이용하기 때문에 FireDAC의 JSON 포맷을 그대로 사용합니다.

필요한 항목을 추가, 변경하기 쉽지 않습니다.

또한, REST 아키텍처의 규칙과 일부 다르게 구현해야 합니다.(GET과 POST 메소드만 사용합니다.)

(개인적으로 델파이 클라이언트-서버 환경으로 개발하는 경우 활용하는 것이 좋다는 의견입니다.)


이 글에 앞서 다음 내용을 선행학습하시기 바랍니다.


이 글에서는 다음 내용을 다룹니다.

  • [이론] 데이터셋과 JSON
    • JSON 포맷 저장/불러오기
    • CachedUpdates와 TFDSchemaAdapter
  • [실습] 데이터셋 기반 REST API 개발하기
    • 데이터셋 기반 REST API 서버 개발하기
    • 데이터셋 기반 REST API 클라이언트 개발하기

이 글의 실습에서는 도서대여 프로그램 만들기에서 사용한 데이터베이스를 사용합니다. 다음 링크에서 데이터베이스 구조를 확인하고, DB 파일을 다운로드 받으시기 바랍니다.

데이터셋과 JSON

FireDAC의 데이터셋(TFDDataSet)은 JSON 포맷으로 데이터 저장하고, JSON 포맷의 데이터를 불러오는 기능을 제공합니다.


JSON 포맷 저장/불러오기

FireDAC 데이터셋(TFDDataSet)은 SaveToStream, LoadFromStream 메소드를 이용해 데이터를 저장하고 불러오는 기능을 제공합니다. 파라메터로 스트림(TStream)과 데이터포맷을 지정할 수 있으며, 데이터포맷으로는 XML, JSON, Binary 중 선택할 수 있습니다.


아래와 같은 코드로 데이터셋의 데이터를 JSON 포맷으로 저장하고, 불러올 수 있습니다.

var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    FDQuery1.SaveToStream(Stream, TFDStorageFormat.sfJSON);

    Stream.Position := 0;
    FDQuery1.LoadFromStream(Stream, TFDStorageFormat.sfJSON);
    FDQuery1.ApplyUpdates;
  finally
    Stream.Free;
  end;

단, 데이터를 로드(LoadFromStream)의 경우 데이터셋의 CachedUpdates 속성이 True로 설정되어 있어야 합니다.


참고: 위 코드를 사용하기 위해서는 TFDStanStorageJSONLink 컴포넌트를 화면(또는 데이터모듈)에 올려놓거나, "FireDAC.Stan.StorageJSON" 유닛을 유즈절에 추가해야 합니다.


알아야 하는 주요 컴포넌트, 속성

캐쉬 업데이트 속성(CachedUpdates)

데이터셋의 CachedUpdates 속성을 사용하면 데이터셋의 Post 메소드 호출 시 데이터셋의 캐쉬(메모리)에 임시로 저장되고, ApplyUpdates 메소드 호출 시 캐쉬 데이터가 실제 DBMS에 변경된 데이터가 적용됩니다. 즉, 캐쉬 기반으로 데이터 조작 후 일괄 적용할 수 있습니다.


스키마어댑터 컴포넌트(TFDSchemaAdapter)

스키마어댑터(TFDSchemaAdapter) 컴포넌트는 중앙 캐쉬 업데이트를 지원합니다. 스키마어댑터 컴포넌트(TFDSchemaAdapter) 추가 후 데이터셋의 SchemaAdapter 속성에 할당하면, 스키마어댑터 컴포넌트를 통해 여러 데이터셋의 데이터 변경을 순차적으로 처리할 수 있습니다.


스키마어댑터 컴포넌트는 Open, Close, SaveToStream, LoadFromStream, ApplyUpdates 등의 메소드를 제공합니다. 이 메소드를 호출하면 스키마어댑터 컴포넌트와 연결된 여러개의 데이터셋의 데이터를 열기/닫기, 저장/불러오기, 일괄적용 할 수 있습니다.


메모리 기반 데이터셋 컴포넌트(TFDMemTable)

메모리 상에서 데이터 처리를 지원합니다. 이번 실습의 클라이언트는 메모리 기반 데이터셋 컴포넌트에 서버에서 받은 데이터를 보관하고, 조작(추가, 수정, 삭제) 후 변경된 내용을 서버에 저장 요청합니다.


EMS FireDAC 클라이언트 컴포넌트(TEMSFireDACClient)

EMS 서버에 데이터셋 기반 REST End-point와 연동하는 컴포넌트입니다. EMS 프로바이더, 리소스, 스키마어댑터 속성을 제공합니다.

  • EMS 프로바이더 : 접속할 EMS 서버 정보 설정
  • 리소스 : 연결할 리소스 설정
  • 스키마어댑터 : 데이터를 처리 위임
GetDatas, PostUpdates 메소드를 제공합니다.
  • GetDatas : 서버에서 데이터를 요청합니다.
  • PostUpdates : 클라이언트에서 변경된 데이터를 서버에 저장 요청합니다.


[실습] 데이터셋 기반 REST API 개발하기

이 실습에서는 도서대여 프로그램의 도서정보와 사용자정보를 제공하는 REST API 서버를 개발하고, REST API 서버에서 받은 도서 정보를 편집 후 저장하는 클라이언트 프로그램 개발을 실습합니다.


REST API 자료 구조는 FireDAC 데이터셋의 JSON 포맷으로 저장 기능을 사용합니다.

데이터셋 기반 REST API 서버 개발하기

이 실습에서는 "도서대여 프로그램 만들기"의 도서(BOOK)와 사용자(USERS) 테이블 데이터를 제공하는 REST API 서버 개발을 실습합니다.


다음 순서로 진행합니다.

1) EMS 패키지 프로젝트를 생성합니다.

2) 데이터 제공하기 위해 컴포넌트 추가 및 설정합니다.

3) GET 메소드를 구현해 데이터 제공 기능을 개발합니다.

4) POST 메소드를 구현해 데이터 저장 기능을 개발합니다.


EMS 패키지 프로젝트 생성

EMS 패키지 프로젝트를 생성(File > New > Other > Delphi Projects > EMS > EMS Package) 합니다.


리소스 이름을 "datasets"로 지정합니다.



엔드포인트는 Get과 Post를 선택합니다.


프로젝트와 소스코드를 저장합니다.


데이터 제공을 위한 컴포넌트 추가 및 설정


아래 목록을 참고해 데이터 연결을 위한 컴포넌트 추가 및 설정합니다.

  • conBookRental: TFDConnection
    • 도서대여 프로그램 데이터베이스 연결
  • scmAdtBookRental: TFDSchemaAdapter
  • qryBook: TFDQuery
    • CachedUpdates = True
    • Connection = conBookRental
    • SQL = "SELECT BOOK_SEQ, BOOK_TITLE, BOOK_AUTHOR, BOOK_ISBN, BOOK_PRICE, BOOK_LINK FROM BOOK"
    • SchemaAdapter = scmAdtBookRental
    • UpdateOptions.AutoIncFields = BOOK_SEQ
  • qryUser: TFDQuery
    • CachedUpdates = True
    • Connection = conBookRental
    • SQL = "SELECT USER_SEQ, USER_NAME, USER_BIRTH, USER_SEX, USER_PHONE, USER_MAIL FROM USERS"
    • SchemaAdapter = scmAdtBookRental
    • UpdateOptions.AutoIncFields = USER_SEQ
  • FDStanStroageJSONLink1: TFDStanStorageJSONLink


엔드포인트 추가

기본으로 생성한 엔드포인트에는 전체 데이터를 제공, 저장하는 용도로 사용합니다.

"/books/" 엔드포인트를 추가해 도서 정보만 제공, 저장하는 과정도 소개합니다.(이 엔드포인트는 클라이언트 실습에서 사용하지 않습니다.)

published procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); procedure Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); [ResourceSuffix('/books/')] procedure GetItemBooks(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); [ResourceSuffix('/books/')] procedure PostBooks(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse); end;


Get / GetItemBooks

procedure TDatasetResource1.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    scmAdtBookRental.SaveToStream(Stream, TFDStorageFormat.sfJSON);

    AResponse.Body.SetStream(Stream, 'application/json', True);
  except
    Stream.DisposeOf;
  end;
end;

procedure TDatasetResource1.GetItemBooks(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TMemoryStream;
begin
  Stream := TMemoryStream.Create;
  try
    qryBook.SaveToStream(Stream, TFDStorageFormat.sfJSON);

    AResponse.Body.SetStream(Stream, 'application/json', True);
  except
    Stream.DisposeOf;
  end;
end;

Get과 GetItemBooks 메소드는 데이터셋의 데이터를 스트림으로 JSON 포맷으로 저장 후 응답합니다.


Get은 스키마 어댑터와 연결된 데이터셋 들의 데이터(도서, 사용자)를 일괄 제공합니다.

GetItemBooks는 도서 쿼리(데이터셋)의 데이터만 제공합니다.


Post / PostBooks

procedure TDatasetResource1.Post(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TStream;
begin
  if not SameText(ARequest.Body.ContentType, 'application/json') then
    AResponse.RaiseBadRequest('content type');
  if not ARequest.Body.TryGetStream(Stream) then
    AResponse.RaiseBadRequest('Invailed stream');

  Stream.Position := 0;
  scmAdtBookRental.LoadFromStream(Stream, TFDStorageFormat.sfJSON);
  scmAdtBookRental.ApplyUpdates;
end;

procedure TDatasetResource1.PostBooks(const AContext: TEndpointContext;
  const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
  Stream: TStream;
begin
  if not SameText(ARequest.Body.ContentType, 'application/json') then
    AResponse.RaiseBadRequest('content type');
  if not ARequest.Body.TryGetStream(Stream) then
    AResponse.RaiseBadRequest('Invailed stream');

  Stream.Position := 0;
  qryBook.LoadFromStream(Stream, TFDStorageFormat.sfJSON);
  qryBook.ApplyUpdates;
end;

Post와 PostBooks 메소드는 요청 바디의 컨텐트타입을 확인하고, 바디의 컨텐트를 스트림으로 가져옵니다.(TryGetStream) 


Post 메소드는 스키마 어댑터로 데이터가 담긴 스트림의 데이터를 읽고, DB에 적용(ApplyUpdates)합니다.

PostBooks 메소드는 도서 쿼리에서 데이터가 담긴 스트림의 데이터를 읽고, DB에 적용(ApplyUpdates)합니다.


데이터셋 기반 REST API 클라이언트 개발하기

이 실습에서는 위에서 개발한 데이터셋 기반 REST API 서버와 연결해 데이터를 불러오고, 데이터 변경(추가, 수정, 삭제) 후 서버에 저장 요청합니다.


멀티-디바이스 애플리케이션(파이어몽키) 프로젝트로 진행합니다.(VCL 폼 애플리케이션에서도 같은 방법으로 개발할 수 있습니다.)


다음 순서로 진행합니다.

1) 프로젝트 생성 및 화면 개발

2) 데이터 연결 기능 개발

3) UI 컨트롤과 데이터 연결

4) 각 버튼 이벤트 개발


프로젝트 생성 및 화면 개발

멀티-디바이스 애플리케이션 프로젝트를 생성(File > New > Multi-Device Application)합니다.

(앞에서 만든 REST API 서버와 프로젝트 그룹으로 묶어 놓으면 편리합니다. Project Group > Add New Project)


아래 그림을 참고해 화면을 개발합니다.


에디트(TEdit)의 이름을 각각 "edtTitle, edtAuthor, edtISBN, edtPrice, edtLink"로 변경합니다.

버튼(TButton)의 이름을 각각 "btnSearch, btnDelete, btnAppend, btnSave, btnCancel"로 변경합니다.


데이터 연결 기능 구현

프로젝트에 데이터 모듈 추가 후 EMS 서버와 연결할 컴포넌트를 추가하겠습니다.


프로젝트에 데이터 모듈을 추가(File > New > Other > Delphi Files > Data Module)합니다.

데이터 모듈의 이름을 변경하고, 파일을 저장합니다.(저는 이름은 dmData, 파일이름은 DataAccessModule.pas로 했습니다.)


아래 그림과 표를 참고해 컴포넌트를 추가합니다.


 상위 오브젝트

 오브젝트

 속성

 값(또는 설명)

 dmData

 (데이터 모듈)

 EMSProvider1

 URLHost

 localhost(EMS 서버 주소)

 URLPort 8080

 FDSchemaAdapter1

 

 

 EMSFireDACClient1 Provider

 EMSProvider1

 Resource

 datasets

 SchemaAdapter FDSchemaAdapter1

 tblAdtBook

 (TFDTableAdapter)

 DatSTableName

 qryBook

 Name

 tblAdtBook

 SchemaAdapter FDSchemaAdapter1

 qryBook
 (TFDMemTable)

 Adapter

 tblAdtBook

 CachedUpdates True

 tblAdtUser

 (TFDTableAdapter)

 DatSTableName

 qryUser

 Name tblAdtUser
 SchemaAdapter FDSchemaAdapter1

 qryUser

 (TFDMemTable)

 Adapter

 tblAdtUser

 CachedUpdates

 True

 FDGUIxWaitCursor1

  


UI 컨트롤과 데이터 연결

라이브 바인딩 기술을 이용해 UI 컨트롤에 데이터를 표현합니다.


메모리 테이블에 필드 정보 추가하기

에디트(TEdit) 등의 UI 컨트롤과 데이터 연결 시 데이터셋의 필드 정보를 이용합니다. 메모리 테이블의 경우 자체적으로 필드 정보를 생성할 수 없어 아래 절차를 통해 메모리 테이블에 필드 정보를 추가합니다.


1) REST API 서버 프로젝트를 열고, 쿼리 컴포넌트(TFDQuery)의 Field Editor 메뉴를 선택합니다.


2) Field Editor 창의 팝업 메뉴에서 Add all fields 메뉴를 선택해 필드 정보를 추가합니다.


3) 추가된 필드 정보를 모두 선택 후 (클립보드로)복사 합니다.


4) 현재 프로젝트의 데이터 모듈로 돌아와 메모리 테이블 팝업 메뉴에서 Field Editor 메뉴를 선택합니다.


5) Field Editor 창에 붙여넣기 합니다.


위 과정을 메모리 테이블 별로 진행합니다.


라이브 바인딩으로 연결

폼 화면 유닛의 유즈(uses) 절에 데이터 모듈 유닛을 추가합니다.


폼 화면에서 라이브 바인딩 디자이너를 표시(View > Tool Windows > LiveBindings Designer)합니다.


아래 그림을 참고해 UI 컨트롤과 데이터를 연결(Linking)합니다.


버튼 기능 구현

각 버튼의 클릭 이벤트를 아래 코드를 참조해 입력합니다.

procedure TForm1.btnSearchClick(Sender: TObject);
begin
  dmData.EMSFireDACClient1.GetData;
end;

procedure TForm1.btnDeleteClick(Sender: TObject);
begin
  dmData.qryBook.Delete;
end;

procedure TForm1.btnAppendClick(Sender: TObject);
begin
  dmData.qryBook.Append;
end;

procedure TForm1.btnSaveClick(Sender: TObject);
begin
  dmData.EMSFireDACClient1.PostUpdates;
end;

procedure TForm1.btnCancelClick(Sender: TObject);
begin
  dmData.qryBook.Cancel;
end;

조회와 저장은 서버의 데이터를 다뤄야 하기 때문에 EMSFireDACClient를

추가, 삭제, 취소는 로컬의 메모리 테이블(qryBook)을 다루도록 구현했습니다.


테스트

REST API 서버와 클라이언트 구현이 완료되었습니다. 간단한(?) 작업으로 REST API 서버를 제작하고, 클라이언트에서 데이터 조회, 추가, 편집, 삭제 기능을 개발할 수 있었습니다.


테스트는 다음 단계로 진행합니다.

1) REST API 서버 실행(EMS 패키지 프로젝트를 실행합니다.)

2) REST API 클라이언트 실행

3) [조회] 버튼으로 데이터 출력

4) 데이터 추가, 수정 후 [저장] 버튼 클릭


참고/관련 자료



저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

험프리.김현수 험프리.김현수 Delphi/C++Builder

[REST API][실습] REST API 클라이언트 개발하기(REST Client 이용)

2017.05.19 18:24

이 글에서는 델파이를 이용해 REST API 서버와 연동하는 클라이언트 기술을 실습합니다.

REST 클라이언트 라이브러리 기반으로 실습합니다.

REST API 엔드포인트 연동


이 글에서는 REST API 서버의 REST API를 분석하고, 클라이언트에 표시 및 입력데이터를 서버에 저장하는 내용을 실습 위주로 진행합니다.

이 글에서는 데이터 처리하는 내용에 집중합니다. 이 글을 참고해 여러분이 만든 멋진 화면과 REST API를 연동하는 기술을 습득하시기 바랍니다.


이 글에 앞서 다음 내용의 이해가 필요합니다. 미리 선행 학습하시기 바랍니다.


    이 글에서는 다음 내용을 다룹니다.

    • REST API 분석
    • [실습] REST API 연동
      • Get
      • GetItem
      • GetItem - 이미지 수신
      • Post / PutItem / DeleteItem
    • 추가 학습할 내용

    REST API 분석

    이 글에서는 아래 링크에서 제작한 도서정보 REST API 서버를 대상으로 분석 및 연동합니다.


    도서정보 REST API 서버에서 제공하는 엔드포인트는 아래와 같습니다. {item}에는 도서정보 아이디로 치환해야 합니다.

     엔드포인트

     역할

      데이터 포맷

     GET http://localhost:8080/books/

     도서정보 목록 조회

     응답: application/json

     GET http://localhost:8080/books/{item}/

     도서정보 상세 조회

     응답: application/json

     GET http://localhost:8080/books/{Item}/photo/

     도서정보 사진 조회

     응답: image/jpg

     POST http://localhost:8080/books/

     도서정보 생성

     요청 : application/json(Body)

     PUT http://localhost:8080/books/{item}/

     도서정보 수정

     요청 : application/json(Body)

     DELETE http://localhost:8080/books/{item}/

     도서정보 삭제 


    REST API 분석은 REST Debugger(실행: Tools > REST Debugger 메뉴)를 이용합니다.

    REST Debugger 상세정보 - http://docwiki.embarcadero.com/RADStudio/en/REST_Debugger_Tool (자동변역)

    [실습] REST API 연동

    실습에서는 도서대여 REST API 서버와 연동하는 클라이언트 프로그램을 제작합니다.


    실습을 위해 도서대여 패키지를 포함한 EMS 서버를 실행합니다.


    사전 준비사항

    프로젝트 생성

    클라이언트 프로그램은 델파이 멀티-디바이스 애플리케이션(Multi-Device Application)으로 진행합니다.


    멀티-디바이스 애플리케이션 프로젝트를 생성(File > New > Multi-Device Application)합니다. 


    프로젝트에 델파이 데이터 모듈을 추가(File > New > Other > Delphi Projects > Delphi Files > Data Module)합니다.


    프로젝트를 저장합니다.(저는 아래와 같은 이름으로 저장했지만 다른 이름으로 저장해도 됩니다.)


    데이터 모듈 이름을 "dmDataAccess"로 변경합니다. 폼 유닛(MobileForm)에서 데이터 모듈 유닛(DataAccessModule)을 사용할 수 있도록 uses 절에 추가합니다.


    화면 구성(폼 디자인)

    아래 그림을 참고해 화면을 구성합니다.

    다음을 참고해 컴포넌트 이름을 지정합니다.

    • 버튼(TButton)
      • btnLoadData : 데이터 로드
      • btnNewData :  신규
      • btnDeleteData : 삭제
      • btnSaveData : 저장
    • 에디트(TEdit)
      • edtTtile : 제목
      • edtAuthor : 저자
      • edtISBN : ISBN
      • edtPrice : 가격
      • edtLink : 관련링크
    • 메모(TMemo)
      • mmoDescription : 설명

    Get

    Get 엔드포인트를 통해 도서정보 목록을 조회합니다.

    REST API 분석

    REST Debugger에서 아래와 같이 입력합니다.

    • Request 탭
      • Method : Get
      • URL : http://localhost:8080
      • Content-type : application/json
    • Parameters 탭
      • Resource : books
    [Send Request] 버튼을 누릅니다.

    위 이미지와 같이 200 : OK 메시지가 Response 영역에 표시되야 합니다.(200이 아닌 응답 상태 코드를 수신한 경우 EMS 서버 실행여부 또는 URL 등을 확인하시기 바랍니다.)


    Response 영역의 Headers 탭과 Body탭을 통해 데이터 수신을 확인합니다.

    JSON 데이터를 확인해, JSON 배열([])로 구성된 속성을 확인해 JSON Root Element에 입력 후 [Apply] 버튼을 클릭(또는 엔터)합니다.


    Tabular Data 탭에 배열의 데이터가 테이블 형식으로 표시됩니다.


    [Copy Components] 버튼을 눌러 분석한 데이터를 클립보드로 복사합니다.(이 컴포넌트는 개발에 직접 사용합니다.)


    REST API 연동 구현

    데이터 모듈에 붙여넣기(Ctrl + V) 후 아래 그림을 참고해 이름을 변경합니다.


    각 컴포넌트의 역할과 주요 속성

    • TRESTClient - 서버 정보 설정
      • BaseURL : 서버 기본 URL
    • TRESTRequest - 요청할 정보 설정 및 요청 작업 수행
      • Client : TRESTClient 지정
      • Method : 요청 시 HTTP 메소드 종류
      • Resource : REST API 리소스 지정(URI에서 BaseURL 제외한 영역), 파라메터는 중괄호({})로 지정
      • Params : Resource에 포함된 파라메터 값 정
      • Response : 응답 객체(TRESTResponse) 지정
    • TRESTResponse - 요청의 응답 정보 보관
      • Content : 응답받은 데이터(string)
      • RootElement : 응답 데이터 중 필요한 항목 지정
    • TRESTResponseDataSetAdapter - 응답 데이터를 데이터셋으로 변환
      • Response : 변환 대상 응답데이터
      • Dataset : 변환한 데이터를 기록할 데이터 셋(TDataSet을 상속받은 메모리 테이블, TFDMemTable 또는 TClientDataSet 등)
      • RootElement : 응답 데이터 중 변환할 항목 지정
    • TFDMemTable - 메모리 테이블

    reqList 컴포넌트 팝업메뉴에서 Execute 메뉴를 눌러 연결을 확인하고 데이터를 가져옵니다.

    이 작업은 REST API 서버의 데이터를 받아 메모리테이블(TFDMemTable)에 데이터를 로드합니다. 이 데이터를 기반으로 메모리 테이블의 필드 정보를 표시하고, UI 컨트롤에 데이터를 표시합니다. 델파이에서 프로젝트를 새로 열었다면, 위 작업을 다시 시도해 데이터를 메모리 테이블에 로드해야 합니다.

    폼의 [데이터 로드] 버튼 클릭 이벤트에 아래 코드를 구현합니다.

      dmDataAccess.reqList.Execute;


    도서목록 데이터를 그리드에 표시하기 위해 라이브 바인딩 디자이너 표시(View > Tool Windows > LiveBindings Designer) 후 아래 그림을 참고해 데이터와 UI컨트롤을 바인딩 합니다.


    프로젝트를 실행하고, [데이터 로드] 버튼을 눌러 그리드에 정보가 표시되는 것을 확인합니다.

    GetItem

    GetItem 엔드포인트를 통해 도서정보 목록을 조회합니다.

    REST API 분석

    REST Debugger에서 아래와 같이 입력합니다.

    • Request 탭
      • Method : Get
      • URL : http://localhost:8080
      • Content-type : application/json
    • Parameters 탭
      • Resource : books/{item}/
      • Request parameters : item 항목에 도서정보 아이디 값 입력
    [Send Request] 버튼을 누릅니다.

    응답 데이터 확인 후 JSON Root Element에 "book" 입력 후 [Apply] 버튼을 누르고, Tabular Data 탭에서 데이터 표시를 확인합니다.

    [Copy Components] 버튼을 누릅니다.

    REST API 연동 구현

    데이터 모듈에 붙여넣기 후 아래와 같이 이름을 변경합니다.


     TRESTClient는 재사용합니다. RESTClient2 컴포넌트를 제거합니다. reqDetail.Client 항목을 RESTClient1으로 지정합니다.


    reqDetail 컴포넌트 팝업메뉴에서 Execute 메뉴를 눌러 데이터를 가져옵니다.


    폼에서 라이브 바인딩 디자이너를 표시하고, 아래 그림을 참고해 바인딩합니다.


    private 영역에 아래 변수와 메소드 추가 후 자동 완성(Shift + Ctrl + C)으로 구현부를 생성합니다.

      private
        FSelectedSeq: Integer;
        procedure RequestDetail(ASeq: Integer);
        procedure LoadPhoto(ASeq: Integer);
        procedure ClearControls;


    그리드의 OnSelChanged 이벤트(그리드 셀 변경)를 추가후 아래 코드를 구현합니다.

    procedure TForm1.Grid1SelChanged(Sender: TObject);
    var
      Seq: Integer;
    begin
      if dmDataAccess.memBookList.RecordCount = 0 then
        Exit;
    
      Seq := dmDataAccess.memBookList.FieldByName('BOOK_SEQ').AsInteger;
      RequestDetail(Seq);
    end;
    
    procedure TForm1.ClearControls;
    begin
      dmDataAccess.memBookDetail.EmptyDataSet;
      ImageControl1.Bitmap.Assign(nil);
    end;
    
    procedure TForm1.RequestDetail(ASeq: Integer);
    begin
      FSelectedSeq := ASeq;
    
      ClearControls;
    
      dmDataAccess.reqDetail.Params.ParameterByName('item').Value := ASeq.ToString;
      BindSourceDB2.DataSource.Enabled := False;
      dmDataAccess.reqDetail.ExecuteAsync(procedure
      begin
        BindSourceDB2.DataSource.Enabled := True;
      end);
    
      LoadPhoto(ASeq);
    end;
    
    procedure TForm1.LoadPhoto(ASeq: Integer);
    begin
    end;

    LoadPhoto 메소드는 아래에서 구현합니다.

    GetItem - 이미지 수신

    REST API 연동 구현

    이미지 수신, 생성, 수정, 삭제에 공통으로 사용할 컴포넌트를 데이터 모듈에 추가합니다.


    RESTRequest의 Client 항목을 RESTClient1으로 Reponse 항목을 RESTResponse로 지정합니다.


    LoadPhoto 메소드에 아래와 같이 구현합니다.
    procedure TForm1.LoadPhoto(ASeq: Integer);
    var
      Stream: TMemoryStream;
    begin
      dmDataAccess.RESTRequest.Method := TRESTRequestMethod.rmGET;
      dmDataAccess.RESTRequest.Resource := '/books/{item}/photo/';
      dmDataAccess.RESTRequest.Params.ParameterByName('item').Value := ASeq.ToString;
      dmDataAccess.RESTRequest.ExecuteAsync(procedure
        begin
          if dmDataAccess.RESTResponse.StatusCode = 404 then
            Exit;
          Stream := TMemoryStream.Create;
          try
            Stream.WriteData(dmDataAccess.RESTResponse.RawBytes, dmDataAccess.RESTResponse.ContentLength);
            ImageControl1.Bitmap.LoadFromStream(Stream);
          finally
            Stream.Free;
          end;
        end);
    
    end;
    프로젝트를 실행하고, 그리드를 변경해 상세 데이터 및 이미지 표현을 확인합니다.

    Post / PutItem / DeleteItem

    신규, 저장, 삭제 기능을 구현합니다.

    REST API 연동 구현

    데이터 모듈에서 데이터셋의 데이터를 JSON으로 반환하는 메소드를 작성합니다. 이 메소드는 Post, PutItem 엔드포인트 호출 시 사용합니다.


    데이터 모듈의 uses 절에 "System.JSON, System.JSON.Writers"을 추가합니다.


    아래 코드를 참고해, GetBookData 메소드를 정의하고, 구현합니다.

    function TdmDataAccess.GetBookData: TJSONObject;
    var
      Writer: TJsonObjectWriter;
    begin
      Writer := TJsonObjectWriter.Create(False);
      try
        Writer.WriteStartObject;  // start resource
        Writer.WritePropertyName('book');
    
        Writer.WriteStartObject;  // start item
        Writer.WritePropertyName('BOOK_TITLE');
        Writer.WriteValue(memBookDetail.FieldByName('BOOK_TITLE').AsString);
    
        Writer.WritePropertyName('BOOK_ISBN');
        Writer.WriteValue(memBookDetail.FieldByName('BOOK_ISBN').AsString);
    
        Writer.WritePropertyName('BOOK_AUTHOR');
        Writer.WriteValue(memBookDetail.FieldByName('BOOK_AUTHOR').AsString);
    
        Writer.WritePropertyName('BOOK_PRICE');
        Writer.WriteValue(memBookDetail.FieldByName('BOOK_PRICE').AsString);
    
        Writer.WritePropertyName('BOOK_LINK');
        Writer.WriteValue(memBookDetail.FieldByName('BOOK_LINK').AsString);
    
        Writer.WritePropertyName('BOOK_DESCRIPTION');
        Writer.WriteValue(memBookDetail.FieldByName('BOOK_DESCRIPTION').AsString);
    
        Writer.WriteEndObject;  // end item
        Writer.WriteEndObject;  // end resource
    
        Result := Writer.JSON as TJSONObject;
      finally
        Writer.Free;
      end;
    end;

    [신규], [삭제], [저장] 버튼의 이벤트에 아래 코드를 작성합니다.

    procedure TForm1.btnNewDataClick(Sender: TObject);
    begin
      FSelectedSeq := -1;
      ClearControls;
    end;
    
    procedure TForm1.btnSaveDataClick(Sender: TObject);
    var
      Data: TJSONObject;
    begin
      if FSelectedSeq = -1 then
      begin
        dmDataAccess.RESTRequest.Method := TRESTRequestMethod.rmPOST;
        dmDataAccess.RESTRequest.Resource := '/books/';
      end
      else
      begin
        dmDataAccess.RESTRequest.Method := TRESTRequestMethod.rmPUT;
        dmDataAccess.RESTRequest.Resource := '/books/{item}/';
        dmDataAccess.RESTRequest.Params.ParameterByName('item').Value := FSelectedSeq.ToString;
      end;
      dmDataAccess.RESTRequest.ClearBody;
      Data := dmDataAccess.GetBookData;
      dmDataAccess.RESTRequest.Body.Add(Data);
      dmDataAccess.RESTRequest.Execute;
    
      ShowMessage('저장');
    end;
    
    procedure TForm1.btnDeleteDataClick(Sender: TObject);
    begin
      dmDataAccess.RESTRequest.Method := TRESTRequestMethod.rmDELETE;
      dmDataAccess.RESTRequest.Resource := '/books/{item}/';
      dmDataAccess.RESTRequest.Params.ParameterByName('item').Value := FSelectedSeq.ToString;
      dmDataAccess.RESTRequest.Execute;
    
      ShowMessage('삭제');
    end;

    위 코드와 같이 단순한 요청, 응답의 경우 컴포넌트를 재사용 하는 것도 좋은 방법입니다.

    추가 학습할 내용










    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder

    [REST API][실습] REST API 서버 개발하기(엔드포인트 구현, RAD 서버 이용)

    2017.05.17 11:31

    이 글에서는 델파이를 이용해 REST API 엔드포인트를 제공하는 서버 제작 기술을 학습합니다.

    RAD 서버의 EMS 패키지 프로젝트로 진행합니다.

    REST API 엔드포인트

    리소스에 HTTP 메소드 별로 구현해 놓은 것을 REST API 엔드포인트라 합니다.


    REST는 GET, POST, PUT, DELETE 4개의 HTTP 메소드 지원을 원칙으로하며, 그 중 지원할 HTTP 메소드를 지정 및 구현해 REST API를 제공합니다. 즉 리소스에 따라 필요한 HTTP 메소드에 한해 엔드포인트를 제공할 수 있습니다.


    이 글에서는 RAD 서버를 이용해 리소스를 생성하고, 엔드포인트를 작성하는 실습을 진행합니다.

    실습의 주제는 도서정보를 제공하는 REST API를 개발합니다.


    이 글에 앞서 다음 내용을 이해하고 있어야 합니다. 미리 선행 학습하시기 바랍니다.


    이 글에서는 다음 내용을 다룹니다.

    • EMS 패키지 프로젝트 시작하기
    • [실습] REST API 엔드포인트 개발하기
      • Get / GetItem
        • REST Debugger 소개
      • Post / PutItem / DeleteItem
      • GetItem - 이미지 제공
    • 추가 학습할 내용


    이 글의 실습에서는 "[따라하기] 도서대여 프로그램 만들기"에서 사용한 데이터베이스(Interbase)를 사용합니다.


    EMS 패키지 프로젝트 시작하기

    EMS 패키지 프로젝트를 이용해 REST API 리소스와 엔드포인트를 개발할 수 있습니다.


    EMS 패키지를 생성하고, 실행하는 내용은 다음 링크를 통해 선행 학습하시기 바랍니다.

    EMS 패키지 프로젝트 생성

    "books" 이름으로 리소스 이름을 지정하고 파일유형으로 Data Module을 선택 합니다.


    엔드포인트를 모두 선택 합니다.


    프로젝트를 생성하면 아래와 같은 코드가 자동 생성됩니다.


    [ResourceName('books')]

    리소스 이름을 지정하는 특성 구문으로, 아래의 클래스(TBooksResource1)의 리소스 이름을 지정합니다.

    이 이름은 URI에 사용되며, URI(http://localhost:8080/books/) 요청 시 리소스 이름이 지정된 클래스의 엔드포인트 프로시저를 실행합니다. 이 이름은 코드 상에서 수정할 수 있습니다.


    [ResourceSuffix('{item}')]

    리소스 뒤에 붙는 리소스접미사를 정의하는 특성(Attribute) 구문입니다.

    URI(http://localhost:8080/books/100/)에서 리소스 이름 뒤의 영역을 지정합니다.

    중괄호({..})로 정의된 항목은 파라메터로 지정되어, 코드로 읽어 올 수 있습니다.


    예를들어 "http://localhost:8080/books/100/"으로 호출하는 경우, "100"이 접미사이며, 중괄호로 파라메터로 지정된 경우 다음 코드로 값을 가져올 수 있습니다.

    ARequest.Params.Values['item'] // 100

    리소스접미사는 여러 단계로 정의할 수 있습니다. 

    [ResourceSuffix('{item}/photo/')]
    procedure GetItemPhoto(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [ResourceSuffix('{item}/{subitem}/')]
    procedure GetItemSub(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);


    HTTP 메소드 프로시저

    EMS 패키지에서는 Get, GetItem, Post, PutItem, DeleteItem 총 5개의 엔드포인트를 제공합니다.

     엔드포인트

     HTTP 메소드

     리소스 접미사

     Get

     GET

     

     GetItem

     GET

     {item}

     Post

     POST

     

     PutItem

     PUT

     {item}

     DeleteItem

     DELETE

     {item}


    위 표와 같이, 엔드포인트는 HTTP 메소드, 리소스 접미사와 매핑됩니다. 

    Get 을 예로 들면, GET으로 리소스에 요청하면 엔드포인트 Get 프로시저가 호출되고, {item}이 포함되어 GET으로 요청하면 GetItem 프로시저가 호출됩니다.


    각 엔드포인트의 프로시저는 엔드포인트 이름으로 시작되도록 정의해야합니다. 만약, 같은 엔드포인트가 프로시저가 2개이상 구현된 경우 실행 시 오류가 발생합니다.


    [실습] REST API 엔드포인트 개발하기

    이 장에서는 엔드포인트 별로 기능을 구현하고 실습하는 방법을 안내합니다.

    실습에서는 도서대여 프로그램의 데이터베이스를 사용 합니다. 데이터 포맷으로는 JSON을 사용합니다.


    사전 준비사항

    다음 디렉토리 구조로 진행합니다.

    도서대여 프로그램 데이터베이스 파일을 DB 디렉토리에 저장합니다.


    위에서 생성한 EMS 패키지 프로젝트 Source 디렉토리에 저장합니다.


    프로젝트 옵션에서 Output 디렉토리를 지정합니다.


    BookResource 데이터 모듈에 아래와 같이 TFDConnection, TFDQuery 컴포넌트를 추가하고 이름을 변경합니다.


    conBookRental(TFDConnection)의 OnBeforeConnect 이벤트 핸들러 생성 후 아래 코드(데이터베이스 파일 경로를 지정)를 추가합니다. uses 절에 System.IOUtils를 추가합니다.

    procedure TBooksResource1.conBookRentalBeforeConnect(Sender: TObject);
    var
      Path: string;
    begin
      Path := TPath.GetFullPath('..\DB\BOOKRENTAL.IB');
      if not TFile.Exists(Path) then
      begin
        raise Exception.Create('Not found database.');
      end;
    
      conBookRental.Params.Values['Database'] := Path;
    end;


    Get / GetItem

    리소스에 GET 메소드로 요청하면 Get 또는 GetItem(접미사가 있을 경우)이 호출됩니다. GET 메소드는 조회 역할을 하면 각 엔드포인트는 다음 역할을 구현합니다.

    • Get - 리소스의 목록 정보 조회
    • GetItem - 리소스의 상세 정보 조회

    Get - 리소스 목록

    Get 엔드포인트는 리소스 목록을 조회하는 역할을 합니다. 실습에서는 도서정보 리소스를 구현하므로, 도서 정보 목록을 제공하도록 구현합니다.


    GET http://localhost:8080/books/

    {
      "books":
      {
        "total":6,
        "book":
        [
          {
            "BOOK_SEQ":15,
            "BOOK_TITLE":"델파이 Begin...End",
            "BOOK_AUTHOR":"김원경"
          },      
          {
            "BOOK_SEQ":16,
            "BOOK_TITLE":"한 번에 개발하는 안드로이드 iOS앱 with 델파이. 1편",
            "BOOK_AUTHOR":"김원경 , 김현수, 오상현"
          }
        ]
      }
    }

    Get 엔드포인트에 아래와 같이 구현합니다. JSON 데이터 작성은 TJsonObjectWriter를 이용했습니다. 다른 JSON 라이브러리를 사용해 작성해도 무관합니다.

    procedure TBooksResource1.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    const
      SQL_LIST ='SELECT BOOK_SEQ, BOOK_TITLE, BOOK_AUTHOR, BOOK_PRICE FROM BOOK';
    var
      Writer: TJsonObjectWriter;
    begin
      qryBook.Close;
      qryBook.SQL.Text := SQL_LIST;
      qryBook.Open;
    
      Writer := TJsonObjectWriter.Create;
      try
        Writer.WriteStartObject; // start resource
        Writer.WritePropertyName('books');
    
        Writer.WriteStartObject; // start item
        Writer.WritePropertyName('total');
        Writer.WriteValue(qryBook.RecordCount);
    
        Writer.WritePropertyName('book');
        Writer.WriteStartArray;
    
        qryBook.First;
        while not qryBook.Eof do
        begin
          Writer.WriteStartObject;
          Writer.WritePropertyName('BOOK_SEQ');
          Writer.WriteValue(qryBook.FieldByName('BOOK_SEQ').AsInteger);
    
          Writer.WritePropertyName('BOOK_TITLE');
          Writer.WriteValue(qryBook.FieldByName('BOOK_TITLE').AsString);
    
          Writer.WritePropertyName('BOOK_AUTHOR');
          Writer.WriteValue(qryBook.FieldByName('BOOK_AUTHOR').AsString);
    
          Writer.WritePropertyName('BOOK_PRICE');
          Writer.WriteValue(qryBook.FieldByName('BOOK_PRICE').AsString);
    
          Writer.WriteEndObject;
          qryBook.Next;
        end;
    
        Writer.WriteEndArray;
    
        Writer.WriteEndObject;  // end item
        Writer.WriteEndObject;  // end resource
    
        AResponse.Body.SetValue(Writer.JSON as TJSONValue, True);
      except
        Writer.Free;
        raise;
      end;
    end;

    구현을 마치면, 프로젝트를 실행하고 웹브라우저에서 "http://localhost:8080/books/"를 입력해 JSON 문자열이 화면에 표시되는 것을 확인합니다.


    GetItem - 리소스 상세

    GetItem 엔드포인트는 특정 도서의 상세 정보를 제공하도록 구현합니다.


    GET http://localhost:8080/books/{item}/

    {
      "book":
      {
        "BOOK_SEQ":15,
        "BOOK_TITLE":"델파이 Begin...End",
        "BOOK_ISBN":"9788996251613",
        "BOOK_AUTHOR":"김원경",
        "BOOK_PRICE":"28000",
        "BOOK_LINK":"http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788996251613&orderClick=LAG&Kc=SETLBkserp1_5",
        "BOOK_DESCRIPTION":"델파이의 시작부터 끝까지 파헤치다!
    ... (생략)"
      }
    }

    다음과 같이 구현합니다.

    procedure TBooksResource1.GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    const
      SQL_ITEM_INFO = 'SELECT ' +
                        'BOOK_SEQ, BOOK_TITLE, BOOK_ISBN, BOOK_AUTHOR, BOOK_PRICE, ' +
                        'BOOK_LINK, BOOK_DESCRIPTION ' +
                      'FROM BOOK WHERE BOOK_SEQ = :BOOK_SEQ';
    
    var
      BOOK_SEQ: string;
      Writer: TJsonObjectWriter;
    begin
      BOOK_SEQ := ARequest.Params.Values['item'];
      // Sample code
      qryBook.Close;
      qryBook.SQL.Text := SQL_ITEM_INFO;
      qryBook.ParamByName('BOOK_SEQ').AsString := BOOK_SEQ;
      qryBook.Open;
    
      if qryBook.RecordCount = 0 then
        AResponse.RaiseNotFound('Not found', '''' + BOOK_SEQ + ''' is not found');
    
      Writer := TJsonObjectWriter.Create;
      try
        Writer.WriteStartObject;  // start resource
        Writer.WritePropertyName('book');
    
        Writer.WriteStartObject;  // start item
        Writer.WritePropertyName('BOOK_SEQ');
        Writer.WriteValue(qryBook.FieldByName('BOOK_SEQ').AsInteger);
    
        Writer.WritePropertyName('BOOK_TITLE');
        Writer.WriteValue(qryBook.FieldByName('BOOK_TITLE').AsString);
    
        Writer.WritePropertyName('BOOK_ISBN');
        Writer.WriteValue(qryBook.FieldByName('BOOK_ISBN').AsString);
    
        Writer.WritePropertyName('BOOK_AUTHOR');
        Writer.WriteValue(qryBook.FieldByName('BOOK_AUTHOR').AsString);
    
        Writer.WritePropertyName('BOOK_PRICE');
        Writer.WriteValue(qryBook.FieldByName('BOOK_PRICE').AsString);
    
        Writer.WritePropertyName('BOOK_LINK');
        Writer.WriteValue(qryBook.FieldByName('BOOK_LINK').AsString);
    
        Writer.WritePropertyName('BOOK_DESCRIPTION');
        Writer.WriteValue(qryBook.FieldByName('BOOK_DESCRIPTION').AsString);
    
        Writer.WriteEndObject;  // end item
        Writer.WriteEndObject;  // end resource
    
        AResponse.Body.SetValue(Writer.JSON as TJSONValue, True);
      except
        Writer.Free;
        raise;
      end;
    end;

    구현을 마치고, 프로젝트를 실행하고, 웹브라우저에 "http://localhost:8080/books/{item}/"({item}은 목록의 BOOK_SEQ로 치환합니다.) 입력해 결과를 확인합니다.

    RESTDebugger - REST API 분석도구

    GET 메소드의 경우 웹브라우저로 손쉽게 테스트 해볼 수 있습니다. 테스트 하기 어려운 POST, PUT, DELETE 메소들은 RESTDebugger를 이용해 테스트 할 수 있습니다.


    RESTDebugger는 Tools > REST Debugger 메뉴를 사용해 실행합니다.

    Request 탭의 Method(HTTP 메소드), URL, Conentt-type, Custom body 등을 지정 후


    Parameters 탭에서 Resource, Request Parameters 항목을 지정 해 [Send Reuqest] 버튼을 눌러 요청합니다.

    Resource 항목의 경우 {item}과 같이 중괄호로 지정한 항목이 자동으로 파라메터에 추가됩니다.


    응답은 Headers와 Body 탭에 정보가 표시됩니다.



    자세한 사용법은 다음 링크를 참고하시기 바랍니다.


    Post / PutItem / DeleteItem

    리소스를 생성, 수정, 삭제합니다. 생성과 수정에 필요한 정보는 요청 시 Custom Body영역에 JSON 포맷으로 전달하도록 구현합니다.

    Post - 리소스 생성

    Post 엔드포인트는 도서정보를 생성하도록 구현합니다. 도서에 대한 정보는 요청(Request)의 Custom Body에 담긴JSON 데이터를 이용합니다.

    POST http://localhost:8080/books/
    Custom Body

    {
      "book":
      {
        "BOOK_TITLE":"테스트",
        "BOOK_ISBN":"1234567890123",
        "BOOK_AUTHOR":"홍길동",
        "BOOK_PRICE":"10000",
        "BOOK_LINK":"",
        "BOOK_DESCRIPTION":"12345."
      }
    }


    다음과 같이 구현합니다.

    procedure TBooksResource1.Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    const
      SQL_ITEM_INSERT = 'INSERT INTO BOOK(BOOK_TITLE, BOOK_ISBN, BOOK_AUTHOR, BOOK_PRICE, BOOK_LINK, BOOK_DESCRIPTION)' +
                        '  VALUES (:BOOK_TITLE, :BOOK_ISBN, :BOOK_AUTHOR, :BOOK_PRICE, :BOOK_LINK, :BOOK_DESCRIPTION)';
    
    var
      Title, Author, Price, ISBN, Link, Desc: string;
      Json: TJSONValue;
    begin
      JSON := ARequest.Body.GetValue;
    
      Title := JSON.GetValue<string>('book.BOOK_TITLE');
      ISBN := JSON.GetValue<string>('book.BOOK_ISBN');
      Author := JSON.GetValue<string>('book.BOOK_AUTHOR');
      Price := JSON.GetValue<string>('book.BOOK_PRICE');
      Link := JSON.GetValue<string>('book.BOOK_LINK');
      Desc := JSON.GetValue<string>('book.BOOK_DESCRIPTION');
      qryBook.Close;
      qryBook.SQL.Text := SQL_ITEM_INSERT;
      qryBook.ParamByName('BOOK_TITLE').AsString := Title;
      qryBook.ParamByName('BOOK_ISBN').AsString := ISBN;
      qryBook.ParamByName('BOOK_AUTHOR').AsString := Author;
      qryBook.ParamByName('BOOK_PRICE').AsString := Price;
      qryBook.ParamByName('BOOK_LINK').AsString := Link;
      qryBook.ParamByName('BOOK_DESCRIPTION').AsString := Desc;
      qryBook.ExecSQL;
    end;


    구현을 마치고, 프로젝트를 실행합니다. REST Debugger에서 아래와 같이 정보 입력해 테스트 합니다. 요청 후 도서 목록(http://localhost:8080/books/)에서 추가된 것을 확인합니다.


    현재(2017.05) REST Debugger의 Custom Body가 유니코드를 지원하지 않는 버그가 있습니다.(한글을 입력 후 요청 시 캐릭터셋이 맞지 않는 오류가 EMS 패키지에서 발생합니다.)


    조치방법

    1번안 - REST Debugger 소스코드를 수정합니다.

    • C:\Program Files (x86)\Embarcadero\Studio\19.0\source\data\rest\restdebugger\RESTDebugger.dpr(10.2 도쿄 기준) 프로젝트 열기
    • uMain_frm.pas의 762번째 줄을 아래와 같이 수정
      •   memo_RequestBody.Lines.SaveToStream(FRESTParams.CustomBody, TEncoding.UTF8);
    • 컴파일(Project > Options > Delphi Compoiler > Output Directory를 접근 권한이 있는 곳으로 변경)


    2번안 - 아래 RESTDebugger 실행파일을 다운로드 후 테스트 합니다.

    RESTDebugger.zip


    PutItem - 리소스 수정

    PutItem 엔드포인트는 특정한 도서정보를 수정하도록 구현합니다. 도서의 Id는 리소스 접미사를 이용해 파악합니다. 도서에 대한 정보는 요청(Request)의 Custom Body에 담긴JSON 데이터를 이용합니다.

    PUT http://localhost:8080/books/{item}/ - {item}은 유효한 도서일련번호로 변경할 것
    Custom Body

    {
      "book":
      {
        "BOOK_TITLE":"테스트 수정",
        "BOOK_ISBN":"1234567890123",
        "BOOK_AUTHOR":"홍길동",
        "BOOK_PRICE":"10000",
        "BOOK_LINK":"",
        "BOOK_DESCRIPTION":"12345."
      }
    }


    다음과 같이 구현합니다.

    procedure TBooksResource1.PutItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    const
      SQL_ITEM_UPDATE = 'UPDATE BOOK SET ' +
                        '  BOOK_TITLE = :BOOK_TITLE,' +
                        '  BOOK_ISBN = :BOOK_ISBN, ' +
                        '  BOOK_AUTHOR = :BOOK_AUTHOR,' +
                        '  BOOK_PRICE = :BOOK_PRICE,' +
                        '  BOOK_LINK = :BOOK_LINK, ' +
                        '  BOOK_DESCRIPTION = :BOOK_DESCRIPTION ' +
                        ' WHERE ' +
                        '  BOOK_SEQ = :BOOK_SEQ';
    
    var
      BOOK_SEQ: string;
      Title, Author, Price, ISBN, Link, Desc: string;
      Json: TJSONValue;
    begin
      BOOK_SEQ := ARequest.Params.Values['item'];
      JSON := ARequest.Body.GetValue;
    
      Title := JSON.GetValue<string>('book.BOOK_TITLE');
      ISBN := JSON.GetValue<string>('book.BOOK_ISBN');
      Author := JSON.GetValue<string>('book.BOOK_AUTHOR');
      Price := JSON.GetValue<string>('book.BOOK_PRICE');
      Link := JSON.GetValue<string>('book.BOOK_LINK');
      Desc := JSON.GetValue<string>('book.BOOK_DESCRIPTION');
      qryBook.Close;
      qryBook.SQL.Text := SQL_ITEM_UPDATE;
      qryBook.ParamByName('BOOK_TITLE').AsString := Title;
      qryBook.ParamByName('BOOK_ISBN').AsString := ISBN;
      qryBook.ParamByName('BOOK_AUTHOR').AsString := Author;
      qryBook.ParamByName('BOOK_PRICE').AsString := Price;
      qryBook.ParamByName('BOOK_LINK').AsString := Link;
      qryBook.ParamByName('BOOK_DESCRIPTION').AsString := Desc;
      qryBook.ParamByName('BOOK_SEQ').AsString := BOOK_SEQ;
      qryBook.ExecSQL;
    
      if qryBook.RowsAffected = 0 then
        AResponse.RaiseNotFound('Not found', '''' + BOOK_SEQ + ''' is not found');
    end;


    구현을 마치고, 프로젝트를 실행합니다. REST Debugger에서 아래와 같이 정보 입력해 테스트 합니다. 요청 후 도서 목록(http://localhost:8080/books/)에서 수정된 것을 확인합니다.


    DeleteItem - 리소스 삭제

    DeleteItem 엔드포인트는 특정 도서정보를 삭제하도록 구현합니다. 도서의 Id는 리소스 접미사를 이용해 파악합니다. 

    DELETE http://localhost:8080/books/{item}/ - {item}은 유효한 도서일련번호로 변경할 것

    다음과 같이 구현합니다.

    procedure TBooksResource1.DeleteItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    const
      SQL_ITEM_DELETE ='DELETE FROM BOOK WHERE BOOK_SEQ = :BOOK_SEQ';
    
    var
      BOOK_SEQ: string;
    begin
      BOOK_SEQ := ARequest.Params.Values['item'];
    
      qryBook.Close;
      qryBook.SQL.Text := SQL_ITEM_DELETE;
      qryBook.ParamByName('BOOK_SEQ').AsString := BOOK_SEQ;
      qryBook.ExecSQL;
    
      if qryBook.RowsAffected = 0 then
        AResponse.RaiseNotFound('Not found', '''' + BOOK_SEQ + ''' is not found');
    end;


    구현을 마치고, 프로젝트를 실행합니다. REST Debugger에서 아래와 같이 정보 입력해 테스트 합니다. 요청 후 도서 목록(http://localhost:8080/books/)에서 삭제된 것을 확인합니다.


    GetItem - 이미지 제공

    도서정보의 경우 이미지를 제공합니다. 이미지 제공은 상세 정보 하위 "photo" 리소스를 추가해 구현합니다.


    GET http://localhost:8080/books/{item}/photo/ - {item}은 유효한 도서일련번호로 변경할 것

    아래 코드를 참고해 하위 리소스 엔드포인트를 추가합니다.

    [ResourceSuffix('{item}/photo/')]
    procedure GetItemPhoto(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);

    다음과 같이 구현합니다. 

    procedure TBooksResource1.GetItemPhoto(const AContext: TEndpointContext;
      const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    const
      SQL_ITEM_IMAGE ='SELECT BOOK_IMAGE FROM BOOK WHERE BOOK_SEQ = :BOOK_SEQ';
    var
      BOOK_SEQ: string;
      Stream: TMemoryStream;
    begin
      BOOK_SEQ := ARequest.Params.Values['item'];
    
      Stream := TMemoryStream.Create;
      try
        qryBook.Close;
        qryBook.SQL.Text := SQL_ITEM_IMAGE;
        qryBook.ParamByName('BOOK_SEQ').AsString := BOOK_SEQ;
        qryBook.Open;
    
        if qryBook.RecordCount = 0 then
          AResponse.RaiseNotFound('Not found', '''' + BOOK_SEQ + ''' is not found');
    
        TBlobField(qryBook.FieldByName('BOOK_IMAGE')).SaveToStream(Stream);
    
        if Stream.Size = 0 then
          AResponse.RaiseNotFound('Not found', '''' + BOOK_SEQ + ''' is not found');
    
        Stream.Position := 0;
        AResponse.Body.SetStream(Stream, 'image/jpg', True);
      except
        Stream.Free;
        raise;
      end;
    end;

    위 코드는 DB에 저장된 이미지 데이터를 Stream으로 로드 해 그대로 응답합니다. 특히 Content-type을 'image/jpg'로 지정해 데이터가 이미지라는 것을 정의합니다.


    구현을 마치면, 프로젝트를 실행하고 웹브라우저에서 "http://localhost:8080/books/{item}/photo/"를 입력해 화면에 이미지가 표시되는 것을 확인합니다.



    추가 학습할 내용

    REST API 클라이언트 개발하기(REST 클라이언트 이용)

    위에서 작성한 REST API 서버와 연동하는 클라이언트를 개발합니다. REST API를 분석해 조회, 입력, 수정, 삭제, 이미지 수신 기능을 실습위주로 학습합니다.

    • http://blog.hjf.pe.kr/464

    참고/관련 자료





    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder

    [REST API] REST API 이해하기

    2017.05.11 17:17

    이 글에서는 REST 아키텍처를 소개합니다.


    REST는?

    REST(Representational State Transfer)는 인터넷 상의 컴퓨터 시스템간 상호 운용성을 제공하는 방법 중 하나입니다. 


    REST는 HTTP 기반으로 필요한 자원에 접근하는 방식을 정해놓은 네트워크 아키텍처입니다.

    여기서 자원이란, 저장된 데이터(DBMS 등)는 물론, 이미지/동영상/문서(PDF 등)와 같은 파일, 서비스(이메일 전송, 푸쉬 메시지 등) 등을 모두 포함합니다.


    REST는 HTTP의 주요 저자 중 한사람인 로이 필딩의 2000년 박사학위 논문에서 처음 소개되었습니다.

    REST의 제약조건

    아래 REST 제약조건을 준수하는 웹서비스를 RESTful 하다고 합니다.

    • 클라이언트/서버 : 클라이언트와 서버가 각각 역할이 구분되어야 한다. 
      • 서버는 API를 제공하고 API 요청 시 비지니스 로직 처리와 데이터 저장을 책임. 클라이언트는 사용자 인증, 상태(세션, 로그인 정보)관리와 서버 리소스 요청을 책임지는 구조로 역할 구분(상호 의존성을 줄임)
    • 무상태(Stateless) : REST 서버는 작업을 위한 상태정보(세션, 쿠키 등)를 관리하지 않아야 한다.
      • 시스템 영향없이 관리 및 업데이트 가능
    • 캐쉬(Cacheable) : 캐쉬를 제공해야 한다.
      • HTTP 웹표준으로 HTTP가 가진 캐싱 기능이 적용됨
    • 계층화(Layered system) : 서버를 다중 계층으로 구성 할 수 있어야 한다.
      • 비지니스 로직을 수행하는 API 서버와 그 앞단에 사용자 인증, 암호화, 로드밸런싱 등의 계층을 추가해 구조상의 유연성 제공
    • 인터페이스 일관성(Uniform interface)
      • 아키텍처를 단순화하고 분리해 각 부분을 독립적으로 발전 시킬 수 있음


    REST 주요 구성요소

    REST 주요 구성요소 3가지

    • 자원(리소스) : 접근할 대상 - URI를 통해 식별
    • 행위(메소드) : 자원에 대한 행위 - 표준 HTTP 메소드에 따라 자원에 접근
    • 정보(메시지) : 자원에 대한 정보 - HTTP 해더와 바디, 응답코드 활용
    즉, REST는 어떤 자원(리소스)에 어떤 행위(메소드)를 어떻게(메시지) 할지 HTTP 기반으로 정해놓은 아키텍처입니다.

    자원(리소스)

    리소스는 URI를 통해 정의합니다.


    예를 들면, 아래와 같은 URI는 다음과 같은 의미를 갖습니다.

     URI 

     의미 

     http://api.domain.com/books/

     도서정보 콜렉션 

     http://api.domain.com/books/1/ 

     1번 도서 정보 

     http://api.domain.com/books/1/photo/

     1번 도서의 사진


    리소스명은 동사보다 명사를 활용해 어떤 자원인지 표현하는데 집중해야 합니다.(/getBooks/와 같은 리소스는 적절하지 않습니다.)


    슬래시(/)는 계층 관계를 나타내며, URI 앞쪽부터 넓은 의미로 사용합니다.

    일반적으로 계층은 컬렉션(목록) 하위에 아이템을 지정하는 방식으로 정의합니다.


    아래와 같이 URI를 구성할 수 있습니다.

    /sports/soccer/players/1/

    /(컬렉션)/(아이템)/(컬렉션)/(아이템)/


    메소드

    REST에서는 HTTP 메소드를 통해 리소스에 대한 행위를 정의합니다.


    표준 HTTP 메소드 중 Get, Post, Put, Delete를 통해 자원의 CRUD를 정의합니다.

     HTTP 메소드

     자원에 대한 행위

     POST

     자원 생성(Create)

     GET

     자원 조회(Read)

     PUT

     자원 수정(Update)

     DELETE

     자원 삭제(Delete)


    Endpoint

    다음과 같이 메소드와 URI를 이용해 리소스에 접근합니다. URI 별 HTTP 메소드로 구현된 항목을 Endpoint라고 합니다.

     HTTP 메소드 

     URI(자원)

     Endpoint의 행위

     POST

     http://api.domain.com/books/

     새로운 도서정보 생성

     GET

     http://api.domain.com/books/

     도서정보 목록 조회

     GET

     http://api.domain.com/books/1/

     1번 도서정보 조회

     PUT

     http://api.domain.com/books/1/

     1번 도서정보 수정

     DELETE

     http://api.domain.com/books/1/

     1번 도서정보 삭제


    메시지

    REST에서 자원에 대한 정보는 HTTP 바디와 HTTP 해더, 응답 상태코드를 활용해 표현합니다.


    HTTP 바디

    HTTP 바디에 포함된 데이터를 통해 자원에 대한 정보를 전달합니다.

    데이터 포맷으로는 최근 JSON을 많이 사용하는 추세이며, XML과 사용자정의 포맷 등을 정해서 사용할 수도 있습니다.


    조회(GET 메소드) 요청 시 서버는 조건에 맞는 정보를 HTTP 바디에 담아 클라이언트에 응답합니다.

    생성(POST 메소드), 수정(PUT 메소드) 요청 시 클라이언트는 자원에 대한 정보를 요청 HTTP 바디에 담아 서버에 요청합니다.


    HTTP 해더

    HTTP 해더에는 HTTP 바디의 컨텐츠 종류를 명시할 수 있습니다. 해더에 정의된 컨텐츠 타입에 따라 데이터를 분석하도록 구현해야 합니다.

    요청 HTTP 해더는 "Accept" 항목으로, 응답 HTTP 해더는 "Content-type"으로 컨텐츠 타입을 설명합니다.


    몇가지 컨텐츠 타입은 다음과 같습니다.

    • application/json
    • application/xml
    • text/plain
    • image/jpeg
    • image/png
    미디어 타입, 컨텐츠 타입 자세히 보기 : https://ko.wikipedia.org/wiki/미디어타입

    응답 상태코드

    리소스 요청에 대한 결과는 응답 상태코드로 표현할 수 있습니다. 자원 요청 시 1차적으로 응답 상태코드로 결과를 표현하고, 바디 영역의 데이터로 상세 결과(코드 또는 메시지)를 제공할 수 있습니다.


    대표적인 응답 상태코드는 아래와 같습니다.
    • 200 - 요청을 정상 수행
    • 201 - 리소스 생성 요청 성공(Post로 생성 요청 시에 한함)
    • 400 - 요청이 부적절함
    • 401 - 인증되지 않은 상태에서 보호된 리소스 요청
    • 403 - 공개되지 않은 리소스에 접근 요청(인증과 무관)
    • 404 - 존재하지 않는 리소스 요청
    • 406 - 지원하지 않는 미디어타입을 요청
    • 409 - 리소스 상태에 의해 해당 요청을 수행하지 못함
    HTTP 상태코드 자세히 보기 : https://ko.wikipedia.org/wiki/HTTP_상태_코드

    REST API 구현

    REST 기반으로 서비스 API를 구현한 것을 REST API라고 합니다.

    REST API 구현의 특징

    최근 OpenAPI(누구나 사용할 수 있도록 공개된 API: 구글 맵, 공공 데이터 등), 마이크로 서비스(하나의 큰 애플리케이션을 여러 개의 작은 애플리케이션으로 쪼개어 변경과 조합이 가능하도록 만든 아키텍처) 등을 제공하는 업체 대부분은 REST API를 제공합니다. 


    또, 사내 시스템들도 REST 기반으로 시스템을 분산해 확장성과 재사용성을 높여 유지보수 및 운용을 편리하게 할 수 있습니다. 


    REST는 HTTP 표준을 기반으로 구현하므로, HTTP를 지원하는 프로그램 언어로 클라이언트, 서버를 구현할 수 있습니다. 

    즉, REST API를 제작하면 델파이 클라이언트 뿐 아니라, 자바, C#, 웹 등을 이용해 클라이언트를 제작할 수 있습니다. 물론 반대의 방법도 가능합니다.


    델파이로 REST API 구현

    델파이의 HTTP 라이브러리를 이용핸 REST API를 구현할 수 있습니다.

    REST API 구현해 특화된 기술은 서버 측 기술로는 EMS 서버 클라이언트 기술로는 REST 클라이언트 라이브러리가 있습니다.

    서버(Back-End) 프레임워크

    • RAD Server(EMS Server) - REST API Endpoint 제공
    • WebBroker - RAD 스튜디오(델파이, C++빌더) 웹 개발 프레임워크
    • DataSnap - RAD 스튜디오 멀티티어 개발 프레임워크, TCP/IP, HTTP 프로토콜 제공
    • Delphi MVC Framework - 오픈소스 웹서비스 개발 프레임워크
    • mORMot - 오픈소스 SOA, ORM 개발 프레임워크

    클라이언트(Front-End) 프레임워크

    • REST Client - REST 클라이언트 프레임워크
    • Net HTTP Client - 네이티브 HTTP 클라이언트 프레임워크
    • Indy Library - 범용 네트워크 라이브러리(HTTP 클라리언트, 서버 제공)


    추가 학습할 내용

    EMS 패키지 프로젝트 시작하기

    EMS 패키지는 REST API 리소스와 엔드포인트 개발을 지원합니다. EMS 패키지 프로젝트를 만들고, 배포하는 내용을 학습할 수 있습니다.

    REST API 서버 개발하기(엔드포인트 구현, RAD 서버 이용)

    EMS 패키지를 이용해 REST API를 제공하는 서버를 개발합니다. 미리 준비된 DB를 이용해 조회, 입력, 수정, 삭제, 이미지 제공 등의 기능을 실습위주로 학습합니다.

    REST API 클라이언트 개발하기(REST 클라이언트 이용)

    위에서 작성한 REST API 서버와 연동하는 클라이언트를 개발합니다. REST API를 분석해 조회, 입력, 수정, 삭제, 이미지 수신 기능을 실습위주로 학습합니다.


    참고 링크



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder

    [RAD서버] EMS 패키지 프로젝트 시작하기

    2017.04.27 17:10

    이 글에서는 EMS 서버에 필요한 기능을 추가할 수 있는, EMS 패키지 프로젝트 개발을 시작하는 과정을 안내합니다.

    EMS 패키지란?


    EMS 서버는 REST API 기반으로 리소스를 제공하는 미들웨어 서버입니다. EMS 서버가 제공하는 리소스는 기본 제공 리소스(사용자, 그룹, 푸시 등)와 사용자가 직접 개발 후 추가 할 수 있는 사용자 제작 리소스가 있습니다.


    사용자 제작 리소스는 RAD 스튜디오(델파이 또는 C++빌더)에서 EMS 패키지 프로젝트를 이용해 개발해, EMS 서버에 플러그인 형태로 추가할 수 있습니다.


    이 글에서는 다음 내용을 안내합니다.

    • EMS 패키지 프로젝트 생성
    • EMS 패키지 실행
    • EMS 패키지 배포 방법
    • 추가 학습할 내용

    EMS 패키지 프로젝트 생성

    프로젝트 생성

    File > New > Other 메뉴 선택 후 Delphi Projects > EMS 메뉴에서 EMS Package를 선택 후 [OK] 버튼을 누릅니다.


    EMS 패키지 마법사에서 패키지 종류를 선택합니다.

    리소스 제공을 위해 "Create package with resource" 항목 선택 후 [Next] 버튼을 클릭합니다.


    제공할 리소스 이름 입력 후, 파일 타입을 선택 후 [Next] 버튼을 클릭합니다.

    • Data Module : 리소스에서 쿼리 컴포넌트와 같은 논비주얼 컴포넌트를 사용할 경우
    • Unit : 코드만으로 리소스를 구성할 경우


    리소스에서 사용할 EndPoint 종류를 선택하고 [Finish] 버튼을 클릭합니다. 이 과정에서는 모든 EndPoint를 선택합니다.



    프로젝트가 만들어지면, File > Save all 메뉴를 눌러 원하는 경로에 프로젝트를 저장합니다.(저는 프로젝트 명만 HelloEMS으로 지정했습니다.)


    EMS 패키지 실행 및 테스트

    EMS 서버 환경설정

    EMS 서버를 최초 실행하는 경우 환경설정 단계를 수행합니다. 이 단계는 최초 실행 시에만 진행됩니다.


    Run (F9) 버튼을 누르면 아래 그림과 같이 EMS 개발 서버가 실행됩니다. EMS 서버 최초 실행 시 환경정보를 설정하는 과정이 진행됩니다. [Yes] 버튼을 클릭합니다.


    환경설정 첫번째 단계에서는 EMS 데이터베이스를 설정합니다. 

    (EMS 데이터베이스는 인터베이스를 사용합니다. 만약,  인터베이스를 설치 하지 않았다면, 현재 단계를 취소하고, 바로 아래의 "인터베이스 추가 설치" 항목을 참고해 인터베이스를 먼저 설치하고 다시 시도하시기 바랍니다.)

    • Server Instance : 인터베이스 서버의 인스턴스 이름 입력(기본 : gds_db)
      • 확인방법 : InterBase Server Manager 실행(시작 > 모든 프로그램 > Embarcadero Interbase XE7 > InterBase Server Manager 선택)  후 확인
    • DB File Name, DB File Directory, DB User Name, DB Password 입력


    인터베이스 추가 설치

    RAD 스튜디오 설치 시 추가 옵션으로 인터베이스 개발자 에디션을 설치할 수 있습니다.(기본 옵션에 포함)

    만약, 추가 옵션에서 인터베이스를 선택하지 않았다면, RAD 스튜디오 "메인메뉴 > Tools > Manage Platofmrs..." 메뉴를 선택해 인터베이스 개발자 에디션을 추가 설치합니다.


    샘플 데이터를 선택하고 [>> Next] 버튼 클릭합니다.


    EMS 콘솔(모니터링 웹사이트) 로그인 계정을 입력하고 [>>Next] 버튼을 클릭합니다.

    (향후 EMS 서버 설정파일에서 변경할 수 있습니다.)


    최종적으로 확인하고 [Finish] 버튼을 클릭합니다.


    개발 목적으로 EMS 서버를 라이선스 없이 사용할 경우 일부기능이 제한됩니다. [Yes] 버튼을 클릭합니다.


    EMS 패키지 실행

    Run (F9) 버튼을 눌러 EMS 패키지 프로젝트를 실행하면, 아래 그림과 같이 EMS 개발 서버가 실행 후 자동 시작됩니다.


    EMS 개발 서버는 EMS 패키지(*.bpl)를 동적 로드해 서비스하는 역할을 합니다.

    EMS 개발 서버는 RAD 스튜디오 실행파일과 같은 경로에서 제공하며, 

    • EMS 개발 서버 실행파일 경로 : C:\Program Files (x86)\Embarcadero\Studio\19.0\bin\EMSDevServer.exe

    EMS 패키지의 Host application(메인메뉴 > Run > Parameters...)으로 지정되어 있습니다.



    EMS 개발 서버의 화면 로그를 살펴보면 아래와 같은 로그가 있습니다.

    {"Loading":{"Filename":"C:\Users\Public\Documents\Embarcadero\Studio\19.0\Bpl\HelloEms.bpl","Thread":428}}

    {"RegResource":{"Resource":"helloems","Endpoints":["Get","GetItem","GetItemPhoto","Post","PutItem","DeleteItem"],"Thread":428}}


    앞에서 개발한 EMS 패키지(HelloEMS.bpl)을 로드하고, EMS 패키지의 리소스(helloems)와 엔드포인트가 등록됩니다.

    (패키지를 등록하는 내용은 뒤에서 다시 소개합니다.)


    웹브라우저를 열고, "http://(서버IP):(포트번호)/(리소스 이름)/" 주소를 엽니다.


    EMS 패키지 생성하는 과정과 실행하는 과정을 살펴봤습니다.

    아래에서 EMS 패키지의 소스코드 구조와 배포방법을 간단히 살펴봅니다.


    JSON 포맷의 데이터 제공과 데이터셋 연결하는 내용은 아래 "추가 학습할 내용"을 통해 학습하시기 바랍니다.


    참고>

    인터넷 익스플로러에서 json 파일을 다운로드 된다면, 레지스트리를 수정해 웹브라우저에 문자열이 표시되도록 조치할 수 있습니다.


    [HKEY_CLASSES_ROOT\MIME\Database\Content Type\application/json] 

    "CLSID"="{25336920-03F9-11cf-8FD0-00AA00686F13}" 

    "Encoding"=hex:08,00,00,00


    EMS 패키지 소스코드 구조 소개

    생성된 프로젝트는 아래와 같은 구조로 소스코드가 자동 생성됩니다.



    [ResourceName('helloems')]

    리소스 이름을 지정하는 특성(Attribute)입니다. 구문 바로아래 정의된 클래스(THelloemsResource1)의 리소스 이름을 지정합니다.

    즉, 'helloems' 리소스 호출 시 THelloemsResource1 클래스에 정의된 함수가 호출됩니다.


    Get, GetItem, Post, PutItem, DeleteItem

    HTTP 메소드 Get, Post, Put, Delete와 매핑되는 메소드들 입니다. REST 규약에 맞춰 Get은 읽기, Post는 쓰기, Put은 수정, Delete는 삭제 기능을 구현하면 됩니다.


    HTTP 메소드 중 Get의 경우 Get과 GetItem 두개의 함수를 제공합니다. 일반적으로 Get 함수는 리소스 목록을 GetItem은 특정한 리소스의 상세정보를 제공합니다. 뒤쪽에 Item이 붙은 함수들(GetItem, PutItem, DeleteItem)은 리소스의 특정 항목에 대해 처리(조회, 수정, 삭제)합니다.


    [ResourceSuffix('{item}')]

    리소스 항목을 가져올 파라메터 이름 지정하는 특성(Attribute)입니다. 

    예를 들어 "GET http://localhost:8080/helloems/101/" 요청의 경우 "helloems" 리소스 중 id가 101인 항목의 정보를 제공합니다. 리소스 id를 가져올 파라메터 이름을 지정하는 특성인 ResourceSuffix 입니다.


    GetItem 구현부에서는 아래와 같은 코드로 리소스 id를 가져올 수 있습니다.

      LItem := ARequest.Params.Values['item'];

    참고로, 리소스 하위의 리소스에 접근하는 경우 아래와 같이 정의해 접근할 수 있습니다.

    [ResourceSuffix('{item}')]
    procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [ResourceSuffix('{item}/photo/{id}')]
    procedure GetItemPhoto(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);


    EMS 패키지 배포 방법

    EMS 패키지 생성 경로

    EMS 패키지 기본 생성 경로는 패키지와 마찬가지로 "C:\Users\Public\Documents\Embarcadero\Studio\19.0\Bpl"(10.2 도쿄 기준)에 생성됩니다.


    EMS 패키지 생성 경로는 프로젝트 옵션의 Package output directory에서 지정할 수 있습니다.

    경로를 선택하거나, 상대경로를 지정할 수 있습니다. 아래 그림과 같이 환경변수를 이용해 플랫폼, 빌드옵션 별로 파일을 생성할 수 있습니다.


    EMS 패키지 등록

    EMS 패키지를 여러개 개발해 테스트하거나 운영하려면 EMS 서버 환경파일(emsserver.ini)에 패키지를 추가해야합니다.

    EMS 서버 환경파일의 경로는 EMS 환경설정 단계에서 지정되며, 기본 값으로 "C:\Users\Public\Documents\Embarcadero\EMS"을 사용합니다.


    환경파일의 경로는 레지스트리(HKEY_CURRNET_USER\Software\Embarcadero\EMS\ConfigFile)를 통해 재설정 할 수 있습니다.


    환경파일(emsserver.ini)을 열고, [Server.Packages] 섹션으로 이동합니다.

    아래 그림과 같이 "경로"="패키지 설명" 형식으로 패키지 정보를 추가합니다.


    환경파일 수정에 대한 자세한 내용은 다음 링크에서 자세히 확인할 수 있습니다.


    추가 학습할 내용

    EMS 패키지 - JSON 형식의 REST EndPoint 제공



    EMS 패키지 - 데이터셋 기반 REST EndPoint 제공


    EMS 서버 운영환경에 설치하기(독립형 실행파일)

    독립현 실행파일 방식의 EMS 서버를 운영환경(윈도우즈 서버)에 설치하는 내용을 설명합니다.


    참고/관련 자료



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder

    [디자인(설계)] 팩토리 메소드 패턴과 Class 타입을 활용 객체 생성 시 참조 관계 제거

    2017.04.25 16:30

    데브맥스 프레임워크를 개발하고 있습니다. 데브맥스에서 사용하는 기술을 틈틈히 정리 및 공유하려 합니다.

    (데브맥스 프레임워크에 대한 소개는 다음에 진행하겠습니다.)


    이번 글에서는 팩토리 메소드 패턴(부모 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴)과 클래스 타입을 활용해, 여러가지 객체 생성 시 참조 관계를 제거하는 방법을 소개합니다.


    배경

    1) 메인UI에서 여러가지 서브UI를 동적으로 생성하고 싶다.

    2) 메인UI 소스에서 서버UI 소스 참조시 서브UI가 많아질 수록 메인UI 소스가 복잡해 진다.


    과제

    메인UI에서 서브UI Id로 서브UI 객체를 생성할 수 있어야 한다.

    메인UI 소스에서 서브UI 소스를 직접 참조하지 않아야 한다.

    서브UI가 늘어나도 메인UI 소스는 변경되지 않아야 한다.


    방안

    팩토리 메소드 패턴 활용

    Class 타입을 이용해 확장성 제공


    구현

    아키텍처

    위 그림과 같이 서브UI들은 ClassTypeFactory에 등록하고, 메인UI는 ClassTypeFactory를 참조해 객체를 생성하는 구조이다.


    클래스 타입 지정

      TViewItemClass = class of TControl;
      TViewItemClassInfo = record
        Id: string;
        ViewItemClass: TViewItemClass;
    
        constructor Create(AId: string; AItemClass: TViewItemClass);
      end;
    서브UI(TViewItem)의 클래스 타입(TViewItemClass)을 지정한다.
    클래스의 종류는 현재 TControl로 정의 되어 있지만 폼(TForm), 여러분들이 만든 객체(또는 컴포넌트)  등으로 정의해 사용할 수 있다.
    클래스 정보 구조체를 선언한다.

    Factory 클래스

    type
      TViewItemClass = class of TControl;
      TViewItemClassInfo = record
        Id: string;
        ViewItemClass: TViewItemClass;
    
        constructor Create(AId: string; AItemClass: TViewItemClass);
      end;
    
      TViewItemFactory = class
      private
        class var FInstance: TViewItemFactory;
      private
        FViewItems: TDictionary;
        function GetList: TArray;
      public
        constructor Create;
        destructor Destroy; override;
    
        procedure Regist(AId: string; AItemClass: TViewItemClass);
        function GetClass(AId: string): TViewItemClass;
        function CreateControl(AId: string): TControl;
    
        property List: TArray read GetList;
    
        class function Instance: TViewItemFactory;
        class procedure ReleaseInstance;
      end;
    
    { TViewItemFactory }
    
    class function TViewItemFactory.Instance: TViewItemFactory;
    begin
      if not Assigned(FInstance) then
        FInstance := Create;
      Result := FInstance;
    end;
    
    class procedure TViewItemFactory.ReleaseInstance;
    begin
      if Assigned(FInstance) then
        FInstance.Free;
    end;
    
    constructor TViewItemFactory.Create;
    begin
      FViewItems := TDictionary.Create;
    end;
    
    function TViewItemFactory.CreateControl(AId: string): TControl;
    var
      ItemClass: TViewItemClass;
    begin
      ItemClass := GetClass(AId);
      if not Assigned(ItemClass) then
        Exit(nil);
      Result := ItemClass.Create(nil);
    end;
    
    destructor TViewItemFactory.Destroy;
    begin
      FViewItems.Free;
    
      inherited;
    end;
    
    function TViewItemFactory.GetClass(AId: string): TViewItemClass;
    var
      Info: TViewItemClassInfo;
    begin
      Result := nil;
      if FViewItems.TryGetValue(AId, Info) then
        Result := Info.ViewItemClass;
    end;
    
    function TViewItemFactory.GetList: TArray;
    begin
      Result := FViewItems.Values.ToArray;
    end;
    
    procedure TViewItemFactory.Regist(AId: string; AItemClass: TViewItemClass);
    begin
      FViewItems.Add(AId, TViewItemClassInfo.Create(AId, AItemClass));
    end;
    
    initialization
    finalization
      TViewItemFactory.ReleaseInstance;

    Factory 클래스가 싱글톤 패턴이 적용되어 약간 복잡하다

    주요 메소드는 클래스 타입을 등록하는 Regist 메소드와 클래스 타입을 제공하는 GetClass 메소드이다.


    서브UI에서 클래스 등록

    type
      TFrame1 = class(TFrame)
        Label1: TLabel;
        Circle1: TCircle;
      private
        { Private declarations }
      public
        { Public declarations }
      end;
    
    implementation
    
    uses
      ClassTypeFactoryType;
    
    {$R *.fmx}
    
    initialization
      TViewItemFactory.Instance.Regist('프레임1', TFrame1);

    서브UI에서는 ClassTypeFactory(TViewItemFactory)에 id('프레임1')와 클래스 타입(TFrame1)을 등록한다.


    메인UI에서 서브UI 생성

    uses
      ClassTypeFactoryType;
    
    {$R *.fmx}
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      Info: TViewItemClassInfo;
      Item: TListBoxItem;
    begin
      for Info in TViewItemFactory.Instance.List do
      begin
        Item := TListBoxItem.Create(ListBox1);
        Item.Parent := ListBox1;
        Item.Text := Info.Id;
      end;
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    var
      Id: string;
      ItemClass: TViewItemClass;
    begin
      if Assigned(FActiveControl) then
        FActiveControl.Free;
    
      Id := ListBox1.Selected.Text;
      ItemClass := TViewItemFactory.Instance.GetClass(Id);
      FActiveControl := ItemClass.Create(Self);
      FActiveControl.Parent := Layout1;
      FActiveControl.Align := TAlignLayout.Client;
    end;

    메인UI에서는 ClassTypeFactory에 등록된 서브UI를 참조할 수 있다.

    메인UI는 Id 값으로 서브UI 클래스(TViewItemClass)를 가져와 동적으로 생성할 수 있다.

    메인UI 소스에서는 서브UI 소스를 참조하지 않는다.(결합도를 낮출 수 있다.)


    샘플코드

    다운로드 :  01_ClassTypeFactory.zip

    활용 : https://github.com/devgear/DevMax/blob/master/View/DevMax.View.Factory.pas



    PS

    짧은 시간을 할애해 정보를 자주 올리기 위해 두서없이 글을 작성했습니다. 혹시 이해되지 않는 내용이 있으면 댓글로 질문주시면 앞으로 보강하도록 하겠습니다.

    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Team Thoth/DevMax

    [환경설정] 아마존 EC2 이용해 리눅스 서버 환경 구축하기

    2017.04.20 12:02

    이 글에서는 아마존 EC2 기반으로 리눅스 서버 환경을 구축하는 내용을 안내합니다.

    RAD 스튜디오 10.2 도쿄 버전부터 리눅스 개발을 지원합니다. WebBroker, 데이테스냅, EMS 서버, 소켓 서버 용 테스트 및 서비스에 사용할 수 있습니다.


    아마존 EC2

    아마존 EC2란?

    아마존 EC2는 클라우드 기반으로 서버 인프라를 제공합니다. 웹 페이지를 이용해 필요한 플랫폼과 용량을 선택해 서버 인스턴스를 실행하고 접속해 테스트 및 서비스 할 수 있습니 - 아마존 EC2 자세히보기


    아마존 EC2를 사용하는 이유

    AWS 프리 티어를 이용해 12개월 동안 무료로 체험할 수 있습니다. - AWS 프리 티어 자세히보기



    이 글에서 다루는 내용은 아래와 같습니다.

    • Ubuntu Server 16.04 LTS 인스턴스 생성
    • 보안설정(필요한 포트번호 오픈)
    • 터미널로 연결
    • 리눅스 개발환경 설정

    준비

    AWS 가입(게정생성)

    EC2를 사용하기 위해서는 AWS 계정이 필요합니다.

    이미 많은 글들에서 가입 절차를 소개하고 있으니 아래 링크를 참조해 가입하시기 바랍니다.

    (참고로 가입 시 마스터 또는 비자 카드가 필요합니다.)

    인스턴스 생성

    리눅스 서버 인스턴스를 추가하는 과정을 설명합니다.


    아마존 웹서비스 로그인


    EC2 서비스 선택

    • 필터에 ec2 입력 후 선택


    인스턴스 목록 표시

    • 왼쪽 메뉴 중 Instances 항목 선택


    가까운 지역 선택

    • 상단 우측의 지역을 누르고, 가까운 지역 선택
      (저는 Asia Pacific(Seoul)을 선택했습니다.)


    새로운 인스턴스 추가

    • [Launch Instance] 버튼 클릭


    인스턴스 이미지 선택

    • 적합한 항목의 [Select] 버튼 클릭
      (저는 "Ubuntu Server 16.04 LTS (HVM), SSD Volume Type"(64 - bit)를 선택 했습니다.)



    인스턴스 유형 선택

    • 적합한 인스턴스 유형 선택
    • [Review and Launch] 버튼 클릭


    내용 확인

    • 내용 확인 후 [Launch] 버튼 클릭
    • 주의 문구는 무시합니다.(보안설정은 뒤에서 다시 진행합니다.)


    Key Pair 생성 및 다운로드

    중요: 해당 키페어 파일(*.pem)은 터미널 접속 시 개인키(*.ppk) 생성에 사용합니다. 해당 파일을 분실하지 않도록 주의하시기 바랍니다.
    • "Create a new key pair" 항목 선택
    • Key pair name 입력
    • [Download key Pair] 버튼 클릭 해 다운로드(*.pem 파일이 다운로드 됩니다.)


    보안설정

    애플리케이션 서버에서 사용할 포트번호를 Inbound 규칙에 추가하는 절차를 설명합니다.

    인스턴스의 Security Group 확인

    • 인스턴스 목록 항목 중 가장 오른쪽 항목에서 Security Group을 확인합니다.
      (저는 launch-wizard-2 입니다.)

     Security Groups 메뉴 선택

    • 왼쪽 메뉴에서 Network & Security > Security Groups 메뉴 클릭


    Security Group 선택

    • 인스턴스에 설정된 Security Group을 선택합니다.
      (저는 launch-wizard-2을 선택)



    Inbound 탭에서 Inbound 규칙 수정

    • 아래 탭 중 Inbound 탭 선택
    • [Edit] 버튼 클릭


    Inbound 규칙 편집

    • [Add Rule] 버튼 클릭
    • 필요한 규칙을 추가합니다.

      • EMS 서버, 데이터스냅 HTTP, WebBroker의 경우 "Custom TCP Rule / TCP / 8080" 규칙 추가

      • PAServer(Platform Assistance Server: 원격 디버깅 및 SDK 취득) 포트번호 "64211" 규칙 추가
      • 데이터스냅 TCP/IP의 경우 "Custom TCP Rule / TCP / 211" 규칙 추가
      • 리눅스 상에서 FTP 서비스 이용 시 "1024 - 1048", "20 - 22")" 규칙 추가
      • 기타 서버에서 사용할 포트번호를 추가합니다.
    • [Save] 버튼을 눌러 저장


    터미널로 연결

    리눅스 서버 인스턴스에 터미널을 이용해 연결하는 절차를 안내합니다.

    다음 내용은 아마존 웹서비스 도움말을 참고해 작성되었습니다.

    인스턴스 선택 후 연결(연결 방법 확인)

    • 인스턴스 목록에서 인스턴스 선택
    • [Connect] 버튼 클릭
    • Connect To Your Instance 팝업창 표시


    접속 주소(Public DNS) 확인

    • 4번 단계의 접속 주소(Public DNS) 확인


    접속에 필요한 소프트웨어 설치

    리눅스에 터미널에 접속할 SSH Client와 파일 전송을 위한 SCP(Secure Copy) 프로그램을 아래 링크에서 다운로드 후  설치합니다.


    개인 키 변환

    PuTTY는 위에서 다운로드 받은 키페어 파일(*.pem)을 지원하지 않습니다. PuTTYgen을 통해 PuTTY에서 지원하는 개인 키(*.ppk)로 변환합니다.

    • PuTTYgen을 시작합니다.(시작 > 모든 프로그램 > PuTTY > PuTTYgen 선택)
    • Type of key to generate 에서 RSA를 선택
    • [Load] 버튼을 눌러 키페어파일을 선택(파일 필터를 All Files로 변경 후 선택)
    • [Save private key] 버튼을 누르고 개인 키(*.ppk)를 저장합니다.


    터미널 접속

    PuTTY를 이용해 리눅스 서버 터미널로 접속합니다.

    • PuTTY를 시작합니다.(시작 > 모든 프로그램 > PuTTY > PuTTY 선택)
    • Host Name에 "ubuntu@{접속주소}"를 입력합니다.
      • 기본 계정명은 "ubuntu" 입니다.
      • 포트 번호는 22 입력
      • 연결 타입은 "SSH" 선택
    • [Category] 탭에서 Connection > SSH > Auth 메뉴를 선택합니다.
    • private key file for authentication 항목에서 [Browse...] 버튼을 눌러 개인 키(*.ppk)를 선택합니다.
    • [Open] 버튼을 눌러 접속을 시도합니다.
      (최초 1회 신뢰할 수 있는지 묻는 보안 알림 대화상자가 표시되면 [Yes]를 선택 합니다.)


    파일 전송을 위한 SCP 연결

    WinSCP를 이용해 SCP 연결해 파일을 전송할 수 있는 환경을 확인합니다.

    • WinSCP를 실행합니다.
    • 로그인 창에서 접속 정보를 입력합니다.
      • 호스트 이름에 (접속주소)를 입력합니다.
      • 사용자 이름에 "ubuntu"를 입력합니다.
    • [고급] 버튼을 눌러 개인 키를 설정합니다.
      • 개인 키 파일 항목에서 [...] 버튼을 눌러 개인 키 파일을 선택합니다.
      • [확인] 버튼을 눌러 적용합니다.
    • 로그인 화면에서 [저장] 버튼을 누르고 세션 이름을 지정해 저장합니다.
    • [로그인] 버튼을 눌러 접속합니다.

    리눅스 개발환경 설정

    RAD 스튜디오에서 리눅스 애플리케이션 개발하기 위한 환경을 설정합니다.


    자세한 내용은 다음 링크를 통해 설명합니다.


    관련링크



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder AWS, 델파이, 리눅스

    델파이 앱에서 움직이는 GIF 표현하기(소스 포함)

    2017.04.20 10:20

    한 중국인 델파이 개발자 블로그(武稀松(wr960204)的博客)에서 움직이는 GIF를 파이어몽키에서 사용할 수 있는 소스코드가 공개되어 공유합니다.



    위 GIF는 샘플 프로젝트로 돌려본 화면입니다. 

    첫번째 버튼은 GIF 파일 로드, 두번째 버튼으로 시작/종료 입니다. 세번째, 네번째는 느리게 빠르게 동작하는 버튼입니다.


    이 소스코드는 윈도우32, 윈도우64, 안드로이드 그리고 리눅스까지 지원합니다.

    더 자세한 내용(중국어)과 소스코드 다운로드는 아래 링크를 참고하세요.


    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 파이어몽키

    [환경설정] 아마존 EC2 이용해 윈도우 서버 환경 구축하기

    2017.04.06 12:42

    이 글에서는 아마존 EC2 기반으로 윈도우 서버 환경을 구축하는 내용을 안내합니다.

    데이터 스냅 서버, EMS 서버, WebBroker 서버, 소켓 서버 용 테스트 및 서비스에 사용할 수 있습니다.


    아마존 EC2

    아마존 EC2란?

    아마존 EC2는 클라우드 기반으로 서버 인프라를 제공합니다. 웹 페이지를 이용해 필요한 플랫폼과 용량을 선택해 서버 인스턴스를 실행하고 접속해 테스트 및 서비스 할 수 있습니 - 아마존 EC2 자세히보기


    아마존 EC2를 사용하는 이유

    AWS 프리 티어를 이용해 12개월 동안 무료로 체험할 수 있습니다. - AWS 프리 티어 자세히보기



    이 글에서 다루는 내용은 아래와 같습니다.

    • Windows 2012 R2 인스턴스 생성
    • 보안설정(서버에서 사용하는 포트번호 오픈)
    • 원격 데스크탑으로 연결

    준비

    AWS 가입(게정생성)

    EC2를 사용하기 위해서는 AWS 계정이 필요합니다.

    이미 많은 글들에서 가입 절차를 소개하고 있으니 아래 링크를 참조해 가입하시기 바랍니다.

    (참고로 가입 시 마스터 또는 비자 카드가 필요합니다.)

    인스턴스 생성

    윈도우즈 서버 인스턴스를 추가하는 과정을 설명합니다.


    아마존 웹서비스 로그인


    EC2 서비스 선택

    • 필터에 ec2 입력 후 선택


    인스턴스 목록 표시

    • 왼쪽 메뉴 중 Instances 항목 선택


    가까운 지역 선택

    • 상단 우측의 지역을 누르고, 가까운 지역 선택
      (저는 Asia Pacific(Seoul)을 선택했습니다.)


    새로운 인스턴스 추가

    • [Launch Instance] 버튼 클릭


    인스턴스 이미지 선택

    • 적합한 항목의 [Select] 버튼 클릭
      (저는 "Microsoft Windows Server 2012 Base"(64 - bit)를 선택 했습니다.)


    인스턴스 유형 선택

    • 적합한 인스턴스 유형 선택
    • [Review and Launch] 버튼 클릭


    내용 확인

    • 내용 확인 후 [Launch] 버튼 클릭
    • 주의 문구는 무시합니다.(보안설정은 뒤에서 다시 진행합니다.)


    Key Pair 생성 및 다운로드

    중요: 인스턴스(서버) 접속 시 비밀번호 인증에 사용할 키파일을 로컬 저장소에 저장합니다. 해당 파일을 분실하지 않도록 주의하시기 바랍니다.
    • "Create a new key pair" 항목 선택
    • Key pair name 입력
    • [Download key Pair] 버튼 클릭 해 다운로드


    보안설정

    애플리케이션 서버에서 사용할 포트번호를 Inbound 규칙에 추가하는 절차를 설명합니다.

    인스턴스의 Security Group 확인

    • 인스턴스 목록 항목 중 가장 오른쪽 항목에서 Security Group을 확인합니다.
      (저는 launch-wizard-1 입니다.)


     Security Groups 메뉴 선택

    • 왼쪽 메뉴에서 Network & Security > Security Groups 메뉴 클릭
    • 인스턴스의 Security Groups 항목을 확인합니다.


    Security Group 선택

    • 인스턴스에 설정된 Security Group을 선택합니다.
      (저는 launch-wizard-1을 선택)


    Inbound 탭에서 Inbound 규칙 수정

    • 아래 탭 중 Inbound 탭 선택
    • [Edit] 버튼 클릭


    Inbound 규칙 편집

    • [Add Rule] 버튼 클릭
    • 필요한 규칙을 추가합니다.

      • 데이터스냅 TCP/IP의 경우 "Custom TCP Rule / TCP / 211" 규칙 추가

      • EMS 서버, 데이터스냅 HTTP, WebBroker의 경우 "Custom TCP Rule / TCP / 8080" 규칙 추가

      • 기타 서버에서 사용하는 포트번호를 추가합니다.

    원격 데스크탑 연결

    윈도우즈 서버 인스턴스에 원격 데스크탑 연결하는 절차를 설명합니다.

    인스턴스 선택 후 연결

    • 인스턴스 목록에서 인스턴스 선택

    • [Connect] 버튼 클릭

    • Connect To Your Instnace 팝업창 표시


    비밀번호 가져오기

    • [Get Password] 버튼 클릭


    Key pair 파일 선택

    • 인스턴스 생성 시 다운로드 받은 Key Pair 파일을 로컬 저장소에서 선택
    • [Decrypt Password] 버튼 클릭


    접속 정보 확인

    • 화면에 표시된 접속 정보 확인


    원격 데스크톱 연결

    • 원격 데스크톱 연결 실행
    • 컴퓨터 항목에 Public DNS 항목 입력(복사 > 붙여넣기)


    사용자 자격 증명 입력

    • User name, Password 입력
    • [확인] 버튼 클릭


    서버 접속 확인


    윈도우즈 방화벽 규칙 추가

    관련링크



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder

    [RAD서버] EMS 서버 운영환경에 설치하기(독립형 실행파일)

    2017.04.06 09:46

    이 글에서는 독립형 실행파일(Stand alone) 방식 EMS 서버를 운영환경(윈도우즈 서버)에 설치하는 내용을 설명합니다.

    EMS 서버

    EMS 서버란?

    EMS 서버는 HTTP 기반 REST API를 제공하는 웹서버입니다.


    REST API란?


    더보기


    델파이 클라이언트 뿐 아니라 대부분의 클라이언트 프로그램은 REST API를 통해 EMS 서버에 접속 해 필요한 데이터(리소스)를 조회하거나 추가, 수정, 삭제 할 수 있습니다.


    EMS 서버는 자주 사용하는 리소스(사용자 정보, 사용자 그룹 정보, 설치정보, 모바일 원격 푸시 등)가 내장되어 있습니다.(일명 턴키 방식 미들웨어라 칭합니다.)

    추가로 필요한 업무 로직 등의 리소스는 개발자가 직접 EMS Package를 작성할 수 있습니다. EMS Package는 EMS 서버에 등록해 지속적으로 기능을 추가 확장할 수 있습니다.


    EMS 서버 설치 방식

    운영환경에 EMS 서버를 운영환경에 설치하는 방식은 3가지 입니다.

    1, 독립형 실행파일(Stand alone) 방식으로 설치

    2, ISAPI(인터넷 정보 서비스: IIS) 기반으로 설치

    3, 아파치 서버 기반으로 설치


    이 글에서 독립형 실행파일 방식으로 설치하는 내용을 설명합니다.

    ISAPI 또는 아파치 서버 기반으로 설치하는 내용은 엠바카데로 기술문서를 통해 참고하시기 바랍니다.

    이 글은 델파이 10.2 도쿄 기준으로 작성되었습니다. 다른 버전의 델파이 또는 C++빌더의 경우 버전 번호(10.2 도쿄: 19.0)가 다를 수 있습니다.


    다음 순서로 진행합니다.

    • 윈도우즈 서버 준비하기
    • 인터베이스 데이터베이스 설치
    • 필요한 파일을 개발환경에서 운영환경으로 복사
    • 환경 설정
    • 직접 개발한 EMS 패키지 추가하기
    • 실행 및 테스트


    윈도우즈 서버 준비하기

    이 글에서는 EMS 서버를 윈도우즈 서버에 설치하는 내용을 설명합니다.
    참고로, EMS 서버는 윈도우 서버와 리눅스 서버(RAD 스튜디오 10.2 도쿄 부터)를 지원합니다.

    윈도우즈 서버 준비

    사내 또는 IDC에 구성된 윈도우즈 서버를 준비합니다. 원격 데스크톱 연결과 파일 복사, 외부에서 연결(포트: 8080)이 가능해야 합니다.

    만약, 윈도우즈 서버가 준비되지 않았다면, 다음 글을 통해 아마존 웹서비스를 이용해 테스트 용 윈도우즈 서버 환경을 먼저 구성할 수 있습니다.

    윈도우즈 서버 접속

    원격 데스크톱을 이용해 윈도우즈 서버에 접속합니다.
    (저는 아마존 EC2를 이용 Windows 2012 R2 64bit / 영문 서버로 접속합니다.)

    인터베이스 데이터베이스 설치

    EMS 서버는 내부 데이터 관리를 인터베이스 데이터베이스를 이용합니다.


    RAD 서버용 인터베이스 다운로드 및 설치

    RAD 서버 배포용 InterBase XE7을 다음 링크에서 다운로드 후 설치파일을 이용해 설치합니다.

    라이선스 입력 단계에서 RAD 서버 라이선스를 입력합니다.


    InterBase XE7 for RAD Server deployment : https://cc.embarcadero.com/item/30669


    필요한 파일을 개발환경에서 운영환경으로 복사

    EMS 실행파일과 환경파일, DB파일을 개발 PC에서 원격서버로 복사하는 과정을 설명합니다.

    EMS 서버 폴더 지정 및 하위 폴더 생성

    원격 서버에서 EMS 서버를 설치할 경로를 지정합니다.(저는 C:\EMSServer로 지정했습니다. 원하는 경로로 지정해도 됩니다.)

    하위 디렉토리 3개를 아래와 같이 생성합니다.

    • EMSDevServer : EMS Development Server 실행파일과 필요한 바이너리 파일을 복사할 경로
    • EMSPackages : EMS Package를 배포할 경로
    • EnvFile : 환경파일(ini)과 EMS DB 파일을 복사할 경로

    EMS 실행파일과 필요한 라이브러리(*.bpl) 복사

    개발 PC에서 탐색기를 열고, RAD 스튜디오 설치 경로 하위 bin 디렉토로리로 이동합니다.
    (10.2 도쿄 기준 기본 경로: C:\Program Files (x86)\Embarcadero\Studio\19.0\bin)


    원격 서버에서 탐색기를 열고 EMS 서버 디렉토리 하위 \EMSDevServer 디렉토리로 이동합니다.

    개발 PC에서 원격서버로 아래 그림의 목록을 참고해 파일들을 복사합니다.
    (10.2 도쿄 버전 기준으로 bpl 파일들의 번호가 다를 수 있으니 주의하시기 바랍니다.)

    • 소스(개발PC) : C:\Program Files (x86)\Embarcadero\Studio\19.0\bin
    • 대상(원격서버) : C:\EMSServer\EMSDevServer


    파일 복사가 잘 됐는지 확인하기 위해 EMSDevServer를 실행합니다.

    만약, 아래와 같은 오류가 표시된다면 오류에서 설명하는 bpl 파일을 개발PC에서 원격 서버로 복사합니다.

    위 메시지가 표시되지 않는다면 EMSDevServer를 종료합니다.


    EMS 환경파일, DB파일 복사

    개발 PC에서 탐색기를 열고, EMS 환경파일 경로(C:\Users\Public\Documents\Embarcadero\EMS)로 이동합니다.


    원격서버에서 탐색기를 열고, 환경파일 경로(C:\EMSServer\EnvFile)로 이동합니다.

    개발 PC의 환경파일(emsserver.ini) 파일과 EMS DB파일(EMSSERVER.IB)을 원격서버로 복사합니다.

    환경 설정

    환경파일과 DB파일 경로를 설정하는 과정을 설명합니다.

    환경파일 경로 지정

    원격 서버에서 레지스트리 편집기를 실행합니다.

    HKEY_CURRENT_USER\Software\Embarcadero\EMS 경로로 이동(만약 경로가 없으면 키 생성 후 이동)합니다.

    문자열 값을 추가합니다.(오른쪽 마우스 클릭 > New > String Value)

    Value name을 "ConfigFile", Value data에 emsserver.ini 파일 경로(C:\EMSServer\EnvFile\emsserver.ini)을 입력하고 [OK] 버튼을 클릭합니다.


    DB파일 경로 지정

    원격 서버에서 탐색기를 열고, EMS 서버 환경파일 경로(C:\EMSServer\EnvFile)로 이동합니다.

    EMS 서버 환경파일(emsserver.ini)을 메모장으로 엽니다.

    [Data] 섹션의 Database 값을 EMS DB파일 경로(C:\EMSServer\EnvFile\emsserver.ib)로 변경합니다.


    직접 개발한 EMS 패키지 추가하기

    RAD 스튜디오로 개발한 EMS Package를 EMS 서버에 추가하는 과정을 설명합니다.

    EMS 패키지 제작하는 과정은 다음 링크를 통해 학습가능합니다.

    원격 서버에 EMS 패키지 복사

    원격서버에서 탐색기를 열고, EMS Package를 배포할 경로(C:\emsserver\Packages)로 이동합니다.
    직접 개발한 패키지 파일을 개발PC에서 해당 경로로 복사합니다.


    EMS 환경파일에 패키지 추가

    EMS 환경파일을 열고, [Server.Packages] 섹션으로 이동합니다.

    아래 그림과 같이 패키지 경로와 패키지 이름을 입력합니다.

    • 형식 : 패키지 경로(bpl 파일 포함)=패키지 이름

    실행과 테스트

    EMS 서버 실행

    원격서버에서 탐색기를 열고, EMS 서버 실행파일 경로 이동후 EMSDevServer를 실행합니다.

    로그 창에 환경파일 경로, 배포한 EMS 패키지 등록 여부를 확인합니다.

    테스트

    개발 PC에서 웹브라우저 실행 후 http://(서버 IP):(EMS 포트번호)/version을 입력합니다.
    저는 "http://13.124.73.214:8080/version"으로 테스트 합니다.

    직접 작성한 패키지 리소스도 확인합니다.


    만약, 연결이 되지 않는다면, 서버의 IP 확인하고 윈도우 방화벽에 EMS 서버의 포트번호(기본 8080)가 Inbound 규칙으로 추가되었는지 확인하고 다시 시도 하시기 바랍니다.


    참고링크



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder

    [10.2 도쿄] 델파이로 리눅스 기반 웹서비스 제작하기(WebBroker 이용)

    2017.03.29 15:19



    이 글에서는 델파이를 이용해 리눅스 서버에서 구동되는 아파치 모듈을 웹브로커 기술을 이용해 만드는 방법을 소개합니다.

    (이 글에서는 리눅스 서버 설정과 MySQL 설정 등에 대해서는 자세히 다루지 않습니다.)


    델파이로 리눅스 기반 웹서비스 제작하기(WebBroker 이용)


    RAD 스튜디오 10.2 도쿄버전에서 리눅스 개발을 지원합니다. 이제 델파이로 리눅스 서버용 프로그램을 개발 및 배포, 디버깅 할 수 있습니다. 


    이 글은 RAD 스튜디오 10.2 도쿄 버전 기준으로 작성되었으며, 리눅스는 Ubuntu 16.04.1(가상머신)에서 동작합니다.


    이 글에서 아래의 내용을 진행합니다.

    • 콘솔 기반 WebBroker 프로젝트 제작 및 리눅스에서 실행
    • 아파치 모듈 기반 WebBroker 프로젝트 제작 및 리눅스에 배포, 테스트
    • 리눅스의 MySQL(FireDAC 이용) 데이터 연동해 JSON 데이터 응답하기

    다음 순서로 진행합니다.

    • 개발준비
    • 콘솔 기반 WebBroker 프로젝트 만들기
    • 콘솔 애플리케이션 리눅스에서 실행
    • 아파치 모듈 기반 WebBroker 프로젝트 만들기
    • 아파치 모듈 리눅스에 배포
    • MySQL 연결(FireDAC 이용)

    개발 준비

    리눅스가 설치된 별도의 컴퓨터 또는 가상머신을 준비합니다.

    다음 링크를 통해 RAD 스튜디오 리눅스 개발환경을 설정합니다.


    콘솔 기반 WebBroker 프로젝트 만들기

    WebBroker 프로젝트 생성(Stand-alone console application)

    델파이 실행 후 File > New > Other 메뉴를 선택합니다.

    Delphi Projects > WebBroker 메뉴 선택 후 Web Server Application 항목 선택 후 [OK] 버튼을 클릭합니다.


    마법사 창에서 Linux 플랫폼을 추가 선택 후 [Next >>] 버튼을 클릭합니다.


    프로젝트 타입으로 Stand-alone console application 항목을 선택합니다.

    • Apache dynamic link module : 아파치 모듈을 만들어 아파치 웹서버를 이용해 구동합니다.
    • Stand-alone console application : 독립형 콘솔 어플리케이션을 제작합니다.


    독립형 콘솔 애플리케이션이 사용할 HTTP 포트번호를 확인 및 변경 후 [Finish] 버튼을 누릅니다.


    프로젝트 파일을 저장합니다.(단축키 Ctrl + Shift + S) 프로젝트 명은 LinuxSAConsole로 지정합니다.
    (이름을 다르게 지정해도 됩니다.)


    윈도우에서 실행

    테스트를 위해 윈도우에서 실행(단축키 F9)하면 아래와 같이 콘솔모드로 실행됩니다."start" 커맨드를 입력해 서버를 시작합니다.


    웹브라우저에서 "http://localhost:8080" 주소를 열면 아래와 같이 응답데이터가 표시됩니다.
    (WebModuleUnit1에 정의된 내용이 출력됩니다.)


    콘솔 프로그램을 종료합니다.


    프로그램 시작 시 WebBroker 서버를 시작하도록 코드를 수정합니다.

    LinuxSAConsole 파일의 RunServer 함수에 "StartServer(LServer);" 라인을 추가합니다.


    리눅스에서 실행(콘솔 기반 WebBroker 프로젝트)

    RAD 스튜디오는 리눅스에 손쉽게 실행파일을 배포하고 디버깅하기위해 PAServer를 사용합니다. 

    PAServer는 별도의 설치프로그램으로, RAD 스튜디오에서 설치파일을 제공합니다.(설치방법은 상단의 개발 준비 과정을 참고하기 바랍니다.)

    PAServer 실행

    리눅스 터미널에서 PAServer가 설치된 경로로 이동 후 PAServer를 실행합니다.


    연결에 사용할 비밀번호 입력 후 엔터, "i" 명령어로 PAServer의 IP주소를 확인합니다.


    타겟 플랫폼을 리눅스로 지정

    델파이 IDE의 프로젝트 매니저에서 Target Platforms을 "64-bit Linux" 항목을 선택합니다.

    만약, "64-bit Linux" 항목이 없다면, Target Platforms 항목의 팝업메뉴에서 "Add Platform..." 메뉴를 선택 후 "64-bit Linux"를 추가합니다. 

    (64-bit Linux 플랫폼은 RAD 스튜디오 10.2 도쿄 이후 버전,  엔터프라이즈 에디션 이상에서 사용할 수 있습니다.)


    PAServer 연결

    PAServer 연결 설정하기 위해 "64-bit Linux" 항목의 팝업 메뉴에서 "Edit Connection..." 메뉴를 선택합니다.


    리눅스에서 실행한 PAServer 연결설정을 선택(또는 추가 후 선택) 후 [OK] 버튼을 누릅니다.


    리눅스에서 실행

    프로젝트를 실행(Run, F9)합니다.


    웹브라우저에서 리눅스 서버 IP와 8080포트로 웹페이지를 열면 아래와 같이 표시됩니다.


    프로젝트 파일 저장 경로 하위 "\Linux64\Debug\" 경로에 리눅스 실행파일이 생성됩니다.


    PAServer를 통해 리눅스에도 실행파일이 배포됩니다.

    홈디렉토리 하위 "/PAServer/scratch-dir/(PAServer 연결문자)/(프로젝트명)" 하위에 LinuxSAConsole 파일이 복사됩니다. 리눅스에서 콘솔 프로그램을 직접 실행할 수 있습니다.


    아파치 모듈 기반 WebBroker 프로젝트 만들기

    WebBroker 프로젝트 생성(Apache dynamic link module)

    WebBroker 프로젝트를 생성(File > New > Other > Delphi > WebBroker > Web Server Application)하고, "Apache dynamic link module" 프로젝트 타입을 선택합니다.


    Apache module을 수정하고 [Finish] 버튼을 누릅니다.

    • Apache module(아파치 모듈 이름)은 뒤에서 설명하는 "apache2 모듈 활성화에 등록" 시 사용합니다.


    생성한 프로젝트를 저장합니다.(Ctrl + Shift + S)


    WebBroker 프로젝트 빌드

    WebModule 유닛에 필요한 기능을 구현합니다.


    리눅스 타겟으로 빌드합니다.


    아래와 같이 프로젝트 저장 경로 하위 ".\Linux64\Debug\" 디렉토리에 so 확장자의 아파치 모듈이 생성됩니다.

    리눅스에 배포(아파치 모듈 기반 WebBroker 프로젝트)

    아파치 모듈 리눅스로 복사

    위에서 생성한 아파치 모듈(libmod_webbroker.so)을 리눅스로 복사합니다.(USB 메모리, FTP 클라이언트 프로그램 등을 이용합니다.) 

    저는 제 작업디렉토리(/home/humphrey/WorkData/)에 복사했습니다.


    아파치 모듈 디렉토리로 복사

    아파치 모듈 디렉토리(/usr/lib/apache2/modules)로 아파치 모듈을 복사합니다.

    su : root 권한을 가져옵니다.(/usr/lib 하위 경로에 복사하기 위해 root 권한이 필요합니다.)

    cp : 파일을 복사합니다.

    cd : 디렉토리를 변경합니다.

    ls : 현재 디렉토리의 파일목록을 표시합니다.


    apache2 모듈 활성화에 등록

    /etc/apache2/mods-enabled/ 디렉토리로 이동 합니다.

    nano 에디터를 이용(vi 등 다른 에디터를 이용해도 됩니다.) libmod_webbroker.load 파일을 편집(생성) 합니다.(root 권한 필요)

    nano libmod_webbroker.load


    nano 에디터에서 아래와 같이 입력하고 저장(Ctrl + X > Y > 엔터)합니다.(root 권한 필요)

    LoadModule (아파치 모듈 이름) (아파치 모듈 파일경로) 

    LoadModule webbroker_module /usr/lib/apache2/modules/libmod_webbroker.so


    아파치를 다시 시작합니다.(root 권한 필요)

    /etc/init.d/apache2 restart


    apache2 사이트 활성화에 등록

    /etc/apache2/sites-enabled/ 디렉토리로 이동합니다.

    nano 에디터를 이용해 000-default.conf 파일을 편집합니다.(root 권한 필요)

    nano 000-default.conf


    DocumentRoot 아래에 아래 코드를 입력합니다.


    <Location /webbroker>

      SetHandler libmod_webbroker-handler

    </Location>


    현재서버의 /webbroker 경로로 웹서버 호출 시 libmod_webbroker.load 로드하도록 설정

    저장하고 빠져나옵니다.(Ctrl + X > Y > 엔터)


    배포가 완료되었습니다. 리눅스 서버의 아이피(또는 도메인)과 webbroker 경로를 웹브라우저에서 입력해 열면 아래와 같은 결과를 볼 수 있습니다.

    MySQL 연결(FireDAC 이용)

    리눅스에 설치된 MySQL의 데이터를 JSON 포맷으로 출력합니다. 위에서 만든 WebBroker 아파치 모듈에 기능을 추가합니다. 데이터 엑세스 컴포넌트는 FireDAC을 이용합니다.


    MySQL 준비

    리눅스 서버에 설치된 MySQL과 연결하기 위해 리눅스 서버에 MySQL이 설치되어 있어야 합니다.


    델파이가 설치된 원격환경에서 MySQL과 연결하기 위해 MySQL 외부 접속을 허용합니다.

    스키마와 계정, 테이블을 생성합니다. 스키마 명과 계정명, 비밀번호는 모두 'test'로 합니다.

    스키마 생성

    create schema test;

    계정 생성

    create user 'test'@'%' identified by 'test';

    테이블 생성

    CREATE TABLE USER(
      USER_ID int, 
      NAME VARCHAR(255) character set utf8, 
      ADDRESS VARCHAR(255) character set utf8, 
      CITY VARCHAR(255) character set utf8);

    테스트 데이터 입력

    INSERT INTO USER VALUES(1, '김현수', '내가 사는 우리집', '인천');

    INSERT INTO USER VALUES(2, '홍길동', '네가 사는 너희집', '강릉');

    FireDAC 컴포넌트를 이용해 MySQL과 연결

    FireDAC을 이용해 리눅스 서버의 MySQL과 연결합니다.


    DB 연결 컴포넌트 추가

    WebModuleUnit1을 열고, TFDConnection, TFDQuery 컴포넌트를 추가합니다.


    데이터베이스 연결 설정

    TFDConnection 컴포넌트의 연결설정 화면을 열고(컴포넌트 더블클릭) 아래와 같이 입력합니다.

    • Driver ID : MySQL
    • Database : test
    • User_Name : test
    • Password : test
    • Server : 리눅스 서버의 IP 주소(또는 도메인)
    • CharacterSet : UTF8(한글을 표현하기 위해)

    [Test] 버튼을 눌러 연결을 확인합니다.(만약 연결되지 않는 경우 서버 IP주소 또는 MySQL 외부연결 설정을 확인합니다.)

    LoginPrompt 속성을 False로 변경합니다.


    쿼리 설정

    쿼리 컴포넌트(TFDQuery)의 Query Editor를 열고(컴포넌트 더블클릭) 쿼리문을 작성합니다.


    SELECT * FROM USER 

    [Execute] 버튼을 눌러 결과를 확인합니다. 

    [OK] 버튼을 눌러 적용합니다.

    테이블 내용을 JSON 포맷으로 데이터 작성

    JSON 데이터 작성은 TJsonObjectWriter 클래스를 활용합니다.(System.JSON.Writers 유닛 필요)


    아래 코드를 참조해 private 영역에 QueryUser 메소드를 작성합니다.

    QueryUser 메소드는 쿼리 결과를 JSON 포맷으로 작성 후 JSON 문자열로 반환하는 역할을 합니다.

    uses
      System.JSON, System.JSON.Writers;
    
    function TWebModule1.QueryUser: string;
    var
      Writer: TJsonObjectWriter;
    begin
      Writer := TJsonObjectWriter.Create;
      try
        Writer.WriteStartObject; // start resource
        Writer.WritePropertyName('users');
        Writer.WriteStartArray;
    
        FDQuery1.Open;
        FDQuery1.First;
        while not FDQuery1.Eof do
        begin
          Writer.WriteStartObject;
    
          Writer.WritePropertyName('USER_ID');
          Writer.WriteValue(FDQuery1.FieldByName('USER_ID').AsString);
    
          Writer.WritePropertyName('NAME');
          Writer.WriteValue(FDQuery1.FieldByName('NAME').AsString);
    
          Writer.WritePropertyName('ADDRESS');
          Writer.WriteValue(FDQuery1.FieldByName('ADDRESS').AsString);
    
          Writer.WritePropertyName('CITY');
          Writer.WriteValue(FDQuery1.FieldByName('CITY').AsString);
    
          Writer.WriteEndObject;
          FDQuery1.Next;
        end;
        Writer.WriteEndArray;
    
        Result := Writer.JSON.ToJSON;
      finally
        Writer.DisposeOf;
      end;
    end;


    WebModule1DefaultHandlerAction에서 응답 컨텐츠로 QueryUser를 연결합니다.

    procedure TWebModule1.WebModule1DefaultHandlerAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    begin
      Response.Content := QueryUser;
    end;


    리눅스로 배포하기

    프로젝트를 빌드하고, 생성된 아파치 모듈(libmod_webbroker.so)을 리눅스로 복사합니다.

    저는 제 작업디렉토리(/home/humphrey/WorkData/)에 복사했습니다.


    아파치 모듈 복사

    아파치 모듈 복사하기 전 아파치 서비스를 중단합니다.(root 권한 필요)

    아파치 모듈을 복사하고 다시 아파치 서비스를 시작합니다.


    결과확인

    리눅스 서버의 주소 하위 /webbroker 페이지 호출 시 JSON 포맷 문자열로 출력됩니다.

    (한글은 UTF8로 인코딩된것을 확인할 수 있습니다.)


    REST Debugger(Tools > REST Debugger)로 확인해 한글이 잘 표현되는 것을 확인할 수 있습니다.


    참고자료



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder 10.2 도쿄, 델파이, 리눅스, 웹브로커

    블루투스 바코드 스캐너(BI-07) 안드로이드 라이브러리를 델파이에서 연동하는 방법

    2017.03.20 17:03

    S모전자에서 의뢰한 휴대용 바코드 스캐너와 연동한 내용 공유합니다.

    휴대용 바코드 스캐너는 JAR 형태의 안드로이드 라이브러리를 제공합니다.

    이 과정을 통해 델파이와 안드로이드 라이브러리 연동하는 내용을 소개합니다.


    휴대용 바코드 스캐너 연동



    위 영상의 휴대용 바코더 스캐너는 클래식 블루투스 기반으로 연결을 제공합니다.

    해당 안드로이드 앱은 델파이로 제작했으며, JAR 파일을 이용해 스캐너와 연동합니다.


    JAR 파일 연동과정은 아래와 같습니다.

    1, 준비(라이브러리 준비 및 분석)

    2, 델파이용 안드로이드 라이브러리 브릿지 파일 생성

    3, 델파이 프로젝트에 안드로이드 라이브러리 추가

    4, 브릿지 파일을 이용해 안드로이드 라이브러리 연동


    기타 안드로이드 라이브러리 연동 시 참고사항

    델파이 샘플 프로젝트


    준비(라이브러리 준비 및 분석)

    대상 장비(Point Mobile BI-07)

    Point Mobile: BI-07

    링크: http://www.pointmobile.co.kr/item.php?it_id=1381210470&ca_id=401010


    라이브러리 준비 및 분석

    안드로이드와 iOS 라이브러리를 제공합니다.

    안드로이드 라이브러리(jar) :  PM3SDK_connectByAddress.jar

    라이브러리 관련 문서 및 샘플 :  BI07PM3SDK_Document.chm

    대부분의 라이브러리 제공 시 연동 문서와 샘플코드를 제공합니다.

    문서를 잘 숙지하고, 샘플코드를 분석합니다. 대략적으로 소스코드를 파악하되 해당 라이브러리 호출하는 부분은 정확히 숙지해야 합니다.

    델파이용 안드로이드 라이브러리 브릿지 파일 생성

    델파이에서 안드로이드 라이브러리를 사용하기위해 브릿지 파일을 생성해야 합니다.

    브릿지 파일 생성

    Java2OP.exe를 이용 델파이 브릿지 파일을 생성합니다. Java2OP는 JAR 파일(*.jar) 또는 Java 소스파일을 호출할 수 있는 델파이 코드를 생성합니다.(참고: 파이어모키에서 외부 라이브러리 연동하기(jar, so, a))

    아래 커맨드를 이용해 브릿지 파일(Androidapi.JNI.PM3SDK_BI07.pas)을 생성합니다.
    D:\ProjectExt\SamsungElectronics>set path=%PATH%;"c:\Program Files (x86)\Embarcadero\Studio\18.0\bin\converters\java2op"
    
    D:\ProjectExt\SamsungElectronics>java2op -jar .\SDK\PM3SDK_connectByAddress.jar -unit AndroidapiJNI.PM3SDK_BI07
    Warning: error opening ReservedWordsOP.txt
    Warning: error opening ReservedWordsC.txt
    Parsing xml: c:\Program Files (x86)\Embarcadero\Studio\18.0\bin\converters\java2op\bootclasses.xml
    Parsing jar: D:\ProjectExt\SamsungElectronics\SDK\PM3SDK_connectByAddress.jar
    
    D:\ProjectExt\SamsungElectronics>

    생성된 결과


    델파이 프로젝트에 안드로이드 라이브러리 추가

    라이브러리 추가

    멀티-디바이스 폼 애플리케이션(파이어몽키) 프로젝트에서 대상 플랫폼(Target Platform)을 안드로이드로 설정 후 Libraries 항목에 JAR 파일(안드로이드 라이브러리)을 추가합니다.


    델파이 브릿지 파일 추가

    해당 라이브러리를 사용하기 위해 델파이 브릿지 파일(Androidapi.JNI.PM3SDK_BI07.pas)을 프로젝트에 추가합니다.

    브릿지 파일 확인을 위해 컴파일 합니다.

    만약, 컴파일 시 E2029 오류 발생 시 다음을 참고해 조치합니다.

    E2029 오류 조치방법

    E2029 오류는 예약어를 용도와 다른 곳에서 사용할 경우 발생합니다.

    오류가 발생하는 예약어 키워드 앞에 "&"을 추가해 해결합니다.

    (예> type -> &type, in -> &in)

    (참고: http://tech.devgear.co.kr/delphi_qna/420082)

    브릿지 파일을 이용해 안드로이드 라이브러리 연동

    라이브러리 연동 문서와 샘플코드를 참고해 기능을 구현했습니다.

    그 중 가장 빈번히 사용되는 자바 객체 생성과 처음 구현해본 자바 핸들러 콜백함수 구현, 가장 어렵게 변환했던 자바 오브젝트로 캐스팅된 바이트 배열에서 데이터 가져오기 3가지 내용 소개합니다.

    자바 객체 생성

    브릿지 파일은 라이브러리에 구현된 클래스 인터페이스가 다음과 같이 구현되어 있습니다.

    (BluetoothChatService 클래스)

    위와 같이 자바 클래스는 2개의 인터페이스와 1개의 클래스로 정의됩니다.


    JBluetoothChatServiceClass 인터페이스는 클래스 함수와 속성, 상수를 제공합니다.(생성하기 전 사용)

    JBluetoothChatService 인터페이스는 인스턴스 함수와 속성, 상수를 제공합니다.(생성해서 사용)


    델파이에서 자바 객체 생성은 아래와 같이 클래스.JavaClass.Init(파라메터) 형식으로 구현합니다.

    var
      FChatService: JBluetoothChatService;
    ...
      FChatService := TJBluetoothChatService.JavaClass.init(Context, FHandler);

    자바 핸들러 콜백함수 구현

    바코드 스캐너에서 바코드 데이터 수신은 핸들러의 콜백함수를 통해 받습니다.

    그 자바코드는 아래와 같습니다.
    private final Handler mHandler = new Handler() {
    }
    
    mChatService = new BluetoothChatService(mContext, mHandler);
    SendCommand.SendCommandInit(mChatService, mHandler);

    델파이에서는 다음과 같이 구현했습니다.

    1) 핸들러 콜백함수 정의

    type
      THandleMessageEvent = procedure(msg: JMessage) of object;
      TCharServiceHandlerCallback = class(TJavaLocal, JHandler_Callback)
      private
        FOnHandleMessage: THandleMessageEvent;
      public
        function handleMessage(msg: JMessage): Boolean; cdecl;
    
        property OnHandleMessage: THandleMessageEvent read FOnHandleMessage write FOnHandleMessage;
      end;

    주의할 점은, 인터페이스(JHandler_Callback)를 상속받은 메소드(handleMessage)는 public 영역에 구현해야 합니다.

    습관적으로 델파이와 같이 private 영역에 선언 시 "Segmentation fault (11)" 오류(잘못된 메모리 영역 참조)로 상당히 고생했습니다.


    2) 핸들러, 콜백 객체 생성, 이벤트 연결(OnHandleMessage)

      private
        FHandler: JHandler;
        FCallback: TCharServiceHandler;
    
      FCallback := TCharServiceHandlerCallback.Create;
      FCallback.OnHandleMessage := ChatServiceHandleMessage;
    
      Context := TAndroidHelper.Context;
      Looper := TJLooper.JavaClass.getMainLooper;
      FHandler := TJHandler.JavaClass.init(Looper, FCallback);
    
      FChatService := TJBluetoothChatService.JavaClass.init(Context, FHandler);
      TJSendCommand.JavaClass.SendCommandInit(FChatService, FHandler);

    콜백 객체 생성 후 OnHandleMessage 이벤트를 연결해 데이터를 수신합니다.

    핸들러 생성 시 콜백 객체를 파라메터로 전달합니다.

    핸들러 객체는 BluetoothChatService와 SendCommand 객체 생성 시 파라메터로 전달합니다.


    3) 핸들러 콜백과 연결된 이벤트 발생

    procedure TPointMobileBluetoothChatService.ChatServiceHandleMessage(
      msg: JMessage);
    var
      DeviceName, JData: JString;
    begin
      Log.d(msg.what.ToString);
      case msg.what of
      MESSAGE_STATE_CHANGE:
        ProcessHandlerStateChage;
      MESSAGE_BARCODE:
        ProcessHandlerBarcord(msg.obj, msg.arg1);
      MESSAGE_DEVICE_NAME:
        begin
          DeviceName := msg.getData.getString(StringToJString('device_name'));
          Log.d('Device name: ' + JStringToString(DeviceName));
        end;
    
      // 필요한 이벤트 추가 할 것
      end;
    end;

    스캐너 라이브러리는 바코드 정보 수신 시 핸들러 콜백과 연결된 이벤트로 데이터를 제공합니다.(이벤트가 발생합니다.)

    자바 오브젝트로 캐스팅된 바이트 배열(byte[]) 데이터 가져오기

    어디에서도 참고할 내용이 없어 가장 고생했던 내용입니다. 델파이 내부 구조를 분석하고 예측해서 구현했습니다.


    자바에서 다음과 같이 오브젝트를 바이트 배열로 치환(캐스팅)하는 코드입니다.

    byte[] BarcodeBuff = (byte[]) msg.obj;


    델파이에서는 다음과 같이 구현했습니다.

    function JObjectToStr(const AJObject: JObject): string;
    var
      LObj: ILocalObject;
      ObjID: Pointer;
      JBytes: TJavaArray;
      JData: JString;
    begin
      Result := '';
    
      if JStringToString(AJObject.getClass.getCanonicalName) <> 'byte[]' then
        Exit;
    
      if AJObject.QueryInterface(ILocalObject, LObj) = 0  then
        ObjID := LObj.GetObjectID
      else
        Exit;
    
      JBytes := TJavaArray(WrapJNIArray(ObjID, TypeInfo(TJavaArray)));
      JData := TJString.JavaClass.init(JBytes, 0, JBytes.Length);
      Result := JStringToString(JData);
    end;
    
    var
      Data: string;
    begin
      Data := JObjectToStr(msg.obj);
    end;


    안드로이드 라이브러리 연동 참고사항

    가장 좋은 참고자료는 파이어몽키 소스코드였습니다.


    델파이 설치 경로 하위 source 디렉토리에는 파이어몽키 라이브러리 소스코드들이 포함되어 있습니다.(정식 제품의 경우에 한함)


    그중 아래 경로의 소스코드들을 참고하시기 바랍니다.(Delphi 10.1 베를린 버전 기준)


    안드로이드 SDK 브릿지 파일들

    안드로이드 SDK 델파이 브릿지 파일이 포함되어 있습니다. 자바 소스코드의 객체이름을 다음 경로의 소스코드들에서 찾아 필요한 경우 uses 절에 추가 후 사용합니다.

    C:\Program Files (x86)\Embarcadero\Studio\18.0\source\rtl\android

    파이어몽키 라이브러리

    다음 경로 중 FMX.AddressBook.Android.pas과 같이 Android가 포함된 파일들을 참고하기 바랍니다.

    위 안드로이드 SDK 브릿지 파일들을 이용한 코드들로 구현되어 있습니다.

    C:\Program Files (x86)\Embarcadero\Studio\18.0\source\fmx



    델파이 샘플 프로젝트


    기타 궁금한 내용은 댓글 또는 http://blog.hjf.pe.kr/452의 댓글을 참고하거나 등록해주세요.

    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 파이어몽키 FMX, JNI, PointMobile, 바코드스캐너, 안드로이드

    1. Blog Icon
      silkroad99

      안녕하세요
      많은 도움을 받고 있습니다.
      혹 PM3SDK_connectByAddress.jar 파일은 어디서 다운받는건가요

    2. 이런, 제가 링크를 안걸었네요^^
      다운로드 링크 추가했습니다.

    3. Blog Icon
      barcoder

      위에 소스를 다운받아 컴파일시에 오류가 발생합니다..
      시애틀 사용자구요..
      메시지는 아래와 같습니다..

      Checking project dependencies...
      Compiling PointMobileBI07.dproj (Debug, Android)
      dccaarm command line for "PointMobileBI07.dpr"
      f:\program files (x86)\embarcadero\studio\17.0\bin\dccaarm.exe -$O- --no-config -M -Q -TX.so -AGenerics.Collections=System.Generics.Collections;
      Generics.Defaults=System.Generics.Defaults;WinTypes=Winapi.Windows;WinProcs=Winapi.Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE -DDEBUG
      -E.\Android\Debug -I"f:\program files (x86)\embarcadero\studio\17.0\lib\Android\debug";"f:\program files
      (x86)\embarcadero\studio\17.0\lib\Android\Release" -LEC:\Users\Public\Documents\Embarcadero\Studio\17.0\Bpl\Android
      -LNC:\Users\Public\Documents\Embarcadero\Studio\17.0\Dcp\Android -NU.\Android\Debug -NSSystem;Xml;Data;Datasnap;Web;Soap; -O"f:\program files
      (x86)\embarcadero\studio\17.0\lib\Android\Release" -R"f:\program files (x86)\embarcadero\studio\17.0\lib\Android\Release" -U"f:\program files
      (x86)\embarcadero\studio\17.0\lib\Android\debug";"f:\program files (x86)\embarcadero\studio\17.0\lib\Android\Release" --libpath:"F:\Program Files
      (x86)\Embarcadero\Studio\15.0\PlatformSDKs\android-ndk-r9c\platforms\android-14\arch-arm\usr\lib";"F:\Program Files
      (x86)\Embarcadero\Studio\15.0\PlatformSDKs\android-ndk-r9c\sources\cxx-stl\gnu-libstdc++\4.8\libs\armeabi-v7a" --linker:"F:\Program Files
      (x86)\Embarcadero\Studio\15.0\PlatformSDKs\android-ndk-r9c\toolchains\arm-linux-androideabi-4.6\prebuilt\windows\bin\arm-linux-androideabi-ld.exe" -V
      -VN -NO.\Android\Debug PointMobileBI07.dpr
      [DCC Error] PointMobileBluetoothChatService.pas(147): E2035 Not enough actual parameters
      [DCC Warning] PointMobileBluetoothChatService.pas(158): W1000 Symbol 'SharedActivityContext' is deprecated: 'Use TAndroidHelper.Context'
      [DCC Error] PointMobileBluetoothChatService.pas(160): E2250 There is no overloaded version of 'CallInUIThread' that can be called with these arguments
      [DCC Error] PointMobileBluetoothChatService.pas(308): E2250 There is no overloaded version of 'Queue' that can be called with these arguments
      [DCC Fatal Error] MainForm.pas(18): F2063 Could not compile used unit 'PointMobileBluetoothChatService.pas'
      Failed
      Elapsed time: 00:00:02.9
      [Warning] W1000 Symbol 'SharedActivityContext' is deprecated: 'Use TAndroidHelper.Context'
      [Warning] W1000 Symbol 'SharedActivityContext' is deprecated: 'Use TAndroidHelper.Context'

    4. 해당 프로젝트는 10.1 베를린에서 작성되었습니다.
      버전별로 함수의 파라메터가 다를 수 있으니, 시애틀로 컴파일 시 오류를 확인해 조치하시기 바랍니다.

    5. Blog Icon
      silkroad99

      안녕하세요
      샘플 프로젝트를 베를린에서 테스트 해보고 있습니다.
      프로젝트 소스에서 압축을 풀면

      begin
      Application.Initialize;
      AApplication.CreateForm(TForm1, Form1);
      pplication.Run;
      end.

      상단에서 압축문제인지 잘 못된것을 수정하고나서
      컴파일하고 실행하면 바코드 스캐너가 스캔을 못합니다.

      메모장에 커서를 놓으면 바코드 리딩이 되는데
      스캔 버튼 클릭하면 작동이 안됩니다.

      메모장이나 에디트박스에서 커서 위치시키고
      스캔해야만 됩니다.
      제가 뭐를 잘못하고 있는건가요

    6. 위 영상을 보시면 아시겠지만, 저는 메모나 에디터에 커서를 놓지 않아도 스캔이 잘 동작합니다.

      자세한 내용은 github 소스코드와 설치한 소스코드를 비교해보시거나, 디버깅(F9 버튼으로 실행)해보시며 찾아보시기 바랍니다.

      질문만 보고는 제가 조언드릴 부분이 없네요.

    7. Blog Icon
      silkroad99

      빠른 답변에 감사드립니다.

      여기에는 파일 업로드를 할수없어
      부득이하게 데브기어 개발자QnA에
      프로젝트를 업로드했습니다.

      가능하시다면 어디가 오류인지 부탁드립니다.

      감사합니다.

    8. http://tech.devgear.co.kr/delphi_qna/432793
      위 글에 답변 달았습니다.

    9. Blog Icon
      silkroad99

      감사합니다.

    10. 안녕하세요.

      델파이 10.1 베를린 사용자 입니다.

      안드로이드가 아닌 윈도우 프로그램으로 자바파일을 호출하여 델파이에서 음성입력과 출력이 가능한 프로젝트를 구성해 보려고 하는데 위의 사항과 동일한 방법을 사용해도 무관할까요?

      음성인식과 음성출력은 자바에서 사용하였습니다.

      혹여 가능하시다면 답변부탁드립니다.

    11. 저도 윈도우 환경에서 자바파일 호출하는 작업을 진행해보지는 않았지만,
      개념은 비슷할 것으로 보입니다.

      다만, 안드로이드 환경에서는 필요한 기능이 유닛과 함수로 준비되어 있지만, 윈도우 환경에서는 필요한 기능들을 안드로이드 코드를 참조하는 등으로 새로 구성해야 할것으로 예상됩니다.

      구글에서 "call java from delphi" 등의 검색어로 검색해 보시면 관련된 자료들을 찾을 수 있으니 참고하시기 바랍니다.

    12. Blog Icon
      파이어버드

      위 소스 실행시키면
      Context := TAndroidHelper.Context;

      < 오류내용 >
      [DCC Error] PointMobileBluetoothChatService.pas(264): E2003 Undeclared identifier: 'TAndroidHelper'
      [DCC Error] PointMobileBluetoothChatService.pas(264): E2066 Missing operator or semicolon
      [DCC Error] PointMobileBluetoothChatService.pas(364): E2250 There is no overloaded version of 'Queue' that can be called with these arguments


      procedure TPointMobileBluetoothChatService.SetupChatService;
      var
      Context: JContext;
      Looper: JLooper;
      begin

      if FBluetoothAdapter.State = TBluetoothAdapterState.Off then
      begin
      Log.d('Turn on bluetooth');
      Exit;
      end;

      FCallback := TCharServiceHandlerCallback.Create;

      FCallback.OnHandleMessage := ChatServiceHandleMessage;

      //이부분이 에러가 납니다.
      Context := TAndroidHelper.Context;

      Looper := TJLooper.JavaClass.getMainLooper;

      FHandler := TJHandler.JavaClass.init(Looper, FCallback);

      FChatService := TJBluetoothChatService.JavaClass.init(Context, FHandler);
      TJSendCommand.JavaClass.SendCommandInit(FChatService, FHandler);

      end;


      xe8로 개발하고 있습니다.

      혹시 아시는 분 있으시면 답변 부탁드립니다.

      감사합니다.

    13. XE8에서는 TAndroidHelper.Context 대신 SharedActivityContext라고 선언하면 됩니다.

      참고로, 모바일 개발 시 되도록 최신버전으로 진행하는 것이 좋습니다.
      최신버전에서 새로운 안드로이드/iOS 플랫폼을 정식지원합니다.

    14. Blog Icon
      파이어버드

      안녕하세요. 험프리님
      xe8에서
      TAndroidHelper.Context -> SharedActivityContext 변경하니 잘 됩니다.
      이것 때문에 몇일동안 고생했는데 너무 감사합니다.

      그리고 silkroad99님도 원격 및 소스도 주시면서 도와 주셔서 감사하고
      두분 언제 저녁 한번 대접하고 싶은데 가능하신지...
      메일 주시면 달려가겠습니다.

      감사합니다.







    15. Blog Icon
      장창원

      안녕하세요. 자료 잘 봤습니다.
      delphi 10. berlin 쓰고 있습니다.
      머 좀 여쭤봐도 될른지요...

      제가 가지고 있던 java 파일을 jar을 만들었고, java2op 를 쓰는데, 여기서 문제가 생겼습니다.
      java2op -jar Recognition.jar -unit Android.JNI.Recognition -classes *
      라고 해서, 맨 뒤에 -classes * 을 붙이지 않으면, no classes selecte 라고 에러가 나더이다.
      여튼 붙여서 작업을 하면 돌아는 갑니다만, 결과로 나온 pas파일이 다 똑같습니다.
      java2op -jar FreeImage.jar -unit Android.JNI.FreeImage -classes * 라고 해서,
      FreeImage.jar 란 파일을 돌려도, 물론 java 파일의 내용은 틀리고, 안에 몇줄 없습니다만, 결과로 나온 pas 가 둘다 똑같습니다. 아무래도 bootclasses.xml 파일을 parsing 한다고 메세지가 나오던데, xml 과 비슷한 내용만 들어가 있습니다. 파일도 엄청 크고요...
      뭐가 문제인지 삽질 중입니다.
      고견 있으시면 댓글 부탁드립니다.
      감사합니다.

    16. 데브기어 테크게시판(tech.devgear.co.kr)의 개발자 Q&A에 해당 jar파일과 함께 다시 등록해 주세요.

      잘될지는 모르겠지만 저도 시도해보도록 하겠습니다.

    [10.2 도쿄] 델파이/C++빌더 리눅스 개발환경 설정하기

    2017.02.15 18:08

    RAD 스튜디오 10.2 도쿄버전이 출시되어 해당 내용을 아래 글에서 업데이트 하였습니다.



    곧 출시 예정인 RAD 스튜디오 10.2 도쿄 버전에서는 리눅스 개발 지원을 대기하고 있습니다.

    이 글에서는 RAD 스튜디오로 리눅스 개발하기 위한 환경설정 및 개발 절차를 설명합니다.

    (이 글은 베타버전 기준으로 작성되며, 정식 출시 이후 업데이트하도록 하겠습니다.)

    리눅스 개발환경 설정

    리눅스 개발환경 설정은 아래 절차 순으로 진행합니다.

    1. 리눅스 OS 설치
    2. RAD 스튜디오 설치
    3. 리눅스 머신에 PAServer 설치 및 실행
    4. RAD 스튜디오에서 PAServer 연결 설정
    5. RAD 스튜디오에서 리눅스 SDK 설정
    6. 코드 작성 및 실행

    리눅스 OS 설치

    공식 지원 리눅스 버전

    RAD 스튜디오가 공식 지원하는 리눅스 버전은 아래와 같습니다.

    • Ubuntu Server(Ubuntu LTS version 16.04) - 우분투 서버
    • RedHat enterprise Linux (version 7) - 레드햇/페도라 계열 리눅스

    리눅스 설치

    (이미 위 버전의 리눅스가 설치되있다면 이 단계를 넘어갑니다.)


    별도의 컴퓨터 또는 가상머신에 리눅스 OS를 설치합니다.

    • 우분투 서버 다운로드 : https://www.ubuntu.com/download/server
    • 레드햇/페도라 리눅스 다운로드 : https://getfedora.org/en/server/download/


    다음 링크에서 무료 가상머신(Virtual Box)과 리눅스 OS 설치 과정을 소개합니다.

    리눅스 소프트웨어 업데이트 - 우분투

    설치가 완료되면 시스템 업데이트를 해야합니다. 

    다음 명령어를 실행합니다.(참고: sudo 명령어를 사용하는 경우 관리자 비밀번호를 1회 입력해야 합니다.)

    설치파일 용량이 큰 경우 설치 여부를 묻습니다. 'Y' 입력 후 엔터키를 누릅니다.

    sudo apt-get update
    sudo apt-get upgrade
    sudo apt-get dist-upgrade

    개발에 필요한 몇가지 패키지를 설치합니다.

    sudo apt-get install joe wget p7zip-full curl
    sudo apt-get install openssh-server
    sudo apt-get install build-essential
    sudo apt-get install zlib1g-dev
    sudo apt-get install libcurl4-gnutls-dev

    마지막으로 약간의 정리를 합니다.

    sudo apt-get autoremove
    sudo apt-get autoclean

    리눅스 소프트웨어 업데이트 - 레드햇/페도라

    시스템 소프트웨어를 .업데이트 합니다.

    sudo yum update

    RAD 스튜디오 설치

    설치파일 다운로드

    현재(2017년 2월 16일)  RAD 스튜디오 10.2 도쿄 버전 출시 준비 중입니다.

    RAD 스튜디오 업데이트 서브스크립트를 등록한 경우 베타버전인 고질라 프로젝트를 통해 미리 만나보실 수 있습니다.

     - 베타버전(고질라) 다운로드 : http://digsig.embarcadero.com/ft/GodzillaSub


    (정식버전 출시 후)

    리눅스 지원 기술검토가 필요한 경우 트라이얼 버전을 다운로드 받아 설치하시기 바랍니다.

     - RAD 스튜디오 트라이얼 다운로드 : http://devgear.co.kr/products/rad-studio/ (하단 무료 평가판 참조)


    RAD 스튜디오 설치

    설치 안내에 따라 설치합니다. 플랫폼 선택 시 Delphi Linux 64-bit Enterprise 항목을 선택합니다.


    만약, 이미 설치했고 Delphi Linux 64-bit Enterprise 항목을 선택하지 않았다면, 

    RAD 스튜디오 IDE 메인메뉴에서 "Tools > Manage Platforms..." 항목을 선택 해 항목을 추가선택 후 설치할 수 있습니다.


    리눅스 머신에 PAServer 설치 및 실행

    PAServer(Platform Assistant Server)는 RAD 스튜디오가 생성한 리눅스 실행파일을 리눅스 플랫폼에서 실행하고, 디버깅할 수 있도록 지원하는 소프트웨어입니다. RAD 스튜디오와 PAServer는 원격으로 연결되어 실행파일 전달 및 디버깅 정보를 주고 받아 윈도우 환경의 IDE에서 리눅스 실행파일을 구동하고 디버깅할 수 있도록 지원합니다.

    리눅스에 PAServer 설치

    1) PAServer 설치파일 복사

    리눅스 PAServer 설치파일은 RAD 스튜디오 설치경로 하위의 PAServer 디렉토리에 제공합니다.


    LinuxPAServer19.0.tar 파일을 리눅스 시스템으로 복사합니다.


    2) 압축해제

    터미널을 열고, LinuxPAServer19.0.tar파일이 복사된 경로로 이동합니다.

    다음 명령어로 압축을 해제합니다.

    tar –xvf LinuxPAServer19.0.tar

    아래와 같이 압축해제 됩니다.



    3) PAServer 실행

    PAServer 설치 경로로 이동 후 paserver를 실행합니다.


    비밀번호를 묻습니다. 임의의 비밀번호를 입력 후 엔터를 누릅니다.(RAD 스튜디오에서 연결 시 해당 비밀번호가 필요합니다.)

    RAD 스튜디오에서 PAServer 연결 설정

    PAServer IP/포트번호 확인

    RAD 스튜디오와 PAServer 연결하기 위해서는 PAServer의 IP와 포트번호, 비밀번호가 필요합니다.

    위에서 실행한 PAServer 대화창에서 "i" 입력 후 엔터키를 눌러 IP 주소를 확인합니다.

    포트번호는 아래 그림과 같이 표시됩니다. 기본포트번호는 "64211"입니다.


    RAD 스튜디오에서 PAServer 연결

    RAD 서버에서 Connection Profile Manager 옵션을 실행합니다.(Tools > Options > Connection Profile Manager)

    Add 버튼을 누르고, "Profile name" 항목에 접속정보의 명칭을 입력하고 Next >> 버튼을 누릅니다.


    Remote machine 항목에 PAServer의 IP를 입력합니다.

    Port number 항목에 PAServer 포트번호를 입력합니다.

    Password 항목에 PAServer 실행 시 입력한 비밀번호를 입력합니다.

    "Test Connection" 버튼을 눌러 연결을 확인하고, Finish 버튼을 눌러 완료합니다.


    만약, 연결되지 않은 경우, PAServer가 설치된 리눅스 시스템과의 네트워크 환경을 검토하기 바랍니다.


    RAD 스튜디오에서 리눅스 SDK 설정

    RAD 스튜디오에서 SDK Manager(Tools > Options > SDK Manager)를 실행합니다.

    Add 버튼을 누릅니다.

    Select a platform 항목에서 "64-bit Linux"를 선택합니다.

    Select a profile to connect 항목에서 위에서 추가한 연결을 선택합니다.

    Select an SDK version 항목 확인 후 "OK" 버튼을 누릅니다.


    아래와 같이 리눅스의 SDK를 로컬로 복사하는 작업이 진행됩니다.(몇분 정도 소요)

    완료되면 Close 버튼을 눌러 완료합니다.

    코드 작성 및 실행

    프로젝트 생성

    콘솔 애플리케이션 프로젝트를 생성합니다.(File > New > Other > Delphi Projects > Console Application)


    샘플 코드 작성

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      System.SysUtils;
    
    begin
      try
        { TODO -oUser -cConsole Main : Insert code here }
    
        WriteLn('Hello Linux');
        ReadLn;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end.

    타겟 플랫폼 선택

    프로젝트 매니저에서 64-bit Linux 플랫폼을 추가합니다.(프로젝트 매니저 > Target Platforms > 오른쪽 클릭 > Add Platform > 64-bit Linux > OK)


    빌드 및 실행

    Run 버튼을 눌러 실행하면, Event Log에 아래와 같이 "Hello Linux" 메시지가 표시됩니다.

    참고 링크



    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 Delphi/C++Builder

    [안드로이드] 포토 라이브러리 목록 불러오기

    2016.12.09 17:15



    안드로이드 내부 저장소에 저장된 사진의 썸네일 목록을 가져오는 코드입니다.


    uses
      Androidapi.Helpers, Androidapi.JNI.Net, Androidapi.JNIBridge,
      Androidapi.JNI.Provider, Androidapi.JNI.GraphicsContentViewText,
      FMX.Helpers.Android, Androidapi.JNI.JavaTypes;
    
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      Uri: Jnet_Uri;
      Projection: TJavaObjectArray<jstring>;
      Cursor: JCursor;
      FilePath: string;
      Item: TListBoxItem;
    begin
      Uri := TJImages_Thumbnails.JavaClass.EXTERNAL_CONTENT_URI;
      Projection := TJavaObjectArray<jstring>.Create(1);
      Projection[0] := TJImages_Thumbnails.JavaClass.DATA;
    
      Cursor := TAndroidHelper.ContentResolver.query(Uri, Projection, nil, nil, nil);
    
      if not  Assigned(Cursor) then
        Exit;
    
      try
        Cursor.moveToFirst;
        while(Cursor.moveToNext) do
        begin
          FilePath := JStringToString(Cursor.getString(Cursor.getColumnIndex(TJImages_Thumbnails.JavaClass.DATA)));
    
          Item := TListBoxItem.Create(ListBox1);
          Item.Parent := ListBox1;
          Item.Text := FilePath;
          Item.ItemData.Bitmap.LoadFromFile(Filepath);
          // 쓰레드로 처리해야 함(로딩이 오래 걸림)
        end;
      finally
        Cursor.close;
      end;
    end;

    자세한 설명은 추가하겠습니다.


    저작자 표시 비영리 동일 조건 변경 허락
    신고
    크리에이티브 커먼즈 라이선스
    Creative Commons License

    험프리.김현수 험프리.김현수 파이어몽키

    Out of memory 오류 해소 방안

    2016.11.21 10:42

    메모리 부족(Out of memory) 오류 해소 방안을 델파이 기준으로 설명합니다.

    증상

    윈도우즈 기반 애플리케이션 시작 또는 실행 중 Out of memory 예외(Exception)이 발생



    원인

    • 데스크탑 힙 제한에 의한 메모리 부족
    • 메모리 누수(Leak)에 의한 메모리 부족
    • 과도한 메모리 사용에 의한 메모리 부족


      조치방법

      데스크탑 힙 제한에 의한 메모리 부족

      데스크탑 힙 제한에 의한 메모리 부족 오류가 발생할 수 있습니다.

      마이크로소프트 지원 페이지에서 레지스트리를 수정하는 해결 방법을 안내합니다.


      메모리 누수(Leak)에 의한 메모리 부족

      프로그램내에서 할당된 메모리를 사용 후 반환하지 않는 메모리 누수가 반복될 경우 메모리 부족이 발생합니다.


      델파이에서는 FastMM 메모리 관리자(2006 이후 기본 제공)의 정지 시 메모리 누수 보고 기능을 이용해 애플리케이션 종료 시 메모리 누수 보고를 받을 수 있습니다.


      종료 시 메모리 누수 보고 기능을 활성화 하려면

      • ReportMemoryLeaksOnShutdown 전역 변수를 True로 설정합니다.
      • 보통 프로젝트 파일에 선언합니다.

      begin

        ReportMemoryLeaksOnShutdown := True;


        Application.Initialize;

        Application.MainFormOnTaskbar := True;

        Application.CreateForm(TForm1, Form1);

        Application.Run;

      end. 

      • 프로그램 종료 시 메시지 박스를 통해 누수된 내용을 표시합니다.
        • 생성 후 해재하지 않은 객체의 클래스명과 크기, 갯수를 알려줍니다.
        • 단, 어느 지점에서 발생한지는 알 수 없습니다.
        • 되도록, 개발시점에 메모리 릭을 파악하는 용도록 활용하는 것이 좋습니다.

      기타 자세한 내용은 아래 엠바카데로 기술문서를 참고하시기 바랍니다.


      과도한 메모리 사용에 의한 메모리 부족

      시스템 상의 메모리보다 과도한 메모리를 사용한 경우 메모리 부족 오류가 발생할 수 있습니다.


      이경우 작은 범위의 테스트에서 문제가 발생하지 않다가, 실사용 시 문제가 발생할 수 있습니다.

      보통 다수의 UI 컨트롤들을 갖는 화면 여러개를 동시에 표시하거나, 다수의 응용 프로그램을 동시에 실행하는 경우 발생할 수 있습니다.


      이 경우 구현상의 이슈보다는 운영상의 이슈이므로, 운영 중 사용자의 메모리 상태를 모니터링 하고, 동시에 표시할 수 있는 화면을 제한하는 등의 과도한 메모리 사용 방어 장치가 필요합니다.


      저작자 표시 비영리 동일 조건 변경 허락
      신고
      크리에이티브 커먼즈 라이선스
      Creative Commons License

      험프리.김현수 험프리.김현수 Delphi/C++Builder

      1. UMDH(user-mode dump heap)를 이용해 메모리 릭을 찾아내는 방법이 있네요.
        http://www.gamedevforever.com/103
        https://support.microsoft.com/ko-kr/help/268343/umdhtools.exe-how-to-use-umdh.exe-to-find-memory-leaks

        나중에 저도 한번 (델파이 코드에 대해)테스트 해봐야겠습니다.

        UMLD: Memory Leak Detector and Bug Reporter v.1.0
        http://torry.net/pages.php?id=1526

      [필립스 휴] API 정리

      2016.10.04 11:44

      필립스 휴는 REST API를 통해 전구를 제어할 수 있는 제품입니다.


      구성


      전구는 브릿지를 통해 제어하며, 애플리케이션은 브릿지에게 명령을 전송하면 브릿지가 개별전구를 제어하는 방식으로 구성됩니다. 

      브릿지는 여러개의 전구와 ZigBee로 연결되어 있습니다.


      필립스 휴 API 시작하기

      http://www.developers.meethue.com/documentation/getting-started

      사용자 등록

      Addresshttp://<bridge ip address>/api
      Body{"devicetype":"사용자 아이디"}
      MethodPOST

      조명 ON/OFF

      Addresshttp://<bridge ip address>/api/{사용자 아이디}/lights/1/state
      Body{"on":false}
      MethodPUT



      새로운 조명 추가

      http://www.developers.meethue.com/documentation/lights-api#13_search_for_new_lights


      새 전구 검색

      URL/api/<username>/lights
      MethodPOST
      Version1.0
      PermissionWhitelist

      새로운 조명에 전원을 연결하고 위 명령 전송

      조명이 검색되면 조명이 깜빡임




      저작자 표시 비영리 동일 조건 변경 허락
      신고
      크리에이티브 커먼즈 라이선스
      Creative Commons License

      험프리.김현수 험프리.김현수 기타정보

      마우스 이벤트 캡쳐 - SetCapture, ReleaseCapture

      2016.09.28 17:10

      마우스 이벤트를 캡쳐해야 하는 경우 SetCapture(Win32 API)를 이용하면 됩니다.

      다 사용한 경우 ReleaseCapture로 해제합니다.



      아래 예제는

      1) 폼위에 패널 위치

      2) 폼과 패널은 각각 MouseMove 이벤트에서 로그 기록

      3) 패널에서 MouseDown 시 폼으로 SetCapture 시도 > 이후 패널 및 메모 위의 마우스 이벤트가 폼에서 발생

      4) MouseUp 시(폼에서 마우스 이벤트를 가져가 폼의 MouseUp이벤트에서 구현필요) ReleaseCapture로 캡쳐 해제

      procedure TForm2.FormCreate(Sender: TObject);
      begin
        FIsCaptured := False;
      end;
      
      procedure TForm2.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
        Y: Integer);
      begin
        Memo1.Lines.Add(Format('F> X: %d, Y: %d', [X, Y]))
      end;
      
      procedure TForm2.FormMouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer);
      begin
        if FIsCaptured then
        begin
          Memo1.Lines.Add('Form> ReleaseCapture');
          ReleaseCapture;
        end;
      end;
      
      procedure TForm2.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer);
      begin
        SetCaptureControl(Self);
      //  SetCapture(Self.Handle);
        Memo1.Lines.Add('Panel> Capture');
      
        FIsCaptured := True;
      end;
      
      procedure TForm2.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
        Y: Integer);
      begin
        Memo1.Lines.Add(Format('P> X: %d, Y: %d', [X, Y]))
      end;
      
      procedure TForm2.Panel1MouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer);
      begin
        ReleaseCapture;
        Memo1.Lines.Add('Panel> ReleaseCapture');
      end;



      샘플 : 

      SetCaptureReleaseCapture.zip


      저작자 표시 비영리 동일 조건 변경 허락
      신고
      크리에이티브 커먼즈 라이선스
      Creative Commons License

      험프리.김현수 험프리.김현수 Delphi/C++Builder

      모바일 디바이스 화면 방향전환 이벤트 수신하기

      2016.09.22 10:23

      모바일 디바이스의 방향을 가로 또는 세로, 그리고 역방향으로 돌리는 동작을 알아낼 수 있습니다.


      파이어몽키 프레임워크에서 제공하는 기능으로 TMessageManager에 구독해 이벤트를 받을 수 있습니다.

      (부가적으로 이런 개발 방식을 옵저버 패턴이라고 합니다.^^)



      위 스크린샷은 

      1) 시작(폼생성) 시 TMessageMenager에 TOrientationChageMessage에 대해 OrientationChaged 이벤트를 구독하고

      2) 종료 시 : 구독을 해지합니다.

      3) 현재 스크린 방향은 IFMXScreenService.GetScreenOrientation 메소드를 통해 알아 올 수 있습니다.


      자세한 내용은 아래 샘플코드를 통해 확인하세요.

      델파이 10.1 베를린 버전으로 작성되었습니다.

      ScreenOrientation-Berlin.zip




      해당 내용은 델파이 부트캠프 - 델파이 게임 개발 소스코드를 살펴보다 발견했습니다.

      델파이 부트캠프: 게임 개발 - http://community.embarcadero.com/blogs?view=entry&id=8785

      델파이 게임개발 소스코드(Github) : https://github.com/EmbarcaderoPublic/DelphiArcadeGames

      저작자 표시 비영리 동일 조건 변경 허락
      신고
      크리에이티브 커먼즈 라이선스
      Creative Commons License

      험프리.김현수 험프리.김현수 파이어몽키