Source

Webware / WebKit / Launch.py

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
#!/usr/bin/env python

"""Launch.py

DESCRIPTION

Python launch script for the WebKit application server.

This launch script will run in its standard location in the Webware/WebKit
directory as well as in a WebKit work directory outside of the Webware tree.

USAGE

Launch.py [StartOptions] [AppServer [AppServerOptions]]

StartOptions:
  -d, --work-dir=...     Set the path to the app server working directory.
                         By default this is the directory containing Lauch.py.
  -w, --webware-dir=...  Set the path to the Webware root directory.
                         By default this is the parent directory.
  -l, --library=...      Other directories to be included in the search path.
                         You may specify this option multiple times.
  -p, --run-profile      Set this to get profiling going (see Profiler.py).
  -o, --log-file=...     Redirect standard output and error to this file.
  -i, --pid-file=...     Set the file path to hold the app server process id.
                         This option is fully supported under Unix only.
  -u, --user=...         The name or uid of the user to run the app server.
                         This option is supported under Unix only.
  -g, --group=...        The name or gid of the group to run the app server.
                         This option is supported under Unix only.

AppServer:
  The name of the application server module.
  By default, the ThreadedAppServer will be used.

AppServerOptions:
  Options that shall be passed to the application server.
  For instance, the ThreadedAppServer accepts: start, stop, daemon
  You can also change configuration settings here by passing
  arguments of the form ClassName.SettingName=value

Please note that the default values for the StartOptions and the AppServer
can be easily changed at the top of the Launch.py script.

"""

# FUTURE
# * This shares a lot of code with ThreadedAppServer.py and AppServer.py.
#   Try to consolidate these things. The default settings below in the
#   global variables could go completely into AppServer.config.
# CREDITS
# * Contributed to Webware for Python by Chuck Esterbrook
# * Improved by Ian Bicking
# * Improved by Christoph Zwerschke


## Default options ##

# You can change the following default values:

# The path to the app server working directory, if you do not
# want to use the directory containing this script:
workDir = None

# The path to the Webware root directory; by default this will
# be the parent directory of the directory containing this script:
webwareDir = None

# A list of additional directories (usually some libraries)
# that you want to include into the search path for modules:
libraryDirs = []

# To get profiling going, set runProfile = True (see also
# the description in the docstring of Profiler.py):
runProfile = False

# The path to the log file, if you want to redirect the
# standard output and error to a log file:
logFile = None

# The pass to the pid file, if you want to check and terminate
# a running server by storing its server process id:
pidFile = None

# The name or uid of the server process user, if you want
# to run the server under a different user:
user = None

# The name or uid of the server process group, if you want
# to run the server under a different group:
group = None

# The default app server to be used:
appServer = 'ThreadedAppServer'


## Launch app server ##

import os, sys

def usage():
    """Print the docstring and exit with error."""
    sys.stdout.write(__doc__)
    sys.exit(2)

def launchWebKit(appServer=appServer, workDir=None, args=None):
    """Import and launch the specified WebKit app server.

    appServer  -- the name of the WebKit app server module
    workDir -- the server-side work directory of the app server
    args -- other options that will be given to the app server

    """
    # Set up the arguments for the app server:
    if args is None:
        args = []
    if workDir:
        args.append('workdir=' + workDir)
    # Allow for a .py on the server name:
    if appServer[-3:] == '.py':
        appServer = appServer[:-3]
    # Import the app server's main() function:
    try:
        appServerMain = __import__('WebKit.' + appServer, None, None, 'main').main
    except ImportError, e:
        print 'Error: Cannot import the AppServer module.'
        print 'Reason:', str(e)
        sys.exit(1)
    # Set Profiler.startTime if this has not been done already:
    from WebKit import Profiler
    if Profiler.startTime is None:
        from time import time
        Profiler.startTime = time()
    # Run the app server:
    return appServerMain(args) # go!


## Main ##

