VirtualBox

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

Last change on this file since 82633 was 82633, checked in by vboxsync, 5 years ago

TestManager/webservergluebase.py: Python 3 adjust.

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