요청 처리 방법

이 문서는 App Engine 애플리케이션이 요청을 수신하고 응답을 전송하는 방법을 설명합니다. 자세한 내용은 요청 헤더 및 응답 참조를 참조하세요.

애플리케이션이 서비스를 사용한다면 요청 주소를 특정 서비스 또는 해당 서비스의 특정 버전으로 지정할 수 있습니다. 서비스 주소 지정에 대한 자세한 내용은 요청 라우팅 방법을 참조하세요.

요청 처리

애플리케이션은 웹 서버를 시작하고 요청을 처리하는 작업을 담당합니다. 사용 중인 개발 언어용으로 제공되는 모든 웹 프레임워크를 사용할 수 있습니다.

App Engine에서 애플리케이션의 여러 인스턴스를 실행하며 각 인스턴스에는 요청 처리를 위한 자체 웹 서버가 있습니다. 요청이 임의의 인스턴스로 라우팅될 수 있으므로, 동일한 사용자가 보내는 연속 요청이 같은 인스턴스로 전송되지 않을 수 있습니다. 인스턴스 한 개가 여러 요청을 동시에 처리할 수 있습니다. 인스턴스 수는 트래픽의 변화에 따라 자동으로 조정될 수 있습니다. 또한 app.yaml 파일에서 max_concurrent_requests 요소를 설정하여 인스턴스 한 개가 처리할 수 있는 동시 요청 수를 변경할 수 있습니다.

App Engine이 애플리케이션에 대한 웹 요청을 수신하면 애플리케이션의 app.yaml 구성 파일의 설명대로 URL에 해당하는 핸들러 스크립트를 호출합니다. Python 2.7 런타임은 이전 버전과 호환되도록 WSGI 표준CGI 표준을 지원합니다. WSGI를 사용하는 것이 좋으며, Python 2.7의 일부 기능은 WSGI가 없으면 작동하지 않습니다. 애플리케이션의 스크립트 핸들러 구성에 따라 요청이 WSGI를 사용하여 처리되는지 아니면 CGI를 사용하여 처리되는지 결정됩니다.

다음 Python 스크립트는 HTTP 헤더 및 Hello, World! 메시지를 사용하여 요청에 응답합니다.

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')

app = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

여러 요청을 각 웹 서버에 동시에 전달하려면 threadsafe: trueapp.yaml 파일에 추가하여 애플리케이션을 threadsafe로 표시합니다. CGI를 사용하는 스크립트 핸들러가 있으면 동시 요청이 불가능합니다.

할당량 및 한도

App Engine에서는 트래픽 증가에 따라 애플리케이션에 자동으로 리소스를 할당합니다. 그러나 여기에는 다음과 같은 제한사항이 적용됩니다.

  • App Engine은 애플리케이션이 요청에 응답하는 시간이 1초 미만인, 지연 시간이 짧은 애플리케이션에 자동 확장 기능을 예약합니다. 여러 요청을 처리할 때 요청당 1초가 넘는 등 지연 시간과 처리량이 많은 애플리케이션에는 실버, 골드, 플래티넘 지원이 필요합니다. 이러한 수준의 지원을 신청한 고객은 지원팀 담당자에게 처리량 한도를 상향 요청할 수 있습니다.

  • CPU 사용량이 많은 애플리케이션의 경우, 동일 서버의 다른 애플리케이션과 효율적으로 리소스를 공유하기 위해 지연 시간이 추가로 발생할 수 있습니다. 정적 파일에 대한 요청은 지연 시간 한도에서 제외됩니다.

애플리케이션에 수신되는 각 요청은 요청 한도에 반영됩니다. 요청에 대한 응답으로 전송되는 데이터는 발신 대역폭(청구 가능) 한도에 반영됩니다.

HTTP 및 HTTPS(보안) 요청은 모두 요청, 수신 대역폭(청구 가능), 발신 대역폭(청구 가능) 한도에 반영됩니다. GCP Console의 할당량 세부정보 페이지도 정보 제공 목적으로 보안 요청, 보안 수신 대역폭, 보안 발신 대역폭을 별도의 값으로 보고합니다. HTTPS 요청만 이러한 값에 반영됩니다. 자세한 내용은 할당량 페이지를 참조하세요.

다음은 요청 핸들러 사용 시 적용되는 한도입니다.

한도 용량
요청 크기 32MB
응답 크기 32MB
요청 기간 60초
최대 총 파일 수(앱 파일과 정적 파일) 총 10,000개
디렉터리당 1,000개
애플리케이션 파일 최대 크기 32MB
정적 파일 최대 크기 32MB
모든 애플리케이션과 정적 파일의 최대 총 크기 첫 1GB는 무료
첫 1GB 이후 1GB당 월 $0.026