def main(args=None):
    """Evaluate the command line arguments and call launchWebKit."""
    global workDir, webwareDir, libraryDirs, runProfile, \
        logFile, pidFile, user, group, appServer
    if args is None:
        args = sys.argv[1:]
    # Accept AppServer even if placed before StartOptions:
    if args and not args[0].startswith('-'):
        arg2 = args.pop(0)
    else:
        arg2 = None
    # Accept AppServerOptions that look like StartOptions:
    args1 = []
    args2 = []
    while args:
        arg = args.pop(0)
        if (arg.startswith('--')
                and 2 < arg.find('.', 2) < arg.find('=', 5)):
            args2.append(arg)
        else:
            args1.append(arg)
    # Get all launch options:
    from getopt import getopt, GetoptError
    try:
        opts, args1 = getopt(args1, 'd:w:l:po:i:u:g:', [
            'work-dir=', 'webware-dir=', 'library=', 'run-profile',
            'log-file=', 'pid-file=', 'user=', 'group='])
    except GetoptError, error:
        print str(error)
        print
        usage()
    for opt, arg in opts:
        if opt in ('-d', '--work-dir'):
            workDir = arg
        elif opt in ('-w', '--webware-dir'):
            webwareDir = arg
        elif opt in ('-l', '--library'):
            libraryDirs.append(arg)
        elif opt in ('-p', '--run-profile'):
            runProfile = True
        elif opt in ('-o', '--log-file'):
            logFile = arg
        elif opt in ('-i', '--pid-file'):
            pidFile = arg
        elif opt in ('-u', '--user'):
            user = arg
        elif opt in ('-g', '--group'):
            group = arg
    if arg2:
        appServer = arg2
    elif args1 and not args1[0].startswith('-') and '=' not in args1[0]:
        appServer = args1.pop(0)
    args = args2 + args1
    # Figure out the group id:
    gid = group
    if gid is not None:
        try:
            gid = int(gid)
        except ValueError:
            try:
                import grp
                entry = grp.getgrnam(gid)
            except KeyError:
                print 'Error: Group %r does not exist.' % gid
                sys.exit(2)
            except ImportError:
                print 'Error: Group names are not supported.'
                sys.exit(2)
            gid = entry[2]
    # Figure out the user id:
    uid = user
    if uid is not None:
        try:
            uid = int(uid)
        except ValueError:
            try:
                import pwd
                entry = pwd.getpwnam(uid)
            except KeyError:
                print 'Error: User %r does not exist.' % uid
                sys.exit(2)
            except ImportError:
                print 'Error: User names are not supported.'
                sys.exit(2)
            if not gid:
                gid = entry[3]
            uid = entry[2]
    # Figure out the work directory and make it the current directory:
    if workDir:
        workDir = os.path.expanduser(workDir)
    else:
        scriptName = sys.argv and sys.argv[0]
        if not scriptName or scriptName == '-c':
            scriptName = 'Launch.py'
        workDir = os.path.dirname(os.path.abspath(scriptName))
    try:
        os.chdir(workDir)
    except OSError, error:
        print 'Error: Could not set working directory.'
        print 'The path %r cannot be used.' % workDir
        print error.strerror
        print 'Check the --work-dir option.'
        sys.exit(1)
    workDir = os.curdir
    # Expand user components in directories:
    if webwareDir:
        webwareDir = os.path.expanduser(webwareDir)
    else:
        webwareDir = os.pardir
    if libraryDirs:
        libraryDirs = map(os.path.expanduser, libraryDirs)
    # If this module is inside a package, make it a standalone module,
    # because otherwise the package path will be used for imports, too:
    global __name__, __package__
    name = __name__.rsplit('.', 1)[-1]
    if name != __name__:
        sys.modules[name] = sys.modules[__name__]
        del sys.modules[__name__]
        __name__ = name
        __package__ = None
    # Check the validity of the Webware directory:
    sysPath = sys.path # memorize the standard Python search path
    sys.path = [webwareDir] # now include only the Webware directory
    try: # check whether Webware is really located here
        from Properties import name as webwareName
        from WebKit.Properties import name as webKitName
    except ImportError:
        webwareName = None
    if webwareName != 'Webware for Python' or webKitName != 'WebKit':
        print 'Error: Cannot find the Webware directory.'
        print 'The path %r seems to be wrong.' % webwareDir
        print 'Check the --webware-dir option.'
        sys.exit(1)
    if not os.path.exists(os.path.join(webwareDir, 'install.log')):
        print 'Error: Webware has not been installed.'
        print 'Please run install.py in the Webware directory:'
        print '> cd', os.path.abspath(webwareDir)
        print '> python install.py'
        #sys.exit(1)
    # Now assemble a new clean Python search path:
    path = [] # the new search path will be collected here
    webKitDir = os.path.abspath(os.path.join(webwareDir, 'WebKit'))
    for p in [workDir, webwareDir] + libraryDirs + sysPath:
        if not p:
            continue # do not include the empty ("current") directory
        p = os.path.abspath(p)
        if p == webKitDir or p in path or not os.path.exists(p):
            continue # do not include WebKit and duplicates
        path.append(p)
    sys.path = path # set the new search path
    # Prepare the arguments for launchWebKit:
    args = (appServer, workDir, args)
    # Handle special case where app server shall be stopped:
    if 'stop' in args[2]:
        print 'Stopping WebKit.%s...' % appServer
        errorlevel = launchWebKit(*args)
        if not errorlevel:
            if pidFile and os.path.exists(pidFile):
                try:
                    os.remove(pidFile)
                except Exception:
                    print 'The pid file could not be removed.'
                    print
        sys.exit(errorlevel)
    # Handle the pid file:
    if pidFile:
        pidFile = os.path.expanduser(pidFile)
        # Read the old pid file:
        try:
            pid = int(open(pidFile).read())
        except Exception:
            pid = None
        if pid is not None:
            print 'According to the pid file, the server is still running.'
            # Try to kill an already running server:
            killed = False
            try:
                from signal import SIGTERM, SIGKILL
                print 'Trying to terminate the server with pid %d...' % pid
                os.kill(pid, SIGTERM)
            except OSError, error:
                from errno import ESRCH
                if error.errno == ESRCH: # no such process
                    print 'The pid file was stale, continuing with startup...'
                    killed = True
                else:
                    print 'Cannot terminate server with pid %d.' % pid
                    print error.strerror
                    sys.exit(1)
            except (ImportError, AttributeError):
                print 'Cannot check or terminate server with pid %d.' % pid
                sys.exit(1)
            if not killed:
                from time import sleep
                try:
                    for i in range(100):
                        sleep(0.1)
                        os.kill(pid, SIGTERM)
                except OSError, error:
                    from errno import ESRCH
                    if error.errno == ESRCH:
                        print 'Server with pid %d has been terminated.' % pid
                        killed = True
            if not killed:
                try:
                    for i in range(100):
                        sleep(0.1)
                        os.kill(pid, SIGKILL)
                except OSError, error:
                    from errno import ESRCH
                    if error.errno == ESRCH:
                        print 'Server with pid %d has been killed by force.' % pid
                        killed = True
            if not killed:
                print 'Server with pid %d cannot be terminated.' % pid
                sys.exit(1)
        # Write a new pid file:
        try:
            open(pidFile, 'w').write(str(os.getpid()))
        except Exception:
            print 'The pid file %r could not be written.' % pidFile
            sys.exit(1)
    olduid = oldgid = stdout = stderr = log = None
    errorlevel = 1
    try:
        # Change server process group:
        if gid is not None:
            try:
                oldgid = os.getgid()
                if gid != oldgid:
                    os.setgid(gid)
                    if group:
                        print 'Changed server process group to %r.' % group
                else:
                    oldgid = None
            except Exception:
                if group:
                    print 'Could not set server process group to %r.' % group
                    oldgid = None
                    sys.exit(1)
        # Change server process user:
        if uid is not None:
            try:
                olduid = os.getuid()
                if uid != olduid:
                    os.setuid(uid)
                    print 'Changed server process user to %r.' % user
                else:
                    olduid = None
            except Exception:
                print 'Could not change server process user to %r.' % user
                olduid = None
                sys.exit(1)
        msg = 'WebKit.' + appServer
        if args[2]:
            msg = '%s %s' % (msg, ' '.join(args[2]))
        else:
            msg = 'Starting %s...' % msg
        print msg
        # Handle the log file:
        if logFile:
            logFile = os.path.expanduser(logFile)
            try:
                log = open(logFile, 'a', 1) # append, line buffered mode
                print 'Output has been redirected to %r...' % logFile
                stdout, stderr = sys.stdout, sys.stderr
                sys.stdout = sys.stderr = log
            except IOError, error:
                print 'Cannot redirect output to %r.' % logFile
                print error.strerror
                log = None
                sys.exit(1)
        else:
            print
        # Set up a reference to our profiler so apps can import and use it:
        from WebKit import Profiler
        if Profiler.startTime is None:
            from time import time
            Profiler.startTime = time()
        # Now start the app server:
        if runProfile:
            print ('Profiling is on. '
                'See docstring in Profiler.py for more info.')
            print
            try:
                from cProfile import Profile
            except ImportError:
                from profile import Profile
            profiler = Profile()
            Profiler.profiler = profiler
            errorlevel = Profiler.runCall(launchWebKit, *args)
            print
            print 'Writing profile stats to %s...' % Profiler.statsFilename
            Profiler.dumpStats()
            print 'WARNING: Applications run much slower when profiled,'
            print 'so turn off profiling in Launch.py when you are finished.'
        else:
            errorlevel = launchWebKit(*args)
    finally:
        print
        # Close the log file properly:
        if log:
            sys.stdout, sys.stderr = stdout, stderr
            log.close()
        # Restore server process group and user.
        # Note that because we changed the real group and
        # user id of the process (most secure thing), we
        # cannot change them back now, but we try anyway:
        if oldgid is not None:
            try:
                os.setgid(oldgid)
            except Exception:
                pass
            else:
                oldgid = None
        if olduid is not None:
            try:
                os.setuid(olduid)
            except Exception:
                pass
            else:
                olduid = None
        # Remove the pid file again.
        # Note that this may fail when the group or user id
        # has been changed, but we try anyway:
        if pidFile and os.path.exists(pidFile):
            try:
                os.remove(pidFile)
            except Exception:
                if oldgid is None and olduid is None:
                    print 'The pid file could not be removed.'
                    print
    sys.exit(errorlevel)

if __name__ == '__main__':
    main()