Tambahkan awalan ke semua rute Flask

99

Saya memiliki awalan yang ingin saya tambahkan ke setiap rute. Sekarang saya menambahkan konstanta ke rute di setiap definisi. Apakah ada cara untuk melakukan ini secara otomatis?

PREFIX = "/abc/123"

@app.route(PREFIX + "/")
def index_page():
  return "This is a website about burritos"

@app.route(PREFIX + "/about")
def about_page():
  return "This is a website about burritos"
Evan Hahn
sumber

Jawaban:

75

Jawabannya tergantung bagaimana Anda melayani aplikasi ini.

Sub-mount di dalam wadah WSGI lain

Dengan asumsi bahwa Anda akan menjalankan aplikasi ini di dalam wadah WSGI (mod_wsgi, uwsgi, gunicorn, dll); Anda harus benar-benar memasang, pada awalan itu aplikasi sebagai sub-bagian dari wadah WSGI itu (apa pun yang berbicara WSGI akan dilakukan) dan untuk mengatur APPLICATION_ROOTnilai konfigurasi Anda ke awalan Anda:

app.config["APPLICATION_ROOT"] = "/abc/123"

@app.route("/")
def index():
    return "The URL for this page is {}".format(url_for("index"))

# Will return "The URL for this page is /abc/123/"

Menyetel nilai APPLICATION_ROOTkonfigurasi cukup membatasi cookie sesi Flask ke awalan URL itu. Segala sesuatu yang lain akan ditangani secara otomatis untuk Anda oleh kemampuan penanganan WSGI Flask dan Werkzeug yang sangat baik.

Contoh pemasangan aplikasi Anda dengan benar

Jika Anda tidak yakin apa arti paragraf pertama, lihat aplikasi contoh ini dengan Flask terpasang di dalamnya:

from flask import Flask, url_for
from werkzeug.serving import run_simple
from werkzeug.wsgi import DispatcherMiddleware

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/abc/123'

@app.route('/')
def index():
    return 'The URL for this page is {}'.format(url_for('index'))

def simple(env, resp):
    resp(b'200 OK', [(b'Content-Type', b'text/plain')])
    return [b'Hello WSGI World']

app.wsgi_app = DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app})

if __name__ == '__main__':
    app.run('localhost', 5000)

Permintaan proxy ke aplikasi

