示例#1
0
  def test_get_headers_from_environ(self):
    environ = {'SERVER_PORT': '42', 'REQUEST_METHOD': 'GET',
               'SERVER_NAME': 'localhost',
               'CONTENT_TYPE': 'application/json',
               'HTTP_CONTENT_LENGTH': '0', 'HTTP_X_USER_IP': '127.0.0.1'}
    headers = util.get_headers_from_environ(environ)

    self.assertEqual(len(headers), 3)
    self.assertEqual(headers['Content-Type'], 'application/json')
    self.assertEqual(headers['Content-Length'], '0')
    self.assertEqual(headers['X-User-IP'], '127.0.0.1')
示例#2
0
  def test_get_headers_from_environ(self):
    environ = {'SERVER_PORT': '42', 'REQUEST_METHOD': 'GET',
               'SERVER_NAME': 'localhost',
               'CONTENT_TYPE': 'application/json',
               'HTTP_CONTENT_LENGTH': '0', 'HTTP_X_USER_IP': '127.0.0.1'}
    headers = util.get_headers_from_environ(environ)

    self.assertEqual(len(headers), 3)
    self.assertEqual(headers['Content-Type'], 'application/json')
    self.assertEqual(headers['Content-Length'], '0')
    self.assertEqual(headers['X-User-IP'], '127.0.0.1')
    def __init__(self, environ):
        """Constructor.

    Args:
      environ: An environ dict for the request as defined in PEP-333.

    Raises:
      ValueError: If the path for the request is invalid.
    """
        self.headers = util.get_headers_from_environ(environ)
        self.http_method = environ['REQUEST_METHOD']
        self.server = environ['SERVER_NAME']
        self.port = environ['SERVER_PORT']
        self.path = environ['PATH_INFO']
        self.query = environ.get('QUERY_STRING')
        self.body = environ['wsgi.input'].read()
        if self.body and self.headers.get('CONTENT-ENCODING') == 'gzip':
            # Increasing wbits to 16 + MAX_WBITS is necessary to be able to decode
            # gzipped content (as opposed to zlib-encoded content).
            self.body = zlib.decompress(self.body, 16 + zlib.MAX_WBITS)
        self.source_ip = environ.get('REMOTE_ADDR')
        self.relative_url = self._reconstruct_relative_url(environ)

        if not self.path.startswith(self._API_PREFIX):
            raise ValueError('Invalid request path: %s' % self.path)
        self.path = self.path[len(self._API_PREFIX):]
        if self.query:
            self.parameters = cgi.parse_qs(self.query, keep_blank_values=True)
        else:
            self.parameters = {}
        self.body_json = json.loads(self.body) if self.body else {}
        self.request_id = None

        # Check if it's a batch request.  We'll only handle single-element batch
        # requests on the dev server (and we need to handle them because that's
        # what RPC and JS calls typically show up as).  Pull the request out of the
        # list and record the fact that we're processing a batch.
        if isinstance(self.body_json, list):
            if len(self.body_json) != 1:
                logging.warning(
                    'Batch requests with more than 1 element aren\'t '
                    'supported in devappserver2.  Only the first element '
                    'will be handled.  Found %d elements.',
                    len(self.body_json))
            else:
                logging.info('Converting batch request to single request.')
            self.body_json = self.body_json[0]
            self.body = json.dumps(self.body_json)
            self._is_batch = True
        else:
            self._is_batch = False
示例#4
0
  def __init__(self, environ):
    """Constructor.

    Args:
      environ: An environ dict for the request as defined in PEP-333.

    Raises:
      ValueError: If the path for the request is invalid.
    """
    self.headers = util.get_headers_from_environ(environ)
    self.http_method = environ['REQUEST_METHOD']
    self.server = environ['SERVER_NAME']
    self.port = environ['SERVER_PORT']
    self.path = environ['PATH_INFO']
    self.query = environ.get('QUERY_STRING')
    self.body = environ['wsgi.input'].read()
    if self.body and self.headers.get('CONTENT-ENCODING') == 'gzip':
      # Increasing wbits to 16 + MAX_WBITS is necessary to be able to decode
      # gzipped content (as opposed to zlib-encoded content).
      self.body = zlib.decompress(self.body, 16 + zlib.MAX_WBITS)
    self.source_ip = environ.get('REMOTE_ADDR')
    self.relative_url = self._reconstruct_relative_url(environ)

    if not self.path.startswith(self._API_PREFIX):
      raise ValueError('Invalid request path: %s' % self.path)
    self.path = self.path[len(self._API_PREFIX):]
    if self.query:
      self.parameters = cgi.parse_qs(self.query, keep_blank_values=True)
    else:
      self.parameters = {}
    self.body_json = json.loads(self.body) if self.body else {}
    self.request_id = None

    # Check if it's a batch request.  We'll only handle single-element batch
    # requests on the dev server (and we need to handle them because that's
    # what RPC and JS calls typically show up as).  Pull the request out of the
    # list and record the fact that we're processing a batch.
    if isinstance(self.body_json, list):
      if len(self.body_json) != 1:
        logging.warning('Batch requests with more than 1 element aren\'t '
                        'supported in devappserver2.  Only the first element '
                        'will be handled.  Found %d elements.',
                        len(self.body_json))
      else:
        logging.info('Converting batch request to single request.')
      self.body_json = self.body_json[0]
      self.body = json.dumps(self.body_json)
      self._is_batch = True
    else:
      self._is_batch = False
