GET/POST etc are one-off transactions
No concept of a user session with a web server in HTTP
HTTP/1.1 -- multiple requests per connection, each independant
Problem: how to allow login, transactions, user customisation?
HTTP wasn't designed as a protocol for long-lived transactions -- if we were starting again we might build in some idea of state or at least have the option of a stateful protocol. We didn't so we need some other mechanisms; luckily these are easily managed within the HTTP protocol.
How do we get the effect of transaction state?
Additional problem: can't identify clients uniquely
Primary method:
Give client a unique token
Client returns token with each request
HTTP Mechanisms: form variables, cookies
What info do we want to exchange? In general we need to get some info back from the client to enable us to identify them on each transaction. This could be as simple as a username but more generally will be a unique session key. The exchanged value allows the server to look up the user or session state in a local data store.
Hidden form variable carries session info:
<form action='go.cgi'> ... <input type='hidden' name='userid' value='steve' /> ... </form>
When you generate a form via a CGI script the value can be inserted based on the logged in user.
Recall that form variables can be sent as part of the URL in a GET request:
http://www.here.com/process.cgi?session=31926xxks6
This can be used to pass state information back to a server:
Modify all URLs in a delivered page by adding
?session=31926xxks6
Server can track a user through a session via parameters in the GET request.
NOTE: for this to work, page generation must be via a program (CGI, JSP, etc), but then, you can't keep track of users without code.
A name=value pair sent to client by the server
Client returns the same cookie with every HTTP request to that server
Similar effect as a hidden form variable without the URL ugliness
Cookies can persist indefinitely or for a fixed time
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: session=31926xxks6; Path=/;
<html>
...
Set-Cookie header instructs client to remember this cookie. Client sends it back on next request:
GET /acme/shopping HTTP/1.1
Cookie: session=31926xxks6
A cookie has more than just name=value:
Path: subset of URLs to which to cookie applies, eg.
/products
Domain: DNS domain that the cookie applies to, eg.
.ics.mq.edu.au
Max-Age: lifetime of the cookie in seconds
Expires: date at which cookie becomes invalid (older specification)
Note that Expires was the old way to specify the lifetime of a cookie, Max-Age is the new way.
Secure: if present, only send this cookie down secure connections
Comment: human readable information about the cookie
Version: which version of the cookie standard is being used
POST /acme/login HTTP/1.1 [form data] HTTP/1.1 200 OK Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme" POST /acme/pickitem HTTP/1.1 Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme" [form data] HTTP/1.1 200 OK Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme" POST /acme/shipping HTTP/1.1 Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" [form data]
This example is taken from the HTTP state Management Mechanism standard (RFC2109).
Session cookie:
No Max-Age/Expires attribute
Survives only during the browser session
Persistant cookie:
Max-Age/Expires attribute set
Stored in local persistent store by client
Survives browser session
Cookie value is opaque to the client
Client should store:
At least 300 cookies
At least 4096 bytes per cookie
At least 20 cookies per unique host/domain
Clients may disable cookies -- why?
Cookies have been seen as a serious privacy threat since they allow sites to track users browsing patterns and potentially identify them via personal info entered into forms etc.
>>> import Cookie >>> C = Cookie.SimpleCookie() >>> C['name'] = 'steve' >>> print C Set-Cookie: name=steve; >>> C['item'] = 'water pistol' >>> print C Set-Cookie: item="water pistol"; Set-Cookie: name=steve; >>> C['item']['path'] = '/products' >>> print C Set-Cookie: item="water pistol"; Path=/products; Set-Cookie: name=steve;
from Cookie import SimpleCookie import os C = SimpleCookie() C['item'] = "Water Pistol" C['item']['path'] = '/~cassidy/' # now output the HTTP header and content print C print "Content-Type: text/html\n" print "<html><head><title>Cookie</title></head><body>" print "<p>", C ,"</p>" print "</body></html>"
The cookie header is passed to the CGI script in the HTTP_COOKIE environment variable.
from Cookie import SimpleCookie
import os
user = ''
item = ''
if os.environ.has_key('HTTP_COOKIE'):
C = SimpleCookie(os.environ['HTTP_COOKIE'])
if C.has_key('user'):
user = C['user']
if C.has_key('item'):
item = C['item']
else:
C = SimpleCookie()
print C
print "Content-Type: text/html\n"
print "<html><head><title>Cookie</title></head><body>"
if not user == '':
print "<p>User is ", user ,"</p>"
print "<p>Item is ", item ,"</p>"
else:
print "<p>Our cookie not found.</p>"
print "</body></html>"
| fullame | password | |
|---|---|---|
| Steve Cassidy | Steve@here.com | foo |
| Jim Cricket | jim@here.com | bar |
| Dave Dust | dave@here.com | baz |
| message | |
|---|---|
| Steve@here.com | Great site! |
| jim@here.com | Love to see you |
| dave@here.com | Finally! |
One of the import features of good RDBMS is ACID compliance:
SQL is the standard query language for relational databases. It supports queries to the database as well as various database maintainance operations.
SQL queries select data from one or more tables and produce a new table as output.
SELECT fullname, password FROM users
WHERE email='Steve@here.com';
SELECT users.fullname, messages.message
FROM users, messages
WHERE users.email = messages.email AND
users.email = 'Steve@here.com';
SQL also allows update and delete:
INSERT INTO users
(fullname, email, password)
VALUES
('Joe User', 'Joe@here.com', 'secret');
DELETE FROM users WHERE email = 'Steve@here.com';
Like many scripting languages Python has a standardised way of talking to relational databases
The Python DB-API defines how databases should be exposed to Python
Implemented for: DB/2, Informix, Ingres, JDBC, MySQL, Oracle, PostgreSQL, Sybase
Standard interface means projects can be portable between database vendors
From The Python DB-API in Linux Journal
>>> import soliddb
>>> conn = soliddb.soliddb("Upipe SOLID', 'user', 'pwd')
>>> cursor = conn.cursor()
>>> cursor.execute("select * from Seminars")
>>> cursor.fetchall()
[(4, 'Web Commerce', 300.0, 26),
(1, 'Python Programming', 200.0, 15),
(3, 'Socket Programming', 475.0, 7),
(2, 'Intro to Linux', 100.0, 32),
]
For SQLite (pysqlite2)
>>> from pysqlite2 import dbapi2 as sqlite
>>> conn = sqlite.connect(dbname)
>>> sql = "select fullname, password from users"
>>> cursor = conn.execute(sql)
>>> for row in cursor.fetchall():
print "Name: ", row[0], "Password:", row[1]
A cursor object allows you to send a query and get the results.
...
cursor = conn.cursor()
cursor.execute('select * from Attendees where seminar = 1')
for attendee in cursor.fetchall():
print attendee
cursor.execute("update Attendees set paid='yes' where name='steve'")
#...more updates...
conn.commit() # commit changes to database
#...more updates...
# discover a problem
conn.rollback() # undo changes since last commit
Often want to insert values into a query:
# WRONG sql = "SELECT password FROM users WHERE email='%s'" conn.execute(sql % (email, ))
# RIGHT sql = "SELECT password FROM users WHERE email=?" conn.execute(sql, (email, ))
Why? Consider the case when email="Steve's here"
from pysqlite2 import dbapi2 as sqlite
conn = sqlite.connect("test.db")
conn.execute("""
CREATE TABLE users (
fullname VARCHAR,
email VARCHAR,
password VARCHAR)""")
users = [['Steve Cassidy', 'steve@here.com', 'foo'],
['Jim Johns', 'jim@here.com', 'bar']]
sql = """INSERT INTO users (fullname, email, password)
VALUES (?, ?, ?)"""
for user in users:
conn.execute(sql, user)