Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] - نسخه متنی

David Ascher, Alex Martelli, Anna Ravenscroft

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید


Recipe 13.15. Implementing the Dynamic IP Protocol


Credit: Nicola Paolucci, Mark Rowe, Andrew
Notspecified


Problem


You use a Dynamic DNS Service which
accepts the GnuDIP protocol (like

Solution


The Twisted framework has plenty of power for all kinds of network
tasks, so we can use it to write a script to implement GnuDIP:

import md5, sys
from twisted.internet import protocol, reactor
from twisted.protocols import basic
from twisted.python import usage
def hashPassword(password, salt):
''' compute and return MD5 hash for given password and `salt'. '''
p1 = md5.md5(password).hexdigest( ) + '.' + salt.strip( )
return md5.md5(p1).hexdigest( )
class DIPProtocol(basic.LineReceiver):
"" Implementation of GnuDIP protocol(TCP) as described at:
http://gnudip2.sourceforge.net/
gnudip-www/latest/gnudip/html/protocoll
""
delimiter = '\n'
def connectionMade(self):
''' at connection, we start in state "expecting salt". '''
basic.LineReceiver.connectionMade(self)
self.expectingSalt = True
def lineReceived(self, line):
''' we received a full line, either "salt" or normal response '''
if self.expectingSalt:
self.saltReceived(line)
self.expectingSalt = False
else:
self.responseReceived(line)
def saltReceived(self, salt):
"" Override this 'abstract method' ""
raise NotImplementedError
def responseReceived(self, response):
"" Override this 'abstract method' ""
raise NotImplementedError
class DIPUpdater(DIPProtocol):
"" A simple class to update an IP, then disconnect. ""
def saltReceived(self, salt):
''' having received `salt', login to the DIP server '''
password = self.factory.getPassword( )
username = self.factory.getUsername( )
domain = self.factory.getDomain( )
msg = '%s:%s:%s:2' % (username, hashPassword(password, salt), domain)
self.sendLine(msg)
def responseReceived(self, response):
''' response received: show errors if any, then disconnect. '''
code = response.split(':', 1)[0]
if code == '0':
pass # OK
elif code == '1':
print 'Authentication failed'
else:
print 'Unexpected response from server:', repr(response)
self.transport.loseConnection( )
class DIPClientFactory(protocol.ClientFactory):
"" Factory used to instantiate DIP protocol instances with
correct username, password and domain.
""
protocol = DIPUpdater
# simply collect data for login and provide accessors to them
def _ _init_ _(self, username, password, domain):
self.u = username
self.p = password
self.d = domain
def getUsername(self):
return self.u
def getPassword(self):
return self.p
def getDomain(self):
return self.d
def clientConnectionLost(self, connector, reason):
''' terminate script when we have disconnected '''
reactor.stop( )
def clientConnectionFailed(self, connector, reason):
''' show error message in case of network problems '''
print 'Connection failed. Reason:', reason
class Options(usage.Options):
''' parse options from commandline or config script '''
optParameters = [['server', 's', 'gnudip2.', 'DIP Server'],
['port', 'p', 3495, 'DIP Server port'],
['username', 'u', 'durdn', 'Username'],
['password', 'w', None, 'Password'],
['domain', 'd', 'durdn.', 'Domain']]
if _ _name_ _ == '_ _main_ _':
# running as main script: first, get all the needed options
config = Options( )
try:
config.parseOptions( )
except usage.UsageError, errortext:
print '%s: %s' % (sys.argv[0], errortext)
print '%s: Try --help for usage details.' % (sys.argv[0])
sys.exit(1)
server = config['server']
port = int(config['port'])
password = config['password']
if not password:
print 'Password not entered. Try --help for usage details.'
sys.exit(1)
# and now, start operations (via Twisted's ``reactor'')
reactor.connectTCP(server, port,
DIPClientFactory(config['username'], password, config['domain']))
reactor.run( )


Discussion


I wanted to use a Dynamic DNS Service called , but I did not like the option of
installing the suggested small client application to update my IP
address on my OpenBSD box. So I resorted to writing the script shown
in this recipe. I put it into my crontab to keep
my domain always up-to-date with my dynamic IP address at home.

This little script is now at version 0.4, and its development history
is quite instructive. I thought that even the first version. 0.1,
which I got working in a few minutes, effectively demonstrated the
power of the Twisted framework in developing network applications, so
I posted that version on the ActiveState cookbook site. Lo and
beholdMark first, then Andrew, showered me with helpful
suggestions, and I repeatedly updated the script in response to their
advice. So it now demonstrates even better, not just the power of
Twisted, but more generally the power of collaborative development in
an open-source or free-software community.

To give just one example: originally, I had overridden
buildProtocol and passed the factory object to the
protocol object explicitly. The factory object, in the Twisted
framework architecture, is where shared state is kept (in this case,
the username, password, and domain), so I had to ensure the protocol
knew about the factoryI thought. It turns out that, exactly
because just about every protocol needs to know about its factory
object, Twisted takes care of it in its own default implementation of
buildProtocol, making the factory object available
as the factory attribute of every protocol object.
So, my code, which duplicated Twisted's built-in
functionality in this regard, was simply ripped out, and the
recipe's code is simpler and better as a result.

Too often, software is presented as a finished and polished artifact,
as if it sprang pristine and perfect like Athena from
Zeus' forehead. This gives entirely the wrong
impression to budding software developers, making them feel
inadequate because their code
isn't born perfect and fully developed. So, as a
counterweight, I thought it important to present one little story
about how software actually grows and develops!

One last detail: it's tempting to place methods
updateIP and removeIP in the
DIPProtocol class, to ease the writing of subclasses
such as DIPUpdater. However, in my view, that would
be an over-generalization, overkill for such a simple, lightweight
recipe as Python and Twisted make this one. In practice we
won't need all that many dynamic IP protocol
subclasses, and if it turns out that we're wrong and
we do, in fact, need them, hey, refactoring is clearly
not a hard task with such a fluid, dynamic
language and powerful frameworks to draw on. So, respect the prime
directive: "do the simplest thing that can possibly
work."

In a sense, the code in this recipe could be said to violate the
prime directive, because it uses an elegant object-oriented
architecture with an abstract base class, a concrete subclass to
specialize it, and, in the factory class, accessor methods rather
than simple attribute access for the login data (i.e., user,
password, domain). All of these niceties are lifesavers in big
programs, but they admittedly could be foregone
for a program of only 120 lines (which would shrink a little further
if it didn't use all these niceties). However,
adopting a uniform style of program architecture, even for small
programs, eases the refactoring task in those not-so-rare cases where
a small program grows into a big one. So, I have deliberately
developed the habit of always coding in such an
"elegant OO way", and once the
habit is acquired, I find that it enhances, rather than reduces, my
productivity.


See Also


The GnuDIP protocol is specified at http://gnudip2.sourceforge.net/gnudip-www/latest/gnudip/html/protocoll;
Twisted is at http://www.twistedmatrix.com/.

/ 394