26
26
#
27
27
28
28
import cgi
29
+ import codecs
29
30
import importlib
30
31
import os
31
32
import sys
35
36
from mig .shared .bailout import bailout_helper , crash_helper , compact_string
36
37
from mig .shared .base import requested_backend , allow_script , \
37
38
is_default_str_coding , force_default_str_coding_rec
39
+ from mig .shared .compat import PY2
38
40
from mig .shared .defaults import download_block_size , default_fs_coding
39
41
from mig .shared .conf import get_configuration_object
40
42
from mig .shared .objecttypes import get_object_type_info
43
45
from mig .shared .scriptinput import fieldstorage_to_dict
44
46
45
47
48
+ if PY2 :
49
+ def _ensure_encoded_string (chunk ):
50
+ return chunk
51
+ else :
52
+ def _ensure_encoded_string (chunk ):
53
+ return codecs .encode (chunk , 'utf8' )
54
+
55
+
56
+ def _import_backend (backend ):
57
+ import_path = 'mig.shared.functionality.%s' % backend
58
+ module_handle = importlib .import_module (import_path )
59
+ return module_handle .main
60
+
61
+
46
62
def object_type_info (object_type ):
47
63
"""Lookup object type"""
48
64
49
65
return get_object_type_info (object_type )
50
66
51
67
52
- def stub (configuration , client_id , import_path , backend , user_arguments_dict ,
53
- environ ):
68
+ def stub (configuration , client_id , user_arguments_dict , environ , _retrieve_handler ):
54
69
"""Run backend on behalf of client_id with supplied user_arguments_dict.
55
70
I.e. import main from import_path and execute it with supplied arguments.
56
71
"""
@@ -61,6 +76,7 @@ def stub(configuration, client_id, import_path, backend, user_arguments_dict,
61
76
before_time = time .time ()
62
77
63
78
output_objects = []
79
+ backend = 'UNKNOWN'
64
80
main = dummy_main
65
81
66
82
# _logger.debug("stub for backend %r" % backend)
@@ -69,10 +85,12 @@ def stub(configuration, client_id, import_path, backend, user_arguments_dict,
69
85
# NEVER print/output it verbatim before it is validated below.
70
86
71
87
try :
88
+ default_page = configuration .site_landing_page # TODO: avoid doing this work a second time
89
+ backend = requested_backend (environ , fallback = default_page )
72
90
valid_backend_name (backend )
73
91
except InputException as iex :
74
- _logger .error ("%s refused to import invalid backend %r (%s) : %s" %
75
- (_addr , backend , import_path , iex ))
92
+ _logger .error ("%s refused to import invalid backend %r: %s" %
93
+ (_addr , backend , iex ))
76
94
bailout_helper (configuration , backend , output_objects ,
77
95
header_text = 'User Error' )
78
96
output_objects .extend ([
@@ -81,41 +99,40 @@ def stub(configuration, client_id, import_path, backend, user_arguments_dict,
81
99
{'object_type' : 'link' , 'text' : 'Go to default interface' ,
82
100
'destination' : configuration .site_landing_page }
83
101
])
84
- return (output_objects , returnvalues .CLIENT_ERROR )
102
+ return backend , (output_objects , returnvalues .CLIENT_ERROR )
85
103
86
104
try :
87
105
# Import main from backend module
88
106
89
107
# _logger.debug("import main from %r" % import_path)
90
108
# NOTE: dynamic module loading to find corresponding main function
91
- module_handle = importlib .import_module (import_path )
92
- main = module_handle .main
109
+ main = _retrieve_handler (backend )
93
110
except Exception as err :
94
- _logger .error ("%s could not import %r (%s) : %s" %
95
- (_addr , backend , import_path , err ))
111
+ _logger .error ("%s could not import %r: %s" %
112
+ (_addr , backend , err ))
96
113
bailout_helper (configuration , backend , output_objects )
97
114
output_objects .extend ([
98
115
{'object_type' : 'error_text' , 'text' :
99
116
'Could not load backend: %s' % html_escape (backend )},
100
117
{'object_type' : 'link' , 'text' : 'Go to default interface' ,
101
118
'destination' : configuration .site_landing_page }
102
119
])
103
- return (output_objects , returnvalues .SYSTEM_ERROR )
120
+ return backend , (output_objects , returnvalues .SYSTEM_ERROR )
104
121
105
122
# _logger.debug("imported main %s" % main)
106
123
107
124
# Now backend value is validated to be safe for output
108
125
109
126
if not isinstance (user_arguments_dict , dict ):
110
- _logger .error ("%s invalid user args %s for %s " % (_addr ,
127
+ _logger .error ("%s invalid user args %s for backend %r " % (_addr ,
111
128
user_arguments_dict ,
112
- import_path ))
129
+ backend ))
113
130
bailout_helper (configuration , backend , output_objects ,
114
131
header_text = 'Input Error' )
115
132
output_objects .append (
116
133
{'object_type' : 'error_text' , 'text' :
117
134
'User input is not on expected format!' })
118
- return (output_objects , returnvalues .INVALID_ARGUMENT )
135
+ return backend , (output_objects , returnvalues .INVALID_ARGUMENT )
119
136
120
137
try :
121
138
(output_objects , (ret_code , ret_msg )) = main (client_id ,
@@ -125,7 +142,7 @@ def stub(configuration, client_id, import_path, backend, user_arguments_dict,
125
142
_logger .error ("%s script crashed:\n %s" % (_addr ,
126
143
traceback .format_exc ()))
127
144
crash_helper (configuration , backend , output_objects )
128
- return (output_objects , returnvalues .ERROR )
145
+ return backend , (output_objects , returnvalues .ERROR )
129
146
130
147
(val_ret , val_msg ) = validate (output_objects )
131
148
if not val_ret :
@@ -138,7 +155,7 @@ def stub(configuration, client_id, import_path, backend, user_arguments_dict,
138
155
after_time = time .time ()
139
156
output_objects .append ({'object_type' : 'timing_info' , 'text' :
140
157
"done in %.3fs" % (after_time - before_time )})
141
- return (output_objects , (ret_code , ret_msg ))
158
+ return backend , (output_objects , (ret_code , ret_msg ))
142
159
143
160
144
161
def wrap_wsgi_errors (environ , configuration , max_line_len = 100 ):
@@ -193,6 +210,14 @@ def application(environ, start_response):
193
210
*start_response* is a helper function used to deliver the client response.
194
211
"""
195
212
213
+ def _set_os_environ (value ):
214
+ os .environ = value
215
+
216
+ return _application (None , environ , start_response , _set_environ = _set_os_environ , _wrap_wsgi_errors = wrap_wsgi_errors )
217
+
218
+
219
+ def _application (configuration , environ , start_response , _set_environ , _format_output = format_output , _retrieve_handler = _import_backend , _wrap_wsgi_errors = True , _config_file = None , _skip_log = False ):
220
+
196
221
# NOTE: pass app environ including apache and query args on to sub handlers
197
222
# through the usual 'os.environ' channel expected in functionality
198
223
# handlers. Special care is needed to avoid various sub-interpreter
@@ -235,18 +260,20 @@ def application(environ, start_response):
235
260
os_env_value ))
236
261
237
262
# Assign updated environ to LOCAL os.environ for the rest of this session
238
- os . environ = environ
263
+ _set_environ ( environ )
239
264
240
265
# NOTE: redirect stdout to stderr in python 2 only. It breaks logger in 3
241
266
# and stdout redirection apparently is already handled there.
242
267
if sys .version_info [0 ] < 3 :
243
268
sys .stdout = sys .stderr
244
269
245
- configuration = get_configuration_object ()
270
+ if configuration is None :
271
+ configuration = get_configuration_object (_config_file , _skip_log )
272
+
246
273
_logger = configuration .logger
247
274
248
275
# NOTE: replace default wsgi errors to apache error log with our own logs
249
- wrap_wsgi_errors (environ , configuration )
276
+ _wrap_wsgi_errors (environ , configuration )
250
277
251
278
for line in env_sync_status :
252
279
_logger .debug (line )
@@ -298,7 +325,6 @@ def application(environ, start_response):
298
325
default_page = configuration .site_landing_page
299
326
script_name = requested_backend (environ , fallback = default_page ,
300
327
strip_ext = False )
301
- backend = requested_backend (environ , fallback = default_page )
302
328
# _logger.debug('DEBUG: wsgi found backend %s and script %s' %
303
329
# (backend, script_name))
304
330
fieldstorage = cgi .FieldStorage (fp = environ ['wsgi.input' ],
@@ -307,13 +333,12 @@ def application(environ, start_response):
307
333
if 'output_format' in user_arguments_dict :
308
334
output_format = user_arguments_dict ['output_format' ][0 ]
309
335
310
- module_path = 'mig.shared.functionality.%s' % backend
311
336
(allow , msg ) = allow_script (configuration , script_name , client_id )
312
337
if allow :
313
338
# _logger.debug("wsgi handling script: %s" % script_name)
314
- (output_objs , ret_val ) = stub (configuration , client_id ,
315
- module_path , backend ,
316
- user_arguments_dict , environ )
339
+ backend , (output_objs , ret_val ) = stub (configuration , client_id ,
340
+ user_arguments_dict , environ ,
341
+ _retrieve_handler )
317
342
else :
318
343
_logger .warning ("wsgi handling refused script:%s" % script_name )
319
344
(output_objs , ret_val ) = reject_main (client_id ,
@@ -363,7 +388,7 @@ def application(environ, start_response):
363
388
output_objs .append (wsgi_entry )
364
389
365
390
_logger .debug ("call format %r output to %s" % (backend , output_format ))
366
- output = format_output (configuration , backend , ret_code , ret_msg ,
391
+ output = _format_output (configuration , backend , ret_code , ret_msg ,
367
392
output_objs , output_format )
368
393
# _logger.debug("formatted %s output to %s" % (backend, output_format))
369
394
# _logger.debug("output:\n%s" % [output])
@@ -372,7 +397,7 @@ def application(environ, start_response):
372
397
_logger .error (
373
398
"Formatted output is NOT on default str coding: %s" % [output [:100 ]])
374
399
err_mark = '__****__'
375
- output = format_output (configuration , backend , ret_code , ret_msg ,
400
+ output = _format_output (configuration , backend , ret_code , ret_msg ,
376
401
force_default_str_coding_rec (
377
402
output_objs , highlight = err_mark ),
378
403
output_format )
@@ -396,7 +421,14 @@ def application(environ, start_response):
396
421
# NOTE: send response to client but don't crash e.g. on closed connection
397
422
try :
398
423
start_response (status , response_headers )
424
+ except IOError as ioe :
425
+ _logger .warning ("WSGI %s for %s could not deliver output: %s" %
426
+ (backend , client_id , ioe ))
427
+ except Exception as exc :
428
+ _logger .error ("WSGI %s for %s crashed during response: %s" %
429
+ (backend , client_id , exc ))
399
430
431
+ try :
400
432
# NOTE: we consistently hit download error for archive files reaching ~2GB
401
433
# with showfreezefile.py on wsgi but the same on cgi does NOT suffer
402
434
# the problem for the exact same files. It seems wsgi has a limited
@@ -410,12 +442,12 @@ def application(environ, start_response):
410
442
_logger .info ("WSGI %s yielding %d output parts (%db)" %
411
443
(backend , chunk_parts , content_length ))
412
444
# _logger.debug("send chunked %r response to client" % backend)
413
- for i in xrange ( chunk_parts ):
445
+ for i in list ( range ( chunk_parts ) ):
414
446
# _logger.debug("WSGI %s yielding part %d / %d output parts" %
415
447
# (backend, i+1, chunk_parts))
416
448
# end index may be after end of content - but no problem
417
449
part = output [i * download_block_size :(i + 1 )* download_block_size ]
418
- yield part
450
+ yield _ensure_encoded_string ( part )
419
451
if chunk_parts > 1 :
420
452
_logger .info ("WSGI %s finished yielding all %d output parts" %
421
453
(backend , chunk_parts ))
0 commit comments