[Zope-Checkins] CVS: Zope/lib/python/ZServer/medusa/sendfile - README:1.1.2.1 asynchat_sendfile.py:1.1.2.1 sendfilemodule.c:1.1.2.1 test_sendfile.py:1.1.2.1

Chris McDonough chrism@zope.com
Tue, 17 Sep 2002 01:16:10 -0400


Update of /cvs-repository/Zope/lib/python/ZServer/medusa/sendfile
In directory cvs.zope.org:/tmp/cvs-serv12650/lib/python/ZServer/medusa/sendfile

Added Files:
      Tag: chrism-install-branch
	README asynchat_sendfile.py sendfilemodule.c test_sendfile.py 
Log Message:
Moved ZServer into lib/python.


=== Added File Zope/lib/python/ZServer/medusa/sendfile/README ===
# -*- Mode: text; tab-width:4 -*-
#
# $Id: README,v 1.1.2.1 2002/09/17 05:16:09 chrism Exp $
#

Preliminary support for asynchronous sendfile() on linux and freebsd.
Not heavily tested yet.

-Sam


=== Added File Zope/lib/python/ZServer/medusa/sendfile/asynchat_sendfile.py ===
# -*- Mode: Python; tab-width: 4 -*-

RCS_ID = '$Id: asynchat_sendfile.py,v 1.1.2.1 2002/09/17 05:16:09 chrism Exp $'

import sendfile
import asynchat

async_chat = asynchat.async_chat

# we can't call sendfile() until ac_out_buffer is empty.

class async_chat_with_sendfile (async_chat):

        # if we are in the middle of sending a file, this will be overriden
    _sendfile = None
    
    def push_sendfile (self, fd, offset, bytes, callback=None):
            # we set out_buffer_size to zero in order to keep async_chat
            # from calling refill_buffer until the whole file has been sent.
        self._saved_obs = self.ac_out_buffer_size
        self.ac_out_buffer_size = 0
        self._sendfile = (fd, offset, bytes, callback)
        
    def initiate_send (self):
        if self._sendfile is None:
            async_chat.initiate_send (self)
        else:
            if len(self.ac_out_buffer):
                async_chat.initiate_send (self)
            else:
                fd, offset, bytes, callback = self._sendfile
                me = self.socket.fileno()
                try:
                    sent = sendfile.sendfile (fd, me, offset, bytes)
                    offset = offset + sent
                    bytes = bytes - sent
                    if bytes:
                        self._sendfile = (fd, offset, bytes, callback)
                    else:
                        self._sendfile = None
                        self.ac_out_buffer_size = self._saved_obs
                        if callback is not None:
                                # success
                            callback (1, fd)
                except:
                    self._sendfile = None
                    self.ac_out_buffer_size = self._saved_obs
                    # failure
                    if callback is not None:
                        callback (0, fd)
                        
                        # here's how you might use this:
                        # fd = os.open (filename, os.O_RDONLY, 0644)
                        # size = os.lseek (fd, 0, 2)
                        # os.lseek (fd, 0, 0)
                        # self.push ('%08x' % size)
                        # self.push_sendfile (fd, 0, size)


=== Added File Zope/lib/python/ZServer/medusa/sendfile/sendfilemodule.c ===
// -*- Mode: C; tab-width: 8 -*-

#include "Python.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>

//
// sendfile(), meant for use with a non-blocking socket
//

#if defined (__linux__)

// these are taken from linux/socket.h, for some reason including that file doesn't work here.
#define SOL_TCP 6
#define TCP_CORK 3

// non-blocking TCP_CORK sucks mugwump jism.

static PyObject *
py_sendfile (PyObject * self, PyObject * args)
{
  int fd;
  int sock;
  long o,n;
  char * head = NULL;
  size_t head_len = 0;
  char * tail = NULL;
  size_t tail_len = 0;
  
  if (!PyArg_ParseTuple (
          args, "iill|s#s#",
	  &fd, &sock, &o, &n,
	  &head, &head_len,
	  &tail, &tail_len
	  )) {
    return NULL;
  } else {
    off_t offset = o;
    size_t nbytes = n;
    int orig_cork = 1;
    int orig_cork_len = sizeof(int);
    PyObject * py_result = NULL;
    ssize_t sent_h = 0;
    ssize_t sent_f = 0;
    ssize_t sent_t = 0;

    if (head || tail) {
      int cork = 1;
      // first, fetch the original setting
      getsockopt (sock, SOL_TCP, TCP_CORK, (void*)&orig_cork, &orig_cork_len);
      setsockopt (sock, SOL_TCP, TCP_CORK, (void*)&cork, sizeof(cork));
    }											

    // send header
    if (head) {
      sent_h = send (sock, head, head_len, 0);
      if (sent_h < 0) {
	PyErr_SetFromErrno (PyExc_OSError);
	py_result = NULL;
	goto done;
      } else if (sent_h < head_len) {
	py_result = PyInt_FromLong (sent_h);
	goto done;
      }
    }
	
    // send file
    sent_f = sendfile (sock, fd, &offset, nbytes);
    if (sent_f < 0) {
      PyErr_SetFromErrno (PyExc_OSError);
      py_result = NULL;
      goto done;
    } else if (sent_f < nbytes) {
      py_result = PyInt_FromLong (sent_h + sent_f);
      goto done;
    }

    // send trailer
    if (tail) {
      sent_t = send (sock, tail, tail_len, 0);
      if (sent_t < 0) {
	PyErr_SetFromErrno (PyExc_OSError);
	py_result = NULL;
	goto done;
      }
    }
      
    py_result = PyInt_FromLong (sent_h + sent_f + sent_t);
    
  done:
    if (head || tail) {
      setsockopt (sock, SOL_TCP, TCP_CORK, (void*)&orig_cork, sizeof(orig_cork));
    }											
    return py_result;
  }
}

