Thêm tiền tố vào tất cả các tuyến Flask


98

Tôi có một tiền tố mà tôi muốn thêm vào mọi tuyến đường. Ngay bây giờ tôi thêm một hằng số vào route ở mọi định nghĩa. Có cách nào để làm điều này tự động không?

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"

Câu trả lời:


75

Câu trả lời phụ thuộc vào cách bạn đang phục vụ ứng dụng này.

Được gắn phụ bên trong một thùng chứa WSGI khác

Giả sử rằng bạn sẽ chạy ứng dụng này bên trong vùng chứa WSGI (mod_wsgi, uwsgi, gunicorn, v.v.); bạn cần thực sự gắn kết, tại tiền tố đó , ứng dụng như một phần con của vùng chứa WSGI đó (bất cứ thứ gì nói WSGI sẽ thực hiện) và đặt APPLICATION_ROOTgiá trị cấu hình thành tiền tố của bạn:

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/"

Việc đặt APPLICATION_ROOTgiá trị cấu hình chỉ đơn giản là giới hạn cookie phiên của Flask ở tiền tố URL đó. Mọi thứ khác sẽ được tự động xử lý cho bạn nhờ khả năng xử lý WSGI tuyệt vời của Flask và Werkzeug.

Một ví dụ về việc gắn phụ đúng cách ứng dụng của bạn

Nếu bạn không chắc đoạn đầu tiên có nghĩa là gì, hãy xem ứng dụng mẫu này với Flask được gắn bên trong nó:

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)

Yêu cầu ủy quyền đối với ứng dụng

Mặt khác, nếu bạn đang chạy ứng dụng Flask của mình tại gốc của vùng chứa WSGI của nó và gửi các yêu cầu proxy tới nó (ví dụ: nếu nó đang là FastCGI hoặc nếu nginx đang proxy_pass-ing yêu cầu điểm cuối phụ đến máy chủ uwsgi/ độc lập của geventbạn thì bạn có thể:

  • Sử dụng Bản thiết kế chi tiết, như Miguel đã chỉ ra trong câu trả lời của mình .
  • hoặc sử dụng câu trả lờiDispatcherMiddleware từ werkzeug(hoặc câu trả lờiPrefixMiddleware từ su27 ) để gắn kết phụ ứng dụng của bạn trong máy chủ WSGI độc lập mà bạn đang sử dụng. (Xem Ví dụ về gắn phụ đúng cách ứng dụng của bạn ở trên để có mã sử dụng).

@jknupp - nhìn vào flask.Flask#create_url_adapterwerkzeug.routing.Map#bind_to_environcó vẻ như nó sẽ hoạt động - bạn đã chạy mã như thế nào? (Ứng dụng thực sự cần được gắn trên đường dẫn phụ trong môi trường WSGI url_forđể trả về giá trị mong đợi.)
Sean Vieira

Tôi đã chạy chính xác những gì bạn đã viết, nhưng đã thêm app = Flask ( tên ) và app.run (gỡ lỗi = True)
jeffknupp

4
@jknupp - đó là vấn đề - bạn sẽ cần thực sự gắn ứng dụng này như một phần phụ của một ứng dụng lớn hơn (bất cứ điều gì nói WSGI sẽ làm được). Tôi đã tóm tắt một ý chính ví dụ và cập nhật câu trả lời của mình để làm rõ hơn rằng tôi đang giả định một môi trường WSGI được gắn phụ, không phải là một môi trường WSGI độc lập đằng sau một proxy chỉ chuyển tiếp các yêu cầu đường dẫn phụ.
Sean Vieira

3
Điều này hoạt động, sử dụng cách DispatcherMiddlewaretiếp cận, khi chạy bình một mình. Dường như không thể làm cho điều này hoạt động khi chạy phía sau Gunicorn.
Justin

1
Cách mount vào đường dẫn phụ trong uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app. tham khảo chi tiết tại (tài liệu uwsgi) [ flask.pocoo.org/docs/1.0/deploy/uwsgi/]
todaynowork

94

Bạn có thể đưa các tuyến đường của mình vào một bản thiết kế:

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"

Sau đó, bạn đăng ký bản thiết kế với ứng dụng bằng tiền tố:

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

2
Chào Miguel; bạn có biết sự khác biệt giữa đăng ký url_prefix cho một bản thiết kế như bạn đã làm bên dưới với app.register_blueprintvà giữa việc đăng ký nó khi bạn khởi tạo đối tượng Bản thiết kế ở trên, bằng cách chuyển qua url_prefix='/abc/123? Cảm ơn bạn!
aralar

4
Sự khác biệt là việc có tiền tố URL trong lệnh register_blueprintgọi cho phép ứng dụng tự do "gắn" bản thiết kế ở bất kỳ đâu nó muốn hoặc thậm chí gắn cùng một bản thiết kế nhiều lần trên các URL khác nhau. Nếu bạn đặt tiền tố trong bản thiết kế, bạn sẽ làm cho ứng dụng dễ dàng hơn, nhưng bạn kém linh hoạt hơn.
Miguel

Cảm ơn bạn!! Điều đó rất hữu ích. Tôi đã bối rối bởi sự dư thừa rõ ràng nhưng tôi thấy sự cân bằng giữa hai lựa chọn.
aralar

Và thực ra, tôi chưa bao giờ thử điều này, nhưng có khả năng bạn có thể kết hợp tiền tố URL cả trong bản thiết kế và ứng dụng, với tiền tố nắm tay của ứng dụng, theo sau là tiền tố bản thiết kế.
Miguel

4
Lưu ý rằng cần phải đăng ký bản thiết kế sau các chức năng được trang trí blueprint.route.
Quint

53

Bạn nên lưu ý rằng APPLICATION_ROOTKHÔNG dành cho mục đích này.

Tất cả những gì bạn phải làm là viết một phần mềm trung gian để thực hiện các thay đổi sau:

  1. sửa đổi PATH_INFOđể xử lý url có tiền tố.
  2. sửa đổi SCRIPT_NAMEđể tạo url có tiền tố.

Như thế này:

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()]

Gói ứng dụng của bạn bằng phần mềm trung gian, như sau:

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)

Ghé thăm http://localhost:9010/foo/bar,

Bạn sẽ nhận được kết quả phù hợp: The URL for this page is /foo/bar

Và đừng quên đặt miền cookie nếu bạn cần.

Giải pháp này được đưa ra bởi ý chính của Larivact . Các APPLICATION_ROOTkhông phải dành cho công việc này, mặc dù nó trông giống như được. Nó thực sự khó hiểu.


4
Cảm ơn đã thêm câu trả lời này. Đã thử các giải pháp khác được đăng ở đây, nhưng đây là giải pháp duy nhất phù hợp với tôi. A +++ Tôi đã triển khai trên IIS bằng cách sử dụng wfastcgi.py
sytech

" APPLICATION_ROOTKhông phải cho công việc này" - đây là nơi tôi đã sai. Tôi muốn Blueprint's url_prefixtham số và APPLICATION_ROOTđược kết hợp theo mặc định, vì vậy mà tôi có thể có APPLICATION_ROOTcác url phạm vi cho toàn bộ ứng dụng, và url_prefixcác url phạm vi trong vòng APPLICATION_ROOTchỉ dành riêng cho các kế hoạch chi tiết cá nhân. Thở dài
Monkpit

Xem ý chính này để biết ví dụ về những gì tôi đang cố gắng thực hiện bằng cách sử dụng APPLICATION_ROOT.
Monkpit

2
Nếu bạn đang sử dụng gunicorn, SCRIPT_NAME đã được hỗ trợ. Thiết lập nó như là một biến môi trường hoặc vượt qua nó thông qua như một http header: docs.gunicorn.org/en/stable/faq.html
blurrcat

1
Mã như nó đứng không hoạt động đối với tôi. Sau khi một số nghiên cứu, tôi đã đưa ra với điều này sau khi khác trong __call__phương pháp: response = Response('That url is not correct for this application', status=404) return response(environ, start_response)sử dụngfrom werkzeug.wrappers import BaseResponse as Response
Louis Becker

10

Đây là câu trả lời của python hơn là câu trả lời của Flask / werkzeug; nhưng nó đơn giản và hoạt động.

Nếu, giống như tôi, bạn muốn cài đặt ứng dụng của mình (được tải từ một .initệp) cũng chứa tiền tố của ứng dụng Flask của bạn (do đó, không phải đặt giá trị trong quá trình triển khai, nhưng trong thời gian chạy), bạn có thể chọn thực hiện như sau:

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

Có thể cho rằng, điều này hơi khó hiểu và dựa trên thực tế là hàm định tuyến Flask yêu cầu một routeđối số vị trí đầu tiên.

Bạn có thể sử dụng nó như thế này:

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

NB: Chẳng có giá trị gì khi bạn có thể sử dụng một biến trong tiền tố (ví dụ bằng cách đặt nó thành /<prefix>), và sau đó xử lý tiền tố này trong các hàm mà bạn trang trí bằng @app.route(...). Nếu bạn làm như vậy, bạn rõ ràng phải khai báo prefixtham số trong (các) hàm được trang trí của bạn. Ngoài ra, bạn có thể muốn kiểm tra tiền tố đã gửi theo một số quy tắc và trả lại 404 nếu kiểm tra không thành công. Để tránh triển khai lại tùy chỉnh 404, vui lòng from werkzeug.exceptions import NotFoundvà sau đó raise NotFound()nếu kiểm tra không thành công.


Nó đơn giản và hiệu quả hơn sử dụng Blueprint. Cám ơn vì đã chia sẻ!
HK boy

5

Vì vậy, tôi tin rằng câu trả lời hợp lệ cho điều này là: tiền tố phải được định cấu hình trong ứng dụng máy chủ thực tế mà bạn sử dụng khi quá trình phát triển hoàn tất. Apache, nginx, v.v.

Tuy nhiên, nếu bạn muốn điều này hoạt động trong quá trình phát triển khi chạy ứng dụng Flask trong gỡ lỗi, hãy xem ý chính này .

Bình thí nghiệm DispatcherMiddleware đến để giải cứu!

Tôi sẽ sao chép mã ở đây cho hậu thế:

"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)

Bây giờ, khi chạy mã trên dưới dạng ứng dụng Flask độc lập, http://localhost:5000/spam/sẽ hiển thịHello, world! .

Trong một bình luận về một câu trả lời khác, tôi bày tỏ rằng tôi muốn làm điều gì đó như thế này:

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/

Áp dụng DispatcherMiddlewarecho ví dụ có sẵn của tôi:

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/

"Vì vậy, tôi tin rằng câu trả lời hợp lệ cho điều này là: tiền tố phải được định cấu hình trong ứng dụng máy chủ thực tế mà bạn sử dụng khi phát triển hoàn tất. Apache, nginx, v.v." Vấn đề là ở chuyển hướng; nếu bạn có một tiền tố và không thiết lập nó trong Flask, thì khi nó chuyển hướng thay vì đi tới / yourprefix / path / to / url, nó chỉ chuyển đến / path / to / url. Có cách nào để thiết lập, trong nginx hoặc Apache, tiền tố phải là gì không?
Jordan Reiter

Cách mà tôi có thể làm điều này là chỉ sử dụng một công cụ quản lý cấu hình như con rối hoặc đầu bếp, và đặt tiền tố ở đó và sau đó để công cụ truyền thay đổi đến các tệp cấu hình nơi nó cần đến. Tôi thậm chí sẽ không giả vờ rằng tôi biết những gì tôi đang nói về apache hoặc nginx. Vì câu hỏi / câu trả lời này dành riêng cho python, tôi khuyến khích bạn đăng kịch bản của mình dưới dạng một câu hỏi riêng biệt. Nếu bạn làm điều này, vui lòng liên kết đến câu hỏi ở đây!
Monkpit

2

Một cách hoàn toàn khác là với các điểm gắn kết trong uwsgi.

Từ tài liệu về Lưu trữ nhiều ứng dụng trong cùng một quy trình ( liên kết cố định ).

