요청 처리 방법

리전 ID

REGION_ID는 앱을 만들 때 선택한 리전을 기준으로 Google에서 할당하는 축약된 코드입니다. 일부 리전 ID는 일반적으로 사용되는 국가 및 주/도 코드와 비슷하게 표시될 수 있지만 코드는 국가 또는 주/도와 일치하지 않습니다. 2020년 2월 이후에 생성된 앱의 경우 REGION_ID.r이 App Engine URL에 포함됩니다. 이 날짜 이전에 만든 기존 앱의 경우 URL에서 리전 ID는 선택사항입니다.

리전 ID에 대해 자세히 알아보세요.

이 문서는 App Engine 애플리케이션이 요청을 수신하고 응답을 전송하는 방법을 설명합니다.

자세한 내용은 요청 헤더 및 응답 참조를 참조하세요.

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

요청 처리

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

App Engine이 애플리케이션의 웹 요청을 받으면 애플리케이션의 WEB-INF/ 디렉터리에 있는 web.xml 파일의 설명대로 URL에 해당하는 서블릿을 호출합니다. 자바 서블릿 2.5 또는 3.1 API 사양을 지원하여 서블릿에 요청 데이터를 제공하고 응답 데이터를 수락합니다.

App Engine은 애플리케이션의 여러 인스턴스를 실행하며 각 인스턴스에는 요청 처리를 위한 자체 웹 서버가 있습니다. 모든 요청은 어떤 인스턴스에든 라우팅될 수 있으므로 동일한 사용자에게서 요청이 연속적으로 수신되더라도 항상 같은 인스턴스로 전송되지는 않습니다. 트래픽 변화에 따라 인스턴스 수를 자동으로 조정할 수 있습니다.

기본적으로 각 웹 서버는 한 번에 하나의 요청만 처리합니다. 여러 요청을 각 웹 서버에 동시에 전달하려면 <threadsafe>true</threadsafe> 요소를 appengine-web.xml 파일에 추가하여 애플리케이션을 threadsafe로 표시합니다.

다음 예의 서블릿 클래스는 사용자 브라우저에 간단한 메시지를 표시합니다.

// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(name = "requests", description = "Requests: Trivial request", urlPatterns = "/requests")
public class RequestsServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    resp.setContentType("text/plain");
    resp.getWriter().println("Hello, world");
  }
}

할당량 및 한도

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

  • App Engine은 애플리케이션이 요청에 응답하는 시간이 1초 미만인, 지연 시간이 짧은 애플리케이션을 위해 자동 확장 용량을 예약합니다.

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

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

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

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

한도 용량
요청 크기 32MB
응답 크기 32MB
요청 제한 시간 앱에서 사용하는 확장 유형에 따라 다름
최대 총 파일 수(앱 파일과 정적 파일) 총 10,000개
디렉터리당 1,000개
애플리케이션 파일 최대 크기 32MB
정적 파일 최대 크기 32MB
모든 애플리케이션과 정적 파일의 최대 총 크기 첫 1GB는 무료
첫 1GB 이후 1GB당 월 $0.026
대기 중인 요청 제한 시간 10초
단일 요청 헤더 필드의 최대 크기 표준 환경의 2세대 런타임의 경우 8KB입니다. 해당 런타임에 대한 요청의 헤더 필드가 8KB를 초과하면 HTTP 400 오류가 반환됩니다.

요청 한도

모든 HTTP/2 요청은 애플리케이션 서버로 전달될 때 HTTP/1.1 요청으로 변환됩니다.

응답 한도

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

  • 2세대 런타임의 응답 헤더 한도는 8KB입니다. 이 제한을 초과하는 응답 헤더는 upstream sent too big header while reading response header from upstream을 표시하는 로그와 함께 HTTP 502 오류를 반환합니다.

요청 헤더

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

자세한 내용은 요청 헤더 참조를 확인하세요.

요청 제한 시간 처리

App Engine은 단기 요청, 일반적으로 수백 밀리초 정도 지속되는 요청을 사용하는 애플리케이션에 최적화되어 있습니다. 효율적인 앱은 대부분의 요청에 신속하게 응답합니다. 그렇지 않은 앱은 App Engine 인프라에 맞춰 제대로 확장되지 않습니다. 이러한 수준의 성능을 보장하기 위해 모든 앱에서 응답해야 하는 시스템 적용 최대 요청 제한 시간이 있습니다.

