# HG changeset patch # User paulb # Date 1082838811 0 # Node ID 8f35654956bbe54d030e94c2400ae4945841f27f # Parent c68a04b1976cde725c43f322f48ce3d40957ec8e [project @ 2004-04-24 20:33:31 by paulb] Made the accept preferences handling more robust. Added some clarifications to the docstrings for certain methods. Moved get_headers functionality for CGI-style environments to a new helper module. Added CGI support. Added cookie "deletion" support for Twisted, based on the existing mod_python support. diff -r c68a04b1976c -r 8f35654956bb WebStack/BaseHTTPRequestHandler.py --- a/WebStack/BaseHTTPRequestHandler.py Sat Apr 24 17:01:26 2004 +0000 +++ b/WebStack/BaseHTTPRequestHandler.py Sat Apr 24 20:33:31 2004 +0000 @@ -70,7 +70,8 @@ def get_headers(self): """ - A framework-specific method which returns all request headers. + A framework-specific method which returns all request headers as a + dictionary-like object mapping header names to values. NOTE: If duplicate header names are permitted, then this interface will NOTE: need to change. """ @@ -103,7 +104,7 @@ Returns the character set preferences. """ - return self.parse_content_preferences(self.trans.headers["Accept-Charset"]) + return self.parse_content_preferences(self.trans.headers.get("Accept-Charset")) def get_content_languages(self): @@ -112,7 +113,7 @@ the transaction. """ - return self.parse_content_preferences(self.trans.headers["Accept-Language"]) + return self.parse_content_preferences(self.trans.headers.get("Accept-Language")) def get_path(self): @@ -195,6 +196,8 @@ """ A framework-specific method which extracts user information from the transaction. + + Returns a username as a string or None if no user is defined. """ auth_header = self.get_headers().get("Authorization") diff -r c68a04b1976c -r 8f35654956bb WebStack/CGI.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebStack/CGI.py Sat Apr 24 20:33:31 2004 +0000 @@ -0,0 +1,343 @@ +#!/usr/bin/env python + +""" +CGI classes. +""" + +import Generic +import os, sys +from Helpers.Request import MessageBodyStream +from Helpers.Auth import UserInfo +from Helpers import Environment +from cgi import parse_qs, FieldStorage +import Cookie +from StringIO import StringIO + +class Transaction(Generic.Transaction): + + """ + CGI transaction interface. + """ + + def __init__(self, input=None, output=None, env=None): + + """ + Initialise the transaction using the CGI 'input' and 'output' streams. + These streams are optional and default to standard input and standard + output respectively. + """ + + self.input = input or sys.stdin + self.output = output or sys.stdout + self.env = env or os.environ + + # Other attributes of interest in instances of this class. + + self.content_type = None + self.response_code = 200 + self.content = StringIO() + self.headers_out = {} + self.cookies_out = Cookie.SimpleCookie() + + # Define the incoming cookies. + + self.cookies_in = Cookie.SimpleCookie(self.env.get("HTTP_COOKIE")) + + def commit(self): + + """ + A special method, synchronising the transaction with framework-specific + objects. + + See draft-coar-cgi-v11-03, section 7. + """ + + # NOTE: Provide sensible messages. + + self.output.write("Status: %s %s\n" % (self.response_code, "WebStack status")) + if self.content_type is not None: + self.output.write("Content-type: %s\n" % self.format_content_type(self.content_type)) + for header, value in self.headers_out.items(): + self.output.write("%s: %s\n" % + (self.format_header_value(header), self.format_header_value(value)) + ) + self.output.write(str(self.cookies_out)) + self.output.write("\n") + + self.content.seek(0) + self.output.write(self.content.read()) + + # Request-related methods. + + def get_request_stream(self): + + """ + A framework-specific method which returns the request stream for + the transaction. + """ + + return self.input + + def get_request_method(self): + + """ + A framework-specific method which gets the request method. + """ + + return self.env.get("REQUEST_METHOD") + + def get_headers(self): + + """ + A framework-specific method which returns all request headers as a + dictionary-like object mapping header names to values. + """ + + return Environment.get_headers(self.env) + + def get_header_values(self, key): + + """ + A framework-specific method which returns a list of all request header + values associated with the given 'key'. Note that according to RFC 2616, + 'key' is treated as a case-insensitive string. + """ + + return self.convert_to_list(self.get_headers().get(key)) + + def get_content_type(self): + + """ + A framework-specific method which gets the content type specified on the + request, along with the charset employed. + """ + + return self.parse_content_type(self.env.get("CONTENT_TYPE")) + + def get_content_charsets(self): + + """ + Returns the character set preferences. + """ + + return self.parse_content_preferences(None) + + def get_content_languages(self): + + """ + A framework-specific method which extracts language information from + the transaction. + """ + + return self.parse_content_preferences(None) + + def get_path(self): + + """ + A framework-specific method which gets the entire path from the request. + """ + + path = self.env.get("SCRIPT_NAME") or "" + if self.env.has_key("PATH_INFO"): + path += self.env["PATH_INFO"] + qs = self.env.get("QUERY_STRING") + if qs: + path += "?" + path += qs + return path + + def get_path_info(self): + + """ + A framework-specific method which gets the "path info" (the part of the + URL after the resource name handling the current request) from the + request. + """ + + return self.env.get("PATH_INFO") or "" + + def get_query_string(self): + + """ + A framework-specific method which gets the query string from the path in + the request. + """ + + return self.env.get("QUERY_STRING") or "" + + # Higher level request-related methods. + + def get_fields_from_path(self): + + """ + A framework-specific method which extracts the form fields from the + path specified in the transaction. The underlying framework may refuse + to supply fields from the path if handling a POST transaction. + + Returns a dictionary mapping field names to lists of values (even if a + single value is associated with any given field name). + """ + + return parse_qs(self.get_query_string(), keep_blank_values=1) + + def get_fields_from_body(self): + + """ + A framework-specific method which extracts the form fields from the + message body in the transaction. + + Returns a dictionary mapping field names to lists of values (even if a + single value is associated with any given field name). + """ + + storage = FieldStorage(fp=self.get_request_stream(), keep_blank_values=1) + + # Avoid strange design issues with FieldStorage by checking the internal + # field list directly. + + fields = {} + if storage.list is not None: + + # Traverse the storage, finding each field value. + + for field_name in storage.keys(): + fields[field_name] = storage.getlist(field_name) + return fields + + def get_user(self): + + """ + A framework-specific method which extracts user information from the + transaction. + + Returns a username as a string or None if no user is defined. + """ + + return self.env.get("REMOTE_USER") + + def get_cookies(self): + + """ + A framework-specific method which obtains cookie information from the + request. + + Returns a dictionary mapping cookie names to cookie objects. + """ + + return self.cookies_in + + def get_cookie(self, cookie_name): + + """ + A framework-specific method which obtains cookie information from the + request. + + Returns a cookie object for the given 'cookie_name' or None if no such + cookie exists. + """ + + return self.cookies_in.get(cookie_name) + + # Response-related methods. + + def get_response_stream(self): + + """ + A framework-specific method which returns the response stream for + the transaction. + """ + + # Return a stream which is later emptied into the real stream. + + return self.content + + def get_response_code(self): + + """ + Get the response code associated with the transaction. If no response + code is defined, None is returned. + """ + + return self.response_code + + def set_response_code(self, response_code): + + """ + Set the 'response_code' using a numeric constant defined in the HTTP + specification. + """ + + self.response_code = response_code + + def set_header_value(self, header, value): + + """ + Set the HTTP 'header' with the given 'value'. + """ + + # The header is not written out immediately due to the buffering in use. + + self.headers_out[header] = value + + def set_content_type(self, content_type): + + """ + A framework-specific method which sets the 'content_type' for the + response. + """ + + # The content type has to be written as a header, before actual content, + # but after the response line. This means that some kind of buffering is + # required. Hence, we don't write the header out immediately. + + self.content_type = content_type + + # Higher level response-related methods. + + def set_cookie(self, cookie): + + """ + A framework-specific method which stores the given 'cookie' object in + the response. + """ + + # NOTE: If multiple cookies of the same name could be specified, this + # NOTE: could need changing. + + self.cookies_out[cookie.name] = cookie.value + + def set_cookie_value(self, name, value, path=None, expires=None): + + """ + A framework-specific method which stores a cookie with the given 'name' + and 'value' in the response. + + The optional 'path' is a string which specifies the scope of the cookie, + and the optional 'expires' parameter is a value compatible with the + time.time function, and indicates the expiry date/time of the cookie. + """ + + self.cookies_out[name] = value + if path is not None: + self.cookies_out[name]["path"] = path + if expires is not None: + self.cookies_out[name]["expires"] = expires + + def delete_cookie(self, cookie_name): + + """ + A framework-specific method which adds to the response a request that + the cookie with the given 'cookie_name' be deleted/discarded by the + client. + """ + + # Create a special cookie, given that we do not know whether the browser + # has been sent the cookie or not. + # NOTE: Magic discovered in Webware. + + self.cookies_out[cookie_name] = "" + self.cookies_out[cookie_name]["path"] = "/" + self.cookies_out[cookie_name]["expires"] = 0 + self.cookies_out[cookie_name]["max-age"] = 0 + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r c68a04b1976c -r 8f35654956bb WebStack/Generic.py --- a/WebStack/Generic.py Sat Apr 24 17:01:26 2004 +0000 +++ b/WebStack/Generic.py Sat Apr 24 20:33:31 2004 +0000 @@ -94,6 +94,9 @@ preferences are appropriate. """ + if accept_preference is None: + return [] + accept_defs = accept_preference.split(",") accept_prefs = [] for accept_def in accept_defs: diff -r c68a04b1976c -r 8f35654956bb WebStack/JavaServlet.py --- a/WebStack/JavaServlet.py Sat Apr 24 17:01:26 2004 +0000 +++ b/WebStack/JavaServlet.py Sat Apr 24 20:33:31 2004 +0000 @@ -85,7 +85,8 @@ def get_headers(self): """ - A framework-specific method which returns all request headers. + A framework-specific method which returns all request headers as a + dictionary-like object mapping header names to values. NOTE: If duplicate header names are permitted, then this interface will NOTE: need to change. """ @@ -218,6 +219,8 @@ """ A framework-specific method which extracts user information from the transaction. + + Returns a username as a string or None if no user is defined. """ return self.request.getRemoteUser() diff -r c68a04b1976c -r 8f35654956bb WebStack/ModPython.py --- a/WebStack/ModPython.py Sat Apr 24 17:01:26 2004 +0000 +++ b/WebStack/ModPython.py Sat Apr 24 20:33:31 2004 +0000 @@ -48,7 +48,8 @@ def get_headers(self): """ - A framework-specific method which returns all request headers. + A framework-specific method which returns all request headers as a + dictionary-like object mapping header names to values. NOTE: If duplicate header names are permitted, then this interface will NOTE: need to change. """ @@ -80,7 +81,7 @@ Returns the character set preferences. """ - return self.parse_content_preferences(self.trans.headers_in["Accept-Charset"]) + return self.parse_content_preferences(self.trans.headers_in.get("Accept-Charset")) def get_content_languages(self): @@ -89,7 +90,7 @@ the transaction. """ - return self.parse_content_preferences(self.trans.headers_in["Accept-Language"]) + return self.parse_content_preferences(self.trans.headers_in.get("Accept-Language")) def get_path(self): @@ -162,6 +163,8 @@ """ A framework-specific method which extracts user information from the transaction. + + Returns a username as a string or None if no user is defined. """ return self.trans.user diff -r c68a04b1976c -r 8f35654956bb WebStack/Twisted.py --- a/WebStack/Twisted.py Sat Apr 24 17:01:26 2004 +0000 +++ b/WebStack/Twisted.py Sat Apr 24 20:33:31 2004 +0000 @@ -43,7 +43,8 @@ def get_headers(self): """ - A framework-specific method which returns all request headers. + A framework-specific method which returns all request headers as a + dictionary-like object mapping header names to values. NOTE: If duplicate header names are permitted, then this interface will NOTE: need to change. """ @@ -155,6 +156,8 @@ """ A framework-specific method which extracts user information from the transaction. + + Returns a username as a string or None if no user is defined. """ # NOTE: Twisted makes headers lower case, for some reason. @@ -276,10 +279,12 @@ A framework-specific method which adds to the response a request that the cookie with the given 'cookie_name' be deleted/discarded by the client. - - NOTE: Not supported yet. """ - pass + # Create a special cookie, given that we do not know whether the browser + # has been sent the cookie or not. + # NOTE: Magic discovered in Webware. + + self.trans.addCookie(cookie_name, "", expires=0, path="/", max_age=0) # vim: tabstop=4 expandtab shiftwidth=4 diff -r c68a04b1976c -r 8f35654956bb WebStack/Webware.py --- a/WebStack/Webware.py Sat Apr 24 17:01:26 2004 +0000 +++ b/WebStack/Webware.py Sat Apr 24 20:33:31 2004 +0000 @@ -7,6 +7,7 @@ import Generic from cgi import parse_qs import StringIO +from Helpers import Environment class Transaction(Generic.Transaction): @@ -54,7 +55,8 @@ def get_headers(self): """ - A framework-specific method which returns all request headers. + A framework-specific method which returns all request headers as a + dictionary-like object mapping header names to values. NOTE: If duplicate header names are permitted, then this interface will NOTE: need to change. """ @@ -63,13 +65,7 @@ # NOTE: Using lower case for the header names. env = self.trans.request().environ() - headers = {} - for cgi_key, value in env.items(): - if cgi_key.startswith("HTTP_"): - header_name = cgi_key[len("HTTP_"):].replace("_", "-").lower() - headers[header_name] = value - - return headers + return Environment.get_headers(env) def get_header_values(self, key): @@ -183,6 +179,8 @@ """ A framework-specific method which extracts user information from the transaction. + + Returns a username as a string or None if no user is defined. """ # NOTE: Webware relies entirely on a CGI-style environment where the