Commit 266751aa authored by Amos Latteier's avatar Amos Latteier

added support for sized input (via integer terminators) to asynchat,

added ready medthod to producer interface and updated most existing producers
to work with it. The ready method allows future producers.
parent fd639be3
# -*- Mode: Python; tab-width: 4 -*-
# $Id: asynchat.py,v 1.1 1999/01/08 23:04:43 jim Exp $
# $Id: asynchat.py,v 1.2 1999/01/13 03:00:27 amos Exp $
# Author: Sam Rushing <rushing@nightmare.com>
# ======================================================================
......@@ -28,6 +28,7 @@
import socket
import asyncore
import string
import types
# This class adds support for 'chat' style protocols - where one side
# sends a 'command', and the other sends a response (examples would be
......@@ -47,6 +48,11 @@ import string
# method) up to the terminator, and then control will be returned to
# you - by calling your self.found_terminator() method
# Added support for sized input. If you set terminator to an integer
# or a long, instead of a string, then output will be collected and
# sent to 'collect_incoming_data' until the given number of bytes have
# been read. At that point, 'found_terminator' will be called.
class async_chat (asyncore.dispatcher):
"""This is an abstract class. You must derive from this class, and add
the two methods collect_incoming_data() and found_terminator()"""
......@@ -55,6 +61,7 @@ class async_chat (asyncore.dispatcher):
ac_in_buffer_size = 4096
ac_out_buffer_size = 4096
ac_in_buffer_read = 0L
def __init__ (self, conn=None):
self.ac_in_buffer = ''
......@@ -63,7 +70,10 @@ class async_chat (asyncore.dispatcher):
asyncore.dispatcher.__init__ (self, conn)
def set_terminator (self, term):
"Set the input delimiter. Can be a fixed string of any length, or None"
"""Set the input delimiter.
Can be a fixed string of any length, or None,
or an integer or long to indicate a sized input.
"""
if term is None:
self.terminator = ''
else:
......@@ -95,6 +105,28 @@ class async_chat (asyncore.dispatcher):
while self.ac_in_buffer:
terminator = self.get_terminator()
# if terminator is numeric measure, then collect data
# until we have read that much. ac_in_buffer_read tracks
# how much data has been read.
if type(terminator)==types.IntType or \
type(terminator)==types.LongType:
self.ac_in_buffer_read=self.ac_in_buffer_read+len(data)
if self.ac_in_buffer_read < self.terminator:
self.collect_incoming_data(self.ac_in_buffer)
self.ac_in_buffer=''
elif self.ac_in_buffer_read == self.terminator:
self.collect_incoming_data(self.ac_in_buffer)
self.ac_in_buffer=''
self.ac_in_buffer_read=0
self.found_terminator()
else:
border=int(self.terminator-self.ac_in_buffer_read) # border < 0
self.collect_incoming_data(self.ac_in_buffer[:border])
self.ac_in_buffer=self.ac_in_buffer[border:]
self.ac_in_buffer_read=0
self.found_terminator()
break
terminator_len = len(terminator)
# 4 cases:
# 1) end of buffer matches terminator exactly:
......@@ -147,7 +179,8 @@ class async_chat (asyncore.dispatcher):
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
def writable (self):
return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
return len(self.ac_out_buffer) or self.producer_fifo.ready() \
or (not self.connected)
def close_when_done (self):
self.producer_fifo.push (None)
......@@ -156,7 +189,7 @@ class async_chat (asyncore.dispatcher):
# of the first producer in the queue
def refill_buffer (self):
while 1:
if len(self.producer_fifo):
if self.producer_fifo.ready():
p = self.producer_fifo.first()
# a 'None' in the producer fifo is a sentinel,
# telling us to close the channel.
......@@ -190,8 +223,7 @@ class async_chat (asyncore.dispatcher):
# Emergencies only!
self.ac_in_buffer = ''
self.ac_out_buffer == ''
while self.producer_fifo:
self.producer_fifo.pop()
self.producer_fifo.list=[]
# ==================================================
# support for push mode.
......@@ -203,6 +235,7 @@ class async_chat (asyncore.dispatcher):
def writable_push (self):
return self.connected and len(self.ac_out_buffer)
class simple_producer:
def __init__ (self, data, buffer_size=512):
self.data = data
......@@ -217,6 +250,13 @@ class simple_producer:
result = self.data
self.data = ''
return result
def ready(self):
"""Returns true if the producer's 'more' method
is ready to be called.
"""
return 1
class fifo:
def __init__ (self, list=None):
......@@ -235,12 +275,17 @@ class fifo:
self.list.append (data)
def pop (self):
if self.list:
if self.ready():
result = self.list[0]
del self.list[0]
return (1, result)
else:
return (0, None)
def ready(self):
"Is the first producer in the fifo ready?"
if len(self.list):
return self.list[0] is None or self.list[0].ready()
# Given 'haystack', see if any prefix of 'needle' is at its end. This
# assumes an exact match has already been checked. Return the number of
......
......@@ -9,7 +9,7 @@
# interested in using this software in a commercial context, or in
# purchasing support, please contact the author.
RCS_ID = '$Id: http_server.py,v 1.1 1999/01/09 03:17:32 amos Exp $'
RCS_ID = '$Id: http_server.py,v 1.2 1999/01/13 03:00:27 amos Exp $'
# python modules
import os
......@@ -106,7 +106,7 @@ class http_request:
def found_terminator (self):
if self.collector:
self.collector.collect_incoming_data (data)
self.collector.found_terminator()
else:
sys.stderr.write (
'warning: unexpected end-of-record for incoming request\n'
......@@ -420,10 +420,6 @@ class http_channel (asynchat.async_chat):
# no handlers, so complain
r.error (404)
def writable (self):
# this is just the normal async_chat 'writable', here for comparison
return self.ac_out_buffer or len(self.producer_fifo)
def writable_for_proxy (self):
# this version of writable supports the idea of a 'stalled' producer
# [i.e., it's not ready to produce any output yet] This is needed by
......@@ -598,32 +594,12 @@ def crack_request (r):
version = None
return string.lower (REQUEST.group (1)), REQUEST.group(2), version
class fifo:
def __init__ (self, list=None):
if not list:
self.list = []
else:
self.list = list
def __len__ (self):
return len(self.list)
def first (self):
return self.list[0]
class fifo(asynchat.fifo):
def push_front (self, object):
self.list.insert (0, object)
def push (self, data):
self.list.append (data)
def pop (self):
if self.list:
result = self.list[0]
del self.list[0]
return (1, result)
else:
return (0, None)
def compute_timezone_for_log ():
if time.daylight:
......
# -*- Mode: Python; tab-width: 4 -*-
RCS_ID = '$Id: producers.py,v 1.1 1999/01/09 03:17:32 amos Exp $'
RCS_ID = '$Id: producers.py,v 1.2 1999/01/13 03:00:27 amos Exp $'
import string
......@@ -11,6 +11,14 @@ in various ways to get interesting and useful behaviors.
For example, you can feed dynamically-produced output into the compressing
producer, then wrap this with the 'chunked' transfer-encoding producer.
Added 'ready' method to all producers. This allows future producers which
may not be ready until after they are created. Returning false means that
a call to 'more' will not give you useful information, right now, but will
later. A producer which is not ready is saying that it will be ready sometime
in the future. When a producer is exhausted, it should return true for ready.
When 'more' returns '', the producer is done.
"""
class simple_producer:
......@@ -29,6 +37,9 @@ class simple_producer:
self.data = ''
return result
def ready(self):
return 1
class scanning_producer:
"like simple_producer, but more efficient for large strings"
def __init__ (self, data, buffer_size=1024):
......@@ -49,6 +60,9 @@ class scanning_producer:
else:
return ''
def ready(self):
return 1
class lines_producer:
"producer for a list of lines"
......@@ -56,7 +70,7 @@ class lines_producer:
self.lines = lines
def ready (self):
return len(self.lines)
return 1
def more (self):
if self.lines:
......@@ -89,6 +103,9 @@ class file_producer:
else:
return data
def ready(self):
return 1
# A simple output producer. This one does not [yet] have
# the safety feature builtin to the monitor channel: runaway
# output will not be caught.
......@@ -98,6 +115,8 @@ class file_producer:
class output_producer:
"Acts like an output file; suitable for capturing sys.stdout"
# XXX this should be updated for new ready semantics
# including fixing ready, more and adding a close method
def __init__ (self):
self.data = ''
......@@ -134,11 +153,18 @@ class output_producer:
class composite_producer:
"combine a fifo of producers into one"
# I had to add a buffer to this producer to ensure
# that it really was ready when it said it was ready
def __init__ (self, producers):
self.producers = producers
self.buffer = ''
def more (self):
while len(self.producers):
if self.buffer:
b=self.buffer
self.buffer=''
return b
while self.producers.ready():
p = self.producers.first()
d = p.more()
if d:
......@@ -148,6 +174,15 @@ class composite_producer:
else:
return ''
def ready(self):
if self.buffer or len(self.producers)==0:
return 1
if not self.producers.ready():
return None
self.buffer=self.more()
if self.buffer or len(self.producers)==0:
return 1
class globbing_producer:
"""
......@@ -162,7 +197,7 @@ class globbing_producer:
self.buffer_size = buffer_size
def more (self):
while len(self.buffer) < self.buffer_size:
while len(self.buffer) < self.buffer_size and self.producer.ready():
data = self.producer.more()
if data:
self.buffer = self.buffer + data
......@@ -172,6 +207,9 @@ class globbing_producer:
self.buffer = ''
return r
def ready(self):
return self.producer is None or self.producer.ready()
class hooked_producer:
"""
......@@ -197,6 +235,9 @@ class hooked_producer:
else:
return ''
def ready(self):
return self.producer is None or self.producer.ready()
# HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
# correct. In the face of Strange Files, it is conceivable that
# reading a 'file' may produce an amount of data not matching that
......@@ -236,6 +277,9 @@ class chunked_producer:
else:
return ''
def ready(self):
return self.producer is None or self.producer.ready()
# Unfortunately this isn't very useful right now (Aug 97), because
# apparently the browsers don't do on-the-fly decompression. Which
# is sad, because this could _really_ speed things up, especially for
......@@ -279,6 +323,9 @@ class compressed_producer:
else:
return ''
def ready(self):
return self.producer is None or self.producer.ready()
class escaping_producer:
"A producer that escapes a sequence of characters"
......@@ -311,3 +358,7 @@ class escaping_producer:
return buffer
else:
return buffer
def ready(self):
return self.producer is None or self.producer.ready()
# -*- Mode: Python; tab-width: 4 -*-
# $Id: asynchat.py,v 1.1 1999/01/08 23:04:43 jim Exp $
# $Id: asynchat.py,v 1.2 1999/01/13 03:00:27 amos Exp $
# Author: Sam Rushing <rushing@nightmare.com>
# ======================================================================
......@@ -28,6 +28,7 @@
import socket
import asyncore
import string
import types
# This class adds support for 'chat' style protocols - where one side
# sends a 'command', and the other sends a response (examples would be
......@@ -47,6 +48,11 @@ import string
# method) up to the terminator, and then control will be returned to
# you - by calling your self.found_terminator() method
# Added support for sized input. If you set terminator to an integer
# or a long, instead of a string, then output will be collected and
# sent to 'collect_incoming_data' until the given number of bytes have
# been read. At that point, 'found_terminator' will be called.
class async_chat (asyncore.dispatcher):
"""This is an abstract class. You must derive from this class, and add
the two methods collect_incoming_data() and found_terminator()"""
......@@ -55,6 +61,7 @@ class async_chat (asyncore.dispatcher):
ac_in_buffer_size = 4096
ac_out_buffer_size = 4096
ac_in_buffer_read = 0L
def __init__ (self, conn=None):
self.ac_in_buffer = ''
......@@ -63,7 +70,10 @@ class async_chat (asyncore.dispatcher):
asyncore.dispatcher.__init__ (self, conn)
def set_terminator (self, term):
"Set the input delimiter. Can be a fixed string of any length, or None"
"""Set the input delimiter.
Can be a fixed string of any length, or None,
or an integer or long to indicate a sized input.
"""
if term is None:
self.terminator = ''
else:
......@@ -95,6 +105,28 @@ class async_chat (asyncore.dispatcher):
while self.ac_in_buffer:
terminator = self.get_terminator()
# if terminator is numeric measure, then collect data
# until we have read that much. ac_in_buffer_read tracks
# how much data has been read.
if type(terminator)==types.IntType or \
type(terminator)==types.LongType:
self.ac_in_buffer_read=self.ac_in_buffer_read+len(data)
if self.ac_in_buffer_read < self.terminator:
self.collect_incoming_data(self.ac_in_buffer)
self.ac_in_buffer=''
elif self.ac_in_buffer_read == self.terminator:
self.collect_incoming_data(self.ac_in_buffer)
self.ac_in_buffer=''
self.ac_in_buffer_read=0
self.found_terminator()
else:
border=int(self.terminator-self.ac_in_buffer_read) # border < 0
self.collect_incoming_data(self.ac_in_buffer[:border])
self.ac_in_buffer=self.ac_in_buffer[border:]
self.ac_in_buffer_read=0
self.found_terminator()
break
terminator_len = len(terminator)
# 4 cases:
# 1) end of buffer matches terminator exactly:
......@@ -147,7 +179,8 @@ class async_chat (asyncore.dispatcher):
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
def writable (self):
return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
return len(self.ac_out_buffer) or self.producer_fifo.ready() \
or (not self.connected)
def close_when_done (self):
self.producer_fifo.push (None)
......@@ -156,7 +189,7 @@ class async_chat (asyncore.dispatcher):
# of the first producer in the queue
def refill_buffer (self):
while 1:
if len(self.producer_fifo):
if self.producer_fifo.ready():
p = self.producer_fifo.first()
# a 'None' in the producer fifo is a sentinel,
# telling us to close the channel.
......@@ -190,8 +223,7 @@ class async_chat (asyncore.dispatcher):
# Emergencies only!
self.ac_in_buffer = ''
self.ac_out_buffer == ''
while self.producer_fifo:
self.producer_fifo.pop()
self.producer_fifo.list=[]
# ==================================================
# support for push mode.
......@@ -203,6 +235,7 @@ class async_chat (asyncore.dispatcher):
def writable_push (self):
return self.connected and len(self.ac_out_buffer)
class simple_producer:
def __init__ (self, data, buffer_size=512):
self.data = data
......@@ -217,6 +250,13 @@ class simple_producer:
result = self.data
self.data = ''
return result
def ready(self):
"""Returns true if the producer's 'more' method
is ready to be called.
"""
return 1
class fifo:
def __init__ (self, list=None):
......@@ -235,12 +275,17 @@ class fifo:
self.list.append (data)
def pop (self):
if self.list:
if self.ready():
result = self.list[0]
del self.list[0]
return (1, result)
else:
return (0, None)
def ready(self):
"Is the first producer in the fifo ready?"
if len(self.list):
return self.list[0] is None or self.list[0].ready()
# Given 'haystack', see if any prefix of 'needle' is at its end. This
# assumes an exact match has already been checked. Return the number of
......
......@@ -9,7 +9,7 @@
# interested in using this software in a commercial context, or in
# purchasing support, please contact the author.
RCS_ID = '$Id: http_server.py,v 1.1 1999/01/09 03:17:32 amos Exp $'
RCS_ID = '$Id: http_server.py,v 1.2 1999/01/13 03:00:27 amos Exp $'
# python modules
import os
......@@ -106,7 +106,7 @@ class http_request:
def found_terminator (self):
if self.collector:
self.collector.collect_incoming_data (data)
self.collector.found_terminator()
else:
sys.stderr.write (
'warning: unexpected end-of-record for incoming request\n'
......@@ -420,10 +420,6 @@ class http_channel (asynchat.async_chat):
# no handlers, so complain
r.error (404)
def writable (self):
# this is just the normal async_chat 'writable', here for comparison
return self.ac_out_buffer or len(self.producer_fifo)
def writable_for_proxy (self):
# this version of writable supports the idea of a 'stalled' producer
# [i.e., it's not ready to produce any output yet] This is needed by
......@@ -598,32 +594,12 @@ def crack_request (r):
version = None
return string.lower (REQUEST.group (1)), REQUEST.group(2), version
class fifo:
def __init__ (self, list=None):
if not list:
self.list = []
else:
self.list = list
def __len__ (self):
return len(self.list)
def first (self):
return self.list[0]
class fifo(asynchat.fifo):
def push_front (self, object):
self.list.insert (0, object)
def push (self, data):
self.list.append (data)
def pop (self):
if self.list:
result = self.list[0]
del self.list[0]
return (1, result)
else:
return (0, None)
def compute_timezone_for_log ():
if time.daylight:
......
# -*- Mode: Python; tab-width: 4 -*-
RCS_ID = '$Id: producers.py,v 1.1 1999/01/09 03:17:32 amos Exp $'
RCS_ID = '$Id: producers.py,v 1.2 1999/01/13 03:00:27 amos Exp $'
import string
......@@ -11,6 +11,14 @@ in various ways to get interesting and useful behaviors.
For example, you can feed dynamically-produced output into the compressing
producer, then wrap this with the 'chunked' transfer-encoding producer.
Added 'ready' method to all producers. This allows future producers which
may not be ready until after they are created. Returning false means that
a call to 'more' will not give you useful information, right now, but will
later. A producer which is not ready is saying that it will be ready sometime
in the future. When a producer is exhausted, it should return true for ready.
When 'more' returns '', the producer is done.
"""
class simple_producer:
......@@ -29,6 +37,9 @@ class simple_producer:
self.data = ''
return result
def ready(self):
return 1
class scanning_producer:
"like simple_producer, but more efficient for large strings"
def __init__ (self, data, buffer_size=1024):
......@@ -49,6 +60,9 @@ class scanning_producer:
else:
return ''
def ready(self):
return 1
class lines_producer:
"producer for a list of lines"
......@@ -56,7 +70,7 @@ class lines_producer:
self.lines = lines
def ready (self):
return len(self.lines)
return 1
def more (self):
if self.lines:
......@@ -89,6 +103,9 @@ class file_producer:
else:
return data
def ready(self):
return 1
# A simple output producer. This one does not [yet] have
# the safety feature builtin to the monitor channel: runaway
# output will not be caught.
......@@ -98,6 +115,8 @@ class file_producer:
class output_producer:
"Acts like an output file; suitable for capturing sys.stdout"
# XXX this should be updated for new ready semantics
# including fixing ready, more and adding a close method
def __init__ (self):
self.data = ''
......@@ -134,11 +153,18 @@ class output_producer:
class composite_producer:
"combine a fifo of producers into one"
# I had to add a buffer to this producer to ensure
# that it really was ready when it said it was ready
def __init__ (self, producers):
self.producers = producers
self.buffer = ''
def more (self):
while len(self.producers):
if self.buffer:
b=self.buffer
self.buffer=''
return b
while self.producers.ready():
p = self.producers.first()
d = p.more()
if d:
......@@ -148,6 +174,15 @@ class composite_producer:
else:
return ''
def ready(self):
if self.buffer or len(self.producers)==0:
return 1
if not self.producers.ready():
return None
self.buffer=self.more()
if self.buffer or len(self.producers)==0:
return 1
class globbing_producer:
"""
......@@ -162,7 +197,7 @@ class globbing_producer:
self.buffer_size = buffer_size
def more (self):
while len(self.buffer) < self.buffer_size:
while len(self.buffer) < self.buffer_size and self.producer.ready():
data = self.producer.more()
if data:
self.buffer = self.buffer + data
......@@ -172,6 +207,9 @@ class globbing_producer:
self.buffer = ''
return r
def ready(self):
return self.producer is None or self.producer.ready()
class hooked_producer:
"""
......@@ -197,6 +235,9 @@ class hooked_producer:
else:
return ''
def ready(self):
return self.producer is None or self.producer.ready()
# HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
# correct. In the face of Strange Files, it is conceivable that
# reading a 'file' may produce an amount of data not matching that
......@@ -236,6 +277,9 @@ class chunked_producer:
else:
return ''
def ready(self):
return self.producer is None or self.producer.ready()
# Unfortunately this isn't very useful right now (Aug 97), because
# apparently the browsers don't do on-the-fly decompression. Which
# is sad, because this could _really_ speed things up, especially for
......@@ -279,6 +323,9 @@ class compressed_producer:
else:
return ''
def ready(self):
return self.producer is None or self.producer.ready()
class escaping_producer:
"A producer that escapes a sequence of characters"
......@@ -311,3 +358,7 @@ class escaping_producer:
return buffer
else:
return buffer
def ready(self):
return self.producer is None or self.producer.ready()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment