요청 처리 방법

리전 ID

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

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

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

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

요청 처리

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

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

App Engine용 Go 런타임은 Go 프로그램과 App Engine 서버 간의 인터페이스로 표준 http 패키지를 사용합니다. App Engine이 애플리케이션에 대한 웹 요청을 수신하면 요청 URL과 연결된 http.Handler를 호출합니다.

다음은 하드 코딩된 HTML 문자열을 사용자에게 출력하는 완전한 Go 앱의 예입니다.


// Sample helloworld is an App Engine app.
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", indexHandler)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
		log.Printf("Defaulting to port %s", port)
	}

	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

// indexHandler responds to requests with our greeting.
func indexHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	fmt.Fprint(w, "Hello, World!")
}

할당량 및 한도

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

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

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

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

HTTP 및 HTTPS(보안) 요청은 모두 요청, 수신 대역폭(청구 가능), 발신 대역폭(청구 가능) 한도에 반영됩니다. Console의 할당량 세부정보 페이지도 정보 제공 목적으로 보안 요청, 보안 수신 대역폭, 보안 발신 대역폭을 별도의 값으로 보고합니다. 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은 요청 핸들러를 중단합니다. Go 요청 핸들러의 경우 프로세스가 중지되고 런타임 환경이 HTTP 500 내부 서버 오류를 클라이언트에 반환합니다.

응답

App Engine은 RequestResponseWriter로 핸들러를 호출한 후 핸들러가 ResponseWriter에 쓰고 반환할 때까지 대기합니다. 핸들러가 반환하면 ResponseWriter의 내부 버퍼에 있는 데이터가 사용자에게 전송됩니다.

이는 http 패키지를 사용하는 정상적인 Go 프로그램을 작성할 때와 사실상 동일합니다.

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

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

스트리밍 응답

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

응답 압축

코드에서 반환된 응답의 경우 다음 두 조건이 모두 충족되면 App Engine이 응답 데이터를 압축합니다.

  • 요청에 값으로 gzip을 포함하는 Accept-Encoding 헤더가 있습니다.
  • 응답에는 HTML, CSS, 자바스크립트와 같은 텍스트 기반 데이터가 포함됩니다.

App Engine 정적 파일 또는 디렉터리 핸들러에서 반환된 응답의 경우 다음 조건이 모두 충족되면 응답 데이터가 압축됩니다.

  • 요청에는 gzip을 값 중 하나로 사용하는 Accept-Encoding이 포함됩니다.
  • 클라이언트가 압축된 형식의 응답 데이터를 수신할 수 있습니다. Google 프런트엔드는 압축된 응답 관련 문제가 있는 것으로 알려진 클라이언트 목록을 유지합니다. 이러한 클라이언트는 요청 헤더에 Accept-Encoding: gzip이 포함되어 있더라도 앱의 정적 핸들러에서 전송된 압축 데이터를 수신하지 않습니다.
  • 응답에는 HTML, CSS, 자바스크립트와 같은 텍스트 기반 데이터가 포함됩니다.

다음에 유의하세요.

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

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

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

응답 캐싱

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

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

정적 콘텐츠 캐싱

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

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

  • Google 프런트엔드에서 콘텐츠를 캐시하려면 Cache-Controlpublic으로 설정해야 합니다. Cache-Control private 또는 no-store 지시문을 지정하지 않는 한 Google 프런트엔드에서 캐시할 수도 있습니다. app.yaml에 이 헤더를 설정하지 않으면 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분이 적합합니다.

app.yaml 파일에서 default_expiration 요소를 지정하면 모든 정적 파일 및 디렉터리 핸들러의 기본 만료 시간을 변경할 수 있습니다. 개별 핸들러의 특정 만료 시간을 설정하려면 app.yaml 파일에서 핸들러 요소 안의 expiration 요소를 지정합니다.

만료 요소 시간에 지정된 값은 Cache-ControlExpires HTTP 응답 헤더를 설정하는 데 사용됩니다.

HTTPS 연결 강제 적용

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

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

코드에서 생성된 응답에 이 헤더를 설정하려면 secureheader 패키지를 사용합니다.

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

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

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