Question:
I am in the process of porting my web.py app from its built in server to Apache2 using mod_wsgi. All is well and the code executes, except for the sessions.
Firstly, note that the sessions work fine with the old server. I am testing as follows: I render a page called layout which has the lines
$session
${'loggedIn' in session}
Due to the fact that session (which is a web.sessions.Session) is initialised with initializer={'loggedIn' : False}, I expect the second line to always render as "True". This it does with the default server.
However, on Apache2 with mod_wsgi, it is not the case. If I restart apache, then it renders as True, but subsequent refreshes of the page render as False.
I understand that sometimes the cookie_path can be the culprit, but have set that to /, and am unsure of what else could be the problem.
Code:
/etc/apache2/apache2.conf
LoadModule wsgi_module modules/mod_wsgi.so
WSGIScriptAlias / /var/www/myapp/webUtils.py/
AddType text/html .py
<Directory /var/www/myapp>
Order deny,allow
Allow from all
</Directory>
/var/www/myapp/webUtils.py
import sys,os
abspath = os.path.dirname(__file__) or '.'
sys.path.append(abspath)
os.chdir(abspath)
import web
(...)
## The configuration for the sessions (this function just reads in
## the file and turns it into a dictionary).
session_configuration = config.getConfiguration('config/sessions.cfg')
for i in session_configuration:
web.config.session_parameters[i] = session_configuration[i]
web.config.debug = False
app = web.application(urls,globals(),autoreload=False)
session = web.session.Session(app,
web.session.DiskStore('/home/robert/sessions'),
initializer={'loggedIn' : False})
render = web.template.render('templates',
globals={'session':session},
base='layout')
# Get the WSGI stuff
application = app.wsgifunc()
config/sessions.cfg
timeout : 60*10
secret_key : "stuff"
cookie_name : "someName"
cookie_path : "/"
EDIT: Update
By running exhaustive tests, I have found that calling webUtils.session._load() as the first line in a GET fixes the whole problem. I thus postulate that the _processor of that Session is not being called (even though it is in app.processors). Why not is still a mystery.
You are using a multi process configuration and so cannot rely on an I'm memory session cache as subsequent requests can go to different processes. Also, since using embedded mode of mod_wsgi, Apache can decide to shutdown your processes if it thinks they are no longer needed.
You need to make sure you are using a database for the session cache.
Alternative using mod_wsgi daemon mode with the default of a single process.
Finally found the problem. It had to do with the multi processes, but ended up being that the Session object that created a hook (and thus got loaded every time) was not the same as the object that my page code was seeing. Thus if I loaded and saved my session in each GET statement and removed the hook, it all worked.
To solve this, I just needed to undo an assumption I made and reinstate the code
if web.config.get('_session') is None:
session = web.session.Session(app, web.session.DiskStore('sessions'), {'count': 0})
web.config._session = session
else:
session = web.config._session
which I had removed, since it is only given for the builtin server in the documentation.
Related
I am building a simple flask app, jsonify() works fine on my localhost, it will return the information with new lines and the proper indent in a json format, however when running the exact same code on heroku, it omits the new lines and the indentation
This is how it looks on my localhost and this is on heroku
This is mentioned on the docs for jsonify()
This function's response will be pretty printed if the JSONIFY_PRETTYPRINT_REGULAR config parameter is set to True or the Flask app is running in debug mode
I have both set
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True
app.run(debug=True)
I tried manually setting the content type to application/json, but that did not help, I even tried using json.dumps() and got the same result
return jsonify(data), 200, {'Content-Type': 'application/json; charset=utf-8'}
Any input on what could be causing heroku not pretty printing?
Edit:
from flask import request, jsonify, Flask
app = Flask(__name__)
#app.route('/test', methods = ['GET'])
def test():
test_dict = {"Key1": "Value1", "Key2": ["Value2","Value2","Value2",]}
print(jsonify(test_dict).headers)
return jsonify(test_dict)
if __name__ == '__main__':
app.run(debug=True)
This simple flask app would pretty print on my localhost like the photos linked above, however on heroku it wont. Looks like it is returning plain text. It can be seen here https://jojoapi.herokuapp.com/test.
I am using gunicorn, not sure if that has any impacts on the output
Edit 2
So, I set manually debug to True as suggested in the comments app.config["DEBUG"] = True and it works properly on heroku now
Some servers (not only Heroku) may not run directly your script and not execute app(debug=True) but they may import app to own code and run it with own arguments app(...own args...) - and this can makes problem.
You can set debug mode in code in different method.
app.config["DEBUG"] = True
Eventually you can try to set environment variable in Linux
export FLASK_DEBUG=1
or
export FLASK_ENV=development
See doc: Debug Mode
Flask doc: Standalone WSGI Containers - it shows servers which import app (as myproject:app) and they may runs with own arguments.
Through backlash, TurboGears supports error reporting to Sentry via Raven. Enabling the error reporting is quite easy, just add the appropriate setting in the .ini configuration file, for example:
[DEFAULT]
debug = false
trace_errors.sentry_dsn = https://[…]
trace_slowreqs.enable = true
trace_slowreqs.sentry_dsn = https://[…]
set debug = false
According to Raven's documentation, adding more context to what gets reported should be as simple as
def handle_request(request): # In TurboGears, this would be a controller instead.
client.context.merge({'user': {
'email': request.user.email
}})
try:
...
finally:
client.context.clear()
However, now I wonder what is the easiest, or most correct, way to get hold of the client instance that backlash will use for reporting? I would like to add per-request information, typically from within the request handlers, or Controller methods.
Editing the raven context is currently quite hard as the error reporters are not registered anywhere, so you cannot say "hey give me the error reporters" and look for the Sentry one in that list.
Currently the only way is to register an after_config hook, gather the Raven Client during the configuration process and store it somewhere accessible.
Changing backlash middlewares to store the reporters somewhere accessible should be fairly easy (e.g. the environ) but currently it's not available.
By the way here is a short example of the after_config solution that should make the client available as tg.app_globals.sentry_clients, copy it in your app_cfg.py and it should do what you expect (didn't have time to try it, sorry if you find errors), then you can get the context from the client whenever is needed:
def gather_sentry_client(app):
from backlash import TraceErrorsMiddleware, TraceSlowRequestsMiddleware
try:
trace_errors_app = app.app.application
except:
return app
if not isinstance(trace_errors_app, TraceErrorsMiddleware):
return app
trace_errors_client = None
for reporter in trace_errors_app.reporters:
if hasattr(reporter, 'client'):
trace_errors_client = reporter.client
slow_reqs_app = trace_errors_app.app
slow_reqs_client = None
if isinstance(slow_reqs_app, TraceSlowRequestsMiddleware):
for reporter in slow_reqs_app.reporters:
if hasattr(reporter, 'client'):
slow_reqs_client = reporter.client
from tg import config
app_globals = config['tg.app_globals']
app_globals.sentry_clients = {
'errors': trace_errors_client,
'slowreqs': slow_reqs_client
}
return app
from tg import hooks
hooks.register('after_config', gather_sentry_client)
My simple Django app worked fine in debug mode (manage.py runserver), and works under WSGI+Apache on my dev box, but when I pushed to EC2 I began receiving intermittent (10-80% of the time) errors of Bad Request (400) for any URLs I try to view (whether in my app or in the Django admin.
Where can I find debug information about this? Nothing appears in /var/log/apache2/error.log, even with LogLevel=info. I have checked versions, logged the Request environment (cf. ModWSGI Debugging Tips) and see no major differences.
The one remaining thought I had is, I'm using the mod_wsgi from Ubuntu 12.04 (libapache2-mod-wsgi 3.3-4build1) which was built against Python 2.7.1; I have Python 2.7.3. And Django is 1.6, which is newer than the Ubuntu Precise version. I hesitate to start building packages from source since it's so hard to clean up and these seem like minor version changes...
Thank you for your help.
(For reference, here are the Apache config and WSGI apps)
Apache config (000-default)
<VirtualHost *:80>
ServerAdmin webmaster#localhost
DocumentRoot /var/www
WSGIScriptAlias /rz /usr/local/share/rz/rz.wsgi
...
rz.WSGI app
import os
import sys
import django.core.handlers.wsgi
import pprint
path = '/usr/local/share/rz'
if path not in sys.path:
sys.path.insert(0, path)
os.environ['DJANGO_SETTINGS_MODULE'] = 'rz.settings'
class LoggingMiddleware:
def __init__(self, application):
self.__application = application
def __call__(self, environ, start_response):
errors = environ['wsgi.errors']
pprint.pprint(('REQUEST', environ), stream=errors)
def _start_response(status, headers, *args):
pprint.pprint(('RESPONSE', status, headers), stream=errors)
return start_response(status, headers, *args)
return self.__application(environ, _start_response)
application = LoggingMiddleware(django.core.handlers.wsgi.WSGIHandler())
Add the ALLOWED_HOSTS setting to your settings.py like so...
ALLOWED_HOSTS = [
'.example.com', # Allow domain and subdomains
'.example.com.', # Also allow FQDN and subdomains
]
I had this same problem and found the answer here in the docs
update: django 1.6 docs are no longer online, I updated the link to go to the django 1.7 docs for ALLOWED_HOSTS setting.
If you've definitely set ALOWED_HOSTS - make sure your hostname doesn't contain underscores. It's technically illegal.
I had to print out various functions and it boiled down to this regex failing to detect a domain in django.http
host_validation_re = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9:]+\])(:\d+)?$")
And indeed, my domain had an underscore in it.
This is not a solution, but for debugging purposes you might set the ALLOWED_HOSTS setting in your settings.py like this
ALLOWED_HOSTS = ['*']
It should definitely work. If not, at least you will know the problem isn't Django denying access to the given url.
I am trying to set up logging with Cherrypy on my Openshift python 3.3 app. The 'appserver.log' file only updates until the actual server starts then nothing gets added to the log file. I have read and followed (as far as I know) the documentation at the below links. Still no logging.
CherryPy server errors log
http://docs.cherrypy.org/dev/refman/_cplogging.html
My python code snippet:
def run_cherrypy_server(app, ip, port=8080):
from cherrypy import wsgiserver
from cherrypy import config
# log.screen: Set this to True to have both “error” and “access” messages printed to stdout.
# log.access_file: Set this to an absolute filename where you want “access” messages written.
# log.error_file: Set this to an absolute filename where you want “error” messages written.
appserver_error_log = os.path.join(os.environ['OPENSHIFT_HOMEDIR'], 'python', 'logs','appserver_error.log')
appserver_access_log = os.path.join(os.environ['OPENSHIFT_HOMEDIR'], 'python', 'logs','appserver_access.log')
config.update({
'log.screen': True,
'log.error_file': appserver_error_log,
'log.access_file': appserver_access_log
})
server = wsgiserver.CherryPyWSGIServer(
(ip, port), app, server_name='www.cherrypy.example')
server.start()
The 'appserver_error.log' and 'appserver_access.log' files actually get created in the proper Openshift python directory. However, no logging information in both of the files appserver_error.log and appserver_access.log.
Everything runs fine but no logging.
Any ideas what I am doing wrong?
The WSGI server itself does not do any logging. The CherryPy engine (which controls process startup and shutdown) writes to the "error" log, and only CherryPy applications (that use CherryPy's Request and Response objects) write to the access log. If you're passing your own WSGI application, you'll have to do your own logging.
I want my Flask app to have different behaviors when it is being run on localhost and when it is being hosted online. How can I detect from a flask app when it is on localhost and when it is deployed?
You'll want to look at the configuration handling section of the docs, most specifically, the part on dev / production. To summarize here, what you want to do is:
Load a base configuration which you keep in source control with sensible defaults for things which need to have some value. Anything which needs a value should have the value set to what makes sense for production not for development.
Load an additional configuration from a path discovered via an environment variable that provides environment-specific settings (e. g. the database URL).
An example in code:
from __future__ import absolute_imports
from flask import Flask
import .config # This is our default configuration
app = Flask(__name__)
# First, set the default configuration
app.config.from_object(config)
# Then, load the environment-specific information
app.config.from_envvar("MYAPP_CONFIG_PATH")
# Setup routes and then ...
if __name__ == "__main__":
app.run()
See also: The docs for Flask.config
Here is one way of doing it. The key is comparing the current root url flask.request.url_root against a known url value you want to match.
Excerpt taken from github repo https://github.com/nueverest/vue_flask
from flask import Flask, request
def is_production():
""" Determines if app is running on the production server or not.
Get Current URI.
Extract root location.
Compare root location against developer server value 127.0.0.1:5000.
:return: (bool) True if code is running on the production server, and False otherwise.
"""
root_url = request.url_root
developer_url = 'http://127.0.0.1:5000/'
return root_url != developer_url