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, encoding=None): 242 243 """ 244 A framework-specific method which extracts the form fields from the 245 message body in the transaction. The optional 'encoding' parameter 246 specifies the character encoding of the message body for cases where no 247 such information is available, but where the default encoding is to be 248 overridden. 249 250 Returns a dictionary mapping field names to lists of values (even if a 251 single value is associated with any given field name). 252 """ 253 254 raise NotImplementedError, "get_fields_from_body" 255 256 def get_user(self): 257 258 """ 259 A framework-specific method which extracts user information from the 260 transaction. 261 262 Returns a username as a string or None if no user is defined. 263 """ 264 265 raise NotImplementedError, "get_user" 266 267 def get_cookies(self): 268 269 """ 270 A framework-specific method which obtains cookie information from the 271 request. 272 273 Returns a dictionary mapping cookie names to cookie objects. 274 """ 275 276 raise NotImplementedError, "get_cookies" 277 278 def get_cookie(self, cookie_name): 279 280 """ 281 A framework-specific method which obtains cookie information from the 282 request. 283 284 Returns a cookie object for the given 'cookie_name' or None if no such 285 cookie exists. 286 """ 287 288 raise NotImplementedError, "get_cookie" 289 290 # Response-related methods. 291 292 def get_response_stream(self): 293 294 """ 295 A framework-specific method which returns the response stream for 296 the transaction. 297 """ 298 299 raise NotImplementedError, "get_response_stream" 300 301 def get_response_code(self): 302 303 """ 304 Get the response code associated with the transaction. If no response 305 code is defined, None is returned. 306 """ 307 308 raise NotImplementedError, "get_response_code" 309 310 def set_response_code(self, response_code): 311 312 """ 313 Set the 'response_code' using a numeric constant defined in the HTTP 314 specification. 315 """ 316 317 raise NotImplementedError, "set_response_code" 318 319 def set_header_value(self, header, value): 320 321 """ 322 Set the HTTP 'header' with the given 'value'. 323 """ 324 325 raise NotImplementedError, "set_header_value" 326 327 def set_content_type(self, content_type): 328 329 """ 330 A framework-specific method which sets the 'content_type' for the 331 response. 332 """ 333 334 raise NotImplementedError, "set_content_type" 335 336 # Higher level response-related methods. 337 338 def set_cookie(self, cookie): 339 340 """ 341 A framework-specific method which stores the given 'cookie' object in 342 the response. 343 """ 344 345 raise NotImplementedError, "set_cookie" 346 347 def set_cookie_value(self, name, value, path=None, expires=None): 348 349 """ 350 A framework-specific method which stores a cookie with the given 'name' 351 and 'value' in the response. 352 353 The optional 'path' is a string which specifies the scope of the cookie, 354 and the optional 'expires' parameter is a value compatible with the 355 time.time function, and indicates the expiry date/time of the cookie. 356 """ 357 358 raise NotImplementedError, "set_cookie_value" 359 360 def delete_cookie(self, cookie_name): 361 362 """ 363 A framework-specific method which adds to the response a request that 364 the cookie with the given 'cookie_name' be deleted/discarded by the 365 client. 366 """ 367 368 raise NotImplementedError, "delete_cookie" 369 370 # Session-related methods. 371 372 def get_session(self, create=1): 373 374 """ 375 A framework-specific method which returns a session corresponding to 376 an identifier supplied in the transaction. 377 378 If no session has yet been established according to information 379 provided in the transaction then the optional 'create' parameter 380 determines whether a new session will be established. 381 382 Where no session has been established and where 'create' is set to 0 383 then None is returned. In all other cases, a session object is created 384 (where appropriate) and returned. 385 """ 386 387 raise NotImplementedError, "get_session" 388 389 def expire_session(self): 390 391 """ 392 A framework-specific method which expires any session established 393 according to information provided in the transaction. 394 """ 395 396 raise NotImplementedError, "expire_session" 397 398 # Application-specific methods. 399 400 def set_user(self, username): 401 402 """ 403 An application-specific method which sets the user information with 404 'username' in the transaction. This affects subsequent calls to 405 'get_user'. 406 """ 407 408 raise NotImplementedError, "set_user" 409 410 class Resource: 411 412 "A generic resource interface." 413 414 def respond(self, trans): 415 416 """ 417 An application-specific method which performs activities on the basis of 418 the transaction object 'trans'. 419 """ 420 421 raise NotImplementedError, "respond" 422 423 class Authenticator: 424 425 "A generic authentication component." 426 427 def authenticate(self, trans): 428 429 """ 430 An application-specific method which authenticates the sender of the 431 request described by the transaction object 'trans'. This method should 432 consider 'trans' to be read-only and not attempt to change the state of 433 the transaction. 434 435 If the sender of the request is authenticated successfully, the result 436 of this method evaluates to true; otherwise the result of this method 437 evaluates to false. 438 """ 439 440 raise NotImplementedError, "authenticate" 441 442 def get_auth_type(self): 443 444 """ 445 An application-specific method which returns the authentication type to 446 be used. An example value is 'Basic' which specifies HTTP basic 447 authentication. 448 """ 449 450 raise NotImplementedError, "get_auth_type" 451 452 def get_realm(self): 453 454 """ 455 An application-specific method which returns the name of the realm for 456 which authentication is taking place. 457 """ 458 459 raise NotImplementedError, "get_realm" 460 461 # vim: tabstop=4 expandtab shiftwidth=4