3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """The main entry point for the new development server."""
30 from google
.appengine
.api
import appinfo
31 from google
.appengine
.datastore
import datastore_stub_util
32 from google
.appengine
.tools
import boolean_action
33 from google
.appengine
.tools
.devappserver2
import api_server
34 from google
.appengine
.tools
.devappserver2
import application_configuration
35 from google
.appengine
.tools
.devappserver2
import dispatcher
36 from google
.appengine
.tools
.devappserver2
import gcd_application
37 from google
.appengine
.tools
.devappserver2
import login
38 from google
.appengine
.tools
.devappserver2
import runtime_config_pb2
39 from google
.appengine
.tools
.devappserver2
import shutdown
40 from google
.appengine
.tools
.devappserver2
import update_checker
41 from google
.appengine
.tools
.devappserver2
import wsgi_request_info
42 from google
.appengine
.tools
.devappserver2
.admin
import admin_server
44 # Initialize logging early -- otherwise some library packages may
45 # pre-empt our log formatting. NOTE: the level is provisional; it may
46 # be changed in main() based on the --debug flag.
49 format
='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
51 # Valid choices for --log_level and their corresponding constants in
52 # runtime_config_pb2.Config.stderr_log_level.
53 _LOG_LEVEL_TO_RUNTIME_CONSTANT
= {
61 # Valid choices for --dev_appserver_log_level and their corresponding Python
63 _LOG_LEVEL_TO_PYTHON_CONSTANT
= {
64 'debug': logging
.DEBUG
,
66 'warning': logging
.WARNING
,
67 'error': logging
.ERROR
,
68 'critical': logging
.CRITICAL
,
71 # The default encoding used by the production interpreter.
72 _PROD_DEFAULT_ENCODING
= 'ascii'
75 def _generate_storage_paths(app_id
):
76 """Yield an infinite sequence of possible storage paths."""
77 if sys
.platform
== 'win32':
78 # The temp directory is per-user on Windows so there is no reason to add
79 # the username to the generated directory name.
83 user_name
= getpass
.getuser()
84 except Exception: # The possible set of exceptions is not documented.
87 user_format
= '.%s' % user_name
89 tempdir
= tempfile
.gettempdir()
90 yield os
.path
.join(tempdir
, 'appengine.%s%s' % (app_id
, user_format
))
91 for i
in itertools
.count(1):
92 yield os
.path
.join(tempdir
, 'appengine.%s%s.%d' % (app_id
, user_format
, i
))
95 def _get_storage_path(path
, app_id
):
96 """Returns a path to the directory where stub data can be stored."""
97 _
, _
, app_id
= app_id
.replace(':', '_').rpartition('~')
99 for path
in _generate_storage_paths(app_id
):
103 if e
.errno
== errno
.EEXIST
:
104 # Check that the directory is only accessable by the current user to
105 # protect against an attacker creating the directory in advance in
106 # order to access any created files. Windows has per-user temporary
107 # directories and st_mode does not include per-user permission
108 # information so assume that it is safe.
109 if sys
.platform
== 'win32' or (
110 (os
.stat(path
).st_mode
& 0777) == 0700 and os
.path
.isdir(path
)):
117 elif not os
.path
.exists(path
):
120 elif not os
.path
.isdir(path
):
121 raise IOError('the given storage path %r is a file, a directory was '
127 def _get_default_php_path():
128 """Returns the path to the siloed php-cgi binary or None if not present."""
129 default_php_executable_path
= None
130 if sys
.platform
== 'win32':
131 default_php_executable_path
= os
.path
.abspath(
132 os
.path
.join(os
.path
.dirname(sys
.argv
[0]),
133 'php/php-5.4-Win32-VC9-x86/php-cgi.exe'))
134 elif sys
.platform
== 'darwin':
135 # The Cloud SDK uses symlinks in its packaging of the Mac Launcher. First
136 # try to find PHP relative to the apsolute path of this executable. If that
137 # doesn't work, try using the path without dereferencing all symlinks.
138 base_paths
= [os
.path
.realpath(sys
.argv
[0]), sys
.argv
[0]]
139 for base_path
in base_paths
:
140 default_php_executable_path
= os
.path
.abspath(
141 os
.path
.join(os
.path
.dirname(os
.path
.dirname(base_path
)), 'php-cgi'))
142 if os
.path
.exists(default_php_executable_path
):
145 if (default_php_executable_path
and
146 os
.path
.exists(default_php_executable_path
)):
147 return default_php_executable_path
151 class PortParser(object):
152 """A parser for ints that represent ports."""
154 def __init__(self
, allow_port_zero
=True):
155 self
._min
_port
= 0 if allow_port_zero
else 1
157 def __call__(self
, value
):
161 raise argparse
.ArgumentTypeError('Invalid port: %r' % value
)
162 if port
< self
._min
_port
or port
>= (1 << 16):
163 raise argparse
.ArgumentTypeError('Invalid port: %d' % port
)
167 def parse_per_module_option(
168 value
, value_type
, value_predicate
,
169 single_bad_type_error
, single_bad_predicate_error
,
170 multiple_bad_type_error
, multiple_bad_predicate_error
,
171 multiple_duplicate_module_error
):
172 """Parses command line options that may be specified per-module.
175 value: A str containing the flag value to parse. Two formats are supported:
176 1. A universal value (may not contain a colon as that is use to
177 indicate a per-module value).
178 2. Per-module values. One or more comma separated module-value pairs.
179 Each pair is a module_name:value. An empty module-name is shorthand
180 for "default" to match how not specifying a module name in the yaml
181 is the same as specifying "module: default".
182 value_type: a callable that converts the string representation of the value
183 to the actual value. Should raise ValueError if the string can not
185 value_predicate: a predicate to call on the converted value to validate
186 the converted value. Use "lambda _: True" if all values are valid.
187 single_bad_type_error: the message to use if a universal value is provided
188 and value_type throws a ValueError. The message must consume a single
189 format parameter (the provided value).
190 single_bad_predicate_error: the message to use if a universal value is
191 provided and value_predicate returns False. The message does not
192 get any format parameters.
193 multiple_bad_type_error: the message to use if a per-module value
194 either does not have two values separated by a single colon or if
195 value_types throws a ValueError on the second string. The message must
196 consume a single format parameter (the module_name:value pair).
197 multiple_bad_predicate_error: the message to use if a per-module value if
198 value_predicate returns False. The message must consume a single format
199 parameter (the module name).
200 multiple_duplicate_module_error: the message to use if the same module is
201 repeated. The message must consume a single formater parameter (the
205 Either a single value of value_type for universal values or a dict of
206 str->value_type for per-module values.
209 argparse.ArgumentTypeError: the value is invalid.
213 single_value
= value_type(value
)
215 raise argparse
.ArgumentTypeError(single_bad_type_error
% value
)
217 if not value_predicate(single_value
):
218 raise argparse
.ArgumentTypeError(single_bad_predicate_error
)
222 for module_value
in value
.split(','):
224 module_name
, single_value
= module_value
.split(':')
225 single_value
= value_type(single_value
)
227 raise argparse
.ArgumentTypeError(multiple_bad_type_error
% module_value
)
229 module_name
= module_name
.strip()
231 module_name
= appinfo
.DEFAULT_MODULE
232 if module_name
in module_to_value
:
233 raise argparse
.ArgumentTypeError(
234 multiple_duplicate_module_error
% module_name
)
235 if not value_predicate(single_value
):
236 raise argparse
.ArgumentTypeError(
237 multiple_bad_predicate_error
% module_name
)
238 module_to_value
[module_name
] = single_value
239 return module_to_value
242 def parse_max_module_instances(value
):
243 """Returns the parsed value for the --max_module_instances flag.
246 value: A str containing the flag value for parse. The format should follow
247 one of the following examples:
248 1. "5" - All modules are limited to 5 instances.
249 2. "default:3,backend:20" - The default module can have 3 instances,
250 "backend" can have 20 instances and all other modules are
251 unaffected. An empty name (i.e. ":3") is shorthand for default
252 to match how not specifying a module name in the yaml is the
253 same as specifying "module: default".
255 The parsed value of the max_module_instances flag. May either be an int
256 (for values of the form "5") or a dict of str->int (for values of the
257 form "default:3,backend:20").
260 argparse.ArgumentTypeError: the value is invalid.
262 return parse_per_module_option(
263 value
, int, lambda instances
: instances
> 0,
264 'Invalid max instance count: %r',
265 'Max instance count must be greater than zero',
266 'Expected "module:max_instance_count": %r',
267 'Max instance count for module %s must be greater than zero',
268 'Duplicate max instance count for module %s')
271 def parse_threadsafe_override(value
):
272 """Returns the parsed value for the --threadsafe_override flag.
275 value: A str containing the flag value for parse. The format should follow
276 one of the following examples:
277 1. "False" - All modules override the YAML threadsafe configuration
278 as if the YAML contained False.
279 2. "default:False,backend:True" - The default module overrides the
280 YAML threadsafe configuration as if the YAML contained False, the
281 "backend" module overrides with a value of True and all other
282 modules use the value in the YAML file. An empty name (i.e.
283 ":True") is shorthand for default to match how not specifying a
284 module name in the yaml is the same as specifying
287 The parsed value of the threadsafe_override flag. May either be a bool
288 (for values of the form "False") or a dict of str->bool (for values of the
289 form "default:False,backend:True").
292 argparse.ArgumentTypeError: the value is invalid.
294 return parse_per_module_option(
295 value
, boolean_action
.BooleanParse
, lambda _
: True,
296 'Invalid threadsafe override: %r',
298 'Expected "module:threadsafe_override": %r',
300 'Duplicate threadsafe override value for module %s')
303 def parse_path(value
):
304 """Returns the given path with ~ and environment variables expanded."""
305 return os
.path
.expanduser(os
.path
.expandvars(value
))
308 def create_command_line_parser():
309 """Returns an argparse.ArgumentParser to parse command line arguments."""
310 # TODO: Add more robust argument validation. Consider what flags
311 # are actually needed.
313 parser
= argparse
.ArgumentParser(
314 formatter_class
=argparse
.ArgumentDefaultsHelpFormatter
)
315 arg_name
= 'yaml_path'
316 arg_help
= 'Path to a yaml file, or a directory containing yaml files'
317 if application_configuration
.java_supported():
318 arg_name
= 'yaml_or_war_path'
319 arg_help
+= ', or a directory containing WEB-INF/web.xml'
321 'config_paths', metavar
=arg_name
, nargs
='+', help=arg_help
)
323 common_group
= parser
.add_argument_group('Common')
324 common_group
.add_argument(
325 '-A', '--application', action
='store', dest
='app_id',
326 help='Set the application, overriding the application value from the '
328 common_group
.add_argument(
329 '--host', default
='localhost',
330 help='host name to which application modules should bind')
331 common_group
.add_argument(
332 '--port', type=PortParser(), default
=8080,
333 help='lowest port to which application modules should bind')
334 common_group
.add_argument(
335 '--admin_host', default
='localhost',
336 help='host name to which the admin server should bind')
337 common_group
.add_argument(
338 '--admin_port', type=PortParser(), default
=8000,
339 help='port to which the admin server should bind')
340 common_group
.add_argument(
341 '--auth_domain', default
='gmail.com',
342 help='name of the authorization domain to use')
343 common_group
.add_argument(
344 '--storage_path', metavar
='PATH',
346 help='path to the data (datastore, blobstore, etc.) associated with the '
348 common_group
.add_argument(
349 '--log_level', default
='info',
350 choices
=_LOG_LEVEL_TO_RUNTIME_CONSTANT
.keys(),
351 help='the log level below which logging messages generated by '
352 'application code will not be displayed on the console')
353 common_group
.add_argument(
354 '--max_module_instances',
355 type=parse_max_module_instances
,
356 help='the maximum number of runtime instances that can be started for a '
357 'particular module - the value can be an integer, in what case all '
358 'modules are limited to that number of instances or a comma-seperated '
359 'list of module:max_instances e.g. "default:5,backend:3"')
360 common_group
.add_argument(
361 '--use_mtime_file_watcher',
362 action
=boolean_action
.BooleanAction
,
365 help='use mtime polling for detecting source code changes - useful if '
366 'modifying code from a remote machine using a distributed file system')
367 common_group
.add_argument(
368 '--threadsafe_override',
369 type=parse_threadsafe_override
,
370 help='override the application\'s threadsafe configuration - the value '
371 'can be a boolean, in which case all modules threadsafe setting will '
372 'be overridden or a comma-separated list of module:threadsafe_override '
373 'e.g. "default:False,backend:True"')
374 common_group
.add_argument('--docker_daemon_url', help=argparse
.SUPPRESS
)
377 php_group
= parser
.add_argument_group('PHP')
378 php_group
.add_argument('--php_executable_path', metavar
='PATH',
380 default
=_get_default_php_path(),
381 help='path to the PHP executable')
382 php_group
.add_argument('--php_remote_debugging',
383 action
=boolean_action
.BooleanAction
,
386 help='enable XDebug remote debugging')
389 dart_group
= parser
.add_argument_group('Dart')
390 dart_group
.add_argument('--dart_sdk', help=argparse
.SUPPRESS
)
391 dart_group
.add_argument('--dart_dev_mode',
392 choices
=['dev', 'deploy'],
393 help=argparse
.SUPPRESS
)
394 dart_group
.add_argument('--dart_pub_serve_host', help=argparse
.SUPPRESS
)
395 dart_group
.add_argument('--dart_pub_serve_port',
396 type=PortParser(), help=argparse
.SUPPRESS
)
399 appidentity_group
= parser
.add_argument_group('Application Identity')
400 appidentity_group
.add_argument(
401 '--appidentity_email_address',
402 help='email address associated with a service account that has a '
403 'downloadable key. May be None for no local application identity.')
404 appidentity_group
.add_argument(
405 '--appidentity_private_key_path',
406 help='path to private key file associated with service account '
407 '(.pem format). Must be set if appidentity_email_address is set.')
410 python_group
= parser
.add_argument_group('Python')
411 python_group
.add_argument(
412 '--python_startup_script',
413 help='the script to run at the startup of new Python runtime instances '
414 '(useful for tools such as debuggers.')
415 python_group
.add_argument(
416 '--python_startup_args',
417 help='the arguments made available to the script specified in '
418 '--python_startup_script.')
421 java_group
= parser
.add_argument_group('Java')
422 java_group
.add_argument(
423 '--jvm_flag', action
='append',
424 help='additional arguments to pass to the java command when launching '
425 'an instance of the app. May be specified more than once. Example: '
426 '--jvm_flag=-Xmx1024m --jvm_flag=-Xms256m')
429 blobstore_group
= parser
.add_argument_group('Blobstore API')
430 blobstore_group
.add_argument(
433 help='path to directory used to store blob contents '
434 '(defaults to a subdirectory of --storage_path if not set)',
438 cloud_sql_group
= parser
.add_argument_group('Cloud SQL')
439 cloud_sql_group
.add_argument(
442 help='host name of a running MySQL server used for simulated Google '
444 cloud_sql_group
.add_argument(
445 '--mysql_port', type=PortParser(allow_port_zero
=False),
447 help='port number of a running MySQL server used for simulated Google '
449 cloud_sql_group
.add_argument(
452 help='username to use when connecting to the MySQL server specified in '
453 '--mysql_host and --mysql_port or --mysql_socket')
454 cloud_sql_group
.add_argument(
457 help='password to use when connecting to the MySQL server specified in '
458 '--mysql_host and --mysql_port or --mysql_socket')
459 cloud_sql_group
.add_argument(
461 help='path to a Unix socket file to use when connecting to a running '
462 'MySQL server used for simulated Google Cloud SQL storage')
465 datastore_group
= parser
.add_argument_group('Datastore API')
466 datastore_group
.add_argument(
470 help='path to a file used to store datastore contents '
471 '(defaults to a file in --storage_path if not set)',)
472 datastore_group
.add_argument('--clear_datastore',
473 action
=boolean_action
.BooleanAction
,
476 help='clear the datastore on startup')
477 datastore_group
.add_argument(
478 '--datastore_consistency_policy',
480 choices
=['consistent', 'random', 'time'],
481 help='the policy to apply when deciding whether a datastore write should '
482 'appear in global queries')
483 datastore_group
.add_argument(
485 action
=boolean_action
.BooleanAction
,
488 help='generate an error on datastore queries that '
489 'requires a composite index not found in index.yaml')
490 datastore_group
.add_argument(
492 default
=datastore_stub_util
.SCATTERED
,
493 choices
=[datastore_stub_util
.SEQUENTIAL
,
494 datastore_stub_util
.SCATTERED
],
495 help='the type of sequence from which the datastore stub '
496 'assigns automatic IDs. NOTE: Sequential IDs are '
497 'deprecated. This flag will be removed in a future '
498 'release. Please do not rely on sequential IDs in your '
500 datastore_group
.add_argument(
501 '--enable_cloud_datastore',
502 action
=boolean_action
.BooleanAction
,
505 help=argparse
.SUPPRESS
#'enable the Google Cloud Datastore API.'
509 logs_group
= parser
.add_argument_group('Logs API')
510 logs_group
.add_argument(
511 '--logs_path', default
=None,
512 help='path to a file used to store request logs (defaults to a file in '
513 '--storage_path if not set)',)
516 mail_group
= parser
.add_argument_group('Mail API')
517 mail_group
.add_argument(
519 action
=boolean_action
.BooleanAction
,
522 help='logs the contents of e-mails sent using the Mail API')
523 mail_group
.add_argument(
525 action
=boolean_action
.BooleanAction
,
528 help='use the "sendmail" tool to transmit e-mail sent '
529 'using the Mail API (ignored if --smtp_host is set)')
530 mail_group
.add_argument(
531 '--smtp_host', default
='',
532 help='host name of an SMTP server to use to transmit '
533 'e-mail sent using the Mail API')
534 mail_group
.add_argument(
535 '--smtp_port', default
=25,
536 type=PortParser(allow_port_zero
=False),
537 help='port number of an SMTP server to use to transmit '
538 'e-mail sent using the Mail API (ignored if --smtp_host '
540 mail_group
.add_argument(
541 '--smtp_user', default
='',
542 help='username to use when connecting to the SMTP server '
543 'specified in --smtp_host and --smtp_port')
544 mail_group
.add_argument(
545 '--smtp_password', default
='',
546 help='password to use when connecting to the SMTP server '
547 'specified in --smtp_host and --smtp_port')
548 mail_group
.add_argument(
550 action
=boolean_action
.BooleanAction
,
553 help='Allow TLS to be used when the SMTP server announces TLS support '
554 '(ignored if --smtp_host is not set)')
557 prospective_search_group
= parser
.add_argument_group('Prospective Search API')
558 prospective_search_group
.add_argument(
559 '--prospective_search_path', default
=None,
561 help='path to a file used to store the prospective '
562 'search subscription index (defaults to a file in '
563 '--storage_path if not set)')
564 prospective_search_group
.add_argument(
565 '--clear_prospective_search',
566 action
=boolean_action
.BooleanAction
,
569 help='clear the prospective search subscription index')
572 search_group
= parser
.add_argument_group('Search API')
573 search_group
.add_argument(
574 '--search_indexes_path', default
=None,
576 help='path to a file used to store search indexes '
577 '(defaults to a file in --storage_path if not set)',)
578 search_group
.add_argument(
579 '--clear_search_indexes',
580 action
=boolean_action
.BooleanAction
,
583 help='clear the search indexes')
586 taskqueue_group
= parser
.add_argument_group('Task Queue API')
587 taskqueue_group
.add_argument(
588 '--enable_task_running',
589 action
=boolean_action
.BooleanAction
,
592 help='run "push" tasks created using the taskqueue API automatically')
595 misc_group
= parser
.add_argument_group('Miscellaneous')
596 misc_group
.add_argument(
597 '--allow_skipped_files',
598 action
=boolean_action
.BooleanAction
,
601 help='make files specified in the app.yaml "skip_files" or "static" '
602 'handles readable by the application.')
603 # No help to avoid lengthening help message for rarely used feature:
604 # host name to which the server for API calls should bind.
605 misc_group
.add_argument(
606 '--api_host', default
='localhost',
607 help=argparse
.SUPPRESS
)
608 misc_group
.add_argument(
609 '--api_port', type=PortParser(), default
=0,
610 help='port to which the server for API calls should bind')
611 misc_group
.add_argument(
612 '--automatic_restart',
613 action
=boolean_action
.BooleanAction
,
616 help=('restart instances automatically when files relevant to their '
617 'module are changed'))
618 misc_group
.add_argument(
619 '--dev_appserver_log_level', default
='info',
620 choices
=_LOG_LEVEL_TO_PYTHON_CONSTANT
.keys(),
621 help='the log level below which logging messages generated by '
622 'the development server will not be displayed on the console (this '
623 'flag is more useful for diagnosing problems in dev_appserver.py rather '
624 'than in application code)')
625 misc_group
.add_argument(
626 '--skip_sdk_update_check',
627 action
=boolean_action
.BooleanAction
,
630 help='skip checking for SDK updates (if false, use .appcfg_nag to '
632 misc_group
.add_argument(
633 '--default_gcs_bucket_name', default
=None,
634 help='default Google Cloud Storgage bucket name')
639 PARSER
= create_command_line_parser()
642 def _clear_datastore_storage(datastore_path
):
643 """Delete the datastore storage file at the given path."""
644 # lexists() returns True for broken symlinks, where exists() returns False.
645 if os
.path
.lexists(datastore_path
):
647 os
.remove(datastore_path
)
649 logging
.warning('Failed to remove datastore file %r: %s',
654 def _clear_prospective_search_storage(prospective_search_path
):
655 """Delete the perspective search storage file at the given path."""
656 # lexists() returns True for broken symlinks, where exists() returns False.
657 if os
.path
.lexists(prospective_search_path
):
659 os
.remove(prospective_search_path
)
661 logging
.warning('Failed to remove prospective search file %r: %s',
662 prospective_search_path
,
666 def _clear_search_indexes_storage(search_index_path
):
667 """Delete the search indexes storage file at the given path."""
668 # lexists() returns True for broken symlinks, where exists() returns False.
669 if os
.path
.lexists(search_index_path
):
671 os
.remove(search_index_path
)
673 logging
.warning('Failed to remove search indexes file %r: %s',
678 def _setup_environ(app_id
):
679 """Sets up the os.environ dictionary for the front-end server and API server.
681 This function should only be called once.
684 app_id: The id of the application.
686 os
.environ
['APPLICATION_ID'] = app_id
689 class DevelopmentServer(object):
690 """Encapsulates the logic for the development server.
692 Only a single instance of the class may be created per process. See
697 # A list of servers that are currently running.
698 self
._running
_modules
= []
699 self
._module
_to
_port
= {}
700 self
._dispatcher
= None
702 def module_to_address(self
, module_name
, instance
=None):
703 """Returns the address of a module."""
705 if module_name
is None:
706 return self
._dispatcher
.dispatch_address
707 return self
._dispatcher
.get_hostname(
709 self
._dispatcher
.get_default_version(module_name
),
712 def start(self
, options
):
713 """Start devappserver2 servers based on the provided command line arguments.
716 options: An argparse.Namespace containing the command line arguments.
718 logging
.getLogger().setLevel(
719 _LOG_LEVEL_TO_PYTHON_CONSTANT
[options
.dev_appserver_log_level
])
721 configuration
= application_configuration
.ApplicationConfiguration(
722 options
.config_paths
, options
.app_id
)
724 if options
.enable_cloud_datastore
:
725 # This requires the oauth server stub to return that the logged in user
726 # is in fact an admin.
727 os
.environ
['OAUTH_IS_ADMIN'] = '1'
728 gcd_module
= application_configuration
.ModuleConfiguration(
729 gcd_application
.generate_gcd_app(configuration
.app_id
.split('~')[1]))
730 configuration
.modules
.append(gcd_module
)
732 if options
.skip_sdk_update_check
:
733 logging
.info('Skipping SDK update check.')
735 update_checker
.check_for_updates(configuration
)
737 # There is no good way to set the default encoding from application code
738 # (it needs to be done during interpreter initialization in site.py or
739 # sitecustomize.py) so just warn developers if they have a different
740 # encoding than production.
741 if sys
.getdefaultencoding() != _PROD_DEFAULT_ENCODING
:
743 'The default encoding of your local Python interpreter is set to %r '
744 'while App Engine\'s production environment uses %r; as a result '
745 'your code may behave differently when deployed.',
746 sys
.getdefaultencoding(), _PROD_DEFAULT_ENCODING
)
748 if options
.port
== 0:
749 logging
.warn('DEFAULT_VERSION_HOSTNAME will not be set correctly with '
752 _setup_environ(configuration
.app_id
)
754 self
._dispatcher
= dispatcher
.Dispatcher(
759 _LOG_LEVEL_TO_RUNTIME_CONSTANT
[options
.log_level
],
760 self
._create
_php
_config
(options
),
761 self
._create
_python
_config
(options
),
762 self
._create
_java
_config
(options
),
763 self
._create
_cloud
_sql
_config
(options
),
764 self
._create
_vm
_config
(options
),
765 self
._create
_module
_to
_setting
(options
.max_module_instances
,
766 configuration
, '--max_module_instances'),
767 options
.use_mtime_file_watcher
,
768 options
.automatic_restart
,
769 options
.allow_skipped_files
,
770 self
._create
_module
_to
_setting
(options
.threadsafe_override
,
771 configuration
, '--threadsafe_override'))
773 request_data
= wsgi_request_info
.WSGIRequestInfo(self
._dispatcher
)
774 storage_path
= _get_storage_path(options
.storage_path
, configuration
.app_id
)
776 apis
= self
._create
_api
_server
(
777 request_data
, storage_path
, options
, configuration
)
779 self
._running
_modules
.append(apis
)
781 self
._dispatcher
.start(options
.api_host
, apis
.port
, request_data
)
783 xsrf_path
= os
.path
.join(storage_path
, 'xsrf')
784 admin
= admin_server
.AdminServer(options
.admin_host
, options
.admin_port
,
785 self
._dispatcher
, configuration
, xsrf_path
)
787 self
._running
_modules
.append(admin
)
790 """Stops all running devappserver2 modules."""
791 while self
._running
_modules
:
792 self
._running
_modules
.pop().quit()
794 self
._dispatcher
.quit()
797 def _create_api_server(request_data
, storage_path
, options
, configuration
):
798 datastore_path
= options
.datastore_path
or os
.path
.join(storage_path
,
800 logs_path
= options
.logs_path
or os
.path
.join(storage_path
, 'logs.db')
802 search_index_path
= options
.search_indexes_path
or os
.path
.join(
803 storage_path
, 'search_indexes')
805 prospective_search_path
= options
.prospective_search_path
or os
.path
.join(
806 storage_path
, 'prospective-search')
808 blobstore_path
= options
.blobstore_path
or os
.path
.join(storage_path
,
811 if options
.clear_datastore
:
812 _clear_datastore_storage(datastore_path
)
814 if options
.clear_prospective_search
:
815 _clear_prospective_search_storage(prospective_search_path
)
817 if options
.clear_search_indexes
:
818 _clear_search_indexes_storage(search_index_path
)
820 if options
.auto_id_policy
==datastore_stub_util
.SEQUENTIAL
:
821 logging
.warn("--auto_id_policy='sequential' is deprecated. This option "
822 "will be removed in a future release.")
824 application_address
= '%s' % options
.host
825 if options
.port
and options
.port
!= 80:
826 application_address
+= ':' + str(options
.port
)
828 user_login_url
= '/%s?%s=%%s' % (login
.LOGIN_URL_RELATIVE
,
829 login
.CONTINUE_PARAM
)
830 user_logout_url
= '%s&%s=%s' % (user_login_url
, login
.ACTION_PARAM
,
833 if options
.datastore_consistency_policy
== 'time':
834 consistency
= datastore_stub_util
.TimeBasedHRConsistencyPolicy()
835 elif options
.datastore_consistency_policy
== 'random':
836 consistency
= datastore_stub_util
.PseudoRandomHRConsistencyPolicy()
837 elif options
.datastore_consistency_policy
== 'consistent':
838 consistency
= datastore_stub_util
.PseudoRandomHRConsistencyPolicy(1.0)
840 assert 0, ('unknown consistency policy: %r' %
841 options
.datastore_consistency_policy
)
843 api_server
.maybe_convert_datastore_file_stub_data_to_sqlite(
844 configuration
.app_id
, datastore_path
)
845 api_server
.setup_stubs(
846 request_data
=request_data
,
847 app_id
=configuration
.app_id
,
848 application_root
=configuration
.modules
[0].application_root
,
849 # The "trusted" flag is only relevant for Google administrative
851 trusted
=getattr(options
, 'trusted', False),
852 appidentity_email_address
=options
.appidentity_email_address
,
853 appidentity_private_key_path
=os
.path
.abspath(
854 options
.appidentity_private_key_path
)
855 if options
.appidentity_private_key_path
else None,
856 blobstore_path
=blobstore_path
,
857 datastore_path
=datastore_path
,
858 datastore_consistency
=consistency
,
859 datastore_require_indexes
=options
.require_indexes
,
860 datastore_auto_id_policy
=options
.auto_id_policy
,
861 images_host_prefix
='http://%s' % application_address
,
863 mail_smtp_host
=options
.smtp_host
,
864 mail_smtp_port
=options
.smtp_port
,
865 mail_smtp_user
=options
.smtp_user
,
866 mail_smtp_password
=options
.smtp_password
,
867 mail_enable_sendmail
=options
.enable_sendmail
,
868 mail_show_mail_body
=options
.show_mail_body
,
869 mail_allow_tls
=options
.smtp_allow_tls
,
870 matcher_prospective_search_path
=prospective_search_path
,
871 search_index_path
=search_index_path
,
872 taskqueue_auto_run_tasks
=options
.enable_task_running
,
873 taskqueue_default_http_server
=application_address
,
874 user_login_url
=user_login_url
,
875 user_logout_url
=user_logout_url
,
876 default_gcs_bucket_name
=options
.default_gcs_bucket_name
)
878 return api_server
.APIServer(options
.api_host
, options
.api_port
,
879 configuration
.app_id
)
882 def _create_php_config(options
):
883 php_config
= runtime_config_pb2
.PhpConfig()
884 if options
.php_executable_path
:
885 php_config
.php_executable_path
= os
.path
.abspath(
886 options
.php_executable_path
)
887 php_config
.enable_debugger
= options
.php_remote_debugging
891 def _create_python_config(options
):
892 python_config
= runtime_config_pb2
.PythonConfig()
893 if options
.python_startup_script
:
894 python_config
.startup_script
= os
.path
.abspath(
895 options
.python_startup_script
)
896 if options
.python_startup_args
:
897 python_config
.startup_args
= options
.python_startup_args
901 def _create_java_config(options
):
902 java_config
= runtime_config_pb2
.JavaConfig()
904 java_config
.jvm_args
.extend(options
.jvm_flag
)
908 def _create_cloud_sql_config(options
):
909 cloud_sql_config
= runtime_config_pb2
.CloudSQL()
910 cloud_sql_config
.mysql_host
= options
.mysql_host
911 cloud_sql_config
.mysql_port
= options
.mysql_port
912 cloud_sql_config
.mysql_user
= options
.mysql_user
913 cloud_sql_config
.mysql_password
= options
.mysql_password
914 if options
.mysql_socket
:
915 cloud_sql_config
.mysql_socket
= options
.mysql_socket
916 return cloud_sql_config
919 def _create_vm_config(options
):
920 vm_config
= runtime_config_pb2
.VMConfig()
921 if options
.docker_daemon_url
:
922 vm_config
.docker_daemon_url
= options
.docker_daemon_url
924 vm_config
.dart_config
.dart_sdk
= os
.path
.abspath(options
.dart_sdk
)
925 if options
.dart_dev_mode
:
926 vm_config
.dart_config
.dart_dev_mode
= options
.dart_dev_mode
927 if options
.dart_pub_serve_host
:
928 vm_config
.dart_config
.dart_pub_serve_host
= options
.dart_pub_serve_host
929 if options
.dart_pub_serve_port
:
930 vm_config
.dart_config
.dart_pub_serve_port
= options
.dart_pub_serve_port
934 def _create_module_to_setting(setting
, configuration
, option
):
935 """Create a per-module dictionary configuration.
937 Creates a dictionary that maps a module name to a configuration
938 setting. Used in conjunction with parse_per_module_option.
941 setting: a value that can be None, a dict of str->type or a single value.
942 configuration: an ApplicationConfiguration object.
943 option: the option name the setting came from.
951 module_names
= [module_configuration
.module_name
952 for module_configuration
in configuration
.modules
]
953 if isinstance(setting
, dict):
954 # Warn and remove a setting if the module name is unknown.
955 module_to_setting
= {}
956 for module_name
, value
in setting
.items():
957 if module_name
in module_names
:
958 module_to_setting
[module_name
] = value
960 logging
.warning('Unknown module %r for %r', module_name
, option
)
961 return module_to_setting
963 # Create a dict with an entry for every module.
964 return {module_name
: setting
for module_name
in module_names
}
968 shutdown
.install_signal_handlers()
969 # The timezone must be set in the devappserver2 process rather than just in
970 # the runtime so printed log timestamps are consistent and the taskqueue stub
971 # expects the timezone to be UTC. The runtime inherits the environment.
972 os
.environ
['TZ'] = 'UTC'
973 if hasattr(time
, 'tzset'):
974 # time.tzet() should be called on Unix, but doesn't exist on Windows.
976 options
= PARSER
.parse_args()
977 dev_server
= DevelopmentServer()
979 dev_server
.start(options
)
980 shutdown
.wait_until_shutdown()
985 if __name__
== '__main__':