|
1 #!/usr/bin/env python |
|
2 # |
|
3 # Copyright 2007 Google Inc. |
|
4 # |
|
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 |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
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. |
|
16 # |
|
17 """Runs a development application server for an application. |
|
18 |
|
19 %(script)s [options] <application root> |
|
20 |
|
21 Application root must be the path to the application to run in this server. |
|
22 Must contain a valid app.yaml or app.yml file. |
|
23 |
|
24 Options: |
|
25 --help, -h View this helpful message. |
|
26 --debug, -d Use debug logging. (Default false) |
|
27 --clear_datastore, -c Clear the Datastore on startup. (Default false) |
|
28 --address=ADDRESS, -a ADDRESS |
|
29 Address to which this server should bind. (Default |
|
30 %(address)s). |
|
31 --port=PORT, -p PORT Port for the server to run on. (Default %(port)s) |
|
32 --datastore_path=PATH Path to use for storing Datastore file stub data. |
|
33 (Default %(datastore_path)s) |
|
34 --history_path=PATH Path to use for storing Datastore history. |
|
35 (Default %(history_path)s) |
|
36 --require_indexes Disallows queries that require composite indexes |
|
37 not defined in index.yaml. |
|
38 --smtp_host=HOSTNAME SMTP host to send test mail to. Leaving this |
|
39 unset will disable SMTP mail sending. |
|
40 (Default '%(smtp_host)s') |
|
41 --smtp_port=PORT SMTP port to send test mail to. |
|
42 (Default %(smtp_port)s) |
|
43 --smtp_user=USER SMTP user to connect as. Stub will only attempt |
|
44 to login if this field is non-empty. |
|
45 (Default '%(smtp_user)s'). |
|
46 --smtp_password=PASSWORD Password for SMTP server. |
|
47 (Default '%(smtp_password)s') |
|
48 --enable_sendmail Enable sendmail when SMTP not configured. |
|
49 (Default false) |
|
50 --show_mail_body Log the body of emails in mail stub. |
|
51 (Default false) |
|
52 --auth_domain Authorization domain that this app runs in. |
|
53 (Default gmail.com) |
|
54 --debug_imports Enables debug logging for module imports, showing |
|
55 search paths used for finding modules and any |
|
56 errors encountered during the import process. |
|
57 """ |
|
58 |
|
59 |
|
60 |
|
61 import os |
|
62 os.environ['TZ'] = 'UTC' |
|
63 import time |
|
64 if hasattr(time, 'tzset'): |
|
65 time.tzset() |
|
66 |
|
67 import getopt |
|
68 import logging |
|
69 import sys |
|
70 import traceback |
|
71 import tempfile |
|
72 |
|
73 from google.appengine.api import yaml_errors |
|
74 from google.appengine.tools import appcfg |
|
75 from google.appengine.tools import dev_appserver |
|
76 |
|
77 |
|
78 DEFAULT_ADMIN_CONSOLE_SERVER = 'appengine.google.com' |
|
79 |
|
80 ARG_ADDRESS = 'address' |
|
81 ARG_ADMIN_CONSOLE_SERVER = 'admin_console_server' |
|
82 ARG_ADMIN_CONSOLE_HOST = 'admin_console_host' |
|
83 ARG_AUTH_DOMAIN = 'auth_domain' |
|
84 ARG_CLEAR_DATASTORE = 'clear_datastore' |
|
85 ARG_DATASTORE_PATH = 'datastore_path' |
|
86 ARG_DEBUG_IMPORTS = 'debug_imports' |
|
87 ARG_ENABLE_SENDMAIL = 'enable_sendmail' |
|
88 ARG_SHOW_MAIL_BODY = 'show_mail_body' |
|
89 ARG_HISTORY_PATH = 'history_path' |
|
90 ARG_LOGIN_URL = 'login_url' |
|
91 ARG_LOG_LEVEL = 'log_level' |
|
92 ARG_PORT = 'port' |
|
93 ARG_REQUIRE_INDEXES = 'require_indexes' |
|
94 ARG_SMTP_HOST = 'smtp_host' |
|
95 ARG_SMTP_PASSWORD = 'smtp_password' |
|
96 ARG_SMTP_PORT = 'smtp_port' |
|
97 ARG_SMTP_USER = 'smtp_user' |
|
98 ARG_TEMPLATE_DIR = 'template_dir' |
|
99 |
|
100 |
|
101 BASE_PATH = os.path.abspath( |
|
102 os.path.join(os.path.dirname(dev_appserver.__file__), '../../../')) |
|
103 |
|
104 DEFAULT_ARGS = { |
|
105 ARG_PORT: 8080, |
|
106 ARG_LOG_LEVEL: logging.INFO, |
|
107 ARG_DATASTORE_PATH: os.path.join(tempfile.gettempdir(), |
|
108 'dev_appserver.datastore'), |
|
109 ARG_HISTORY_PATH: os.path.join(tempfile.gettempdir(), |
|
110 'dev_appserver.datastore.history'), |
|
111 ARG_LOGIN_URL: '/_ah/login', |
|
112 ARG_CLEAR_DATASTORE: False, |
|
113 ARG_REQUIRE_INDEXES: False, |
|
114 ARG_TEMPLATE_DIR: os.path.join(BASE_PATH, 'templates'), |
|
115 ARG_SMTP_HOST: '', |
|
116 ARG_SMTP_PORT: 25, |
|
117 ARG_SMTP_USER: '', |
|
118 ARG_SMTP_PASSWORD: '', |
|
119 ARG_ENABLE_SENDMAIL: False, |
|
120 ARG_SHOW_MAIL_BODY: False, |
|
121 ARG_AUTH_DOMAIN: 'gmail.com', |
|
122 ARG_ADDRESS: 'localhost', |
|
123 ARG_ADMIN_CONSOLE_SERVER: DEFAULT_ADMIN_CONSOLE_SERVER, |
|
124 ARG_ADMIN_CONSOLE_HOST: None, |
|
125 } |
|
126 |
|
127 |
|
128 def PrintUsageExit(code): |
|
129 """Prints usage information and exits with a status code. |
|
130 |
|
131 Args: |
|
132 code: Status code to pass to sys.exit() after displaying usage information. |
|
133 """ |
|
134 render_dict = DEFAULT_ARGS.copy() |
|
135 render_dict['script'] = os.path.basename(sys.argv[0]) |
|
136 print sys.modules['__main__'].__doc__ % render_dict |
|
137 sys.stdout.flush() |
|
138 sys.exit(code) |
|
139 |
|
140 |
|
141 def ParseArguments(argv): |
|
142 """Parses command-line arguments. |
|
143 |
|
144 Args: |
|
145 argv: Command-line arguments, including the executable name, used to |
|
146 execute this application. |
|
147 |
|
148 Returns: |
|
149 Tuple (args, option_dict) where: |
|
150 args: List of command-line arguments following the executable name. |
|
151 option_dict: Dictionary of parsed flags that maps keys from DEFAULT_ARGS |
|
152 to their values, which are either pulled from the defaults, or from |
|
153 command-line flags. |
|
154 """ |
|
155 option_dict = DEFAULT_ARGS.copy() |
|
156 |
|
157 try: |
|
158 opts, args = getopt.gnu_getopt( |
|
159 argv[1:], |
|
160 'a:cdhp:', |
|
161 [ 'address=', |
|
162 'admin_console_server=', |
|
163 'admin_console_host=', |
|
164 'auth_domain=', |
|
165 'clear_datastore', |
|
166 'datastore_path=', |
|
167 'debug', |
|
168 'debug_imports', |
|
169 'enable_sendmail', |
|
170 'show_mail_body', |
|
171 'help', |
|
172 'history_path=', |
|
173 'port=', |
|
174 'require_indexes', |
|
175 'smtp_host=', |
|
176 'smtp_password=', |
|
177 'smtp_port=', |
|
178 'smtp_user=', |
|
179 'template_dir=', |
|
180 ]) |
|
181 except getopt.GetoptError, e: |
|
182 print >>sys.stderr, 'Error: %s' % e |
|
183 PrintUsageExit(1) |
|
184 |
|
185 for option, value in opts: |
|
186 if option in ('-h', '--help'): |
|
187 PrintUsageExit(0) |
|
188 |
|
189 if option in ('-d', '--debug'): |
|
190 option_dict[ARG_LOG_LEVEL] = logging.DEBUG |
|
191 |
|
192 if option in ('-p', '--port'): |
|
193 try: |
|
194 option_dict[ARG_PORT] = int(value) |
|
195 if not (65535 > option_dict[ARG_PORT] > 0): |
|
196 raise ValueError |
|
197 except ValueError: |
|
198 print >>sys.stderr, 'Invalid value supplied for port' |
|
199 PrintUsageExit(1) |
|
200 |
|
201 if option in ('-a', '--address'): |
|
202 option_dict[ARG_ADDRESS] = value |
|
203 |
|
204 if option == '--datastore_path': |
|
205 option_dict[ARG_DATASTORE_PATH] = value |
|
206 |
|
207 if option == '--history_path': |
|
208 option_dict[ARG_HISTORY_PATH] = value |
|
209 |
|
210 if option in ('-c', '--clear_datastore'): |
|
211 option_dict[ARG_CLEAR_DATASTORE] = True |
|
212 |
|
213 if option == '--require_indexes': |
|
214 option_dict[ARG_REQUIRE_INDEXES] = True |
|
215 |
|
216 if option == '--smtp_host': |
|
217 option_dict[ARG_SMTP_HOST] = value |
|
218 |
|
219 if option == '--smtp_port': |
|
220 try: |
|
221 option_dict[ARG_SMTP_PORT] = int(value) |
|
222 if not (65535 > option_dict[ARG_SMTP_PORT] > 0): |
|
223 raise ValueError |
|
224 except ValueError: |
|
225 print >>sys.stderr, 'Invalid value supplied for SMTP port' |
|
226 PrintUsageExit(1) |
|
227 |
|
228 if option == '--smtp_user': |
|
229 option_dict[ARG_SMTP_USER] = value |
|
230 |
|
231 if option == '--smtp_password': |
|
232 option_dict[ARG_SMTP_PASSWORD] = value |
|
233 |
|
234 if option == '--enable_sendmail': |
|
235 option_dict[ARG_ENABLE_SENDMAIL] = True |
|
236 |
|
237 if option == '--show_mail_body': |
|
238 option_dict[ARG_SHOW_MAIL_BODY] = True |
|
239 |
|
240 if option == '--auth_domain': |
|
241 dev_appserver.DEFAULT_ENV['AUTH_DOMAIN'] = value |
|
242 |
|
243 if option == '--debug_imports': |
|
244 dev_appserver.HardenedModulesHook.ENABLE_LOGGING = True |
|
245 |
|
246 if option == '--template_dir': |
|
247 option_dict[ARG_TEMPLATE_DIR] = value |
|
248 |
|
249 if option == '--admin_console_server': |
|
250 option_dict[ARG_ADMIN_CONSOLE_SERVER] = value.strip() |
|
251 |
|
252 if option == '--admin_console_host': |
|
253 option_dict[ARG_ADMIN_CONSOLE_HOST] = value |
|
254 |
|
255 return args, option_dict |
|
256 |
|
257 |
|
258 def MakeRpcServer(option_dict): |
|
259 """Create a new HttpRpcServer. |
|
260 |
|
261 Creates a new HttpRpcServer to check for updates to the SDK. |
|
262 |
|
263 Args: |
|
264 option_dict: The dict of command line options. |
|
265 |
|
266 Returns: |
|
267 A HttpRpcServer. |
|
268 """ |
|
269 server = appcfg.HttpRpcServer( |
|
270 option_dict[ARG_ADMIN_CONSOLE_SERVER], |
|
271 lambda: ('unused_email', 'unused_password'), |
|
272 host_override=option_dict[ARG_ADMIN_CONSOLE_HOST]) |
|
273 server.authenticated = True |
|
274 return server |
|
275 |
|
276 |
|
277 def main(argv): |
|
278 """Runs the development application server.""" |
|
279 args, option_dict = ParseArguments(argv) |
|
280 |
|
281 if len(args) != 1: |
|
282 print >>sys.stderr, 'Invalid arguments' |
|
283 PrintUsageExit(1) |
|
284 |
|
285 root_path = args[0] |
|
286 log_level = option_dict[ARG_LOG_LEVEL] |
|
287 port = option_dict[ARG_PORT] |
|
288 datastore_path = option_dict[ARG_DATASTORE_PATH] |
|
289 login_url = option_dict[ARG_LOGIN_URL] |
|
290 template_dir = option_dict[ARG_TEMPLATE_DIR] |
|
291 serve_address = option_dict[ARG_ADDRESS] |
|
292 require_indexes = option_dict[ARG_REQUIRE_INDEXES] |
|
293 |
|
294 logging.basicConfig( |
|
295 level=log_level, |
|
296 format='%(levelname)-8s %(asctime)s %(filename)s] %(message)s') |
|
297 |
|
298 config = None |
|
299 try: |
|
300 config, matcher = dev_appserver.LoadAppConfig(root_path, {}) |
|
301 except yaml_errors.EventListenerError, e: |
|
302 logging.error('Fatal error when loading application configuration:\n' + |
|
303 str(e)) |
|
304 return 1 |
|
305 except dev_appserver.InvalidAppConfigError, e: |
|
306 logging.error('Application configuration file invalid:\n%s', e) |
|
307 return 1 |
|
308 |
|
309 if option_dict[ARG_ADMIN_CONSOLE_SERVER] != '': |
|
310 server = MakeRpcServer(option_dict) |
|
311 update_check = appcfg.UpdateCheck(server, config) |
|
312 update_check.CheckSupportedVersion() |
|
313 if update_check.AllowedToCheckForUpdates(): |
|
314 update_check.CheckForUpdates() |
|
315 |
|
316 try: |
|
317 dev_appserver.SetupStubs(config.application, **option_dict) |
|
318 except: |
|
319 exc_type, exc_value, exc_traceback = sys.exc_info() |
|
320 logging.error(str(exc_type) + ': ' + str(exc_value)) |
|
321 logging.debug(''.join(traceback.format_exception( |
|
322 exc_type, exc_value, exc_traceback))) |
|
323 return 1 |
|
324 |
|
325 http_server = dev_appserver.CreateServer(root_path, |
|
326 login_url, |
|
327 port, |
|
328 template_dir, |
|
329 serve_address=serve_address, |
|
330 require_indexes=require_indexes) |
|
331 |
|
332 logging.info('Running application %s on port %d: http://%s:%d', |
|
333 config.application, port, serve_address, port) |
|
334 try: |
|
335 try: |
|
336 http_server.serve_forever() |
|
337 except KeyboardInterrupt: |
|
338 logging.info('Server interrupted by user, terminating') |
|
339 except: |
|
340 exc_info = sys.exc_info() |
|
341 info_string = '\n'.join(traceback.format_exception(*exc_info)) |
|
342 logging.error('Error encountered:\n%s\nNow terminating.', info_string) |
|
343 return 1 |
|
344 finally: |
|
345 http_server.server_close() |
|
346 |
|
347 return 0 |
|
348 |
|
349 |
|
350 if __name__ == '__main__': |
|
351 sys.exit(main(sys.argv)) |