Немецкий [ править ]
Это незаконченная статья. Вы можете помочь проекту, исправив и дополнив её .
В частности, следует уточнить сведения о:
- Немецкий язык
- Немецкие существительные
- Немецкие существительные, склонение (e)s e en
- Средний род/de
- Инструменты/de
- Слова из 8 букв/de
- Статьи со ссылками на Википедию/de
- Иноязычное слово дня
- Статьи со звучащими примерами произношения
- Нужны сведения о семантике/de
- Нужна этимология/de
- Статьи, нуждающиеся в доработке/de
What is Werkzeug?
Posted by Patrick Kennedy Last updated April 5th, 2021
Share this tutorial
This article explains what Werkzeug is and how Flask uses it for its core HTTP functionality. Along the way, you’ll develop your own WSGI-compatible application using Werkzeug to create a Flask-like web framework!
This article assumes that you have prior experience with Flask. If you’re interested in learning more about Flask, check out my course on how to build, test, and deploy a Flask application:
Developing Web Applications with Python and Flask
Contents
- Flask Dependencies
- What is Werkzeug?
- Hello World App
- Installation
- Application
- Development Server
- Redis
- Display Movies
Flask Dependencies
You may have already noticed, but every time you install Flask, you also install the following dependencies:
Flask is a wrapper around all of them.
$ pip install Flask $ pip freeze click==7.1.2 Flask==1.1.2 itsdangerous==1.1.0 Jinja2==2.11.3 MarkupSafe==1.1.1 Werkzeug==1.0.1 # .
What is Werkzeug?
Werkzeug is a collection of libraries that can be used to create a WSGI (Web Server Gateway Interface) compatible web application in Python.
A WSGI (Web Server Gateway Interface) server is necessary for Python web applications since a web server cannot communicate directly with Python. WSGI is an interface between a web server and a Python-based web application.
Put another way, Werkzeug provides a set of utilities for creating a Python application that can talk to a WSGI server, like Gunicorn.
Werkzeug provides the following functionality (which Flask uses):
- Request processing
- Response handling
- URL routing
- Middleware
- HTTP utilities
- Exception handling
It also provides a basic development server with hot reloading.
Let’s dive into an example of building a web application using Werkzeug. We’ll also look at how Flask implements similar functionality.
Hello World App
As an introduction to Werkzeug, let’s start by creating a «Hello World» app using some of the key functionality provided by Werkzeug.
You can find the source code for the project discussed in this article on GitLab: https://gitlab.com/patkennedy79/werkzeug_movie_app.
Installation
Start by creating a new project:
$ mkdir werkzeug_movie_app $ cd werkzeug_movie_app $ python3 -m venv venv $ source venv/bin/activate (venv)$
Install Werkzeug, Jinja, and redis-py:
(venv)$ pip install Werkzeug Jinja2 redis (venv)$ pip freeze > requirements.txt
Redis will be used as the data storage solution for storing movie data.
Application
Werkzeug is a collection of libraries used to build a WSGI-compatible web application. It doesn’t provide a high-level class, like Flask , for scaffolding out a full web application. Instead, you need to create the application yourself from Werkzeug’s libraries.
Create a new app.py file in the top-level folder of your project:
from werkzeug.wrappers import Request, Response class MovieApp(object): """Implements a WSGI application for managing your favorite movies.""" def __init__(self): pass def dispatch_request(self, request): """Dispatches the request.""" return Response('Hello World!') def wsgi_app(self, environ, start_response): """WSGI application that processes requests and returns responses.""" request = Request(environ) response = self.dispatch_request(request) return response(environ, start_response) def __call__(self, environ, start_response): """The WSGI server calls this method as the WSGI application.""" return self.wsgi_app(environ, start_response) def create_app(): """Application factory function that returns an instance of MovieApp.""" app = MovieApp() return app
The MovieApp class implements a WSGI-compatible web application, which processes requests from different users and generates responses back to the users. Here’s the flow of how this class interfaces with a WSGI server:
When a request comes in, it’s processed in wsgi_app() :
def wsgi_app(self, environ, start_response): """WSGI application that processes requests and returns responses.""" request = Request(environ) response = self.dispatch_request(request) return response(environ, start_response)
The environment ( environ ) is automatically processed in the Request class to create a request object. The request is then processed in dispatch_request() . For this initial example, dispatch_request() returns a response of ‘Hello World!’. The response is then returned from wsgi_app() .
Flask Comparison:
MovieApp is an simplified version of the Flask class.
Within the Flask class, the wsgi_app() is the actual WSGI application that interfaces with the WSGI server. Also, dispatch_request() and full_dispatch_request() are used to do the request dispatching, which matches the URL to the applicable view function and handles exceptions.
Development Server
Add the following code to the bottom of app.py to run the Werkzeug development server:
if __name__ == '__main__': # Run the Werkzeug development server to serve the WSGI application (MovieApp) from werkzeug.serving import run_simple app = create_app() run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)
Run the application:
(venv)$ python app.py
Navigate to http://localhost:5000 to see the ‘Hello World!’ Message.
Flask Comparison:
Within the Flask class, there’s an equivalent run() method that utilizes the Werkzeug development server.
Middleware for Serving Static Files
In web applications, middleware is a software component that can be added to the request/response processing pipeline to perform a specific function.
One important function for a web server/application to perform is serving static files (CSS, JavaScript, and image files). Werkzeug provides a middleware for this functionality called SharedDataMiddleware .
SharedDataMiddleware is ideally suited for working with the Werkzeug development server to serve static files.
For a production environment, you’ll want to switch out the Werkzeug development server and SharedDataMiddleware for a web server such as Nginx and a WSGI server such as Gunicorn.
To utilize SharedDataMiddleware , start by adding a new folder called «static» to the project with «css» and «img» folders:
├── app.py ├── requirements.txt └── static ├── css └── img
Next, expand the application factory function:
def create_app(): """Application factory function that returns an instance of MovieApp.""" app = MovieApp() app.wsgi_app = SharedDataMiddleware(app.wsgi_app, '/static': os.path.join(os.path.dirname(__file__), 'static') >) return app
Update the imports at the top:
import os from werkzeug.middleware.shared_data import SharedDataMiddleware from werkzeug.wrappers import Request, Response
Now when a request is processed by the Werkzeug applicaiton ( app ), it will first be routed to SharedDataMiddleware to determine if a static file has been requested:
If a static file is requested, SharedDataMiddleware will generate the response with the static file. Otherwise, the request is passed down the chain to the Werkzeug app for processing in wsgi_app() .
To see SharedDataMiddleware in action, run the server and navigate to http://localhost:5000/static/img/flask.png to view the Flask logo.
For a full list of middleware solutions provided by Werkzeug, check out the Middleware docs.
Flask Comparison:
Flask does not utilize the SharedDataMiddleware . It takes a different approach for serving static files. By default, if a static folder exists, Flask automatically adds a new URL rule to serve the static files up.
To illustrate this concept, run flask routes in the top-level project of a Flask application and you will see:
(venv)$ flask routes Endpoint Methods Rule ———— ——- ———————— index GET / static GET /static/
Templates
As is typically done in a Flask project, we’ll use Jinja for the templating engine for our app.
Start by adding a new folder called «templates» to the project:
├── app.py ├── requirements.txt ├── static │ ├── css │ └── img │ └── flask.png └── templates
In order to utilize Jinja, expand the constructor of the MovieApp class:
def __init__(self): """Initializes the Jinja templating engine to render from the 'templates' folder.""" template_path = os.path.join(os.path.dirname(__file__), 'templates') self.jinja_env = Environment(loader=FileSystemLoader(template_path), autoescape=True)
from jinja2 import Environment, FileSystemLoader
Flask Comparison:
Flask utilizes Jinja Environment as well to create the templating engine.
Within the MovieApp class, add a new render_template() method:
def render_template(self, template_name, **context): """Renders the specified template file using the Jinja templating engine.""" template = self.jinja_env.get_template(template_name) return Response(template.render(context), mimetype='text/html')
This method takes the template_name and any variables to pass to the templating engine ( **context ). It then generates a Response using the render() method from Jinja.
Flask Comparison:
Doesn’t the render_template() function look familiar? The Flask flavor is one of the most used functions in Flask.
To see render_template() in action, update dispatch_request() to render a template:
def dispatch_request(self, request): """Dispatches the request.""" return self.render_template('base.html')
All requests to the app will now render the templates/base.html template.
html lang="en"> head> meta charset="UTF-8"> title>Werkzeug Movie Apptitle> link rel="stylesheet" href="/static/css/style.css" type="text/css"> head> body> h1>Werkzeug Movie Apph1> body> html>
Make sure to add this template to the «templates» folder and save a copy of https://gitlab.com/patkennedy79/werkzeug_movie_app/-/blob/main/static/css/style.css to static/css/style.css.
Run the server. Navigate to http://localhost:5000. You should now see:
Routing
Routing means to match the URL to the appropriate view function. Werkzeug provides a Map class that allows you to match URLs to view functions using Rule objects.
Let’s create the Map object in the MovieApp constructor to illustrate how this works:
def __init__(self): """Initializes the Jinja templating engine to render from the 'templates' folder.""" template_path = os.path.join(os.path.dirname(__file__), 'templates') self.jinja_env = Environment(loader=FileSystemLoader(template_path), autoescape=True) self.url_map = Map([ Rule('/', endpoint='index'), Rule('/movies', endpoint='movies'), ])
Don’t forget the import:
from werkzeug.routing import Map, Rule
Each Rule object defines a URL and the view function ( endpoint ) to call if the URL is matched:
self.url_map = Map([ Rule('/', endpoint='index'), Rule('/movies', endpoint='movies'), ])
For example, when the homepage (‘/’) is requested, the index view function should be called.
Flask Comparison:
One of the amazing features of Flask is the @route decorator, which is used to assign a URL to a view function. This decorator updates the url_map for the Flask app, similar to the hand-coded url_map that we defined above.
In order to utilize the URL mapping, dispatch_request() needs to be updated:
def dispatch_request(self, request): """Dispatches the request.""" adapter = self.url_map.bind_to_environ(request.environ) try: endpoint, values = adapter.match() return getattr(self, endpoint)(request, **values) except HTTPException as e: return e
Now when a request comes in to dispatch_request() , the url_map will be utilized to attempt to match() the URL to an entry. If the URL requested is included in the url_map , then the applicable view function ( endpoint ) will be called. If the URL is not found in the url_map , then an exception is raised.
Exception handling will be covered shortly!
from werkzeug.exceptions import HTTPException
We’ve specified two view functions in the url_map , so let’s create them now within the MovieApp class:
def index(self, request): return self.render_template('base.html') def movies(self, request): return self.render_template('movies.html')
While templates/base.html was created in the previous section, templates/movies.html needs to be created now:
div class="table-container"> table> thead> tr> th>Indexth> th>Movie Titleth> tr> thead> tbody> tr> td>1td> td>Knives Outtd> tr> tr> td>2td> td>Pirates of the Caribbeantd> tr> tr> td>3td> td>Inside Mantd> tr> tbody> table> div>
This template file utilizes template inheritance to use base.html as the parent template. It generates a table of three movies.
However, if you navigate to http://localhost:5000/movies, you’ll now see the table of movies:
Exception Handling
The page returned is the default error page when a URL is not found in url_map :
def dispatch_request(self, request): """Dispatches the request.""" adapter = self.url_map.bind_to_environ(request.environ) try: endpoint, values = adapter.match() return getattr(self, endpoint)(request, **values) except HTTPException as e: return e
Additionally, you should see the following in the console:
127.0.0.1 - - [07/Mar/2021 12:13:17] "GET /movies2 HTTP/1.1" 404 -
Let’s create a custom error page by expanding dispatch_request() :
def dispatch_request(self, request): """Dispatches the request.""" adapter = self.url_map.bind_to_environ(request.environ) try: endpoint, values = adapter.match() return getattr(self, endpoint)(request, **values) except NotFound: return self.error_404() except HTTPException as e: return e
Update the import:
from werkzeug.exceptions import HTTPException, NotFound
Now when a URL is not found in the url_map , it will be handled by calling error_404() . Create this new method within the MovieApp class:
def error_404(self): response = self.render_template("404.html") response.status_code = 404 return response
div class="error-description"> h2>Page Not Found (404)h2> h4>What you were looking for is just not there!h4> h4>a href="/">Werkzeug Movie Appa>h4> div>
Now when you navigate to http://localhost:5000/movies2, you should see a friendly message:
Flask Comparison:
When full_dispatch_request() in the Flask class detects an exception, it will be handled gracefully in handle_user_exceptions() . Flask allows custom error pages for all HTTP error codes as well.
Request Processing
In this section, we’ll add a form to the app to allow the user to input their favorite movies.
Redis
As mentioned, we’ll be using Redis, an in-memory data structure store, to persist the movies due to its fast read/write speed and ease of setup.
The quickest way to get Redis up and running is with Docker:
$ docker run --name some-redis -d -p 6379:6379 redis
To check that the Redis container is running:
$ docker ps
To stop the running Redis container:
$ docker stop some-redis # Use name of Docker container
- Install and Configure Redis on Mac OS X via Homebrew
- DigitalOcean — How to Install and Secure Redis on Ubuntu
In order to utilize Redis, start by updating the MovieApp constructor to create an instance of StrictRedis :
def __init__(self, config): # Updated!! """Initializes the Jinja templating engine to render from the 'templates' folder, defines the mapping of URLs to view methods, and initializes the Redis interface.""" template_path = os.path.join(os.path.dirname(__file__), 'templates') self.jinja_env = Environment(loader=FileSystemLoader(template_path), autoescape=True) self.url_map = Map([ Rule('/', endpoint='index'), Rule('/movies', endpoint='movies'), ]) self.redis = StrictRedis(config['redis_host'], config['redis_port'], decode_responses=True) # New!!
Additionally, the constructor ( __init__() ) has an additional argument ( config ), which is used for creating the instance of StrictRedis .
from redis import StrictRedis
The configuration parameters that are passed in to the constructor need to be specified in the application factory function:
def create_app(): """Application factory function that returns an instance of MovieApp.""" app = MovieApp('redis_host': 'localhost', 'redis_port': 6379>) app.wsgi_app = SharedDataMiddleware(app.wsgi_app, '/static': os.path.join(os.path.dirname(__file__), 'static') >) return app
Form Processing
In order to allow the user to add a movie to the Redis storage, we need to add a new view function in url_map :
def __init__(self, config): """Initializes the Jinja templating engine to render from the 'templates' folder, defines the mapping of URLs to view methods, and initializes the Redis interface.""" . self.url_map = Map([ Rule('/', endpoint='index', methods=['GET']), Rule('/movies', endpoint='movies', methods=['GET']), Rule('/add', endpoint='add_movie', methods=['GET', 'POST']), # . ]) .
The Rule entries in url_map have been expanded to specify the HTTP methods that are allowed for each URL. Additionally, the ‘/add’ URL has been added:
Rule('/add', endpoint='add_movie', methods=['GET', 'POST']),
If the ‘/add’ URL is requested with either the GET or POST methods, then the add_movie() view function will be called.
Next, we need to create the add_movie() view function in the MovieApp class:
def add_movie(self, request): """Adds a movie to the list of favorite movies.""" if request.method == 'POST': movie_title = request.form['title'] self.redis.lpush('movies', movie_title) return redirect('/movies') return self.render_template('add_movie.html')
from werkzeug.utils import redirect
If a GET request is made to ‘/add’, then add_movie() will render the templates/add_movie.html file. If a POST request is made to ‘/add’, then the form data is stored in the Redis storage in the movies list and the user is redirected to the list of movies.
Create the templates/add_movie.html template file:
div class="form-container"> form method="post"> div class="field"> label for="movieTitle">Movie Title:label> input type="text" id="movieTitle" name="title"/> div> div class="field"> button type="submit">Submitbutton> div> form> div>
Display Movies
Since we’re now storing the movies in Redis, the movie() view function needs to be updated to read from the movies list in Redis:
def movies(self, request): """Displays the list of favorite movies.""" movies = self.redis.lrange('movies', 0, -1) return self.render_template('movies.html', movies=movies)
The list of movies is being passed to the templates/movies.html template file, which needs to be updated to loop through this list to create the table of movies:
div class="table-container"> table> thead> tr> th>Indexth> th>Movie Titleth> tr> thead> tbody> tr> td>>td> td>>td> tr> tbody> table> div>
To see the form processing in action, start by navigating to http://localhost:5000/add and adding a new movie:
After submitting the form, you should be automatically redirected to the list of movies (which may include movies previously added):
Why Not Use Werkzeug Instead of Flask?
Werkzeug provides much of the key functionality found in Flask, but Flask adds a number of powerful features, like:
- Sessions
- Application and Request contexts
- Blueprints
- Request callback functions
- Utilities:
- @route decorator
- url_for() function
As with any web framework — Don’t re-invent the wheel! Flask is a much better option (when compared to Werkzeug) for web development based on its rich feature set and large collection of extensions.
Conclusion
This article provided an overview of Werkzeug, which is one of the key components of Flask, by showing how to build a simple web application using Werkzeug. While it’s important to understand how the underlying libraries work in Flask, the complexity of creating a web application using Werkzeug should illustrate how easy it is to develop a web app using Flask!
Additionally, if you’re interested in learning how to test a Werkzeug application, check out the tests for the Werkzeug Movie App: https://gitlab.com/patkennedy79/werkzeug_movie_app/-/tree/main/tests.
If you’d like to learn more about Flask, be sure to check out my course — Developing Web Applications with Python and Flask.
Patrick Kennedy
Patrick is a software engineer from the San Francisco Bay Area with experience in C++, Python, and JavaScript. His favorite areas of teaching are Vue and Flask. In his free time, he enjoys spending time with his family and cooking.
Share this tutorial
Share this tutorial
Featured Course
Developing Web Applications with Python and Flask
This course focuses on teaching the fundamentals of Flask by building and testing a web application using Test-Driven Development (TDD).
Buy Now $40 View Course
Tutorial Topics
Table of Contents
Featured Course
Developing Web Applications with Python and Flask
This course focuses on teaching the fundamentals of Flask by building and testing a web application using Test-Driven Development (TDD).
Werkzeug Tutorial
Добро пожаловать в руководство по Werkzeug, в котором мы создадим клон TinyURL , сохраняющий URLs в экземпляре redis. Для этого приложения мы будем использовать libraries: Jinja 2 для шаблонов, redis для уровня базы данных и, конечно же, Werkzeug для уровня WSGI.
Вы можете использовать pip для установки необходимого libraries:
pip install Jinja2 redis Werkzeug
Также убедитесь, что на вашем локальном компьютере работает сервер redis. Если вы используете OS X, вы можете использовать brew для его установки:
brew install redis
Если вы используете Ubuntu или Debian, вы можете использовать apt-get:
sudo apt-get install redis-server
Redis был разработан для систем UNIX и никогда не предназначался для работы в Windows. Однако в целях развития неофициальные порты работают достаточно хорошо. Вы можете получить их от github .
Introducing Shortly
В этом уроке мы вместе создадим простой сервис сокращения URL с помощью Werkzeug. Имейте в виду, что Werkzeug — это не платформа, а library с утилитами для создания собственной платформы или приложения, и поэтому она очень гибкая. Подход, который мы здесь используем, — лишь один из многих, которые вы можете использовать.
В качестве хранилища данных мы будем использовать redis вместо реляционной базы данных, чтобы упростить задачу и потому, что именно с этой задачей redis справляется превосходно.
Конечный результат будет выглядеть примерно так:
Шаг 0: Базовое введение в WSGI
Werkzeug — это утилита library для WSGI.. WSGI сам по себе представляет собой протокол или соглашение, которое гарантирует, что ваше веб-приложение может взаимодействовать с веб-сервером и, что более важно, что веб-приложения хорошо работают вместе.
Базовое приложение «“Hello World» в WSGI без помощи Werkzeug выглядит так:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return ['Hello World!'.encode('utf-8')]
Приложение WSGI — это то, что вы можете вызвать и передать окружение и вызываемый объект start_response . В окружении хранится вся поступающая информация, для обозначения начала ответа можно использовать функцию start_response . С Werkzeug вам не придется иметь дело ни с тем, ни с другим, поскольку для работы с ними предоставляются объекты запроса и ответа.
Данные запроса принимают объект окружения и позволяют вам получить доступ к данным из этого окружения удобным способом. Объект ответа сам по себе является приложением WSGI и обеспечивает гораздо более удобный способ создания ответов.
Вот как вы могли бы написать это приложение с объектами ответа:
from werkzeug.wrappers import Response def application(environ, start_response): response = Response('Hello World!', mimetype='text/plain') return response(environ, start_response)
А вот расширенная версия, которая рассматривает строку запроса в URL (что более важно, параметр name в URL, чтобы заменить “World” на другой word):).
from werkzeug.wrappers import Request, Response def application(environ, start_response): request = Request(environ) text = f"Hello
'name' , 'World')>!" response = Response(text, mimetype='text/plain') return response(environ, start_response)И это все, что вам нужно знать о WSGI..
Шаг 1: Создание папок
Прежде чем мы начнем, давайте создадим папки, необходимые для этого приложения:
/shortly /static /templates
Папка Short — это не пакет python, а просто место, куда мы помещаем наши файлы. Непосредственно в эту папку мы затем поместим наш основной модуль на следующих шагах. Файлы внутри папки static доступны пользователям приложения через HTTP.. Это место, где находятся файлы CSS и JavaScript go. Внутри папки шаблонов мы заставим Jinja2 искать шаблоны. Шаблоны, которые вы создадите позже в этом руководстве, будут иметь номер go в этом каталоге.
Шаг 2: Базовая структура
Теперь давайте приступим к делу и создадим модуль для нашего приложения. Давайте создадим файл с именем shortly.py в папке shortly . Сначала нам понадобится куча импорта. Я приведу сюда весь импорт, даже если он не будет использован сразу, чтобы не запутаться:
import os import redis from werkzeug.urls import url_parse from werkzeug.wrappers import Request, Response from werkzeug.routing import Map, Rule from werkzeug.exceptions import HTTPException, NotFound from werkzeug.middleware.shared_data import SharedDataMiddleware from werkzeug.utils import redirect from jinja2 import Environment, FileSystemLoader
Затем мы можем создать базовую структуру нашего приложения и функцию для создания нового его экземпляра, при необходимости с помощью промежуточного программного обеспечения WSGI, которое экспортирует все файлы в папке static в Интернете:
class Shortly(object): def __init__(self, config): self.redis = redis.Redis( config['redis_host'], config['redis_port'], decode_responses=True ) def dispatch_request(self, request): return Response('Hello World!') def wsgi_app(self, environ, start_response): request = Request(environ) response = self.dispatch_request(request) return response(environ, start_response) def __call__(self, environ, start_response): return self.wsgi_app(environ, start_response) def create_app(redis_host='localhost', redis_port=6379, with_static=True): app = Shortly(< 'redis_host': redis_host, 'redis_port': redis_port >) if with_static: app.wsgi_app = SharedDataMiddleware(app.wsgi_app, < '/static': os.path.join(os.path.dirname(__file__), 'static') >) return app
Наконец, мы можем добавить фрагмент кода, который запустит локальный сервер разработки с автоматической перезагрузкой кода и отладчиком:
if __name__ == '__main__': from werkzeug.serving import run_simple app = create_app() run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)
Основная идея здесь заключается в том, что наш класс Shortly является реальным приложением WSGI. Метод __call__ напрямую отправляет данные в wsgi_app . Это сделано для того, чтобы мы могли обернуть wsgi_app для применения промежуточного программного обеспечения, как мы это делаем в функции create_app . Фактический метод wsgi_app затем создает объект Request и вызывает метод dispatch_request , который затем должен вернуть объект Response , который затем снова оценивается как приложение WSGI. Как видите: черепахи полностью вниз. Как создаваемый нами класс Shortly , так и любой объект запроса в Werkzeug реализуют интерфейс WSGI. В результате вы даже можете вернуть другое приложение WSGI из метода dispatch_request .
Фабричную функцию create_app можно использовать для создания нового экземпляра нашего приложения. Он не только передаст приложению некоторые параметры в качестве конфигурации, но также при необходимости добавит промежуточное программное обеспечение WSGI, которое экспортирует файлы static. Таким образом, у нас есть доступ к файлам из папки static, даже если мы не настраиваем наш сервер для их предоставления, что очень полезно для разработки.
Интермеццо: запуск приложения
Теперь вы сможете выполнить файл с помощью python и увидеть сервер на своем локальном компьютере:
$ python shortly.py * Running on http://127.0.0.1:5000/ * Restarting with reloader: stat() polling
Он также сообщает вам, что перезагрузка активна. Он будет использовать различные методы, чтобы выяснить, изменился ли какой-либо файл на диске, а затем автоматически перезапустится.
Просто go к URL, и вы увидите “Hello World!».
Шаг 3: Окружающая среда
Теперь, когда у нас есть базовый класс приложения, мы можем заставить конструктор сделать что-нибудь полезное и предоставить туда несколько помощников, которые могут пригодиться. Нам понадобится возможность отображать шаблоны и подключаться к redis, поэтому давайте немного расширим класс:
def __init__(self, config): self.redis = redis.Redis(config['redis_host'], config['redis_port']) template_path = os.path.join(os.path.dirname(__file__), 'templates') self.jinja_env = Environment(loader=FileSystemLoader(template_path), autoescape=True) def render_template(self, template_name, **context): t = self.jinja_env.get_template(template_name) return Response(t.render(context), mimetype='text/html')
Шаг 4: Маршрутизация
Далее идет маршрутизация. Маршрутизация — это процесс сопоставления и анализа URL с чем-то, что мы можем использовать. Werkzeug предоставляет гибкую интегрированную систему маршрутизации, которую мы можем использовать для этого. Это работает следующим образом: вы создаете экземпляр Map и добавляете несколько объектов Rule . Каждое правило имеет шаблон, с которым оно будет пытаться сопоставить URL, и «конечную точку». Конечная точка обычно представляет собой строку и может использоваться для уникальной идентификации URL. Мы также могли бы использовать это для автоматического реверса URL, но в этом уроке мы не будем этого делать.
Просто поместите это в конструктор:
self.url_map = Map([ Rule('/', endpoint='new_url'), Rule('/', endpoint='follow_short_link'), Rule('/+', endpoint='short_link_details') ])
Здесь мы создаем карту URL с тремя правилами. / для корня пространства URL, где мы просто отправим функцию, реализующую логику создания нового URL. А затем тот, который следует по короткой ссылке на целевой URL, и еще один с тем же правилом, но с плюсом ( + ) в конце, чтобы показать детали ссылки.
Так как же нам найти путь от конечной точки к функции? Это зависит от вас. В этом уроке мы сделаем это, вызвав метод on_ + endpoint самого класса. Вот как это работает:
def dispatch_request(self, request): adapter = self.url_map.bind_to_environ(request.environ) try: endpoint, values = adapter.match() return getattr(self, f'on_ ')(request, **values) except HTTPException as e: return e
Мы привязываем карту URL к текущей среде и получаем обратно URLAdapter . Адаптер можно использовать для соответствия запросу, а также для реверса URL. Метод match вернет конечную точку и словарь значений в URL. Например, правило для follow_short_link имеет переменную часть под названием short_id . Когда мы перейдём от go к http://localhost:5000/foo , мы получим обратно следующие значения:
endpoint = 'follow_short_link' values = 'short_id': 'foo'>
Если оно ничему не соответствует, будет выдано исключение NotFound , то есть HTTPException . Все исключения HTTP также являются приложениями WSGI, которые отображают страницу ошибки по умолчанию. Поэтому мы просто перехватываем их все и возвращаем саму ошибку.
Если все работает хорошо, мы вызываем функцию on_ + конечная точка и передаем ей запрос в качестве аргумента, а также все аргументы URL в качестве аргументов ключевого слова и возвращаем объект ответа, который возвращает метод.
Шаг 5: Первый просмотр
Начнем с первого вида: для нового URLs:.
def on_new_url(self, request): error = None url = '' if request.method == 'POST': url = request.form['url'] if not is_valid_url(url): error = 'Please enter a valid URL' else: short_id = self.insert_url(url) return redirect(f"/+") return self.render_template('new_url.html', error=error, url=url)
Эту логику должно быть легко понять. По сути, мы проверяем, что метод запроса — POST,, и в этом случае мы проверяем URL и добавляем новую запись в базу данных, а затем перенаправляем на страницу сведений. Это означает, что нам нужно написать функцию и вспомогательный метод. Для проверки URL этого достаточно:
def is_valid_url(url): parts = url_parse(url) return parts.scheme in ('http', 'https')
Для вставки URL, нам нужен только этот небольшой метод в нашем классе:
def insert_url(self, url): short_id = self.redis.get(f'reverse-url: ') if short_id is not None: return short_id url_num = self.redis.incr('last-url-id') short_id = base36_encode(url_num) self.redis.set(f'url-target: ', url) self.redis.set(f'reverse-url: ', short_id) return short_id
reverse-url: + URL сохранит короткий идентификатор. Если URL уже был отправлен, это не будет None, и мы можем просто вернуть это значение, которое будет коротким ID.. В противном случае мы увеличиваем ключ last-url-id и преобразуем его в base36. Затем сохраняем ссылку и обратную запись в redis. А вот функция преобразования в базу 36:
def base36_encode(number): assert number >= 0, 'positive integer required' if number == 0: return '0' base36 = [] while number != 0: number, i = divmod(number, 36) base36.append('0123456789abcdefghijklmnopqrstuvwxyz'[i]) return ''.join(reversed(base36))
Итак, для работы этого представления не хватает шаблона. Мы создадим это позже, давайте сначала также напишем другие представления, а затем сделаем шаблоны в одном go.
Шаг 6: Перенаправление просмотра
Просмотр перенаправления прост. Все, что ему нужно сделать, это найти ссылку в redis и перенаправить на нее. Кроме того, мы также увеличим счетчик, чтобы знать, как часто нажимали на ссылку:
def on_follow_short_link(self, request, short_id): link_target = self.redis.get(f'url-target: ') if link_target is None: raise NotFound() self.redis.incr(f'click-count: ') return redirect(link_target)
В этом случае мы вручную создадим исключение NotFound , если URL не существует, которое перейдет к функции dispatch_request и будет преобразовано в ответ 404 по умолчанию.
Шаг 7: Детальный просмотр
Подробное представление ссылки очень похоже, мы просто повторно отображаем шаблон. Помимо поиска цели, мы также запрашиваем у redis количество кликов по ссылке и оставляем значение по умолчанию равным нулю, если такой ключ еще не существует:
def on_short_link_details(self, request, short_id): link_target = self.redis.get(f'url-target: ') if link_target is None: raise NotFound() click_count = int(self.redis.get(f'click-count: ') or 0) return self.render_template('short_link_details.html', link_target=link_target, short_id=short_id, click_count=click_count )
Имейте в виду, что redis всегда работает со строками, поэтому вам придется конвертировать количество кликов в int вручную.
Шаг 8: Шаблоны
А вот и все шаблоны. Просто закиньте их в папку templates . Jinja2 поддерживает наследование шаблонов, поэтому первое, что мы сделаем, это создадим шаблон макета с блоками, которые будут выступать в качестве заполнителей. Мы также настроили Jinja2 так, чтобы он автоматически экранировал строки с помощью правил HTML, поэтому нам не придется тратить на это время самостоятельно. Это предотвращает атаки XSS и ошибки рендеринга.
html> title> | shortly title> link rel=stylesheet href=/static/style.css type=text/css> div class=box> h1>a href=/>shortly a> h1> p class=tagline>Shortly is a URL shortener written with Werkzeug div>
Create New Short URL h2>Submit URL h2> form action=«» method=post> p class=error>strong>Error: strong> > p>URL: input type=text name=url value=«>» class=urlinput> input type=submit value=«Shorten»> form>
Details about /> h2>a href=«/>»>/> a> h2> dl> dt>Full link dd class=link>div>> div> dt>Click count: dd>> dl>Шаг 9: Стиль
Чтобы это выглядело лучше, чем уродливое черно-белое изображение, вот простая таблица стилей:
body < background: #E8EFF0; margin: 0; padding: 0; > body, input < font-family: 'Helvetica Neue', Arial, sans-serif; font-weight: 300; font-size: 18px; > .box < width: 500px; margin: 60px auto; padding: 20px; background: white; box-shadow: 0 1px 4px #BED1D4; border-radius: 2px; > a < color: #11557C; > h1, h2 < margin: 0; color: #11557C; > h1 a < text-decoration: none; > h2 < font-weight: normal; font-size: 24px; > .tagline < color: #888; font-style: italic; margin: 0 0 20px 0; > .link div < overflow: auto; font-size: 0.8em; white-space: pre; padding: 4px 10px; margin: 5px 0; background: #E5EAF1; > dt < font-weight: normal; > .error < background: #E8EFF0; padding: 3px 8px; color: #11557C; font-size: 0.9em; border-radius: 2px; > .urlinput < width: 300px; >
Bonus: Refinements
Посмотрите реализацию в словаре примеров в репозитории Werkzeug, чтобы увидеть версию этого руководства с некоторыми небольшими улучшениями, такими как пользовательская страница 404.
Обслуживание приложений WSGI
Существует множество способов обслуживания приложения WSGI. Во время его разработки вам обычно не нужен полноценный веб-сервер, такой как Apache, а нужен простой автономный сервер. По этой причине Werkzeug поставляется со встроенным сервером разработки.
Самый простой способ — создать небольшой файл start-myproject.py , который запускает приложение с помощью встроенного сервера:
from werkzeug.serving import run_simple from myproject import make_app app = make_app(. ) run_simple('localhost', 8080, app, use_reloader=True)
Вы также можете передать ему аргумент ключевого слова extra_files со списком дополнительных файлов (например, файлов конфигурации), которые вы хотите просмотреть.
werkzeug.serving.run_simple(hostname, port, application, use_reloader=False, use_debugger=False, use_evalex=True, extra_files=None, exclude_patterns=None, reloader_interval=1, reloader_type=’auto’, threaded=False, processes=1, request_handler=None, static_files=None, passthrough_errors=False, ssl_context=None)
Запустите сервер разработки для приложения WSGI. Могут быть включены различные дополнительные функции.
Не используйте сервер разработки при развертывании в рабочей среде. Он предназначен для использования только во время локальной разработки. Он не предназначен для того, чтобы быть особенно эффективным, стабильным или безопасным.
- имя хоста ( str ) — хост для привязки, например ‘localhost’ . Это может быть домен, адрес IPv4 или IPv6 или путь к файлу, начинающийся с unix:// для сокета Unix.
- порт ( int ) — порт для привязки, например 8080 . Использование 0 указывает ОС выбрать случайный свободный порт.
- Приложение (WSGIApplication) – приложение WSGI для запуска.
- use_reloader ( bool ) — используйте процесс перезагрузки для перезапуска процесса сервера при изменении файлов.
- use_debugger ( bool ) — используйте отладчик Werkzeug, который будет отображать форматированные обратные трассировки для необработанных исключений.
- use_evalex ( bool ) — сделать отладчик интерактивным. Терминал Python можно открыть для любого кадра обратной трассировки. Некоторая защита обеспечивается требованием PIN,, но его никогда не следует включать на общедоступном сервере.
- extra_files (t.Iterable[ str ] | None) – Релоадер будет следить за изменениями в этих файлах помимо модулей Python. Например, посмотрите файл конфигурации.
- exclude_patterns (t.Iterable[ str ] | None) — программа перезагрузки будет игнорировать изменения в любых файлах, соответствующих этим шаблонам fnmatch . Например, игнорировать файлы кэша.
- reloader_interval ( int ) — Как часто перезагрузщик пытается проверить наличие изменений.
- reloader_type ( str ) — используемый перезарядчик. Перезагрузчик ‘stat’ встроен, но для просмотра файлов может потребоваться значительный объем CPU. Перезагрузчик ‘watchdog’ гораздо более эффективен, но требует предварительной установки пакета watchdog .
- резьбовой ( bool ) — обработка параллельного requests с использованием потоков. Невозможно использовать с processes .
- процессы ( int ) — обработка одновременных процессов requests с использованием указанного количества процессов. Невозможно использовать с threaded .
- request_handler ( type [WSGIRequestHandler] | None) — используйте другой подкласс BaseHTTPRequestHandler для обработки requests.
- static_files ( dict [ str , str | tuple [ str , str ]] | None) — словарь, сопоставляющий префиксы URL с каталогами для обслуживания файлов static из SharedDataMiddleware .
- passthrough_errors ( bool ) — не перехватывать необработанные исключения на уровне сервера, вместо этого допустить сбой сервера. Если use_debugger включен, отладчик все равно будет обнаруживать такие ошибки.
- В45910В (_TSSLContextArg | None) — настройка TLS для обслуживания через HTTPS.. Может быть объектом ssl.SSLContext , кортежем (cert_file, key_file) для создания типичного контекста или строкой ‘adhoc’ для создания временного самозаверяющего сертификата.
Изменено в версии 2.1: Приведены инструкции по устранению ошибки «адрес уже используется».
Изменено в версии 2.1:. При запуске на 0.0.0.0 или :: отображается IP-адрес обратной связи в дополнение к реальному IP..
Изменено в версии 2.1: Удален интерфейс командной строки.
Изменено в версии 2.0:. При запуске на 0.0.0.0 или :: отображается реальный привязанный IP-адрес, а также предупреждение не запускать сервер разработки в рабочей среде.
Изменено в версии 2.0: Добавлен параметр exclude_patterns .
Изменено в версии 0.15:. Привязка к сокету Unix путем передачи hostname , который начинается с unix:// .
Изменено в версии 0.10: Улучшен релоадер и добавлена поддержка смены бэкенда через параметр reloader_type .
Изменено в версии 0.9: Добавлен интерфейс командной строки.
В версии 0.8: изменен ssl_context , может быть кортеж путей к сертификату и файлам ключей private.
Изменено в версии 0.6: Добавлен параметр ssl_context .
Изменено в версии 0.5: Добавлены параметры static_files и passthrough_errors .
werkzeug.serving.is_running_from_reloader()
Проверьте, работает ли сервер как подпроцесс в перезагрузке Werkzeug.
Новое в версии 0.10.
Return type:
werkzeug.serving.make_ssl_devcert(base_path, host=None, cn=None)
Создает ключ SSL для разработки. Его следует использовать вместо ключа ‘adhoc’ , который генерирует новый сертификат при каждом запуске сервера. Он принимает путь, по которому он должен хранить ключ и сертификат, а также хост или CN.. Если хост указан, он будет использовать CN *.host/CN=host .
Дополнительную информацию см. run_simple() .
Новое в версии 0.9.
- base_path ( str ) — путь к сертификату и ключу. Для сертификата добавлено расширение .crt , для ключа — .key .
- хост ( str | None) – имя хоста. Его можно использовать как альтернативу cn .
- cn ( str | None) – использовать CN .
Сервер разработки не предназначен для использования в производственных системах. Он был разработан специально для целей разработки и плохо работает при высокой нагрузке. Для настройки развертывания посетите страницы Deploying to Production .
Reloader
Изменено в версии 0.10.
Перезагрузчик Werkzeug постоянно отслеживает модули и пути вашего веб-приложения и перезапускает сервер, если какой-либо из наблюдаемых файлов изменяется.
Начиная с версии 0.10,, релоадер поддерживает два бэкенда: stat и watchdog .
- Серверная часть stat по умолчанию просто проверяет mtime всех файлов через регулярные промежутки времени. В большинстве случаев этого достаточно, однако известно, что это приводит к разрядке аккумулятора ноутбука.
- Серверная часть watchdog использует события файловой системы и работает намного быстрее, чем stat . Требуется установка модуля watchdog . Рекомендуемый способ добиться этого — добавить Werkzeug[watchdog] в файл требований.
Если watchdog установлен и доступен, он будет автоматически использоваться вместо встроенного загрузчика stat .
Для переключения между серверами вы можете использовать параметр reloader_type функции run_simple() . ‘stat’ устанавливает опрос на основе статистики по умолчанию, а ‘watchdog’ принудительно отправляет его на сервер сторожевого таймера.
Некоторые крайние случаи, например модули, которые не удалось правильно импортировать, не обрабатываются перезагрузкой статистики по соображениям производительности. Сторожевой перезагрузщик также отслеживает такие файлы.
Colored Logging
Сервер разработки выделяет журналы запросов разными цветами в зависимости от кода состояния. В Windows для этого также необходимо установить Colorama .
Virtual Hosts
Многие веб-приложения используют несколько поддоменов. Это может быть немного сложно смоделировать локально. К счастью, существует hosts file , который можно использовать для присвоения локальному компьютеру нескольких имен.
Это позволяет вам вызывать ваш локальный компьютер yourapplication.local и api.yourapplication.local (или что-нибудь еще) в дополнение к localhost .
Вы можете найти файл хостов по следующему адресу:
Вы можете открыть файл в своем любимом текстовом редакторе и добавить новое имя после localhost :
127.0.0.1 localhost yourapplication.local api.yourapplication.local
Сохраните изменения, и через некоторое время вы сможете получить доступ к серверу разработки и по этим именам хостов. Вы можете использовать систему URL Routing для диспетчеризации между разными хостами или самостоятельно проанализировать request.host .
Выключение сервера
В некоторых случаях может быть полезно выключить сервер после обработки запроса. Например, локальный инструмент командной строки, которому требуется аутентификация OAuth, может временно запустить сервер для прослушивания ответа, записать токен пользователя, а затем остановить сервер.
Одним из способов сделать это может быть запуск сервера в процессе multiprocessing , а затем завершение процесса после того, как значение будет передано обратно родительскому процессу.
import multiprocessing from werkzeug import Request, Response, run_simple def get_token(q: multiprocessing.Queue) -> None: @Request.application def app(request: Request) -> Response: q.put(request.args["token"]) return Response("", 204) run_simple("localhost", 5000, app) if __name__ == "__main__": q = multiprocessing.Queue() p = multiprocessing.Process(target=get_token, args=(q,)) p.start() print("waiting") token = q.get(block=True) p.terminate() print(token)
В этом примере используется сервер разработки Werkzeug, но любой рабочий сервер, который можно запустить как процесс Python, может использовать тот же метод, и ему следует отдать предпочтение из соображений безопасности. Другой способ — запустить процесс subprocess и отправить значение обратно через stdout .
Troubleshooting
В операционных системах, поддерживающих ipv6 и настроенных на него, таких как современные системы Linux, OS X 10.4 или выше, а также Windows Vista, некоторые браузеры могут работать очень медленно при доступе к вашему локальному серверу. Причина этого в том, что иногда «localhost» настроен на доступность как для сокетов ipv4, так и для ipv6, и некоторые браузеры сначала пытаются получить доступ к ipv6, а затем к ipv4.
В настоящее время встроенный веб-сервер не поддерживает одновременно ipv6 и ipv4, и для лучшей переносимости по умолчанию используется ipv4.
Если вы заметили, что веб-браузеру требуется много времени для загрузки страницы, есть два способа решить эту проблему. Если вам не нужна поддержка ipv6, вы можете отключить запись ipv6 в hosts file , удалив эту строку:
::1 localhost
Альтернативно вы также можете отключить поддержку ipv6 в своем браузере. Например, если Firefox демонстрирует такое поведение, вы можете отключить его, перейдя на about:config и отключив ключ network.dns.disableIPv6 . Однако это не рекомендуется, начиная с Werkzeug 0.6.1!.
Начиная с Werkzeug 0.6.1,, сервер теперь будет переключаться между ipv4 и ipv6 в зависимости от конфигурации вашей операционной системы. Это означает, что если вы отключили поддержку ipv6 в своем браузере, но ваша операционная система предпочитает ipv6, вы не сможете подключиться к своему серверу. В этой ситуации вы можете либо удалить запись localhost для ::1 , либо явно привязать имя хоста к адресу ipv4 ( 127.0.0.1 ).
SSL
Новое в версии 0.6.
Встроенный сервер поддерживает SSL в целях тестирования. Если предоставлен контекст SSL, он будет использоваться. Это означает, что сервер может работать либо в режиме HTTP, либо в режиме HTTPS, но не в обоих режимах.
Quickstart
Самый простой способ выполнить разработку на основе SSL с помощью Werkzeug — использовать его для создания сертификата SSL и ключа private, сохранить их где-нибудь и затем поместить туда. Для сертификата вам необходимо указать имя вашего сервера при создании или CN .
-
Сгенерируйте ключ SSL и сохраните его где-нибудь:
>>> from werkzeug.serving import make_ssl_devcert >>> make_ssl_devcert('/path/to/the/key', host='localhost') ('/path/to/the/key.crt', '/path/to/the/key.key')
run_simple('localhost', 4000, application, ssl_context=('/path/to/the/key.crt', '/path/to/the/key.key'))
После этого вам придется один раз подтвердить сертификат в браузере.
Загрузка контекстов вручную
Вы можете использовать объект ssl.SSLContext вместо кортежа для полного контроля над конфигурацией TLS.
import ssl ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ctx.load_cert_chain('ssl.cert', 'ssl.key') run_simple('localhost', 4000, application, ssl_context=ctx)
Generating Certificates
Ключ и сертификат можно создать заранее с помощью инструмента openssl вместо make_ssl_devcert() . Для этого необходимо, чтобы в вашей системе была установлена команда openssl :
$ openssl genrsa 1024 > ssl.key $ openssl req -new -x509 -nodes -sha1 -days 365 -key ssl.key > ssl.cert
Adhoc Certificates
Самый простой способ включить SSL — запустить сервер в режиме adhoc. В этом случае Werkzeug сгенерирует для вас сертификат SSL:
run_simple('localhost', 4000, application, ssl_context='adhoc')
Обратной стороной этого, конечно, является то, что вам придется подтверждать сертификат каждый раз при перезагрузке сервера. Сертификаты Adhoc не рекомендуются, поскольку современные браузеры плохо их поддерживают по соображениям безопасности.
Эта функция требует установки шифрования library.
Unix Sockets
Сервер разработки может привязываться к сокету Unix вместо сокета TCP. run_simple() будет привязан к сокету Unix, если параметр hostname начинается с ‘unix://’ .
from werkzeug.serving import run_simple run_simple('unix://example.sock', 0, app)