示例#5
0
    def handle(self, environ, start_response, url_map, match, request_id,
               request_type):
        """Serves this request by forwarding it to the runtime process.

    Args:
      environ: An environ dict for the request as defined in PEP-333.
      start_response: A function with semantics defined in PEP-333.
      url_map: An appinfo.URLMap instance containing the configuration for the
          handler matching this request.
      match: A re.MatchObject containing the result of the matched URL pattern.
      request_id: A unique string id associated with the request.
      request_type: The type of the request. See instance.*_REQUEST module
          constants.

    Yields:
      A sequence of strings containing the body of the HTTP response.
    """
        if self._prior_error:
            yield self._handle_error(self._prior_error, start_response)
            return

        environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(
            url_map.script)
        if request_type == instance.BACKGROUND_REQUEST:
            environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background'
        elif request_type == instance.SHUTDOWN_REQUEST:
            environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown'
        elif request_type == instance.INTERACTIVE_REQUEST:
            environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive'

        for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
            if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ:
                value = environ.get(name, None)
                if value is not None:
                    environ[http_runtime_constants.INTERNAL_ENVIRON_PREFIX +
                            name] = value
        headers = util.get_headers_from_environ(environ)
        if environ.get('QUERY_STRING'):
            url = '%s?%s' % (urllib.quote(
                environ['PATH_INFO']), environ['QUERY_STRING'])
        else:
            url = urllib.quote(environ['PATH_INFO'])
        if 'CONTENT_LENGTH' in environ:
            headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH']
            data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
        else:
            data = ''

        cookies = environ.get('HTTP_COOKIE')
        user_email, admin, user_id = login.get_user_info(cookies)
        if user_email:
            nickname, organization = user_email.split('@', 1)
        else:
            nickname = ''
            organization = ''
        headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id
        headers[http_runtime_constants.INTERNAL_HEADER_PREFIX +
                'User-Id'] = (user_id)
        headers[http_runtime_constants.INTERNAL_HEADER_PREFIX +
                'User-Email'] = (user_email)
        headers[http_runtime_constants.INTERNAL_HEADER_PREFIX +
                'User-Is-Admin'] = (str(int(admin)))
        headers[http_runtime_constants.INTERNAL_HEADER_PREFIX +
                'User-Nickname'] = (nickname)
        headers[http_runtime_constants.INTERNAL_HEADER_PREFIX +
                'User-Organization'] = (organization)
        headers['X-AppEngine-Country'] = 'ZZ'
        connection = httplib.HTTPConnection(self._host, self._port)
        with contextlib.closing(connection):
            try:
                connection.connect()
                connection.request(environ.get('REQUEST_METHOD', 'GET'), url,
                                   data, dict(headers.items()))

                try:
                    response = connection.getresponse()
                except httplib.HTTPException as e:
                    # The runtime process has written a bad HTTP response. For example,
                    # a Go runtime process may have crashed in app-specific code.
                    yield self._handle_error(
                        'the runtime process gave a bad HTTP response: %s' % e,
                        start_response)
                    return

                # Ensures that we avoid merging repeat headers into a single header,
                # allowing use of multiple Set-Cookie headers.
                headers = []
                for name in response.msg:
                    for value in response.msg.getheaders(name):
                        headers.append((name, value))

                response_headers = wsgiref.headers.Headers(headers)

                error_file = self._get_error_file()
                if (error_file and http_runtime_constants.ERROR_CODE_HEADER
                        in response_headers):
                    try:
                        with open(error_file) as f:
                            content = f.read()
                    except IOError:
                        content = 'Failed to load error handler'
                        logging.exception('failed to load error file: %s',
                                          error_file)
                    start_response('500 Internal Server Error',
                                   [('Content-Type', 'text/html'),
                                    ('Content-Length', str(len(content)))])
                    yield content
                    return
                del response_headers[http_runtime_constants.ERROR_CODE_HEADER]
                start_response('%s %s' % (response.status, response.reason),
                               response_headers.items())

                # Yield the response body in small blocks.
                while True:
                    try:
                        block = response.read(512)
                        if not block:
                            break
                        yield block
                    except httplib.HTTPException:
                        # The runtime process has encountered a problem, but has not
                        # necessarily crashed. For example, a Go runtime process' HTTP
                        # handler may have panicked in app-specific code (which the http
                        # package will recover from, so the process as a whole doesn't
                        # crash). At this point, we have already proxied onwards the HTTP
                        # header, so we cannot retroactively serve a 500 Internal Server
                        # Error. We silently break here; the runtime process has presumably
                        # already written to stderr (via the Tee).
                        break
            except Exception:
                with self._process_lock:
                    if self._process and self._process.poll() is not None:
                        # The development server is in a bad state. Log and return an error
                        # message.
                        self._prior_error = (
                            'the runtime process for the instance running '
                            'on port %d has unexpectedly quit' % (self._port))
                        yield self._handle_error(self._prior_error,
                                                 start_response)
                    else:
                        raise
示例#6
0
  def handle(self, environ, start_response, url_map, match, request_id,
             request_type):
    """Serves this request by forwarding it to the runtime process.

    Args:
      environ: An environ dict for the request as defined in PEP-333.
      start_response: A function with semantics defined in PEP-333.
      url_map: An appinfo.URLMap instance containing the configuration for the
          handler matching this request.
      match: A re.MatchObject containing the result of the matched URL pattern.
      request_id: A unique string id associated with the request.
      request_type: The type of the request. See instance.*_REQUEST module
          constants.

    Yields:
      A sequence of strings containing the body of the HTTP response.
    """
    environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script)
    if request_type == instance.BACKGROUND_REQUEST:
      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background'
    elif request_type == instance.SHUTDOWN_REQUEST:
      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown'
    elif request_type == instance.INTERACTIVE_REQUEST:
      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive'

    for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
      if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ:
        value = environ.get(name, None)
        if value is not None:
          environ[
              http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name] = value
    headers = util.get_headers_from_environ(environ)
    if environ.get('QUERY_STRING'):
      url = '%s?%s' % (urllib.quote(environ['PATH_INFO']),
                       environ['QUERY_STRING'])
    else:
      url = urllib.quote(environ['PATH_INFO'])
    if 'CONTENT_LENGTH' in environ:
      headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH']
      data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
    else:
      data = ''

    cookies = environ.get('HTTP_COOKIE')
    user_email, admin, user_id = login.get_user_info(cookies)
    if user_email:
      nickname, organization = user_email.split('@', 1)
    else:
      nickname = ''
      organization = ''
    headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id
    headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Id'] = (
        user_id)
    headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Email'] = (
        user_email)
    headers[
        http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Is-Admin'] = (
            str(int(admin)))
    headers[
        http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Nickname'] = (
            nickname)
    headers[
        http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Organization'] = (
            organization)
    headers['X-AppEngine-Country'] = 'ZZ'
    connection = httplib.HTTPConnection(self._host, self._port)
    with contextlib.closing(connection):
      try:
        connection.connect()
        connection.request(environ.get('REQUEST_METHOD', 'GET'),
                           url,
                           data,
                           dict(headers.items()))
        response = connection.getresponse()

        # Ensures that we avoid merging repeat headers into a single header,
        # allowing use of multiple Set-Cookie headers.
        headers = []
        for name in response.msg:
          for value in response.msg.getheaders(name):
            headers.append((name, value))

        response_headers = wsgiref.headers.Headers(headers)

        error_file = self._get_error_file()
        if (error_file and
            http_runtime_constants.ERROR_CODE_HEADER in response_headers):
          try:
            with open(error_file) as f:
              content = f.read()
          except IOError:
            content = 'Failed to load error handler'
            logging.exception('failed to load error file: %s', error_file)
          start_response('500 Internal Server Error',
                         [('Content-Type', 'text/html'),
                          ('Content-Length', str(len(content)))])
          yield content
          return
        del response_headers[http_runtime_constants.ERROR_CODE_HEADER]
        start_response('%s %s' % (response.status, response.reason),
                       response_headers.items())

        # Yield the response body in small blocks.
        block = response.read(512)
        while block:
          yield block
          block = response.read(512)
      except Exception:
        with self._process_lock:
          if self._process and self._process.poll() is not None:
            # The development server is in a bad state. Log and return an error
            # message and quit.
            message = ('the runtime process for the instance running on port '
                       '%d has unexpectedly quit; exiting the development '
                       'server' % (
                           self._port))
            logging.error(message)
            start_response('500 Internal Server Error',
                           [('Content-Type', 'text/plain'),
                            ('Content-Length', str(len(message)))])
            shutdown.async_quit()
            yield message
          else:
            raise
