PageRenderTime 75ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/gluon/main.py

https://github.com/gokceneraslan/web2py
Python | 914 lines | 851 code | 24 blank | 39 comment | 9 complexity | 1526aba486e2277f0dfe0ed60acdf2b5 MD5 | raw file
Possible License(s): BSD-2-Clause, MIT, BSD-3-Clause
  1. #!/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. This file is part of the web2py Web Framework
  5. Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
  6. License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
  7. Contains:
  8. - wsgibase: the gluon wsgi application
  9. """
  10. if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
  11. import gc
  12. import cgi
  13. import cStringIO
  14. import Cookie
  15. import os
  16. import re
  17. import copy
  18. import sys
  19. import time
  20. import datetime
  21. import signal
  22. import socket
  23. import tempfile
  24. import random
  25. import string
  26. import urllib2
  27. try:
  28. import simplejson as sj #external installed library
  29. except:
  30. try:
  31. import json as sj #standard installed library
  32. except:
  33. import contrib.simplejson as sj #pure python library
  34. from thread import allocate_lock
  35. from fileutils import abspath, write_file, parse_version, copystream
  36. from settings import global_settings
  37. from admin import add_path_first, create_missing_folders, create_missing_app_folders
  38. from globals import current
  39. # Remarks:
  40. # calling script has inserted path to script directory into sys.path
  41. # applications_parent (path to applications/, site-packages/ etc)
  42. # defaults to that directory set sys.path to
  43. # ("", gluon_parent/site-packages, gluon_parent, ...)
  44. #
  45. # this is wrong:
  46. # web2py_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  47. # because we do not want the path to this file which may be Library.zip
  48. # gluon_parent is the directory containing gluon, web2py.py, logging.conf
  49. # and the handlers.
  50. # applications_parent (web2py_path) is the directory containing applications/
  51. # and routes.py
  52. # The two are identical unless web2py_path is changed via the web2py.py -f folder option
  53. # main.web2py_path is the same as applications_parent (for backward compatibility)
  54. web2py_path = global_settings.applications_parent # backward compatibility
  55. create_missing_folders()
  56. # set up logging for subsequent imports
  57. import logging
  58. import logging.config
  59. # This needed to prevent exception on Python 2.5:
  60. # NameError: name 'gluon' is not defined
  61. # See http://bugs.python.org/issue1436
  62. # attention!, the import Tkinter in messageboxhandler, changes locale ...
  63. import gluon.messageboxhandler
  64. logging.gluon = gluon
  65. # so we must restore it! Thanks ozancag
  66. import locale
  67. locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
  68. exists = os.path.exists
  69. pjoin = os.path.join
  70. logpath = abspath("logging.conf")
  71. if exists(logpath):
  72. logging.config.fileConfig(abspath("logging.conf"))
  73. else:
  74. logging.basicConfig()
  75. logger = logging.getLogger("web2py")
  76. from restricted import RestrictedError
  77. from http import HTTP, redirect
  78. from globals import Request, Response, Session
  79. from compileapp import build_environment, run_models_in, \
  80. run_controller_in, run_view_in
  81. from contenttype import contenttype
  82. from dal import BaseAdapter
  83. from settings import global_settings
  84. from validators import CRYPT
  85. from cache import CacheInRam
  86. from html import URL, xmlescape
  87. from utils import is_valid_ip_address, getipaddrinfo
  88. from rewrite import load, url_in, THREAD_LOCAL as rwthread, \
  89. try_rewrite_on_error, fixup_missing_path_info
  90. import newcron
  91. __all__ = ['wsgibase', 'save_password', 'appfactory', 'HttpServer']
  92. requests = 0 # gc timer
  93. # Security Checks: validate URL and session_id here,
  94. # accept_language is validated in languages
  95. # pattern used to validate client address
  96. regex_client = re.compile('[\w\-:]+(\.[\w\-]+)*\.?') # ## to account for IPV6
  97. try:
  98. version_info = open(pjoin(global_settings.gluon_parent, 'VERSION'), 'r')
  99. raw_version_string = version_info.read().split()[-1].strip()
  100. version_info.close()
  101. global_settings.web2py_version = raw_version_string
  102. web2py_version = global_settings.web2py_version
  103. except:
  104. raise RuntimeError("Cannot determine web2py version")
  105. try:
  106. import rocket
  107. except:
  108. if not global_settings.web2py_runtime_gae:
  109. logger.warn('unable to import Rocket')
  110. load()
  111. HTTPS_SCHEMES = set(('https', 'HTTPS'))
  112. def get_client(env):
  113. """
  114. guess the client address from the environment variables
  115. first tries 'http_x_forwarded_for', secondly 'remote_addr'
  116. if all fails, assume '127.0.0.1' or '::1' (running locally)
  117. """
  118. g = regex_client.search(env.get('http_x_forwarded_for', ''))
  119. client = (g.group() or '').split(',')[0] if g else None
  120. if client in (None, '', 'unknown'):
  121. g = regex_client.search(env.get('remote_addr', ''))
  122. if g:
  123. client = g.group()
  124. elif env.http_host.startswith('['): # IPv6
  125. client = '::1'
  126. else:
  127. client = '127.0.0.1' # IPv4
  128. if not is_valid_ip_address(client):
  129. raise HTTP(400, "Bad Request (request.client=%s)" % client)
  130. return client
  131. def copystream_progress(request, chunk_size=10 ** 5):
  132. """
  133. copies request.env.wsgi_input into request.body
  134. and stores progress upload status in cache_ram
  135. X-Progress-ID:length and X-Progress-ID:uploaded
  136. """
  137. env = request.env
  138. if not env.content_length:
  139. return cStringIO.StringIO()
  140. source = env.wsgi_input
  141. try:
  142. size = int(env.content_length)
  143. except ValueError:
  144. raise HTTP(400, "Invalid Content-Length header")
  145. try: # Android requires this
  146. dest = tempfile.NamedTemporaryFile()
  147. except NotImplementedError: # and GAE this
  148. dest = tempfile.TemporaryFile()
  149. if not 'X-Progress-ID' in request.vars:
  150. copystream(source, dest, size, chunk_size)
  151. return dest
  152. cache_key = 'X-Progress-ID:' + request.vars['X-Progress-ID']
  153. cache_ram = CacheInRam(request) # same as cache.ram because meta_storage
  154. cache_ram(cache_key + ':length', lambda: size, 0)
  155. cache_ram(cache_key + ':uploaded', lambda: 0, 0)
  156. while size > 0:
  157. if size < chunk_size:
  158. data = source.read(size)
  159. cache_ram.increment(cache_key + ':uploaded', size)
  160. else:
  161. data = source.read(chunk_size)
  162. cache_ram.increment(cache_key + ':uploaded', chunk_size)
  163. length = len(data)
  164. if length > size:
  165. (data, length) = (data[:size], size)
  166. size -= length
  167. if length == 0:
  168. break
  169. dest.write(data)
  170. if length < chunk_size:
  171. break
  172. dest.seek(0)
  173. cache_ram(cache_key + ':length', None)
  174. cache_ram(cache_key + ':uploaded', None)
  175. return dest
  176. def serve_controller(request, response, session):
  177. """
  178. this function is used to generate a dynamic page.
  179. It first runs all models, then runs the function in the controller,
  180. and then tries to render the output using a view/template.
  181. this function must run from the [application] folder.
  182. A typical example would be the call to the url
  183. /[application]/[controller]/[function] that would result in a call
  184. to [function]() in applications/[application]/[controller].py
  185. rendered by applications/[application]/views/[controller]/[function].html
  186. """
  187. # ##################################################
  188. # build environment for controller and view
  189. # ##################################################
  190. environment = build_environment(request, response, session)
  191. # set default view, controller can override it
  192. response.view = '%s/%s.%s' % (request.controller,
  193. request.function,
  194. request.extension)
  195. # also, make sure the flash is passed through
  196. # ##################################################
  197. # process models, controller and view (if required)
  198. # ##################################################
  199. run_models_in(environment)
  200. response._view_environment = copy.copy(environment)
  201. page = run_controller_in(request.controller, request.function, environment)
  202. if isinstance(page, dict):
  203. response._vars = page
  204. response._view_environment.update(page)
  205. run_view_in(response._view_environment)
  206. page = response.body.getvalue()
  207. # logic to garbage collect after exec, not always, once every 100 requests
  208. global requests
  209. requests = ('requests' in globals()) and (requests + 1) % 100 or 0
  210. if not requests:
  211. gc.collect()
  212. # end garbage collection logic
  213. # ##################################################
  214. # set default headers it not set
  215. # ##################################################
  216. default_headers = [
  217. ('Content-Type', contenttype('.' + request.extension)),
  218. ('Cache-Control',
  219. 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'),
  220. ('Expires', time.strftime('%a, %d %b %Y %H:%M:%S GMT',
  221. time.gmtime())),
  222. ('Pragma', 'no-cache')]
  223. for key, value in default_headers:
  224. response.headers.setdefault(key, value)
  225. raise HTTP(response.status, page, **response.headers)
  226. class LazyWSGI(object):
  227. def __init__(self, environ, request, response):
  228. self.wsgi_environ = environ
  229. self.request = request
  230. self.response = response
  231. @property
  232. def environ(self):
  233. if not hasattr(self,'_environ'):
  234. new_environ = self.wsgi_environ
  235. new_environ['wsgi.input'] = self.request.body
  236. new_environ['wsgi.version'] = 1
  237. self._environ = new_environ
  238. return self._environ
  239. def start_response(self,status='200', headers=[], exec_info=None):
  240. """
  241. in controller you can use::
  242. - request.wsgi.environ
  243. - request.wsgi.start_response
  244. to call third party WSGI applications
  245. """
  246. self.response.status = str(status).split(' ', 1)[0]
  247. self.response.headers = dict(headers)
  248. return lambda *args, **kargs: \
  249. self.response.write(escape=False, *args, **kargs)
  250. def middleware(self,*a):
  251. """
  252. In you controller use::
  253. @request.wsgi.middleware(middleware1, middleware2, ...)
  254. to decorate actions with WSGI middleware. actions must return strings.
  255. uses a simulated environment so it may have weird behavior in some cases
  256. """
  257. def middleware(f):
  258. def app(environ, start_response):
  259. data = f()
  260. start_response(self.response.status,
  261. self.response.headers.items())
  262. if isinstance(data, list):
  263. return data
  264. return [data]
  265. for item in middleware_apps:
  266. app = item(app)
  267. def caller(app):
  268. return app(self.environ, self.start_response)
  269. return lambda caller=caller, app=app: caller(app)
  270. return middleware
  271. ISLE25 = sys.version_info[1] <= 5
  272. def parse_get_post_vars(request, environ):
  273. # always parse variables in URL for GET, POST, PUT, DELETE, etc. in get_vars
  274. env = request.env
  275. dget = cgi.parse_qsl(env.query_string or '', keep_blank_values=1)
  276. for (key, value) in dget:
  277. if key in request.get_vars:
  278. if isinstance(request.get_vars[key], list):
  279. request.get_vars[key] += [value]
  280. else:
  281. request.get_vars[key] = [request.get_vars[key]] + [value]
  282. else:
  283. request.get_vars[key] = value
  284. request.vars[key] = request.get_vars[key]
  285. try:
  286. request.body = body = copystream_progress(request)
  287. except IOError:
  288. raise HTTP(400, "Bad Request - HTTP body is incomplete")
  289. #if content-type is application/json, we must read the body
  290. is_json = env.get('http_content_type', '')[:16] == 'application/json'
  291. if is_json:
  292. try:
  293. json_vars = sj.load(body)
  294. body.seek(0)
  295. except:
  296. # incoherent request bodies can still be parsed "ad-hoc"
  297. json_vars = {}
  298. pass
  299. # update vars and get_vars with what was posted as json
  300. if isinstance(json_vars,dict):
  301. request.get_vars.update(json_vars)
  302. request.vars.update(json_vars)
  303. # parse POST variables on POST, PUT, BOTH only in post_vars
  304. if (body and env.request_method in ('POST', 'PUT', 'DELETE', 'BOTH')):
  305. dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1)
  306. # The same detection used by FieldStorage to detect multipart POSTs
  307. is_multipart = dpost.type[:10] == 'multipart/'
  308. body.seek(0)
  309. def listify(a):
  310. return (not isinstance(a, list) and [a]) or a
  311. try:
  312. keys = sorted(dpost)
  313. except TypeError:
  314. keys = []
  315. for key in keys:
  316. if key is None:
  317. continue # not sure why cgi.FieldStorage returns None key
  318. dpk = dpost[key]
  319. # if en element is not a file replace it with its value else leave it alone
  320. if isinstance(dpk, list):
  321. value = []
  322. for _dpk in dpk:
  323. if not _dpk.filename:
  324. value.append(_dpk.value)
  325. else:
  326. value.append(_dpk)
  327. elif not dpk.filename:
  328. value = dpk.value
  329. else:
  330. value = dpk
  331. pvalue = listify(value)
  332. if key in request.vars:
  333. gvalue = listify(request.vars[key])
  334. if ISLE25:
  335. value = pvalue + gvalue
  336. elif is_multipart:
  337. pvalue = pvalue[len(gvalue):]
  338. else:
  339. pvalue = pvalue[:-len(gvalue)]
  340. request.vars[key] = value
  341. if len(pvalue):
  342. request.post_vars[key] = (len(pvalue) >
  343. 1 and pvalue) or pvalue[0]
  344. if is_json and isinstance(json_vars,dict):
  345. # update post_vars with what was posted as json
  346. request.post_vars.update(json_vars)
  347. def wsgibase(environ, responder):
  348. """
  349. this is the gluon wsgi application. the first function called when a page
  350. is requested (static or dynamic). it can be called by paste.httpserver
  351. or by apache mod_wsgi.
  352. - fills request with info
  353. - the environment variables, replacing '.' with '_'
  354. - adds web2py path and version info
  355. - compensates for fcgi missing path_info and query_string
  356. - validates the path in url
  357. The url path must be either:
  358. 1. for static pages:
  359. - /<application>/static/<file>
  360. 2. for dynamic pages:
  361. - /<application>[/<controller>[/<function>[/<sub>]]][.<extension>]
  362. - (sub may go several levels deep, currently 3 levels are supported:
  363. sub1/sub2/sub3)
  364. The naming conventions are:
  365. - application, controller, function and extension may only contain
  366. [a-zA-Z0-9_]
  367. - file and sub may also contain '-', '=', '.' and '/'
  368. """
  369. current.__dict__.clear()
  370. request = Request()
  371. response = Response()
  372. session = Session()
  373. env = request.env
  374. env.web2py_path = global_settings.applications_parent
  375. env.web2py_version = web2py_version
  376. env.update(global_settings)
  377. static_file = False
  378. try:
  379. try:
  380. try:
  381. # ##################################################
  382. # handle fcgi missing path_info and query_string
  383. # select rewrite parameters
  384. # rewrite incoming URL
  385. # parse rewritten header variables
  386. # parse rewritten URL
  387. # serve file if static
  388. # ##################################################
  389. fixup_missing_path_info(environ)
  390. (static_file, version, environ) = url_in(request, environ)
  391. response.status = env.web2py_status_code or response.status
  392. if static_file:
  393. if environ.get('QUERY_STRING', '').startswith(
  394. 'attachment'):
  395. response.headers['Content-Disposition'] \
  396. = 'attachment'
  397. if version:
  398. response.headers['Cache-Control'] = 'max-age=315360000'
  399. response.headers[
  400. 'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT'
  401. response.stream(static_file, request=request)
  402. # ##################################################
  403. # fill in request items
  404. # ##################################################
  405. app = request.application # must go after url_in!
  406. if not global_settings.local_hosts:
  407. local_hosts = set(['127.0.0.1', '::ffff:127.0.0.1', '::1'])
  408. if not global_settings.web2py_runtime_gae:
  409. try:
  410. fqdn = socket.getfqdn()
  411. local_hosts.add(socket.gethostname())
  412. local_hosts.add(fqdn)
  413. local_hosts.update([
  414. addrinfo[4][0] for addrinfo
  415. in getipaddrinfo(fqdn)])
  416. if env.server_name:
  417. local_hosts.add(env.server_name)
  418. local_hosts.update([
  419. addrinfo[4][0] for addrinfo
  420. in getipaddrinfo(env.server_name)])
  421. except (socket.gaierror, TypeError):
  422. pass
  423. global_settings.local_hosts = list(local_hosts)
  424. else:
  425. local_hosts = global_settings.local_hosts
  426. client = get_client(env)
  427. x_req_with = str(env.http_x_requested_with).lower()
  428. request.update(
  429. client = client,
  430. folder = abspath('applications', app) + os.sep,
  431. ajax = x_req_with == 'xmlhttprequest',
  432. cid = env.http_web2py_component_element,
  433. is_local = env.remote_addr in local_hosts,
  434. is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \
  435. request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
  436. or env.https == 'on')
  437. request.compute_uuid() # requires client
  438. request.url = environ['PATH_INFO']
  439. # ##################################################
  440. # access the requested application
  441. # ##################################################
  442. if not exists(request.folder):
  443. if app == rwthread.routes.default_application \
  444. and app != 'welcome':
  445. redirect(URL('welcome', 'default', 'index'))
  446. elif rwthread.routes.error_handler:
  447. _handler = rwthread.routes.error_handler
  448. redirect(URL(_handler['application'],
  449. _handler['controller'],
  450. _handler['function'],
  451. args=app))
  452. else:
  453. raise HTTP(404, rwthread.routes.error_message
  454. % 'invalid request',
  455. web2py_error='invalid application')
  456. elif not request.is_local and \
  457. exists(pjoin(request.folder, 'DISABLED')):
  458. raise HTTP(503, "<html><body><h1>Temporarily down for maintenance</h1></body></html>")
  459. # ##################################################
  460. # build missing folders
  461. # ##################################################
  462. create_missing_app_folders(request)
  463. # ##################################################
  464. # get the GET and POST data
  465. # ##################################################
  466. parse_get_post_vars(request, environ)
  467. # ##################################################
  468. # expose wsgi hooks for convenience
  469. # ##################################################
  470. request.wsgi = LazyWSGI(environ, request, response)
  471. # ##################################################
  472. # load cookies
  473. # ##################################################
  474. if env.http_cookie:
  475. try:
  476. request.cookies.load(env.http_cookie)
  477. except Cookie.CookieError, e:
  478. pass # invalid cookies
  479. # ##################################################
  480. # try load session or create new session file
  481. # ##################################################
  482. if not env.web2py_disable_session:
  483. session.connect(request, response)
  484. # ##################################################
  485. # run controller
  486. # ##################################################
  487. if global_settings.debugging and app != "admin":
  488. import gluon.debug
  489. # activate the debugger
  490. gluon.debug.dbg.do_debug(mainpyfile=request.folder)
  491. serve_controller(request, response, session)
  492. except HTTP, http_response:
  493. if static_file:
  494. return http_response.to(responder, env=env)
  495. if request.body:
  496. request.body.close()
  497. # ##################################################
  498. # on success, try store session in database
  499. # ##################################################
  500. session._try_store_in_db(request, response)
  501. # ##################################################
  502. # on success, commit database
  503. # ##################################################
  504. if response.do_not_commit is True:
  505. BaseAdapter.close_all_instances(None)
  506. # elif response._custom_commit:
  507. # response._custom_commit()
  508. elif response.custom_commit:
  509. BaseAdapter.close_all_instances(response.custom_commit)
  510. else:
  511. BaseAdapter.close_all_instances('commit')
  512. # ##################################################
  513. # if session not in db try store session on filesystem
  514. # this must be done after trying to commit database!
  515. # ##################################################
  516. session._try_store_in_cookie_or_file(request, response)
  517. if request.cid:
  518. if response.flash:
  519. http_response.headers['web2py-component-flash'] = \
  520. urllib2.quote(xmlescape(response.flash)\
  521. .replace('\n',''))
  522. if response.js:
  523. http_response.headers['web2py-component-command'] = \
  524. urllib2.quote(response.js.replace('\n',''))
  525. # ##################################################
  526. # store cookies in headers
  527. # ##################################################
  528. rcookies = response.cookies
  529. if session._forget and response.session_id_name in rcookies:
  530. del rcookies[response.session_id_name]
  531. elif session._secure:
  532. rcookies[response.session_id_name]['secure'] = True
  533. http_response.cookies2headers(rcookies)
  534. ticket = None
  535. except RestrictedError, e:
  536. if request.body:
  537. request.body.close()
  538. # ##################################################
  539. # on application error, rollback database
  540. # ##################################################
  541. ticket = e.log(request) or 'unknown'
  542. if response._custom_rollback:
  543. response._custom_rollback()
  544. else:
  545. BaseAdapter.close_all_instances('rollback')
  546. http_response = \
  547. HTTP(500, rwthread.routes.error_message_ticket %
  548. dict(ticket=ticket),
  549. web2py_error='ticket %s' % ticket)
  550. except:
  551. if request.body:
  552. request.body.close()
  553. # ##################################################
  554. # on application error, rollback database
  555. # ##################################################
  556. try:
  557. if response._custom_rollback:
  558. response._custom_rollback()
  559. else:
  560. BaseAdapter.close_all_instances('rollback')
  561. except:
  562. pass
  563. e = RestrictedError('Framework', '', '', locals())
  564. ticket = e.log(request) or 'unrecoverable'
  565. http_response = \
  566. HTTP(500, rwthread.routes.error_message_ticket
  567. % dict(ticket=ticket),
  568. web2py_error='ticket %s' % ticket)
  569. finally:
  570. if response and hasattr(response, 'session_file') \
  571. and response.session_file:
  572. response.session_file.close()
  573. session._unlock(response)
  574. http_response, new_environ = try_rewrite_on_error(
  575. http_response, request, environ, ticket)
  576. if not http_response:
  577. return wsgibase(new_environ, responder)
  578. if global_settings.web2py_crontype == 'soft':
  579. newcron.softcron(global_settings.applications_parent).start()
  580. return http_response.to(responder, env=env)
  581. def save_password(password, port):
  582. """
  583. used by main() to save the password in the parameters_port.py file.
  584. """
  585. password_file = abspath('parameters_%i.py' % port)
  586. if password == '<random>':
  587. # make up a new password
  588. chars = string.letters + string.digits
  589. password = ''.join([random.choice(chars) for i in range(8)])
  590. cpassword = CRYPT()(password)[0]
  591. print '******************* IMPORTANT!!! ************************'
  592. print 'your admin password is "%s"' % password
  593. print '*********************************************************'
  594. elif password == '<recycle>':
  595. # reuse the current password if any
  596. if exists(password_file):
  597. return
  598. else:
  599. password = ''
  600. elif password.startswith('<pam_user:'):
  601. # use the pam password for specified user
  602. cpassword = password[1:-1]
  603. else:
  604. # use provided password
  605. cpassword = CRYPT()(password)[0]
  606. fp = open(password_file, 'w')
  607. if password:
  608. fp.write('password="%s"\n' % cpassword)
  609. else:
  610. fp.write('password=None\n')
  611. fp.close()
  612. def appfactory(wsgiapp=wsgibase,
  613. logfilename='httpserver.log',
  614. profilerfilename='profiler.log'):
  615. """
  616. generates a wsgi application that does logging and profiling and calls
  617. wsgibase
  618. .. function:: gluon.main.appfactory(
  619. [wsgiapp=wsgibase
  620. [, logfilename='httpserver.log'
  621. [, profilerfilename='profiler.log']]])
  622. """
  623. if profilerfilename and exists(profilerfilename):
  624. os.unlink(profilerfilename)
  625. locker = allocate_lock()
  626. def app_with_logging(environ, responder):
  627. """
  628. a wsgi app that does logging and profiling and calls wsgibase
  629. """
  630. status_headers = []
  631. def responder2(s, h):
  632. """
  633. wsgi responder app
  634. """
  635. status_headers.append(s)
  636. status_headers.append(h)
  637. return responder(s, h)
  638. time_in = time.time()
  639. ret = [0]
  640. if not profilerfilename:
  641. ret[0] = wsgiapp(environ, responder2)
  642. else:
  643. import cProfile
  644. import pstats
  645. logger.warn('profiler is on. this makes web2py slower and serial')
  646. locker.acquire()
  647. cProfile.runctx('ret[0] = wsgiapp(environ, responder2)',
  648. globals(), locals(), profilerfilename + '.tmp')
  649. stat = pstats.Stats(profilerfilename + '.tmp')
  650. stat.stream = cStringIO.StringIO()
  651. stat.strip_dirs().sort_stats("time").print_stats(80)
  652. profile_out = stat.stream.getvalue()
  653. profile_file = open(profilerfilename, 'a')
  654. profile_file.write('%s\n%s\n%s\n%s\n\n' %
  655. ('=' * 60, environ['PATH_INFO'], '=' * 60, profile_out))
  656. profile_file.close()
  657. locker.release()
  658. try:
  659. line = '%s, %s, %s, %s, %s, %s, %f\n' % (
  660. environ['REMOTE_ADDR'],
  661. datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S'),
  662. environ['REQUEST_METHOD'],
  663. environ['PATH_INFO'].replace(',', '%2C'),
  664. environ['SERVER_PROTOCOL'],
  665. (status_headers[0])[:3],
  666. time.time() - time_in,
  667. )
  668. if not logfilename:
  669. sys.stdout.write(line)
  670. elif isinstance(logfilename, str):
  671. write_file(logfilename, line, 'a')
  672. else:
  673. logfilename.write(line)
  674. except:
  675. pass
  676. return ret[0]
  677. return app_with_logging
  678. class HttpServer(object):
  679. """
  680. the web2py web server (Rocket)
  681. """
  682. def __init__(
  683. self,
  684. ip='127.0.0.1',
  685. port=8000,
  686. password='',
  687. pid_filename='httpserver.pid',
  688. log_filename='httpserver.log',
  689. profiler_filename=None,
  690. ssl_certificate=None,
  691. ssl_private_key=None,
  692. ssl_ca_certificate=None,
  693. min_threads=None,
  694. max_threads=None,
  695. server_name=None,
  696. request_queue_size=5,
  697. timeout=10,
  698. socket_timeout=1,
  699. shutdown_timeout=None, # Rocket does not use a shutdown timeout
  700. path=None,
  701. interfaces=None # Rocket is able to use several interfaces - must be list of socket-tuples as string
  702. ):
  703. """
  704. starts the web server.
  705. """
  706. if interfaces:
  707. # if interfaces is specified, it must be tested for rocket parameter correctness
  708. # not necessarily completely tested (e.g. content of tuples or ip-format)
  709. import types
  710. if isinstance(interfaces, types.ListType):
  711. for i in interfaces:
  712. if not isinstance(i, types.TupleType):
  713. raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/"
  714. else:
  715. raise "Wrong format for rocket interfaces parameter - see http://packages.python.org/rocket/"
  716. if path:
  717. # if a path is specified change the global variables so that web2py
  718. # runs from there instead of cwd or os.environ['web2py_path']
  719. global web2py_path
  720. path = os.path.normpath(path)
  721. web2py_path = path
  722. global_settings.applications_parent = path
  723. os.chdir(path)
  724. [add_path_first(p) for p in (path, abspath('site-packages'), "")]
  725. if exists("logging.conf"):
  726. logging.config.fileConfig("logging.conf")
  727. save_password(password, port)
  728. self.pid_filename = pid_filename
  729. if not server_name:
  730. server_name = socket.gethostname()
  731. logger.info('starting web server...')
  732. rocket.SERVER_NAME = server_name
  733. rocket.SOCKET_TIMEOUT = socket_timeout
  734. sock_list = [ip, port]
  735. if not ssl_certificate or not ssl_private_key:
  736. logger.info('SSL is off')
  737. elif not rocket.ssl:
  738. logger.warning('Python "ssl" module unavailable. SSL is OFF')
  739. elif not exists(ssl_certificate):
  740. logger.warning('unable to open SSL certificate. SSL is OFF')
  741. elif not exists(ssl_private_key):
  742. logger.warning('unable to open SSL private key. SSL is OFF')
  743. else:
  744. sock_list.extend([ssl_private_key, ssl_certificate])
  745. if ssl_ca_certificate:
  746. sock_list.append(ssl_ca_certificate)
  747. logger.info('SSL is ON')
  748. app_info = {'wsgi_app': appfactory(wsgibase,
  749. log_filename,
  750. profiler_filename)}
  751. self.server = rocket.Rocket(interfaces or tuple(sock_list),
  752. method='wsgi',
  753. app_info=app_info,
  754. min_threads=min_threads,
  755. max_threads=max_threads,
  756. queue_size=int(request_queue_size),
  757. timeout=int(timeout),
  758. handle_signals=False,
  759. )
  760. def start(self):
  761. """
  762. start the web server
  763. """
  764. try:
  765. signal.signal(signal.SIGTERM, lambda a, b, s=self: s.stop())
  766. signal.signal(signal.SIGINT, lambda a, b, s=self: s.stop())
  767. except:
  768. pass
  769. write_file(self.pid_filename, str(os.getpid()))
  770. self.server.start()
  771. def stop(self, stoplogging=False):
  772. """
  773. stop cron and the web server
  774. """
  775. newcron.stopcron()
  776. self.server.stop(stoplogging)
  777. try:
  778. os.unlink(self.pid_filename)
  779. except:
  780. pass