source: trunk/packages/xen-common/xen-common/tools/python/xen/web/httpserver.py @ 34

Last change on this file since 34 was 34, checked in by hartmans, 18 years ago

Add xen and xen-common

File size: 10.5 KB
Line 
1#============================================================================
2# This library is free software; you can redistribute it and/or
3# modify it under the terms of version 2.1 of the GNU Lesser General Public
4# License as published by the Free Software Foundation.
5#
6# This library is distributed in the hope that it will be useful,
7# but WITHOUT ANY WARRANTY; without even the implied warranty of
8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
9# Lesser General Public License for more details.
10#
11# You should have received a copy of the GNU Lesser General Public
12# License along with this library; if not, write to the Free Software
13# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
14#============================================================================
15# Copyright (C) 2005 Mike Wray <mike.wray@hp.com>
16# Copyright (C) 2006 XenSource Ltd.
17#============================================================================
18
19import threading
20
21import string
22import socket
23import types
24from urllib import quote, unquote
25import os
26import os.path
27import fcntl
28
29from xen.xend import sxp
30from xen.xend.Args import ArgError
31from xen.xend.XendError import XendError
32
33import http
34import unix
35from resource import Resource, ErrorPage
36from SrvDir import SrvDir
37
38class ThreadRequest:
39    """A request to complete processing using a thread.
40    """
41   
42    def __init__(self, processor, req, fn, args, kwds):
43        self.processor = processor
44        self.req = req
45        self.fn = fn
46        self.args = args
47        self.kwds = kwds
48       
49    def run(self):
50        self.processor.setInThread()
51        thread = threading.Thread(target=self.main)
52        thread.setDaemon(True)
53        thread.start()
54
55    def call(self):
56        try:
57            self.fn(*self.args, **self.kwds)
58        except SystemExit:
59            raise
60        except Exception, ex:
61            self.req.resultErr(ex)
62        self.req.finish()
63
64    def main(self):
65        self.call()
66        self.processor.process()
67       
68
69class RequestProcessor:
70    """Processor for requests on a connection to an http server.
71    Requests are executed synchonously unless they ask for a thread by returning
72    a ThreadRequest.
73    """
74
75    done = False
76
77    inThread = False
78
79    def __init__(self, server, sock, addr):
80        self.server = server
81        self.sock = sock
82        self.srd = sock.makefile('rb')
83        self.srw = sock.makefile('wb')
84        self.srvaddr = server.getServerAddr()
85
86    def isInThread(self):
87        return self.inThread
88
89    def setInThread(self):
90        self.inThread = True
91
92    def getServer(self):
93        return self.server
94
95    def getRequest(self):
96        return HttpServerRequest(self, self.srvaddr, self.srd, self.srw)
97
98    def close(self):
99        try:
100            self.sock.close()
101        except:
102            pass
103
104    def finish(self):
105        self.done = True
106        self.close()
107
108    def process(self):
109        while not self.done:
110            req = self.getRequest()
111            res = req.process()
112            if isinstance(res, ThreadRequest):
113                if self.isInThread():
114                    res.call()
115                else:
116                    res.run()
117                    break
118            else:
119                req.finish()
120                                       
121class HttpServerRequest(http.HttpRequest):
122    """A single request to an http server.
123    """
124
125    def __init__(self, processor, addr, srd, srw):
126        self.processor = processor
127        self.prepath = ''
128        http.HttpRequest.__init__(self, addr, srd, srw)
129
130    def getServer(self):
131        return self.processor.getServer()
132
133    def process(self):
134        """Process the request. If the return value is a ThreadRequest
135        it is evaluated in a thread.
136        """
137        try:
138            self.prepath = []
139            self.postpath = map(unquote, string.split(self.request_path[1:], '/'))
140            resource = self.getResource()
141            return self.render(resource)
142        except SystemExit:
143            raise
144        except Exception, ex:
145            self.processError(ex)
146
147    def processError(self, ex):
148        import traceback; traceback.print_exc()
149        self.sendError(http.INTERNAL_SERVER_ERROR, msg=str(ex))
150        self.setCloseConnection('close')
151
152    def finish(self):
153        self.sendResponse()
154        if self.close_connection:
155            self.processor.finish()
156
157    def prePathURL(self):
158        url_host = self.getRequestHostname()
159        port = self.getPort()
160        if self.isSecure():
161            url_proto = "https"
162            default_port = 443
163        else:
164            url_proto = "http"
165            default_port = 80
166        if port != default_port:
167            url_host += (':%d' % port)
168        url_path = quote(string.join(self.prepath, '/'))
169        return ('%s://%s/%s' % (url_proto, url_host, url_path))
170
171    def getResource(self):
172        return self.getServer().getResource(self)
173
174    def render(self, resource):
175        val = None
176        if resource is None:
177            self.sendError(http.NOT_FOUND)
178        else:
179            try:
180                while True:
181                    val = resource.render(self)
182                    if not isinstance(val, Resource):
183                        break
184                val = self.result(val)
185            except SystemExit:
186                raise
187            except Exception, ex:
188                self.resultErr(ex)
189        return val
190
191    def threadRequest(self, _fn, *_args, **_kwds):
192        """Create a request to finish request processing in a thread.
193        Use this to create a ThreadRequest to return from rendering a
194        resource if you need a thread to complete processing.
195        """
196        return ThreadRequest(self.processor, self, _fn, _args, _kwds)
197           
198    def result(self, val):
199        if isinstance(val, Exception):
200            return self.resultErr(val)
201        else:
202            return self.resultVal(val)
203
204    def resultVal(self, val):
205        """Callback to complete the request.
206
207        @param val: the value
208        """
209        if val is None:
210            return val
211        elif isinstance(val, ThreadRequest):
212            return val
213        elif self.useSxp():
214            self.setHeader("Content-Type", sxp.mime_type)
215            sxp.show(val, out=self)
216        else:
217            self.write('<html><head></head><body>')
218            self.printPath()
219            if isinstance(val, types.ListType):
220                self.write('<code><pre>')
221                PrettyPrint.prettyprint(val, out=self)
222                self.write('</pre></code>')
223            else:
224                self.write(str(val))
225            self.write('</body></html>')
226        return None
227
228    def resultErr(self, err):
229        """Error callback to complete a request.
230
231        @param err: the error
232        """
233        if not isinstance(err, (ArgError, sxp.ParseError, XendError)):
234            raise
235        #log.exception("op=%s: %s", op, str(err))
236        if self.useSxp():
237            self.setHeader("Content-Type", sxp.mime_type)
238            sxp.show(['xend.err', str(err)], out=self)
239        else:
240            self.setHeader("Content-Type", "text/plain")
241            self.write('Error ')
242            self.write(': ')
243            self.write(str(err))
244        return None
245
246    def useSxp(self):
247        """Determine whether to send an SXP response to a request.
248        Uses SXP if there is no User-Agent, no Accept, or application/sxp is in Accept.
249
250        returns 1 for SXP, 0 otherwise
251        """
252        ok = 0
253        user_agent = self.getHeader('User-Agent')
254        accept = self.getHeader('Accept')
255        if (not user_agent) or (not accept) or (accept.find(sxp.mime_type) >= 0):
256            ok = 1
257        return ok
258
259    def printPath(self):
260        pathlist = [x for x in self.prepath if x != '' ]
261        s = "/"
262        self.write('<h1><a href="/">/</a>')
263        for x in pathlist:
264            s += x + "/"
265            self.write(' <a href="%s">%s</a>/' % (s, x))
266        self.write("</h1>")
267
268class HttpServerClient:
269
270    def __init__(self, server, sock, addr):
271        self.server = server
272        self.sock = sock
273        self.addr = addr
274
275    def process(self):
276        thread = threading.Thread(target=self.doProcess)
277        thread.setDaemon(True)
278        thread.start()
279
280    def doProcess(self):
281        try:
282            rp = RequestProcessor(self.server, self.sock, self.addr)
283            rp.process()
284        except SystemExit:
285            raise
286        except Exception, ex:
287            print 'HttpServer>processRequest> exception: ', ex
288            try:
289                self.sock.close()
290            except:
291                pass
292
293class HttpServer:
294
295    backlog = 5
296
297    def __init__(self, root, interface, port=8080):
298        self.root = root
299        self.interface = interface
300        self.port = port
301        # ready indicates when we are ready to begin accept connections
302        # it should be set after a successful bind
303        self.ready = False
304        self.closed = False
305
306    def run(self):
307        self.bind()
308        self.listen()
309        self.ready = True
310
311        while not self.closed:
312            (sock, addr) = self.accept()
313            cl = HttpServerClient(self, sock, addr)
314            cl.process()
315
316    def stop(self):
317        self.close()
318
319    def bind(self):
320        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
321        flags = fcntl.fcntl(self.socket.fileno(), fcntl.F_GETFD)
322        flags |= fcntl.FD_CLOEXEC
323        fcntl.fcntl(self.socket.fileno(), fcntl.F_SETFD, flags)
324        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
325        self.socket.bind((self.interface, self.port))
326
327    def listen(self):
328        self.socket.listen(self.backlog)
329
330    def accept(self):
331        return self.socket.accept()
332
333    def close(self):
334        self.closed = True
335        self.ready = False
336        # shutdown socket explicitly to allow reuse
337        try:
338            self.socket.shutdown(2)
339        except socket.error:
340            pass
341
342        try:
343            self.socket.close()
344        except socket.error:
345            pass
346
347    def getServerAddr(self):
348        return (socket.gethostname(), self.port)
349
350    def getResource(self, req):
351        return self.root.getRequestResource(req)
352
353    def shutdown(self):
354        self.close()
355
356
357class UnixHttpServer(HttpServer):
358
359    def __init__(self, root, path):
360        HttpServer.__init__(self, root, 'localhost')
361        self.path = path
362       
363    def bind(self):
364        self.socket = unix.bind(self.path)
365        flags = fcntl.fcntl(self.socket.fileno(), fcntl.F_GETFD)
366        flags |= fcntl.FD_CLOEXEC
367        fcntl.fcntl(self.socket.fileno(), fcntl.F_SETFD, flags)
Note: See TracBrowser for help on using the repository browser.