Sebaliknya, jika Anda akan menjalankan aplikasi Flask Anda di root dari wadah WSGI dan mem-proxy permintaan ke sana (misalnya, jika itu menjadi FastCGI, atau jika nginx proxy_passmeminta sub-endpoint ke server uwsgi/ stand-alone geventAnda, maka Anda dapat:

  • Gunakan Cetak Biru, seperti yang ditunjukkan Miguel dalam jawabannya .
  • atau gunakan DispatcherMiddlewaredari werkzeug(atau PrefixMiddlewaredari jawaban su27 ) untuk mensub-mount aplikasi Anda di server WSGI mandiri yang Anda gunakan. (Lihat Contoh memasang aplikasi Anda di atas dengan benar untuk kode yang akan digunakan).
Sean Vieira
sumber
@jknupp - melihat flask.Flask#create_url_adapterdan werkzeug.routing.Map#bind_to_environkelihatannya seperti itu harus bekerja - bagaimana anda menjalankan kode? (Aplikasi sebenarnya perlu dipasang di sub-jalur di lingkungan WSGI untuk url_formengembalikan nilai yang diharapkan.)
Sean Vieira
Saya menjalankan persis seperti yang Anda tulis, tetapi menambahkan app = Flask ( nama ) dan app.run (debug = True)
jeffknupp
4
@jknupp - itulah masalahnya - Anda harus benar-benar me-mount aplikasi sebagai sub-bagian dari aplikasi yang lebih besar (apapun yang berbicara WSGI akan dilakukan). Saya telah membuat sebuah contoh inti dan memperbarui jawaban saya untuk membuatnya lebih jelas bahwa saya mengasumsikan lingkungan WSGI sub-mount, bukan lingkungan WSGI yang berdiri sendiri di belakang proxy yang hanya meneruskan permintaan sub-jalur.
Sean Vieira
3
Ini bekerja, menggunakan DispatcherMiddlewarependekatan, saat menjalankan flask dengan sendirinya. Sepertinya tidak bisa mendapatkan ini berfungsi saat berlari di belakang Gunicorn.
Justin
1
Cara mount ke sub path di uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. detail lihat (dokumen uwsgi) [ flask.pocoo.org/docs/1.0/deploying/uwsgi/]
todaynowork
96

Anda dapat meletakkan rute Anda di cetak biru:

bp = Blueprint('burritos', __name__,
                        template_folder='templates')

@bp.route("/")
def index_page():
  return "This is a website about burritos"

@bp.route("/about")
def about_page():
  return "This is a website about burritos"

Kemudian Anda mendaftarkan cetak biru tersebut dengan aplikasi menggunakan awalan:

app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
Miguel
sumber
2
Hai Miguel; tahukah Anda perbedaan antara mendaftarkan url_prefix untuk cetak biru seperti yang Anda lakukan di bawah ini dengan app.register_blueprintdan antara mendaftarkannya saat Anda membuat instance objek Blueprint di atas, dengan meneruskan url_prefix='/abc/123? Terima kasih!
aralar
4
Perbedaannya adalah memiliki awalan URL dalam register_blueprintpanggilan memberi aplikasi kebebasan untuk "memasang" cetak biru di mana pun yang diinginkannya, atau bahkan memasang cetak biru yang sama beberapa kali pada URL yang berbeda. Jika Anda meletakkan awalan di cetak biru itu sendiri, Anda membuatnya lebih mudah untuk aplikasi, tetapi Anda memiliki fleksibilitas yang kurang.
Miguel
Terima kasih!! Itu sangat membantu. Saya bingung dengan redundansi yang terlihat, tetapi saya melihat trade-off antara kedua opsi tersebut.
aralar
Dan sebenarnya, saya tidak pernah mencoba ini, tetapi kemungkinan Anda dapat menggabungkan awalan URL baik di cetak biru dan aplikasi, dengan tangan awalan aplikasi, diikuti dengan awalan cetak biru.
Miguel
5
Perhatikan bahwa Anda perlu mendaftarkan cetak biru setelah fungsi dekorasi blueprint.route.
Quint
53

Anda harus memperhatikan bahwa APPLICATION_ROOTBUKAN untuk tujuan ini.

Yang harus Anda lakukan adalah menulis middleware untuk membuat perubahan berikut:

  1. ubah PATH_INFOuntuk menangani url yang diawali.
  2. ubah SCRIPT_NAMEuntuk menghasilkan url awalan.

Seperti ini:

class PrefixMiddleware(object):

    def __init__(self, app, prefix=''):
        self.app = app
        self.prefix = prefix

    def __call__(self, environ, start_response):

        if environ['PATH_INFO'].startswith(self.prefix):
            environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
            environ['SCRIPT_NAME'] = self.prefix
            return self.app(environ, start_response)
        else:
            start_response('404', [('Content-Type', 'text/plain')])
            return ["This url does not belong to the app.".encode()]

Bungkus aplikasi Anda dengan middleware, seperti ini:

from flask import Flask, url_for

app = Flask(__name__)
app.debug = True
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix='/foo')


@app.route('/bar')
def bar():
    return "The URL for this page is {}".format(url_for('bar'))


if __name__ == '__main__':
    app.run('0.0.0.0', 9010)

Kunjungi http://localhost:9010/foo/bar,

Anda akan mendapatkan hasil yang benar: The URL for this page is /foo/bar

Dan jangan lupa untuk menyetel domain cookie jika Anda perlu.

Solusi ini diberikan oleh inti Larivact . Ini APPLICATION_ROOTbukan untuk pekerjaan ini, meskipun kelihatannya seperti itu. Benar-benar membingungkan.

su27
sumber
4
Terima kasih telah menambahkan jawaban ini. Mencoba solusi lain yang diposting di sini, tetapi ini adalah satu-satunya yang berhasil untuk saya. A +++ Saya diterapkan di IIS menggunakan wfastcgi.py
sytech
"Ini APPLICATION_ROOTbukan untuk pekerjaan ini" - di sinilah kesalahan saya. Saya berharap Blueprint's url_prefixparameter dan APPLICATION_ROOTdigabungkan secara default, sehingga saya bisa memiliki APPLICATION_ROOTurl lingkup untuk seluruh aplikasi, dan url_prefixurl ruang lingkup dalam APPLICATION_ROOThanya untuk cetak biru individu. Sigh
Monkpit
Lihat inti ini untuk contoh apa yang saya coba lakukan dengan menggunakan APPLICATION_ROOT.
Monkpit
2
Jika Anda menggunakan gunicorn, SCRIPT_NAME sudah didukung. Tetapkan sebagai variabel lingkungan atau teruskan sebagai header http: docs.gunicorn.org/en/stable/faq.html
blurrcat
1
Kode tersebut tidak berfungsi untuk saya. Setelah beberapa penelitian, saya menemukan ini setelah yang lain dalam __call__metode: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)menggunakanfrom werkzeug.wrappers import BaseResponse as Response
Louis Becker
10

Ini lebih merupakan jawaban python daripada jawaban Flask / werkzeug; tapi sederhana dan berhasil.

Jika, seperti saya, Anda ingin pengaturan aplikasi Anda (dimuat dari .inifile) juga berisi awalan aplikasi Flask Anda (dengan demikian, tidak memiliki nilai yang ditetapkan selama penerapan, tetapi selama waktu proses), Anda dapat memilih hal berikut:

def prefix_route(route_function, prefix='', mask='{0}{1}'):
  '''
    Defines a new route function with a prefix.
    The mask argument is a `format string` formatted with, in that order:
      prefix, route
  '''
  def newroute(route, *args, **kwargs):
    '''New function to prefix the route'''
    return route_function(mask.format(prefix, route), *args, **kwargs)
  return newroute

Bisa dibilang, ini agak hackish dan bergantung pada fakta bahwa fungsi rute Flask membutuhkan sebuah routeargumen posisi pertama.

Anda bisa menggunakannya seperti ini:

app = Flask(__name__)
app.route = prefix_route(app.route, '/your_prefix')

NB: Tidak ada artinya bahwa Anda dapat menggunakan variabel di awalan (misalnya dengan menyetelnya ke /<prefix>), lalu memproses awalan ini di fungsi yang Anda hiasi dengan file @app.route(...). Jika Anda melakukannya, Anda jelas harus mendeklarasikan prefixparameter dalam fungsi yang didekorasi. Selain itu, Anda mungkin ingin memeriksa prefiks yang dikirimkan terhadap beberapa aturan, dan mengembalikan 404 jika pemeriksaan gagal. Untuk menghindari implementasi ulang kustom 404, silakan from werkzeug.exceptions import NotFounddan kemudian raise NotFound()jika pemeriksaan gagal.

7heo.tk
sumber
Sederhana dan lebih efisien daripada menggunakan Blueprint. Terima kasih telah berbagi!
Bocah HK
5

Jadi, saya percaya bahwa jawaban yang valid untuk ini adalah: awalan harus dikonfigurasi dalam aplikasi server sebenarnya yang Anda gunakan saat pengembangan selesai. Apache, nginx, dll.

Namun, jika Anda ingin ini bekerja selama pengembangan sambil menjalankan aplikasi Flask dalam debug, lihat inti berikut ini. .

Flask DispatcherMiddleware untuk menyelamatkan!

Saya akan menyalin kode di sini untuk anak cucu:

"Serve a Flask app on a sub-url during localhost development."

from flask import Flask


APPLICATION_ROOT = '/spam'


app = Flask(__name__)
app.config.from_object(__name__)  # I think this adds APPLICATION_ROOT
                                  # to the config - I'm not exactly sure how!
# alternatively:
# app.config['APPLICATION_ROOT'] = APPLICATION_ROOT


@app.route('/')
def index():
    return 'Hello, world!'


if __name__ == '__main__':
    # Relevant documents:
    # http://werkzeug.pocoo.org/docs/middlewares/
    # http://flask.pocoo.org/docs/patterns/appdispatch/
    from werkzeug.serving import run_simple
    from werkzeug.wsgi import DispatcherMiddleware
    app.config['DEBUG'] = True
    # Load a dummy app at the root URL to give 404 errors.
    # Serve app at APPLICATION_ROOT for localhost development.
    application = DispatcherMiddleware(Flask('dummy_app'), {
        app.config['APPLICATION_ROOT']: app,
    })
    run_simple('localhost', 5000, application, use_reloader=True)

Sekarang, saat menjalankan kode di atas sebagai aplikasi Flask yang berdiri sendiri, http://localhost:5000/spam/akan munculHello, world! .

