Advertisement
Guest User

wfastcgi Python 3.3

a guest
Oct 15th, 2013
529
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 21.43 KB | None | 0 0
  1.  # ############################################################################
  2.  #
  3.  # Copyright (c) Microsoft Corporation.
  4.  #
  5.  # This source code is subject to terms and conditions of the Apache License, Version 2.0. A
  6.  # copy of the license can be found in the License.html file at the root of this distribution. If
  7.  # you cannot locate the Apache License, Version 2.0, please send an email to
  8.  # vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9.  # by the terms of the Apache License, Version 2.0.
  10.  #
  11.  # You must not remove this notice, or any other, from this software.
  12.  #
  13.  # ###########################################################################
  14.  
  15. import sys
  16. import struct
  17. try:
  18.     import cStringIO
  19. except:
  20.     import io as cStringIO
  21.  
  22. import os
  23. import traceback
  24. import ctypes
  25. from os import path
  26. from xml.dom import minidom
  27. import re
  28. import datetime
  29. try:
  30.     import thread
  31. except:
  32.     import _thread as thread
  33.  
  34. __version__ = '2.0.0'
  35.  
  36. # http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3
  37.  
  38. FCGI_VERSION_1 = 1
  39. FCGI_HEADER_LEN = 8
  40.  
  41. FCGI_BEGIN_REQUEST       = 1
  42. FCGI_ABORT_REQUEST       = 2
  43. FCGI_END_REQUEST         = 3
  44. FCGI_PARAMS              = 4
  45. FCGI_STDIN               = 5
  46. FCGI_STDOUT              = 6
  47. FCGI_STDERR              = 7
  48. FCGI_DATA                = 8
  49. FCGI_GET_VALUES          = 9
  50. FCGI_GET_VALUES_RESULT  = 10
  51. FCGI_UNKNOWN_TYPE       = 11
  52. FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
  53.  
  54. FCGI_NULL_REQUEST_ID    = 0
  55.  
  56. FCGI_KEEP_CONN = 1
  57.  
  58. FCGI_RESPONDER  = 1
  59. FCGI_AUTHORIZER = 2
  60. FCGI_FILTER     = 3
  61.  
  62. FCGI_REQUEST_COMPLETE = 0
  63. FCGI_CANT_MPX_CONN    = 1
  64. FCGI_OVERLOADED       = 2
  65. FCGI_UNKNOWN_ROLE     = 3
  66.  
  67. FCGI_MAX_CONNS  = "FCGI_MAX_CONNS"
  68. FCGI_MAX_REQS   = "FCGI_MAX_REQS"
  69. FCGI_MPXS_CONNS = "FCGI_MPXS_CONNS"
  70.  
  71. class FastCgiRecord(object):
  72.     """Represents a FastCgiRecord.  Encapulates the type, role, flags.  Holds
  73. onto the params which we will receive and update later."""
  74.     def __init__(self, type, req_id, role, flags):
  75.         self.type = type
  76.         self.req_id = req_id
  77.         self.role = role
  78.         self.flags = flags
  79.         self.params = {}
  80.        
  81.     def __repr__(self):
  82.         return '<FastCgiRecord(%d, %d, %d, %d)>' % (self.type,
  83.                                                     self.req_id,
  84.                                                     self.role,
  85.                                                     self.flags)
  86.  
  87. #typedef struct {
  88. #   unsigned char version;
  89. #   unsigned char type;
  90. #   unsigned char requestIdB1;
  91. #   unsigned char requestIdB0;
  92. #   unsigned char contentLengthB1;
  93. #   unsigned char contentLengthB0;
  94. #   unsigned char paddingLength;
  95. #   unsigned char reserved;
  96. #   unsigned char contentData[contentLength];
  97. #   unsigned char paddingData[paddingLength];
  98. #} FCGI_Record;
  99.  
  100. class _ExitException(Exception):
  101.     pass
  102.  
  103. if sys.version_info[0] >= 3:
  104.     # indexing into byte strings gives us an int, so
  105.     # ord is unnecessary on Python 3
  106.     def ord(x): return x
  107.     def chr(x): return bytes((x, ))
  108.     def wsgi_decode(x):
  109.         return x.decode('iso-8859-1')
  110.     def wsgi_encode(x):
  111.         return x.encode('iso-8859-1')
  112. else:
  113.     def wsgi_decode(x):
  114.         return x
  115.     def wsgi_encode(x):
  116.         return x
  117.  
  118. def read_fastcgi_record(input):
  119.     """reads the main fast cgi record"""
  120.     data = input.read(8)     # read record
  121.     if not data:
  122.         # no more data, our other process must have died...
  123.         raise _ExitException()
  124.  
  125.     content_size = ord(data[4]) << 8 | ord(data[5])
  126.  
  127.     content = input.read(content_size)  # read content    
  128.     input.read(ord(data[6]))     # read padding
  129.  
  130.     if ord(data[0]) != FCGI_VERSION_1:
  131.         raise Exception('Unknown fastcgi version ' + str(data[0]))
  132.  
  133.     req_id = (ord(data[2]) << 8) | ord(data[3])
  134.    
  135.     reqtype = ord(data[1])
  136.     processor = REQUEST_PROCESSORS.get(reqtype)
  137.     if processor is None:
  138.         # unknown type requested, send response
  139.         send_response(req_id, FCGI_UNKNOWN_TYPE, data[1] + '\0' * 7)
  140.         return None
  141.  
  142.     return processor(req_id, content)
  143.  
  144.  
  145. def read_fastcgi_begin_request(req_id, content):
  146.     """reads the begin request body and updates our
  147. _REQUESTS table to include the new request"""
  148.     #    typedef struct {
  149.     #        unsigned char roleB1;
  150.     #        unsigned char roleB0;
  151.     #        unsigned char flags;
  152.     #        unsigned char reserved[5];
  153.     #    } FCGI_BeginRequestBody;
  154.  
  155.     # TODO: Ignore request if it exists    
  156.     res = FastCgiRecord(
  157.         FCGI_BEGIN_REQUEST,
  158.         req_id,
  159.         (ord(content[0]) << 8) | ord(content[1]),   # role
  160.         ord(content[2]),  # flags
  161.     )
  162.     _REQUESTS[req_id] = res    
  163.  
  164.  
  165. def read_fastcgi_keyvalue_pairs(content, offset):
  166.     """Reads a FastCGI key/value pair stream"""
  167.  
  168.     name_len = ord(content[offset])
  169.  
  170.     if (name_len & 0x80) != 0:
  171.         name_full_len = chr(name_len & ~0x80) + content[offset + 1:offset+4]
  172.         name_len = int_struct.unpack(name_full_len)[0]
  173.         offset += 4
  174.     else:
  175.         offset += 1
  176.    
  177.     value_len = ord(content[offset])
  178.  
  179.     if (value_len & 0x80) != 0:
  180.         value_full_len = chr(value_len & ~0x80) + content[offset+1:offset+4]
  181.         value_len = int_struct.unpack(value_full_len)[0]
  182.         offset += 4
  183.     else:
  184.         offset += 1
  185.  
  186.     name = wsgi_decode(content[offset:offset+name_len])
  187.     offset += name_len
  188.    
  189.     value = wsgi_decode(content[offset:offset+value_len])
  190.     offset += value_len
  191.  
  192.     return offset, name, value
  193.  
  194.  
  195. def write_name_len(io, name):
  196.     """Writes the length of a single name for a key or value in
  197. a key/value stream"""
  198.     if len(name) <= 0x7f:
  199.         io.write(chr(len(name)))
  200.     else:
  201.         io.write(int_struct.pack(len(name)))
  202.  
  203.  
  204. def write_fastcgi_keyvalue_pairs(pairs):
  205.     """creates a FastCGI key/value stream and returns it as a string"""
  206.     res = cStringIO.StringIO()
  207.     for key, value in pairs.iteritems():
  208.         write_name_len(res, key)
  209.         write_name_len(res, value)
  210.        
  211.         res.write(key)
  212.         res.write(value)
  213.  
  214.     return res.getvalue()
  215.  
  216.  
  217. def read_fastcgi_params(req_id, content):
  218.     if not content:
  219.         return None
  220.  
  221.     offset = 0
  222.     res = _REQUESTS[req_id].params
  223.     while offset < len(content):
  224.         offset, name, value = read_fastcgi_keyvalue_pairs(content, offset)
  225.         res[name] = value
  226.  
  227.  
  228. def read_fastcgi_input(req_id, content):
  229.     """reads FastCGI std-in and stores it in wsgi.input passed in the
  230. wsgi environment array"""
  231.     res = _REQUESTS[req_id].params
  232.     if 'wsgi.input' not in res:
  233.         res['wsgi.input'] = wsgi_decode(content)
  234.     else:
  235.         res['wsgi.input'] += wsgi_decode(content)
  236.  
  237.     if not content:
  238.         # we've hit the end of the input stream, time to process input...
  239.         return _REQUESTS[req_id]
  240.  
  241.  
  242. def read_fastcgi_data(req_id, content):
  243.     """reads FastCGI data stream and publishes it as wsgi.data"""
  244.     res = _REQUESTS[req_id].params
  245.     if 'wsgi.data' not in res:
  246.         res['wsgi.data'] = wsgi_decode(content)
  247.     else:
  248.         res['wsgi.data'] += wsgi_decode(content)
  249.  
  250.  
  251. def read_fastcgi_abort_request(req_id, content):
  252.     """reads the wsgi abort request, which we ignore, we'll send the
  253. finish execution request anyway..."""
  254.     pass
  255.  
  256.  
  257. def read_fastcgi_get_values(req_id, content):
  258.     """reads the fastcgi request to get parameter values, and immediately
  259. responds"""
  260.     offset = 0
  261.     request = {}
  262.     while offset < len(content):
  263.         offset, name, value = read_fastcgi_keyvalue_pairs(content, offset)
  264.         request[name] = value
  265.  
  266.     response = {}
  267.     if FCGI_MAX_CONNS in request:
  268.         response[FCGI_MAX_CONNS] = '1'
  269.  
  270.     if FCGI_MAX_REQS in request:
  271.         response[FCGI_MAX_REQS] = '1'
  272.  
  273.     if FCGI_MPXS_CONNS in request:
  274.         response[FCGI_MPXS_CONNS] = '0'
  275.  
  276.     send_response(req_id, FCGI_GET_VALUES_RESULT,
  277.                   write_fastcgi_keyvalue_pairs(response))
  278.  
  279.  
  280. # Formatting of 4-byte ints in network order
  281. int_struct = struct.Struct('!i')
  282.  
  283. # Our request processors for different FastCGI protocol requests.  Only
  284. # the requests which we receive are defined here.
  285. REQUEST_PROCESSORS = {
  286.     FCGI_BEGIN_REQUEST : read_fastcgi_begin_request,
  287.     FCGI_ABORT_REQUEST : read_fastcgi_abort_request,
  288.     FCGI_PARAMS : read_fastcgi_params,
  289.     FCGI_STDIN : read_fastcgi_input,
  290.     FCGI_DATA : read_fastcgi_data,
  291.     FCGI_GET_VALUES : read_fastcgi_get_values
  292. }
  293.  
  294. def log(txt):
  295.     """Logs fatal errors to a log file if WSGI_LOG env var is defined"""
  296.     log_file = os.environ.get('WSGI_LOG')
  297.     if log_file:
  298.         f = open(log_file, 'a+')
  299.         try:
  300.             f.write(str(datetime.datetime.now()))
  301.             f.write(': ')
  302.             f.write(txt)
  303.         finally:
  304.           f.close()
  305.  
  306.  
  307. def send_response(id, resp_type, content, streaming = True):
  308.     """sends a response w/ the given id, type, and content to the server.
  309. If the content is streaming then an empty record is sent at the end to
  310. terminate the stream"""
  311.     offset = 0
  312.     while 1:
  313.         if id < 256:
  314.             id_0 = 0
  315.             id_1 = id
  316.         else:
  317.             id_0 = id >> 8
  318.             id_1 = id & 0xff
  319.    
  320.         # content len, padding len, content
  321.         len_remaining = len(content) - offset
  322.         if len_remaining > 65535:
  323.             len_0 = 0xff
  324.             len_1 = 0xff
  325.             content_str = content[offset:offset+65535]
  326.             offset += 65535
  327.         else:
  328.             len_0 = len_remaining >> 8
  329.             len_1 = len_remaining & 0xff
  330.             content_str = content[offset:]
  331.             offset += len_remaining
  332.  
  333.         data = wsgi_encode('%c%c%c%c%c%c%c%c%s' % (
  334.                 FCGI_VERSION_1,     # version
  335.                 resp_type,          # type
  336.                 id_0,               # requestIdB1
  337.                 id_1,               # requestIdB0
  338.                 len_0,              # contentLengthB1
  339.                 len_1,              # contentLengthB0
  340.                 0,                  # paddingLength
  341.                 0,                  # reserved
  342.                 content_str))
  343.  
  344.         os.write(stdout, data)
  345.         if len_remaining == 0 or not streaming:
  346.             break
  347.     sys.stdin.flush()
  348.  
  349. def get_environment(dir):
  350.     web_config = path.join(dir, 'Web.config')
  351.  
  352.     d = {}
  353.  
  354.     if os.path.exists(web_config):
  355.         try:
  356.             wc = open(web_config)
  357.             try:
  358.                 doc = minidom.parse(wc)
  359.                 config = doc.getElementsByTagName('configuration')
  360.                 for configSection in config:
  361.                     appSettings = configSection.getElementsByTagName('appSettings')
  362.                     for appSettingsSection in appSettings:
  363.                         values = appSettingsSection.getElementsByTagName('add')
  364.                         for curAdd in values:
  365.                             key = curAdd.getAttribute('key')
  366.                             value = curAdd.getAttribute('value')
  367.                             if key and value is not None:
  368.                                 d[key.strip()] = value
  369.             finally:
  370.               wc.close()
  371.         except:
  372.             # unable to read file
  373.             log(traceback.format_exc())
  374.             pass
  375.     return d
  376.  
  377. ReadDirectoryChangesW = ctypes.WinDLL('kernel32').ReadDirectoryChangesW
  378. ReadDirectoryChangesW.restype = ctypes.c_bool
  379. ReadDirectoryChangesW.argtypes  = [ctypes.c_void_p,     # HANDLE hDirectory
  380.                                    ctypes.c_void_p,     # LPVOID lpBuffer
  381.                                    ctypes.c_uint32,     # DWORD nBufferLength
  382.                                    ctypes.c_bool,       # BOOL bWatchSubtree
  383.                                    ctypes.c_uint32,     # DWORD dwNotifyFilter
  384.                                    ctypes.POINTER(ctypes.c_uint32),  # LPDWORD lpBytesReturned
  385.                                    ctypes.c_void_p,     # LPOVERLAPPED lpOverlapped
  386.                                    ctypes.c_void_p      # LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
  387.                                   ]
  388.  
  389. CreateFile = ctypes.WinDLL('kernel32').CreateFileW
  390. CreateFile.restype = ctypes.c_void_p
  391. CreateFile.argtypes  = [ctypes.c_wchar_p,     # lpFilename
  392.                                    ctypes.c_uint32,      # dwDesiredAccess
  393.                                    ctypes.c_uint32,      # dwShareMode
  394.                                    ctypes.c_voidp,       # LPSECURITY_ATTRIBUTES,
  395.                                    ctypes.c_uint32,      # dwCreationDisposition,
  396.                                    ctypes.c_uint32,      # dwFlagsAndAttributes,
  397.                                    ctypes.c_void_p       # hTemplateFile
  398.                                   ]
  399.  
  400. ExitProcess = ctypes.WinDLL('kernel32').ExitProcess
  401. ExitProcess.restype = ctypes.c_void_p
  402. ExitProcess.argtypes  = [ctypes.c_uint32]
  403.  
  404. FILE_LIST_DIRECTORY = 1
  405. FILE_SHARE_READ = 0x00000001
  406. FILE_SHARE_WRITE = 0x00000002
  407. FILE_SHARE_DELETE = 0x00000004
  408. OPEN_EXISTING = 3
  409. FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
  410. MAX_PATH = 260
  411. FILE_NOTIFY_CHANGE_LAST_WRITE  = 0x10
  412.  
  413. class FILE_NOTIFY_INFORMATION(ctypes.Structure):
  414.     _fields_ = [('NextEntryOffset', ctypes.c_uint32),
  415.                 ('Action', ctypes.c_uint32),
  416.                 ('FileNameLength', ctypes.c_uint32),
  417.                 ('Filename', ctypes.c_wchar)]
  418.  
  419. def start_file_watcher(path, restartRegex):
  420.     if restartRegex is None:
  421.         restartRegex = ".*((\\.py)|(\\.config))$"
  422.     elif not restartRegex:
  423.         # restart regex set to empty string, no restart behavior
  424.         return
  425.    
  426.     log('wfastcgi.py will restart when files in ' + path + ' are changed: ' + restartRegex + '\n')
  427.     restart = re.compile(restartRegex)
  428.     def watcher(path, restart):
  429.         the_dir = CreateFile(path,
  430.                                 FILE_LIST_DIRECTORY,
  431.                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  432.                                 None,
  433.                                 OPEN_EXISTING,
  434.                                 FILE_FLAG_BACKUP_SEMANTICS,
  435.                                 None
  436.                             )
  437.        
  438.         buffer = ctypes.create_string_buffer(32 * 1024)
  439.         bytes_ret = ctypes.c_uint32()
  440.  
  441.         while ReadDirectoryChangesW(the_dir,
  442.                                 buffer,
  443.                                 ctypes.sizeof(buffer),
  444.                                 True,
  445.                                 FILE_NOTIFY_CHANGE_LAST_WRITE,
  446.                                 ctypes.byref(bytes_ret),
  447.                                 None,
  448.                                 None):
  449.             cur_pointer = ctypes.addressof(buffer)
  450.             while True:
  451.                 fni = ctypes.cast(cur_pointer, ctypes.POINTER(FILE_NOTIFY_INFORMATION))
  452.                 filename = ctypes.wstring_at(cur_pointer + 12)
  453.                 if restart.match(filename):
  454.                     log('wfastcgi.py exiting because ' + filename + ' has changed, matching ' + restartRegex + '\n')
  455.                     # we call ExitProcess directly to quickly shutdown the whole process
  456.                     # because sys.exit(0) won't have an effect on the main thread.
  457.                     ExitProcess(0)
  458.                 if fni.contents.NextEntryOffset == 0:
  459.                     break
  460.                 cur_pointer = cur_pointer + fni.contents.NextEntryOffset
  461.  
  462.     thread.start_new_thread(watcher, (path, restart))
  463.  
  464. def get_wsgi_handler(handler_name):
  465.         if not handler_name:
  466.             raise Exception('WSGI_HANDLER env var must be set')
  467.    
  468.         module, _, callable = handler_name.rpartition('.')
  469.         if not module:
  470.             raise Exception('WSGI_HANDLER must be set to module_name.wsgi_handler, got %s' % handler_name)
  471.    
  472.         if sys.version_info[0] == 2:
  473.             if isinstance(callable, unicode):
  474.                 callable = callable.encode('ascii')
  475.         else:
  476.             if isinstance(callable, bytes):
  477.                 callable = callable.encode('ascii')
  478.  
  479.         if callable.endswith('()'):
  480.             callable = callable.rstrip('()')
  481.             handler = getattr(__import__(module, fromlist=[callable]), callable)()
  482.         else:
  483.             handler = getattr(__import__(module, fromlist=[callable]), callable)
  484.    
  485.         if handler is None:
  486.             raise Exception('WSGI_HANDLER "' + handler_name + '" was set to None')
  487.  
  488.         return handler
  489.  
  490. def read_wsgi_handler(physical_path):
  491.     env = get_environment(physical_path)
  492.     os.environ.update(env)
  493.     for env_name in env:
  494.         if env_name.lower() == 'pythonpath':
  495.             # expand any environment variables in the path...
  496.             path = env[env_name]
  497.             for var in os.environ:
  498.                 pat = re.compile(re.escape('%' + var + '%'), re.IGNORECASE)
  499.                 path = pat.sub(lambda _:os.environ[var], path)
  500.            
  501.  
  502.             for path_location in path.split(';'):
  503.                 sys.path.append(path_location)
  504.             break
  505.    
  506.     handler_ex = None
  507.     try:
  508.         handler_name = os.getenv('WSGI_HANDLER')
  509.         handler = get_wsgi_handler(handler_name)
  510.     except:
  511.         handler = None
  512.         handler_ex = sys.exc_info()
  513.    
  514.    
  515.     return env, handler, handler_ex
  516.  
  517. if __name__ == '__main__':
  518.     stdout = sys.stdin.fileno()
  519.     try:
  520.         import msvcrt
  521.         msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
  522.     except ImportError:
  523.         pass
  524.    
  525.     _REQUESTS = {}
  526.    
  527.     initialized = False
  528.     fatal_error = False
  529.     log('wfastcgi.py ' + __version__ + ' started\n')
  530.     if sys.version_info[0] == 2:
  531.         input_src = sys.stdin
  532.     else:
  533.         input_src = sys.stdin.buffer.raw
  534.     while fatal_error is False:
  535.         try:
  536.             record = read_fastcgi_record(input_src)
  537.             if record:
  538.                 record.params['wsgi.input'] = cStringIO.StringIO(record.params['wsgi.input'])
  539.                 record.params['wsgi.version'] = (1,0)
  540.                 record.params['wsgi.url_scheme'] = 'https' if 'HTTPS' in record.params and record.params['HTTPS'].lower() == 'on' else 'http'
  541.                 record.params['wsgi.multiprocess'] = True
  542.                 record.params['wsgi.multithread'] = False
  543.                 record.params['wsgi.run_once'] = False
  544.  
  545.                 physical_path = record.params.get('DOCUMENT_ROOT', path.dirname(__file__))
  546.                
  547.                 errors = sys.stderr = sys.__stderr__ = record.params['wsgi.errors'] = cStringIO.StringIO()
  548.                 output = sys.stdout = sys.__stdout__ = cStringIO.StringIO()
  549.                
  550.                 if not initialized:
  551.                     os.chdir(physical_path)
  552.  
  553.                     env, handler, handler_ex = read_wsgi_handler(physical_path)
  554.  
  555.                     start_file_watcher(physical_path, env.get('WSGI_RESTART_FILE_REGEX'))
  556.  
  557.                     log('wfastcgi.py ' + __version__ + ' initialized\n')
  558.                     initialized = True
  559.                    
  560.                 def start_response(status, headers, exc_info = None):
  561.                     status = 'Status: ' + status + '\r\n'
  562.                     headers = ''.join('%s: %s\r\n' % (name, value) for name, value in headers)
  563.                     send_response(record.req_id, FCGI_STDOUT, status + headers + '\r\n')
  564.                
  565.                 os.environ.update(env)
  566.                 if 'HTTP_X_ORIGINAL_URL' in record.params:
  567.                     # We've been re-written for shared FastCGI hosting, send the original URL as the PATH_INFO.
  568.                     record.params['PATH_INFO'] = record.params['HTTP_X_ORIGINAL_URL']
  569.                
  570.                 # PATH_INFO is not supposed to include the query parameters, so remove them
  571.                 record.params['PATH_INFO'] = record.params['PATH_INFO'].partition('?')[0]
  572.  
  573.                 # SCRIPT_NAME + PATH_INFO is supposed to be the full path http://www.python.org/dev/peps/pep-0333/
  574.                 # but by default (http://msdn.microsoft.com/en-us/library/ms525840(v=vs.90).aspx) IIS is sending us
  575.                 # the full URL in PATH_INFO, so we need to clear the script name here
  576.                 if 'AllowPathInfoForScriptMappings' not in os.environ:
  577.                     record.params['SCRIPT_NAME'] = ''
  578.  
  579.                 if handler is None:
  580.                     fatal_error = True
  581.                     error_msg = ('Error while importing WSGI_HANDLER:\n\n' +
  582.                                     ''.join(traceback.format_exception(*handler_ex)) + '\n\n' +
  583.                                     'StdOut: ' + output.getvalue() + '\n\n'
  584.                                     'StdErr: ' + errors.getvalue() + '\n\n')
  585.                     log(error_msg)
  586.                     send_response(record.req_id, FCGI_STDERR, error_msg)
  587.                 else:                    
  588.                     try:
  589.                         for part in handler(record.params, start_response):
  590.                             if part:
  591.                                 send_response(record.req_id, FCGI_STDOUT, wsgi_decode(part))
  592.                     except:
  593.                         log('Exception from handler: ' + traceback.format_exc())
  594.                         send_response(record.req_id, FCGI_STDERR, errors.getvalue() + '\n\n' + traceback.format_exc())
  595.  
  596.                 send_response(record.req_id, FCGI_END_REQUEST, '\x00\x00\x00\x00\x00\x00\x00\x00', streaming=False)
  597.                 del _REQUESTS[record.req_id]
  598.         except _ExitException:
  599.             break
  600.         except:
  601.             log('Unhandled exception in wfastcgi.py: ' + traceback.format_exc())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement