참고: Google 프로토콜 RPC를 통해 인증을 사용하려면 현재 Google Cloud 콘솔에서 App Engine 앱에 사용할 수 있는 인증을 사용하면 됩니다. 또한 app.yaml
파일에 로그인 요구사항을 지정해야 합니다. 현재 다른 인증 방법은 App Engine의 Google Protocol RPC 라이브러리에서 지원되지 않습니다.
Google Protocol RPC 라이브러리는 HTTP 기반 원격 절차 호출(RPC) 서비스를 구현하기 위한 프레임워크입니다. RPC 서비스는 외부 애플리케이션이 웹 애플리케이션과 상호작용할 수 있는 체계적인 방법을 제공하는 메시지 유형 및 원격 메소드의 컬렉션입니다. Python 프로그래밍 언어에서 메시지와 서비스를 정의할 수 있으므로, App Engine에서 쉽게 Protocol RPC 서비스를 개발하고 해당 서비스를 테스트하고 확장할 수 있습니다.
모든 종류의 HTTP 기반 RPC 서비스에 대해 Google Protocol RPC 프로토콜을 사용할 수 있지만 일반적인 사용 사례는 다음과 같습니다.
- 제3자가 사용하기 위한 웹 API 게시
- 구조화된 Ajax 백엔드 생성
- 장기간 실행되는 서버 통신에 복제
다수의 선언된 원격 메소드를 포함한 단일 Python 클래스에서 Google Protocol RPC 서비스를 정의할 수 있습니다. 각 원격 메소드는 특정 매개변수 집합을 요청으로 수락하고 특정한 응답을 반환합니다. 이러한 요청과 응답 매개변수는 메시지라 하는 맞춤 설정 클래스입니다.
Google Protocol RPC의 Hello World
이 섹션에서는 원격 클라이언트의 메시지를 수신하는 매우 간단한 서비스 정의에 대한 예시를 보여 줍니다. 이 메시지에는 사용자 이름(HelloRequest.my_name
)이 포함되며 해당 사용자에게 인사말을 다시 보냅니다(HelloResponse.hello
).
from protorpc import messages from protorpc import remote from protorpc.wsgi import service package = 'hello' # Create the request string containing the user's name class HelloRequest(messages.Message): my_name = messages.StringField(1, required=True) # Create the response string class HelloResponse(messages.Message): hello = messages.StringField(1, required=True) # Create the RPC service to exchange messages class HelloService(remote.Service): @remote.method(HelloRequest, HelloResponse) def hello(self, request): return HelloResponse(hello='Hello there, %s!' % request.my_name) # Map the RPC service and path (/hello) app = service.service_mappings([('/hello.*', HelloService)])
Google Protocol RPC 시작하기
이 섹션은 Python으로 개발한 방명록 애플리케이션을 사용하여 Google Protocol RPC를 시작하는 방법을 보여줍니다. 사용자는 온라인에서 방명록(Python SDK에 데모로 포함되어 있음)에 방문하고 항목을 쓰고 모든 사용자의 항목을 볼 수 있습니다. 사용자는 인터페이스와 직접 상호 작용하지만 웹 애플리케이션이 해당 정보에 쉽게 액세스할 수 있는 방법은 없습니다.
여기서는 프로토콜 RPC가 유용합니다. 이 가이드에서 Google은 Google Protocol RPC를 이 기본 방명록에 적용하여 다른 웹 애플리케이션이 방명록 데이터에 액세스할 수 있도록 합니다. 이 가이드는 Google Protocol RPC를 사용하여 방명록 기능을 확장하는 내용만 설명하며, 다음 해야 할 일은 사용자 몫입니다. 예를 들어, 사용자가 게시한 메시지를 읽고 게시글을 일일 시계열 그래프로 나타내는 도구를 작성할 수 있습니다. Protocol RPC 사용 방법은 특정 앱에 따라 다릅니다. Google Protocol RPC가 애플리케이션의 데이터에 대한 활용 범위를 넓혀준다는 점이 중요합니다.
시작하려면 우선 방명록 애플리케이션의 Datastore에 있는 데이터에 액세스하기 위한 원격 메서드를 구현하는 postservice.py
파일을 만듭니다.
PostService 모듈 만들기
Google Protocol RPC를 시작하는 첫 번째 단계는 애플리케이션 디렉터리에 postservice.py
라는 파일을 만드는 것입니다. 이 파일을 사용하여 데이터를 원격으로 게시하는 메서드와 데이터를 원격으로 가져오는 또 다른 메서드 등 두 가지 메서드를 구현하는 새로운 서비스를 정의합니다.
지금은 이 파일에 어떤 내용도 추가할 필요가 없지만 이후 섹션에서 정의하는 모든 코드를 이 파일에 입력할 수 있습니다. 다음 섹션에서는 방명록 애플리케이션의 데이터 저장소에 게시되는 메모를 나타내는 메시지를 만듭니다.
메시지 작업
메시지는 Google Protocol RPC에서 사용되는 기본적인 데이터 형식입니다. 메시지는 Message기본 클래스에서 상속 받는 클래스를 선언함으로써 정의됩니다. 그런 다음 메시지의 각 필드에 해당하는 클래스 속성을 정의합니다.
예를 들어 방명록 서비스를 사용하면 사용자가 메모를 게시할 수 있습니다. 애플리케이션 디렉터리에 postservice.py
파일을 만들지 않았으면 지금 만듭니다. PostService는 Greeting 클래스를 사용하여 Datastore에 게시물을 저장합니다. 이러한 메모를 나타내는 메시지를 정의해 보겠습니다.
from protorpc import messages class Note(messages.Message): text = messages.StringField(1, required=True) when = messages.IntegerField(2)
메모 메시지는 text
와 when
등 두 가지 필드로 정의됩니다. 각 필드에는 특정 유형이 있습니다. 텍스트 필드는 방명록 페이지에 사용자 게시글 내용을 나타내는 유니코드 문자열입니다. when
필드는 게시글의 타임스탬프를 나타내는 정수입니다. 문자열 정의 시 다음 작업도 수행합니다.
- 기본 네트워크 프로토콜이 필드를 식별하는 데 사용하는 고유한 숫자 값(
text
의 경우1
,when
의 경우2
)을 각 필드에 입력합니다. text
를 필수 필드로 만듭니다. 필드는 기본적으로 선택사항이며required=True
를 설정하면 필수로 표시할 수 있습니다. 필수 필드를 값으로 설정하여 메시지를 초기화해야 합니다. Google Protocol RPC 서비스 메소드는 제대로 초기화된 메시지만 수락합니다.
Note 클래스의 생성자를 사용하여 필드 값을 설정할 수 있습니다.
# Import the standard time Python library to handle the timestamp. import time note_instance = Note(text=u'Hello guestbook!', when=int(time.time()))
기본 Python 속성 값과 같이 메시지에서 값을 읽고 설정할 수도 있습니다. 예를 들어, 메시지를 변경하려면 다음과 같이 하세요.
print note_instance.text note_instance.text = u'Good-bye guestbook!' print note_instance.text
Hello guestbook! Good-bye guestbook!
서비스 정의
서비스는 Service 기본 클래스에서 상속받는 클래스 정의입니다. 서비스의 원격 메서드는 remote
데코레이터를 사용하여 표시됩니다. 서비스의 각 메서드는 단일 메시지를 매개변수로 허용하고 단일 메시지를 응답으로 반환합니다.
PostService의 첫 번째 메서드를 정의해 보겠습니다. postservice.py
파일에 다음을 추가합니다.
import datetime from protorpc import message_types from protorpc import remote import guestbook class PostService(remote.Service): # Add the remote decorator to indicate the service methods @remote.method(Note, message_types.VoidMessage) def post_note(self, request): # If the Note instance has a timestamp, use that timestamp if request.when is not None: when = datetime.datetime.utcfromtimestamp(request.when) # Else use the current time else: when = datetime.datetime.now() note = guestbook.Greeting(content=request.text, date=when, parent=guestbook.guestbook_key) note.put() return message_types.VoidMessage()
remote
데코레이터는 다음 두 가지 매개변수를 사용합니다.
- 예상되는 요청 유형입니다. post_note() 메소드는 Note 인스턴스를 요청 유형으로 수락합니다.
- 예상되는 응답 유형입니다. Google Protocol RPC 라이브러리에는 필드가 없는 메시지로 정의되는 VoidMessage(
protorpc.message_types
모듈에 있음)라는 기본 제공 유형이 있습니다. 이는 post_note() 메시지가 호출자에게 유용한 어떠한 내용도 반환하지 않음을 의미합니다. 오류 없이 반환되면 메시지가 게시된 것으로 간주됩니다.
Note.when
은 선택사항 필드이므로 호출자가 설정하지 않았을 수 있습니다. 이 경우 when
의 값이 None으로 설정됩니다. Note.when
이 None으로 설정되면 post_note()가 메시지를 수신한 시간을 사용하여 타임스탬프를 만듭니다.
응답 메시지는 원격 메서드에 의해 인스턴스화되고 원격 메서드의 반환 값이 됩니다.
서비스 등록
protorpc.wsgi.service
라이브러리를 사용하여 새 서비스를 WSGI 애플리케이션으로 게시할 수 있습니다. 애플리케이션 디렉터리에 services.py
라는 새 파일을 만들고 다음 코드를 추가하여 서비스를 만듭니다.
from protorpc.wsgi import service import postservice # Map the RPC service and path (/PostService) app = service.service_mappings([('/PostService', postservice.PostService)])
이제 기존 catch-all 항목 상위의 app.yaml
파일에 다음 핸들러를 추가합니다.
- url: /PostService.* script: services.app - url: .* script: guestbook.app
명령줄에서 서비스 테스트
이제 서비스를 만들었으므로 curl
또는 이와 유사한 명령줄 도구를 사용하여 서비스를 테스트할 수 있습니다.
# After starting the development web server: # NOTE: ProtoRPC always expect a POST. % curl -H \ 'content-type:application/json' \ -d '{"text": "Hello guestbook!"}'\ http://localhost:8080/PostService.post_note
빈 JSON 응답은 메모가 성공적으로 게시되었음을 나타냅니다. 브라우저(http://localhost:8080/)에서 방명록 애플리케이션으로 이동해 메모를 확인할 수 있습니다.
메시지 필드 추가
이제 PostService에 메시지를 게시할 수 있으므로 PostService의 메시지를 가져오기 위한 새 메서드를 추가해 보겠습니다. 먼저 응답에서 메모 순서를 지정하는 방법을 서버에 지시하는 새로운 enum 필드와 일부 기본값을 정의하는 postservice.py
에 요청 메시지를 정의합니다. 앞서 정의한 PostService
클래스의 상위에 정의합니다.
class GetNotesRequest(messages.Message): limit = messages.IntegerField(1, default=10) on_or_before = messages.IntegerField(2) class Order(messages.Enum): WHEN = 1 TEXT = 2 order = messages.EnumField(Order, 3, default=Order.WHEN)
PostService로 보내면 이 메시지는 특정 날짜 이전의 여러 메모를 특정 순서로 요청합니다. limit
필드는 가져올 메모의 최대 수를 나타냅니다. 명시적으로 설정하지 않으면 limit
는 기본적으로 메모 10개(default=10
키워드 인수로 표시됨)로 지정됩니다.
필드 값이 제한된 수의 알려진 기호화된 값으로 제한된 경우, order 필드는 enum
필드 유형을 사용 설정하는 EnumField 클래스를 도입합니다. 이 경우 enum
은 응답에서 메모 순서 지정 방법을 서버에 표시합니다. enum 값을 정의하려면 Enum 클래스의 서브클래스를 만듭니다. 각 이름에는 고유한 유형 번호가 할당되어야 합니다. 각 번호는 enum 유형의 인스턴스로 변환되고 클래스에서 액세스될 수 있습니다.
print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN.name, GetNotesRequest.Order.WHEN.number)
각 enum
값에는 해당 이름이나 번호로 쉽게 변환되도록 해주는 특수한 특성이 있습니다. 이름과 번호 속성에 액세스하지 않고 각 값을 문자열이나 정수로 변환하면 됩니다.
print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN, GetNotesRequest.Order.WHEN)
Enum 필드는 필드 번호 앞의 첫 번째 매개변수로 enum 유형이 있어야 한다는 점을 제외하고 다른 필드와 유사하게 선언됩니다. Enum 필드는 기본값을 가질 수도 있습니다.
응답 메시지 정의
이제 get_notes() 응답 메시지를 정의해 보겠습니다. 응답은 메모 메시지 컬렉션이어야 합니다. 메시지는 다른 메시지를 포함할 수 있습니다. 아래에 정의된 Notes.notes
필드의 경우 Note
클래스를 messages.MessageField
생성자의 필드 번호 앞에 첫 번째 매개변수로 제공하여 메시지 컬렉션임을 나타냅니다.
class Notes(messages.Message): notes = messages.MessageField(Note, 1, repeated=True)
Notes.notes
필드는 repeated=True
키워드 인수로 표시되는 반복되는 필드이기도 합니다. 반복되는 필드 값은 해당 선언의 필드 유형 목록이어야 합니다. 이 경우 Notes.notes
는 Note 인스턴스의 목록이어야 합니다. 목록은 자동으로 만들어지며 None으로 할당될 수 없습니다.
예를 들어, 다음은 메모 객체를 만드는 방법입니다.
response = Notes(notes=[Note(text='This is note 1'), Note(text='This is note 2')]) print 'The first note is:', response.notes[0].text print 'The second note is:', response.notes[1].text
get_notes 구현
이제 get_notes() 메서드를 PostService 클래스에 추가할 수 있습니다.
import datetime import time from protorpc import remote class PostService(remote.Service): @remote.method(GetNotesRequest, Notes) def get_notes(self, request): query = guestbook.Greeting.query().order(-guestbook.Greeting.date) if request.on_or_before: when = datetime.datetime.utcfromtimestamp( request.on_or_before) query = query.filter(guestbook.Greeting.date <= when) notes = [] for note_model in query.fetch(request.limit): if note_model.date: when = int(time.mktime(note_model.date.utctimetuple())) else: when = None note = Note(text=note_model.content, when=when) notes.append(note) if request.order == GetNotesRequest.Order.TEXT: notes.sort(key=lambda note: note.text) return Notes(notes=notes)