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_without_query(self): 199 200 """ 201 A framework-specific method which gets the entire path from the request 202 minus the query string. 203 """ 204 205 raise NotImplementedError, "get_path_without_query" 206 207 def get_path_info(self): 208 209 """ 210 A framework-specific method which gets the "path info" (the part of the 211 URL after the resource name handling the current request) from the 212 request. 213 """ 214 215 raise NotImplementedError, "get_path_info" 216 217 def get_query_string(self): 218 219 """ 220 A framework-specific method which gets the query string from the path in 221 the request. 222 """ 223 224 raise NotImplementedError, "get_query_string" 225 226 # Higher level request-related methods. 227 228 def get_fields_from_path(self): 229 230 """ 231 A framework-specific method which extracts the form fields from the 232 path specified in the transaction. The underlying framework may refuse 233 to supply fields from the path if handling a POST transaction. 234 235 Returns a dictionary mapping field names to lists of values (even if a 236 single value is associated with any given field name). 237 """ 238 239 raise NotImplementedError, "get_fields_from_path" 240 241 def get_fields_from_body(self): 242 243 """ 244 A framework-specific method which extracts the form fields from the 245 message body in the transaction. 246 247 Returns a dictionary mapping field names to lists of values (even if a 248 single value is associated with any given field name). 249 """ 250 251 raise NotImplementedError, "get_fields_from_body" 252 253 def get_user(self): 254 255 """ 256 A framework-specific method which extracts user information from the 257 transaction. 258 259 Returns a username as a string or None if no user is defined. 260 """ 261 262 raise NotImplementedError, "get_user" 263 264 def get_cookies(self): 265 266 """ 267 A framework-specific method which obtains cookie information from the 268 request. 269 270 Returns a dictionary mapping cookie names to cookie objects. 271 """ 272 273 raise NotImplementedError, "get_cookies" 274 275 def get_cookie(self, cookie_name): 276 277 """ 278 A framework-specific method which obtains cookie information from the 279 request. 280 281 Returns a cookie object for the given 'cookie_name' or None if no such 282 cookie exists. 283 """ 284 285 raise NotImplementedError, "get_cookie" 286 287 # Response-related methods. 288 289 def get_response_stream(self): 290 291 """ 292 A framework-specific method which returns the response stream for 293 the transaction. 294 """ 295 296 raise NotImplementedError, "get_response_stream" 297 298 def get_response_code(self): 299 300 """ 301 Get the response code associated with the transaction. If no response 302 code is defined, None is returned. 303 """ 304 305 raise NotImplementedError, "get_response_code" 306 307 def set_response_code(self, response_code): 308 309 """ 310 Set the 'response_code' using a numeric constant defined in the HTTP 311 specification. 312 """ 313 314 raise NotImplementedError, "set_response_code" 315 316 def set_header_value(self, header, value): 317 318 """ 319 Set the HTTP 'header' with the given 'value'. 320 """ 321 322 raise NotImplementedError, "set_header_value" 323 324 def set_content_type(self, content_type): 325 326 """ 327 A framework-specific method which sets the 'content_type' for the 328 response. 329 """ 330 331 raise NotImplementedError, "set_content_type" 332 333 # Higher level response-related methods. 334 335 def set_cookie(self, cookie): 336 337 """ 338 A framework-specific method which stores the given 'cookie' object in 339 the response. 340 """ 341 342 raise NotImplementedError, "set_cookie" 343 344 def set_cookie_value(self, name, value, path=None, expires=None): 345 346 """ 347 A framework-specific method which stores a cookie with the given 'name' 348 and 'value' in the response. 349 350 The optional 'path' is a string which specifies the scope of the cookie, 351 and the optional 'expires' parameter is a value compatible with the 352 time.time function, and indicates the expiry date/time of the cookie. 353 """ 354 355 raise NotImplementedError, "set_cookie_value" 356 357 def delete_cookie(self, cookie_name): 358 359 """ 360 A framework-specific method which adds to the response a request that 361 the cookie with the given 'cookie_name' be deleted/discarded by the 362 client. 363 """ 364 365 raise NotImplementedError, "delete_cookie" 366 367 # Application-specific methods. 368 369 def set_user(self, username): 370 371 """ 372 An application-specific method which sets the user information with 373 'username' in the transaction. This affects subsequent calls to 374 'get_user'. 375 """ 376 377 raise NotImplementedError, "set_user" 378 379 class Resource: 380 381 "A generic resource interface." 382 383 def respond(self, trans): 384 385 """ 386 An application-specific method which performs activities on the basis of 387 the transaction object 'trans'. 388 """ 389 390 raise NotImplementedError, "respond" 391 392 class Authenticator: 393 394 "A generic authentication component." 395 396 def authenticate(self, trans): 397 398 """ 399 An application-specific method which authenticates the sender of the 400 request described by the transaction object 'trans'. This method should 401 consider 'trans' to be read-only and not attempt to change the state of 402 the transaction. 403 404 If the sender of the request is authenticated successfully, the result 405 of this method evaluates to true; otherwise the result of this method 406 evaluates to false. 407 """ 408 409 raise NotImplementedError, "authenticate" 410 411 def get_auth_type(self): 412 413 """ 414 An application-specific method which returns the authentication type to 415 be used. An example value is 'Basic' which specifies HTTP basic 416 authentication. 417 """ 418 419 raise NotImplementedError, "get_auth_type" 420 421 def get_realm(self): 422 423 """ 424 An application-specific method which returns the name of the realm for 425 which authentication is taking place. 426 """ 427 428 raise NotImplementedError, "get_realm" 429 430 # vim: tabstop=4 expandtab shiftwidth=4