Topics
¨ Sockets in Python
¨ The FTP module
¨ Email processing
¨
¨ Grail applets (client)
¨ Jython: Python for Java systems
¨ Active Scripting and COM
¨ Other tools: urllib, HTMLgen, XML, Zope
¨ At the heart of Internet communications
¨ A standard C extension type: BSD socket wrapper
¨ C functions become socket object methods
Socket module details
¨ Supports all types: TCP/IP, UDP datagram, Unix domain
¨ Also supports timeouts, non-blocking, SSL
¨ Encrypted sockets supported if SSL library enabled
¨ Urllib modules do https:// if secure sockets enabled
¨ Email also uses secure sockets if enabled
¨ Python also supports “select” multiplex processing
Basic client/server example
· Server: echoes all data that it receives back
· Client: sends data to the server
· Client calls: “socket”, “connect”
· Server calls: “socket”, “bind”, “listen”, “accept”
file: echoserver.py
#########################################################
# Server side: open a socket on a port, listen for
# a message from a client, and send an echo reply;
# this is a simple one-shot listen/reply per client,
# but it goes into an infinite loop to listen for
# more clients as long as this server script runs;
#########################################################
from socket import * # get socket constructor and constants
myHost = '' # server machine, '' means local host
myPort = 50007 # listen on a non-reserved port number
sockobj = socket(AF_
sockobj.bind((myHost, myPort)) # bind it to server port number
sockobj.listen(5) # listen, allow 5 pending connects
while 1: # listen until process killed
connection, address = sockobj.accept() # wait for next client connect
print 'Server connected by', address # connection is a new socket
while 1:
data = connection.recv(1024) # read next line on client socket
if not data: break # send a reply line to the client
connection.send('Echo=>' + data) # until eof when socket closed
connection.close()
file: echoclient.py
#############################################################
# Client side: use sockets to send data to the server, and
# print server's reply to each message line; 'localhost'
# means that the server is running on the same machine as
# the client, which lets us test client and server on one
# machine; to test over the net, run server on a remote
# machine, set serverHost to machine's domain name or IP addr;
#############################################################
import sys
from socket import * # portable socket interface plus constants
serverHost = 'localhost' # server name, or: 'starship.python.net'
serverPort = 50007 # non-reserved port used by the server
message = ['Hello network world'] # text to send to server
sockobj = socket(AF_
sockobj.connect((serverHost, serverPort)) # connect to serve and port
for line in message:
sockobj.send(line) # send line to server over socket
data = sockobj.recv(1024) # receive from server: up to 1k
print 'Client received:', `data`
sockobj.close() # close to send eof to server
Forking socket servers
import os, time, sys, signal, signal
from socket import * # get socket constructor and constants
myHost = '' # server machine, '' means local host
myPort = 50007 # listen on a non-reserved port number
sockobj = socket(AF_
sockobj.bind((myHost, myPort)) # bind to server port number
sockobj.listen(5) # up to 5 pending connects
signal.signal(signal.SIGCHLD, signal.SIG_IGN) # avoid child zombie processes
def now(): # time on server machine
return time.ctime(time.time())
def handleClient(connection): # child process replies, exits
time.sleep(5) # simulate a blocking activity
while 1: # read, write a client socket
data = connection.recv(1024)
if not data: break
connection.send('Echo=>%s at %s' % (data, now()))
connection.close()
os._exit(0)
def dispatcher(): # listen until process killed
while 1: # wait for next connection,
connection, address = sockobj.accept() # pass to process for service
print 'Server connected by', address,
print 'at', now()
childPid = os.fork() # copy this process
if childPid == 0: # if in child process: handle
handleClient(connection) # else: go accept next connect
dispatcher()
Threading socket servers
import thread, time
from socket import * # get socket constructor and constants
myHost = '' # server machine, '' means local host
myPort = 50007 # listen on a non-reserved port number
sockobj = socket(AF_
sockobj.bind((myHost, myPort)) # bind to server port number
sockobj.listen(5) # upto 5 pending connects
def now():
return time.ctime(time.time()) # current time on the server
def handleClient(connection): # in spawned thread: reply
time.sleep(5) # simulate a blocking activity
while 1: # read, write a client socket
data = connection.recv(1024)
if not data: break
connection.send('Echo=>%s at %s' % (data, now()))
connection.close()
def dispatcher(): # listen until process killd
while 1: # wait for next connection,
connection, address = sockobj.accept() # pass to thread for service
print 'Server connected by', address,
print 'at', now()
thread.start_new(handleClient, (connection,))
dispatcher()
Select socket servers
# event loop: listen and multiplex until server process killed
from select import select
print 'select-server loop starting'
while 1:
readables, writeables, exceptions = select(readsocks, writesocks, [])
for sockobj in readables:
if sockobj in mainsocks: # for ready input sockets
# port socket: accept new client
newsock, address = sockobj.accept() # accept should not block
print 'Connect:', address, id(newsock) # newsock is a new socket
readsocks.append(newsock) # add to select list, wait
else:
# client socket: read next line
data = sockobj.recv(1024) # recv should not block
print '\tgot', data, 'on', id(sockobj)
if not data: # if closed by the clients
sockobj.close() # close here and remv from
readsocks.remove(sockobj)
#
else:
# this may block: should really select for writes too
sockobj.send('Echo=>%s at %s' % (data, now()))
Standard library server types
Threading/Forking
Asyncore
select-based server classes
See also: HTTP/
See also: Twisted
3rd party system
import SocketServer, time
# get socket server, handler objects
myHost = ''
# server machine, '' means local host
myPort = 50007
# listen on a non-reserved port number
def now():
return
time.ctime(time.time())
class MyClientHandler(SocketServer.BaseRequestHandler):
def handle(self): # on each client connect
print
self.client_address, now() # show
this client's addr
time.sleep(5) # simulate blocking
activity
while 1: # self.request
is client socket
data =
self.request.recv(1024) # read,
write a client socket
if not data: break
self.request.send('Echo=>%s at %s' % (data, now()))
self.request.close()
# make a threaded server, listen/handle clients forever
myaddr = (myHost, myPort)
server = SocketServer.ThreadingTCPServer(myaddr, MyClientHandler)
server.serve_forever()
¨ ftplib library module uses sockets to transfer files
¨ Supports both binary and text retrieve/store operations
¨ Handles all handshaking with the remote site
¨ See Python library manual for more details
Example: fetch file
file: getone.py
import
os, sys
from
getpass import getpass
nonpassive = False
filename = 'lawnlake2-jan-03.jpg' # file to download
dirname = '.' # remote directory
sitename = 'ftp.rmi.net' # ftp site to contact
userinfo = ('lutz', getpass('Pswd?')) # use () for anonymous
if
len(sys.argv) > 1: filename = sys.argv[1]
# file on command-line?
print
'Connecting...'
from
ftplib import FTP #
socket-based ftp tools
localfile = open(filename, 'wb') # local file to store to
connection
= FTP(sitename) #
connect to ftp site
connection.login(*userinfo) # default anonymous login
connection.cwd(dirname) # xfer 1k at a time
if
nonpassive:
# if server requires
connection.set_pasv(False)
#
thread me in a GUI
print
'Downloading...'
connection.retrbinary('RETR
' + filename, localfile.write, 1024)
connection.quit()
localfile.close()
Example: simple FTP site mirror (PP book)
#!/bin/env python
########################################################################
# use ftp to copy all files from a remote
site/directory to a local dir;
# e.g., run me periodically from a unix
cron job to mirror an ftp site;
# script assumes this is a flat directory--see
the mirror program in
# Python's Tools directory for a version
that handles subdirectories;
########################################################################
import os, sys, ftplib
from getpass import getpass
remotesite = 'home.rmi.net'
remotedir
= 'public_html'
remoteuser = 'lutz'
remotepass = getpass('Please enter
password for %s: ' % remotesite)
localdir
= (len(sys.argv) > 1 and sys.argv[1]) or '.'
cleanall
= raw_input('Clean local directory first? ')[:1] in ['y', 'Y']
print 'connecting...'
connection = ftplib.FTP(remotesite) # connect to ftp site
connection.login(remoteuser,
remotepass) # login as
user/password
connection.cwd(remotedir) # cd to directory to
copy
if cleanall:
for localname in os.listdir(localdir): # try to delete all locals
try: # to
remove old files
print 'deleting local', localname
os.remove(os.path.join(localdir, localname))
except:
print 'cannot delete local', localname
count = 0 #
download remote files
remotefiles = connection.nlst() # nlst() gives files list
# dir() gives all details
for remotename in remotefiles:
localname = os.path.join(localdir, remotename)
print 'copying', remotename, 'to', localname
if remotename[-4:] == 'html' or remotename[-3:] == 'txt':
# use ascii mode xfer
localfile = open(localname, 'w')
callback = lambda line,
file=localfile: file.write(line + '\n')
connection.retrlines('RETR ' + remotename, callback)
else:
# use binary mode xfer
localfile = open(localname, 'wb')
connection.retrbinary('RETR ' + remotename, localfile.write)
localfile.close()
count = count+1
connection.quit()
print 'Done:', count, 'files downloaded.'
Example: upload site by FTP (PP book)
#!/bin/env python
########################################################################
# use ftp to upload all files from a local
dir to a remote site/directory;
# e.g., run me to copy an ftp site's files
from your machine to your
# especially handy if you only have ftp
access to your website, not a
# telnet/shell account access (else you
could tar up all files and
# transfer in a single step to the remote
machine and untar there);
# to upload subdirectories too, use
os.path.isdir(path), FTP().mkd(path),
# and recursion--see uploadall.py for a
version that supports subdirs.
##########################################################################
import os, sys, ftplib, getpass
remotesite = 'starship.python.net' # upload to starship site
remotedir
= 'public_html/home'
# from win laptop or other
remoteuser = 'lutz'
remotepass = getpass.getpass('Please enter
password for %s: ' % remotesite)
localdir
= (len(sys.argv) > 1 and sys.argv[1]) or '.'
cleanall
= raw_input('Clean remote directory first? ')[:1] in ['y', 'Y']
print 'connecting...'
connection = ftplib.FTP(remotesite) # connect to ftp site
connection.login(remoteuser,
remotepass) # login as
user/password
connection.cwd(remotedir) # cd to directory to copy
if cleanall:
for remotename in connection.nlst(): # try to delete remotes
try: # to
remove old files
print 'deleting remote', remotename
connection.delete(remotename)
except:
print 'cannot delete remote', remotename
count = 0
localfiles = os.listdir(localdir) # upload all local files
#
listdir() strips dirpath
for localname in localfiles:
localpath = os.path.join(localdir, localname)
print 'uploading', localpath, 'to', localname
if localname[-4:] == 'html' or localname[-3:] == 'txt':
# use ascii mode xfer
localfile = open(localpath, 'r')
connection.storlines('
else:
# use binary mode xfer
localfile = open(localpath, 'rb')
connection.storbinary('
localfile.close()
count = count+1
connection.quit()
print 'Done:', count, 'files uploaded.'
¨
¨ Newer email.* module package: parse+compose, attachments, en/decoding …
Reading a
#!/usr/local/bin/python
######################################################
# use
the Python
# your
pop email account messages;
######################################################
import
poplib, getpass, sys, mailconfig
mailserver
= mailconfig.popservername # ex:
'pop.rmi.net'
mailuser = mailconfig.popusername # ex: 'lutz'
mailpasswd
= getpass.getpass('Password for %s?' % mailserver)
print 'Connecting...'
server
= poplib.
server.user(mailuser) # connect,login to server
server.pass_(mailpasswd) # pass is a reserved word
try:
print server.getwelcome() # print greeting message
msgCount, msgBytes = server.stat()
print 'There are', msgCount, 'mail messages
in', msgBytes, 'bytes'
print '-'*80
raw_input('[Press Enter key]')
for i in range(msgCount):
hdr, message, octets = server.retr(i+1)
for line in message: print line # retrieve, print all
print '-'*80 # mbox locked till
quit
if i < msgCount - 1:
raw_input('[Press Enter key]')
finally:
server.quit() # make sure to unlock
print
'Bye.'
Sending
email via a SMTP server
#!/usr/local/bin/python
########################################################
# use
the Python SMTP mail interface module to send mail
########################################################
import
smtplib, string, sys, time, mailconfig
mailserver
= mailconfig.smtpservername # ex:
starship.python.net
From =
string.strip(raw_input('From? ')) #
ex: lutz@rmi.net
To = string.strip(raw_input('To? '))
# ex: guido@python.org
To = string.split(To, ',') # allow a list of tos
Subj =
string.strip(raw_input('Subj? '))
#
prepend standard headers
date =
time.ctime(time.time())
text =
('From: %s\nTo: %s\nDate: %s\nSubject: %s\n'
% (From,
string.join(To, ','), date, Subj))
text =
text + '\n' # blank line between
hdrs,body
print
'Type message text, end with line=(ctrl + D or Z)'
while
1:
line = sys.stdin.readline()
if not line:
break # exit on
ctrl-d
text = text + line # servers do this auto
if
sys.platform[:3] == 'win': print
print
'Connecting...'
server = smtplib.SMTP(mailserver) # connect, no login step
errors = server.sendmail(From, To, text)
server.quit()
if
errors:
# smtplib also raises excepts
print 'Errors:', errors
else:
print 'No errors.'
print
'Bye.'
The email
package
·
Parses
mail text fetched with poplib
·
Generates
mail text to send with smtplib
# Composing a simple message
>>> from email.Message import Message
>>> m = Message()
>>> m['from'] = 'Sue Jones <sue@jones.com>'
>>> m['to'] = 'pp3e@earthlink.net'
>>> m.set_payload('The owls are not what they seem...')
>>> s = str(m)
>>> print s
From nobody Sun Jan 22 21:26:53 2006
from: Sue Jones <sue@jones.com>
to: pp3e@earthlink.net
The owls are not what they seem...
# Parsing a simple message
>>> from email.Parser import Parser
>>> x = Parser().parsestr(s)
>>> x
<email.Message.Message instance at 0x00A7DA30>
>>> x['From']
'Sue Jones <sue@jones.com>'
>>> x.get_payload()
'The owls are not what they seem...'
>>> x.items()
[('from', 'Sue Jones <sue@jones.com>'), ('to', 'pp3e@earthlink.net')]
>>> for part in x.walk():
... print x.get_content_type()
... print x.get_payload()
...
text/plain
The owls are not what they seem...
# Composing a multi-part message (attachments)
>>> from email.MIMEMultipart import MIMEMultipart
>>> from email.MIMEText import MIMEText
>>>
>>> top = MIMEMultipart()
>>> top['from'] = 'Art <arthur@camelot.org>'
>>> top['to'] = 'pp3e@earthlink.net'
>>>
>>> sub1 = MIMEText('nice red uniforms...\n')
>>> sub2 = MIMEText(open('data.txt').read())
>>> sub2.add_header('Content-Disposition', 'attachment',
filename='data.txt')
>>> top.attach(sub1)
>>> top.attach(sub2)
>>> text = top.as_string() # same as str() or print
>>> print text
Content-Type: multipart/mixed; boundary="===============0257358049=="
MIME-Version: 1.0
from: Art <arthur@camelot.org>
to: pp3e@earthlink.net
--===============0257358049==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
nice red uniforms...
--===============0257358049==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="data.txt"
line1
line2
line3
--===============0257358049==--
# Parsing a multi-part message
>>> from email.Parser import Parser
>>> msg = Parser().parsestr(text)
>>> msg['from']
'Art <arthur@camelot.org>'
>>> for part in msg.walk():
... print part.get_content_type()
... print part.get_payload()
...
multipart/mixed
[<email.Message.Message instance at 0x00A82058>,
<email.Message.Message instance at 0x00A82260>]
text/plain
nice red uniforms...
text/plain
line1
line2
line3
Running the email examples
·
See also: pymail.py and PyMailGui.py in examples directory
·
PyMailGui is a Python/Tk mail client (screen shots in GUI unit)
C:\examples\Part3\Internet\Email>python
smtpmail.py
From?
lutz@rmi.net
To? lutz@rmi.net
Subj?
testing 1 2 3
Type
message text, end with line=(ctrl + D or Z)
words
Connecting...
No
errors.
Bye.
C:\examples\Part3\Internet\Email>python
popmail.py
Password
for pop.rmi.net?
Connecting...
+OK
Cubic Circle's v1.31 1998/05/13
There
are 1 mail messages in 780 bytes
-----------------------------------------------------------------------
[Press
Enter key]
Received:
by chevalier (mbox lutz)
(with
X-From_:
lutz@rmi.net Sun
Return-Path:
<lutz@chevalier.rmi.net>
Received:
from server.python.net (server.python.net [209.50.192.113])
by chevalier.rmi.net (8.9.3/8.9.3) with
SMTP id NAA16859
for <lutz@rmi.net>; Sun, 13 Feb
2000 13:43:00 -0700 (MST)
Message-Id:
<200002132043.NAA16859@chevalier.rmi.net>
Received:
(qmail 31944 invoked from network); 13 Feb 2000 20:43:22 -0000
Received:
from dial-67.73.denco.rmi.net (166.93.67.73)
by server.python.net with SMTP; 13 Feb 2000
20:43:22 -0000
From:
lutz@rmi.net
To:
lutz@rmi.net
Date:
Sun
Subject:
testing 1 2 3
words
------------------------------------------------------------------------
Bye.
· urllib: fetching web pages
· nntplib: reading and posting usenet news
· httplib: lower-level web conversations
· telnet, gopher, imap,…
· htmllib, xml package: parsing fetched web pages and data (soap)
· Active Scripting: embedded Python in HTML
· Jython: client-side applets in Python
¨ CGI scripting
· Simpler techniques for simpler sites
· Augment with state retention: hidden form fields, query parameters, cookies, server-side databases
· Embeds HTML in Python (opposite of PSP, etc.)
· Cgi module parses inputs, escapes outputs
· See also HTMLgen and similar for reply generation
· Require web server that can run Python scripts
¨ Web frameworks and tools
·
Zope (OO meets
websites: DTML, TAL, ZMI, ZODB, …)
·
Plone (CMS built
on top of Zope, workflow)
·
TurboGears (multi-tool
package:
·
Django (model
view controller implicit)
· CherryPy, WebWare, Quixote (embarrassment of riches!)
·
PSP (Webware and
mod_python, like PHP/ASP)
· mod_python for Apache (speed, sessions, PSP,…)
· Fast CGI (persistent processes for state retention)
· Twisted (network server framework)
¨ Server-side scripts referenced from HTML pages
¨ Run on server, connected to client/browser via sockets
¨ cgi module handles input parsing, output formatting
Typical operation
¨ Inputs: fetch info typed into forms (cgi.FieldStorage)
· cgi scripts get input from stdin plus environment info
¨ cgi library module
· provides dictionary interface to parsed form data
¨ Outputs: sesults in browser (cgi.escape, urllib.quote)
· cgi scripts write HTML to stdout to display results
Basic
¨ FieldStorage class
· Reads the form contents from standard input or the environment
print "Content-type: text/html" # HTML text follows
print # blank line: end headers
print "<TITLE>
form = cgi.FieldStorage()
form_ok = 0
if form.has_key("name") and form.has_key("addr"):
if (form["name"].value != "" and
form["addr"].value != ""):
form_ok = 1
if not form_ok:
print "<H1>Error</H1>"
print "Please fill in the name and addr fields."
return
...more form processing here...
¨ FormContent class
· A (now) outdated interface: same functionality, but FieldStorage above is preferred scheme
import cgi # see library manual
. . .
form = cgi.FormContent() # parse input stream
if form.has_key("fieldname"):
data = form["fieldname"][0]
# input form: cgi101.html
·
use GET
instead of POST for some server/browser combos
<html><body>
<title>Interactive Page</title>
<form method=GET action="cgi-bin/cgi101.py">
<P><B>Enter your name:</B>
<P><input type=text name=user>
<P><input type=submit>
</form>
</body></html>
# reply script: cgi-bin/cgi101.py
#!/usr/bin/python
import cgi
form = cgi.FieldStorage() # parse form data
print "Content-type: text/html\n" # hdr plus blank line
print "<title>Reply Page</title>" # html reply page
if not form.has_key('user'):
print "<h1>Who are you?</h1>"
else:
print "<h1>Hello <i>%s</i>!</h1>" % cgi.escape(form['user'].value)
# running a local webserver: Extras\Misc\webserver.py
#################################################
# implement HTTP server in Python which
#
knows how to run server-side
# serves files/scripts from current working dir;
# scripts must be in webdir\cgi-bin or htbin
#################################################
webdir = '.'
import os, sys
from BaseHTTPServer import HTTPServer
from CGIHTTPServer import CGIHTTPRequestHandler
# hack for Windows: os.environ not propogated
# to subprocess by os.popen2, force in-process
if sys.platform[:3] == 'win':
CGIHTTPRequestHandler.have_popen2 = False
CGIHTTPRequestHandler.have_popen3 = False
os.chdir(webdir) # run in html root dir
srvraddr = ("", 80) # my hostname, portnumber
srvrobj = HTTPServer(srvraddr, CGIHTTPRequestHandler)
srvrobj.serve_forever() # run as perpetual demon
# testing with query strings and urllib
>>> from urllib import urlopen
>>> conn =
urlopen('http://localhost/cgi-bin/cgi101.py?user=Sue+Smith')
>>> reply = conn.read()
>>> reply
'<title>Reply
Page</title>\n<h1>Hello <i>Sue Smith</i>!</h1>\n'
>>>
urlopen('http://localhost/cgi-bin/cgi101.py').read()
'<title>Reply
Page</title>\n<h1>Who are you?</h1>\n'
>>>
urlopen('http://localhost/cgi-bin/cgi101.py?user=Bob').read()
'<title>Reply
Page</title>\n<h1>Hello <i>Bob</i>!</h1>\n'
Note: these examples live at http://starship.python.net/~lutz/PyInternetDemos.html.
Visit that site to view the HTML that generates this and other pages--select
‘view source’ in your browser.
test5b.html
<HTML><BODY>
<TITLE>
<H1>Common input devices: alternative
layout</H1>
<P>Use the same test5.cgi server side script,
but change the
layout of the form itself. Notice the separation of user interface
and processing logic here; the
HTML used to interact with the
user/client.</P><HR>
<
<H3>Please complete the following form and click Submit</H3>
<P><TABLE border cellpadding=3>
<TR>
<TH
align=right>Name:
<TD><input type=text name=name>
<TR>
<TH
align=right>Shoe size:
<TD><input type=radio name=shoesize value=small>Small
<input type=radio name=shoesize value=medium>Medium
<input type=radio name=shoesize value=large>Large
<TR>
<TH
align=right>Occupation:
<TD><select name=job>
<option>Developer
<option>Manager
<option>Student
<option>Evangelist
<option>Other
</select>
<TR>
<TH
align=right>Political affiliations:
<TD><P><input type=checkbox name=language
value=Python>Pythonista
<P><input type=checkbox name=language value=Perl>Perlmonger
<P><input type=checkbox name=language value=Tcl>Tcler
<TR>
<TH
align=right>Comments:
<TD><textarea name=comment cols=30 rows=2>Enter spam
here</textarea>
<TR>
<TD
colspan=2 align=center>
<input type=submit value="Submit">
<input type=reset
value="Reset">
</TABLE>
</
</BODY></HTML>
test5.cgi
#!/usr/local/bin/python
# runs on the server, reads form input, prints html;
# executable privileges, stored in ~/public_html,
import cgi, sys, string
form = cgi.FieldStorage() # parse form data
print "Content-type: text/html" # plus blank line
html = """
<TITLE>test4.cgi</TITLE>
<H1>Greetings</H1>
<HR>
<H4>Your name is %(name)s</H4>
<H4>You wear rather %(shoesize)s
shoes</H4>
<H4>Your current job: %(job)s</H4>
<H4>You program in %(language)s</H4>
<H4>You also said:</H4>
<P>%(comment)s</P>
<HR>"""
data = {}
for field in ['name', 'shoesize', 'job', 'language',
'comment']:
if not
form.has_key(field):
data[field] = '(unknown)'
else:
if
type(form[field]) != type([]):
data[field] = form[field].value
else:
values = map(lambda x: x.value, form[field])
data[field]
= string.join(values, ' and ')
print html % data
In typical interactions, a user directs a
browser to a HTML file, which includes a form action that automatically invokes
the server-side
test5c.html
<HTML><BODY>
<TITLE>
<H1>Common
input devices: URL parameters</H1>
<P>
This
demo invokes the test5.cgi server-side script again,
but
hardcodes input data to the end of the script's URL,
within
a simple hyperlink (instead of packaging up a form's
inputs). Click your browser's "show page
source" button
to
view the links associated with each list item below.
<P>This
is really more about
Python's
cgi module handles both this form of input (which is
also
produced by
they
look the same to the Python
cgi
module users are independent of the method used to submit
data.
<P>Also
notice that URLs with appended input values like this
can
be generated as part of the page output by another
to
direct a next user click to the right place and context; together
with
type 'hidden' input fields, they provide one way to
save
state between clicks.
</P><HR>
<UL>
<LI><A
href=
"http://starship.python.net/~lutz/test5.cgi?name=Bob&shoesize=small">
Send
Bob, small</A>
<LI><A
href=
"http://starship.python.net/~lutz/test5.cgi?name=Tom&language=Python">
Send
Tom, Python</A>
<LI><A
href=
"http://starship.python.net/~lutz/test5.cgi?job=Evangelist&comment=spam">
Send
Evangelist, spam</A>
</UL>
</UL>
<HR>
</BODY></HTML>
· Portable: written in Python, uses Tkinter as browser GUI
· Browser downloads/runs applets referenced in HTML
·
Applets more powerful than HTML+
· Python also supports ActiveX, Netscape plug-ins,…
HTML file
<HEAD>
<TITLE>Grail Applet Test Page</TITLE>
</HEAD>
<BODY>
<H1>Test an Applet Here!</H1>
Click this button!
<APP CLASS=Question>
</BODY>
file: Question.py
# Python applet file: Question.py
# in the same location (URL) as the html file
# that references it; adds widgets to browser;
from Tkinter import *
class Question: # run by grail?
def __init__(self, parent): # parent=browser
self.button = Button(parent,
bitmap='question',
command=self.action)
self.button.pack()
def action(self):
if self.button['bitmap'] == 'question':
self.button.config(bitmap='questhead')
else:
self.button.config(bitmap='question')
if __name__ == '__main__':
root = Tk() # run stand-alone?
button = Question(root) # parent=Tk: top-level
root.mainloop()
Grail sample screen: a Python/Tk
GUI
¨ An independent implementation of Python
¨ Python for Java-based applications
¨ Python scripting complements Java systems language
¨ Renamed from “JPython” to “Jython” (2000: copyright)
Structure
· A collection of Java classes that compile and run Python code
· “jython” program: equivalent to “python”, interactive, scripts
· “jythonc” compiler/packager program: makes .jar, .class files
· Embedding: “PythonInterpreter” class runs Python from Java
· Extending: automatic, Python coded imports, uses Java classes
Advantages
¨ Compiles Python code to JVM byte-code
· “100% Pure Java” (written in Java, generates JVM)
· More seamless than Tcl and Perl approaches
· Can run as client-side applets in Java-aware browsers
¨ Includes Python/Java integration support
· Python can access Java classes
· Java can embed/call Python scripts
· Access to AWT and Swing from Python for GUIs
Examples
Hello World
from java.applet import Applet
class HelloWorld(Applet):
def paint(self, gc):
gc.drawString("Hello world", 20, 30)
A simple calculator
from java import awt
from pawt import swing
labels = ['7', '8', '9', '+',
'4', '5', '6', '-',
'1', '2', '3', '*',
'0', '.', '=', '/' ]
keys = swing.JPanel(awt.GridLayout(4, 4))
display = swing.JTextField()
def push(event): # Callback for regular keys
display.replaceSelection(event.actionCommand)
def enter(event): # Callback for '=' key
display.text = str(eval(display.text))
display.selectAll()
for label in labels:
key = swing.JButton(label)
if label == '=':
key.actionPerformed = enter
else:
key.actionPerformed = push
keys.add(key)
panel = swing.JPanel(awt.BorderLayout())
panel.add("North", display)
panel.add("Center", keys)
swing.test(panel)
Jython downsides
¨ Not yet fully compatible with standard Python
¨ Only applicable where a JVM is installed or shipped
¨ Requires users to learn Java too (environment, libs)
¨ Doesn’t support standard C/C++ extension modules
¨
Slower than C Python (1.7x, 10x, 100X), for now?
· Active Scripting: Python embedded in HTML, like JavaScript or VBScript
· Extracted and run on client (browser object module) or server (form and reply object models)
· Windows only, IE and IIS only today, requires Python + PyWin32 (formerly win32all) package installs
· More useful on server side: only one machine requires Python + PyWin32 installs, any browser will work
·
COM and
· Host uses Active Scripting to export COM object interfaces for use in Python code
· Future: Python port to C#/.Net framework; see also Python Soap, XML-RPC support for web services
Active Scripting example
è Embedded
Python in HTML currently disabled on client due to rexec security issue, though
Javascript can still call registered Python COM objects there; see also ASP,
PSP, Zope,…
<HTML>
<BODY>
<H1>Embedded code demo: Python</H1>
<SCRIPT Language=Python>
# embedded python code shows three alert boxes
# as page is loaded; any Python code works here,
# and uses auto-imported global funcs and objects
def message(i):
if i == 2:
alert("Finished!")
else:
alert("A Python-generated alert => %d" % i)
for count in range(3): message(count)
</SCRIPT>
</BODY></HTML>
A Python COM client
from sys import argv
docdir = 'C:\\temp\\'
if len(argv) == 2: docdir = argv[1] # ex: comclient.py a:\
from win32com.client import Dispatch # early or late binding
word = Dispatch('Word.Application') # connect/start word
word.Visible = 1 # else word runs hidden
# create and save new doc file
newdoc = word.Documents.Add() # call word methods
spot = newdoc.Range(0,0)
spot.InsertBefore('Hello COM client world!') # insert some text
newdoc.SaveAs(docdir + 'pycom.doc') # save in doc file
newdoc.SaveAs(docdir + 'copy.doc')
newdoc.Close()
A Python COM server
import sys
from win32com.server.exception import COMException # what to raise
import win32com.server.util # server tools
globhellos = 0
class MyServer:
# com info settings
_reg_clsid_ = '{1BA63CC0-7CF8-11D4-98D8-BB74DD3DDE3C}'
_reg_desc_ = 'Example Python Server'
_reg_progid_ = 'PythonServers.MyServer' # external name
_reg_class_spec_ = 'comserver.MyServer' # internal name
_public_methods_ = ['Hello', 'Square']
_public_attrs_ = ['version']
# python methods
def __init__(self):
self.version = 1.0
self.hellos = 0
def Square(self, arg): # exported methods
return arg ** 2
def Hello(self): # global variables
global globhellos # retain state, but
globhellos = globhellos + 1 # self vars don't
self.hellos = self.hellos + 1
return 'Hello COM server world [%d, %d]' % (globhellos, self.hellos)
# registration functions
def Register(pyclass=MyServer):
from win32com.server.register import UseCommandLine
UseCommandLine(pyclass)
def Unregister(classid=MyServer._reg_clsid_):
from win32com.server.register import UnregisterServer
UnregisterServer(classid)
if __name__ == '__main__': # register server if file run or clicked
Register() # unregisters if --unregister cmd-line arg
See Extras\Internet directory on the class CD for
more examples
¨ XML:
SAX/DOM parsers, XML-RPC,
· See Extras\XML DOM/SAX examples on class CD
· 3rd-party extensions for XPath,… (see xml-sig page)
· O’Reilly book: Python & XML
· xmlrpclib in Python std lib
· PySoap, Soapy: SOAP protocol
· ElementTree: XML parser/generator in Python 2.5 std lib
¨ HTMLgen
· generates HTML from class-based page descriptions (see CD)
¨ HTML, SGML parsers
· useful for extracting information from web pages
¨ rfc822 module, email package
· parses standard message headers
¨ ILU, fnorb, and OmniORB packages
· can be used for CORBA networking
¨ urllib module
· opens URL, returns file-like object: read, readline (see CD)
· Parse fetched page with htmllib, string.find, xml package
¨ Restricted execution mode (security issue)
· See Extras\Internet directory on CD
· for running code fetched from untrusted sources
· modules “rexec” and “bastion”
· è Withdrawn in Python 2.4: security concerns
¨ Other Internet protocol support
· web server classes (see Extras\Internet on CD)
· telnetlib, nntplib (news), SSL sockets,…
¨ Zope & Plone (www.zope.org)
· An open-source web application framework
· Written in and customized with Python
· Plone runs on Zope: workflow-based content management
· “http://server/module/func?arg1=val1&arg2=val2” URL becomes
“module.func(arg1=val1, arg2=val2)” call on server-side Python
· Supplemental Zope/Plone CD available
Click here to go to
lab exercises
Click here to go to
exercise solutions
Click here to go to solution source files
Click here to go to
lecture example files