앱이 이 기한을 초과하면 App Engine은 요청 핸들러를 중단합니다. 자바 런타임 환경은 com.google.apphosting.api.DeadlineExceededException을 발생시켜 서블릿을 중단합니다. 이 예외를 포착할 요청 핸들러가 없으면 런타임 환경에서 HTTP 500 서버 오류를 클라이언트에 반환합니다.

요청 핸들러가 있고 DeadlineExceededException이 포착되면 런타임 환경에서 커스텀 응답을 준비할 시간(1초 미만)을 요청 핸들러에 제공합니다. 커스텀 응답 준비를 위해 예외를 발생시킨 후 요청 핸들러에서 시간이 1초를 초과하면 HardDeadlineExceededError가 발생합니다.

DeadlineExceededExceptionsHardDeadlineExceededErrors 모두 요청을 강제 종료하고 인스턴스를 중지합니다.

기한이 얼마나 남았는지 확인하려면 애플리케이션에서 com.google.apphosting.api.ApiProxy를 가져오고 ApiProxy.getCurrentEnvironment().getRemainingMillis()를 호출하면 됩니다. 이는 애플리케이션에서 시간이 오래 걸리는 작업을 시작할 예정일 때 유용합니다. 작업 단위 하나를 처리하는 데 5초가 필요하지만 getRemainingMillis()가 더 짧은 시간을 반환하면 이 작업 단위를 시작할 의미가 없습니다.

응답

App Engine은 요청 객체와 응답 객체를 사용해 서블릿을 호출한 후 서블릿이 응답 객체를 채우고 반환되기를 기다립니다. 서블릿이 반환되면 응답 객체의 데이터가 사용자에게 전송됩니다.

생성하는 응답에는 크기 제한이 적용되며 응답이 클라이언트에 반환되기 전에 이 응답을 수정할 수 있습니다.

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

스트리밍 응답

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

응답 압축

App Engine은 압축된(gzipped) 콘텐츠를 지원하는 클라이언트에 이 콘텐츠를 제공하기 위해 최선을 다하고 있습니다. 콘텐츠 압축 여부를 결정하기 위해 App Engine은 요청을 수신할 때 다음을 수행합니다.

  1. 요청에서 Accept-EncodingUser-Agent 헤더를 모두 확인하여 클라이언트가 압축된 응답을 안정적으로 수신할 수 있는지 확인합니다. 이러한 방법은 일반 브라우저에서 잘 알려진 gzip 콘텐츠 관련 버그의 일부를 예방해줍니다.

  2. 응답 핸들러에 구성한 Content-Type 헤더를 확인하고 콘텐츠를 압축하는 것이 적절한지 확인합니다. 일반적으로 텍스트 기반 콘텐츠 유형은 압축되지만 바이너리 콘텐츠 유형은 압축되지 않습니다.

다음에 유의하세요.

  • 클라이언트는 Accept-EncodingUser-Agent 요청 헤더를 모두 gzip으로 설정하여 텍스트 기반 콘텐츠 유형을 강제로 압축할 수 있습니다.

  • 요청의 Accept-Encoding 헤더에 gzip이 지정되지 않으면 App Engine이 응답 데이터를 압축하지 않습니다.

  • Google 프런트엔드에서 App Engine 정적 파일 및 디렉터리 핸들러에서 수신한 응답을 캐시합니다. 어떤 유형의 응답 데이터가 먼저 캐시되는지, 응답에 어떤 Vary 헤더를 지정했는지, 요청에 어떤 헤더가 포함되는지 등의 다양한 요소에 따라 클라이언트에서 압축 데이터를 요청했지만 압축되지 않은 데이터를 수신할 수도 있고 그 반대일 수도 있습니다. 자세한 내용은 응답 캐싱을 참조하세요.

응답 캐싱

Google 프런트엔드와 잠재적으로 사용자 브라우저 및 기타 중간 캐싱 프록시 서버가 응답에 지정한 표준 캐싱 헤더 설명에 따라 앱의 응답을 캐시합니다. 프레임워크를 통해 코드에서 직접 지정하거나 App Engine 정적 파일 및 디렉터리 핸들러를 통해 이러한 응답 헤더를 지정할 수 있습니다.