#elif defined (__FreeBSD__)
// XXX support the header/trailer stuff.
static PyObject *
py_sendfile (PyObject * self, PyObject * args)
{
  int fd;
  int sock;
  long o,n;
  char * head = NULL;
  size_t head_len = 0;
  char * tail = NULL;
  size_t tail_len = 0;
  
  if (!PyArg_ParseTuple (
          args, "iill|s#s#",
	  &fd, &sock, &o, &n,
	  &head, &head_len,
	  &tail, &tail_len
	  )
      ) {
    return NULL;
  } else {
    off_t offset = o;
    size_t nbytes = n;
    off_t sbytes;
    int result;

    if (head || tail) {
      struct iovec ivh = {head, head_len};
      struct iovec ivt = {tail, tail_len};
      struct sf_hdtr hdtr = {&ivh, 1, &ivt, 1};
      result = sendfile (fd, sock, offset, nbytes, &hdtr, &sbytes, 0);
    } else {
      result = sendfile (fd, sock, offset, nbytes, NULL, &sbytes, 0);
    }
    if (result == -1) {
      if (errno == EAGAIN) {
	return PyInt_FromLong (sbytes);
      } else {
	PyErr_SetFromErrno (PyExc_OSError);
      }
    } else {
      return PyInt_FromLong (sbytes);
    }
  }
}

#else
#error ("unknown platform")
#endif


// <off_t> and <size_t> are 64 bits on FreeBSD.
// If you need this feature, then look at PyLong_FromUnsignedLongLong.
// You'll have to specifically check for the PyLong type, etc...
// Also, LONG_LONG is a configure variable (i.e., the conversion function
// might not be available).

static PyMethodDef sendfile_methods[] = {
  {"sendfile",		py_sendfile,		1},
  {NULL,		NULL}		/* sentinel */
};

DL_EXPORT(void)
initsendfile()
{
  PyObject *m, *d;
  m = Py_InitModule("sendfile", sendfile_methods);
}


=== Added File Zope/lib/python/ZServer/medusa/sendfile/test_sendfile.py ===
# -*- Mode: Python; tab-width: 4 -*-

RCS_ID = '$Id: test_sendfile.py,v 1.1.2.1 2002/09/17 05:16:09 chrism Exp $'

import asyncore
import os
import socket
import string

# server: just run the script with no args
# client: python test_sendfile.py -c <remote-host> <remote-filename>

if __name__ == '__main__':
    import sys
    if '-c' in sys.argv:
        import operator
        import socket
        s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
        host = sys.argv[2]
        s.connect ((host, 9009))
        fname = sys.argv[3]
        s.send (fname + '\r\n')
        size = string.atoi (s.recv(8), 16)
        total = 0
        blocks = []
        while (total < size):
            block = s.recv (8192)
            if not block:
                break
            else:
                total = total + len(block)
                blocks.append (block)
        import sys
        for b in blocks:
            sys.stdout.write (b)
    else:
        import asynchat_sendfile
        
        class test_channel (asynchat_sendfile.async_chat_with_sendfile):
        
            def __init__ (self, conn, addr):
                asynchat_sendfile.async_chat_with_sendfile.__init__ (self, conn)
                self.set_terminator ('\r\n')
                self.buffer = ''
                
            def collect_incoming_data (self, data):
                self.buffer = self.buffer + data
                
            def found_terminator (self):
                filename, self.buffer = self.buffer, ''
                if filename:
                    fd = os.open (filename, os.O_RDONLY, 0644)
                    size = os.lseek (fd, 0, 2)
                    os.lseek (fd, 0, 0)
                    self.push ('%08x' % size)
                    self.push_sendfile (fd, 0, size, self.sendfile_callback)
                    self.close_when_done()
                else:
                    self.push ('ok, bye\r\n')
                    self.close_when_done()
                    
            def sendfile_callback (self, success, fd):
                os.close (fd)
                
        class test_server (asyncore.dispatcher):
            def __init__ (self, addr=('', 9009)):
                self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
                self.set_reuse_addr()
                self.bind (addr)
                self.listen (2048)
                print 'server started on', addr
                
            def handle_accept (self):
                conn, addr = self.accept()
                test_channel (conn, addr)
                
        test_server()
        asyncore.loop()