Skip to content

Instantly share code, notes, and snippets.

@pyguerder
Last active December 3, 2023 23:24
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyguerder/37ade154837e550646b0 to your computer and use it in GitHub Desktop.
Save pyguerder/37ade154837e550646b0 to your computer and use it in GitHub Desktop.
Installation de Flask sur un hébergement mutualisé OVH
Options +ExecCGI
AddHandler cgi-script .cgi
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ flask.cgi/$1 [QSA,L]

OVH permettait l'exécution du code Python sur son hébergement mutualisé. La version de Python utilisée était 2.6 mais Flask et ses dependances n'étaient sont pas installés en standard (vérifier).

Flask-0.10.1 (télécharger) fonctionne sur cet environnement. Ses dépendances sont :

Une fois les archives téléchargées extrayez-les dans un dossier projets.

Placez le code de votre application Flask dans le dossier projets/website.

L'arborescence résultante est la suivante :

/
├ projects/
│ ├ Flask-0.10.1/
│ │ ├ flask/
│ │ └ LICENSE
│ ├ itsdangerous-0.24/
│ │ ├ itsdangerous.py
│ │ └ LICENSE
│ ├ Jinja2-2.8/
│ │ ├ jinja2/
│ │ └ LICENSE
│ ├ MarkupSafe-0.23/
│ │ ├ markupsafe/
│ │ └ LICENSE
│ ├ Werkzeug-0.11.3/
│ │ ├ werkzeug/
│ │ └ LICENSE
│ ├ wheel-0.26.0/
│ │ ├ wheel/
│ │ └ LICENSE
│ └ website/
│   ├ templates/
│   | ├ template1.htm
│   | └ template2.htm
│   ├ main.py
│   ├ __init__.py
│   └ database.sqlite
└ www/
  ├ static/
  │ ├ css/
  │ ├ img/
  │ └ js/
  ├ flask.cgi
  ├ .htaccess
  ├ sitemap.gz
  └ robots.txt

Cette arborescence peut être personnalisée grâce aux instructions sys.path.append du fichier flask.cgi.

Ces instructions sont également à adapter en fonction du nom de votre compte OVH (moncompte dans le fichier ci-joint). En cas de doute, vous pouvez obtenir cette information en créant dans le dossier www un fichier PHP contenant <?php echo getcwd(); ?>.

Le fonctionnement repose sur les fichiers /www/.htaccess et /www/flask.cgi donnés ci-joints.

Il est essentiel que les permissions du fichier /www/flask.cgi soient 755 ou supérieures. Le problème est que le serveur OVH remet les permissions à 604 dès que le fichier est modifié. Pensez-y quand vous rencontrez des erreurs 500 !

