blob: 63e4e8c6b952f3e4e0fd5189c71213e9b1b76830 [file] [log] [blame]
pswangdaef3492017-09-21 22:04:20 -05001'''
2Created on Aug 15, 2017
3
4@author: sw6830
5'''
6import os
7import posixpath
8import BaseHTTPServer
9import urllib
10import urlparse
11import cgi, sys, shutil, mimetypes
12from jsonschema import validate
13import jsonschema, json
14import DcaeVariables
15import SimpleHTTPServer
16from robot.api import logger
17
18
19try:
20 from cStringIO import StringIO
21except ImportError:
22 from StringIO import StringIO
23
24EvtSchema = None
25DMaaPHttpd = None
26
27
28def cleanUpEvent():
29 sz = DcaeVariables.VESEventQ.qsize()
30 for i in range(sz):
31 try:
32 self.evtQueue.get_nowait()
33 except:
34 pass
35
36def enqueEvent(evt):
37 if DcaeVariables.VESEventQ != None:
38 try:
39 DcaeVariables.VESEventQ.put(evt)
40 if DcaeVariables.IsRobotRun:
41 logger.console("DMaaP Event enqued - size=" + str(len(evt)))
42 else:
43 print ("DMaaP Event enqueued - size=" + str(len(evt)))
44 return True
45 except Exception as e:
46 print (str(e))
47 return False
48 return False
49
50def dequeEvent(waitSec=25):
51 if DcaeVariables.IsRobotRun:
52 logger.console("Enter DequeEvent")
53 try:
54 evt = DcaeVariables.VESEventQ.get(True, waitSec)
55 if DcaeVariables.IsRobotRun:
56 logger.console("DMaaP Event dequeued - size=" + str(len(evt)))
57 else:
58 print("DMaaP Event dequeued - size=" + str(len(evt)))
59 return evt
60 except Exception as e:
61 if DcaeVariables.IsRobotRun:
62 logger.console(str(e))
63 logger.console("DMaaP Event dequeue timeout")
64 else:
65 print("DMaaP Event dequeue timeout")
66 return None
67
68class DMaaPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
69
70 def do_PUT(self):
71 self.send_response(405)
72 return
73
74 def do_POST(self):
75
76 respCode = 0
77 # Parse the form data posted
78 '''
79 form = cgi.FieldStorage(
80 fp=self.rfile,
81 headers=self.headers,
82 environ={'REQUEST_METHOD':'POST',
83 'CONTENT_TYPE':self.headers['Content-Type'],
84 })
85
86
87 form = cgi.FieldStorage(
88 fp=self.rfile,
89 headers=self.headers,
90 environ={"REQUEST_METHOD": "POST"})
91
92 for item in form.list:
93 print "%s=%s" % (item.name, item.value)
94
95 '''
96
97 if 'POST' not in self.requestline:
98 respCode = 405
99
100 '''
101 if respCode == 0:
102 if '/eventlistener/v5' not in self.requestline and '/eventlistener/v5/eventBatch' not in self.requestline and \
103 '/eventlistener/v5/clientThrottlingState' not in self.requestline:
104 respCode = 404
105
106
107 if respCode == 0:
108 if 'Y29uc29sZTpaakprWWpsbE1qbGpNVEkyTTJJeg==' not in str(self.headers):
109 respCode = 401
110 '''
111
112 if respCode == 0:
113 content_len = int(self.headers.getheader('content-length', 0))
114 post_body = self.rfile.read(content_len)
115
116 if DcaeVariables.IsRobotRun:
117 logger.console("\n" + "DMaaP Receive Event:\n" + post_body)
118 else:
119 print("\n" + "DMaaP Receive Event:")
120 print (post_body)
121
122 indx = post_body.index("{")
123 if indx != 0:
124 post_body = post_body[indx:]
125
126 if enqueEvent(post_body) == False:
127 print "enque event fails"
128
129 global EvtSchema
130 try:
131 if EvtSchema == None:
132 with open(DcaeVariables.CommonEventSchemaV5) as file:
133 EvtSchema = json.load(file)
134 decoded_body = json.loads(post_body)
135 jsonschema.validate(decoded_body, EvtSchema)
136 except:
137 respCode = 400
138
139 # Begin the response
140 if DcaeVariables.IsRobotRun == False:
141 print ("Response Message:")
142
143 '''
144 {
145 "200" : {
146 "description" : "Success",
147 "schema" : {
148 "$ref" : "#/definitions/DR_Pub"
149 }
150 }
151
152 rspStr = "{'responses' : {'200' : {'description' : 'Success'}}}"
153 rspStr1 = "{'count': 1, 'serverTimeMs': 3}"
154
155 '''
156
157 if respCode == 0:
158 if 'clientThrottlingState' in self.requestline:
159 self.send_response(204)
160 else:
161 self.send_response(200)
162 self.send_header('Content-Type', 'application/json')
163 self.end_headers()
164 #self.wfile.write("{'responses' : {'200' : {'description' : 'Success'}}}")
165 self.wfile.write("{'count': 1, 'serverTimeMs': 3}")
166 self.wfile.close()
167 else:
168 self.send_response(respCode)
169
170 '''
171 self.end_headers()
172 self.wfile.write('Client: %s\n' % str(self.client_address))
173 self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent']))
174 self.wfile.write('Path: %s\n' % self.path)
175 self.wfile.write('Form data:\n')
176 self.wfile.close()
177
178 # Echo back information about what was posted in the form
179 for field in form.keys():
180 field_item = form[field]
181 if field_item.filename:
182 # The field contains an uploaded file
183 file_data = field_item.file.read()
184 file_len = len(file_data)
185 del file_data
186 self.wfile.write('\tUploaded %s as "%s" (%d bytes)\n' % \
187 (field, field_item.filename, file_len))
188 else:
189 # Regular form value
190 self.wfile.write('\t%s=%s\n' % (field, form[field].value))
191 '''
192 return
193
194
195 def do_GET(self):
196 """Serve a GET request."""
197 f = self.send_head()
198 if f:
199 try:
200 self.copyfile(f, self.wfile)
201 finally:
202 f.close()
203
204 def do_HEAD(self):
205 """Serve a HEAD request."""
206 f = self.send_head()
207 if f:
208 f.close()
209
210 def send_head(self):
211 """Common code for GET and HEAD commands.
212
213 This sends the response code and MIME headers.
214
215 Return value is either a file object (which has to be copied
216 to the outputfile by the caller unless the command was HEAD,
217 and must be closed by the caller under all circumstances), or
218 None, in which case the caller has nothing further to do.
219
220 """
221 path = self.translate_path(self.path)
222 f = None
223 if os.path.isdir(path):
224 parts = urlparse.urlsplit(self.path)
225 if not parts.path.endswith('/'):
226 # redirect browser - doing basically what apache does
227 self.send_response(301)
228 new_parts = (parts[0], parts[1], parts[2] + '/',
229 parts[3], parts[4])
230 new_url = urlparse.urlunsplit(new_parts)
231 self.send_header("Location", new_url)
232 self.end_headers()
233 return None
234 for index in "index.html", "index.htm":
235 index = os.path.join(path, index)
236 if os.path.exists(index):
237 path = index
238 break
239 else:
240 return self.list_directory(path)
241 ctype = self.guess_type(path)
242 try:
243 # Always read in binary mode. Opening files in text mode may cause
244 # newline translations, making the actual size of the content
245 # transmitted *less* than the content-length!
246 f = open(path, 'rb')
247 except IOError:
248 self.send_error(404, "File not found")
249 return None
250 try:
251 self.send_response(200)
252 self.send_header("Content-type", ctype)
253 fs = os.fstat(f.fileno())
254 self.send_header("Content-Length", str(fs[6]))
255 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
256 self.end_headers()
257 return f
258 except:
259 f.close()
260 raise
261
262 def list_directory(self, path):
263 """Helper to produce a directory listing (absent index.html).
264
265 Return value is either a file object, or None (indicating an
266 error). In either case, the headers are sent, making the
267 interface the same as for send_head().
268
269 """
270 try:
271 list = os.listdir(path)
272 except os.error:
273 self.send_error(404, "No permission to list directory")
274 return None
275 list.sort(key=lambda a: a.lower())
276 f = StringIO()
277 displaypath = cgi.escape(urllib.unquote(self.path))
278 f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
279 f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
280 f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
281 f.write("<hr>\n<ul>\n")
282 for name in list:
283 fullname = os.path.join(path, name)
284 displayname = linkname = name
285 # Append / for directories or @ for symbolic links
286 if os.path.isdir(fullname):
287 displayname = name + "/"
288 linkname = name + "/"
289 if os.path.islink(fullname):
290 displayname = name + "@"
291 # Note: a link to a directory displays with @ and links with /
292 f.write('<li><a href="%s">%s</a>\n'
293 % (urllib.quote(linkname), cgi.escape(displayname)))
294 f.write("</ul>\n<hr>\n</body>\n</html>\n")
295 length = f.tell()
296 f.seek(0)
297 self.send_response(200)
298 encoding = sys.getfilesystemencoding()
299 self.send_header("Content-type", "text/html; charset=%s" % encoding)
300 self.send_header("Content-Length", str(length))
301 self.end_headers()
302 return f
303
304 def translate_path(self, path):
305 """Translate a /-separated PATH to the local filename syntax.
306
307 Components that mean special things to the local file system
308 (e.g. drive or directory names) are ignored. (XXX They should
309 probably be diagnosed.)
310
311 """
312 # abandon query parameters
313 path = path.split('?',1)[0]
314 path = path.split('#',1)[0]
315 # Don't forget explicit trailing slash when normalizing. Issue17324
316 trailing_slash = path.rstrip().endswith('/')
317 path = posixpath.normpath(urllib.unquote(path))
318 words = path.split('/')
319 words = filter(None, words)
320 path = os.getcwd()
321 for word in words:
322 if os.path.dirname(word) or word in (os.curdir, os.pardir):
323 # Ignore components that are not a simple file/directory name
324 continue
325 path = os.path.join(path, word)
326 if trailing_slash:
327 path += '/'
328 return path
329
330 def copyfile(self, source, outputfile):
331 """Copy all data between two file objects.
332
333 The SOURCE argument is a file object open for reading
334 (or anything with a read() method) and the DESTINATION
335 argument is a file object open for writing (or
336 anything with a write() method).
337
338 The only reason for overriding this would be to change
339 the block size or perhaps to replace newlines by CRLF
340 -- note however that this the default server uses this
341 to copy binary data as well.
342
343 """
344 shutil.copyfileobj(source, outputfile)
345
346 def guess_type(self, path):
347 """Guess the type of a file.
348
349 Argument is a PATH (a filename).
350
351 Return value is a string of the form type/subtype,
352 usable for a MIME Content-type header.
353
354 The default implementation looks the file's extension
355 up in the table self.extensions_map, using application/octet-stream
356 as a default; however it would be permissible (if
357 slow) to look inside the data to make a better guess.
358
359 """
360
361 base, ext = posixpath.splitext(path)
362 if ext in self.extensions_map:
363 return self.extensions_map[ext]
364 ext = ext.lower()
365 if ext in self.extensions_map:
366 return self.extensions_map[ext]
367 else:
368 return self.extensions_map['']
369
370 if not mimetypes.inited:
371 mimetypes.init() # try to read system mime.types
372 extensions_map = mimetypes.types_map.copy()
373 extensions_map.update({
374 '': 'application/octet-stream', # Default
375 '.py': 'text/plain',
376 '.c': 'text/plain',
377 '.h': 'text/plain',
378 })
379
380def test(HandlerClass = DMaaPHandler,
381 ServerClass = BaseHTTPServer.HTTPServer, protocol="HTTP/1.0", port=3904):
382 print "Load event schema file: " + DcaeVariables.CommonEventSchemaV5
383 with open(DcaeVariables.CommonEventSchemaV5) as file:
384 global EvtSchema
385 EvtSchema = json.load(file)
386
387 server_address = ('', port)
388
389 HandlerClass.protocol_version = protocol
390 httpd = ServerClass(server_address, HandlerClass)
391
392 global DMaaPHttpd
393 DMaaPHttpd = httpd
394 DcaeVariables.HTTPD = httpd
395
396 sa = httpd.socket.getsockname()
397 print "Serving HTTP on", sa[0], "port", sa[1], "..."
398 #httpd.serve_forever()
399
400def _main_ (HandlerClass = DMaaPHandler,
401 ServerClass = BaseHTTPServer.HTTPServer, protocol="HTTP/1.0"):
402
403 if sys.argv[1:]:
404 port = int(sys.argv[1])
405 else:
406 port = 3904
407
408 print "Load event schema file: " + DcaeVariables.CommonEventSchemaV5
409 with open(DcaeVariables.CommonEventSchemaV5) as file:
410 global EvtSchema
411 EvtSchema = json.load(file)
412
413 server_address = ('', port)
414
415 HandlerClass.protocol_version = protocol
416 httpd = ServerClass(server_address, HandlerClass)
417
418 sa = httpd.socket.getsockname()
419 print "Serving HTTP on", sa[0], "port", sa[1], "..."
420 httpd.serve_forever()
421
422if __name__ == '__main__':
423 _main_()