1 #!/usr/bin/env python 2 3 """ 4 Generic Web framework interfaces. 5 The WebStack architecture consists of the following layers: 6 7 * Framework - The underlying Web framework implementation. 8 * Adapter - Code operating under the particular framework which creates 9 WebStack abstractions and issues them to the application. 10 * Resources - Units of functionality operating within the hosted Web 11 application. 12 13 Resources can act as both content producers within an application and as request 14 dispatchers to other resources; in the latter role, they may be referred to as 15 directors. 16 """ 17 18 class ContentType: 19 20 "A container for content type information." 21 22 def __init__(self, content_type, charset=None): 23 self.content_type = content_type 24 self.charset = charset 25 26 class Transaction: 27 28 """ 29 A generic transaction interface containing framework-specific methods to be 30 overridden. 31 """ 32 33 def commit(self): 34 35 """ 36 A special method, synchronising the transaction with framework-specific 37 objects. 38 """ 39 40 pass 41 42 # Utility methods. 43 44 def parse_content_type(self, content_type_field): 45 46 """ 47 Determine the content type and charset from the supplied 48 'content_type_field' string. 49 """ 50 51 if content_type_field is None: 52 return ContentType(None, "iso-8859-1") 53 54 t = content_type_field.split("; charset=") 55 if len(t) == 1: 56 return ContentType(t[0], "iso-8859-1") 57 else: 58 return ContentType(t[0], t[1]) 59 60 def format_content_type(self, content_type): 61 62 """ 63 Format the given 'content_type' object, producing a string suitable for 64 the response header field. 65 """ 66 67 if content_type.charset: 68 field = "%s; charset=%s" % (content_type.content_type, content_type.charset) 69 else: 70 field = content_type.content_type 71 72 # Make sure that only ASCII is used in the header. 73 74 return field.encode("US-ASCII") 75 76 def format_header_value(self, value): 77 78 """ 79 Format the given header 'value'. Typically, this just ensures the usage 80 of US-ASCII. 81 """ 82 83 return value.encode("US-ASCII") 84 85 def parse_content_preferences(self, accept_preference): 86 87 """ 88 Returns the preferences as requested by the user agent. The preferences are 89 returned as a list of codes in the same order as they appeared in the 90 appropriate environment variable. In other words, the explicit weighting 91 criteria are ignored. 92 93 As the 'accept_preference' parameter, values for language and charset 94 preferences are appropriate. 95 """ 96 97 if accept_preference is None: 98 return [] 99 100 accept_defs = accept_preference.split(",") 101 accept_prefs = [] 102 for accept_def in accept_defs: 103 t = accept_def.split(";") 104 if len(t) >= 1: 105 accept_prefs.append(t[0].strip()) 106 return accept_prefs 107 108 def convert_to_list(self, value): 109 110 """ 111 Returns a single element list containing 'value' if it is not itself a list, a 112 tuple, or None. If 'value' is a list then it is itself returned; if 'value' is a 113 tuple then a new list containing the same elements is returned; if 'value' is None 114 then an empty list is returned. 115 """ 116 117 if type(value) == type([]): 118 return value 119 elif type(value) == type(()): 120 return list(value) 121 elif value is None: 122 return [] 123 else: 124 return [value] 125 126 # Request-related methods. 127 128 def get_request_stream(self): 129 130 """ 131 A framework-specific method which returns the request stream for 132 the transaction. 133 """ 134 135 raise NotImplementedError, "get_request_stream" 136 137 def get_request_method(self): 138 139 """ 140 A framework-specific method which gets the request method. 141 """ 142 143 raise NotImplementedError, "get_request_method" 144 145 def get_headers(self): 146 147 """ 148 A framework-specific method which returns all request headers as a 149 dictionary-like object mapping header names to values. 150 """ 151 152 raise NotImplementedError, "get_headers" 153 154 def get_header_values(self, key): 155 156 """ 157 A framework-specific method which returns a list of all request header 158 values associated with the given 'key'. Note that according to RFC 2616, 159 'key' is treated as a case-insensitive string. 160 """ 161 162 raise NotImplementedError, "get_header_values" 163 164 def get_content_type(self): 165 166 """ 167 A framework-specific method which gets the content type specified on the 168 request, along with the charset employed. 169 """ 170 171 raise NotImplementedError, "get_content_type" 172 173 def get_content_charsets(self): 174 175 """ 176 Returns the character set preferences. 177 """ 178 179 raise NotImplementedError, "get_content_charsets" 180 181 def get_content_languages(self): 182 183 """ 184 A framework-specific method which extracts language information from 185 the transaction. 186 """ 187 188 raise NotImplementedError, "get_content_languages" 189 190 def get_path(self): 191 192 """ 193 A framework-specific method which gets the entire path from the request. 194 """ 195 196 raise NotImplementedError, "get_path" 197 198 def get_path_info(self): 199 200 """ 201 A framework-specific method which gets the "path info" (the part of the 202 URL after the resource name handling the current request) from the 203 request. 204 """ 205 206 raise NotImplementedError, "get_path_info" 207 208 def get_query_string(self): 209 210 """ 211 A framework-specific method which gets the query string from the path in 212 the request. 213 """ 214 215 raise NotImplementedError, "get_query_string" 216 217 # Higher level request-related methods. 218 219 def get_fields_from_path(self): 220 221 """ 222 A framework-specific method which extracts the form fields from the 223 path specified in the transaction. The underlying framework may refuse 224 to supply fields from the path if handling a POST transaction. 225 226 Returns a dictionary mapping field names to lists of values (even if a 227 single value is associated with any given field name). 228 """ 229 230 raise NotImplementedError, "get_fields_from_path" 231 232 def get_fields_from_body(self): 233 234 """ 235 A framework-specific method which extracts the form fields from the 236 message body in the transaction. 237 238 Returns a dictionary mapping field names to lists of values (even if a 239 single value is associated with any given field name). 240 """ 241 242 raise NotImplementedError, "get_fields_from_body" 243 244 def get_user(self): 245 246 """ 247 A framework-specific method which extracts user information from the 248 transaction. 249 250 Returns a username as a string or None if no user is defined. 251 """ 252 253 raise NotImplementedError, "get_user" 254 255 def get_cookies(self): 256 257 """ 258 A framework-specific method which obtains cookie information from the 259 request. 260 261 Returns a dictionary mapping cookie names to cookie objects. 262 """ 263 264 raise NotImplementedError, "get_cookies" 265 266 def get_cookie(self, cookie_name): 267 268 """ 269 A framework-specific method which obtains cookie information from the 270 request. 271 272 Returns a cookie object for the given 'cookie_name' or None if no such 273 cookie exists. 274 """ 275 276 raise NotImplementedError, "get_cookie" 277 278 # Response-related methods. 279 280 def get_response_stream(self): 281 282 """ 283 A framework-specific method which returns the response stream for 284 the transaction. 285 """ 286 287 raise NotImplementedError, "get_response_stream" 288 289 def get_response_code(self): 290 291 """ 292 Get the response code associated with the transaction. If no response 293 code is defined, None is returned. 294 """ 295 296 raise NotImplementedError, "get_response_code" 297 298 def set_response_code(self, response_code): 299 300 """ 301 Set the 'response_code' using a numeric constant defined in the HTTP 302 specification. 303 """ 304 305 raise NotImplementedError, "set_response_code" 306 307 def set_header_value(self, header, value): 308 309 """ 310 Set the HTTP 'header' with the given 'value'. 311 """ 312 313 raise NotImplementedError, "set_header_value" 314 315 def set_content_type(self, content_type): 316 317 """ 318 A framework-specific method which sets the 'content_type' for the 319 response. 320 """ 321 322 raise NotImplementedError, "set_content_type" 323 324 # Higher level response-related methods. 325 326 def set_cookie(self, cookie): 327 328 """ 329 A framework-specific method which stores the given 'cookie' object in 330 the response. 331 """ 332 333 raise NotImplementedError, "set_cookie" 334 335 def set_cookie_value(self, name, value, path=None, expires=None): 336 337 """ 338 A framework-specific method which stores a cookie with the given 'name' 339 and 'value' in the response. 340 341 The optional 'path' is a string which specifies the scope of the cookie, 342 and the optional 'expires' parameter is a value compatible with the 343 time.time function, and indicates the expiry date/time of the cookie. 344 """ 345 346 raise NotImplementedError, "set_cookie_value" 347 348 def delete_cookie(self, cookie_name): 349 350 """ 351 A framework-specific method which adds to the response a request that 352 the cookie with the given 'cookie_name' be deleted/discarded by the 353 client. 354 """ 355 356 raise NotImplementedError, "delete_cookie" 357 358 class Resource: 359 360 "A generic resource interface." 361 362 def respond(self, trans): 363 364 """ 365 An application-specific method which performs activities on the basis of 366 the transaction object 'trans'. 367 """ 368 369 raise NotImplementedError, "respond" 370 371 class Authenticator: 372 373 "A generic authentication component." 374 375 def authenticate(self, trans): 376 377 """ 378 An application-specific method which authenticates the sender of the 379 request described by the transaction object 'trans'. This method should 380 consider 'trans' to be read-only and not attempt to change the state of 381 the transaction. 382 383 If the sender of the request is authenticated successfully, the result 384 of this method evaluates to true; otherwise the result of this method 385 evaluates to false. 386 """ 387 388 raise NotImplementedError, "authenticate" 389 390 def get_auth_type(self): 391 392 """ 393 An application-specific method which returns the authentication type to 394 be used. An example value is 'Basic' which specifies HTTP basic 395 authentication. 396 """ 397 398 raise NotImplementedError, "get_auth_type" 399 400 def get_realm(self): 401 402 """ 403 An application-specific method which returns the name of the realm for 404 which authentication is taking place. 405 """ 406 407 raise NotImplementedError, "get_realm" 408 409 # vim: tabstop=4 expandtab shiftwidth=4