changeset 2434:a2df85adface

http server: support persistent connections. only "hg serve" affected yet. http server running cgi script will not use persistent connections. support for fastcgi will help that. clients that support keepalive can use one tcp connection for all commands during clone and pull. this makes latency of binary search during pull much lower over wan. if server does not know content-length, it will force connection to close at end. right fix is to use chunked transfer-encoding but this is easier and does not hurt performance. only command that is affected is "changegroup" which is always last command during a pull.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Thu, 15 Jun 2006 12:55:58 -0700
parents d09da6fc1061
children ff2bac730b99
files mercurial/hgweb/hgweb_mod.py mercurial/hgweb/request.py mercurial/hgweb/server.py
diffstat 3 files changed, 38 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -10,7 +10,7 @@ import os
 import os.path
 import mimetypes
 from mercurial.demandload import demandload
-demandload(globals(), "re zlib ConfigParser")
+demandload(globals(), "re zlib ConfigParser cStringIO")
 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
 demandload(globals(), "mercurial.hgweb.request:hgrequest")
 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
@@ -761,26 +761,32 @@ class hgweb(object):
                                    req.form['filenode'][0]))
 
         elif cmd == 'heads':
-            req.httphdr("application/mercurial-0.1")
-            h = self.repo.heads()
-            req.write(" ".join(map(hex, h)) + "\n")
+            resp = " ".join(map(hex, self.repo.heads())) + "\n"
+            req.httphdr("application/mercurial-0.1", length=len(resp))
+            req.write(resp)
 
         elif cmd == 'branches':
-            req.httphdr("application/mercurial-0.1")
             nodes = []
             if req.form.has_key('nodes'):
                 nodes = map(bin, req.form['nodes'][0].split(" "))
+            resp = cStringIO.StringIO()
             for b in self.repo.branches(nodes):
-                req.write(" ".join(map(hex, b)) + "\n")
+                resp.write(" ".join(map(hex, b)) + "\n")
+            resp = resp.getvalue()
+            req.httphdr("application/mercurial-0.1", length=len(resp))
+            req.write(resp)
 
         elif cmd == 'between':
-            req.httphdr("application/mercurial-0.1")
             nodes = []
             if req.form.has_key('pairs'):
                 pairs = [map(bin, p.split("-"))
                          for p in req.form['pairs'][0].split(" ")]
+            resp = cStringIO.StringIO()
             for b in self.repo.between(pairs):
-                req.write(" ".join(map(hex, b)) + "\n")
+                resp.write(" ".join(map(hex, b)) + "\n")
+            resp = resp.getvalue()
+            req.httphdr("application/mercurial-0.1", length=len(resp))
+            req.write(resp)
 
         elif cmd == 'changegroup':
             req.httphdr("application/mercurial-0.1")
@@ -819,3 +825,4 @@ class hgweb(object):
 
         else:
             req.write(self.t("error"))
+        req.done()
--- a/mercurial/hgweb/request.py
+++ b/mercurial/hgweb/request.py
@@ -16,6 +16,7 @@ class hgrequest(object):
         self.out = out or sys.stdout
         self.env = env or os.environ
         self.form = cgi.parse(self.inp, self.env, keep_blank_values=1)
+        self.will_close = True
 
     def write(self, *things):
         for thing in things:
@@ -29,16 +30,30 @@ class hgrequest(object):
                     if inst[0] != errno.ECONNRESET:
                         raise
 
+    def done(self):
+        if self.will_close:
+            self.inp.close()
+            self.out.close()
+        else:
+            self.out.flush()
+
     def header(self, headers=[('Content-type','text/html')]):
         for header in headers:
             self.out.write("%s: %s\r\n" % header)
         self.out.write("\r\n")
 
-    def httphdr(self, type, file="", size=0):
+    def httphdr(self, type, filename=None, length=0):
 
         headers = [('Content-type', type)]
-        if file:
-            headers.append(('Content-disposition', 'attachment; filename=%s' % file))
-        if size > 0:
-            headers.append(('Content-length', str(size)))
+        if filename:
+            headers.append(('Content-disposition', 'attachment; filename=%s' %
+                            filename))
+        # we do not yet support http 1.1 chunked transfer, so we have
+        # to force connection to close if content-length not known
+        if length:
+            headers.append(('Content-length', str(length)))
+            self.will_close = False
+        else:
+            headers.append(('Connection', 'close'))
+            self.will_close = True
         self.header(headers)
--- a/mercurial/hgweb/server.py
+++ b/mercurial/hgweb/server.py
@@ -27,6 +27,7 @@ def _splitURI(uri):
 
 class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler):
     def __init__(self, *args, **kargs):
+        self.protocol_version = 'HTTP/1.1'
         BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
 
     def log_error(self, format, *args):
@@ -85,7 +86,7 @@ class _hgwebhandler(object, BaseHTTPServ
 
         req = hgrequest(self.rfile, self.wfile, env)
         self.send_response(200, "Script output follows")
-        self.server.make_and_run_handler(req)
+        self.close_connection = self.server.make_and_run_handler(req)
 
 def create_server(ui, repo):
     use_threads = True
@@ -135,6 +136,7 @@ def create_server(ui, repo):
             else:
                 raise hg.RepoError(_('no repo found'))
             hgwebobj.run(req)
+            return req.will_close
 
     class IPv6HTTPServer(MercurialHTTPServer):
         address_family = getattr(socket, 'AF_INET6', None)