Google 프런트엔드에서 캐시 키는 요청의 전체 URL입니다.

정적 콘텐츠 캐싱

업데이트된 정적 콘텐츠가 게시되는 즉시 클라이언트에서 이를 확인하려면 css/v1/styles.css와 같은 버전 디렉터리에서 정적 콘텐츠를 제공하는 것이 좋습니다. Google 프런트엔드는 캐시가 만료될 때까지 캐시를 검증하지 않고 업데이트된 콘텐츠를 확인합니다. 캐시가 만료된 후에도 요청 URL의 콘텐츠가 변경될 때까지 캐시가 업데이트되지 않습니다.

appengine-web.xml에서 설정할 수 있는 다음 응답 헤더는 Google 프런트엔드에서 콘텐츠를 캐시하는 방식과 시기에 영향을 줍니다.

  • Google 프런트엔드에서 콘텐츠를 캐시하려면 Cache-Controlpublic으로 설정해야 합니다. Cache-Control private 또는 no-store 지시문을 지정하지 않는 한 Google 프런트엔드에서 캐시할 수도 있습니다. appengine-web.xml에 이 헤더를 설정하지 않으면 App Engine이 정적 파일이나 디렉터리 핸들러에서 처리하는 모든 응답에 자동으로 헤더를 추가합니다. 자세한 내용은 추가 또는 대체되는 헤더를 참조하세요.

  • Vary: 요청에서 전송되는 헤더를 기반으로 URL에 대해 서로 다른 응답을 반환하도록 하려면 Vary 응답 헤더에 Accept, Accept-Encoding, Origin, X-Origin 값 중 하나 이상을 설정합니다.

    카디널리티가 발생할 가능성이 높기 때문에 다른 Vary 값에서는 데이터가 캐시되지 않습니다.

    예를 들면 다음과 같습니다.

    1. 다음 응답 헤더를 지정합니다.

      Vary: Accept-Encoding

    2. 앱에서 Accept-Encoding: gzip 헤더가 포함된 요청을 받습니다. App Engine은 압축된 응답을 반환하고 Google 프런트엔드는 응답 데이터의 gzip 버전을 캐시합니다. Accept-Encoding: gzip 헤더가 포함된 이 URL의 모든 후속 요청은 캐시가 만료된 후 콘텐츠가 변경되어 캐시가 무효화되기 전까지는 캐시에서 gzip 처리된 데이터를 수신합니다.

    3. 앱에서 Accept-Encoding 헤더를 포함하지 않는 요청을 수신합니다. App Engine은 압축되지 않은 응답을 반환하고 Google 프런트엔드는 압축되지 않은 버전의 응답 데이터를 캐시합니다. Accept-Encoding 헤더가 포함되지 않은 이 URL의 모든 후속 요청은 캐시가 무효화될 때까지 캐시에서 압축된 데이터를 수신합니다.

    Vary 응답 헤더를 지정하지 않으면 Google 프런트엔드는 URL에 대한 단일 캐시 항목을 만들고 요청의 헤더와 관계없이 모든 요청에 이 항목을 사용합니다. 예를 들면 다음과 같습니다.

    1. Vary: Accept-Encoding 응답 헤더를 지정하지 않습니다.
    2. 요청에 Accept-Encoding: gzip 헤더가 포함되고 응답 데이터의 gzip 버전이 캐시됩니다.
    3. 두 번째 요청에는 Accept-Encoding: gzip 헤더가 포함되어 있지 않습니다. 그러나 캐시에는 응답 데이터의 gzip 버전이 포함되어 있으므로 클라이언트가 압축되지 않은 데이터를 요청했더라도 응답이 gzip으로 압축됩니다.

요청에 포함된 헤더는 캐싱에도 영향을 미칩니다.

  • 요청에 Authorization 헤더가 포함된 경우 Google 프런트엔드에서 콘텐츠를 캐시하지 않습니다.

캐시 만료

기본적으로 App Engine 정적 파일 및 디렉터리 핸들러가 응답에 추가하는 캐싱 헤더는 클라이언트 및 Google 프런트엔드와 같은 웹 프록시에 10분 후에 캐시를 만료하도록 지시합니다.

지정된 만료 시간으로 파일이 전송된 후에는 사용자가 자신의 브라우저 캐시를 삭제하더라도 웹 프록시 캐시에서 파일을 지울 수 있는 일반적인 방법은 없습니다. 앱의 새 버전을 다시 배포해도 캐시는 재설정되지 않습니다. 따라서 정적 파일을 수정할 계획이 있다면 만료 시간이 짧아야 합니다(1시간 미만). 대부분의 경우 만료 시간 기본값인 10분이 적합합니다.

appengine-web.xml 파일에서 static-files 요소를 지정하면 모든 정적 파일 및 디렉터리 핸들러의 기본 만료 시간을 변경할 수 있습니다.

로깅

애플리케이션에서 java.util.logging.Logger를 사용하여 애플리케이션 로그에 정보를 작성할 수 있습니다. Cloud Logging을 사용하여 Google Cloud 콘솔에서 애플리케이션의 로그 데이터를 볼 수 있습니다. 로깅된 각 요청에는 요청 시작 시간을 기준으로 전역적으로 고유한 식별자인 요청 ID가 할당됩니다. Google Cloud 콘솔은 Logger 클래스의 로그 수준을 인식하고 메시지를 다양한 수준에서 대화식으로 표시할 수 있습니다.

서블릿이 표준 출력 스트림(System.out)과 표준 오류 스트림(System.err)에 작성하는 모든 데이터는 App Engine에서 캡처되어 애플리케이션 로그에 기록됩니다. 표준 출력 스트림에 작성된 행은 '정보' 수준에서 기록되고 표준 오류 스트림에 작성된 행은 '경고' 수준에서 기록됩니다. 출력 또는 오류 스트림에 로깅하는 모든 로깅 프레임워크(log4j 등)가 작동합니다. 하지만 Google Cloud 콘솔의 로그 수준 표시를 더욱 세밀하게 제어할 수 있도록 로깅 프레임워크는 java.util.logging 어댑터를 사용해야 합니다.

// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(
    name = "RequestLogging",
    description = "Requests: Logging example",
    urlPatterns = "/requests/log"
)
public class LoggingServlet extends HttpServlet {

  private static final Logger log = Logger.getLogger(LoggingServlet.class.getName());

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    log.info("An informational message.");
    log.warning("A warning message.");
    log.severe("An error message.");
    // ...
  }
}

App Engine 자바 SDK에는 appengine-java-sdk/config/user/ 디렉터리에 템플릿 logging.properties 파일이 포함되어 있습니다. 이 파일을 사용하려면 파일을 WEB-INF/classes 디렉터리(또는 WAR의 다른 위치)에 복사한 후 시스템 속성 java.util.logging.config.file"WEB-INF/logging.properties"(또는 애플리케이션 루트를 기준으로 선택한 경로)에 복사합니다. 다음과 같이 appengine-web.xml 파일에서 시스템 속성을 설정할 수 있습니다.

<appengine-web-app xmlns="http://appengine.google.com/ns/1.0"> ... <system-properties> <property name="java.util.logging.config.file" value="WEB-INF/logging.properties" /> </system-properties> </appengine-web-app>

서블릿은 INFO 로그 수준(log.info() 사용)을 사용하여 메시지를 로깅합니다. 기본 로그 수준은 WARNING이며, 출력에서 INFO 메시지를 표시하지 않습니다. 로그 수준을 변경하려면 logging.properties 파일을 수정합니다.

환경

모든 시스템 속성 및 환경 변수는 애플리케이션에 공개되지 않습니다. 시스템 속성을 설정하면 JVM 뷰가 아닌 해당 속성의 애플리케이션 뷰에만 영향을 미칩니다.

배포 설명자로 앱의 시스템 속성 및 환경 변수를 설정할 수 있습니다.

App Engine에서 런타임 환경을 식별하는 여러 시스템 속성을 설정합니다.

  • com.google.appengine.runtime.environment는 App Engine에서 실행 중인 경우에는 "Production"이고, 개발 서버에서 실행 중인 경우에는 "Development"입니다.

    System.getProperty() 사용 외에도 type-safe API를 사용하여 시스템 속성에 액세스할 수 있습니다. 예를 들면 다음과 같습니다.

    if (SystemProperty.environment.value() ==
        SystemProperty.Environment.Value.Production) {
        // The app is running on App Engine...
    }
    
  • com.google.appengine.runtime.version"1.3.0"과 같은 런타임 환경의 버전 ID입니다. String version = SystemProperty.version.get();을 호출하면 버전을 가져올 수 있습니다.

  • com.google.appengine.application.id는 애플리케이션의 ID입니다. String ID = SystemProperty.applicationId.get();을 호출하면 이 ID를 가져올 수 있습니다.

  • com.google.appengine.application.version은 현재 실행 중인 애플리케이션 서비스의 주 버전 및 부 버전(예: 'X.Y')입니다. 주 버전 번호('X')는 서비스의 appengine-web.xml 파일에 지정됩니다. 부 버전 번호('Y')는 앱의 각 버전이 App Engine에 업로드될 때 자동으로 설정됩니다. String ID = SystemProperty.applicationVersion.get();을 호출하면 이 ID를 가져올 수 있습니다.

    개발 웹 서버에서 반환되는 주 버전은 항상 기본 서비스의 버전이고 부 버전은 항상 '1'입니다.

App Engine에서는 앱 서버에서 JVM을 초기화할 때 다음 시스템 속성도 설정합니다.

  • file.separator
  • path.separator
  • line.separator
  • java.version
  • java.vendor
  • java.vendor.url
  • java.class.version
  • java.specification.version
  • java.specification.vendor
  • java.specification.name
  • java.vm.vendor
  • java.vm.name
  • java.vm.specification.version
  • java.vm.specification.vendor
  • java.vm.specification.name
  • user.dir

인스턴스 ID

이 코드를 사용하면 요청을 처리하는 인스턴스의 ID를 검색할 수 있습니다.

com.google.apphosting.api.ApiProxy.getCurrentEnvironment().getAttributes().get("com.google.appengine.instance.id")

프로덕션 환경에서 로그인한 관리자는 URL(https://INSTANCE_ID-dot-VERSION_ID-dot-SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com)에서 ID를 사용할 수 있습니다. 요청은 해당하는 특정 인스턴스로 라우팅됩니다. 인스턴스에서 요청을 처리할 수 없는 경우 즉시 503이 반환됩니다.

요청 ID

요청 시에 요청 별로 고유한 요청 ID를 저장할 수 있습니다. 요청 ID는 나중에 다른 요청을 해당 요청의 로그와 연결하는 데 사용할 수 있습니다.

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

com.google.apphosting.api.ApiProxy.getCurrentEnvironment().getAttributes().get("com.google.appengine.runtime.request_log_id")

HTTPS 연결 강제 적용

보안상의 이유로 모든 애플리케이션은 클라이언트가 https를 통해 연결하도록 권장해야 합니다. 브라우저에서 특정 페이지 또는 전체 도메인에 대해 http보다 https를 우선 사용하도록 지시하려면 응답에 Strict-Transport-Security 헤더를 설정합니다. 예를 들면 다음과 같습니다.

Strict-Transport-Security: max-age=31536000; includeSubDomains
앱에서 제공하는 모든 정적 콘텐츠에 이 헤더를 설정하려면 앱의 정적 파일 및 디렉터리 핸들러에 헤더를 추가합니다.

대부분의 앱 프레임워크 및 웹 서버는 코드에서 생성된 응답용 헤더를 설정하기 위한 지원을 제공합니다. Spring Boot의 Strict-Transport-Security 헤더에 관한 자세한 내용은 HTTP Strict Transport Security(HSTS)를 참조하세요.

비동기 백그라운드 작업 처리

백그라운드 작업은 HTTP 응답을 전송한 후에 앱이 요청에 수행하는 작업을 의미합니다. 앱에서 백그라운드 작업을 수행하지 못하게 하고 코드를 검토하여 응답을 전달하기 전에 모든 비동기 작업이 완료되었는지 확인합니다.

장기 실행 작업의 경우 Cloud Tasks를 사용하는 것이 좋습니다. Cloud Tasks를 사용하면 HTTP 요청이 오랫동안 유지되며 비동기 작업이 종료된 후에만 응답을 반환합니다.