VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/webservergluebase.py@ 65032

Last change on this file since 65032 was 65032, checked in by vboxsync, 8 years ago

config.g_kfSrvGlueDebugTS now also includes a pid so it's possible to sort the log lines.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.7 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: webservergluebase.py 65032 2016-12-29 22:31:17Z vboxsync $
3
4"""
5Test Manager Core - Web Server Abstraction Base Class.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2016 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 65032 $"
30
31
32# Standard python imports.
33import cgitb
34import codecs;
35import os
36import sys
37
38# Validation Kit imports.
39from common import webutils, utils;
40from testmanager import config;
41
42
43class WebServerGlueException(Exception):
44 """
45 For exceptions raised by glue code.
46 """
47 pass;
48
49
50class WebServerGlueBase(object):
51 """
52 Web server interface abstraction and some HTML utils.
53 """
54
55 ## Enables more debug output.
56 kfDebugInfoEnabled = True;
57
58 ## The maximum number of characters to cache.
59 kcchMaxCached = 65536;
60
61 ## Special getUserName return value.
62 ksUnknownUser = 'Unknown User';
63
64
65 def __init__(self, sValidationKitDir, fHtmlDebugOutput = True):
66 self._sValidationKitDir = sValidationKitDir;
67
68 # Debug
69 self.tsStart = utils.timestampNano();
70 self._fHtmlDebugOutput = fHtmlDebugOutput; # For trace
71 self._oDbgFile = sys.stderr;
72 if config.g_ksSrcGlueDebugLogDst is not None and config.g_kfSrvGlueDebug is True:
73 self._oDbgFile = open(config.g_ksSrcGlueDebugLogDst, 'a');
74 self._afnDebugInfo = [];
75
76 # HTTP header.
77 self._fHeaderWrittenOut = False;
78 self._dHeaderFields = \
79 { \
80 'Content-Type': 'text/html; charset=utf-8',
81 };
82
83 # Body.
84 self._sBodyType = None;
85 self._dParams = dict();
86 self._sHtmlBody = '';
87 self._cchCached = 0;
88 self._cchBodyWrittenOut = 0;
89
90 # Output.
91 self.oOutputRaw = sys.stdout;
92 self.oOutputText = codecs.getwriter('utf-8')(sys.stdout);
93
94
95 #
96 # Get stuff.
97 #
98
99 def getParameters(self):
100 """
101 Returns a dictionary with the query parameters.
102
103 The parameter name is the key, the values are given as lists. If a
104 parameter is given more than once, the value is appended to the
105 existing dictionary entry.
106 """
107 return dict();
108
109 def getClientAddr(self):
110 """
111 Returns the client address, as a string.
112 """
113 raise WebServerGlueException('getClientAddr is not implemented');
114
115 def getMethod(self):
116 """
117 Gets the HTTP request method.
118 """
119 return 'POST';
120
121 def getLoginName(self):
122 """
123 Gets login name provided by Apache.
124 Returns kUnknownUser if not logged on.
125 """
126 return WebServerGlueBase.ksUnknownUser;
127
128 def getUrlScheme(self):
129 """
130 Gets scheme name (aka. access protocol) from request URL, i.e. 'http' or 'https'.
131 See also urlparse.scheme.
132 """
133 return 'http';
134
135 def getUrlNetLoc(self):
136 """
137 Gets the network location (server host name / ip) from the request URL.
138 See also urlparse.netloc.
139 """
140 raise WebServerGlueException('getUrlNetLoc is not implemented');
141
142 def getUrlPath(self):
143 """
144 Gets the hirarchical path (relative to server) from the request URL.
145 See also urlparse.path.
146 Note! This includes the leading slash.
147 """
148 raise WebServerGlueException('getUrlPath is not implemented');
149
150 def getUrlBasePath(self):
151 """
152 Gets the hirarchical base path (relative to server) from the request URL.
153 Note! This includes both a leading an trailing slash.
154 """
155 sPath = self.getUrlPath();
156 iLastSlash = sPath.rfind('/');
157 if iLastSlash >= 0:
158 sPath = sPath[:iLastSlash];
159 sPath = sPath.rstrip('/');
160 return sPath + '/';
161
162 def getUrl(self):
163 """
164 Gets the URL being accessed, sans parameters.
165 For instance this will return, "http://localhost/testmanager/admin.cgi"
166 when "http://localhost/testmanager/admin.cgi?blah=blah" is being access.
167 """
168 return '%s://%s%s' % (self.getUrlScheme(), self.getUrlNetLoc(), self.getUrlPath());
169
170 def getBaseUrl(self):
171 """
172 Gets the base URL (with trailing slash).
173 For instance this will return, "http://localhost/testmanager/" when
174 "http://localhost/testmanager/admin.cgi?blah=blah" is being access.
175 """
176 return '%s://%s%s' % (self.getUrlScheme(), self.getUrlNetLoc(), self.getUrlBasePath());
177
178 def getUserAgent(self):
179 """
180 Gets the User-Agent field of the HTTP header, returning empty string
181 if not present.
182 """
183 return '';
184
185 def getContentType(self):
186 """
187 Gets the Content-Type field of the HTTP header, parsed into a type
188 string and a dictionary.
189 """
190 return ('text/html', {});
191
192 def getContentLength(self):
193 """
194 Gets the content length.
195 Returns int.
196 """
197 return 0;
198
199 def getBodyIoStream(self):
200 """
201 Returns file object for reading the HTML body.
202 """
203 raise WebServerGlueException('getUrlPath is not implemented');
204
205 #
206 # Output stuff.
207 #
208
209 def _writeHeader(self, sHeaderLine):
210 """
211 Worker function which child classes can override.
212 """
213 self.oOutputText.write(sHeaderLine);
214 return True;
215
216 def flushHeader(self):
217 """
218 Flushes the HTTP header.
219 """
220 if self._fHeaderWrittenOut is False:
221 for sKey in self._dHeaderFields:
222 self._writeHeader('%s: %s\n' % (sKey, self._dHeaderFields[sKey]));
223 self._fHeaderWrittenOut = True;
224 self._writeHeader('\n'); # End of header indicator.
225 return None;
226
227 def setHeaderField(self, sField, sValue):
228 """
229 Sets a header field.
230 """
231 assert self._fHeaderWrittenOut is False;
232 self._dHeaderFields[sField] = sValue;
233 return True;
234
235 def setRedirect(self, sLocation, iCode = 302):
236 """
237 Sets up redirection of the page.
238 Raises an exception if called too late.
239 """
240 if self._fHeaderWrittenOut is True:
241 raise WebServerGlueException('setRedirect called after the header was written');
242 if iCode != 302:
243 raise WebServerGlueException('Redirection code %d is not supported' % (iCode,));
244
245 self.setHeaderField('Location', sLocation);
246 self.setHeaderField('Status', '302 Found');
247 return True;
248
249 def _writeWorker(self, sChunkOfHtml):
250 """
251 Worker function which child classes can override.
252 """
253 self.oOutputText.write(sChunkOfHtml);
254 return True;
255
256 def write(self, sChunkOfHtml):
257 """
258 Writes chunk of HTML, making sure the HTTP header is flushed first.
259 """
260 if self._sBodyType is None:
261 self._sBodyType = 'html';
262 elif self._sBodyType is not 'html':
263 raise WebServerGlueException('Cannot use writeParameter when body type is "%s"' % (self._sBodyType, ));
264
265 self._sHtmlBody += sChunkOfHtml;
266 self._cchCached += len(sChunkOfHtml);
267
268 if self._cchCached > self.kcchMaxCached:
269 self.flush();
270 return True;
271
272 def writeRaw(self, abChunk):
273 """
274 Writes a raw chunk the document. Can be binary or any encoding.
275 No caching.
276 """
277 if self._sBodyType is None:
278 self._sBodyType = 'html';
279 elif self._sBodyType is not 'html':
280 raise WebServerGlueException('Cannot use writeParameter when body type is "%s"' % (self._sBodyType, ));
281
282 self.flushHeader();
283 if self._cchCached > 0:
284 self.flush();
285
286 self.oOutputRaw.write(abChunk);
287 return True;
288
289 def writeParams(self, dParams):
290 """
291 Writes one or more reply parameters in a form style response. The names
292 and values in dParams are unencoded, this method takes care of that.
293
294 Note! This automatically changes the content type to
295 'application/x-www-form-urlencoded', if the header hasn't been flushed
296 already.
297 """
298 if self._sBodyType is None:
299 if not self._fHeaderWrittenOut:
300 self.setHeaderField('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
301 elif self._dHeaderFields['Content-Type'] != 'application/x-www-form-urlencoded; charset=utf-8':
302 raise WebServerGlueException('Cannot use writeParams when content-type is "%s"' % \
303 (self._dHeaderFields['Content-Type'],));
304 self._sBodyType = 'form';
305
306 elif self._sBodyType is not 'form':
307 raise WebServerGlueException('Cannot use writeParams when body type is "%s"' % (self._sBodyType, ));
308
309 for sKey in dParams:
310 sValue = str(dParams[sKey]);
311 self._dParams[sKey] = sValue;
312 self._cchCached += len(sKey) + len(sValue);
313
314 if self._cchCached > self.kcchMaxCached:
315 self.flush();
316
317 return True;
318
319 def flush(self):
320 """
321 Flush the output.
322 """
323 self.flushHeader();
324
325 if self._sBodyType == 'form':
326 sBody = webutils.encodeUrlParams(self._dParams);
327 self._writeWorker(sBody);
328
329 self._dParams = dict();
330 self._cchBodyWrittenOut += self._cchCached;
331
332 elif self._sBodyType == 'html':
333 self._writeWorker(self._sHtmlBody);
334
335 self._sHtmlBody = '';
336 self._cchBodyWrittenOut += self._cchCached;
337
338 self._cchCached = 0;
339 return None;
340
341 #
342 # Paths.
343 #
344
345 def pathTmWebUI(self):
346 """
347 Gets the path to the TM 'webui' directory.
348 """
349 return os.path.join(self._sValidationKitDir, 'testmanager', 'webui');
350
351 #
352 # Error stuff & Debugging.
353 #
354
355 def errorLog(self, sError, aXcptInfo, sLogFile):
356 """
357 Writes the error to a log file.
358 """
359 # Easy solution for log file size: Only one report.
360 try: os.unlink(sLogFile);
361 except: pass;
362
363 # Try write the log file.
364 fRc = True;
365 fSaved = self._fHtmlDebugOutput;
366
367 try:
368 oFile = open(sLogFile, 'w');
369 oFile.write(sError + '\n\n');
370 if aXcptInfo[0] is not None:
371 oFile.write(' B a c k t r a c e\n');
372 oFile.write('===================\n');
373 oFile.write(cgitb.text(aXcptInfo, 5));
374 oFile.write('\n\n');
375
376 oFile.write(' D e b u g I n f o\n');
377 oFile.write('=====================\n\n');
378 self._fHtmlDebugOutput = False;
379 self.debugDumpStuff(oFile.write);
380
381 oFile.close();
382 except:
383 fRc = False;
384
385 self._fHtmlDebugOutput = fSaved;
386 return fRc;
387
388 def errorPage(self, sError, aXcptInfo, sLogFile = None):
389 """
390 Displays a page with an error message.
391 """
392 if sLogFile is not None:
393 self.errorLog(sError, aXcptInfo, sLogFile);
394
395 # Reset buffering, hoping that nothing was flushed yet.
396 self._sBodyType = None;
397 self._sHtmlBody = '';
398 self._cchCached = 0;
399 if not self._fHeaderWrittenOut:
400 if self._fHtmlDebugOutput:
401 self.setHeaderField('Content-Type', 'text/html; charset=utf-8');
402 else:
403 self.setHeaderField('Content-Type', 'text/plain; charset=utf-8');
404
405 # Write the error page.
406 if self._fHtmlDebugOutput:
407 self.write('<html><head><title>Test Manage Error</title></head>\n' +
408 '<body><h1>Test Manager Error:</h1>\n' +
409 '<p>' + sError + '</p>\n');
410 else:
411 self.write(' Test Manage Error\n'
412 '===================\n'
413 '\n'
414 '' + sError + '\n\n');
415
416 if aXcptInfo[0] is not None:
417 if self._fHtmlDebugOutput:
418 self.write('<h1>Backtrace:</h1>\n');
419 self.write(cgitb.html(aXcptInfo, 5));
420 else:
421 self.write('Backtrace\n'
422 '---------\n'
423 '\n');
424 self.write(cgitb.text(aXcptInfo, 5));
425 self.write('\n\n');
426
427 if self.kfDebugInfoEnabled:
428 if self._fHtmlDebugOutput:
429 self.write('<h1>Debug Info:</h1>\n');
430 else:
431 self.write('Debug Info\n'
432 '----------\n'
433 '\n');
434 self.debugDumpStuff();
435
436 for fn in self._afnDebugInfo:
437 try:
438 fn(self, self._fHtmlDebugOutput);
439 except Exception as oXcpt:
440 self.write('\nDebug info callback %s raised exception: %s\n' % (fn, oXcpt));
441
442 if self._fHtmlDebugOutput:
443 self.write('</body></html>');
444
445 self.flush();
446
447 def debugInfoPage(self, fnWrite = None):
448 """
449 Dumps useful debug info.
450 """
451 if fnWrite is None:
452 fnWrite = self.write;
453
454 fnWrite('<html><head><title>Test Manage Debug Info</title></head>\n<body>\n');
455 self.debugDumpStuff(fnWrite = fnWrite);
456 fnWrite('</body></html>');
457 self.flush();
458
459 def debugDumpDict(self, sName, dDict, fSorted = True, fnWrite = None):
460 """
461 Dumps dictionary.
462 """
463 if fnWrite is None:
464 fnWrite = self.write;
465
466 asKeys = list(dDict.keys());
467 if fSorted:
468 asKeys.sort();
469
470 if self._fHtmlDebugOutput:
471 fnWrite('<h2>%s</h2>\n'
472 '<table border="1"><tr><th>name</th><th>value</th></tr>\n' % (sName,));
473 for sKey in asKeys:
474 fnWrite(' <tr><td>' + webutils.escapeElem(sKey) + '</td><td>' \
475 + webutils.escapeElem(str(dDict.get(sKey))) \
476 + '</td></tr>\n');
477 fnWrite('</table>\n');
478 else:
479 for i in range(len(sName) - 1):
480 fnWrite('%s ' % (sName[i],));
481 fnWrite('%s\n\n' % (sName[-1],));
482
483 fnWrite('%28s Value\n' % ('Name',));
484 fnWrite('------------------------------------------------------------------------\n');
485 for sKey in asKeys:
486 fnWrite('%28s: %s\n' % (sKey, dDict.get(sKey),));
487 fnWrite('\n');
488
489 return True;
490
491 def debugDumpList(self, sName, aoStuff, fnWrite = None):
492 """
493 Dumps array.
494 """
495 if fnWrite is None:
496 fnWrite = self.write;
497
498 if self._fHtmlDebugOutput:
499 fnWrite('<h2>%s</h2>\n'
500 '<table border="1"><tr><th>index</th><th>value</th></tr>\n' % (sName,));
501 for i, _ in enumerate(aoStuff):
502 fnWrite(' <tr><td>' + str(i) + '</td><td>' + webutils.escapeElem(str(aoStuff[i])) + '</td></tr>\n');
503 fnWrite('</table>\n');
504 else:
505 for ch in sName[:-1]:
506 fnWrite('%s ' % (ch,));
507 fnWrite('%s\n\n' % (sName[-1],));
508
509 fnWrite('Index Value\n');
510 fnWrite('------------------------------------------------------------------------\n');
511 for i, oStuff in enumerate(aoStuff):
512 fnWrite('%5u %s\n' % (i, str(oStuff)));
513 fnWrite('\n');
514
515 return True;
516
517 def debugDumpParameters(self, fnWrite):
518 """ Dumps request parameters. """
519 if fnWrite is None:
520 fnWrite = self.write;
521
522 try:
523 dParams = self.getParameters();
524 return self.debugDumpDict('Parameters', dParams);
525 except Exception as oXcpt:
526 if self._fHtmlDebugOutput:
527 fnWrite('<p>Exception %s while retriving parameters.</p>\n' % (oXcpt,))
528 else:
529 fnWrite('Exception %s while retriving parameters.\n' % (oXcpt,))
530 return False;
531
532 def debugDumpEnv(self, fnWrite = None):
533 """ Dumps os.environ. """
534 return self.debugDumpDict('Environment (os.environ)', os.environ, fnWrite = fnWrite);
535
536 def debugDumpArgv(self, fnWrite = None):
537 """ Dumps sys.argv. """
538 return self.debugDumpList('Arguments (sys.argv)', sys.argv, fnWrite = fnWrite);
539
540 def debugDumpPython(self, fnWrite = None):
541 """
542 Dump python info.
543 """
544 dInfo = {};
545 dInfo['sys.version'] = sys.version;
546 dInfo['sys.hexversion'] = sys.hexversion;
547 dInfo['sys.api_version'] = sys.api_version;
548 dInfo['sys.subversion'] = sys.subversion;
549 dInfo['sys.platform'] = sys.platform;
550 dInfo['sys.executable'] = sys.executable;
551 dInfo['sys.copyright'] = sys.copyright;
552 dInfo['sys.byteorder'] = sys.byteorder;
553 dInfo['sys.exec_prefix'] = sys.exec_prefix;
554 dInfo['sys.prefix'] = sys.prefix;
555 dInfo['sys.path'] = sys.path;
556 dInfo['sys.builtin_module_names'] = sys.builtin_module_names;
557 dInfo['sys.flags'] = sys.flags;
558
559 return self.debugDumpDict('Python Info', dInfo, fnWrite = fnWrite);
560
561
562 def debugDumpStuff(self, fnWrite = None):
563 """
564 Dumps stuff to the error page and debug info page.
565 Should be extended by child classes when possible.
566 """
567 self.debugDumpParameters(fnWrite);
568 self.debugDumpEnv(fnWrite);
569 self.debugDumpArgv(fnWrite);
570 self.debugDumpPython(fnWrite);
571 return True;
572
573 def dprint(self, sMessage):
574 """
575 Prints to debug log (usually apache error log).
576 """
577 if config.g_kfSrvGlueDebug is True:
578 if config.g_kfSrvGlueDebugTS is False:
579 self._oDbgFile.write(sMessage);
580 if not sMessage.endswith('\n'):
581 self._oDbgFile.write('\n');
582 else:
583 tsNow = utils.timestampMilli();
584 tsReq = tsNow - (self.tsStart / 1000000);
585 iPid = os.getpid();
586 for sLine in sMessage.split('\n'):
587 self._oDbgFile.write('%s/%03u,pid=%04x: %s\n' % (tsNow, tsReq, iPid, sLine,));
588
589 return True;
590
591 def registerDebugInfoCallback(self, fnDebugInfo):
592 """
593 Registers a debug info method for calling when the error page is shown.
594
595 The fnDebugInfo function takes two parameters. The first is this
596 object, the second is a boolean indicating html (True) or text (False)
597 output. The return value is ignored.
598 """
599 if self.kfDebugInfoEnabled:
600 self._afnDebugInfo.append(fnDebugInfo);
601 return True;
602
603 def unregisterDebugInfoCallback(self, fnDebugInfo):
604 """
605 Unregisters a debug info method previously registered by
606 registerDebugInfoCallback.
607 """
608 if self.kfDebugInfoEnabled:
609 try: self._afnDebugInfo.remove(fnDebugInfo);
610 except: pass;
611 return True;
612
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette