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
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
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
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
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
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