forked from Kitware/tangelo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtangelo.py.in
196 lines (162 loc) · 7.2 KB
/
tangelo.py.in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import cherrypy
import grp
import imp
import json
import pwd
import StringIO
import sys
import traceback
import types
from cherrypy.lib.static import serve_file
from cherrypy.process.plugins import DropPrivileges
# This function defines the structure of a service response. Each service
# module should import this function from this package.
#
# The payload is contained in the 'result' field, while the 'error' field can
# indicate that something went wrong. Posibly more fields could be added in the
# future.
def empty_response():
return {'result' : None,
'error' : None}
def content_type(t=None):
r = cherrypy.response.headers['Content-type']
if t is not None:
cherrypy.response.headers['Content-type'] = t
return r
def log(msg):
cherrypy.log(msg)
# 'current_dir' is used by the CherryPy config file to set the root for static
# file service.
import os.path
current_dir = os.path.dirname(os.path.abspath(__file__))
# If we are the superuser, try to drop privileges (since we've bound to whatever
# port superuser privileges were needed for already).
if os.getuid() == 0:
# Set the username and groupname to drop privileges down to.
user = "@SERVER_USER@"
group = "@SERVER_GROUP@"
# If we're on windows, don't supply any username/groupname.
if user == "windows" and group == "windows":
DropPrivileges(cherrypy.engine).subscribe()
else:
try:
uid = pwd.getpwnam(user).pw_uid
except KeyError:
log("error: no such user '%s' to drop privileges to" % (user))
sys.exit(1)
try:
gid = grp.getgrnam(group).gr_gid
except KeyError:
log("error: no such group '%s' to drop privileges to" % (group))
sys.exit(1)
DropPrivileges(cherrypy.engine, uid=uid, gid=gid).subscribe()
def invoke_service(module, *pargs, **kwargs):
# TODO(choudhury): This method should attempt to load the named module, then invoke it
# with the given arguments. However, if the named module is "config" or
# something similar, the method should instead launch a special "config"
# app, which lists the available app modules, along with docstrings or
# similar. It should also allow the user to add/delete search paths for
# other modules.
content_type("text/plain")
# Construct a response container for reporting possible errors, as the
# service modules themselves do.
response = empty_response()
# Import the module.
try:
service = imp.load_source("service", module)
except IOError as e:
log("error: " + module)
response['error'] = "IOError: %s" % (e)
return json.dumps(response)
# Report an error if the Handler object has no go() method.
if 'run' not in dir(service):
response['error'] = "tangelo: error: no function `run()` defined in module '%s'" % (module)
return json.dumps(response)
# Call the module's run() method, passing it the positional and keyword args
# that came into this method.
try:
result = service.run(*pargs, **kwargs)
# Check the type of the result - if it's anything other than a string,
# assume it needs to be converted to JSON.
#
# This allows the services to return a Python object if they wish, or to
# perform custom serialization (such as for MongoDB results, etc.).
if not isinstance(result, types.StringTypes):
result = json.dumps(result)
return result
except Exception as e:
# Error message.
response['error'] = "tangelo: error: %s: %s" % (e.__class__.__name__, e.message)
log("Caught exception - %s: %s" % (e.__class__.__name__, e.message))
# Full Python traceback stack.
s = StringIO.StringIO()
traceback.print_exc(file=s)
if 'traceback' in response:
response['traceback'] += "\n" + s.getvalue()
else:
response['traceback'] = "\n" + s.getvalue()
# Serialize to JSON.
return json.dumps(response)
class Server(object):
@cherrypy.expose
def default(self, *path, **args):
# Convert the path argument into a list (from a tuple).
path = list(path)
# If there are no positional arguments, behave as though the root
# index.html was requested.
if len(path) == 0:
path = ["index.html"]
# Check the first character of the first path component. If it's a
# tilde, then assume the path points into a user's home directory.
if path[0][0] == "~":
# Only treat this component as a home directory if there is actually
# text following the tilde (rather than making the server serve
# files from the home directory of whatever user account it is using
# to run).
if len(path[0]) > 1:
# Expand the home directory, append the tangelo_html
# subdirectory, and then the tail of the original path.
path = os.path.expanduser(path[0]).split("/") + ["tangelo_html"] + path[1:]
else:
# TODO(choudhury): check a table of configured custom mappings to
# see if the first path component should be mapped to some other
# filesystem path.
# Reaching this point means the path is relative to the server's web
# root.
path = current_dir.split("/") + ["web"] + path
# Check for the word "service" in the path - this indicates the
# invocation of a web service placed somewhere besides the global
# services list.
try:
service = path.index("service")
# Make sure there is an actual service name after the "service" path
# component.
if len(path) == service + 1:
raise cherrypy.HTTPError(404, "Did you forget the service name?")
# Grab the portion of the path that names the service (appending the
# standard python file extension as well).
service_path = "/".join(path[:(service+2)]) + ".py"
# Grab the rest of the path list, as the positional arguments for
# the service invocation.
pargs = path[(service+2):]
# Invoke the service and return the result.
return invoke_service(service_path, *pargs, **args)
except ValueError:
pass
# Form a path name from the path components.
finalpath = "/".join(path)
# If the path represents a directory, first check if the URL is missing
# a trailing slash - if it is, redirect to a URL with the slash
# appended; otherwise, append "index.html" to the path spec and
# continue.
if os.path.isdir(finalpath):
if cherrypy.request.path_info[-1] != "/":
raise cherrypy.HTTPRedirect(cherrypy.request.path_info + "/")
else:
finalpath += "/index.html"
# If the home directory expansion above failed (or something else went
# wrong), then the filepath will not be an absolute path, and we bail.
if not os.path.isabs(finalpath):
raise cherrypy.HTTPError(404)
# Serve the file.
return serve_file(finalpath)