Astuce : si vous hébergez votre site sur un contrat start10m (donc avec 10 Mo d'espace disque), vous pouvez supprimer tous les fichiers et dossiers des projets (docs, examples, artwork, etc.) sauf flask, itsdangerous.py, jinja2, markupsafe, werkzeug et wheel (et, pour respecter les license, LICENSE). Le dossier projets est ainsi réduit à 2,2 Mo !

De plus, par défaut, Python va stocker les fichiers bytecode (*.pyc) et ceux-ci réduiront d'autant l'espace disponible pour l'hébergement. Les lignes suivantes, à ajouter au fichier django.cgi, permettent d'empêcher Python de stocker ces fichiers (au prix d'une réduction des performances) :

os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
sys.dont_write_bytecode = True

ATTENTION

Cette méthode fonctionnait en 2016 sur un hébergement OVH mutualisé, avec Python 2.6.

Il semble que depuis, OVH ne propose plus Python sur les hébergements Starter/Perso/Pro/Performance mais seulement sur « Cloud Web ».

Par ailleurs, OVH ne semble proposer que Python 3. Les versions des bibliothèques utilisées ici sont probablement obsolètes voire vulnérables.

Je n'utilise plus cette technique et je n'en assure pas le support !

Vous pouvez vous inspirer de ce tutoriel : https://help.ovhcloud.com/csm/fr-cloud-web-hosting-install-django-cms?id=kb_article_view&sysparm_article=KB0039337 et contacter le support officiel en cas de problème.

Si malgré tout, ça fonctionne pour vous, n'hésitez pas à commenter en-dessous.

La méthode présentée ici permettait d'installer une application Flask sur un hébergement mutualisé OVH.

Lisez d'abord le fichier README puis copiez les fichiers .htaccess et flask.cgi vers votre hébergement, ainsi que le code source de Flask et de ses dépendances et le code source de votre application.

#!/usr/bin/python
# encoding: utf-8
import os, sys
sys.path.append("/home/moncompte/projects/") # À modifier
sys.path.append("/home/moncompte/projects/website/") # À modifier
sys.path.append("/home/moncompte/projects/Flask-0.10.1") # À modifier
sys.path.append("/home/moncompte/projects/itsdangerous-0.24") # À modifier
sys.path.append("/home/moncompte/projects/Jinja2-2.8") # À modifier
sys.path.append("/home/moncompte/projects/MarkupSafe-0.23") # À modifier
sys.path.append("/home/moncompte/projects/website") # À modifier
sys.path.append("/home/moncompte/projects/Werkzeug-0.11.3") # À modifier
sys.path.append("/home/moncompte/projects/wheel-0.26.0") # À modifier
def run_with_cgi(application):
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1,0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS','off') in ('on','1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = []
def write(data):
if not headers_set:
raise AssertionError("write() before start_response()")
elif not headers_sent:
# Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
sys.stdout.write('Status: %s\r\n' % status)
for header in response_headers:
sys.stdout.write('%s: %s\r\n' % header)
sys.stdout.write('\r\n')
sys.stdout.write(data)
sys.stdout.flush()
def start_response(status,response_headers,exc_info=None):
if exc_info:
try:
if headers_sent:
# Re-raise original exception if headers sent
raise exc_info[0], exc_info[1], exc_info[2]
finally:
exc_info = None # avoid dangling circular ref
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status,response_headers]
return write
result = application(environ, start_response)
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result,'close'):
result.close()
try:
os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
sys.dont_write_bytecode = True
from website.main import app as application
run_with_cgi(application)
except Exception, inst:
print "Content-type: text/html\n\n"
print inst
@PhilD35
Copy link

PhilD35 commented Mar 20, 2018

Bonsoir @pyguerder

J'ai essayé avec un main.py minimaliste

#!/usr/bin/env python
# coding: UTF-8

from flask import Flask, request, jsonify
app = Flask(__name__) # reprend le nom du module

@app.route('/test', methods=['GET'])
def test():
return "Hello ! I'm alive "
# app.run()

J'ai été obligé de commenter la ligne app.run(), sinon j'avais une exception " [Errno 13] Permission denied "

Malheureusement, je me heurte à une erreur "Not Found"

Je l'appelle avec l'url : flask.mondomaine.net/test
(J'ai fait un sous domaine flsak. pour faciliter ces tests)

Mon fichier minimaliste marche en local sur ma machine ubuntu 16.04 (mais python 3.5)

Pourriez-vous donner un exemple simple de fichier main.py incluant des directives Flask ?

Merci

PS : en cliquant sur votre lien de vérification, OVH semble être passé de la version python 2.6 à 2.7

@PhilD35
Copy link

PhilD35 commented Apr 4, 2018

Bonjour,

J'ai finalement trouvé la bonne configuration pour un main.py minimaliste.
Pour préserver l'affichage de l'indentation, j'ai ajouté un . en début des lignes indentées.

L'appel avec l'url : flask.mondomaine.net/test2
provoque l'affichage : You want path: flask.cgi/test2

Il faut donc tenir compte dans les chemins de la règle de réécriture d'url qui insère flask.cgi...
Merci pour avoir trouvé la bonne configuration chez OVH !
Cela nous ouvre des voies intéressantes !

#!/usr/bin/env python
# coding: UTF-8

from flask import Flask, request, jsonify

app = Flask(__name__) # reprend le nom du module

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
. return 'You want path: %s' % path

if __name__ == 'main':
. app.run()

@o-reo
Copy link

o-reo commented Apr 7, 2018

Bonjour,
J'ai tenté de reproduire tes tentatives Phil mais je me heurte à un "No module named website.main".
Je suis dans une configuration vraiment similaire à la tienne aurais tu fait des modifications particulières sur le flask.cgi ? Ou ajouté autre chose que ton main.py ?
Je débute en Flask
Merci

@xoancosmed
Copy link

xoancosmed commented Jun 14, 2019

Hi!

Is this still working? I am trying to do it as you suggested but I am always getting 500 errors, and I cannot find any log for this. I have applied the permission 755 to all the executable files. I am doing this in a subdomain and with the latest Flask (with the new dependencies). In some of the dependencies I have an "src" folder instead of a folder with the name of the library, so probably the problem is there ...

Thanks

  • '/home/xxxxxx/yyyyyy/api.cgi'
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import os, sys

sys.path.append("/home/xxxxxxx/__python__/")
sys.path.append("/home/xxxxxxx/__python__/projects/")
sys.path.append("/home/xxxxxxx/__python__/projects/test/")
sys.path.append("/home/xxxxxxx/__python__/libraries/")
sys.path.append("/home/xxxxxxx/__python__/libraries/Flask-1.0.3")
sys.path.append("/home/xxxxxxx/__python__/libraries/itsdangerous-1.1.0")
sys.path.append("/home/xxxxxxx/__python__/libraries/Jinja2-2.10.1")
sys.path.append("/home/xxxxxxx/__python__/libraries/MarkupSafe-1.1.1")
sys.path.append("/home/xxxxxxx/__python__/libraries/Werkzeug-0.15.4")
sys.path.append("/home/xxxxxxx/__python__/libraries/wheel-0.33.4")
sys.path.append("/home/xxxxxxx/__python__/libraries/Click-7.0")

def run_with_cgi(application):
    environ                      = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1,0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True

    if environ.get('HTTPS','off') in ('on','1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set  = []
    headers_sent = []

    def write(data):
        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             sys.stdout.write('Status: %s\r\n' % status)
             for header in response_headers:
                 sys.stdout.write('%s: %s\r\n' % header)
             sys.stdout.write('\r\n')

        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status,response_headers,exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status,response_headers]
        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result,'close'):
            result.close()

try:
    os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
    sys.dont_write_bytecode = True
    from test.api import app as application
    run_with_cgi(application)
except Exception, inst:
    print("Content-type: text/html\n\n")
    print(inst)
  • '/home/xxxxxxx/yyyyyyy/.htaccess'
Options +ExecCGI
AddHandler cgi-script .cgi
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /home/xxxxxxx/yyyyyyyy/api.cgi/$1 [QSA,L]
  • /home/xxxxxx/__python__/projects/test/api.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello from Flask'
  • Tree of directories
/home/xxxxx/
├── __python__
│   ├── libraries
│   │   ├── Click-7.0
│   │   │   ├── MANIFEST.in
│   │   │   └── click
│   │   ├── Flask-1.0.3
│   │   │   ├── LICENSE
│   │   │   └── flask
│   │   ├── Jinja2-2.10.1
│   │   │   ├── LICENSE
│   │   │   └── jinja2
│   │   ├── MarkupSafe-1.1.1
│   │   │   ├── MANIFEST.in
│   │   │   └── src
│   │   ├── Werkzeug-0.15.4
│   │   │   ├── LICENSE.rst
│   │   │   └── src
│   │   ├── itsdangerous-1.1.0
│   │   │   ├── LICENSE.rst
│   │   │   └── src
│   │   └── wheel-0.33.4
│   │       ├── LICENSE.txt
│   │       └── wheel
│   └── projects
│       └── test
│           └── api.py
└── yyyyy
    ├── .htaccess
    └── api.cgi

@pyguerder
Copy link
Author

@xoancosmed: I don't know if this is still working, I have another hosting now. But I am pretty sure there is a mistake in your .htaccess
RewriteRule ^(.*)$ /home/xxxxxxx/yyyyyyyy/api.cgi/$1 [QSA,L] should not contain a full path, try with RewriteRule ^(.*)$ api.cgi/$1 [QSA,L].
Also, you can probably have more details about the error 500 in the logs https://logs.ovh.net/
Regards

@xoancosmed
Copy link

Hi @pyguerder, thanks for your answer. I tried using the relative path but I'm having the same problem, I guess the problem is with the libraries or how I import them. If I look in the log file I cannot see anything related to the CGI, that's why I'm so confused.

@nicosouv
Copy link

nicosouv commented Mar 22, 2020

For information, I was able to hide "/flask.cgi/" in the address bar by changing a little bit the .htaccess:

AddHandler cgi-script .cgi
Options +ExecCGI

IndexIgnore *

DirectoryIndex flask.cgi
Options -Indexes

RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ flask.cgi/$1 [L]

There are probably some redundant instructions though.

Also, I was able to run latest version of Flask, Jinja2 without any trouble.
Hope this helps!

@ArnaudMartinCaen
Copy link

Bonjour,

J'essaye de porter une application Flask sur un serveur WEB OVH, mais je rencontre toujours une erreur 500 en consultant la page www du sous domaine utilisé.

J'ai essayé de suivre le readme.

J'ai dons la meme arborescence et voici le contenu des ficheirs :

Flask.cgi :
#!/usr/bin/python

encoding: utf-8

import os, sys
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/website/") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/Flask-0.10.1") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/itsdangerous-0.24") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/Jinja2-2.8") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/MarkupSafe-0.23") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/website") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/Werkzeug-0.11.3") # À modifier
sys.path.append("/home/lyceedesx/www/ldmarket/www/projects/wheel-0.26.0") # À modifier

def run_with_cgi(application):
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1,0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True

if environ.get('HTTPS','off') in ('on','1'):
    environ['wsgi.url_scheme'] = 'https'
else:
    environ['wsgi.url_scheme'] = 'http'

headers_set  = []
headers_sent = []

def write(data):
    if not headers_set:
         raise AssertionError("write() before start_response()")

    elif not headers_sent:
         # Before the first output, send the stored headers
         status, response_headers = headers_sent[:] = headers_set
         sys.stdout.write('Status: %s\r\n' % status)
         for header in response_headers:
             sys.stdout.write('%s: %s\r\n' % header)
         sys.stdout.write('\r\n')

    sys.stdout.write(data)
    sys.stdout.flush()

def start_response(status,response_headers,exc_info=None):
    if exc_info:
        try:
            if headers_sent:
                # Re-raise original exception if headers sent
                raise exc_info[0], exc_info[1], exc_info[2]
        finally:
            exc_info = None     # avoid dangling circular ref
    elif headers_set:
        raise AssertionError("Headers already set!")

    headers_set[:] = [status,response_headers]
    return write

result = application(environ, start_response)
try:
    for data in result:
        if data:    # don't send headers until body appears
            write(data)
    if not headers_sent:
        write('')   # send headers now if body was empty
finally:
    if hasattr(result,'close'):
        result.close()

try:
os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
sys.dont_write_bytecode = True
from website.main import app as application
run_with_cgi(application)
except Exception, inst:
print "Content-type: text/html\n\n"
print inst

Et mon main.py :
from flask import Flask, render_template

app = Flask(name)

@app.route('/')
def index():
return render_template('template1.htm')

@app.route('/page2')
def page2():
return render_template('template2.htm')

if name == 'main':
app.run(debug=False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment