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 -*- # -*- 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> # Author: Sam Rushing <rushing@nightmare.com>
# ====================================================================== # ======================================================================
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
import socket import socket
import asyncore import asyncore
import string import string
import types
# This class adds support for 'chat' style protocols - where one side # This class adds support for 'chat' style protocols - where one side
# sends a 'command', and the other sends a response (examples would be # sends a 'command', and the other sends a response (examples would be
...@@ -47,6 +48,11 @@ import string ...@@ -47,6 +48,11 @@ import string
# method) up to the terminator, and then control will be returned to # method) up to the terminator, and then control will be returned to
# you - by calling your self.found_terminator() method # 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): class async_chat (asyncore.dispatcher):
"""This is an abstract class. You must derive from this class, and add """This is an abstract class. You must derive from this class, and add
the two methods collect_incoming_data() and found_terminator()""" the two methods collect_incoming_data() and found_terminator()"""
...@@ -55,6 +61,7 @@ class async_chat (asyncore.dispatcher): ...@@ -55,6 +61,7 @@ class async_chat (asyncore.dispatcher):
ac_in_buffer_size = 4096 ac_in_buffer_size = 4096
ac_out_buffer_size = 4096 ac_out_buffer_size = 4096
ac_in_buffer_read = 0L
def __init__ (self, conn=None): def __init__ (self, conn=None):
self.ac_in_buffer = '' self.ac_in_buffer = ''
...@@ -63,7 +70,10 @@ class async_chat (asyncore.dispatcher): ...@@ -63,7 +70,10 @@ class async_chat (asyncore.dispatcher):
asyncore.dispatcher.__init__ (self, conn) asyncore.dispatcher.__init__ (self, conn)
def set_terminator (self, term): 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: if term is None:
self.terminator = '' self.terminator = ''
else: else:
...@@ -95,6 +105,28 @@ class async_chat (asyncore.dispatcher): ...@@ -95,6 +105,28 @@ class async_chat (asyncore.dispatcher):
while self.ac_in_buffer: while self.ac_in_buffer:
terminator = self.get_terminator() 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) terminator_len = len(terminator)
# 4 cases: # 4 cases:
# 1) end of buffer matches terminator exactly: # 1) end of buffer matches terminator exactly:
...@@ -147,7 +179,8 @@ class async_chat (asyncore.dispatcher): ...@@ -147,7 +179,8 @@ class async_chat (asyncore.dispatcher):
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size) return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
def writable (self): 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): def close_when_done (self):
self.producer_fifo.push (None) self.producer_fifo.push (None)
...@@ -156,7 +189,7 @@ class async_chat (asyncore.dispatcher): ...@@ -156,7 +189,7 @@ class async_chat (asyncore.dispatcher):
# of the first producer in the queue # of the first producer in the queue
def refill_buffer (self): def refill_buffer (self):
while 1: while 1:
if len(self.producer_fifo): if self.producer_fifo.ready():
p = self.producer_fifo.first() p = self.producer_fifo.first()
# a 'None' in the producer fifo is a sentinel, # a 'None' in the producer fifo is a sentinel,
# telling us to close the channel. # telling us to close the channel.
...@@ -190,8 +223,7 @@ class async_chat (asyncore.dispatcher): ...@@ -190,8 +223,7 @@ class async_chat (asyncore.dispatcher):
# Emergencies only! # Emergencies only!
self.ac_in_buffer = '' self.ac_in_buffer = ''
self.ac_out_buffer == '' self.ac_out_buffer == ''
while self.producer_fifo: self.producer_fifo.list=[]
self.producer_fifo.pop()
# ================================================== # ==================================================
# support for push mode. # support for push mode.
...@@ -203,6 +235,7 @@ class async_chat (asyncore.dispatcher): ...@@ -203,6 +235,7 @@ class async_chat (asyncore.dispatcher):
def writable_push (self): def writable_push (self):
return self.connected and len(self.ac_out_buffer) return self.connected and len(self.ac_out_buffer)
class simple_producer: class simple_producer:
def __init__ (self, data, buffer_size=512): def __init__ (self, data, buffer_size=512):
self.data = data self.data = data
...@@ -217,6 +250,13 @@ class simple_producer: ...@@ -217,6 +250,13 @@ class simple_producer:
result = self.data result = self.data
self.data = '' self.data = ''
return result return result
def ready(self):
"""Returns true if the producer's 'more' method
is ready to be called.
"""
return 1
class fifo: class fifo:
def __init__ (self, list=None): def __init__ (self, list=None):
...@@ -235,12 +275,17 @@ class fifo: ...@@ -235,12 +275,17 @@ class fifo:
self.list.append (data) self.list.append (data)
def pop (self): def pop (self):
if self.list: if self.ready():
result = self.list[0] result = self.list[0]
del self.list[0] del self.list[0]
return (1, result) return (1, result)
else: else:
return (0, None) 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 # 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 # assumes an exact match has already been checked. Return the number of
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
# interested in using this software in a commercial context, or in # interested in using this software in a commercial context, or in
# purchasing support, please contact the author. # 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 # python modules
import os import os
...@@ -106,7 +106,7 @@ class http_request: ...@@ -106,7 +106,7 @@ class http_request:
def found_terminator (self): def found_terminator (self):
if self.collector: if self.collector:
self.collector.collect_incoming_data (data) self.collector.found_terminator()
else: else:
sys.stderr.write ( sys.stderr.write (
'warning: unexpected end-of-record for incoming request\n' 'warning: unexpected end-of-record for incoming request\n'
...@@ -420,10 +420,6 @@ class http_channel (asynchat.async_chat): ...@@ -420,10 +420,6 @@ class http_channel (asynchat.async_chat):
# no handlers, so complain # no handlers, so complain
r.error (404) 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): def writable_for_proxy (self):
# this version of writable supports the idea of a 'stalled' producer # 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 # [i.e., it's not ready to produce any output yet] This is needed by
...@@ -598,32 +594,12 @@ def crack_request (r): ...@@ -598,32 +594,12 @@ def crack_request (r):
version = None version = None
return string.lower (REQUEST.group (1)), REQUEST.group(2), version 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): class fifo(asynchat.fifo):
return self.list[0]
def push_front (self, object): def push_front (self, object):
self.list.insert (0, 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 (): def compute_timezone_for_log ():
if time.daylight: if time.daylight:
......
# -*- Mode: Python; tab-width: 4 -*- # -*- 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 import string
...@@ -11,6 +11,14 @@ in various ways to get interesting and useful behaviors. ...@@ -11,6 +11,14 @@ in various ways to get interesting and useful behaviors.
For example, you can feed dynamically-produced output into the compressing For example, you can feed dynamically-produced output into the compressing
producer, then wrap this with the 'chunked' transfer-encoding producer. 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: class simple_producer:
...@@ -29,6 +37,9 @@ class simple_producer: ...@@ -29,6 +37,9 @@ class simple_producer:
self.data = '' self.data = ''
return result return result
def ready(self):
return 1
class scanning_producer: class scanning_producer:
"like simple_producer, but more efficient for large strings" "like simple_producer, but more efficient for large strings"
def __init__ (self, data, buffer_size=1024): def __init__ (self, data, buffer_size=1024):
...@@ -49,6 +60,9 @@ class scanning_producer: ...@@ -49,6 +60,9 @@ class scanning_producer:
else: else:
return '' return ''
def ready(self):
return 1
class lines_producer: class lines_producer:
"producer for a list of lines" "producer for a list of lines"
...@@ -56,7 +70,7 @@ class lines_producer: ...@@ -56,7 +70,7 @@ class lines_producer:
self.lines = lines self.lines = lines
def ready (self): def ready (self):
return len(self.lines) return 1
def more (self): def more (self):
if self.lines: if self.lines:
...@@ -89,6 +103,9 @@ class file_producer: ...@@ -89,6 +103,9 @@ class file_producer:
else: else:
return data return data
def ready(self):
return 1
# A simple output producer. This one does not [yet] have # A simple output producer. This one does not [yet] have
# the safety feature builtin to the monitor channel: runaway # the safety feature builtin to the monitor channel: runaway
# output will not be caught. # output will not be caught.
...@@ -98,6 +115,8 @@ class file_producer: ...@@ -98,6 +115,8 @@ class file_producer:
class output_producer: class output_producer:
"Acts like an output file; suitable for capturing sys.stdout" "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): def __init__ (self):
self.data = '' self.data = ''
...@@ -134,11 +153,18 @@ class output_producer: ...@@ -134,11 +153,18 @@ class output_producer:
class composite_producer: class composite_producer:
"combine a fifo of producers into one" "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): def __init__ (self, producers):
self.producers = producers self.producers = producers
self.buffer = ''
def more (self): 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() p = self.producers.first()
d = p.more() d = p.more()
if d: if d:
...@@ -148,6 +174,15 @@ class composite_producer: ...@@ -148,6 +174,15 @@ class composite_producer:
else: else:
return '' 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: class globbing_producer:
""" """
...@@ -162,7 +197,7 @@ class globbing_producer: ...@@ -162,7 +197,7 @@ class globbing_producer:
self.buffer_size = buffer_size self.buffer_size = buffer_size
def more (self): 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() data = self.producer.more()
if data: if data:
self.buffer = self.buffer + data self.buffer = self.buffer + data
...@@ -172,6 +207,9 @@ class globbing_producer: ...@@ -172,6 +207,9 @@ class globbing_producer:
self.buffer = '' self.buffer = ''
return r return r
def ready(self):
return self.producer is None or self.producer.ready()
class hooked_producer: class hooked_producer:
""" """
...@@ -197,6 +235,9 @@ class hooked_producer: ...@@ -197,6 +235,9 @@ class hooked_producer:
else: else:
return '' 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 # HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
# correct. In the face of Strange Files, it is conceivable that # correct. In the face of Strange Files, it is conceivable that
# reading a 'file' may produce an amount of data not matching that # reading a 'file' may produce an amount of data not matching that
...@@ -236,6 +277,9 @@ class chunked_producer: ...@@ -236,6 +277,9 @@ class chunked_producer:
else: else:
return '' return ''
def ready(self):
return self.producer is None or self.producer.ready()
# Unfortunately this isn't very useful right now (Aug 97), because # Unfortunately this isn't very useful right now (Aug 97), because
# apparently the browsers don't do on-the-fly decompression. Which # apparently the browsers don't do on-the-fly decompression. Which
# is sad, because this could _really_ speed things up, especially for # is sad, because this could _really_ speed things up, especially for
...@@ -279,6 +323,9 @@ class compressed_producer: ...@@ -279,6 +323,9 @@ class compressed_producer:
else: else:
return '' return ''
def ready(self):
return self.producer is None or self.producer.ready()
class escaping_producer: class escaping_producer:
"A producer that escapes a sequence of characters" "A producer that escapes a sequence of characters"
...@@ -311,3 +358,7 @@ class escaping_producer: ...@@ -311,3 +358,7 @@ class escaping_producer:
return buffer return buffer
else: else:
return buffer return buffer
def ready(self):
return self.producer is None or self.producer.ready()
# -*- Mode: Python; tab-width: 4 -*- # -*- 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> # Author: Sam Rushing <rushing@nightmare.com>
# ====================================================================== # ======================================================================
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
import socket import socket
import asyncore import asyncore
import string import string
import types
# This class adds support for 'chat' style protocols - where one side # This class adds support for 'chat' style protocols - where one side
# sends a 'command', and the other sends a response (examples would be # sends a 'command', and the other sends a response (examples would be
...@@ -47,6 +48,11 @@ import string ...@@ -47,6 +48,11 @@ import string
# method) up to the terminator, and then control will be returned to # method) up to the terminator, and then control will be returned to
# you - by calling your self.found_terminator() method # 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): class async_chat (asyncore.dispatcher):
"""This is an abstract class. You must derive from this class, and add """This is an abstract class. You must derive from this class, and add
the two methods collect_incoming_data() and found_terminator()""" the two methods collect_incoming_data() and found_terminator()"""
...@@ -55,6 +61,7 @@ class async_chat (asyncore.dispatcher): ...@@ -55,6 +61,7 @@ class async_chat (asyncore.dispatcher):
ac_in_buffer_size = 4096 ac_in_buffer_size = 4096
ac_out_buffer_size = 4096 ac_out_buffer_size = 4096
ac_in_buffer_read = 0L
def __init__ (self, conn=None): def __init__ (self, conn=None):
self.ac_in_buffer = '' self.ac_in_buffer = ''
...@@ -63,7 +70,10 @@ class async_chat (asyncore.dispatcher): ...@@ -63,7 +70,10 @@ class async_chat (asyncore.dispatcher):
asyncore.dispatcher.__init__ (self, conn) asyncore.dispatcher.__init__ (self, conn)
def set_terminator (self, term): 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: if term is None:
self.terminator = '' self.terminator = ''
else: else:
...@@ -95,6 +105,28 @@ class async_chat (asyncore.dispatcher): ...@@ -95,6 +105,28 @@ class async_chat (asyncore.dispatcher):
while self.ac_in_buffer: while self.ac_in_buffer:
terminator = self.get_terminator() 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) terminator_len = len(terminator)
# 4 cases: # 4 cases:
# 1) end of buffer matches terminator exactly: # 1) end of buffer matches terminator exactly:
...@@ -147,7 +179,8 @@ class async_chat (asyncore.dispatcher): ...@@ -147,7 +179,8 @@ class async_chat (asyncore.dispatcher):
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size) return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
def writable (self): 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): def close_when_done (self):
self.producer_fifo.push (None) self.producer_fifo.push (None)
...@@ -156,7 +189,7 @@ class async_chat (asyncore.dispatcher): ...@@ -156,7 +189,7 @@ class async_chat (asyncore.dispatcher):
# of the first producer in the queue # of the first producer in the queue
def refill_buffer (self): def refill_buffer (self):
while 1: while 1:
if len(self.producer_fifo): if self.producer_fifo.ready():
p = self.producer_fifo.first() p = self.producer_fifo.first()
# a 'None' in the producer fifo is a sentinel, # a 'None' in the producer fifo is a sentinel,
# telling us to close the channel. # telling us to close the channel.
...@@ -190,8 +223,7 @@ class async_chat (asyncore.dispatcher): ...@@ -190,8 +223,7 @@ class async_chat (asyncore.dispatcher):
# Emergencies only! # Emergencies only!
self.ac_in_buffer = '' self.ac_in_buffer = ''
self.ac_out_buffer == '' self.ac_out_buffer == ''
while self.producer_fifo: self.producer_fifo.list=[]
self.producer_fifo.pop()
# ================================================== # ==================================================
# support for push mode. # support for push mode.
...@@ -203,6 +235,7 @@ class async_chat (asyncore.dispatcher): ...@@ -203,6 +235,7 @@ class async_chat (asyncore.dispatcher):
def writable_push (self): def writable_push (self):
return self.connected and len(self.ac_out_buffer) return self.connected and len(self.ac_out_buffer)
class simple_producer: class simple_producer:
def __init__ (self, data, buffer_size=512): def __init__ (self, data, buffer_size=512):
self.data = data self.data = data
...@@ -217,6 +250,13 @@ class simple_producer: ...@@ -217,6 +250,13 @@ class simple_producer:
result = self.data result = self.data
self.data = '' self.data = ''
return result return result
def ready(self):
"""Returns true if the producer's 'more' method
is ready to be called.
"""
return 1
class fifo: class fifo:
def __init__ (self, list=None): def __init__ (self, list=None):
...@@ -235,12 +275,17 @@ class fifo: ...@@ -235,12 +275,17 @@ class fifo:
self.list.append (data) self.list.append (data)
def pop (self): def pop (self):
if self.list: if self.ready():
result = self.list[0] result = self.list[0]
del self.list[0] del self.list[0]
return (1, result) return (1, result)
else: else:
return (0, None) 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 # 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 # assumes an exact match has already been checked. Return the number of
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
# interested in using this software in a commercial context, or in # interested in using this software in a commercial context, or in
# purchasing support, please contact the author. # 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 # python modules
import os import os
...@@ -106,7 +106,7 @@ class http_request: ...@@ -106,7 +106,7 @@ class http_request:
def found_terminator (self): def found_terminator (self):
if self.collector: if self.collector:
self.collector.collect_incoming_data (data) self.collector.found_terminator()
else: else:
sys.stderr.write ( sys.stderr.write (
'warning: unexpected end-of-record for incoming request\n' 'warning: unexpected end-of-record for incoming request\n'
...@@ -420,10 +420,6 @@ class http_channel (asynchat.async_chat): ...@@ -420,10 +420,6 @@ class http_channel (asynchat.async_chat):
# no handlers, so complain # no handlers, so complain
r.error (404) 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): def writable_for_proxy (self):
# this version of writable supports the idea of a 'stalled' producer # 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 # [i.e., it's not ready to produce any output yet] This is needed by
...@@ -598,32 +594,12 @@ def crack_request (r): ...@@ -598,32 +594,12 @@ def crack_request (r):
version = None version = None
return string.lower (REQUEST.group (1)), REQUEST.group(2), version 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): class fifo(asynchat.fifo):
return self.list[0]
def push_front (self, object): def push_front (self, object):
self.list.insert (0, 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 (): def compute_timezone_for_log ():
if time.daylight: if time.daylight:
......
# -*- Mode: Python; tab-width: 4 -*- # -*- 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 import string
...@@ -11,6 +11,14 @@ in various ways to get interesting and useful behaviors. ...@@ -11,6 +11,14 @@ in various ways to get interesting and useful behaviors.
For example, you can feed dynamically-produced output into the compressing For example, you can feed dynamically-produced output into the compressing
producer, then wrap this with the 'chunked' transfer-encoding producer. 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: class simple_producer:
...@@ -29,6 +37,9 @@ class simple_producer: ...@@ -29,6 +37,9 @@ class simple_producer:
self.data = '' self.data = ''
return result return result
def ready(self):
return 1
class scanning_producer: class scanning_producer:
"like simple_producer, but more efficient for large strings" "like simple_producer, but more efficient for large strings"
def __init__ (self, data, buffer_size=1024): def __init__ (self, data, buffer_size=1024):
...@@ -49,6 +60,9 @@ class scanning_producer: ...@@ -49,6 +60,9 @@ class scanning_producer:
else: else:
return '' return ''
def ready(self):
return 1
class lines_producer: class lines_producer:
"producer for a list of lines" "producer for a list of lines"
...@@ -56,7 +70,7 @@ class lines_producer: ...@@ -56,7 +70,7 @@ class lines_producer:
self.lines = lines self.lines = lines
def ready (self): def ready (self):
return len(self.lines) return 1
def more (self): def more (self):
if self.lines: if self.lines:
...@@ -89,6 +103,9 @@ class file_producer: ...@@ -89,6 +103,9 @@ class file_producer:
else: else:
return data return data
def ready(self):
return 1
# A simple output producer. This one does not [yet] have # A simple output producer. This one does not [yet] have
# the safety feature builtin to the monitor channel: runaway # the safety feature builtin to the monitor channel: runaway
# output will not be caught. # output will not be caught.
...@@ -98,6 +115,8 @@ class file_producer: ...@@ -98,6 +115,8 @@ class file_producer:
class output_producer: class output_producer:
"Acts like an output file; suitable for capturing sys.stdout" "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): def __init__ (self):
self.data = '' self.data = ''
...@@ -134,11 +153,18 @@ class output_producer: ...@@ -134,11 +153,18 @@ class output_producer:
class composite_producer: class composite_producer:
"combine a fifo of producers into one" "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): def __init__ (self, producers):
self.producers = producers self.producers = producers
self.buffer = ''
def more (self): 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() p = self.producers.first()
d = p.more() d = p.more()
if d: if d:
...@@ -148,6 +174,15 @@ class composite_producer: ...@@ -148,6 +174,15 @@ class composite_producer:
else: else:
return '' 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: class globbing_producer:
""" """
...@@ -162,7 +197,7 @@ class globbing_producer: ...@@ -162,7 +197,7 @@ class globbing_producer:
self.buffer_size = buffer_size self.buffer_size = buffer_size
def more (self): 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() data = self.producer.more()
if data: if data:
self.buffer = self.buffer + data self.buffer = self.buffer + data
...@@ -172,6 +207,9 @@ class globbing_producer: ...@@ -172,6 +207,9 @@ class globbing_producer:
self.buffer = '' self.buffer = ''
return r return r
def ready(self):
return self.producer is None or self.producer.ready()
class hooked_producer: class hooked_producer:
""" """
...@@ -197,6 +235,9 @@ class hooked_producer: ...@@ -197,6 +235,9 @@ class hooked_producer:
else: else:
return '' 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 # HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
# correct. In the face of Strange Files, it is conceivable that # correct. In the face of Strange Files, it is conceivable that
# reading a 'file' may produce an amount of data not matching that # reading a 'file' may produce an amount of data not matching that
...@@ -236,6 +277,9 @@ class chunked_producer: ...@@ -236,6 +277,9 @@ class chunked_producer:
else: else:
return '' return ''
def ready(self):
return self.producer is None or self.producer.ready()
# Unfortunately this isn't very useful right now (Aug 97), because # Unfortunately this isn't very useful right now (Aug 97), because
# apparently the browsers don't do on-the-fly decompression. Which # apparently the browsers don't do on-the-fly decompression. Which
# is sad, because this could _really_ speed things up, especially for # is sad, because this could _really_ speed things up, especially for
...@@ -279,6 +323,9 @@ class compressed_producer: ...@@ -279,6 +323,9 @@ class compressed_producer:
else: else:
return '' return ''
def ready(self):
return self.producer is None or self.producer.ready()
class escaping_producer: class escaping_producer:
"A producer that escapes a sequence of characters" "A producer that escapes a sequence of characters"
...@@ -311,3 +358,7 @@ class escaping_producer: ...@@ -311,3 +358,7 @@ class escaping_producer:
return buffer return buffer
else: else:
return buffer 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