Trong của uwsgi.inibạn, bạn thêm

[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

Nếu bạn không gọi tập tin của bạn main.py, bạn cần phải thay đổi cả mountmodule

Của bạn main.pycó thể trông như thế này:

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

Và cấu hình nginx (một lần nữa để hoàn thiện):

server {
  listen 80;
  server_name example.com

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

Bây giờ, cuộc gọi example.com/foo/barsẽ hiển thị /foo/barnhư được trả về bởi bình url_for('bar'), vì nó tự động điều chỉnh. Bằng cách đó, các liên kết của bạn sẽ hoạt động mà không gặp vấn đề về tiền tố.


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"

1
Vui lòng xem xét thêm một lời giải thích.
jpp

1
Hai lời giải thích thú vị mà tôi tìm thấy là trong exploreflask và các tài liệu chính thức
yuriploc

1

Tôi cần tương tự như vậy được gọi là "gốc ngữ cảnh". Tôi đã làm điều đó trong tệp conf dưới /etc/httpd/conf.d/ bằng cách sử dụng WSGIScriptAlias:

myapp.conf:

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

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

</VirtualHost>

Vì vậy, bây giờ tôi có thể truy cập ứng dụng của mình bằng: http: // localhost: 5000 / myapp

Xem hướng dẫn - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html


1

Giải pháp của tôi trong đó ứng dụng flask và PHP cùng tồn tại nginx và PHP5.6

GIỮ Flask trong thư mục gốc và PHP trong thư mục con

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

Thêm 1 dòng

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

SỬ DỤNG CÁC VỊ TRÍ NESTED cho PHP và để FLASK vẫn ở trong thư mục gốc

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;
    #}
}

ĐỌC kỹ https://www.digitalocean.com/community/tutorials/und hieu-nginx-server-and-location-block-selection-algorithm

Chúng ta cần hiểu đối sánh vị trí (không có): Nếu không có bổ ngữ nào, vị trí được hiểu là đối sánh tiền tố. Điều này có nghĩa là vị trí được cung cấp sẽ được khớp với phần đầu của URI yêu cầu để xác định vị trí phù hợp. =: Nếu dấu bằng được sử dụng, khối này sẽ được coi là khớp nếu URI yêu cầu khớp chính xác với vị trí đã cho. ~: Nếu có công cụ sửa đổi dấu ngã, vị trí này sẽ được hiểu là đối sánh biểu thức chính quy phân biệt chữ hoa chữ thường. ~ *: Nếu sử dụng công cụ sửa đổi dấu ngã và dấu hoa thị, khối vị trí sẽ được hiểu là đối sánh biểu thức chính quy không phân biệt chữ hoa chữ thường. ^ ~: Nếu có công cụ sửa đổi dấu ngã và dấu ngã và nếu khối này được chọn là đối sánh cụm từ không chính quy tốt nhất, thì đối sánh biểu thức chính quy sẽ không diễn ra.

Thứ tự rất quan trọng, từ mô tả "vị trí" của nginx:

Để tìm vị trí phù hợp với một yêu cầu nhất định, trước tiên nginx sẽ kiểm tra các vị trí được xác định bằng các chuỗi tiền tố (vị trí tiền tố). Trong số đó, vị trí có tiền tố phù hợp dài nhất được chọn và ghi nhớ. Sau đó, các biểu thức chính quy được kiểm tra, theo thứ tự xuất hiện của chúng trong tệp cấu hình. Việc tìm kiếm các biểu thức chính quy kết thúc vào lần so khớp đầu tiên và cấu hình tương ứng được sử dụng. Nếu không tìm thấy kết quả phù hợp với một biểu thức chính quy thì cấu hình của vị trí tiền tố đã nhớ trước đó sẽ được sử dụng.

Nó có nghĩa là:

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

1

Đối với những người vẫn đang gặp khó khăn với điều này, ví dụ đầu tiên hoạt động, nhưng ví dụ đầy đủ ở đây nếu bạn có ứng dụng Flask không nằm trong tầm kiểm soát của bạn:

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,
    )
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.