pymolhttpd.py (pymol-v1.8.6.0.tar.bz2) | : | pymolhttpd.py (pymol-v2.1.0.tar.bz2) | ||
---|---|---|---|---|
# Copyright (C) Schrodinger, LLC. | # Copyright (C) Schrodinger, LLC. | |||
# All Rights Reserved | # All Rights Reserved | |||
# | # | |||
# For more information, see LICENSE in PyMOL's home directory. | # For more information, see LICENSE in PyMOL's home directory. | |||
# | # | |||
# pymolhttpd.py | # pymolhttpd.py | |||
# | # | |||
# web server interface for controlling PyMOL | # web server interface for controlling PyMOL | |||
from __future__ import print_function | from __future__ import print_function | |||
from __future__ import absolute_import | ||||
# we make extensive use of Python's build-in in web infrastructure | # we make extensive use of Python's build-in in web infrastructure | |||
import BaseHTTPServer, cgi, urlparse | import sys | |||
import StringIO, socket | ||||
_PY3 = sys.version_info[0] > 2 | ||||
if _PY3: | ||||
import http.server as BaseHTTPServer | ||||
import io as StringIO | ||||
import urllib.parse as urlparse | ||||
from urllib.request import urlopen | ||||
else: | ||||
import BaseHTTPServer | ||||
import StringIO | ||||
import urlparse | ||||
from urllib import urlopen | ||||
import cgi | ||||
import socket | ||||
# we also rely upon Python's json infrastructure | # we also rely upon Python's json infrastructure | |||
try: | try: | |||
import simplejson as json | import simplejson as json | |||
except: | except: | |||
import json | import json | |||
# standard Python dependencies | # standard Python dependencies | |||
skipping to change at line 46 | skipping to change at line 62 | |||
_json_mime_types = [ 'text/json', 'application/json' ] | _json_mime_types = [ 'text/json', 'application/json' ] | |||
class _PymolHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): | class _PymolHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): | |||
# for now, we're using a single-threaded server | # for now, we're using a single-threaded server | |||
# our actual HTTP server class is private for the time being | # our actual HTTP server class is private for the time being | |||
# if we need to, then we'll change this | # if we need to, then we'll change this | |||
def wfile_write(self, s): | ||||
if _PY3 and not isinstance(s, bytes): | ||||
s = s.encode('utf-8') | ||||
self.wfile.write(s) | ||||
def do_GET(self): | def do_GET(self): | |||
self.process_request() | self.process_request() | |||
def do_POST(self): | def do_POST(self): | |||
self.process_request() | self.process_request() | |||
def log_message(self, format, *args): | def log_message(self, format, *args): | |||
if self.server.pymol_logging: | if self.server.pymol_logging: | |||
BaseHTTPServer.BaseHTTPRequestHandler.log_message(self,format, | BaseHTTPServer.BaseHTTPRequestHandler.log_message(self,format, | |||
*args) | *args) | |||
skipping to change at line 135 | skipping to change at line 156 | |||
self.echo_args(parts[2]) | self.echo_args(parts[2]) | |||
else: # simple file retrieval | else: # simple file retrieval | |||
self.send_doc() | self.send_doc() | |||
def pymol_getattr(self, attr): | def pymol_getattr(self, attr): | |||
""" | """ | |||
apply the repr method to the requested attr, but only for | apply the repr method to the requested attr, but only for | |||
allowed attributes - those stored in the session dictionary | allowed attributes - those stored in the session dictionary | |||
""" | """ | |||
key = '/getattr/' + attr; | key = '/getattr/' + attr; | |||
if self.session.has_key(key): | if key in self.session: | |||
try: | try: | |||
result = repr(self.session[key]) | result = repr(self.session[key]) | |||
self.send_json_result(result) | self.send_json_result(result) | |||
except: | except: | |||
self.send_error(500,"Unable to get attribute.") | self.send_error(500,"Unable to get attribute.") | |||
self.wfile.write(" %s\n" % attr) | self.wfile_write(" %s\n" % attr) | |||
traceback.print_exc(file=self.wfile) | traceback.print_exc(file=self.wfile) | |||
else: | else: | |||
self.send_error(404,"Not a recognized attribute") | self.send_error(404,"Not a recognized attribute") | |||
self.wfile.write(" %s is not a recognized attribute\n" % attr) | self.wfile_write(" %s is not a recognized attribute\n" % attr) | |||
def wrap_return(self, result, status="OK", indent=None): | def wrap_return(self, result, status="OK", indent=None): | |||
r = { 'status' : status, 'result' : result } | r = { 'status' : status, 'result' : result } | |||
if self.server.wrap_natives==1: | if self.server.wrap_natives==1: | |||
return json.dumps(r,indent) | return json.dumps(r, indent=indent) | |||
else: | else: | |||
return json.dumps(result,indent) | return json.dumps(result, indent=indent) | |||
def send_json_result(self, result): | def send_json_result(self, result): | |||
""" | """ | |||
send the mime header and result body. requests that came from | send the mime header and result body. requests that came from | |||
XMLHTTPRequest have specified they will accept (expect) json | XMLHTTPRequest have specified they will accept (expect) json | |||
formatted results. other requests will have come from | formatted results. other requests will have come from | |||
ordinary GET or POST requests via links or forms | ordinary GET or POST requests via links or forms | |||
""" | """ | |||
if self.callback != None: | if self.callback != None: | |||
self.send_resp_header(200,'text/javascript') | self.send_resp_header(200,'text/javascript') | |||
self.wfile.write("%s(%s)"%(self.callback,self.wrap_return(result))) | self.wfile_write("%s(%s)"%(self.callback,self.wrap_return(result))) | |||
else: | else: | |||
accept_mime = self.headers.getheader('Accept') | accept_mime = self.headers.get('Accept') | |||
if accept_mime in _json_mime_types: | if accept_mime in _json_mime_types: | |||
self.send_resp_header(200,accept_mime) | self.send_resp_header(200,accept_mime) | |||
self.wfile.write(self.wrap_return(result)) | self.wfile_write(self.wrap_return(result)) | |||
else: | else: | |||
self.send_resp_header(200,'text/html') | self.send_resp_header(200,'text/html') | |||
self.wfile.write("PyMOL's JSON response: <pre>") | self.wfile_write("PyMOL's JSON response: <pre>") | |||
self.wfile.write(self.wrap_return(result,indent=4)) | self.wfile_write(self.wrap_return(result,indent=4)) | |||
self.wfile.write("</pre>") | self.wfile_write("</pre>") | |||
def send_json_error(self, code, message): | def send_json_error(self, code, message): | |||
if self.callback != None: | if self.callback != None: | |||
self.send_resp_header(code,'text/javascript') | self.send_resp_header(code,'text/javascript') | |||
self.wfile.write("%s(%s)"%(self.callback,self.wrap_return(message,"E RROR"))) | self.wfile_write("%s(%s)"%(self.callback,self.wrap_return(message,"E RROR"))) | |||
else: | else: | |||
accept_mime = self.headers.getheader('Accept') | accept_mime = self.headers.get('Accept') | |||
if accept_mime in _json_mime_types: | if accept_mime in _json_mime_types: | |||
self.send_resp_header(code,accept_mime) | self.send_resp_header(code,accept_mime) | |||
self.wfile.write(self.wrap_return(message,"ERROR")) | self.wfile_write(self.wrap_return(message,"ERROR")) | |||
else: | else: | |||
self.send_resp_header(code,'text/html') | self.send_resp_header(code,'text/html') | |||
self.wfile.write("PyMOL's JSON response: <pre>") | self.wfile_write("PyMOL's JSON response: <pre>") | |||
self.wfile.write(self.wrap_return(message,"ERROR",indent=4)) | self.wfile_write(self.wrap_return(message,"ERROR",indent=4)) | |||
self.wfile.write("</pre>") | self.wfile_write("</pre>") | |||
def send_exception_json(self, code, message): | def send_exception_json(self, code, message): | |||
fp = StringIO.StringIO() | fp = StringIO.StringIO() | |||
traceback.print_exc(file=fp) | traceback.print_exc(file=fp) | |||
tb = fp.getvalue() | tb = fp.getvalue() | |||
message = message + tb.split('\n') | message = message + tb.split('\n') | |||
response = json.dumps(message) | response = json.dumps(message) | |||
if self.callback != None: | if self.callback != None: | |||
self.send_resp_header(code, 'text/javascript') | self.send_resp_header(code, 'text/javascript') | |||
self.wfile.write("%s(%s)"%(self.callback,response)) | self.wfile_write("%s(%s)"%(self.callback,response)) | |||
else: | else: | |||
accept_mime = self.headers.getheader('Accept') | accept_mime = self.headers.get('Accept') | |||
if accept_mime in _json_mime_types: | if accept_mime in _json_mime_types: | |||
self.send_resp_header(code,accept_mime) | self.send_resp_header(code,accept_mime) | |||
self.wfile.write(response) | self.wfile_write(response) | |||
else: | else: | |||
self.send_resp_header(code,'text/html') | self.send_resp_header(code,'text/html') | |||
self.wfile.write("PyMOL's JSON response: <pre>") | self.wfile_write("PyMOL's JSON response: <pre>") | |||
self.wfile.write(json.dumps(json.loads(response),indent=4)) | self.wfile_write(json.dumps(json.loads(response),indent=4)) | |||
self.wfile.write("</pre>") | self.wfile_write("</pre>") | |||
def pymol_apply(self,method): | def pymol_apply(self,method): | |||
""" | """ | |||
apply the appropriate method held in the session dictionary. | apply the appropriate method held in the session dictionary. | |||
supply the method arguements in the form of key/value | supply the method arguements in the form of key/value | |||
""" | """ | |||
args = None | args = None | |||
kwds = None | kwds = None | |||
query_kwds = {} | query_kwds = {} | |||
send_multi_result_list = False | send_multi_result_list = False | |||
skipping to change at line 244 | skipping to change at line 265 | |||
method = json.loads(self.fs.getfirst(k)) | method = json.loads(self.fs.getfirst(k)) | |||
elif k == '_args': # tentative, not in spec -- may disappear | elif k == '_args': # tentative, not in spec -- may disappear | |||
args = json.loads(self.fs.getfirst(k)) | args = json.loads(self.fs.getfirst(k)) | |||
elif k == '_kwds': # tentative, not in spec -- may disappear | elif k == '_kwds': # tentative, not in spec -- may disappear | |||
kwds = json.loads(self.fs.getfirst(k)) | kwds = json.loads(self.fs.getfirst(k)) | |||
# other underscore arguments are ignored (not passed on) | # other underscore arguments are ignored (not passed on) | |||
elif k[0:1] != '_': | elif k[0:1] != '_': | |||
query_kwds[k] = self.fs.getfirst(k) | query_kwds[k] = self.fs.getfirst(k) | |||
blocks = [] | blocks = [] | |||
if isinstance(method,types.StringType): | if isinstance(method, str): | |||
# method is merely a string | # method is merely a string | |||
if kwds == None: | if kwds == None: | |||
kwds = query_kwds | kwds = query_kwds | |||
if args == None: | if args == None: | |||
args = () | args = () | |||
if len(method): | if len(method): | |||
blocks = [ [ method, args, kwds ] ] | blocks = [ [ method, args, kwds ] ] | |||
elif isinstance(method,types.ListType) and len(method): | elif isinstance(method, list) and len(method): | |||
# method is a list | # method is a list | |||
if not isinstance(method[0],types.ListType): | if not isinstance(method[0], list): | |||
blocks = [ method ] # contains just [name, args, kwds] | blocks = [ method ] # contains just [name, args, kwds] | |||
else: | else: | |||
blocks = method | blocks = method | |||
# contains [ [name, arg, kwds], [name, args, kwds], ... ] | # contains [ [name, arg, kwds], [name, args, kwds], ... ] | |||
send_multi_result_list = False # only return final result | send_multi_result_list = False # only return final result | |||
else: | else: | |||
self.send_json_error(500,[ "Unable to apply method:", str(method)]) | self.send_json_error(500,[ "Unable to apply method:", str(method)]) | |||
return | return | |||
result = [] | result = [] | |||
skipping to change at line 298 | skipping to change at line 319 | |||
"Args: " + str(args) , | "Args: " + str(args) , | |||
"Kwds: " + str(kwds)]) | "Kwds: " + str(kwds)]) | |||
return | return | |||
else: | else: | |||
self.send_json_error(500,[ "Method not found:", | self.send_json_error(500,[ "Method not found:", | |||
str(block) ]) | str(block) ]) | |||
return | return | |||
if block[0] == '_quit': # special quit behavior | if block[0] == '_quit': # special quit behavior | |||
self.send_resp_header() | self.send_resp_header() | |||
self.wfile.write("<html>") | self.wfile_write("<html>") | |||
href = None | href = None | |||
if kwds.has_key("href"): | if "href" in kwds: | |||
href = str(kwds['href']) | href = str(kwds['href']) | |||
elif len(args): | elif len(args): | |||
href = str(args[1]) | href = str(args[1]) | |||
if href == None: | if href == None: | |||
self.wfile.write("<body>") | self.wfile_write("<body>") | |||
elif not len(href): # simply | elif not len(href): # simply | |||
self.wfile.write("<body onload=\"window.close()\">") | self.wfile_write("<body onload=\"window.close()\">") | |||
else: | else: | |||
self.wfile.write( | self.wfile_write( | |||
"<body onload=\"document.location.replace('"+ | "<body onload=\"document.location.replace('"+ | |||
kwds['href']+"')\">") | kwds['href']+"')\">") | |||
self.wfile.write("<p>PyMOL-HTTPd: Shutting down...</p>") | self.wfile_write("<p>PyMOL-HTTPd: Shutting down...</p>") | |||
self.wfile.write("<p><i>Please close this window.</i></p>") | self.wfile_write("<p><i>Please close this window.</i></p>") | |||
self.wfile.write("</body></html>") | self.wfile_write("</body></html>") | |||
self.wfile.flush() | self.wfile.flush() | |||
self.server.pymol_cmd.quit() | self.server.pymol_cmd.quit() | |||
return | return | |||
if send_multi_result_list: | if send_multi_result_list: | |||
self.send_json_result(result) | self.send_json_result(result) | |||
elif len(result): | elif len(result): | |||
self.send_json_result(result[-1]) | self.send_json_result(result[-1]) | |||
else: | else: | |||
self.send_json_result(None) | self.send_json_result(None) | |||
return | return | |||
def send_doc(self): | def send_doc(self): | |||
""" | """ | |||
send a document (file) in the current directory or any sub-directory | send a document (file) in the current directory or any sub-directory | |||
""" | """ | |||
path_list = self.path.split('/')[1:] | path_list = self.path.split('/')[1:] | |||
if '..' in path_list: # prevent access to parent directories | if '..' in path_list: # prevent access to parent directories | |||
self.send_error(404,"Illegal path.") | self.send_error(404,"Illegal path.") | |||
self.wfile.write(": %s" % self.path) | self.wfile_write(": %s" % self.path) | |||
elif self.server.pymol_root == None: | elif self.server.pymol_root == None: | |||
self.send_error(404,"No content root specified.") | self.send_error(404,"No content root specified.") | |||
else: | else: | |||
try: | try: | |||
full_path = os.path.join(*[self.server.pymol_root] + | full_path = os.path.join(*[self.server.pymol_root] + | |||
list(path_list)) | list(path_list)) | |||
if os.path.isdir(full_path): | if os.path.isdir(full_path): | |||
full_path = full_path + "/index.html" | full_path = full_path + "/index.html" | |||
fp = open(full_path,"rb") | fp = open(full_path,"rb") | |||
self.send_resp_header(200,self.guess_mime(full_path)) | self.send_resp_header(200,self.guess_mime(full_path)) | |||
self.wfile.write(fp.read()) | self.wfile_write(fp.read()) | |||
fp.close() | fp.close() | |||
except: | except: | |||
self.send_error(404,"Unable to locate document.") | self.send_error(404,"Unable to locate document.") | |||
self.wfile.write(": %s" % self.path) | self.wfile_write(": %s" % self.path) | |||
self.wfile.write(str(sys.exc_info())) | self.wfile_write(str(sys.exc_info())) | |||
# exc_info() is thread safe | # exc_info() is thread safe | |||
# self.wfile.write(sys.exc_value) # exc_value not thread safe | # self.wfile.write(sys.exc_value) # exc_value not thread safe | |||
def guess_mime(self,path): | def guess_mime(self,path): | |||
""" | """ | |||
guess the mime type based on the file extension | guess the mime type based on the file extension | |||
""" | """ | |||
if path.endswith('.html'): | if path.endswith('.html'): | |||
return 'text/html' | return 'text/html' | |||
elif path.endswith('.js'): | elif path.endswith('.js'): | |||
skipping to change at line 384 | skipping to change at line 405 | |||
else: | else: | |||
return 'text/plain' | return 'text/plain' | |||
def send_error(self,errcode,errmsg): | def send_error(self,errcode,errmsg): | |||
self.send_response(errcode) | self.send_response(errcode) | |||
self.send_header('Content-type', 'text/plain') | self.send_header('Content-type', 'text/plain') | |||
self.send_header('Pragma','no-cache') | self.send_header('Pragma','no-cache') | |||
self.send_header('Cache-Control','no-cache, must-revalidate') | self.send_header('Cache-Control','no-cache, must-revalidate') | |||
self.send_header('Expires','Sat, 10 Jan 2008 01:00:00 GMT') | self.send_header('Expires','Sat, 10 Jan 2008 01:00:00 GMT') | |||
self.end_headers() | self.end_headers() | |||
self.wfile.write("PyMOL-HTTPd-Error: "+errmsg+"\n") | self.wfile_write("PyMOL-HTTPd-Error: "+errmsg+"\n") | |||
def send_resp_header(self, code=200, mime='text/html'): | def send_resp_header(self, code=200, mime='text/html'): | |||
self.send_response(code) | self.send_response(code) | |||
self.send_header('Content-type', mime) | self.send_header('Content-type', mime) | |||
self.send_header('Pragma','no-cache') | self.send_header('Pragma','no-cache') | |||
self.send_header('Cache-Control','no-cache, must-revalidate') | self.send_header('Cache-Control','no-cache, must-revalidate') | |||
self.send_header('Expires','Sat, 10 Jan 2008 01:00:00 GMT') | self.send_header('Expires','Sat, 10 Jan 2008 01:00:00 GMT') | |||
self.end_headers() | self.end_headers() | |||
def echo_args(self): | def echo_args(self): | |||
""" | """ | |||
for debugging requests | for debugging requests | |||
""" | """ | |||
self.wfile.write("%s\n" % self.command) | self.wfile_write("%s\n" % self.command) | |||
if (self.fs): | if (self.fs): | |||
for k in self.fs.keys(): | for k in self.fs.keys(): | |||
self.wfile.write("%s = " % k) | self.wfile_write("%s = " % k) | |||
# key can have multiple values, as with checkboxes, | # key can have multiple values, as with checkboxes, | |||
# but also arbitrarily | # but also arbitrarily | |||
if (isinstance(self.fs[k], types.ListType)): | if (isinstance(self.fs[k], list)): | |||
self.wfile.write("%s\n" % self.fs.getlist(k)) | self.wfile_write("%s\n" % self.fs.getlist(k)) | |||
else: | else: | |||
# key can be uploaded file | # key can be uploaded file | |||
if (self.fs[k].filename): | if (self.fs[k].filename): | |||
self.wfile.write("%s\n" % self.fs[k].filename) | self.wfile_write("%s\n" % self.fs[k].filename) | |||
fp = self.fs[k].file | fp = self.fs[k].file | |||
#self.wfile.write("FILE %s" % cgi.escape(repr(fp))) | #self.wfile.write("FILE %s" % cgi.escape(repr(fp))) | |||
#self.wfile.write("%s\n" % fp.name) | #self.wfile.write("%s\n" % fp.name) | |||
# fails for StringIO instances | # fails for StringIO instances | |||
self.wfile.write("%s\n" % repr(fp)) | self.wfile_write("%s\n" % repr(fp)) | |||
# two ways to get file contents | # two ways to get file contents | |||
#file_contents = self.fs.getvalue(k) | #file_contents = self.fs.getvalue(k) | |||
#file_contents = fp.read() | #file_contents = fp.read() | |||
#self.wfile.write("%s" % file_contents) | #self.wfile.write("%s" % file_contents) | |||
else: | else: | |||
#plain-old key/value | #plain-old key/value | |||
self.wfile.write("%s\n" % self.fs.getvalue(k)) | self.wfile_write("%s\n" % self.fs.getvalue(k)) | |||
else: | else: | |||
self.wfile.write("No args\n") | self.wfile_write("No args\n") | |||
# this is the public class we're exposing to PyMOL consortium members | # this is the public class we're exposing to PyMOL consortium members | |||
class PymolHttpd: | class PymolHttpd: | |||
def __init__(self, port=8080, root=None, logging=1, wrap_natives=0, self_cmd =None): | def __init__(self, port=8080, root=None, logging=1, wrap_natives=0, self_cmd =None): | |||
if self_cmd == None: | if self_cmd == None: | |||
# fallback on the global singleton PyMOL API | # fallback on the global singleton PyMOL API | |||
try: | try: | |||
from pymol import cmd | from pymol import cmd | |||
skipping to change at line 487 | skipping to change at line 508 | |||
self.port ) | self.port ) | |||
t = threading.Thread(target=self._server_thread) | t = threading.Thread(target=self._server_thread) | |||
t.setDaemon(1) | t.setDaemon(1) | |||
self.stop_event.clear() | self.stop_event.clear() | |||
t.start() | t.start() | |||
def stop(self): | def stop(self): | |||
if not self.stop_event.isSet(): | if not self.stop_event.isSet(): | |||
self.stop_event.set() | self.stop_event.set() | |||
try: # create a request in order to release the handler | try: # create a request in order to release the handler | |||
import urllib | urlopen("http://localhost:%d" % self.port) | |||
urllib.urlopen("http://localhost:%d" % self.port) | ||||
except: | except: | |||
pass | pass | |||
self.server.socket.close() | self.server.socket.close() | |||
def quit(self): | def quit(self): | |||
self.stop_event.set() | self.stop_event.set() | |||
def expose(self, name, value): | def expose(self, name, value): | |||
''' | ''' | |||
exposes a Python method or symbol to the web services interface | exposes a Python method or symbol to the web services interface | |||
End of changes. 41 change blocks. | ||||
51 lines changed or deleted | 71 lines changed or added |