225 lines
7.5 KiB
Python
225 lines
7.5 KiB
Python
# Copyright (C) Schrodinger, LLC.
|
|||
|
|
# All Rights Reserved
|
||
|
|
#
|
||
|
|
# For more information, see LICENSE in PyMOL's home directory.
|
||
|
|
#
|
||
|
|
# justhttpd.py
|
||
|
|
#
|
||
|
|
# vanilla web server designed for testing multi-origin applications
|
||
|
|
# by serving up content on 127.0.0.1:xxxx instead of localhost:yyyy
|
||
|
|
|
||
|
|
from __future__ import print_function
|
||
|
|
|
||
|
|
import BaseHTTPServer, cgi, urlparse, socket
|
||
|
|
|
||
|
|
import types, os, sys, traceback, threading
|
||
|
|
|
||
|
|
class _HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||
|
|
|
||
|
|
def do_GET(self):
|
||
|
|
self.process_request()
|
||
|
|
|
||
|
|
def do_POST(self):
|
||
|
|
self.process_request()
|
||
|
|
|
||
|
|
def process_request(self):
|
||
|
|
"""
|
||
|
|
parse any URL or FORM arguments and process the request
|
||
|
|
"""
|
||
|
|
# verify that the request is coming from localhost
|
||
|
|
try:
|
||
|
|
host, port = self.client_address
|
||
|
|
if host != '127.0.0.1':
|
||
|
|
self.send_error(403,
|
||
|
|
"Only localhost requests are allowed (not: %s)"
|
||
|
|
% host)
|
||
|
|
else:
|
||
|
|
self.callback = None
|
||
|
|
self.parse_args()
|
||
|
|
self.send_doc()
|
||
|
|
except socket.error:
|
||
|
|
pass
|
||
|
|
|
||
|
|
def parse_args(self):
|
||
|
|
if (self.command == "POST"):
|
||
|
|
self.fs = cgi.FieldStorage(fp=self.rfile, headers=self.headers,
|
||
|
|
environ = {'REQUEST_METHOD':'POST'},
|
||
|
|
keep_blank_values = 1)
|
||
|
|
self.urlpath = self.path
|
||
|
|
elif (self.command == "GET"):
|
||
|
|
scheme,netloc,path,params,qs,fragment = urlparse.urlparse(self.path)
|
||
|
|
self.fs = cgi.FieldStorage(environ = {'REQUEST_METHOD':'GET',
|
||
|
|
'QUERY_STRING':qs},
|
||
|
|
keep_blank_values = 1)
|
||
|
|
self.urlpath = path
|
||
|
|
else:
|
||
|
|
self.fs = None
|
||
|
|
|
||
|
|
def send_doc(self):
|
||
|
|
"""
|
||
|
|
send a document (file) in the current directory or any sub-directory
|
||
|
|
"""
|
||
|
|
path_list = self.path.split('/')[1:]
|
||
|
|
if '..' in path_list: # prevent access to parent directories
|
||
|
|
self.send_error(404,"Illegal path.")
|
||
|
|
self.wfile.write(": %s" % self.path)
|
||
|
|
elif self.server.root == None:
|
||
|
|
self.send_error(404,"No content root specified.")
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
full_path = os.path.join(*[self.server.root] +
|
||
|
|
list(path_list))
|
||
|
|
print(full_path)
|
||
|
|
if os.path.isdir(full_path):
|
||
|
|
full_path = full_path + "/index.html"
|
||
|
|
fp = open(full_path,"rb")
|
||
|
|
self.send_ok(self.guess_mime(full_path))
|
||
|
|
self.wfile.write(fp.read())
|
||
|
|
fp.close()
|
||
|
|
except:
|
||
|
|
self.send_error(404,"Unable to locate document.")
|
||
|
|
self.wfile.write(": %s" % self.path)
|
||
|
|
self.wfile.write(str(sys.exc_info())) # exc_info() is thread safe
|
||
|
|
# self.wfile.write(sys.exc_value) # exc_value is not thread safe
|
||
|
|
|
||
|
|
def guess_mime(self,path):
|
||
|
|
"""
|
||
|
|
guess the mime type based on the file extension
|
||
|
|
"""
|
||
|
|
if path.endswith('.html'):
|
||
|
|
return 'text/html'
|
||
|
|
elif path.endswith('.js'):
|
||
|
|
return 'application/x-javascript'
|
||
|
|
elif path.endswith('.jpg'):
|
||
|
|
return 'image/jpeg'
|
||
|
|
elif path.endswith('.png'):
|
||
|
|
return 'image/png'
|
||
|
|
elif path.endswith('.gif'):
|
||
|
|
return 'image/gif'
|
||
|
|
elif path.endswith('.sdf'):
|
||
|
|
return 'chemical/x-mdl-sdfile'
|
||
|
|
elif path.endswith('.mol'):
|
||
|
|
return 'chemical/x-mdl-molfile'
|
||
|
|
elif path.endswith('.pwg'):
|
||
|
|
return 'application/x-pymol'
|
||
|
|
else:
|
||
|
|
return 'text/plain'
|
||
|
|
|
||
|
|
def send_error(self,errcode,errmsg):
|
||
|
|
try:
|
||
|
|
self.send_response(errcode)
|
||
|
|
self.send_header('Content-type', 'text/plain')
|
||
|
|
self.end_headers()
|
||
|
|
self.wfile.write("HTTPd-Error: "+errmsg+"\n")
|
||
|
|
except:
|
||
|
|
# right now we're swallowing any/all exceptions
|
||
|
|
# (e.g. Broken Pipe)
|
||
|
|
pass
|
||
|
|
|
||
|
|
def send_ok(self, mime='text/html'):
|
||
|
|
self.send_response(200)
|
||
|
|
self.send_header('Content-type', mime)
|
||
|
|
self.send_header('Pragma','no-cache')
|
||
|
|
self.send_header('Cache-Control','no-cache, must-revalidate')
|
||
|
|
self.send_header('Expires','Sat, 10 Jan 2008 01:00:00 GMT')
|
||
|
|
self.end_headers()
|
||
|
|
|
||
|
|
def echo_args(self):
|
||
|
|
"""
|
||
|
|
for debugging requests
|
||
|
|
"""
|
||
|
|
self.wfile.write("%s\n" % self.command)
|
||
|
|
if (self.fs):
|
||
|
|
for k in self.fs.keys():
|
||
|
|
self.wfile.write("%s = " % k)
|
||
|
|
# key can have multiple values, as with checkboxes,
|
||
|
|
# but also arbitrarily
|
||
|
|
if (isinstance(self.fs[k], types.ListType)):
|
||
|
|
self.wfile.write("%s\n" % self.fs.getlist(k))
|
||
|
|
else:
|
||
|
|
# key can be uploaded file
|
||
|
|
if (self.fs[k].filename):
|
||
|
|
self.wfile.write("%s\n" % self.fs[k].filename)
|
||
|
|
fp = self.fs[k].file
|
||
|
|
#self.wfile.write("FILE %s" % cgi.escape(repr(fp)))
|
||
|
|
#self.wfile.write("%s\n" % fp.name)
|
||
|
|
# fails for StringIO instances
|
||
|
|
self.wfile.write("%s\n" % repr(fp))
|
||
|
|
# two ways to get file contents
|
||
|
|
#file_contents = self.fs.getvalue(k)
|
||
|
|
#file_contents = fp.read()
|
||
|
|
#self.wfile.write("%s" % file_contents)
|
||
|
|
else:
|
||
|
|
#plain-old key/value
|
||
|
|
self.wfile.write("%s\n" % self.fs.getvalue(k))
|
||
|
|
else:
|
||
|
|
self.wfile.write("No args\n")
|
||
|
|
|
||
|
|
class PlainHttpd:
|
||
|
|
|
||
|
|
def __init__(self, port=0, root=None):
|
||
|
|
self.port = int(port)
|
||
|
|
self.stop_event = threading.Event()
|
||
|
|
self.stop_event.set()
|
||
|
|
self.root = root
|
||
|
|
|
||
|
|
self.server = BaseHTTPServer.HTTPServer(('', self.port),
|
||
|
|
_HTTPRequestHandler)
|
||
|
|
if self.port == 0:
|
||
|
|
self.port = self.server.socket.getsockname()[1]
|
||
|
|
|
||
|
|
self.server.root = self.root
|
||
|
|
|
||
|
|
def _server_thread(self):
|
||
|
|
while not self.stop_event.isSet():
|
||
|
|
self.server.handle_request()
|
||
|
|
|
||
|
|
def start(self): # spawn thread
|
||
|
|
print(" HTTPd: serving requests on http://127.0.0.1:%d" % self.port)
|
||
|
|
t = threading.Thread(target=self._server_thread)
|
||
|
|
t.setDaemon(1)
|
||
|
|
self.stop_event.clear()
|
||
|
|
t.start()
|
||
|
|
|
||
|
|
def stop(self):
|
||
|
|
if not self.stop_event.isSet():
|
||
|
|
self.stop_event.set()
|
||
|
|
try: # create a request in order to release the handler
|
||
|
|
import urllib
|
||
|
|
urllib.urlopen("http://localhost:%d" % self.port)
|
||
|
|
except:
|
||
|
|
pass
|
||
|
|
self.server.socket.close()
|
||
|
|
|
||
|
|
def main():
|
||
|
|
|
||
|
|
import os
|
||
|
|
|
||
|
|
# initialize the server, with current local working directory as root
|
||
|
|
|
||
|
|
server = PlainHttpd(0, ".")
|
||
|
|
|
||
|
|
# get a dynamically assigned port number
|
||
|
|
|
||
|
|
port = server.port
|
||
|
|
|
||
|
|
# start handling requests
|
||
|
|
|
||
|
|
server.start()
|
||
|
|
|
||
|
|
# now launch a browser pointing at our server
|
||
|
|
|
||
|
|
import webbrowser
|
||
|
|
webbrowser.open("http://127.0.0.1:%d"%port)
|
||
|
|
|
||
|
|
if __name__ in [ '__main__', 'pymol' ]:
|
||
|
|
|
||
|
|
# intended to be launched with normal Python or
|
||
|
|
# pymol -qc justhttpd.py
|
||
|
|
|
||
|
|
main()
|
||
|
|
import time
|
||
|
|
while 1:
|
||
|
|
time.sleep(1)
|
||
|
|
|