응답 한도

동적 응답은 32MB로 제한됩니다. 스크립트 핸들러가 이 한도를 초과하는 응답을 생성하면 서버가 500 내부 서버 오류 상태 코드와 함께 빈 응답을 반환합니다. 이 제한은 Blobstore 또는 Cloud Storage의 데이터를 제공하는 응답에는 적용되지 않습니다.

요청 헤더

수신되는 HTTP 요청에는 클라이언트가 전송한 HTTP 헤더가 포함되어 있습니다. 보안을 위해 일부 헤더는 애플리케이션에 도달하기 전 중간 프록시에 의해 삭제 또는 수정됩니다.

자세한 내용은 요청 헤더 참조에서 볼 수 있습니다.

요청 응답

App Engine은 Request로 핸들러 스크립트를 호출하고 스크립트가 반환될 때까지 기다립니다. 표준 출력 스트림에 작성된 모든 데이터가 HTTP 응답으로 전송됩니다.

생성하는 응답에는 한도가 적용되며, 응답이 클라이언트에 반환되기 전에 수정될 수 있습니다.

자세한 내용은 요청 응답 참조를 확인하세요.

스트리밍 응답

App Engine은 요청이 처리되는 동안 증분 청크의 데이터가 클라이언트에 전송되는 스트리밍 응답을 지원하지 않습니다. 위 설명과 같이 코드의 모든 데이터가 수집되어 단일 HTTP 응답으로 전송됩니다.

응답 압축

클라이언트가 HTTP 헤더를 원본 요청과 함께 보내면서 압축된 콘텐츠(gzip)를 허용한다고 표시할 경우, App Engine은 핸들러 응답 데이터를 자동으로 압축하고 적절한 응답 헤더를 연결합니다. 그리고 클라이언트가 압축된 응답을 안정적으로 받을 수 있는지 확인하기 위해 Accept-Encoding 요청 헤더와 User-Agent 요청 헤더를 모두 사용합니다.

커스텀 클라이언트는 Accept-Encoding 헤더와 User-Agent 헤더 모두를 gzip 값으로 지정하여 압축된 응답을 받을 수 있음을 표시할 수 있습니다. 또한 응답의 Content-Type을 사용하여 압축이 적절한지 여부를 확인합니다. 일반적으로 텍스트 기반 콘텐츠 유형은 압축되지만 바이너리 콘텐츠 유형은 압축되지 않습니다.

App Engine에서 응답을 자동으로 압축하면 Content-Encoding 헤더가 응답에 추가됩니다.

요청 기한 지정

요청 핸들러에는 요청에 대한 응답을 생성하고 반환하는 시간 제한이 있으며, 일반적으로 약 60초입니다. 기한에 도달하면 요청 핸들러가 중단됩니다. Python 런타임 환경은 google.appengine.runtime 패키지로부터 DeadlineExceededError를 발생시켜 요청 핸들러를 중단합니다. 요청 핸들러가 이 예외를 포착하지 못하면 포착되지 않은 모든 예외와 마찬가지로 런타임 환경이 HTTP 500 서버 오류를 클라이언트에 반환합니다.

요청 핸들러는 이 오류를 포착하여 응답을 맞춤설정할 수 있습니다. 런타임 환경은 예외를 발생시킨 후에 요청 핸들러에 커스텀 응답을 준비할 시간을 조금 더 줍니다(1초 미만).

class TimerHandler(webapp2.RequestHandler):
    def get(self):
        from google.appengine.runtime import DeadlineExceededError

        try:
            time.sleep(70)
            self.response.write('Completed.')
        except DeadlineExceededError:
            self.response.clear()
            self.response.set_status(500)
            self.response.out.write(
                'The request did not complete in time.')

두 번째 기한까지 핸들러가 응답을 반환하지 않거나 예외를 발생시키지 않으면 핸들러가 종료되고 기본 오류 응답이 반환됩니다.

요청이 응답하는 데 최대 60초가 소요될 수 있지만 App Engine은 아주 짧은 요청, 즉 일반적으로 수백 밀리 초가 걸리는 요청을 사용하는 애플리케이션에 최적화되어 있습니다. 효율적인 앱은 대부분의 요청에 신속하게 응답합니다. 그렇지 않은 앱은 App Engine 인프라에 맞춰 제대로 확장되지 않습니다.

DeadlineExceededErrors의 일반적인 원인과 제안 해결책은 DeadlineExceededError를 참조하세요.

로깅

App Engine 웹 서버는 웹 요청에 응답하기 위해 핸들러 스크립트가 표준 출력 스트림에 쓰는 모든 항목을 캡처합니다. App Engine 웹 서버는 핸들러 스크립트가 표준 오류 스트림에 쓰는 모든 항목도 캡처하여 로그 데이터로 저장합니다. 각 요청에는 요청 시작 시간을 기반으로 하는 전역적으로 고유한 식별자인 request_id가 할당됩니다. Stackdriver Logging을 사용하면 GCP Console에서 애플리케이션의 로그 데이터를 확인할 수 있습니다.

로그 수준('디버그', '정보', '경고', '오류', '심각')과 같은 로깅 개념을 이해할 수 있도록 App Engine Python 런타임 환경은 Python 표준 라이브러리의 로깅 모듈을 특별히 지원합니다.

import logging

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        logging.debug('This is a debug message')
        logging.info('This is an info message')
        logging.warning('This is a warning message')
        logging.error('This is an error message')
        logging.critical('This is a critical message')

        try:
            raise ValueError('This is a sample value error.')
        except ValueError:
            logging.exception('A example exception log.')

        self.response.out.write('Logging example.')

app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

환경

실행 환경은 여러 가지 환경 변수를 자동으로 설정합니다. app.yaml에서 더 설정할 수 있습니다. 자동으로 설정되는 변수 중에서 몇 가지는 App Engine에 중요하며 다른 것들은 WSGI 또는 CGI 표준의 일부입니다. Python 코드는 os.environ 사전을 사용하여 이러한 변수에 액세스할 수 있습니다.

다음 환경 변수는 App Engine에 고유합니다.

  • CURRENT_VERSION_ID: 현재 실행 중인 애플리케이션의 주 버전 및 부 버전입니다(예: 'X.Y'). 주 버전 번호('X')는 앱의 app.yaml 파일에서 지정합니다. 부 버전 번호('Y')는 앱의 각 버전이 App Engine에 업로드될 때 자동으로 설정됩니다. 개발용 웹 서버에서 부 버전은 항상 '1'입니다.

  • AUTH_DOMAIN: Users API로 사용자를 인증하는 데 사용되는 도메인입니다. appspot.com에서 호스팅되는 앱의 AUTH_DOMAINgmail.com이며, 모든 Google 계정을 허용합니다. 커스텀 도메인에서 호스팅되는 앱의 AUTH_DOMAIN은 커스텀 도메인과 같습니다.

  • INSTANCE_ID: 요청을 처리하는 프런트엔드 인스턴스의 인스턴스 ID를 포함합니다. ID는 16진수 문자열입니다(예: 00c61b117c7f7fd0ce9e1325a04b8f0df30deaaf). 로그인한 관리자는 URL에 ID를 사용할 수 있습니다(예: http://[INSTANCE_ID].myApp.appspot.com/). 그러면 요청이 해당 프런트엔드 인스턴스로 라우팅됩니다. 인스턴스가 요청을 처리할 수 없는 경우 즉시 503을 반환합니다.

다음 환경 변수는 WSGI 및 CGI 표준의 일부이며, App Engine에서 특별한 동작을 보입니다.

  • SERVER_SOFTWARE: 개발용 웹 서버에서 이 값은 'Development/X.Y'입니다. 여기서 'X.Y'는 런타임의 버전입니다. App Engine에서 실행할 때는 이 값이 'Google App Engine/X.Y.Z'입니다.

WSGI 또는 CGI 표준에 따라 추가적인 환경 변수가 설정됩니다. 이러한 변수에 대한 자세한 내용은 각각 WSGI 표준 또는 CGI 표준을 참조하세요.

app.yaml 파일에서 환경 변수도 설정할 수 있습니다.

env_variables:
  DJANGO_SETTINGS_MODULE: 'myapp.settings'

다음 webapp2 요청 핸들러는 브라우저에서 애플리케이션에 표시되는 모든 환경 변수를 표시합니다.

class PrintEnvironmentHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        for key, value in os.environ.iteritems():
            self.response.out.write(
                "{} = {}\n".format(key, value))

요청 ID

요청 당시 요청별로 고유한 요청 ID를 저장할 수 있습니다. 나중에 이 요청 ID를 사용하여 Stackdriver Logging에서 요청의 로그를 찾을 수 있습니다.

다음 샘플 코드에서는 요청 컨텍스트에서 요청 ID를 가져오는 방법을 보여줍니다.

class RequestIdHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        request_id = os.environ.get('REQUEST_LOG_ID')
        self.response.write(
            'REQUEST_LOG_ID={}'.format(request_id))

앱 캐싱

여러 파일이 한 모듈을 가져오더라도 독립형 Python 애플리케이션이 해당 모듈을 한 번만 로드하는 것과 유사하게 Python 런타임 환경은 가져온 모듈을 단일 웹 서버에서 여러 요청 간에 캐시 처리합니다. WSGI 핸들러는 모듈이기 때문에 요청 간에 캐시 처리됩니다. CGI 핸들러 스크립트는 main() 루틴을 제공하는 경우에만 캐시됩니다. 그렇지 않으면 CGI 핸들러 스크립트가 모든 요청에 대해 로드됩니다.

앱 캐싱은 응답 시간에 상당한 이점을 가져다 줍니다. 아래에 설명된 대로 모든 CGI 핸들러 스크립트가 main() 루틴을 사용하는 것이 좋습니다.

가져오기 항목 캐시 처리

효율성을 위해 웹 서버는 가져온 모듈을 메모리에 유지하며, 같은 서버의 같은 애플리케이션에 대한 후속 요청이 있을 경우 해당 모듈을 다시 로드하거나 다시 평가하지 않습니다. 대부분의 모듈은 가져올 때 전역 데이터를 초기화하거나 다른 부작용을 일으키지 않기 때문에 모듈을 캐싱한다고 해서 애플리케이션의 동작이 바뀌지 않습니다.

모든 요청에 대해 평가되는 모듈에 종속된 모듈을 가져오는 경우 애플리케이션은 이 캐싱 동작을 수용해야 합니다.

CGI 핸들러 캐싱

가져온 모듈 외에 CGI 핸들러 스크립트 자체도 캐시 처리하도록 App Engine에 지시할 수 있습니다. 핸들러 스크립트가 main()이라는 함수를 정의하는 경우 스크립트와 해당 전역 환경이 가져온 모듈처럼 캐시 처리됩니다. 지정된 웹 서버에서 스크립트에 대한 첫 번째 요청은 스크립트를 정상적으로 평가합니다. 후속 요청의 경우에는 App Engine이 캐시 처리된 환경에서 main() 함수를 호출합니다.

핸들러 스크립트를 캐시 처리하려면 App Engine이 main()을 인수 없이 호출할 수 있어야 합니다. 핸들러 스크립트가 main() 함수를 정의하지 않거나 main() 함수에 인수(기본값 없음)가 필요한 경우 App Engine은 모든 요청에 대해 전체 스크립트를 로드하여 평가합니다.

파싱된 Python 코드를 메모리에 유지하면 시간을 절약하고 더 빠르게 응답할 수 있습니다. 전역 환경을 캐싱하면 다음과 같은 기타 상황에서도 유용할 수 있습니다.

  • 컴파일된 정규 표현식. 모든 정규 표현식은 파싱되어 컴파일된 형태로 저장됩니다. 컴파일된 정규 표현식을 전역 변수에 저장한 후 앱 캐싱을 사용하여 컴파일된 객체를 요청 사이에서 재사용할 수 있습니다.

  • GqlQuery 객체. GQL 쿼리 문자열은 GqlQuery 객체가 생성될 때 파싱됩니다. GqlQuery 객체를 매개변수 결합 및 bind() 메서드와 함께 재사용하는 것이 매번 객체를 다시 생성하는 것보다 더 빠릅니다. GqlQuery 객체를 매개변수 결합과 함께 전역 변수의 값에 저장한 후 각 요청의 새 매개변수 값을 결합하여 재사용할 수 있습니다.

  • 구성 및 데이터 파일. 애플리케이션이 파일에서 구성 데이터를 로드 및 파싱하는 경우 애플리케이션은 파싱된 데이터를 메모리에 보존하여 요청마다 파일을 다시 로드하는 것을 방지할 수 있습니다.

핸들러 스크립트는 가져올 때 main()을 호출해야 합니다. App Engine은 이 스크립트를 가져올 때 main()이 호출되리라 예상하기 때문에 서버에서 요청 핸들러를 처음으로 로드할 때 App Engine이 이 함수를 호출하지 않습니다.

main()을 통한 앱 캐싱은 CGI 핸들러의 응답 시간을 상당히 개선시키므로 CGI를 사용하는 모든 애플리케이션에 사용하는 것이 좋습니다.

이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...

Python 2용 App Engine 표준 환경