1 """
2 This file is part of web2py Web Framework (Copyrighted, 2007)
3 Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>
4 License: GPL v2
5 """
6
7 import cgi, cStringIO, Cookie, cPickle, os
8 import re, copy, sys, types, time, thread
9 import datetime, signal, socket, stat
10 import tempfile
11
12 from random import random
13 from storage import Storage, load_storage, save_storage
14 from restricted import RestrictedError
15 from languages import translator
16 from http import HTTP, redirect
17 from globals import Request, Response, Session
18 from cache import Cache
19 from compileapp import run_models_in, run_controller_in, run_view_in
20 from fileutils import listdir, copystream
21 from contenttype import contenttype
22 from sql import SQLDB, SQLField
23 from sqlhtml import SQLFORM, SQLTABLE
24 from rewrite import rewrite
25 from xmlrpc import handler
26 from streamer import streamer
27 import html
28 import validators
29 import myregex
30 import wsgiserver
31 import portalocker
32
33 import contrib.simplejson
34 import contrib.pyrtf
35 import contrib.rss2
36 import contrib.feedparser
37 import contrib.markdown
38 import contrib.memcache
39
40 __all__=['wsgibase', 'save_password', 'appfactory', 'HttpServer']
41
42
43
44 regex_url=re.compile('(?:^$)|(?:^(\w+/?){0,3}$)|(?:^(\w+/){3}\w+(/?\.?[\w\-\.]+)*/?$)|(?:^(\w+)/static(/\.?[\w\-\.]+)*/?$)')
45
46 regex_session_id=re.compile('([0-9]+\.)+[0-9]+')
47
48 error_message='<html><body><h1>Invalid request</h1></body></html>'
49 error_message_ticket='<html><body><h1>Internal error</h1>Ticket issued: <a href="/admin/default/ticket/%s" target="_blank">%s</a></body></html>'
50
51 working_folder=os.getcwd()
52
54 return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(seconds))
55
57 """
58 this function is used to generate a dynmaic page.
59 It first runs all models, then runs the function in the controller,
60 and then tries to render the output using a view/template.
61 this function must run from the [applciation] folder.
62 A typical examples would be the call to the url
63 /[applicaiton]/[controller]/[function] that would result in a call
64 to [function]() in applications/[application]/[controller].py
65 renedred by applications/[application]/[controller]/[view].html
66 """
67
68
69
70 environment={}
71 for key in html.__all__: environment[key]=eval('html.%s' % key)
72 for key in validators.__all__: environment[key]=eval('validators.%s' % key)
73 environment['T']=translator(request)
74 environment['HTTP']=HTTP
75 environment['redirect']=redirect
76 environment['request']=request
77 environment['response']=response
78 environment['session']=session
79 environment['cache']=Cache(request)
80 environment['SQLDB']=SQLDB
81 SQLDB._set_thread_folder(os.path.join(request.folder,'databases'))
82 environment['SQLField']=SQLField
83 environment['SQLFORM']=SQLFORM
84 environment['SQLTABLE']=SQLTABLE
85
86 response.view='%s/%s.html' % (request.controller,request.function)
87
88 if session.flash: response.flash, session.flash=session.flash, None
89
90
91
92 run_models_in(environment)
93 response._view_environment=copy.copy(environment)
94 run_controller_in(request.controller,request.function,environment)
95 if not type(response.body) in [types.StringType, types.GeneratorType]:
96 for key,value in response._vars.items():
97 response._view_environment[key]=value
98 run_view_in(response._view_environment)
99 response.body=response.body.getvalue()
100 raise HTTP(200,response.body,**response.headers)
101
103 """
104 this is the gluon wsgi application. the furst function called when a page
105 is requested (static or dynamical). it can be called by paste.httpserver
106 or by apache mod_wsgi.
107 """
108
109 request=Request()
110 response=Response()
111 session=Session()
112 try:
113 try:
114 session_file=None
115 session_new=False
116
117
118
119 for key, value in environ.items():
120 request.env[key.lower().replace('.','_')]=value
121 if not request.env.web2py_path:
122 request.env.web2py_path=working_folder
123
124
125
126 if not request.env.path_info and request.env.request_uri:
127 request.env.path_info=request.env.request_uri
128 path=request.env.path_info[1:].replace('\\','/')
129 if not regex_url.match(path):
130 raise HTTP(400,error_message,web2py_error='invalid path')
131 items=path.split('/')
132
133
134
135
136 if len(items)>2 and items[1]=='static':
137 static_file=os.path.join(request.env.web2py_path,'applications',items[0],'static','/'.join(items[2:]))
138 if not os.access(static_file,os.R_OK):
139 raise HTTP(400,error_message,web2py_error='invalid application')
140 stat_file=os.stat(static_file)
141 mtime=RFC1123_DATETIME(stat_file[stat.ST_MTIME])
142 headers={'Content-Type':contenttype(static_file),
143 'Content-Length':stat_file[stat.ST_SIZE],
144 'Last-Modified':mtime}
145 if environ.get('HTTP_IF_MODIFIED_SINCE', '') == mtime:
146 raise HTTP(304)
147 else:
148 raise HTTP(200,streamer(open(static_file,'rb')),**headers)
149
150
151
152 if len(items) and items[-1]=='': del items[-1]
153 if len(items)==0: redirect('/init/default/index')
154 if len(items)==1: redirect('/%s/default/index' % items[0])
155 if len(items)==2: redirect('/%s/%s/index' % tuple(items))
156 if len(items)>3: items,request.args=items[:3],items[3:]
157 if request.args==None: request.args=[]
158 request.application=items[0]
159 request.controller=items[1]
160 request.function=items[2]
161 request.folder=os.path.join(request.env.web2py_path,'applications',request.application)+'/'
162
163
164
165 if not os.access(request.folder,os.F_OK):
166 if items==['init','default','index']:
167 redirect('/welcome/default/index')
168 raise HTTP(400,error_message,web2py_error='invalid application')
169
170
171
172 request.body=tempfile.TemporaryFile()
173 if request.env.content_length:
174 copystream(request.env.wsgi_input,request.body,
175 int(request.env.content_length))
176 if request.env.request_method in ['POST', 'BOTH']:
177 dpost=cgi.FieldStorage(fp=request.body,
178 environ=environ,keep_blank_values=1)
179 request.body.seek(0)
180 try: keys=dpost.keys()
181 except TypeError: keys=[]
182 for key in keys:
183 dpk=dpost[key]
184 if type(dpk)==types.ListType:
185 request.post_vars[key]=request.vars[key]=[x.value for x in dpk]
186 elif not dpk.filename:
187 request.post_vars[key]=request.vars[key]=dpk.value
188 else:
189 request.post_vars[key]=request.vars[key]=dpk
190 if request.env.request_method in ['GET', 'BOTH']:
191 dget=cgi.FieldStorage(environ=environ,keep_blank_values=1)
192 for key in dget.keys():
193 request.get_vars[key]=request.vars[key]=dget[key].value
194
195
196
197 request.cookies=Cookie.SimpleCookie()
198 response.cookies=Cookie.SimpleCookie()
199 if request.env.http_cookie:
200 request.cookies.load(request.env.http_cookie)
201
202
203
204 session_id_name='session_id_%s'%request.application
205 if request.cookies.has_key(session_id_name):
206 response.session_id=request.cookies[session_id_name].value
207 if regex_session_id.match(response.session_id):
208 session_filename=os.path.join(request.folder,'sessions',response.session_id)
209 else: response.session_id=None
210 if response.session_id:
211 try:
212 session_file=open(session_filename,'rb+')
213 portalocker.lock(session_file,portalocker.LOCK_EX)
214 session=Storage(cPickle.load(session_file))
215 session_file.seek(0)
216 except:
217 if session_file: portalocker.unlock(session_file)
218 response.session_id=None
219 if not response.session_id:
220 response.session_id=request.env.remote_addr+'.'+str(int(time.time()))+'.'+str(random())[2:]
221 session_filename=os.path.join(request.folder,'sessions',response.session_id)
222 session_new=True
223 response.cookies[session_id_name]=response.session_id
224 response.cookies[session_id_name]['path']="/"
225 response.session_id_name=session_id_name
226
227
228
229 if not items[1]=='static':
230 serve_controller(request,response,session)
231 except HTTP, http_response:
232
233
234
235 SQLDB.close_all_instances(SQLDB.commit)
236
237
238
239 if response.session_id:
240 http_response.headers['Set-Cookie']=[str(response.cookies[i])[11:] for i in response.cookies.keys()]
241 if session_new:
242 session_file=open(session_filename,'wb')
243 portalocker.lock(session_file,portalocker.LOCK_EX)
244 cPickle.dump(dict(session),session_file)
245
246
247
248 if session_file: portalocker.unlock(session_file)
249 return http_response.to(responder)
250 except RestrictedError, e:
251
252
253
254 SQLDB.close_all_instances(SQLDB.rollback)
255 ticket=e.log(request)
256
257 if session_file: portalocker.unlock(session_file)
258 return HTTP(200,error_message_ticket % (ticket,ticket),\
259 web2py_error='ticket %s'%ticket).to(responder)
260 except BaseException, exception:
261
262
263
264
265 try: SQLDB.close_all_instances(SQLDB.rollback)
266 except: pass
267 e=RestrictedError('Framework','','',locals())
268 try: ticket=e.log(request)
269 except:
270 ticket='unrecoverable'
271 print '*'*10,'intenral error traceback','*'*10
272 print e.traceback
273 print '*'*49
274
275 if session_file: portalocker.unlock(session_file)
276 return HTTP(200,error_message_ticket % (ticket,ticket),
277 web2py_error='ticket %s'%ticket).to(responder)
278
279 wsgibase,html.URL=rewrite(wsgibase,html.URL)
280
282 """
283 used by main() to save the password in the parameters.py file.
284 """
285 if password=='<recycle>': return
286 import gluon.validators
287 crypt=gluon.validators.CRYPT()
288 file=open('parameters_%i.py'%port,'w')
289 if len(password)>0: file.write('password="%s"\n' % crypt(password)[0])
290 else: file.write('password=None\n')
291 file.close()
292
294 def app_with_logging(environ, responder):
295 environ['web2py_path']=web2py_path
296 status_headers=[]
297 def responder2(s,h):
298 status_headers.append(s)
299 status_headers.append(h)
300 return responder(s,h)
301 time_in=time.time()
302 ret=wsgiapp(environ,responder2)
303 try:
304 line='%s, %s, %s, %s, %s, %s, %f\n' % (environ['REMOTE_ADDR'], datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'), environ['REQUEST_METHOD'],environ['PATH_INFO'].replace(',','%2C'),environ['SERVER_PROTOCOL'],status_headers[0][:3],time.time()-time_in)
305 if logfilename: open(logfilename,'a').write(line)
306 else: sys.stdout.write(line)
307 except: pass
308 return ret
309 return app_with_logging
310
312 - def __init__(self,ip='127.0.0.1',port=8000,password='',
313 pid_filename='httpserver.pid',
314 log_filename='httpserver.log',
315 ssl_certificate=None,
316 ssl_private_key=None,
317 numthreads=10,
318 server_name=None,
319 request_queue_size=5,
320 timeout=10,
321 shutdown_timeout=5,
322 path=working_folder):
323 """
324 starts the web server.
325 """
326 save_password(password,port)
327 self.pid_filename=pid_filename
328 if not server_name: server_name=socket.gethostname()
329 print 'starting web server...'
330 self.server=wsgiserver.CherryPyWSGIServer((ip, port),
331 appfactory(wsgibase,log_filename,web2py_path=path),
332 numthreads=int(numthreads), server_name=server_name,
333 request_queue_size=int(request_queue_size),
334 timeout=int(timeout),
335 shutdown_timeout=int(shutdown_timeout))
336 if not ssl_certificate or not ssl_private_key:
337 print 'SSL is off'
338 elif not wsgiserver.SSL:
339 print 'Error: OpenSSL libraries available. SSL is OFF'
340 elif not os.access(ssl_certificate,os.R_OK):
341 print 'Error: unable to open SSL certificate. SSL is OFF'
342 elif not os.access(ssl_private_key,os.R_OK):
343 print 'Error: unable to open SSL private key. SSL is OFF'
344 else:
345 self.server.ssl_certificate=ssl_certificate
346 self.server.ssl_private_key=ssl_private_key
347 print 'SSL is ON'
349 try: signal.signal(signal.SIGTERM,lambda a,b,s=self:s.stop())
350 except: pass
351 open(self.pid_filename,'w').write(str(os.getpid()))
352 self.server.start()
354 self.server.stop()
355 os.unlink(self.pid_filename)
356