Dalam komentar di jawaban lain, saya menyatakan bahwa saya ingin melakukan sesuatu seperti ini:

from flask import Flask, Blueprint

# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
app.run()

# I now would like to be able to get to my route via this url:
# http://host:8080/api/some_submodule/record/1/

Menerapkan DispatcherMiddlewarecontoh buatan saya:

from flask import Flask, Blueprint
from flask.serving import run_simple
from flask.wsgi import DispatcherMiddleware

# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
application = DispatcherMiddleware(Flask('dummy_app'), {
    app.config['APPLICATION_ROOT']: app
})
run_simple('localhost', 5000, application, use_reloader=True)

# Now, this url works!
# http://host:8080/api/some_submodule/record/1/
Monkpit
sumber
"Jadi, saya percaya bahwa jawaban yang valid untuk ini adalah: awalan harus dikonfigurasi dalam aplikasi server sebenarnya yang Anda gunakan saat pengembangan selesai. Apache, nginx, dll." Masalahnya ada pada pengalihan; jika Anda memiliki sebuah prefiks dan tidak memasangnya di Flask, maka ketika ia mengalihkan alih-alih pergi ke / yourprefix / path / ke / url, ia hanya pergi ke / path / ke / url. Apakah ada cara untuk menyiapkan, di nginx atau Apache, apa awalannya?
Jordan Reiter
Cara saya mungkin akan melakukan ini hanya dengan menggunakan alat manajemen konfigurasi seperti boneka atau koki, dan mengatur awalan di sana dan kemudian alat tersebut menyebarkan perubahan ke file konfigurasi di mana ia harus pergi. Saya bahkan tidak akan berpura-pura bahwa saya tahu apa yang saya bicarakan untuk apache atau nginx. Karena pertanyaan / jawaban ini khusus untuk python, saya akan mendorong Anda untuk memposting skenario Anda sebagai pertanyaan terpisah. Jika Anda melakukan ini, silakan tautkan ke pertanyaan di sini!
Monkpit
2

Cara lain yang sama sekali berbeda adalah dengan mountpoints di uwsgi.

Dari dokumen tentang Hosting beberapa aplikasi dalam proses yang sama ( tautan permanen ).

Dalam uwsgi.iniAnda menambahkan

[uwsgi]
mount = /foo=main.py
manage-script-name = true

# also stuff which is not relevant for this, but included for completeness sake:    
module = main
callable = app
socket = /tmp/uwsgi.sock

Jika Anda tidak memanggil file main.pyAnda, Anda perlu mengubah mountdanmodule

Anda main.pybisa terlihat seperti ini:

from flask import Flask, url_for
app = Flask(__name__)
@app.route('/bar')
def bar():
  return "The URL for this page is {}".format(url_for('bar'))
# end def

Dan konfigurasi nginx (sekali lagi untuk kelengkapan):

server {
  listen 80;
  server_name example.com

  location /foo {
    include uwsgi_params;
    uwsgi_pass unix:///temp/uwsgi.sock;
  }
}

Sekarang pemanggilan example.com/foo/barakan ditampilkan /foo/barsebagai dikembalikan oleh flask url_for('bar'), karena ia beradaptasi secara otomatis. Dengan begitu, tautan Anda akan berfungsi tanpa masalah awalan.

luckydonald
sumber
2
from flask import Flask

app = Flask(__name__)

app.register_blueprint(bp, url_prefix='/abc/123')

if __name__ == "__main__":
    app.run(debug='True', port=4444)


bp = Blueprint('burritos', __name__,
                        template_folder='templates')

@bp.route('/')
def test():
    return "success"
abimanyu
sumber
1
Harap pertimbangkan untuk menambahkan penjelasan.
jpp
1
Dua penjelasan bagus yang saya temukan ada di exploreflask dan dokumen resmi
yuriploc
1

Saya membutuhkan yang serupa yang disebut "akar konteks". Saya melakukannya di file conf di bawah /etc/httpd/conf.d/ menggunakan WSGIScriptAlias:

myapp.conf:

<VirtualHost *:80>
    WSGIScriptAlias /myapp /home/<myid>/myapp/wsgi.py

    <Directory /home/<myid>/myapp>
        Order deny,allow
        Allow from all
    </Directory>

