mercurial/hgweb/request.py
changeset 36908 cd6ae9ab7bd8
parent 36900 219b23359f4c
child 36909 84110a1d0f7d
equal deleted inserted replaced
36907:c1de7efca574 36908:cd6ae9ab7bd8
     6 # This software may be used and distributed according to the terms of the
     6 # This software may be used and distributed according to the terms of the
     7 # GNU General Public License version 2 or any later version.
     7 # GNU General Public License version 2 or any later version.
     8 
     8 
     9 from __future__ import absolute_import
     9 from __future__ import absolute_import
    10 
    10 
    11 import errno
       
    12 import socket
       
    13 import wsgiref.headers as wsgiheaders
    11 import wsgiref.headers as wsgiheaders
    14 #import wsgiref.validate
    12 #import wsgiref.validate
    15 
       
    16 from .common import (
       
    17     ErrorResponse,
       
    18     statusmessage,
       
    19 )
       
    20 
    13 
    21 from ..thirdparty import (
    14 from ..thirdparty import (
    22     attr,
    15     attr,
    23 )
    16 )
    24 from .. import (
    17 from .. import (
   607         self.multiprocess = wsgienv[r'wsgi.multiprocess']
   600         self.multiprocess = wsgienv[r'wsgi.multiprocess']
   608         self.run_once = wsgienv[r'wsgi.run_once']
   601         self.run_once = wsgienv[r'wsgi.run_once']
   609         self.env = wsgienv
   602         self.env = wsgienv
   610         self.req = parserequestfromenv(wsgienv, inp, altbaseurl=altbaseurl)
   603         self.req = parserequestfromenv(wsgienv, inp, altbaseurl=altbaseurl)
   611         self.res = wsgiresponse(self.req, start_response)
   604         self.res = wsgiresponse(self.req, start_response)
   612         self._start_response = start_response
       
   613         self.server_write = None
       
   614         self.headers = []
       
   615 
       
   616     def respond(self, status, type, filename=None, body=None):
       
   617         if not isinstance(type, str):
       
   618             type = pycompat.sysstr(type)
       
   619         if self._start_response is not None:
       
   620             self.headers.append((r'Content-Type', type))
       
   621             if filename:
       
   622                 filename = (filename.rpartition('/')[-1]
       
   623                             .replace('\\', '\\\\').replace('"', '\\"'))
       
   624                 self.headers.append(('Content-Disposition',
       
   625                                      'inline; filename="%s"' % filename))
       
   626             if body is not None:
       
   627                 self.headers.append((r'Content-Length', str(len(body))))
       
   628 
       
   629             for k, v in self.headers:
       
   630                 if not isinstance(v, str):
       
   631                     raise TypeError('header value must be string: %r' % (v,))
       
   632 
       
   633             if isinstance(status, ErrorResponse):
       
   634                 self.headers.extend(status.headers)
       
   635                 status = statusmessage(status.code, pycompat.bytestr(status))
       
   636             elif status == 200:
       
   637                 status = '200 Script output follows'
       
   638             elif isinstance(status, int):
       
   639                 status = statusmessage(status)
       
   640 
       
   641             # Various HTTP clients (notably httplib) won't read the HTTP
       
   642             # response until the HTTP request has been sent in full. If servers
       
   643             # (us) send a response before the HTTP request has been fully sent,
       
   644             # the connection may deadlock because neither end is reading.
       
   645             #
       
   646             # We work around this by "draining" the request data before
       
   647             # sending any response in some conditions.
       
   648             drain = False
       
   649             close = False
       
   650 
       
   651             # If the client sent Expect: 100-continue, we assume it is smart
       
   652             # enough to deal with the server sending a response before reading
       
   653             # the request. (httplib doesn't do this.)
       
   654             if self.env.get(r'HTTP_EXPECT', r'').lower() == r'100-continue':
       
   655                 pass
       
   656             # Only tend to request methods that have bodies. Strictly speaking,
       
   657             # we should sniff for a body. But this is fine for our existing
       
   658             # WSGI applications.
       
   659             elif self.env[r'REQUEST_METHOD'] not in (r'POST', r'PUT'):
       
   660                 pass
       
   661             else:
       
   662                 # If we don't know how much data to read, there's no guarantee
       
   663                 # that we can drain the request responsibly. The WSGI
       
   664                 # specification only says that servers *should* ensure the
       
   665                 # input stream doesn't overrun the actual request. So there's
       
   666                 # no guarantee that reading until EOF won't corrupt the stream
       
   667                 # state.
       
   668                 if not isinstance(self.req.bodyfh, util.cappedreader):
       
   669                     close = True
       
   670                 else:
       
   671                     # We /could/ only drain certain HTTP response codes. But 200
       
   672                     # and non-200 wire protocol responses both require draining.
       
   673                     # Since we have a capped reader in place for all situations
       
   674                     # where we drain, it is safe to read from that stream. We'll
       
   675                     # either do a drain or no-op if we're already at EOF.
       
   676                     drain = True
       
   677 
       
   678             if close:
       
   679                 self.headers.append((r'Connection', r'Close'))
       
   680 
       
   681             if drain:
       
   682                 assert isinstance(self.req.bodyfh, util.cappedreader)
       
   683                 while True:
       
   684                     chunk = self.req.bodyfh.read(32768)
       
   685                     if not chunk:
       
   686                         break
       
   687 
       
   688             self.server_write = self._start_response(
       
   689                 pycompat.sysstr(status), self.headers)
       
   690             self._start_response = None
       
   691             self.headers = []
       
   692         if body is not None:
       
   693             self.write(body)
       
   694             self.server_write = None
       
   695 
       
   696     def write(self, thing):
       
   697         if thing:
       
   698             try:
       
   699                 self.server_write(thing)
       
   700             except socket.error as inst:
       
   701                 if inst[0] != errno.ECONNRESET:
       
   702                     raise
       
   703 
       
   704     def flush(self):
       
   705         return None
       
   706 
   605 
   707 def wsgiapplication(app_maker):
   606 def wsgiapplication(app_maker):
   708     '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
   607     '''For compatibility with old CGI scripts. A plain hgweb() or hgwebdir()
   709     can and should now be used as a WSGI application.'''
   608     can and should now be used as a WSGI application.'''
   710     application = app_maker()
   609     application = app_maker()