VirtualBox

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

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

TestManager/WebServerGlueBase: Added config options for dumping the enviornment and argument vector to the debug log.

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