</VirtualHost>

Jadi sekarang saya dapat mengakses aplikasi saya sebagai: http: // localhost: 5000 / myapp

Lihat panduan - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

dganesh2002
sumber
1

Solusi saya di mana flask dan aplikasi PHP hidup berdampingan dengan nginx dan PHP5.6

KEEP Flask di root dan PHP di subdirektori

sudo vi /etc/php/5.6/fpm/php.ini

Tambahkan 1 baris

cgi.fix_pathinfo=0
sudo vi /etc/php/5.6/fpm/pool.d/www.conf
listen = /run/php/php5.6-fpm.sock

uwsgi

sudo vi /etc/nginx/sites-available/default

GUNAKAN LOKASI NESTED untuk PHP dan biarkan FLASK tetap di root

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # SSL configuration
    #
    # listen 443 ssl default_server;
    # listen [::]:443 ssl default_server;
    #
    # Note: You should disable gzip for SSL traffic.
    # See: https://bugs.debian.org/773332
    #
    # Read up on ssl_ciphers to ensure a secure configuration.
    # See: https://bugs.debian.org/765782
    #
    # Self signed certs generated by the ssl-cert package
    # Don't use them in a production server!
    #
    # include snippets/snakeoil.conf;

    root /var/www/html;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.php index.nginx-debian.html;

    server_name _;

    # Serve a static file (ex. favico) outside static dir.
    location = /favico.ico  {    
        root /var/www/html/favico.ico;    
    }

    # Proxying connections to application servers
    location / {
        include            uwsgi_params;
        uwsgi_pass         127.0.0.1:5000;
    }

    location /pcdp {
        location ~* \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    location /phpmyadmin {
        location ~* \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #   include snippets/fastcgi-php.conf;
    #
    #   # With php7.0-cgi alone:
    #   fastcgi_pass 127.0.0.1:9000;
    #   # With php7.0-fpm:
    #   fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #   deny all;
    #}
}

BACA dengan cermat https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

Kita perlu memahami pencocokan lokasi (tidak ada): Jika tidak ada pengubah, lokasi diartikan sebagai pencocokan awalan. Ini berarti lokasi yang diberikan akan dicocokkan dengan awal URI permintaan untuk menentukan kecocokan. =: Jika digunakan tanda sama dengan, blok ini akan dianggap cocok jika URI permintaan sama persis dengan lokasi yang diberikan. ~: Jika ada pengubah tilde, lokasi ini akan ditafsirkan sebagai pencocokan ekspresi reguler peka huruf besar kecil. ~ *: Jika pengubah tilde dan tanda bintang digunakan, blok lokasi akan ditafsirkan sebagai pencocokan ekspresi reguler peka huruf besar / kecil. ^ ~: Jika pengubah carat dan tilde ada, dan jika blok ini dipilih sebagai pencocokan ekspresi non-reguler terbaik, pencocokan ekspresi reguler tidak akan berlangsung.

Urutan itu penting, dari deskripsi "lokasi" nginx:

Untuk menemukan lokasi yang cocok dengan permintaan yang diberikan, nginx pertama-tama memeriksa lokasi yang ditentukan menggunakan string prefiks (lokasi prefiks). Di antara mereka, lokasi dengan awalan pencocokan terpanjang dipilih dan diingat. Kemudian ekspresi reguler diperiksa, dalam urutan kemunculannya di file konfigurasi. Pencarian ekspresi reguler berakhir pada kecocokan pertama, dan konfigurasi terkait digunakan. Jika tidak ditemukan kecocokan dengan ekspresi reguler, maka konfigurasi lokasi prefiks yang diingat sebelumnya akan digunakan.

Itu berarti:

First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)
Jayanta
sumber
1

Untuk orang-orang yang masih kesulitan dengan ini, contoh pertama memang berfungsi, tetapi contoh lengkapnya ada di sini jika Anda memiliki aplikasi Flask yang tidak Anda kendalikan:

from os import getenv
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple
from custom_app import app

application = DispatcherMiddleware(
    app, {getenv("REBROW_BASEURL", "/rebrow"): app}
)

if __name__ == "__main__":
    run_simple(
        "0.0.0.0",
        int(getenv("REBROW_PORT", "5001")),
        application,
        use_debugger=False,
        threaded=True,
    )
vishnugopal.dll
sumber