示例#7
0
  def handle(self, environ, start_response, url_map, match, request_id,
             request_type):
    """Serves this request by forwarding it to the runtime process.

    Args:
      environ: An environ dict for the request as defined in PEP-333.
      start_response: A function with semantics defined in PEP-333.
      url_map: An appinfo.URLMap instance containing the configuration for the
          handler matching this request.
      match: A re.MatchObject containing the result of the matched URL pattern.
      request_id: A unique string id associated with the request.
      request_type: The type of the request. See instance.*_REQUEST module
          constants.

    Yields:
      A sequence of strings containing the body of the HTTP response.
    """

    if self._prior_error:
      logging.error(self._prior_error)
      yield self._respond_with_error(self._prior_error, start_response)
      return

    environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script)
    if request_type == instance.BACKGROUND_REQUEST:
      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background'
    elif request_type == instance.SHUTDOWN_REQUEST:
      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown'
    elif request_type == instance.INTERACTIVE_REQUEST:
      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive'

    for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
      if http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name not in environ:
        value = environ.get(name, None)
        if value is not None:
          environ[
              http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name] = value
    headers = util.get_headers_from_environ(environ)
    if environ.get('QUERY_STRING'):
      url = '%s?%s' % (urllib.quote(environ['PATH_INFO']),
                       environ['QUERY_STRING'])
    else:
      url = urllib.quote(environ['PATH_INFO'])
    if 'CONTENT_LENGTH' in environ:
      headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH']
      data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
    else:
      data = None

    cookies = environ.get('HTTP_COOKIE')
    user_email, admin, user_id = login.get_user_info(cookies)
    if user_email:
      nickname, organization = user_email.split('@', 1)
    else:
      nickname = ''
      organization = ''
    headers[self.request_id_header_name] = request_id
    headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Id'] = (
        user_id)
    headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Email'] = (
        user_email)
    headers[
        http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Is-Admin'] = (
            str(int(admin)))
    headers[
        http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Nickname'] = (
            nickname)
    headers[http_runtime_constants.APPENGINE_HEADER_PREFIX +
            'User-Organization'] = organization
    headers['X-AppEngine-Country'] = 'ZZ'
    connection = httplib.HTTPConnection(self._host, self._port)
    with contextlib.closing(connection):
      try:
        connection.connect()
        connection.request(environ.get('REQUEST_METHOD', 'GET'),
                           url,
                           data,
                           dict(headers.items()))

        try:
          response = connection.getresponse()
        except httplib.HTTPException as e:
          # The runtime process has written a bad HTTP response. For example,
          # a Go runtime process may have crashed in app-specific code.
          yield self._respond_with_error(
              'the runtime process gave a bad HTTP response: %s' % e,
              start_response)
          return

        # Ensures that we avoid merging repeat headers into a single header,
        # allowing use of multiple Set-Cookie headers.
        headers = []
        for name in response.msg:
          for value in response.msg.getheaders(name):
            headers.append((name, value))

        response_headers = wsgiref.headers.Headers(headers)

        if self._error_handler_file and (
            http_runtime_constants.ERROR_CODE_HEADER in response_headers):
          try:
            with open(self._error_handler_file) as f:
              content = f.read()
          except IOError:
            content = 'Failed to load error handler'
            logging.exception('failed to load error file: %s',
                              self._error_handler_file)
          start_response('500 Internal Server Error',
                         [('Content-Type', 'text/html'),
                          ('Content-Length', str(len(content)))])
          yield content
          return
        del response_headers[http_runtime_constants.ERROR_CODE_HEADER]
        start_response('%s %s' % (response.status, response.reason),
                       response_headers.items())

        # Yield the response body in small blocks.
        while True:
          try:
            block = response.read(512)
            if not block:
              break
            yield block
          except httplib.HTTPException:
            # The runtime process has encountered a problem, but has not
            # necessarily crashed. For example, a Go runtime process' HTTP
            # handler may have panicked in app-specific code (which the http
            # package will recover from, so the process as a whole doesn't
            # crash). At this point, we have already proxied onwards the HTTP
            # header, so we cannot retroactively serve a 500 Internal Server
            # Error. We silently break here; the runtime process has presumably
            # already written to stderr (via the Tee).
            break
      except Exception:
        if self._instance_died_unexpectedly():
          yield self._respond_with_error(
              'the runtime process for the instance running on port %d has '
              'unexpectedly quit' % self._port, start_response)
        else:
          raise
示例#8
0
    def handle(self, environ, start_response, url_map, match, request_id,
               request_type):
        """Serves this request by forwarding it to the runtime process.

    Args:
      environ: An environ dict for the request as defined in PEP-333.
      start_response: A function with semantics defined in PEP-333.
      url_map: An appinfo.URLMap instance containing the configuration for the
          handler matching this request.
      match: A re.MatchObject containing the result of the matched URL pattern.
      request_id: A unique string id associated with the request.
      request_type: The type of the request. See instance.*_REQUEST module
          constants.

    Yields:
      A sequence of strings containing the body of the HTTP response.
    """
        environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(
            url_map.script)
        if request_type == instance.BACKGROUND_REQUEST:
            environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background'
        elif request_type == instance.SHUTDOWN_REQUEST:
            environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown'
        elif request_type == instance.INTERACTIVE_REQUEST:
            environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive'

        for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
            if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ:
                value = environ.get(name, None)
                if value is not None:
                    environ[http_runtime_constants.INTERNAL_ENVIRON_PREFIX +
                            name] = value
        headers = util.get_headers_from_environ(environ)
        if environ.get('QUERY_STRING'):
            url = '%s?%s' % (urllib.quote(
                environ['PATH_INFO']), environ['QUERY_STRING'])
        else:
            url = urllib.quote(environ['PATH_INFO'])
        if 'CONTENT_LENGTH' in environ:
            headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH']
            data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
        else:
            data = ''

        cookies = environ.get('HTTP_COOKIE')
        user_email, admin, user_id = login.get_user_info(cookies)
        if user_email:
            nickname, organization = user_email.split('@', 1)
        else:
            nickname = ''
            organization = ''
        headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id

        prefix = http_runtime_constants.INTERNAL_HEADER_PREFIX

        # The Go runtime from the 1.9.48 SDK looks for different headers.
        if self._module_configuration.runtime == 'go':
            headers['X-Appengine-Dev-Request-Id'] = request_id
            prefix = 'X-Appengine-'

        headers[prefix + 'User-Id'] = (user_id)
        headers[prefix + 'User-Email'] = (user_email)
        headers[prefix + 'User-Is-Admin'] = (str(int(admin)))
        headers[prefix + 'User-Nickname'] = (nickname)
        headers[prefix + 'User-Organization'] = (organization)
        headers['X-AppEngine-Country'] = 'ZZ'
        connection = httplib.HTTPConnection(self._host, self._port)
        with contextlib.closing(connection):
            try:
                connection.connect()
                connection.request(environ.get('REQUEST_METHOD', 'GET'), url,
                                   data, dict(headers.items()))
                response = connection.getresponse()

                # Ensures that we avoid merging repeat headers into a single header,
                # allowing use of multiple Set-Cookie headers.
                headers = []
                for name in response.msg:
                    for value in response.msg.getheaders(name):
                        headers.append((name, value))

                response_headers = wsgiref.headers.Headers(headers)

                error_file = self._get_error_file()
                if (error_file and http_runtime_constants.ERROR_CODE_HEADER
                        in response_headers):
                    try:
                        with open(error_file) as f:
                            content = f.read()
                    except IOError:
                        content = 'Failed to load error handler'
                        logging.exception('failed to load error file: %s',
                                          error_file)
                    start_response('500 Internal Server Error',
                                   [('Content-Type', 'text/html'),
                                    ('Content-Length', str(len(content)))])
                    yield content
                    return
                del response_headers[http_runtime_constants.ERROR_CODE_HEADER]
                start_response('%s %s' % (response.status, response.reason),
                               response_headers.items())

                # Yield the response body in small blocks.
                block = response.read(512)
                while block:
                    yield block
                    block = response.read(512)
            except Exception:
                with self._process_lock:
                    if self._process and self._process.poll() is not None:
                        # The development server is in a bad state. Log and return an error
                        # message and quit.
                        message = (
                            'the runtime process for the instance running on port '
                            '%d has unexpectedly quit; exiting the development '
                            'server' % (self._port))
                        logging.error(message)
                        start_response('500 Internal Server Error',
                                       [('Content-Type', 'text/plain'),
                                        ('Content-Length', str(len(message)))])
                        shutdown.async_quit()
                        yield message
                    else:
                        raise