<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Maks live</title><link href="https://maks.live/" rel="alternate"></link><link href="https://maks.live/feeds/all.atom.xml" rel="self"></link><id>https://maks.live/</id><updated>2017-06-10T15:00:00+03:00</updated><entry><title>Web исключения в Django</title><link href="https://maks.live/articles/python/web-iskliucheniia-v-django/" rel="alternate"></link><published>2017-06-10T15:00:00+03:00</published><updated>2017-06-10T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2017-06-10:articles/python/web-iskliucheniia-v-django/</id><summary type="html">&lt;p&gt;Как и положено веб фраймворку, &lt;code&gt;Django&lt;/code&gt; позволяет
возвращать в ответ на запрос &lt;code&gt;HttpResponse&lt;/code&gt; с любым статус кодом
из диапазона &lt;code&gt;[100 .. 599]&lt;/code&gt;. Этот ответ должен быть явно отправлен
через &lt;code&gt;return&lt;/code&gt; во вьюхе обрабатывающей запрос.
Однако &lt;code&gt;pythonyc way&lt;/code&gt; предусматривает не только
явный &lt;code&gt;return&lt;/code&gt;, но и гибкую обработку исключений.
Рассмотрим веб исключения в &lt;code&gt;Django&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="standartnye-iskliucheniia"&gt;&lt;a class="toclink" href="#standartnye-iskliucheniia"&gt;Стандартные&amp;nbsp;исключения&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt; допускает ограниченный набор &lt;code&gt;http&lt;/code&gt; ответов через
выбрасывание&amp;nbsp;исключений.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;400&lt;/code&gt; &lt;a href="https://docs.djangoproject.com/en/1.11/ref/exceptions/#suspiciousoperation"&gt;SuspiciousOperation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;403&lt;/code&gt; &lt;a href="https://docs.djangoproject.com/en/1.11/ref/exceptions/#permissiondenied"&gt;PermissionDenied&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;404&lt;/code&gt; &lt;a href="https://docs.djangoproject.com/en/1.11/topics/http/views/#the-http404-exception"&gt;Http404&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;500&lt;/code&gt; Любое другое неперехваченное&amp;nbsp;исключение&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="nastroika-http-otveta"&gt;&lt;a class="toclink" href="#nastroika-http-otveta"&gt;Настройка http&amp;nbsp;ответа&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Ответ возвращаемый каждым из этих исключений можно персонализировать
через перегрузку соответствующих
&lt;a href="https://docs.djangoproject.com/en/1.11/topics/http/views/#customizing-error-views"&gt;хендлеров&lt;/a&gt;
в файле &lt;code&gt;urls.py&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateView&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ErrorHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TemplateView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Render error template &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;error_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index/error.html&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; For error on any methods return just GET &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_context_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_context_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;error_code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_code&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_to_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;response_kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Return correct status code &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;response_kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response_kwargs&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;response_kwargs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render_to_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;response_kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# urls.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;index.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ErrorHandler&lt;/span&gt;

&lt;span class="c1"&gt;# error handing handlers - fly binding&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;handler{}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ErrorHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="testirovanie-khendlerov"&gt;&lt;a class="toclink" href="#testirovanie-khendlerov"&gt;Тестирование&amp;nbsp;хендлеров&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Проверить работу хендлеров, можно явно выбросив соответствующие исключения.
Перегрузив какую либо из существующих вьюх через &lt;code&gt;mock&lt;/code&gt;.
Но этот способ не позволяет проверить ответ со статусом &lt;code&gt;500&lt;/code&gt;, т.к. в тестах
общие исключения не перехватываются. Полностью убедится в корректности
обработки ошибки сервера &amp;#8212; можно только через
&lt;a href="https://docs.djangoproject.com/en/1.11/topics/testing/tools/#django.test.LiveServerTestCase"&gt;LiveServerTestCase&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# tests.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SuspiciousOperation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PermissionDenied&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Http404&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;index&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ErrorHandlersTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Check is correct error handlers work &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;raise_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Test exception&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapped&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_index_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Should check is 200 on index page &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTemplateUsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index/index.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@mock.patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;index.views.IndexView.get&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raise_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Http404&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_404_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Should check is 404 page correct &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTemplateUsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index/error.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;404 Page not found&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nd"&gt;@mock.patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;index.views.IndexView.get&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_500_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Should check is 500 page correct &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTemplateUsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index/error.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;500 Server Error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nd"&gt;@mock.patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;index.views.IndexView.get&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raise_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SuspiciousOperation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_400_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Should check is 400 page correct &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTemplateUsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index/error.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;400 Bad request&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nd"&gt;@mock.patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;index.views.IndexView.get&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raise_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PermissionDenied&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_403_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Should check is 403 page correct &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTemplateUsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index/error.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;403 Permission Denied&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="rasshirennye-iskliucheniia"&gt;&lt;a class="toclink" href="#rasshirennye-iskliucheniia"&gt;Расширенные&amp;nbsp;исключения&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В &lt;a href="https://github.com/aio-libs/aiohttp"&gt;AioHTTP&lt;/a&gt;, любой &lt;code&gt;HttpResponse&lt;/code&gt;
можно выбрасывать как исключение. Это действительно круто. Но если попробовать
такое в &lt;code&gt;Django&lt;/code&gt;, получаем ошибку типа,
т.к. исключение должно быть наследником базового класса&amp;nbsp;исключений.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;console&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="n"&gt;derive&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;BaseException&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;С пакетом &lt;a href="https://pypi.python.org/pypi/django-web-exceptions"&gt;Django web exceptions&lt;/a&gt;
можно выбрасывать любой &lt;code&gt;http&lt;/code&gt; ответ как&amp;nbsp;исключение.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;web_exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPOk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;console&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;web_exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPOk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="bystryi-start"&gt;&lt;a class="toclink" href="#bystryi-start"&gt;Быстрый&amp;nbsp;старт&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;1 Устанавливаем через &lt;code&gt;pip&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;pip install django-web-exceptions
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;2 Подключаем &lt;code&gt;middleware&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;
&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;web_exceptions.middleware.WebExceptionsMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;3 Вызываем&amp;nbsp;исключение&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;web_exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Simple view raise redirectexception &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPMovedPermanently&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="kak-eto-rabotaet"&gt;&lt;a class="toclink" href="#kak-eto-rabotaet"&gt;Как это&amp;nbsp;работает&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Для того чтобы выбрасывать &lt;code&gt;http&lt;/code&gt; ответ как исключение,
в базовом классе наследуемся сразу от &lt;code&gt;HttpResponse&lt;/code&gt; и &lt;code&gt;Exception&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Base Web explanation&lt;/span&gt;
&lt;span class="sd"&gt;    In subclasses should set status_code attr&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;empty_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_reason_phrase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_reason_phrase&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty_body&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{}: {}&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason_phrase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reason_phrase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h5 id="200x-status-code"&gt;&lt;a class="toclink" href="#200x-status-code"&gt;200x status&amp;nbsp;code&lt;/a&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;200&lt;/code&gt; HTTPOk&lt;/li&gt;
&lt;li&gt;&lt;code&gt;201&lt;/code&gt; HTTPCreated&lt;/li&gt;
&lt;li&gt;&lt;code&gt;202&lt;/code&gt; HTTPAccepted&lt;/li&gt;
&lt;li&gt;&lt;code&gt;203&lt;/code&gt; HTTPNonAuthoritativeInformation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;204&lt;/code&gt; HTTPNoContent&lt;/li&gt;
&lt;li&gt;&lt;code&gt;205&lt;/code&gt; HTTPResetContent&lt;/li&gt;
&lt;li&gt;&lt;code&gt;206&lt;/code&gt; HTTPPartialContent&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="300x-status-code"&gt;&lt;a class="toclink" href="#300x-status-code"&gt;300x status&amp;nbsp;code&lt;/a&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;300&lt;/code&gt; HTTPMultipleChoices&lt;/li&gt;
&lt;li&gt;&lt;code&gt;301&lt;/code&gt; HTTPMovedPermanently&lt;/li&gt;
&lt;li&gt;&lt;code&gt;302&lt;/code&gt; HTTPFound&lt;/li&gt;
&lt;li&gt;&lt;code&gt;303&lt;/code&gt; HTTPSeeOther&lt;/li&gt;
&lt;li&gt;&lt;code&gt;304&lt;/code&gt; HTTPNotModified&lt;/li&gt;
&lt;li&gt;&lt;code&gt;305&lt;/code&gt; HTTPUseProxy&lt;/li&gt;
&lt;li&gt;&lt;code&gt;307&lt;/code&gt; HTTPTemporaryRedirect&lt;/li&gt;
&lt;li&gt;&lt;code&gt;308&lt;/code&gt; HTTPPermanentRedirect&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="400x-status-code"&gt;&lt;a class="toclink" href="#400x-status-code"&gt;400x status&amp;nbsp;code&lt;/a&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;400&lt;/code&gt; HTTPBadRequest&lt;/li&gt;
&lt;li&gt;&lt;code&gt;401&lt;/code&gt; HTTPUnauthorized&lt;/li&gt;
&lt;li&gt;&lt;code&gt;402&lt;/code&gt; HTTPPaymentRequired&lt;/li&gt;
&lt;li&gt;&lt;code&gt;403&lt;/code&gt; HTTPForbidden&lt;/li&gt;
&lt;li&gt;&lt;code&gt;404&lt;/code&gt; HTTPNotFound&lt;/li&gt;
&lt;li&gt;&lt;code&gt;405&lt;/code&gt; HTTPMethodNotAllowed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;406&lt;/code&gt; HTTPNotAcceptable&lt;/li&gt;
&lt;li&gt;&lt;code&gt;407&lt;/code&gt; HTTPProxyAuthenticationRequired&lt;/li&gt;
&lt;li&gt;&lt;code&gt;408&lt;/code&gt; HTTPRequestTimeout&lt;/li&gt;
&lt;li&gt;&lt;code&gt;409&lt;/code&gt; HTTPConflict&lt;/li&gt;
&lt;li&gt;&lt;code&gt;410&lt;/code&gt; HTTPGone&lt;/li&gt;
&lt;li&gt;&lt;code&gt;411&lt;/code&gt; HTTPLengthRequired&lt;/li&gt;
&lt;li&gt;&lt;code&gt;412&lt;/code&gt; HTTPPreconditionFailed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;413&lt;/code&gt; HTTPRequestEntityTooLarge&lt;/li&gt;
&lt;li&gt;&lt;code&gt;414&lt;/code&gt; HTTPRequestURITooLong&lt;/li&gt;
&lt;li&gt;&lt;code&gt;415&lt;/code&gt; HTTPUnsupportedMediaType&lt;/li&gt;
&lt;li&gt;&lt;code&gt;416&lt;/code&gt; HTTPRequestRangeNotSatisfiable&lt;/li&gt;
&lt;li&gt;&lt;code&gt;417&lt;/code&gt; HTTPExpectationFailed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;421&lt;/code&gt; HTTPMisdirectedRequest&lt;/li&gt;
&lt;li&gt;&lt;code&gt;426&lt;/code&gt; HTTPUpgradeRequired&lt;/li&gt;
&lt;li&gt;&lt;code&gt;428&lt;/code&gt; HTTPPreconditionRequired&lt;/li&gt;
&lt;li&gt;&lt;code&gt;429&lt;/code&gt; HTTPTooManyRequests&lt;/li&gt;
&lt;li&gt;&lt;code&gt;431&lt;/code&gt; HTTPRequestHeaderFieldsTooLarge&lt;/li&gt;
&lt;li&gt;&lt;code&gt;451&lt;/code&gt; HTTPUnavailableForLegalReasons&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id="500x-status-code"&gt;&lt;a class="toclink" href="#500x-status-code"&gt;500x status&amp;nbsp;code&lt;/a&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;500&lt;/code&gt; HTTPInternalServerError&lt;/li&gt;
&lt;li&gt;&lt;code&gt;501&lt;/code&gt; HTTPNotImplemented&lt;/li&gt;
&lt;li&gt;&lt;code&gt;502&lt;/code&gt; HTTPBadGateway&lt;/li&gt;
&lt;li&gt;&lt;code&gt;503&lt;/code&gt; HTTPServiceUnavailable&lt;/li&gt;
&lt;li&gt;&lt;code&gt;504&lt;/code&gt; HTTPGatewayTimeout&lt;/li&gt;
&lt;li&gt;&lt;code&gt;505&lt;/code&gt; HTTPVersionNotSupported&lt;/li&gt;
&lt;li&gt;&lt;code&gt;506&lt;/code&gt; HTTPVariantAlsoNegotiates&lt;/li&gt;
&lt;li&gt;&lt;code&gt;510&lt;/code&gt; HTTPNotExtended&lt;/li&gt;
&lt;li&gt;&lt;code&gt;511&lt;/code&gt; HTTPNetworkAuthenticationRequired&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="web-exceptions-links"&gt;&lt;a class="toclink" href="#web-exceptions-links"&gt;Web exceptions&amp;nbsp;links&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Source code on &lt;span class="caps"&gt;GH&lt;/span&gt; &amp;#8212; &lt;a href="https://github.com/samael500/web-exceptions"&gt;samael500/web-exceptions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Pypi package &amp;#8212; &lt;a href="https://pypi.python.org/pypi/django-web-exceptions/0.1.4"&gt;django-web-exceptions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docs on readthedocs &amp;#8212; &lt;a href="http://web-exceptions.readthedocs.io/en/latest/readme.html"&gt;web-exceptions.readthedocs.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Example usage proj on &lt;span class="caps"&gt;GH&lt;/span&gt; &amp;#8212; &lt;a href="https://github.com/samael500/web-exceptions/tree/master/example"&gt;samael500/web-exceptions/example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</summary><category term="django"></category><category term="exceptions"></category><category term="web"></category><category term="http"></category><category term="response"></category></entry><entry><title>Кластеризация маркеров в GeoServer</title><link href="https://maks.live/articles/python/klasterizatsiia-markerov-v-geoserver/" rel="alternate"></link><published>2017-05-10T15:00:00+03:00</published><updated>2017-05-10T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2017-05-10:articles/python/klasterizatsiia-markerov-v-geoserver/</id><summary type="html">&lt;p&gt;На одном из текущих проектов мы строим геоинформационную систему.
Работаем с геоданными через &lt;a href="http://postgis.net/"&gt;PostGIS&lt;/a&gt;
и &lt;a href="http://geoserver.org/"&gt;GeoServer&lt;/a&gt;. Объектов на карте достаточно много
и в перспективе будет всё больше. Отрисовка всех маркеров на крупном масштабе
заставляет геосервер нагружать систему на 100%. Для оптимизации работы системы,
а также повышения наглядности для пользователя. Отдельные маркеры на карте
необходимо группировать в&amp;nbsp;кластеры.&lt;/p&gt;
&lt;p&gt;Как оказалось, сделать это силами геосервeра очень просто,
но пока этот способ был найден, пришлось перерыть
всю документацию по геосерверу и весь &lt;code&gt;Gis StackExchange&lt;/code&gt;. В результате
группировка точек в кластеры осуществляется с помощью векторной трансформации
&lt;a href="http://docs.geoserver.org/latest/en/user/styling/ysld/reference/transforms.html#point-stacker"&gt;vec:PointStacker&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;link rel="stylesheet" href="/extra/wbt/comparator.css"&gt;
&lt;script src="/extra/wbt/comparator.js"&gt;&lt;/script&gt;&lt;/p&gt;
&lt;h3 id="klasterizatsiia-tochek"&gt;&lt;a class="toclink" href="#klasterizatsiia-tochek"&gt;Кластеризация&amp;nbsp;точек&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Для объединения объектов в один кластер необходимо в стиле слоя объявить
векторную трансформацию &lt;code&gt;vec:PointStacker&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Данная трансформация принимает следующие&amp;nbsp;параметры:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cellSize&lt;/code&gt; &amp;#8212; Размер ячейки в пределах которой точки будут объединяться в&amp;nbsp;кластер.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outputBBOX&lt;/code&gt; &amp;#8212; Координаты углов результирующего&amp;nbsp;тайла.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outputWidth&lt;/code&gt; &amp;#8212; Ширина результирующего&amp;nbsp;тайла.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;outputHeight&lt;/code&gt; &amp;#8212; Высота результирующего&amp;nbsp;тайла.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;На выходе получаем кластеры с информацие о количестве&amp;nbsp;объектов.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;geom&lt;/code&gt; &amp;#8212; геометрия&amp;nbsp;кластера.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count&lt;/code&gt; &amp;#8212; число объектов вошедших в&amp;nbsp;кластер.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;countUnique&lt;/code&gt; &amp;#8212; число уникальных объектов в&amp;nbsp;кластере.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Размер ячейки выбираем &amp;#8220;на глаз&amp;#8221;, чтобы объекты красиво группировались
с точки зрения пользователя. Чем больше ячейка, тем меньше кластеров
получается в&amp;nbsp;результате.&lt;/p&gt;
&lt;p&gt;Остальные параметры передаем из исходного тайла через функцию &lt;code&gt;env&lt;/code&gt;
входящего &lt;code&gt;wms&lt;/code&gt; запроса.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Transformation&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vec:PointStacker&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;data&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;cellSize&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;99&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;outputBBOX&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;wms_bbox&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;outputWidth&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;wms_width&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;outputHeight&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;wms_height&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Transformation&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Далее для кластеров необходимо задать стиль отображения. Будем отображать
кластеры на карте в виде кругов с текстом соответствующим количеству
объектов вошедших в кластер. Размер круга выбираем
в зависимости от числа объектов вошедших в него, например по&amp;nbsp;формуле:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Стиль состоит из описания текста &lt;code&gt;TextSymbolizer&lt;/code&gt; и точки &lt;code&gt;PointSymbolizer&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;Rule&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Name&amp;gt;&lt;/span&gt;Point group cluster&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Title&amp;gt;&lt;/span&gt;Realties group&lt;span class="nt"&gt;&amp;lt;/Title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TextSymbolizer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Label&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ogc:PropertyName&amp;gt;&lt;/span&gt;count&lt;span class="nt"&gt;&amp;lt;/ogc:PropertyName&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Font&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;font-family&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Arial&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;font-size&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;12&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;font-weight&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;bold&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Font&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;LabelPlacement&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;PointPlacement&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;AnchorPoint&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;AnchorPointX&amp;gt;&lt;/span&gt;0.5&lt;span class="nt"&gt;&amp;lt;/AnchorPointX&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;AnchorPointY&amp;gt;&lt;/span&gt;0.8&lt;span class="nt"&gt;&amp;lt;/AnchorPointY&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/AnchorPoint&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/PointPlacement&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/LabelPlacement&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Fill&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;#000&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill-opacity&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1.0&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Fill&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;VendorOption&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;partials&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/VendorOption&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/TextSymbolizer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PointSymbolizer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Graphic&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Mark&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;WellKnownName&amp;gt;&lt;/span&gt;circle&lt;span class="nt"&gt;&amp;lt;/WellKnownName&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Fill&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;#1E90FF&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill-opacity&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0.75&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/Fill&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/Mark&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Size&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;ogc:Add&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;25&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;ogc:Mul&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;log&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                           &lt;span class="nt"&gt;&amp;lt;ogc:PropertyName&amp;gt;&lt;/span&gt;count&lt;span class="nt"&gt;&amp;lt;/ogc:PropertyName&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;5&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/ogc:Mul&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/ogc:Add&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/Size&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Graphic&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PointSymbolizer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Для описания стиля текста кластера важно указать опцию &lt;code&gt;partials&lt;/code&gt;,
иначе любой текст на границе тайла не будет отображаться и, возможно,
возникновение кластеров без подписи числа&amp;nbsp;объектов.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;VendorOption&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;partials&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/VendorOption&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В одном слое у нас отображаются объекты разных торговых сетей
и стиль иконок должен быть различным для них.
Но при кластеризации сохраняется только информация о количестве объектов,
но никак не о качестве (индивидуальных особенностях).
Даже для кластера из одного объекта нельзя получить свойства этого объекта.
Поэтому будем запускать кластеризацию, начиная с некоторого масштаба.
А для меньшего масштаба отображать все точки&amp;nbsp;отдельно.&lt;/p&gt;
&lt;p&gt;Разделить отображения можно с помощью задания минимального
и максимального масштаба&amp;nbsp;отображения.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- Стиль отдельного объекта для масштабов меньше заданного максимальной величиной --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MaxScaleDenominator&amp;gt;&lt;/span&gt;70000&lt;span class="nt"&gt;&amp;lt;/MaxScaleDenominator&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Стиль кластера для всех масштабов больше чем минимальная величина --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MinScaleDenominator&amp;gt;&lt;/span&gt;70000&lt;span class="nt"&gt;&amp;lt;/MinScaleDenominator&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="rezultat"&gt;&lt;a class="toclink" href="#rezultat"&gt;Результат&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;details&gt;
    &lt;summary&gt;Весь стиль объектов&lt;/summary&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;StyledLayerDescriptor&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0.0&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.opengis.net/sld&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:ogc=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.opengis.net/ogc&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:xlink=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.w3.org/1999/xlink&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NamedLayer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Name&amp;gt;&lt;/span&gt;Realties&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;UserStyle&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Name&amp;gt;&lt;/span&gt;Realties&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Title&amp;gt;&lt;/span&gt;Realties objects icons&lt;span class="nt"&gt;&amp;lt;/Title&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Abstract&amp;gt;&lt;/span&gt;SVG styles for realties objects&lt;span class="nt"&gt;&amp;lt;/Abstract&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;FeatureTypeStyle&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Rule&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;MaxScaleDenominator&amp;gt;&lt;/span&gt;70000&lt;span class="nt"&gt;&amp;lt;/MaxScaleDenominator&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;Title&amp;gt;&lt;/span&gt;Realty&lt;span class="nt"&gt;&amp;lt;/Title&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;PointSymbolizer&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Graphic&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ExternalGraphic&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;OnlineResource&lt;/span&gt;
                                    &lt;span class="na"&gt;xlink:type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;simple&amp;quot;&lt;/span&gt;
                                    &lt;span class="na"&gt;xlink:href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./img/${logo_name}.svg&amp;quot;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;Format&amp;gt;&lt;/span&gt;image/svg+xml&lt;span class="nt"&gt;&amp;lt;/Format&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/ExternalGraphic&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Size&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;35&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/Size&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/Graphic&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/PointSymbolizer&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/FeatureTypeStyle&amp;gt;&lt;/span&gt;

            &lt;span class="nt"&gt;&amp;lt;FeatureTypeStyle&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;Transformation&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;vec:PointStacker&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;data&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;cellSize&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;99&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;outputBBOX&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;wms_bbox&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;outputWidth&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;wms_width&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;parameter&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;outputHeight&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;env&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;wms_height&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/Transformation&amp;gt;&lt;/span&gt;

                &lt;span class="nt"&gt;&amp;lt;Rule&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;MinScaleDenominator&amp;gt;&lt;/span&gt;70000&lt;span class="nt"&gt;&amp;lt;/MinScaleDenominator&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;Name&amp;gt;&lt;/span&gt;Point group cluster&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;Title&amp;gt;&lt;/span&gt;Realties group&lt;span class="nt"&gt;&amp;lt;/Title&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;TextSymbolizer&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Label&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;ogc:PropertyName&amp;gt;&lt;/span&gt;count&lt;span class="nt"&gt;&amp;lt;/ogc:PropertyName&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/Label&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Font&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;font-family&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Arial&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;font-size&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;12&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;font-weight&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;bold&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/Font&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;LabelPlacement&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;PointPlacement&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;AnchorPoint&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;AnchorPointX&amp;gt;&lt;/span&gt;0.5&lt;span class="nt"&gt;&amp;lt;/AnchorPointX&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;AnchorPointY&amp;gt;&lt;/span&gt;0.8&lt;span class="nt"&gt;&amp;lt;/AnchorPointY&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/AnchorPoint&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/PointPlacement&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/LabelPlacement&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Fill&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;#000&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill-opacity&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1.0&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/Fill&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;VendorOption&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;partials&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/VendorOption&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/TextSymbolizer&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;PointSymbolizer&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;Graphic&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Mark&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;WellKnownName&amp;gt;&lt;/span&gt;circle&lt;span class="nt"&gt;&amp;lt;/WellKnownName&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;Fill&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;#1E90FF&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;CssParameter&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fill-opacity&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0.75&lt;span class="nt"&gt;&amp;lt;/CssParameter&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/Fill&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/Mark&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;Size&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;ogc:Add&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;25&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;ogc:Mul&amp;gt;&lt;/span&gt;
                                        &lt;span class="nt"&gt;&amp;lt;ogc:Function&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;log&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                                           &lt;span class="nt"&gt;&amp;lt;ogc:PropertyName&amp;gt;&lt;/span&gt;count&lt;span class="nt"&gt;&amp;lt;/ogc:PropertyName&amp;gt;&lt;/span&gt;
                                        &lt;span class="nt"&gt;&amp;lt;/ogc:Function&amp;gt;&lt;/span&gt;
                                        &lt;span class="nt"&gt;&amp;lt;ogc:Literal&amp;gt;&lt;/span&gt;5&lt;span class="nt"&gt;&amp;lt;/ogc:Literal&amp;gt;&lt;/span&gt;
                                    &lt;span class="nt"&gt;&amp;lt;/ogc:Mul&amp;gt;&lt;/span&gt;
                                &lt;span class="nt"&gt;&amp;lt;/ogc:Add&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;/Size&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/Graphic&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/PointSymbolizer&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/Rule&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/FeatureTypeStyle&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;/UserStyle&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/NamedLayer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/StyledLayerDescriptor&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;Для сравнения представлены изображения с кластеризацией объектов и без&amp;nbsp;неё.&lt;/p&gt;
&lt;div id='map'&gt;&lt;/div&gt;

&lt;div id='map-zoom'&gt;&lt;/div&gt;

&lt;p&gt;Также кластеризация позволила значительно увеличить производительность системы.
Рендер большого числа отдельных маркеров выполнялся медленно и существенно
нагружал процессор. Часто соединение обрывалось по &lt;code&gt;504&lt;/code&gt;.
Объединение в кластеры работает очень быстро без нагрузки на&amp;nbsp;систему.&lt;/p&gt;
&lt;p&gt;Кластеризованный результат (полная карта) загружается в среднем за 1.6 секунд.
Тогда как отдельные маркеры грузились порядка 130&amp;nbsp;секунд.&lt;/p&gt;
&lt;h4 id="zagruzka-klasterizirovannykh-markerov"&gt;&lt;a class="toclink" href="#zagruzka-klasterizirovannykh-markerov"&gt;Загрузка кластеризированных&amp;nbsp;маркеров&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img alt="waterfall cluster" class="center" src="/media/pointstacker/waterfall-cluster.png" /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Load Time&lt;/th&gt;
&lt;th&gt;First Byte&lt;/th&gt;
&lt;th&gt;Start Render&lt;/th&gt;
&lt;th&gt;Speed Index&lt;/th&gt;
&lt;th&gt;Interactive (beta)&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Requests&lt;/th&gt;
&lt;th&gt;Bytes In&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.647s&lt;/td&gt;
&lt;td&gt;1.557s&lt;/td&gt;
&lt;td&gt;3.718s&lt;/td&gt;
&lt;td&gt;3718&lt;/td&gt;
&lt;td&gt;&amp;gt; 3.763s&lt;/td&gt;
&lt;td&gt;1.677s&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;9 &lt;span class="caps"&gt;KB&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id="zagruzka-otdelnykh-markerov"&gt;&lt;a class="toclink" href="#zagruzka-otdelnykh-markerov"&gt;Загрузка отдельных&amp;nbsp;маркеров&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img alt="waterfall points" src="/media/pointstacker/waterfall-points.png" /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Load Time&lt;/th&gt;
&lt;th&gt;First Byte&lt;/th&gt;
&lt;th&gt;Start Render&lt;/th&gt;
&lt;th&gt;Speed Index&lt;/th&gt;
&lt;th&gt;Interactive (beta)&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Requests&lt;/th&gt;
&lt;th&gt;Bytes In&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;130.397s&lt;/td&gt;
&lt;td&gt;8.268s&lt;/td&gt;
&lt;td&gt;21.934s&lt;/td&gt;
&lt;td&gt;57691&lt;/td&gt;
&lt;td&gt;8.445s&lt;/td&gt;
&lt;td&gt;130.397s&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;960 &lt;span class="caps"&gt;KB&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Так что простое объединение маркеров в кластеры,
позволило повысить скорость загрузки страницы в сотню&amp;nbsp;раз.&lt;/p&gt;
&lt;script&gt;
$("#map").wbtComparator({
    // direction: "horizontal",
    src: ["/media/pointstacker/cluster.png", "/media/pointstacker/objects.png"],
    timeout: false
});
$("#map-zoom").wbtComparator({
    // direction: "horizontal",
    src: ["/media/pointstacker/zoom-cluster.png", "/media/pointstacker/zoom-objects.png"],
    timeout: false
});
$('table').addClass('table table-bordered table-responsive');
&lt;/script&gt;</summary><category term="geoserver"></category><category term="map"></category><category term="postgis"></category><category term="gis"></category><category term="geodata"></category><category term="sld"></category></entry><entry><title>Простой чат на AioHTTP</title><link href="https://maks.live/articles/python/prostoi-chat-na-aiohttp/" rel="alternate"></link><published>2017-04-10T15:00:00+03:00</published><updated>2017-04-10T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2017-04-10:articles/python/prostoi-chat-na-aiohttp/</id><summary type="html">&lt;p&gt;В повседневной работе я тесно связан с &lt;code&gt;Python 3&lt;/code&gt;, но такие его замечательные
возможности, как асинхронность &lt;a href="https://docs.python.org/3/library/asyncio.html"&gt;asyncio&lt;/a&gt;
и синтаксический сахар &lt;a href="https://www.python.org/dev/peps/pep-0492/"&gt;&lt;span class="caps"&gt;PEP&lt;/span&gt; 492&lt;/a&gt;
использовать не приходиться. Из асинхронных задач сталкиваюсь только с &lt;code&gt;Celery&lt;/code&gt;,
но это не совсем та асинхронность, скорее бэкграунд
с очередью задач выполняемых воркерами синхронно. Пришло время исправить это
и поближе познакомится с асинхронностью в &lt;code&gt;Python 3.5+&lt;/code&gt;. Сделаем это
на примере простого чата с&amp;nbsp;комнатами.&lt;/p&gt;
&lt;h3 id="postanovka-zadachi"&gt;&lt;a class="toclink" href="#postanovka-zadachi"&gt;Постановка&amp;nbsp;задачи&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Реализовать простой асинхронный чат с комнатами на &lt;code&gt;WebSocket&lt;/code&gt;-ах.&lt;/p&gt;
&lt;p&gt;Необходимый&amp;nbsp;функционал:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Создать пользователя или авторизоваться под&amp;nbsp;существующим.&lt;/li&gt;
&lt;li&gt;Создать комнату или подключиться к уже&amp;nbsp;существующей.&lt;/li&gt;
&lt;li&gt;Иметь возможность видеть всю историю чат комнаты.
    В том числе подключения и отключения пользователей от&amp;nbsp;комнаты.&lt;/li&gt;
&lt;li&gt;Администрирование&amp;nbsp;комнаты.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tekhnologii"&gt;&lt;a class="toclink" href="#tekhnologii"&gt;Технологии&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Для реализации чата будем использовать &lt;code&gt;Python 3.6&lt;/code&gt; и фреймворк
&lt;a href="http://aiohttp.readthedocs.io/en/stable/"&gt;AioHTTP&lt;/a&gt;,
данные хранить в базе &lt;code&gt;PostgreSQL&lt;/code&gt;, текущие сессии в &lt;code&gt;Redis&lt;/code&gt;,
запросы проксировать через &lt;code&gt;Nginx&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="asinkhronnyi-freimvork"&gt;&lt;a class="toclink" href="#asinkhronnyi-freimvork"&gt;Асинхронный&amp;nbsp;фреймворк&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Текущая версия &lt;code&gt;AioHTTP 2.0.6&lt;/code&gt;, однако оказалось, что с ней несовместима
текущая версия &lt;code&gt;AioHTTP debugtoolbar&lt;/code&gt;
&lt;a href="https://github.com/aio-libs/aiohttp-debugtoolbar/issues/115"&gt;issue #115&lt;/a&gt;.
Так что будем использовать предыдущую версию &lt;code&gt;AioHTTP 1.3.5&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Простейшее приложение на &lt;code&gt;AioHTTP&lt;/code&gt; выглядит&amp;nbsp;так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match_info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Anonymous&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello, &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/&lt;/span&gt;&lt;span class="si"&gt;{name}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Как можно заметить, роуты конфигурируются после инициализации приложения.
&lt;code&gt;Url&lt;/code&gt; аргументы задаются подобно форматированию строк &lt;code&gt;{slug}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В момент создания приложения можно указать &lt;code&gt;middlewares&lt;/code&gt;, передав
их в конструктор&amp;nbsp;приложения.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;middlewares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="baza-dannykh-i-modeli"&gt;&lt;a class="toclink" href="#baza-dannykh-i-modeli"&gt;База данных и&amp;nbsp;модели&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В качестве базы данных используем &lt;code&gt;PostgreSQL&lt;/code&gt;, для работы с которой воспользуемся
&lt;a href="http://docs.peewee-orm.com/en/latest/"&gt;PeeWee &lt;span class="caps"&gt;ORM&lt;/span&gt;&lt;/a&gt; и асинхронным менеджером,
&lt;a href="https://peewee-async.readthedocs.io/en/latest/"&gt;PeeWee Async&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Создадим базовую модель с неинициализированной базой.
Саму базу инициализируем в момент конфигурирования&amp;nbsp;приложения.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;peewee&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;peewee_async&lt;/span&gt;


&lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peewee_async&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PostgresqlDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peewee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Base model with db Meta &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;peewee_async&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;helpers.models&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;

&lt;span class="n"&gt;DATABASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;database&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aiochat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;supersecret&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aiochat_user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;database&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_allow_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;peewee_async&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Сама идея Менеджера базы данных взята из &lt;code&gt;Django&lt;/code&gt;, но в &lt;code&gt;PeeWee Async&lt;/code&gt;
работает это немного по-другому и с непривычки вызывает&amp;nbsp;недоумение.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;High-level &lt;span class="caps"&gt;API&lt;/span&gt; provides a single point for all async &lt;span class="caps"&gt;ORM&lt;/span&gt; calls.
Meet the Manager class! The idea of Manager originally comes from Django,
but it’s redesigned to meet new asyncio&amp;nbsp;patterns.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Django manager&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username__iexact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Pee Wee manager&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Через менеджер вызываются асинхронные запросы,
но через контекст можно провести синхронный запрос, например, создание&amp;nbsp;таблицы.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allow_sync&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="shablony-i-puti"&gt;&lt;a class="toclink" href="#shablony-i-puti"&gt;Шаблоны и&amp;nbsp;пути&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В качестве шаблонизатора будем использовать
&lt;a href="http://aiohttp-jinja2.readthedocs.io/en/stable/"&gt;асинхронную jinja2&lt;/a&gt;.
Конфигурируется через вызов метода &lt;code&gt;setup&lt;/code&gt;, в который передается
загрузчик шаблонов и список контекстных&amp;nbsp;процессоров.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jinja2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;aiohttp_jinja2&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="n"&gt;jinja_env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aiohttp_jinja2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;jinja2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileSystemLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TEMPLATE_DIR&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;context_processors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aiohttp_jinja2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_processor&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Рендер шаблона изящно спрятан в декоратор для&amp;nbsp;вьюхи.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;aiohttp_jinja2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@aiohttp_jinja2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;template_name.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# return context for render&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;boo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Routes, как уже было сказано выше, конфигурируются после инициализации приложения.
Через функциию &lt;code&gt;app.router.add_route&lt;/code&gt;. Несмотря на то, что статику у нас будет
отдавать &lt;code&gt;Nginx&lt;/code&gt;, задаем её специальным роутом &lt;code&gt;add_static&lt;/code&gt;, чтобы в шаблонах
иметь возможность подключать статические&amp;nbsp;файлы.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# urls.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;accounts.urls&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;accounts_routes&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;chat.urls&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;chat_routes&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;views&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Index&lt;/span&gt;

&lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;accounts_routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;chat_routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urls&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/static&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STATIC_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;static&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В шаблоне получаем доступ к роутам, через объект &lt;code&gt;app&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app.router&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;index&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;.url_for&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Main page&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app.router.static.url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;chat.css&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;stylesheet&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;app.router.static.url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;chat.js&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="sessii"&gt;&lt;a class="toclink" href="#sessii"&gt;Сессии&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Для работы с сессиями будем использовать
&lt;a href="http://aiohttp-session.readthedocs.io/en/latest/"&gt;aiohttp session&lt;/a&gt;.
Данная библиотека позволяет на выбор работать с &lt;code&gt;cookies&lt;/code&gt; или &lt;code&gt;redis&lt;/code&gt; хранилищами.
Мне больше нравится &lt;code&gt;redis&lt;/code&gt;. Для подключения потребуется библиотека
&lt;a href="https://aioredis.readthedocs.io/en/v0.3.0/"&gt;aioredis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Подключим сессии и&amp;nbsp;редис.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;aioredis&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp_session&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;session_middleware&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp_session.redis_storage&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RedisStorage&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="n"&gt;redis_pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;aioredis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REDIS_CON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;middlewares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;session_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedisStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis_pool&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Чтобы получить текущюю сессию, нужно дождаться ответа из корутины &lt;code&gt;get_session&lt;/code&gt;.
Мне нравится подход в джанго, когда в каждом объекте Запрос
есть прямой доступ к сессии и пользователю.
Сделаем подобное в &lt;code&gt;aiohttp&lt;/code&gt; с помощью &lt;code&gt;middlewares&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp_session&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_session&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;accounts.models&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request_user_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Раз уж у нас есть объект пользователя в запросе, создадим декораторы
для ограничения доступа авторизованным и анонимным&amp;nbsp;пользователям.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;helpres.tools&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Allow only auth users &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapped&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;anonymous_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Allow only anonymous users &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapped&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В данных декораторах используем функцию &lt;code&gt;redirect&lt;/code&gt;, она вызывает исключение,
которое пораждает редирект. В &lt;code&gt;AioHTTP&lt;/code&gt; можно вызвать лобой из
&lt;a href="https://github.com/aio-libs/aiohttp/blob/master/aiohttp/web_exceptions.py"&gt;web исключений&lt;/a&gt;
это сделано круче, чем в &lt;code&gt;Django&lt;/code&gt;, где есть возможность вернуть только
&lt;code&gt;404&lt;/code&gt;, &lt;code&gt;403&lt;/code&gt; и &lt;code&gt;400&lt;/code&gt; через исключения &lt;code&gt;Http404&lt;/code&gt;, &lt;code&gt;PermissionDenied&lt;/code&gt;,
&lt;code&gt;SuspiciousOperation&lt;/code&gt; соответсвенно, а редирект должен быть отдан явным&amp;nbsp;ответом.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;router_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permanent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Redirect to given URL name &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;router_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPMovedPermanently&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="websockets"&gt;&lt;a class="toclink" href="#websockets"&gt;WebSockets&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В &lt;code&gt;AioHTTP&lt;/code&gt; реализованы вебсокеты через &lt;code&gt;web.WebSocketResponse()&lt;/code&gt;,
работа с ними практически не отличается от обычных вьюх,
разве что добавляется асинхронный цикл пока соединение&amp;nbsp;активно.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wshandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebSocketResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MsgType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MsgType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MsgType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Для хранения текущих соединений и рассылки бродкаст сообщений,
будем использовать объект &lt;code&gt;app&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebSocketResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Send messages to all in this room &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;По умолчанию &lt;code&gt;Nginx&lt;/code&gt; не проксирует заголовки &lt;code&gt;Upgrade&lt;/code&gt; и &lt;code&gt;Connection&lt;/code&gt;,
которые используются при переключении на &lt;code&gt;WS&lt;/code&gt; запрос.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt; &lt;span class="nv"&gt;$connection_upgrade&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;default&lt;/span&gt; &lt;span class="s"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;&amp;#39;&amp;#39;&lt;/span&gt;      &lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt;          &lt;span class="s"&gt;http://localhost:8000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;    &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt;    &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;# ws support&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="nv"&gt;$connection_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_read_timeout&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="testirovanie"&gt;&lt;a class="toclink" href="#testirovanie"&gt;Тестирование&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Тестировать приложение &lt;code&gt;AioHTTP&lt;/code&gt; можно разными способами.
Мне нравится подход &lt;a href="http://aiohttp.readthedocs.io/en/stable/testing.html#unittest"&gt;Unittest&lt;/a&gt;.
Единственная особенность: нужно добавить метод &lt;code&gt;get_application&lt;/code&gt;
и объявлять асинхронные тесты, декарируя их через &lt;code&gt;unittest_run_loop&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;aiohttp.test_utils&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AioHTTPTestCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unittest_run_loop&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_app&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AioChatTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AioHTTPTestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Base test case for aiochat &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_application&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Return current app &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;serv_generator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;create_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IndexTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AioChatTestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Testing index app views &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;url_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;index&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@unittest_run_loop&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_url_reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Url should be / &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@unittest_run_loop&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Should get 200 on index page &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Simple asyncio chat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Cоздание тестовой базы данных через &lt;code&gt;PeeWee Async&lt;/code&gt; оказалось не очень удобно,
поэтому тесты будут весьма поверхностные,
только чтобы обозначить общий подход к тестированию приложения на &lt;code&gt;AioHTTP&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="funktsionalnaia-chast"&gt;&lt;a class="toclink" href="#funktsionalnaia-chast"&gt;Функциональная&amp;nbsp;часть&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Основная функциональность чата &amp;#8212; передача сообщений клиентам в пределах&amp;nbsp;комнаты.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Process WS connections &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Send messages to all in this room &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;get_object_or_404&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;slug&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;

        &lt;span class="c1"&gt;# При подключении создаем WebSocketResponse&lt;/span&gt;
        &lt;span class="n"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebSocketResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Создаем пустую комнату если ещё нет&lt;/span&gt;
            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="c1"&gt;# Сохраняем соединение в объекте app и создаем сервисное сообщение&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{user}&lt;/span&gt;&lt;span class="s1"&gt; join chat room&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Рассылка всем подключенным клиентам&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# В асинхронном цикле слушаем сообщения от текущего сокета&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;MsgType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;close&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# Сохраняем сообщение в базу и шлем бродкаст&lt;/span&gt;
                    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Когда соединение закрывается, удаляем пользователя из сохраненных соединений&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Сервисное сообщение об отключении в бродкаст&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{user}&lt;/span&gt;&lt;span class="s1"&gt; left chat room&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# возвращаем WebSocketResponse&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В качестве администрирования комнаты, добавим возможность выполнять команды
пользователей. Возьмем две простые команды:
&lt;code&gt;очистить историю комнаты&lt;/code&gt; и &lt;code&gt;удалить пользователя из комнаты&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Run chat command &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/kill&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# unconnect user from room&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="c1"&gt;# Найдем пользователя по имени и отключим от чата&lt;/span&gt;
            &lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;silent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/clear&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Удалим все сообщения из комнаты&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;# В бродкаст вышлем пользователям команды для очистки истории на клиенте&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wslist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cmd&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;empty&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/help&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Вспомогательная команда для отображения справки&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dedent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;            - /help - display this msg&lt;/span&gt;
&lt;span class="s1"&gt;            - /kill &lt;/span&gt;&lt;span class="si"&gt;{username}&lt;/span&gt;&lt;span class="s1"&gt; - remove user from room&lt;/span&gt;
&lt;span class="s1"&gt;            - /clear - empty all messages in room&lt;/span&gt;
&lt;span class="s1"&gt;            &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;wrong cmd &lt;/span&gt;&lt;span class="si"&gt;{cmd}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В асинхронном цикле будем сравнивать, если сообщение начинается с &lt;code&gt;/&lt;/code&gt;
значит обрабатывать его как&amp;nbsp;команду.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ans&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ans&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="klient"&gt;&lt;a class="toclink" href="#klient"&gt;Клиент&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Браузерный клиент это простое &lt;code&gt;WebSocket&lt;/code&gt; подключение с определенными
командами на отправку и отображение&amp;nbsp;сообщений.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ws://&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;WS_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* Append message to chat area */&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;$messagesContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;li class=&amp;quot;media&amp;quot;&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;/li&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;$chatArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$messagesContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#send&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input[name=&amp;quot;text&amp;quot;]&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Connection to server started&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wasClean&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Clean connection end&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Connection broken&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="rezultat-i-kritika"&gt;&lt;a class="toclink" href="#rezultat-i-kritika"&gt;Результат и&amp;nbsp;критика&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Как выяснилось, асинхронный код в &lt;code&gt;Python 3&lt;/code&gt; практически
не отличается от синхронного. Работать с ним легко и весело. &lt;code&gt;WebSocket&lt;/code&gt;
вызывает отдельный восторг, позволяя отправлять сообщения клиентам со стороны
сервера без явного&amp;nbsp;запроса.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AioHTTP&lt;/code&gt; позволяет легко реализовать простой асинхронный сервер с &lt;code&gt;WebSocket&lt;/code&gt;ами,
но что-то большое я бы не стал на на нем писать,
иначе всё начнет превращаться в сплошную&amp;nbsp;рутину-корутины.&lt;/p&gt;
&lt;p&gt;Полный пример чата можно посмотреть в репозитории на &lt;code&gt;github&lt;/code&gt;
&lt;a href="https://github.com/Samael500/aiochat"&gt;Samael500/aiochat&lt;/a&gt;, а при желании
поиграться, запустив настроенный &lt;code&gt;Vagrant&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="chat" class="center shadow" src="/media/aiochat/chat.png" /&gt;&lt;/p&gt;
&lt;p&gt;Данный чат создан исключительно в ознакомительных целях, поэтому имеет ряд&amp;nbsp;допущений:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Регистрация и авторизация пользователей без паролей и каких-либо&amp;nbsp;подтверждений.&lt;/li&gt;
&lt;li&gt;Каждый пользователь имеет полные права администрирования&amp;nbsp;комнат.&lt;/li&gt;
&lt;li&gt;Нет нормальной валидации форм на создание пользователя или&amp;nbsp;комнаты.&lt;/li&gt;
&lt;li&gt;Команды чата реализованы без валидации формата&amp;nbsp;команды.&lt;/li&gt;
&lt;li&gt;История комнаты отдается без паджинации вся полностью, при хоть сколь-либо
большом числе комнат/пользователей/сообщение, эти запросы будут выполнятmся
долго, создавая нагрузку как на сервер, так и на&amp;nbsp;клиент.&lt;/li&gt;
&lt;li&gt;Подключения вебсокетов хранятся в едином объекте &lt;code&gt;APP&lt;/code&gt;, что тоже создает
лишние проблеммы при большом числе полдключений и комнат.
Рационально в данном случае разпараллеливать комнаты между разными инстансами&amp;nbsp;приложения.&lt;/li&gt;
&lt;/ul&gt;</summary><category term="python"></category><category term="async"></category><category term="asyncio"></category><category term="aiohttp"></category><category term="github"></category><category term="chat"></category><category term="tutorial"></category><category term="best"></category></entry><entry><title>Обработка вебхуков GitHub с помощью Nginx и Lua</title><link href="https://maks.live/articles/github/obrabotka-vebkhukov-github-s-pomoshchiu-nginx-i-lua/" rel="alternate"></link><published>2017-03-31T15:00:00+03:00</published><updated>2017-03-31T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2017-03-20:articles/github/obrabotka-vebkhukov-github-s-pomoshchiu-nginx-i-lua/</id><summary type="html">&lt;p&gt;После того как принимается &lt;code&gt;pull-request&lt;/code&gt; и наработки кода попадают в
мастер, нужно обновить сервер, выполнив на нем команды деплоя.
Обычно у нас эта обязанность возложена на сервер &lt;code&gt;CI TeamCity&lt;/code&gt;,
который в случае успешного билда мастер ветки накатывает изменения
на продакшен сервера. Но иногда не нужно такое сложное взаимодействие,
а достаточно просто знать факт пуша в мастер
и обработать его самостоятельно.
С помощью вебхуков &lt;code&gt;GitHub&lt;/code&gt; может уведомлять о
&lt;a href="https://developer.github.com/v3/activity/events/types/#pushevent"&gt;push&lt;/a&gt;
событиях в репозитории.
Но для валидации и обработки этих запросов нужен какой-либо бекенд.
В этом мне помогает знакомая связка &lt;code&gt;Nginx&lt;/code&gt; + &lt;code&gt;Lua&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="predvaritelnaia-nastroika"&gt;&lt;a class="toclink" href="#predvaritelnaia-nastroika"&gt;Предварительная&amp;nbsp;настройка&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Нам понадобятся &lt;code&gt;Nginx&lt;/code&gt; с модулем
&lt;a href="https://github.com/openresty/lua-nginx-module"&gt;lua-nginx-module&lt;/a&gt;.
И две дополнительные библиотеки для &lt;code&gt;lua&lt;/code&gt;. Для того чтобы прочесть &lt;code&gt;json&lt;/code&gt;
тело запроса используем &lt;a href="http://json.luaforge.net/"&gt;JSON4Lua&lt;/a&gt;, а для
валидации подписи &lt;a href="http://mkottman.github.io/luacrypto/"&gt;LuaCrypto&lt;/a&gt;.
Установим их через менеджер пакетов &lt;a href="https://luarocks.org/"&gt;luarocks&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo luarocks install JSON4Lua
$ sudo luarocks install luacrypto
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Подробнее о том как настроить &lt;code&gt;Nginx&lt;/code&gt; и &lt;code&gt;Lua&lt;/code&gt; можно прочитать в
&lt;a href="https://maks.live/articles/drugoe/sobstvennoe-khranilishche-versirovannykh-vagrant-boksov-s-pomoshchiu-nginx-i-lua/#ustanovka-i-zavisimosti"&gt;статье&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="nash-server"&gt;&lt;a class="toclink" href="#nash-server"&gt;Наш&amp;nbsp;сервер&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Сконфигурируем локейшн для принятия&amp;nbsp;вебхука.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/deploy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;client_body_buffer_size&lt;/span&gt; &lt;span class="s"&gt;3M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt;  &lt;span class="s"&gt;3M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;content_by_lua_file&lt;/span&gt; &lt;span class="s"&gt;/path/to/handler.lua&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Важно установить значения &lt;code&gt;client_body_buffer_size&lt;/code&gt; и &lt;code&gt;client_max_body_size&lt;/code&gt; одинаковыми.
Для корректного чтения тела запроса и предотвращения ошибки работы с временным&amp;nbsp;файлом.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;lua entry thread aborted: runtime error: requesty body in temp file not&amp;nbsp;supported&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="github-hooks"&gt;&lt;a class="toclink" href="#github-hooks"&gt;GitHub&amp;nbsp;hooks&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;В настройках репозитория создадим новый вебхук и направим его на наш&amp;nbsp;локейшн.&lt;/p&gt;
&lt;p&gt;&lt;img alt="gh hook" class="center shadow" src="/media/luahook/gh-hook.png" /&gt;&lt;/p&gt;
&lt;h3 id="validatsiia-zaprosa"&gt;&lt;a class="toclink" href="#validatsiia-zaprosa"&gt;Валидация&amp;nbsp;запроса&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Проверка корректности запроса. В первую очередь
нужно убедиться, что запрос действительно &lt;code&gt;POST&lt;/code&gt;.
Сделаем это с помощью функции
&lt;a href="https://github.com/openresty/lua-nginx-module#ngxreqget_method"&gt;req.get_method&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- should be POST method&lt;/span&gt;
&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_method&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;POST&amp;quot;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wrong event request method: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_method&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_NOT_ALLOWED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Следующим шагом проверим, что этот запрос содержит заголовок с хук методом.
Получив все заголовки с помощью функции
&lt;a href="https://github.com/openresty/lua-nginx-module#ngxreqget_headers"&gt;req.get_headers&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;push&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;-- ...&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;-- with correct header&lt;/span&gt;
&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;X-GitHub-Event&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wrong event type: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;X-GitHub-Event&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_NOT_ACCEPTABLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Так как мы будем слушать вебхуки в формате &lt;code&gt;json&lt;/code&gt;, проверим контент тип&amp;nbsp;запроса.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- should be json encoded request&lt;/span&gt;
&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wrong content type header: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_NOT_ACCEPTABLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;По первичным признакам запрос корректный. Проанализируем тело запроса.
С помощью функций
&lt;a href="https://github.com/openresty/lua-nginx-module#ngxreqread_body"&gt;req.read_body&lt;/a&gt; и
&lt;a href="https://github.com/openresty/lua-nginx-module#ngxreqget_body_data"&gt;req.get_body_data&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- read request body&lt;/span&gt;
&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_body_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;failed to get request body&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Проверим корректность подписи запроса, которая передается в заголовке
&lt;code&gt;X-Hub-Signature&lt;/code&gt;, используя функцию &lt;a href="#proverka-podpisi"&gt;verify_signature&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- validate GH signature&lt;/span&gt;
&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;verify_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;X-Hub-Signature&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;wrong webhook signature&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_FORBIDDEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Последний шаг - убедиться, что этот &lt;code&gt;push&lt;/code&gt; был в интересующей нас ветке.
Разобрав тело запроса в таблицу &lt;code&gt;lua&lt;/code&gt; с помощью функции
&lt;a href="http://json.luaforge.net/#json_decode"&gt;json.decode&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;refs/heads/master&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;-- ...&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- on master branch&lt;/span&gt;
&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ref&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="n"&gt;branch&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Skip branch &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ref&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="proverka-podpisi"&gt;&lt;a class="toclink" href="#proverka-podpisi"&gt;Проверка&amp;nbsp;подписи&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Для подтверждения корректности запроса, &lt;code&gt;GitHub&lt;/code&gt; использует &lt;code&gt;HMAC&lt;/code&gt; &lt;code&gt;SHA1&lt;/code&gt; подпись и передает её в заголовке
&lt;code&gt;X-Hub-Signature&lt;/code&gt;, пример в &lt;a href="https://developer.github.com/webhooks/securing/#validating-payloads-from-github"&gt;документации&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Вызовем функцию &lt;a href="http://luacrypto.luaforge.net/0.1/luacrypto.html#usage-hmac"&gt;crypto.hmac.digest&lt;/a&gt;
и сравним её результат с полученным&amp;nbsp;заголовком.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;MY SUPER SECRET&amp;gt;&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;-- ...&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verify_signature&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hub_sign&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;sha1=&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sha1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hub_sign&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;sign&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="konstantnoe-sravnenie-strok"&gt;&lt;a class="toclink" href="#konstantnoe-sravnenie-strok"&gt;Константное сравнение&amp;nbsp;строк&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Простое сравнение строк на &lt;code&gt;==&lt;/code&gt; использовать не рекомендуется, т.к.
злоумышленник может провести &lt;a href="https://en.wikipedia.org/wiki/Timing_attack"&gt;атаку по времени&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;В &lt;code&gt;lua&lt;/code&gt; нельзя просто так взять и обратиться к строке по индексу.
Так что для удобства внедрим данную функцию в метакласс&amp;nbsp;строки.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;getmetatable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;string.sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Напишем функцию сравнения строк за &amp;#8220;константное&amp;#8221; время.
Строки равны тогда и только тогда, когда равны&amp;nbsp;посимвольно.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;const_eq&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;-- Check is string equals, constant time exec&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;equal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;string.len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;string.len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;math.max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string.len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;string.len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;equal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;equal&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;equal&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="avtomaticheskii-deploi"&gt;&lt;a class="toclink" href="#avtomaticheskii-deploi"&gt;Автоматический&amp;nbsp;деплой&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В случае, если &lt;code&gt;webhook&lt;/code&gt; прошел все валидации, можно ему доверять.
Через системный вызов &lt;a href="https://www.lua.org/manual/5.1/manual.html#pdf-io.popen"&gt;io.popen&lt;/a&gt;
выполним необходимые команды деплоя. В данном примере осуществляется
простой &lt;code&gt;pull&lt;/code&gt; из&amp;nbsp;репозитория.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deploy&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;io.popen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;cd /path/to/repo &amp;amp;&amp;amp; sudo -u username git pull&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Полный пример &lt;code&gt;handler.lua&lt;/code&gt; можно посмотреть
в &lt;a href="https://gist.github.com/Samael500/5dbdf6d55838f841a08eb7847ad1c926"&gt;gist&lt;/a&gt;
или вопросе на &lt;a href="http://stackoverflow.com/a/43146712/4716629"&gt;StackOverflow&lt;/a&gt;.&lt;/p&gt;</summary><category term="github"></category><category term="lua"></category><category term="nginx"></category><category term="webhook"></category><category term="autodeploy"></category></entry><entry><title>Сам себе почтальон</title><link href="https://maks.live/articles/drugoe/sam-sebe-pochtalon/" rel="alternate"></link><published>2017-02-28T15:00:00+03:00</published><updated>2017-02-28T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2017-02-28:articles/drugoe/sam-sebe-pochtalon/</id><summary type="html">&lt;p&gt;Каждый раз при запуске проекта в продакшн, встает вопрос, как
отправлять письма с боевого сервера. Есть множество удобных сервисов, таких как
&lt;a href="https://www.mailgun.com/"&gt;mailgun&lt;/a&gt; или &lt;a href="https://www.mailjet.com/"&gt;mailjet&lt;/a&gt;,
можно отправлять письма со своего домена через &lt;code&gt;smtp&lt;/code&gt;
&lt;a href="https://pdd.yandex.ru/"&gt;Яндекса&lt;/a&gt;. Но иногда нужно организовать свой почтовый
сервер и рассылать письма через&amp;nbsp;него.&lt;/p&gt;
&lt;p&gt;На этапе разработки проекта в качестве почтового сервера мы используем
&lt;a href="https://debugmail.io"&gt;DebugMail&lt;/a&gt; сервис, который позволяет без настройки
своего &lt;code&gt;smtp&lt;/code&gt; сервера отправлять тестовые
&lt;a href="https://debugmail.io/mails/ec14ea018ee2944ff36776c9f1ba1b186984df8a"&gt;письма&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Debug Mail" class="center" src="/media/postfix/dm.png" /&gt;&lt;/p&gt;
&lt;h2 id="pisma-pisma-lichno-na-pochtu-noshu"&gt;&lt;a class="toclink" href="#pisma-pisma-lichno-na-pochtu-noshu"&gt;Письма, письма лично на почту&amp;nbsp;ношу&amp;#8230;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Установим свой почтовый сервер, будем использовать простой и удобный
&lt;a href="http://www.postfix.org/"&gt;PostFix&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo apt-get install postfix
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В интерактивном режиме указываем тип и домен нашего&amp;nbsp;сервера.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Configure Postfix" class="center" src="/media/postfix/ps-configure.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Postfix domain" class="center" src="/media/postfix/ps-domain.png" /&gt;&lt;/p&gt;
&lt;p&gt;Для дальнейшей настройки скопируем базовый конфигурационный файл для &lt;code&gt;debian&lt;/code&gt;.
И добавим доступ только из локального&amp;nbsp;хоста.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo cp /usr/share/postfix/main.cf.debian /etc/postfix/main.cf
$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="s2"&gt;mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128&lt;/span&gt;
&lt;span class="s2"&gt;mydestination = localhost&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sudo tee -a /etc/postfix/main.cf
$ sudo service postfix reload
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;По умолчанию у &lt;code&gt;postfix&lt;/code&gt; открыт 25 порт из внешнего мира, закроем его,
отредактировав &lt;code&gt;/etc/postfix/master.cf&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gd"&gt;--- /etc/postfix/master.cf&lt;/span&gt;
&lt;span class="gi"&gt;+++ /etc/postfix/master.cf&lt;/span&gt;
&lt;span class="gu"&gt;@@ -10,7 +10,7 @@&lt;/span&gt;
 #               (yes)   (yes)   (yes)   (never) (100)
 # ==========================================================================
 # smtp      inet  n       -       -       -       -       smtpd
&lt;span class="gd"&gt;-smtp               inet  n       -       n       -       -       smtpd&lt;/span&gt;
&lt;span class="gi"&gt;+127.0.0.1:smtp     inet  n       -       n       -       -       smtpd&lt;/span&gt;
 #smtp      inet  n       -       -       -       1       postscreen
 #smtpd     pass  -       -       -       -       -       smtpd
 #dnsblog   unix  -       -       -       -       0       dnsblog
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь можем попробовать отправить свое первое письмо с собственного сервера.
Воспользуемся для этого стандартной библиотекой &lt;code&gt;python&lt;/code&gt;
&lt;a href="https://docs.python.org/3.6/library/smtplib.html"&gt;SMTPLib&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;smtplib&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;email.mime.multipart&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;

&lt;span class="n"&gt;text_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;There are test mail text content&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;plain&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;html_part&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;head&amp;gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;lt;h1&amp;gt;Hello from Earth&amp;lt;h1&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;lt;p&amp;gt;There are test mail html content.&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;    &amp;lt;p style=&amp;quot;color:red&amp;quot;&amp;gt;have a good day ;)&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;  &amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;from@some.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;to@other.org&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MIMEMultipart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;alternative&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Subject&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Hello from Earth&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;From&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from_&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;To&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to_&lt;/span&gt;

&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html_part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smtplib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SMTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;to_&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;as_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Письмо успешно отправляется и обязательно попадает в спам, т.к. выглядит
очень подозрительно и отправлено с неподтвержденного&amp;nbsp;адреса.&lt;/p&gt;
&lt;p&gt;&lt;img alt="gmail spam" class="center shadow" src="/media/postfix/gmail.png" /&gt;&lt;/p&gt;
&lt;!-- ### Немного безопасности --&gt;

&lt;p&gt;Подключим &lt;code&gt;tls&lt;/code&gt; шифрование писем, добавив следующие строки в &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;smtpd_tls_security_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;may&lt;/span&gt;
&lt;span class="k"&gt;smtp_tls_security_level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;may&lt;/span&gt;
&lt;span class="k"&gt;smtp_tls_loglevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;smtpd_tls_loglevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;smtpd_tls_CAfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/example.com/chain.pem&lt;/span&gt;
&lt;span class="k"&gt;smtpd_tls_cert_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/example.com/cert.pem&lt;/span&gt;
&lt;span class="k"&gt;smtpd_tls_key_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;

&lt;span class="k"&gt;smtp_tls_session_cache_database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;btree&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;/var/lib/postfix/smtp_scache&lt;/span&gt;

&lt;span class="k"&gt;smtp_tls_mandatory_exclude_ciphers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;aNULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MD&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ADH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;RC&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;PSD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SRP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="k"&gt;DES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;eNULL&lt;/span&gt;
&lt;span class="k"&gt;smtp_tls_mandatory_protocols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;SSLv&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;SSLv&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;TLSv&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;TLSv&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;smtp_tls_protocols&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;SSLv&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;SSLv&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;TLSv&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;TLSv&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;smtp_tls_mandatory_ciphers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;high&lt;/span&gt;
&lt;span class="k"&gt;tls_high_cipherlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;EDH&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;CAMELLIA&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;EDH&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;aRSA&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;EECDH&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;aRSA&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;AESGCM&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;EECDH&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;aRSA&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;SHA&lt;/span&gt;&lt;span class="m"&gt;384&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;EECDH&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;aRSA&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;SHA&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;EECDH&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;CAMELLIA&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;AES&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;CAMELLIA&lt;/span&gt;&lt;span class="m"&gt;128&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;AES&lt;/span&gt;&lt;span class="m"&gt;128&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;SSLv&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;aNULL&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;eNULL&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;LOW&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="k"&gt;DES&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;MD&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;EXP&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;PSK&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;DSS&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;RC&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;SEED&lt;/span&gt;&lt;span class="err"&gt;:!&lt;/span&gt;&lt;span class="k"&gt;ECDSA&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;CAMELLIA&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="k"&gt;-SHA&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;AES&lt;/span&gt;&lt;span class="m"&gt;256&lt;/span&gt;&lt;span class="k"&gt;-SHA&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;CAMELLIA&lt;/span&gt;&lt;span class="m"&gt;128&lt;/span&gt;&lt;span class="k"&gt;-SHA&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="k"&gt;AES&lt;/span&gt;&lt;span class="m"&gt;128&lt;/span&gt;&lt;span class="k"&gt;-SHA&lt;/span&gt;
&lt;span class="k"&gt;tls_random_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;dev&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;/dev/urandom&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Здесь нам понадобятся сертификаты, можно подписать их самостоятельно,
но проще и надежнее использовать бесплатные сертификаты от &lt;a href="https://letsencrypt.org/"&gt;Let’s Encrypt&lt;/a&gt;.
Подробнее о том как их получать в статье &lt;a href="https://maks.live/articles/secure/davaite-shifrovat/"&gt;Давайте шифровать&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;После настройки перезапускаем &lt;code&gt;postfix&lt;/code&gt; и проверяем доступность &lt;code&gt;tls&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ openssl s_client -starttls smtp -showcerts -connect localhost:25

CONNECTED&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;00000003&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; US, &lt;span class="nv"&gt;O&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; Let&lt;span class="s1"&gt;&amp;#39;s Encrypt, CN = Let&amp;#39;&lt;/span&gt;s Encrypt Authority X3
verify error:num&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;:unable to get &lt;span class="nb"&gt;local&lt;/span&gt; issuer certificate
---
Certificate chain
 &lt;span class="m"&gt;0&lt;/span&gt; s:/CN&lt;span class="o"&gt;=&lt;/span&gt;example.com
   i:/C&lt;span class="o"&gt;=&lt;/span&gt;US/O&lt;span class="o"&gt;=&lt;/span&gt;Let&lt;span class="s1"&gt;&amp;#39;s Encrypt/CN=Let&amp;#39;&lt;/span&gt;s Encrypt Authority X3
-----BEGIN CERTIFICATE-----
&amp;lt;cert content&amp;gt;
-----END CERTIFICATE-----
 &lt;span class="m"&gt;1&lt;/span&gt; s:/C&lt;span class="o"&gt;=&lt;/span&gt;US/O&lt;span class="o"&gt;=&lt;/span&gt;Let&lt;span class="s1"&gt;&amp;#39;s Encrypt/CN=Let&amp;#39;&lt;/span&gt;s Encrypt Authority X3
   i:/O&lt;span class="o"&gt;=&lt;/span&gt;Digital Signature Trust Co./CN&lt;span class="o"&gt;=&lt;/span&gt;DST Root CA X3
-----BEGIN CERTIFICATE-----
&amp;lt;cert content&amp;gt;
-----END CERTIFICATE-----
---
Server certificate
&lt;span class="nv"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/CN&lt;span class="o"&gt;=&lt;/span&gt;example.com
&lt;span class="nv"&gt;issuer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/C&lt;span class="o"&gt;=&lt;/span&gt;US/O&lt;span class="o"&gt;=&lt;/span&gt;Let&lt;span class="s1"&gt;&amp;#39;s Encrypt/CN=Let&amp;#39;&lt;/span&gt;s Encrypt Authority X3
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, &lt;span class="m"&gt;256&lt;/span&gt; bits
---
SSL handshake has &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="m"&gt;3884&lt;/span&gt; bytes and written &lt;span class="m"&gt;468&lt;/span&gt; bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is &lt;span class="m"&gt;4096&lt;/span&gt; bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    Session-ID: 9C05050D143CE1474438AEC0A57BD8303953053608ECD5775B565A26455EDADB
    Session-ID-ctx: 
    Master-Key: D6A09F3AC016E489542EAA11E9EFF7B56118D24F849FD480B26E219322E8D97D43DE7C537E9B928A67DDCAD3F9397EC6
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: &lt;span class="m"&gt;7200&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;seconds&lt;span class="o"&gt;)&lt;/span&gt;
    TLS session ticket:
    &lt;span class="m"&gt;0000&lt;/span&gt; - &lt;span class="nb"&gt;cd&lt;/span&gt; a5 c1 &lt;span class="m"&gt;43&lt;/span&gt; &lt;span class="m"&gt;13&lt;/span&gt; c9 2c 4b-7e &lt;span class="m"&gt;14&lt;/span&gt; f6 &lt;span class="m"&gt;53&lt;/span&gt; &lt;span class="m"&gt;92&lt;/span&gt; &lt;span class="m"&gt;86&lt;/span&gt; &lt;span class="m"&gt;89&lt;/span&gt; &lt;span class="m"&gt;29&lt;/span&gt;   ...C..,K~..S...&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="m"&gt;0010&lt;/span&gt; - &lt;span class="m"&gt;88&lt;/span&gt; dc ae cc d4 &lt;span class="m"&gt;61&lt;/span&gt; a1 4c-ec &lt;span class="m"&gt;05&lt;/span&gt; b1 &lt;span class="m"&gt;61&lt;/span&gt; &lt;span class="m"&gt;94&lt;/span&gt; 0c b1 6c   .....a.L...a...l
    &lt;span class="m"&gt;0020&lt;/span&gt; - &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="m"&gt;57&lt;/span&gt; &lt;span class="m"&gt;84&lt;/span&gt; &lt;span class="m"&gt;63&lt;/span&gt; 0f e4 6a d7-da &lt;span class="m"&gt;08&lt;/span&gt; &lt;span class="m"&gt;45&lt;/span&gt; 7e &lt;span class="m"&gt;80&lt;/span&gt; 3e fd d7   .W.c..j...E~.&amp;gt;..
    &lt;span class="m"&gt;0030&lt;/span&gt; - &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="m"&gt;82&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt; d5 e4 8a bd ba-6f 9d b9 6f d9 &lt;span class="m"&gt;49&lt;/span&gt; &lt;span class="m"&gt;56&lt;/span&gt; b7   ..p.....o..o.IV.
    &lt;span class="m"&gt;0040&lt;/span&gt; - d4 &lt;span class="m"&gt;41&lt;/span&gt; 5a c7 &lt;span class="m"&gt;27&lt;/span&gt; &lt;span class="m"&gt;53&lt;/span&gt; &lt;span class="m"&gt;82&lt;/span&gt; b5-8d 5d &lt;span class="m"&gt;22&lt;/span&gt; &lt;span class="m"&gt;08&lt;/span&gt; &lt;span class="m"&gt;38&lt;/span&gt; 3c a5 &lt;span class="m"&gt;59&lt;/span&gt;   .AZ.&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;S...&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.8&amp;lt;.Y&lt;/span&gt;
&lt;span class="s2"&gt;    0050 - 6b 06 e7 5f ba 36 2f 91-44 85 7a 5d 1c e4 da d9   k.._.6/.D.z]....&lt;/span&gt;
&lt;span class="s2"&gt;    0060 - 90 78 64 10 63 6a df c9-79 8d d9 10 66 dc 24 74   .xd.cj..y...f.&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="s2"&gt;&lt;/span&gt;
&lt;span class="s2"&gt;    0070 - fb 5a f6 f5 02 14 c5 d5-b0 b3 65 57 24 01 f2 bd   .Z........eW&lt;/span&gt;$&lt;span class="s2"&gt;...&lt;/span&gt;
&lt;span class="s2"&gt;    0080 - 06 31 f9 a9 e3 32 42 ad-f0 3b b5 3d 39 77 2c 95   .1...2B..;.=9w,.&lt;/span&gt;
&lt;span class="s2"&gt;    0090 - 8f fd e9 4f c9 4c 1d 77-8e 23 e4 ca 48 e8 9f ed   ...O.L.w.#..H...&lt;/span&gt;

&lt;span class="s2"&gt;    Start Time: 1489229346&lt;/span&gt;
&lt;span class="s2"&gt;    Timeout   : 300 (sec)&lt;/span&gt;
&lt;span class="s2"&gt;    Verify return code: 20 (unable to get local issuer certificate)&lt;/span&gt;
&lt;span class="s2"&gt;---&lt;/span&gt;
&lt;span class="s2"&gt;250 DSN&lt;/span&gt;

&lt;span class="s2"&gt;QUIT&lt;/span&gt;
&lt;span class="s2"&gt;DONE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;!&amp;#8212;
Установим авториазацию с помощью [SASL](https://tools.ietf.org/html/rfc2222).

bash
$ sudo apt-get install sasl2-bin


Укажим доступные пары логин\пароль в файле `/etc/postfix/sasl_passwd`.

bash
# домен        логин:хеш пароля
example.com    testuser:59de1412ec33fd96ac4a4bfc793f1133


Дадим доступ на чтение этого файла только администратору и сгенерируем таблицу.

bash
$ sudo chown root:root /etc/postfix/sasl_passwd &amp;&amp; chmod 600 /etc/postfix/sasl_passwd
$ postmap hash:/etc/postfix/sasl_passwd
$ ls -all /etc/postfix/sasl_passwd*
-rw&amp;#8212;&amp;#8212;&amp;#8212;- 1 root root    59 фев  28 18:31 /etc/postfix/sasl_passwd
-rw&amp;#8212;&amp;#8212;&amp;#8212;- 1 root root 12288 фев  28 18:37 /etc/postfix/sasl_passwd.db


Разрешим доступ только аутентифицированным пользователям, добавив в `/etc/postfix/main.cf`

Lighttpd
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname
broken_sasl_auth_clients = yes
smtpd_recipient_restrictions =
   permit_sasl_authenticated, permit_mynetworks, check_relay_domains
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd


Перезапускаем `postfix` и проверяем `SMTP` авторизацию по `telnet`.

telnet
EHLO example.com
250-example.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH DIGEST-MD5 CRAM-MD5 NTLM LOGIN PLAIN
250-AUTH=DIGEST-MD5 CRAM-MD5 NTLM LOGIN PLAIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
quit


Следующие строки указывают на наличие аутентификации.

telnet
250-AUTH DIGEST-MD5 CRAM-MD5 NTLM LOGIN PLAIN
250-AUTH=DIGEST-MD5 CRAM-MD5 NTLM LOGIN PLAIN


Проверяем отправку письма добавив авторизацию.

python
conn = smtplib.SMTP(host)
conn.login(&amp;#8216;testuser&amp;#8217;, &amp;#8216;testpasswd&amp;#8217;)

&amp;#8212;&gt;

&lt;h2 id="podpisyvaius-pod-kazhdym-slovom"&gt;&lt;a class="toclink" href="#podpisyvaius-pod-kazhdym-slovom"&gt;Подписываюсь под каждым&amp;nbsp;словом&amp;#8230;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;На сегодняшний день, считается обязательной подпись электронных писем
с помощью &lt;a href="http://www.dkim.org/"&gt;&lt;span class="caps"&gt;DKIM&lt;/span&gt;&lt;/a&gt;. Это делается для того, чтобы почтовый сервер
получателя мог удостоверится в том, что почта отправлена именно с сервера
указанного в поле &lt;code&gt;From&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="ustanovka-i-nastroika-opendkim"&gt;&lt;a class="toclink" href="#ustanovka-i-nastroika-opendkim"&gt;Установка и настройка&amp;nbsp;OpenDKIM&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Устанавливаем необходимые пакеты, &lt;a href="http://www.opendkim.org/"&gt;OpenDKIM&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo apt-get install opendkim opendkim-tools
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Генерируем ключи и сохраняем их доступными для чтения группе &lt;code&gt;opendkim&lt;/code&gt;,
а также добавляем в эту группу &lt;code&gt;postfix&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo mkdir /etc/opendkim
$ sudo opendkim-genkey -D /etc/opendkim -d &lt;span class="k"&gt;$(&lt;/span&gt;hostname -d&lt;span class="k"&gt;)&lt;/span&gt; -s &lt;span class="k"&gt;$(&lt;/span&gt;hostname&lt;span class="k"&gt;)&lt;/span&gt;
$ sudo chgrp opendkim /etc/opendkim/*
$ sudo chmod g+r /etc/opendkim/*
$ sudo gpasswd -a postfix opendkim
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь указываем &lt;code&gt;opendkim&lt;/code&gt; где находятся ключи. Для этого дописываем
в конфигурационный файл &lt;code&gt;/etc/opendkim.conf&lt;/code&gt; следующие&amp;nbsp;строки.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;Canonicalization&lt;/span&gt; &lt;span class="k"&gt;relaxed&lt;/span&gt;&lt;span class="n"&gt;/relaxed&lt;/span&gt;
&lt;span class="k"&gt;SyslogSuccess&lt;/span&gt; &lt;span class="k"&gt;yes&lt;/span&gt;
&lt;span class="k"&gt;RequireSafeKeys&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;KeyTable&lt;/span&gt; &lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;/etc/opendkim/keytable&lt;/span&gt;
&lt;span class="k"&gt;SigningTable&lt;/span&gt; &lt;span class="k"&gt;file&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="n"&gt;/etc/opendkim/signingtable&lt;/span&gt;
&lt;span class="k"&gt;X-Header&lt;/span&gt; &lt;span class="k"&gt;yes&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Подробнее ознакомится с возможными параметрами можно в
&lt;a href="http://www.opendkim.org/opendkim.conf.5.html"&gt;документации&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Теперь заполним таблицы ключей в файлах &lt;code&gt;/etc/opendkim/keytable&lt;/code&gt; и &lt;code&gt;/etc/opendkim/signingtable&lt;/code&gt;.
Они указывают соответсвие между доменом и ключем, которым необходимо подписывать&amp;nbsp;письмо.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# /etc/opendkim/keytable&lt;/span&gt;
ключ домен:селектор:/путь/до/ключа
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# /etc/opendkim/signingtable&lt;/span&gt;
домен ключ
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Например:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# /etc/opendkim/keytable&lt;/span&gt;
mail._domainkey.example.com example.com:mail:/etc/opendkim/mail.private
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# /etc/opendkim/signingtable&lt;/span&gt;
example.com    mail._domainkey.example.com
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="nastroika-postfix-dlia-raboty-s-opendkim"&gt;&lt;a class="toclink" href="#nastroika-postfix-dlia-raboty-s-opendkim"&gt;Настройка Postfix для работы с&amp;nbsp;OpenDKIM&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Указываем о необходимости подписывать все письма с помощью &lt;code&gt;dkim&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo postconf -e &lt;span class="nv"&gt;milter_default_action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;accept
$ sudo postconf -e &lt;span class="nv"&gt;milter_protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
$ sudo postconf -e &lt;span class="nv"&gt;smtpd_milters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unix:/var/run/opendkim/opendkim.sock
$ sudo postconf -e &lt;span class="nv"&gt;non_smtpd_milters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unix:/var/run/opendkim/opendkim.sock
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Перезапускаем службы и отправляем подписанное&amp;nbsp;письмо.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo service postfix restart
$ sudo service opendkim restart
&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id="nastroiiki-domennoi-zony"&gt;&lt;a class="toclink" href="#nastroiiki-domennoi-zony"&gt;Настроийки доменной&amp;nbsp;зоны&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Чтобы сервер мог удостовериться в корректности подписи,
нужно добавить &lt;code&gt;TXT&lt;/code&gt; запись содежащую ключ. Сделать это нужно в контрольной
панели регистратора. Проверим, что &lt;code&gt;dns&lt;/code&gt; зоны&amp;nbsp;обновились.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ dig +short TXT mail._domainkey.example.com
&lt;span class="s2"&gt;&amp;quot;v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvfTJ37Gqs06fhG0YYj/6HbojCrDp&lt;/span&gt;
&lt;span class="s2"&gt;F8X6u20YUaOax+jrvO0KtItfWYUi6hkCJeKbGTAOmqhWLu1T/DMt0XaICAJ7Q8525Z4ghwfvc5LgYyNSDEODeF&lt;/span&gt;
&lt;span class="s2"&gt;LNPlXgn3IP5o6Og2We/SnO4QCv8drKGf0N2xm5IIzIT8CjsbM6gPQIHTQIDAQAB&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Также хорошо указать разрешенные &lt;code&gt;ip&lt;/code&gt; адреса для исходящих писем в запись &lt;code&gt;spf&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ dig +short TXT example.com
&lt;span class="s2"&gt;&amp;quot;v=spf1 a:example.com ip4:&amp;lt;ip v4 addr&amp;gt; ip6:&amp;lt;ip v6 addr&amp;gt; ~all&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id="vse-ne-kak-u-liudei"&gt;&lt;a class="toclink" href="#vse-ne-kak-u-liudei"&gt;Все не как у&amp;nbsp;людей&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Сегодня мы живем в далеком и почти светлом будущем, когда по бескрайним
просторам сети широко распространяется &lt;code&gt;ipv6&lt;/code&gt; адресация. Но, оказывается,
абсолютно все письма отправленные с &lt;code&gt;ipv6&lt;/code&gt; всегда воспринимаются гуглом как спам.
Даже пройдя верификацию по &lt;code&gt;dkim&lt;/code&gt; и &lt;code&gt;spf&lt;/code&gt; записям, всеравно уходят в нежелательную&amp;nbsp;почту.&lt;/p&gt;
&lt;p&gt;Так что укажем в конфигурации &lt;code&gt;postfix&lt;/code&gt; отправку только с использованием &lt;code&gt;ipv4&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo postconf -e &lt;span class="nv"&gt;inet_protocols&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ipv4
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь письма успешно доставляются и проходят все&amp;nbsp;валидации.&lt;/p&gt;
&lt;p&gt;&lt;img alt="gmail ok" class="center shadow" src="/media/postfix/gmail_ok.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;details&gt;
    &lt;summary&gt;Пример письма&lt;/summary&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Delivered&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;samael500&lt;/span&gt;&lt;span class="nd"&gt;@gmail.com&lt;/span&gt;
&lt;span class="n"&gt;Received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="mf"&gt;10.182&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;174.67&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;SMTP&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="n"&gt;bq3csp1452226obc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Tue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="n"&gt;Feb&lt;/span&gt; &lt;span class="mi"&gt;2017&lt;/span&gt; &lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0800&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="mf"&gt;10.46&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;22.18&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;SMTP&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="n"&gt;w18mr1151641ljd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;86.1488298192253&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Tue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="n"&gt;Feb&lt;/span&gt; &lt;span class="mi"&gt;2017&lt;/span&gt; &lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0800&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Return&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;*********&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;Received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="o"&gt;*********&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*********.&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;**.**.**.**&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;mx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ESMTPS&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="n"&gt;x14si1225569lfd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;155.2017&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;02.28&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;08.09&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;samael500&lt;/span&gt;&lt;span class="nd"&gt;@gmail.com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TLS1_2&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ECDHE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;RSA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;AES128&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;GCM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Tue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="n"&gt;Feb&lt;/span&gt; &lt;span class="mi"&gt;2017&lt;/span&gt; &lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;0800&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Received&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;SPF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;*********&lt;/span&gt; &lt;span class="n"&gt;designates&lt;/span&gt; &lt;span class="o"&gt;**.**.**.**&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;permitted&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;=**.**.**.**&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Authentication&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="n"&gt;dkim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=*********&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="n"&gt;spf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;*********&lt;/span&gt; &lt;span class="n"&gt;designates&lt;/span&gt; &lt;span class="o"&gt;**.**.**.**&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;permitted&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;smtp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mailfrom&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;*********&lt;/span&gt;
&lt;span class="n"&gt;Received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="o"&gt;*********&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localhost&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="o"&gt;*********&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Postfix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ESMTP&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="n"&gt;D9ED0452AB&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;samael500&lt;/span&gt;&lt;span class="nd"&gt;@gmail.com&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;Tue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="n"&gt;Feb&lt;/span&gt; &lt;span class="mi"&gt;2017&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;53&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mo"&gt;0000&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DKIM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenDKIM&lt;/span&gt; &lt;span class="n"&gt;Filter&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;9.2&lt;/span&gt; &lt;span class="o"&gt;*********&lt;/span&gt; &lt;span class="n"&gt;D9ED0452AB&lt;/span&gt;
&lt;span class="n"&gt;DKIM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;rsa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;relaxed&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;relaxed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=*********&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1488298193&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;bh&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;juRoCRHzIAJJ4fKO8VlXEyxNddxTS8ftBnWmLxjdAik&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;G0Z6uXOV0LQHscdUOwMg5rjuJA&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;KWZ7x6Iqx3Z2x01nZ2kD&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;E1OgyP4zEfqS9XDiS&lt;/span&gt;
     &lt;span class="n"&gt;fG04P0qpIJyGEmO8hgRDIlH1d5FIDGjGPMAFDynwZ9j7pG1h88yLHThdtesUN7Fjib&lt;/span&gt;
     &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;yL1xxiyw2dZbtfvgXwhj0Nb9RXpphrY&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;c9v2fW4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multipart&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;alternative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;boundary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;===============3211535685628593130==&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Hello&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Earth&lt;/span&gt;
&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="o"&gt;*********&lt;/span&gt;
&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;samael500&lt;/span&gt;&lt;span class="nd"&gt;@gmail.com&lt;/span&gt;
&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;20170228160953.&lt;/span&gt;&lt;span class="n"&gt;D9ED0452AB&lt;/span&gt;&lt;span class="o"&gt;*********&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="n"&gt;Feb&lt;/span&gt; &lt;span class="mi"&gt;2017&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;53&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mo"&gt;0000&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;--===============&lt;/span&gt;&lt;span class="mi"&gt;3211535685628593130&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;plain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-ascii&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;

&lt;span class="n"&gt;There&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="o"&gt;--===============&lt;/span&gt;&lt;span class="mi"&gt;3211535685628593130&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;us-ascii&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;MIME&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;


&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Earth&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;There&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;color:red&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;good&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="p"&gt;;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;--===============&lt;/span&gt;&lt;span class="mi"&gt;3211535685628593130&lt;/span&gt;&lt;span class="o"&gt;==--&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;h2 id="django-settings"&gt;&lt;a class="toclink" href="#django-settings"&gt;Django&amp;nbsp;settings&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Теперь можно подключить отправку писем через наш &lt;code&gt;smtp&lt;/code&gt; в&amp;nbsp;джанго.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;EMAIL_HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_HOST_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_HOST_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_USE_TLS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.core.mail.backends.smtp.EmailBackend&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;DEFAULT_FROM_EMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;info@example.com&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="postfix"></category><category term="email"></category><category term="python"></category><category term="dkim"></category><category term="spf"></category></entry><entry><title>Три года в команде</title><link href="https://maks.live/articles/drugoe/tri-goda-v-komande/" rel="alternate"></link><published>2017-01-18T15:00:00+03:00</published><updated>2017-01-18T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2017-01-18:articles/drugoe/tri-goda-v-komande/</id><summary type="html">&lt;p&gt;Кажется ещё только вчера я был студентом, но вот уже пролетело три года
как я работаю в дружной и слаженной команде &lt;a href="http://wbtech.pro"&gt;Вебтек&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="wbt team" class="center" src="/media/wbt3/team.jpg" /&gt;
Встреча &lt;span class="caps"&gt;WB&lt;/span&gt;&amp;#8212;Tech в Севастополе. Февраль 2016&amp;nbsp;г.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="kak-ia-popal-v-wbt"&gt;&lt;a class="toclink" href="#kak-ia-popal-v-wbt"&gt;Как я попал в &lt;span class="caps"&gt;WBT&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Началось всё в конце 2013 года, я тогда учился на 1-м курсе магистратуры,
а знания &lt;code&gt;python&lt;/code&gt; были весьма поверхностными. Скажи мне кто тогда, что я буду
веб-разработчиком, ни за что не поверил бы. Тогда я воспринимал программный
продукт как единую алгоритмическую структуру с четко ограниченными точками
входа и выхода. Идея независимых обработчиков с различными точками входа,
решающих конкретные подзадачи, казалась мне&amp;nbsp;чуждой.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#8212; Почему обезьяна может программировать на &lt;span class="caps"&gt;PHP&lt;/span&gt;?&lt;br /&gt;
&amp;#8212; Потому что она всю жизнь прыгает с ветки на ветку, с лианы на лиану,
независимые обработчики с различными точками входа &amp;#8212; это её повседневное&amp;nbsp;окружение.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Мне предложили пройти стажировку в вебтек, а для поступления выполнить
простое тестовое задание: создать сайт на &lt;code&gt;django&lt;/code&gt; с парой моделей и
несколькими вьюхами. Даже сохранился репозиторий с этим заданием
&lt;a href="https://github.com/Samael500/todo-test-task"&gt;github.com:Samael500/todo-test-task&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Заканчивался семестр, нужно было сдавать зачеты, впереди предстояли
новогодние каникулы, на которые я уехал из города, а по возвращению
пошел на практику в &lt;a href="http://rarus.ru/"&gt;1С-Рарус&lt;/a&gt;, где занятия проходили
с утра до вечера. Так что выполнение тестового задания слегка затянулось&amp;#8230;
более чем на месяц. Примерно на третий день в рарусе я вспомнил о тестовом
задании и наконец решил его сделать, к тому же срок уже&amp;nbsp;поджимал.&lt;/p&gt;
&lt;p&gt;В то время, я плохо знал &lt;code&gt;python&lt;/code&gt;, не сталкивался с &lt;code&gt;django&lt;/code&gt;, не было опыта
работы с &lt;code&gt;linux&lt;/code&gt; и &lt;code&gt;git&lt;/code&gt;. Но взяв за основу
&lt;a href="https://docs.djangoproject.com/en/1.10/intro/tutorial01/"&gt;django tutorial&lt;/a&gt;,
за несколько дней тестовое задание было полностью готово.
За одним исключением: не было произведено ни одного коммита.
Пришло время закоммитить результаты работы и как говорится, ничто не предвещало&amp;nbsp;беды.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ git add .
$ git status
$ &lt;span class="c1"&gt;# тут оказался длинный список не нужных файлов, т.к. я забыл доавить .gitignore&lt;/span&gt;
$ &lt;span class="c1"&gt;# я решил удалить лишнее, но вместо git reset, я совершил ошибку&lt;/span&gt;
$ git rm -rf
$ git add .
$ git status
$ &lt;span class="c1"&gt;# всё пропало, всё что нажито непосильным трудом...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Было и смешно, и грустно одновременно, но ничего, пришлось сделать
тестовое задание с нуля, заново. Во второй раз я справился всего за полдня.
И уже более аккуратно подходил к контролю версий. После проверки тестового
задания, меня взяли на испытательный срок, а в дальнейшем и на постоянную&amp;nbsp;работу.&lt;/p&gt;
&lt;h2 id="3-goda-v-komande"&gt;&lt;a class="toclink" href="#3-goda-v-komande"&gt;3 года в&amp;nbsp;команде&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;За эти три года работы в &lt;a href="http://wbtech.pro"&gt;&lt;span class="caps"&gt;WB&lt;/span&gt;&amp;#8212;Tech&lt;/a&gt; я узнал много нового
и получил практический опыт разработки. Мы делаем аутсорс крупным заказчикам,
так что приходилось сталкиваться с интересными и сложными задачами
в высоконагруженных проектах. С шардированием больших баз данных.
Порой встречалось &lt;code&gt;legacy&lt;/code&gt; слепленное из говна и палок,
без автоматических тестов и документации. Но были и простые, но не менее
интересные задачи.
За это время, я поучаствовал в создании и поддержке многих&amp;nbsp;проектов.&lt;/p&gt;
&lt;p&gt;&lt;details open&gt;
    &lt;summary&gt;Некоторые из которых не ограничены &lt;code&gt;NDA&lt;/code&gt; представлены ниже:&lt;/summary&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Каталог реалитишоу Мир&amp;nbsp;реалити.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://mirreality.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/mirreality.png" class="center" alt="mirreality"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Площадка для купли-продажи запчастей для автомобилей в&amp;nbsp;Казани.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://autokazan.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/autokazan.png" class="center" alt="autokazan"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Сервис создания скришотов вебстраниц&amp;nbsp;Coment.me.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Flask&lt;/code&gt;, &lt;code&gt;PhantomJS&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://coment.me/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/coment.png" class="center" alt="coment"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Спецпроект Ленты к юбилею победы в Великой Отечественной войне. Победа&amp;nbsp;70.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://pobeda70.lenta.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/may9.png" class="center" alt="pobeda70"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Сервис проектирования каркасных&amp;nbsp;домов.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Flask&lt;/code&gt;, &lt;code&gt;MongoDB&lt;/code&gt;, &lt;code&gt;Celery&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="#" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/fhouse.png" class="center" alt="fhouse"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Визуализация науки от команды&amp;nbsp;Visual-Science.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Yii&lt;/code&gt;, &lt;code&gt;MySQL&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://visual-science.com/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/visual.png" class="center" alt="visual"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Геоинформационная система поиска оптимальной точки размещения коммерческого&amp;nbsp;объекта.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;, &lt;code&gt;PostGIS&lt;/code&gt;, &lt;code&gt;Geoserver&lt;/code&gt;, &lt;code&gt;Celery&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="https://arendohod.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/arend.png" class="center" alt="arend"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Увлекательные путешествия по России и миру. Pro&amp;nbsp;Adventure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;, &lt;code&gt;Celery&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="https://pro-adventure.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/pro.png" class="center" alt="pro"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Научно-популярный форум, посвящённый молекулярным основам современной
биологии и практическим применениям научных достижений в&amp;nbsp;биотехнологии.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;, &lt;code&gt;Celery&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="https://biomolecula.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/biom.png" class="center" alt="biom"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Площадка для купли-продаже брендовой одежды.&amp;nbsp;Preloved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;, &lt;code&gt;Celery&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="https://pre-loved.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/plvd.png" class="center" alt="plvd"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;&lt;details open&gt;
    &lt;summary&gt;Пару проектов сделал для себя:&lt;/summary&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Я &amp;#8212; первооткрыватель. Сотри белые пятна с&amp;nbsp;карты.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://underground-maze.github.io/insta-map/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/revealer.png" class="center" alt="revealer"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Мой свадебный информационный&amp;nbsp;сайт.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="https://olya-maks.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/om.png" class="center" alt="om"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Сайт-портфолио.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Django&lt;/code&gt;, &lt;code&gt;PostgreSQL&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://elenaskorokhod.ru/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/helena.png" class="center" alt="helena"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Вебстраница кафедры информационной безопасности&amp;nbsp;СевГУ.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;gh-pages&lt;/code&gt;, &lt;code&gt;static html&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="http://isev.su/" rel="nofollow"&gt;
        &lt;img src="/media/wbt3/thumb/isev.png" class="center" alt="isev"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
&lt;p&gt;Мой блог&amp;nbsp;;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;gh-pages&lt;/code&gt;, &lt;code&gt;Pelican&lt;/code&gt;.&lt;/p&gt;
&lt;div class="center browser-mockup with-tab" style="max-width:600px; width:100%"&gt;
    &lt;a href="/"&gt;
        &lt;img src="/media/wbt3/thumb/log.png" class="center" alt="log"&gt;
    &lt;/a&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;Вебтек &amp;#8212; небольшая компания. Мы работаем удаленно из разных точек планеты.
Нас объединяет трудолюбие и&amp;nbsp;энтузиазм.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="sip" class="center" src="/media/wbt3/sip.jpg" /&gt;
Мы можем работать в любом месте, в любое время, из любого&amp;nbsp;города.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Удалёнка позволяет максимально эффективно работать на результат
ведь не важно сколько часов потрачено на
задачу, главное, что она решена качественно и в срок.
Это даёт возможность свободно
организовывать своё время, не нанося ущерба рабочему процессу. Мы легко
работаем в выходные и праздничные дни, запускаем деплои боевых серверов
глубокими ночами с минимизированным&amp;nbsp;доунтаймом.&lt;/p&gt;
&lt;p&gt;А самое главное, что это нам нравится. Как сказал&amp;nbsp;Конфуций:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Выбери себе работу по душе и тебе не придется работать
ни одного дня в своей&amp;nbsp;жизни.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Безусловно подтверждаю эти слова.
За выбор профессионального пути, я хотел бы&amp;nbsp;поблагодарить:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="sip" class="center" src="/media/wbt3/ss-team.jpg" /&gt;
&lt;span class="caps"&gt;SS&lt;/span&gt; Team, Севастополь. Первая летняя школа. Август 2010&amp;nbsp;г.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Михнева Сергея Сергеевича &amp;#8212; преподаватель и тренер по спортивному
программированию. Ведь именно он увидел мой потенциал и направил в нужное&amp;nbsp;русло.&lt;/li&gt;
&lt;li&gt;Пешкурова Романа &amp;#8212; друг и наставник по алгоритмам, который поверил в меня
раньше, чем я&amp;nbsp;сам.&lt;/li&gt;
&lt;li&gt;Гришанина Кирилла и Шиканова Юрия &amp;#8212; сооснователи вебтек,
спасибо за практический опыт и работу в интересной&amp;nbsp;команде.&lt;/li&gt;
&lt;li&gt;Землянова Василия и Захарова Андрея &amp;#8212; школьные друзья, а затем и однокурсники,
которые выбрали тот же&amp;nbsp;путь.&lt;/li&gt;
&lt;/ul&gt;</summary><category term="wb-tech"></category></entry><entry><title>Собственное хранилище версированных Vagrant боксов с помощью Nginx и Lua</title><link href="https://maks.live/articles/drugoe/sobstvennoe-khranilishche-versirovannykh-vagrant-boksov-s-pomoshchiu-nginx-i-lua/" rel="alternate"></link><published>2016-10-25T15:00:00+03:00</published><updated>2016-10-25T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2016-10-25:articles/drugoe/sobstvennoe-khranilishche-versirovannykh-vagrant-boksov-s-pomoshchiu-nginx-i-lua/</id><summary type="html">&lt;p&gt;Для удобного процесса разработки, быстрого переключения между проектами и
эффективного взаимодействия бэкенд и фронтенд команд. Мы, в
&lt;a href="http://wbtech.pro/"&gt;&lt;span class="caps"&gt;WB&lt;/span&gt;&amp;#8212;Tech&lt;/a&gt;, работаем в виртуальном окружении
&lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; + &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt;.
&lt;code&gt;Vagrant&lt;/code&gt; &amp;#8212; прекрасный менеджер окружения, а для ускорения развертывания
виртуальной машины можно использовать компилированные, версированные боксы.
Хостить их можно на &lt;a href="https://atlas.hashicorp.com/boxes/search"&gt;родном сервере&lt;/a&gt;,
но триал &lt;code&gt;Vagrant enterprise&lt;/code&gt; у нас закончился, в связи с чем было решено
хостить боксы самостоятельно. А сделали мы это с помощью &lt;a href="https://www.lua.org/"&gt;Lua&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Версийность боксов в &lt;code&gt;Vagrant&lt;/code&gt; описывается при помощи &lt;code&gt;json&lt;/code&gt;
&lt;a href="https://www.vagrantup.com/docs/boxes/format.html"&gt;документа&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;box_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;This box description.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;versions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;42.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;quot;providers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;virtualbox&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://somewhere.com/precise64_010_virtualbox.box&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;quot;checksum_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;quot;checksum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Далее, в самом &lt;code&gt;Vagrantfile&lt;/code&gt; нужно указывать путь к метаданным в атрибуте &lt;code&gt;config.vm.box_url&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;box_name&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;42.0&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://somewhere.com/path/to/metadata.json&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Каждый раз при обновлении версии бокса, описывать данный документ вручную, а
потом загружать на сервер вместе с новым боксом, было бы слишком скучно.
Делать для этого какой либо бэкенд нецелесообразно. Когда можно обойтись одним
лишь &lt;code&gt;Nginx&lt;/code&gt; с дополнительным &lt;a href="https://www.nginx.com/resources/wiki/modules/lua/"&gt;модулем&lt;/a&gt;.
Так что формирование метаданных сделано при помощи простого скрипта на &lt;code&gt;Lua&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="daleko-li-do-luny"&gt;&lt;a class="toclink" href="#daleko-li-do-luny"&gt;Далеко ли до&amp;nbsp;Луны?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Lua&lt;/code&gt; (с порт. — &amp;#8221;луна&amp;#8221;) — скриптовый язык программирования,
разработанный в подразделении &lt;code&gt;Tecgraf&lt;/code&gt; Католического университета
Рио-де-Жанейро. Интерпретатор языка является свободно
распространяемым, с открытыми исходными текстами на языке&amp;nbsp;Си.&lt;/p&gt;
&lt;p&gt;По возможностям, идеологии и реализации язык ближе всего к &lt;code&gt;JavaScript&lt;/code&gt;,
однако &lt;code&gt;Lua&lt;/code&gt; отличается более мощными и гораздо более гибкими конструкциями.
Хотя &lt;code&gt;Lua&lt;/code&gt; не содержит понятия класса и объекта в явном виде,
механизмы объектно-ориентированного программирования, включая множественное
наследование, легко реализуются с использованием метатаблиц, которые также
отвечают за перегрузку операций и т. п. Реализуемая модель
объектно-ориентированного программирования — прототипная (как и в &lt;code&gt;JavaScript&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id="ustanovka-i-zavisimosti"&gt;&lt;a class="toclink" href="#ustanovka-i-zavisimosti"&gt;Установка и&amp;nbsp;зависимости&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Описание приведено для операционных систем семейства &lt;code&gt;Debian&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Прежде всего нам потребуется &lt;code&gt;Nginx&lt;/code&gt; с модулем
&lt;a href="https://github.com/openresty/lua-nginx-module"&gt;lua-nginx-module&lt;/a&gt;.
Собрать его можно вручную, но проще установить готовый пакет
&lt;a href="https://packages.debian.org/ru/sid/nginx-extras"&gt;nginx-extras&lt;/a&gt;,
который содержит множество полезных&amp;nbsp;модулей.&lt;/li&gt;
&lt;li&gt;Также не будет лишним интерпретатор &lt;code&gt;Lua&lt;/code&gt;, чтобы оттестировать скрипт
в интерактивной&amp;nbsp;консоли.&lt;/li&gt;
&lt;li&gt;Для компиляции модулей, потребуется утилита &lt;code&gt;make&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Время собирать лунные камни, нам будет нужен менеджер пакетов &lt;a href="https://luarocks.org/"&gt;luarocks&lt;/a&gt;.&lt;ul&gt;
&lt;li&gt;Для поиска файлов в директории, будем использовать модуль &lt;a href="http://luaposix.github.io/luaposix/"&gt;luaposix&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Ну и для конвертации словаря в &lt;code&gt;JSON&lt;/code&gt; установим &lt;a href="http://json.luaforge.net/"&gt;JSON4Lua&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo apt-get -y install make nginx-extras lua5.1 luarocks
$ &lt;span class="c1"&gt;# install lua modules&lt;/span&gt;
$ sudo luarocks install luaposix
$ sudo luarocks install JSON4Lua
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="zdravstvui-podlunnyi-mir"&gt;&lt;a class="toclink" href="#zdravstvui-podlunnyi-mir"&gt;Здравствуй, подлунный&amp;nbsp;мир!&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Здравствуй, мир!&amp;#8221; на &lt;code&gt;Lua&lt;/code&gt;, так же прост и прекрасен, как и на &lt;code&gt;Python&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Lua&lt;/span&gt; &lt;span class="mf"&gt;5.1.5&lt;/span&gt;  &lt;span class="n"&gt;Copyright&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1994&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2012&lt;/span&gt; &lt;span class="n"&gt;Lua&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PUC&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Rio&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello world!&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;Hello&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь попробуем тоже самое при помощи полнофункционального
&lt;a href="https://github.com/openresty/lua-nginx-module#nginx-api-for-lua"&gt;Nginx Lua &lt;span class="caps"&gt;API&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt;    &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;/hello-world&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;content_by_lua&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&lt;/span&gt;
            &lt;span class="s"&gt;ngx.header.content_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;
            &lt;span class="s"&gt;ngx.say(&amp;quot;Hello&lt;/span&gt; &lt;span class="s"&gt;world!&amp;quot;)&lt;/span&gt;
        &lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ curl http://10.1.1.111/hello-world
Hello world!
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Для исполнения &lt;code&gt;Lua&lt;/code&gt; скрипта, служит директива &lt;code&gt;content_by_lua&lt;/code&gt;,
для которого &lt;code&gt;Nginx&lt;/code&gt; ожидает получить ответ через &lt;code&gt;API&lt;/code&gt;. Если скрипт большой,
то не обязательно описывать его внутри конфига, достаточно лишь подключить
через директиву &lt;code&gt;content_by_lua_file&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="vagrant-metadannye"&gt;&lt;a class="toclink" href="#vagrant-metadannye"&gt;Vagrant&amp;nbsp;метаданные&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id="nastroika-nginx"&gt;&lt;a class="toclink" href="#nastroika-nginx"&gt;Настройка&amp;nbsp;Nginx&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;На сервере мы складываем боксы в директорию &lt;code&gt;hosted&lt;/code&gt;, создавая поддиректорию
для каждого проекта. Сами боксы в которой хранятся файлами со строго указанным
форматом имени &lt;code&gt;{provider}-{version.subversion}.box&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Формируя примерно такое&amp;nbsp;дерево:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ tree hosted/
hosted/
├── foo
│   ├── docker-1.0.box
│   ├── docker-1.3.box
│   ├── virtualbox-1.0.box
│   ├── virtualbox-1.4.box
│   └── virtualbox-1.7.box
└── bar
    ├── virtualbox-1.0.box
    ├── virtualbox-1.1.box
    └── virtualbox-1.2.box

&lt;span class="m"&gt;2&lt;/span&gt; directories, &lt;span class="m"&gt;8&lt;/span&gt; files
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Настроим &lt;code&gt;nginx&lt;/code&gt; так, чтобы для любого бокса &amp;#8212; начиналось непосредственное скачивание,
а для имени проекта, возвращались вычисленные&amp;nbsp;метаданные.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt;    &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$box_url&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;http://10.1.1.111/%s/%s-%s.box&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$box_prefix&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;/home/vagrant/proj/hosted/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;/*\.box$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;/home/vagrant/proj/hosted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;# just return box&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;/(?&amp;lt;box_name&amp;gt;\w+)/?$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;content_by_lua_file&lt;/span&gt; &lt;span class="s"&gt;/home/vagrant/proj/app/handler.lua&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Переменные &lt;code&gt;$box_url&lt;/code&gt; и &lt;code&gt;$box_prefix&lt;/code&gt; будут использовать при формировании&amp;nbsp;метаданных.&lt;/p&gt;
&lt;h4 id="vychislenie-metadannykh-s-pomoshchiu-lua"&gt;&lt;a class="toclink" href="#vychislenie-metadannykh-s-pomoshchiu-lua"&gt;Вычисление метаданных с помощью&amp;nbsp;Lua&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Приступим теперь непосредственно к формированию метаданных
для версирования &lt;code&gt;Vagrant&lt;/code&gt; боксов.&lt;/p&gt;
&lt;p&gt;Идея будет заключаться в том, что по запросу, &lt;code&gt;Lua&lt;/code&gt; будет осуществлять поиск
сохраненных боксов в заданной директории на сервере, вычислять их хешсуммы,
и формировать ответ в формате метаданных &lt;code&gt;vagrant&lt;/code&gt;а.&lt;/p&gt;
&lt;p&gt;Используя &lt;a href="https://luaposix.github.io/luaposix/modules/posix.glob.html"&gt;glob&lt;/a&gt;
из библиотеки &lt;code&gt;posix&lt;/code&gt; найдем все&amp;nbsp;боксы.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;box_root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_prefix&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_name&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;posix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;posix&amp;quot;&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;box_root&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;*.box&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- Если боксы не найдены, можно сразу возвращать 404&lt;/span&gt;
&lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_NOT_FOUND&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_NOT_FOUND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Итеративно пройдем по найденным боксам, и сформируем словарь с найденными&amp;nbsp;версиями.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="c1"&gt;-- Discover the boxes&lt;/span&gt;
&lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;ipairs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;-- Обрабатываем найденый бокс, определяя версию и формируя описание&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_provider&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
            &lt;span class="c1"&gt;-- Если версия встречается впервые, создаем запись для новой версии&lt;/span&gt;
            &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kr"&gt;else&lt;/span&gt;
            &lt;span class="c1"&gt;-- Если версия уже была описана, обновляем список провайдеров&lt;/span&gt;
            &lt;span class="nb"&gt;table.insert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;providers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Для вычисления хешсуммы больших файлов боксов воспользуемся утилитами операционной системы.
Такими как &lt;code&gt;sha1sum&lt;/code&gt;, &lt;code&gt;sha256sum&lt;/code&gt;, &lt;code&gt;md5sum&lt;/code&gt;&amp;#8230;
Делается это с помощью вызова процесса через &lt;code&gt;io.popen&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;sha1&amp;#39;&lt;/span&gt;

&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;get_hash&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;-- Вычисляем хешсумму используя вызов консольной утилиты sha1sum&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;string.format&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%ssum %s | cut -d &amp;quot; &amp;quot; -f1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;hashsum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;io.popen&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;string.gsub&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hashsum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;*a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hashsum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Функция &lt;code&gt;make_provider&lt;/code&gt; выполняется для каждого найденного бокса.
Подразумевается, что боксы хранятся на сервере со строго заданным форматом имени:
&lt;code&gt;{provider}-{version.subversion}.box&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Разбираем версию и имя провайдера, регуляркой, после чего формируем словарь,
описывающий данный&amp;nbsp;бокс.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;make_provider&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;-- Make vagrant provider from given file&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;box_provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;box_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;string.match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;string.format&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%s(%%a+)-(.+).box&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;box_root&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;-- Название провайдера virtualbox или docker&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box_provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;-- Прямая ссылка на бокс, которую будет запрашивать vagrant&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;string.format&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;box_provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;box_version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;-- Алгоритм хешсуммы sha1, sha256, md5&lt;/span&gt;
        &lt;span class="n"&gt;checksum_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;-- Строка со значением хешсуммы&lt;/span&gt;
        &lt;span class="n"&gt;checksum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;box_version&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Обработав все боксы и сформировав список версий, обернем всё в дополнительный&amp;nbsp;словарь.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Make result response&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;vagrant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;string.format&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Boxes for %s proj&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;box_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;pairs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;table.insert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vagrant&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;versions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ответ сервера &lt;code&gt;JSON&lt;/code&gt; с найденными&amp;nbsp;версиями.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;application/json; charset=utf-8&amp;quot;&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;json&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vagrant&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="lunnoe-zatmenie-vmesto-zakliucheniia"&gt;&lt;a class="toclink" href="#lunnoe-zatmenie-vmesto-zakliucheniia"&gt;Лунное затмение (вместо&amp;nbsp;заключения)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Полученный скрипт формирует &lt;code&gt;JSON&lt;/code&gt; ответ содержащий метаинформацию о&amp;nbsp;боксах.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ curl http://10.1.1.111/example &lt;span class="p"&gt;|&lt;/span&gt; jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
&lt;span class="m"&gt;100&lt;/span&gt;   &lt;span class="m"&gt;943&lt;/span&gt;    &lt;span class="m"&gt;0&lt;/span&gt;   &lt;span class="m"&gt;943&lt;/span&gt;    &lt;span class="m"&gt;0&lt;/span&gt;     &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="m"&gt;13412&lt;/span&gt;      &lt;span class="m"&gt;0&lt;/span&gt; --:--:-- --:--:-- --:--:-- &lt;span class="m"&gt;13471&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;versions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1.7&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;providers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://10.1.1.111/example/virtualbox-1.7.box&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3221c0fd58a4b2430efc5eeaf09cb8eaf877f3a9&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;virtualbox&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1.3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;providers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://10.1.1.111/example/docker-1.3.box&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;def7148aa7ded879dbf5944af4785c2b09aba97a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;docker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1.4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;providers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://10.1.1.111/example/virtualbox-1.4.box&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;63b06d8c065f5c2522c356d4d6ceb718ec3f8198&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;virtualbox&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;quot;providers&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://10.1.1.111/example/docker-1.0.box&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;65cb550765d251604dcfeedc36ea61f66ce205c4&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;docker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;http://10.1.1.111/example/virtualbox-1.0.box&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;c0a9d5c3d6679cfcc4b1374e3ad42465f3dd596e&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;virtualbox&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;quot;checksum_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sha1&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;example&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Boxes for example proj&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Полный пример скрипта можно посмотреть в репозитории на &lt;code&gt;github&lt;/code&gt;
&lt;a href="https://github.com/Samael500/ngx-vagrant"&gt;Samael500/ngx-vagrant&lt;/a&gt;. А при желании
поиграться запустив настроенный &lt;code&gt;Vagrant&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;О том, как ещё можно интересно использовать связку &lt;code&gt;Nginx&lt;/code&gt; и &lt;code&gt;Lua&lt;/code&gt;, можно
прочитать в статье &lt;a href="http://dizballanze.com/drugoe/primenenie-nginx-lua-dlia-obrabotki-prostykh-form/"&gt;Применение Nginx + Lua для обработки контактной&amp;nbsp;формы&lt;/a&gt;&lt;/p&gt;</summary><category term="vagrant"></category><category term="lua"></category><category term="nginx"></category><category term="self-hosted"></category><category term="wb-tech"></category><category term="best"></category></entry><entry><title>Быстрый старт на Джанго</title><link href="https://maks.live/articles/python/bystryi-start-na-dzhango/" rel="alternate"></link><published>2016-10-10T15:00:00+03:00</published><updated>2016-10-10T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2016-10-10:articles/python/bystryi-start-na-dzhango/</id><summary type="html">&lt;p&gt;Новый проект это всегда интересно, новые задачи, новый опыт, новые знания.
Начиная новый проект хочется сразу броситься &amp;#8220;в бой&amp;#8221;. Но перед этим приходится
тратить время на первоначальную настройку окружения, подключения и установку
зависимостей, создания структуры, инициализацию проекта. Порой это может
занимать целый день. Рутинная и во многом однообразная задача написания &lt;code&gt;fab&lt;/code&gt;
скриптов для запуска виртуального окружения &amp;#8212; охлаждает пыл и отвлекает.
Для того чтобы пропускать этот шаг, и сразу приступать к разработке.
Был написан простенький &lt;code&gt;fab&lt;/code&gt; скрипт для фальстарта нового проекта.
Так что теперь, достаточно написать &lt;code&gt;falstart &amp;lt;название проекта&amp;gt;&lt;/code&gt;,
выпить чашечку чая и приступать к работе над новым&amp;nbsp;проектом.&lt;/p&gt;
&lt;p&gt;Мы, в &lt;a href="http://wbtech.pro/"&gt;&lt;span class="caps"&gt;WB&lt;/span&gt;&amp;#8212;Tech&lt;/a&gt; в основном используем следующий стек:
&lt;a href="https://www.python.org/"&gt;Python 3.x&lt;/a&gt; + &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; +
&lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; + &lt;a href="http://www.celeryproject.org/"&gt;Celery&lt;/a&gt; +
&lt;a href="http://redis.io/"&gt;Redis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;А саму разработку ведем в виртуальном окружении &lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; +
&lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt; под управлением
&lt;a href="https://www.debian.org/index.html"&gt;&lt;span class="caps"&gt;OS&lt;/span&gt; Debian&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="falstart" class="center" src="/media/falstart/fs-logo.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Samael500/falstart"&gt;Falstart&lt;/a&gt; позволяет быстро развернуть
виртуальное окружение и приступить к работе ответив на десяток простых&amp;nbsp;вопросов.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ falstart awesome
&amp;gt; Django version &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1.9.5&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;.10.2
&amp;gt; Debian version &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; vagrant box&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jessie64&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt; Python version &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.5.1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;.5.2
&amp;gt; Vagrant box IP-addr &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;10.1.1.123&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.1.1.111
&amp;gt; Do you nead a POSTGRES? &lt;span class="o"&gt;[&lt;/span&gt;Y/n&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt; Do you nead a CELERY? &lt;span class="o"&gt;[&lt;/span&gt;y/N&lt;span class="o"&gt;]&lt;/span&gt; y
&amp;gt; Do you nead a REDIS? &lt;span class="o"&gt;[&lt;/span&gt;y/N&lt;span class="o"&gt;]&lt;/span&gt; y
&amp;gt; Do you nead a SENTRY? &lt;span class="o"&gt;[&lt;/span&gt;y/N&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt; Database name &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;awesome_db&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt; Database user &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;awesome_user&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&amp;gt; Database pass &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;vFeH1uJVN&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;После чего выполняются скрипты и через 10-15 минут будет готова структура&amp;nbsp;проекта.&lt;/p&gt;
&lt;h3 id="pred-zavisimosti"&gt;&lt;a class="toclink" href="#pred-zavisimosti"&gt;Пред&amp;nbsp;зависимости&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Для работы фальстарта необходим &lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; и &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="ustanovka"&gt;&lt;a class="toclink" href="#ustanovka"&gt;Установка&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Фальстарт доступен для установки через &lt;code&gt;pip&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ pip install falstart
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="rezultat"&gt;&lt;a class="toclink" href="#rezultat"&gt;Результат&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Пример результата: &lt;a href="https://github.com/Samael500/falstart-example"&gt;falstart-example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;details&gt;
    &lt;summary&gt;Пример итоговой структуры проекта&lt;/summary&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ tree
.
├── awesome
│   ├── celery.py
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── celery.cpython-35.pyc
│   │   ├── __init__.cpython-35.pyc
│   │   ├── settings.cpython-35.pyc
│   │   ├── settings_local.cpython-35.pyc
│   │   ├── urls.cpython-35.pyc
│   │   └── wsgi.cpython-35.pyc
│   ├── settings_local.py
│   ├── settings_local.py.example
│   ├── settings.py
│   ├── static
│   │   └── admin
│   │       ├── css
│   │       │   ├── base.css
│   │       │   ├── changelists.css
│   │       │   ├── dashboard.css
│   │       │   ├── fonts.css
│   │       │   ├── forms.css
│   │       │   ├── login.css
│   │       │   ├── rtl.css
│   │       │   └── widgets.css
│   │       ├── fonts
│   │       │   ├── LICENSE.txt
│   │       │   ├── README.txt
│   │       │   ├── Roboto-Bold-webfont.woff
│   │       │   ├── Roboto-Light-webfont.woff
│   │       │   └── Roboto-Regular-webfont.woff
│   │       ├── img
│   │       │   ├── calendar-icons.svg
│   │       │   ├── gis
│   │       │   │   ├── move_vertex_off.svg
│   │       │   │   └── move_vertex_on.svg
│   │       │   ├── icon-addlink.svg
│   │       │   ├── icon-alert.svg
│   │       │   ├── icon-calendar.svg
│   │       │   ├── icon-changelink.svg
│   │       │   ├── icon-clock.svg
│   │       │   ├── icon-deletelink.svg
│   │       │   ├── icon-no.svg
│   │       │   ├── icon-unknown-alt.svg
│   │       │   ├── icon-unknown.svg
│   │       │   ├── icon-yes.svg
│   │       │   ├── inline-delete.svg
│   │       │   ├── LICENSE
│   │       │   ├── README.txt
│   │       │   ├── search.svg
│   │       │   ├── selector-icons.svg
│   │       │   ├── sorting-icons.svg
│   │       │   ├── tooltag-add.svg
│   │       │   └── tooltag-arrowright.svg
│   │       └── js
│   │           ├── actions.js
│   │           ├── actions.min.js
│   │           ├── admin
│   │           │   ├── DateTimeShortcuts.js
│   │           │   └── RelatedObjectLookups.js
│   │           ├── calendar.js
│   │           ├── cancel.js
│   │           ├── change_form.js
│   │           ├── collapse.js
│   │           ├── collapse.min.js
│   │           ├── core.js
│   │           ├── inlines.js
│   │           ├── inlines.min.js
│   │           ├── jquery.init.js
│   │           ├── popup_response.js
│   │           ├── prepopulate_init.js
│   │           ├── prepopulate.js
│   │           ├── prepopulate.min.js
│   │           ├── SelectBox.js
│   │           ├── SelectFilter2.js
│   │           ├── timeparse.js
│   │           ├── urlify.js
│   │           └── vendor
│   │               ├── jquery
│   │               │   ├── jquery.js
│   │               │   ├── jquery.min.js
│   │               │   └── LICENSE-JQUERY.txt
│   │               └── xregexp
│   │                   ├── LICENSE-XREGEXP.txt
│   │                   ├── xregexp.js
│   │                   └── xregexp.min.js
│   ├── urls.py
│   └── wsgi.py
├── Makefile
├── manage.py
├── provision
│   ├── fabric_provisioner.py
│   ├── fabric_provisioner.pyc
│   └── templates
│       ├── environment.j2
│       ├── locale.gen.j2
│       └── nginx-host.j2
├── requirements-remote.txt
├── requirements.txt
├── Vagrantfile
├── var
│   ├── celery_awesome_worker.log
│   ├── celery_awesome_worker.pid
│   ├── celerybeat-schedule
│   └── gunicorn.pid
└── wheels
    ├── amqp-1.4.9-py2.py3-none-any.whl
    ├── anyjson-0.3.3-py3-none-any.whl
    ├── billiard-3.3.0.23-py3-none-any.whl
    ├── celery-3.1.23-py2.py3-none-any.whl
    ├── coverage-4.2-cp35-cp35m-linux_x86_64.whl
    ├── coverage_badge-0.1.2-py3-none-any.whl
    ├── Django-1.10.2-py2.py3-none-any.whl
    ├── django_rainbowtests-0.5.1-py3-none-any.whl
    ├── gunicorn-19.4.5-py2.py3-none-any.whl
    ├── kombu-3.0.37-py2.py3-none-any.whl
    ├── mccabe-0.4.0-py2.py3-none-any.whl
    ├── pep257-0.7.0-py2.py3-none-any.whl
    ├── pep8-1.7.0-py2.py3-none-any.whl
    ├── psycopg2-2.6.1-cp35-cp35m-linux_x86_64.whl
    ├── pyflakes-1.0.0-py2.py3-none-any.whl
    ├── pylama-7.0.7-py2.py3-none-any.whl
    ├── pytz-2016.7-py2.py3-none-any.whl
    └── redis-2.10.5-py2.py3-none-any.whl
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;Теперь можно перейти по адресу указанному в приветственном сообщении &lt;code&gt;Vagrant&lt;/code&gt;а
и увидеть начальную страницу&amp;nbsp;Джанго.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant: Machine &lt;span class="s1"&gt;&amp;#39;awesome_vagrant&amp;#39;&lt;/span&gt; has a post &lt;span class="sb"&gt;`&lt;/span&gt;vagrant up&lt;span class="sb"&gt;`&lt;/span&gt; message. This is a &lt;span class="nv"&gt;message&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant: from the creator of the Vagrantfile, and not from Vagrant itself:
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant: awesome dev server successfuly started.
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:     Connect to host with:
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:     http://10.1.1.111/
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:     or over ssh with &lt;span class="sb"&gt;`&lt;/span&gt;vagrant ssh&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:     Admin user credentials:
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:       login: &lt;span class="nv"&gt;root&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:       password: &lt;span class="nv"&gt;123123&lt;/span&gt;
&lt;span class="o"&gt;==&lt;/span&gt;&amp;gt; awesome_vagrant:
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img alt="init app" class="center" src="/media/falstart/init_app.png" /&gt;&lt;/p&gt;</summary><category term="python"></category><category term="github"></category><category term="django"></category><category term="vagrant"></category><category term="wb-tech"></category><category term="falstart"></category><category term="best"></category></entry><entry><title>Отчеты coverage в TeamCity</title><link href="https://maks.live/articles/drugoe/otchety-coverage-v-teamcity/" rel="alternate"></link><published>2016-09-26T15:00:00+03:00</published><updated>2016-09-26T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2016-09-26:articles/drugoe/otchety-coverage-v-teamcity/</id><summary type="html">&lt;p&gt;Мы, в &lt;a href="http://wbtech.pro/"&gt;&lt;span class="caps"&gt;WB&lt;/span&gt;&amp;#8212;Tech&lt;/a&gt; в качестве системы непрерывной
интеграции используем &lt;a href="https://www.jetbrains.com/teamcity/"&gt;TeamCity&lt;/a&gt;.
А саму разработку ведем в приватных репозиториях
на &lt;a href="https://github.com/"&gt;github&lt;/a&gt;. С задачей запуска тестов и публикации
статуса выполнения в ветку на &lt;code&gt;github&lt;/code&gt; &lt;code&gt;TeamCity&lt;/code&gt; справляется отлично. Но
выводить отчет по покрытию кода
&lt;a href="https://confluence.jetbrains.com/display/TCD9/Code+Coverage"&gt;из коробки&lt;/a&gt;
умеет только для &lt;code&gt;Java&lt;/code&gt; и &lt;code&gt;.NET&lt;/code&gt;, а это не наш профиль.
Хотелось получить собственную систему, похожую на
&lt;a href="https://coveralls.io/github/Samael500"&gt;Coveralls&lt;/a&gt; работающую с &lt;code&gt;python&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В результате в пулреквест выводится показатель покрытия кода тестами,
учитывая необходимый минимум &lt;code&gt;85%&lt;/code&gt;, а также предоставляет ссылку на&amp;nbsp;отчет.&lt;/p&gt;
&lt;p&gt;&lt;img alt="pending" class="center" src="/media/teamcity-coverage/github_pending.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="failure" class="center" src="/media/teamcity-coverage/github_failure.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="success" class="center" src="/media/teamcity-coverage/github_success.png" /&gt;&lt;/p&gt;
&lt;h2 id="formirovanie-otcheta"&gt;&lt;a class="toclink" href="#formirovanie-otcheta"&gt;Формирование&amp;nbsp;отчета&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Первым делом, необходимо сформировать данные о покрытии кода. Делаем это
с помощью утилиты &lt;a href="https://coverage.readthedocs.io/"&gt;Coverage.py&lt;/a&gt;.
Т.к. для удобства работы с проектом, мы используем &lt;code&gt;Makefile&lt;/code&gt;,
то пропишем в нем дополнительные команды, для запуска тестов с покрытием кода,
и формирования&amp;nbsp;отчета.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;VENV_PATH&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;HOME&lt;span class="k"&gt;)&lt;/span&gt;/venv/bin
&lt;span class="nv"&gt;PROJ_NAME&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; my_awesome_project

&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;span class="nf"&gt;ci_test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover_test&lt;/span&gt; &lt;span class="n"&gt;cover_report&lt;/span&gt;

&lt;span class="nf"&gt;cover_test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;$(&lt;/span&gt;VENV_PATH&lt;span class="k"&gt;)&lt;/span&gt;/coverage run --source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;PROJ_NAME&lt;span class="k"&gt;)&lt;/span&gt; manage.py &lt;span class="nb"&gt;test&lt;/span&gt; -v &lt;span class="m"&gt;2&lt;/span&gt; --noinput

&lt;span class="nf"&gt;cover_report&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;$(&lt;/span&gt;VENV_PATH&lt;span class="k"&gt;)&lt;/span&gt;/coverage report -m
    &lt;span class="k"&gt;$(&lt;/span&gt;VENV_PATH&lt;span class="k"&gt;)&lt;/span&gt;/coverage html
    &lt;span class="k"&gt;$(&lt;/span&gt;VENV_PATH&lt;span class="k"&gt;)&lt;/span&gt;/coverage-badge &amp;gt; htmlcov/coverage.svg
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Команда &lt;code&gt;cover_test&lt;/code&gt; запускает джанговские тесты, и замеряет покрытие кода.
Команда &lt;code&gt;cover_report&lt;/code&gt; выводит в консоль отчет о покрытии, а также формирует
&lt;code&gt;html&lt;/code&gt; отчет и, при помощи утилиты
&lt;a href="https://github.com/dbrgn/coverage-badge"&gt;coverage-badge&lt;/a&gt; формирует красивый
бейджик со статусом покрытия кода &lt;img alt="badge" src="/media/teamcity-coverage/badge.svg" /&gt;.&lt;/p&gt;
&lt;p&gt;После того, как исходные данные для отчета подготовлены, мы можем отображать
результат. Для этого нужно сконфигурировать сбор артефактов в &lt;code&gt;teamcity&lt;/code&gt;.
Делается это на вкладке &lt;code&gt;General Settings&lt;/code&gt; в настройках проекта. Мы копируем в
артефакты папку &lt;code&gt;htmlcov&lt;/code&gt; содержащую отчет и&amp;nbsp;бейджик.&lt;/p&gt;
&lt;p&gt;&lt;img alt="General Settings" class="center shadow" src="/media/teamcity-coverage/artifacts.png" /&gt;&lt;/p&gt;
&lt;p&gt;После следующего запуска тестов, перейдя во вкладку &lt;code&gt;Artifacts&lt;/code&gt;, можно увидеть
дерево артефактов данного&amp;nbsp;билда.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Artifacts tree" class="center shadow" src="/media/teamcity-coverage/artifacts_tree.png" /&gt;&lt;/p&gt;
&lt;p&gt;Сами артефакты также доступны авторизованным пользователям &lt;code&gt;TeamCity&lt;/code&gt;
напрямую по ссылкам&amp;nbsp;вида:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/repository/download/%teamcity.project.id%/%teamcity.build.id%:id/htmlcov/index.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/repository/download/%teamcity.project.id%/.lastFinished/htmlcov/index.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Более подробно о доступе к артефактам в &lt;a href="https://confluence.jetbrains.com/display/TCD9/Patterns+For+Accessing+Build+Artifacts"&gt;документации&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="uvedomleniia-statusa-na-github"&gt;&lt;a class="toclink" href="#uvedomleniia-statusa-na-github"&gt;Уведомления статуса на&amp;nbsp;Github&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Имея готовый отчет будем отправлять вебхуки на &lt;code&gt;github&lt;/code&gt; с указанием статуса
покрытия кода. Для этого добавим простые &lt;code&gt;build steps&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Build steps" class="center shadow" src="/media/teamcity-coverage/build_steps.png" /&gt;&lt;/p&gt;
&lt;h4 id="coverage-pending-hook"&gt;&lt;a class="toclink" href="#coverage-pending-hook"&gt;Coverage pending&amp;nbsp;hook&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Заставляем гитхаб ждать отчета по покрытию&amp;nbsp;кода.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Runner type&lt;/strong&gt; &lt;em&gt;Command&amp;nbsp;Line&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute step&lt;/strong&gt; &lt;em&gt;Even if some of the previous steps&amp;nbsp;failed&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Custom&amp;nbsp;script&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;OWNER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;GITHUB OWNER&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;REPO NAME&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%build.vcs.number%&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

curl &lt;span class="s2"&gt;&amp;quot;https://api.github.com/repos/&lt;/span&gt;&lt;span class="nv"&gt;$OWNER&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;/statuses/&lt;/span&gt;&lt;span class="nv"&gt;$SHA&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -X POST &lt;span class="se"&gt;\&lt;/span&gt;
    -H &lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -H &lt;span class="s2"&gt;&amp;quot;Authorization: token &amp;lt;GITHUB API TOKEN&amp;gt;&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -d &lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;state&amp;quot;: &amp;quot;pending&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;description&amp;quot;: &amp;quot;Coverage pending.&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;context&amp;quot;: &amp;quot;continuous-integration/coverage&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;    }&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="badge-copy"&gt;&lt;a class="toclink" href="#badge-copy"&gt;Badge&amp;nbsp;copy&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Копируем сформированный бейджик, в папку доступную напрямую через вебсервер.
Для того, чтобы &lt;code&gt;github&lt;/code&gt; имел доступ к бейджику без аутентификации в &lt;code&gt;teamcity&lt;/code&gt;.
Если у вас разрешен гостевой доступ - то этот шаг выполнять не&amp;nbsp;обязательно.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Runner type&lt;/strong&gt; &lt;em&gt;Command&amp;nbsp;Line&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute step&lt;/strong&gt; &lt;em&gt;Even if some of the previous steps&amp;nbsp;failed&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Custom&amp;nbsp;script&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;BADGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/path/to/public/dir/badges/%teamcity.project.id%/%teamcity.build.branch%-coverage.svg&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dirname &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BADGE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
mkdir -p &lt;span class="nv"&gt;$DIR&lt;/span&gt;
cp -f htmlcov/coverage.svg &lt;span class="nv"&gt;$BADGE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="coverage-finish-hook"&gt;&lt;a class="toclink" href="#coverage-finish-hook"&gt;Coverage finish&amp;nbsp;hook&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;По окончанию тестов, отправляем отчет на&amp;nbsp;гитхаб.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Runner type&lt;/strong&gt; &lt;em&gt;Command&amp;nbsp;Line&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute step&lt;/strong&gt; &lt;em&gt;Even if some of the previous steps&amp;nbsp;failed&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Custom&amp;nbsp;script&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;OWNER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;GITHUB OWNER&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;REPO NAME&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;SHA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%build.vcs.number%&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;REPORT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://&amp;lt;YOU TEAMCITY DOMAIN&amp;gt;/repository/download/%teamcity.project.id%/%teamcity.build.id%:id/htmlcov/index.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;COVERAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat ./htmlcov/index.html &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;&amp;lt;span class=&amp;quot;pc_cov&amp;quot;&amp;gt;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -o &lt;span class="s1"&gt;&amp;#39;[0-9]\+&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$COVERAGE&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; -ge &lt;span class="s2"&gt;&amp;quot;85&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;failure&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

curl &lt;span class="s2"&gt;&amp;quot;https://api.github.com/repos/&lt;/span&gt;&lt;span class="nv"&gt;$OWNER&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;/statuses/&lt;/span&gt;&lt;span class="nv"&gt;$SHA&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -X POST &lt;span class="se"&gt;\&lt;/span&gt;
    -H &lt;span class="s2"&gt;&amp;quot;Content-Type: application/json&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -H &lt;span class="s2"&gt;&amp;quot;Authorization: token &amp;lt;GITHUB API TOKEN&amp;gt;&amp;quot;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -d &lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;state&amp;quot;: &amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;target_url&amp;quot;: &amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$REPORT_URL&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;description&amp;quot;: &amp;quot;Coverage &amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$COVERAGE&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;quot;context&amp;quot;: &amp;quot;continuous-integration/coverage&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;    }&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В данном случае, если покрытие менее 85%, то данная проверка будет считаться
ошибкой, и в гитхабе отметиться красным&amp;nbsp;крестиком.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fail" class="center shadow" src="/media/teamcity-coverage/coverage_fail.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="success" class="center shadow" src="/media/teamcity-coverage/coverage_success.png" /&gt;&lt;/p&gt;
&lt;h4 id="beidzhik-v-readme"&gt;&lt;a class="toclink" href="#beidzhik-v-readme"&gt;Бейджик в&amp;nbsp;readme&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Для отображения бейджика в &lt;code&gt;README.md&lt;/code&gt; добавим ссылку, на последний успешний&amp;nbsp;билд.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;!&lt;span class="o"&gt;[&lt;/span&gt;coverage report&lt;span class="o"&gt;](&lt;/span&gt;http://&amp;lt;TEAMCITY DOMAIN&amp;gt;/badges/&amp;lt;TEAMCITY PROJ ID&amp;gt;/master-coverage.svg&lt;span class="o"&gt;)](&lt;/span&gt;http://&amp;lt;TEAMCITY DOMAIN&amp;gt;/repository/download/&amp;lt;TEAMCITY PROJ ID&amp;gt;/.lastFinished/htmlcov/index.html&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Где &lt;code&gt;/badges/&amp;lt;TEAMCITY PROJ ID&amp;gt;/&lt;/code&gt; доступна для анонимных посетителей, чтобы &lt;code&gt;github&lt;/code&gt; мог закешировать&amp;nbsp;изображение.&lt;/p&gt;
&lt;p&gt;Теперь в &lt;code&gt;README&lt;/code&gt; виден кликабельный бейджик с покрытием&amp;nbsp;кода.&lt;/p&gt;
&lt;p&gt;&lt;img alt="readme" class="center shadow" src="/media/teamcity-coverage/readme.md.png" /&gt;&lt;/p&gt;
&lt;p&gt;Кликнув на который, мы попадаем в отчет по покрытию&amp;nbsp;кода.&lt;/p&gt;
&lt;p&gt;&lt;img alt="report" class="center shadow" src="/media/teamcity-coverage/report.png" /&gt;&lt;/p&gt;</summary><category term="ci"></category><category term="coverage"></category><category term="python"></category><category term="github"></category><category term="teamcity"></category><category term="wb-tech"></category><category term="best"></category></entry><entry><title>Заклинатель змей?</title><link href="https://maks.live/articles/python/zaklinatel-zmei/" rel="alternate"></link><published>2016-04-17T15:00:00+03:00</published><updated>2016-04-17T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2016-04-13:articles/python/zaklinatel-zmei/</id><summary type="html">&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; имеет очень простой синтаксис, и практически всегда ведёт себя
предсказуемо. Однако порой происходит нечто&amp;nbsp;невероятное&amp;#8230;&lt;/p&gt;
&lt;h3 id="nepredskazuemye-spiski"&gt;&lt;a class="toclink" href="#nepredskazuemye-spiski"&gt;Непредсказуемые&amp;nbsp;списки&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[]]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;details&gt;
    &lt;summary&gt;Результат&lt;/summary&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[]]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;details&gt;
    &lt;summary&gt;Результат&lt;/summary&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;tuple&amp;#39;&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="n"&gt;does&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;assignment&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# list += str&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;abcd&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;

&lt;span class="c1"&gt;# list + str&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;abcd&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;details&gt;
    &lt;summary&gt;Результат&lt;/summary&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# list += str&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;abcd&amp;#39;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# list + str&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;abcd&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;concatenate&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;str&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;h3 id="oop-takoe-oop"&gt;&lt;a class="toclink" href="#oop-takoe-oop"&gt;ООП, такое&amp;nbsp;ООП&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;this is A&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;this is B&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unbound&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;called&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="n"&gt;nothing&lt;/span&gt; &lt;span class="n"&gt;instead&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;/pre&gt;&lt;/div&gt;</summary><category term="python"></category><category term="code"></category><category term="crazy"></category></entry><entry><title>Full year streak!</title><link href="https://maks.live/articles/github/full-year-streak/" rel="alternate"></link><published>2016-01-21T20:00:00+03:00</published><updated>2016-01-21T20:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2016-01-21:articles/github/full-year-streak/</id><summary type="html">&lt;p&gt;&lt;img alt="streak-year" class="center" src="/media/streak-year/streak.png" /&gt;&lt;/p&gt;
&lt;p&gt;Год непрерывного contribution, как оказывается не так и сложно.
В этом году я не только ежедневно коммитил, но и сделал некоторые вещи,&amp;nbsp;например:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Получил диплом о высшем&amp;nbsp;образовании.&lt;/li&gt;
&lt;li&gt;Окончил автошколу и сдал на&amp;nbsp;права.&lt;/li&gt;
&lt;li&gt;Купил коньки и катаюсь по замерзшему&amp;nbsp;пруду.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;А также бесчисленное количество прочих&amp;nbsp;мелочей&amp;#8230;&lt;/p&gt;</summary><category term="github"></category><category term="streak"></category></entry><entry><title>Давайте шифровать!</title><link href="https://maks.live/articles/secure/davaite-shifrovat/" rel="alternate"></link><published>2016-01-15T15:00:00+03:00</published><updated>2016-01-15T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2016-01-15:articles/secure/davaite-shifrovat/</id><summary type="html">&lt;p&gt;&lt;code&gt;Let's Encrypt&lt;/code&gt; представляет собой центр сертификации, который позволяет
просто и &lt;strong&gt;бесплатно&lt;/strong&gt; получить &lt;code&gt;TLS / SSL&lt;/code&gt; сертификаты, тем самым
позволяя использовать зашифрованное соединение &lt;code&gt;HTTPS&lt;/code&gt;.
В данный момент сервис находится на этапе бета тестирования, и получить
сертификат в полностью автоматическом режиме, можно лишь при использовании
веб-сервера &lt;code&gt;Apache&lt;/code&gt;. Я предпочитаю использовать &lt;code&gt;nginx&lt;/code&gt;, поэтому опишу
как легко получить сертификат в ручном&amp;nbsp;режиме.&lt;/p&gt;
&lt;h2 id="poluchenie-sertifikata-lets-encrypt"&gt;&lt;a class="toclink" href="#poluchenie-sertifikata-lets-encrypt"&gt;Получение сертификата Let&amp;#8217;s&amp;nbsp;Encrypt&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Let's Encrypt&lt;/code&gt; устанавливается просто клонируя&amp;nbsp;репозиторий.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
$ &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /opt/letsencrypt
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь получим сертификат используя следующую&amp;nbsp;команду:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ ./letsencrypt-auto certonly --standalone
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Для данной команды необходимы привелегии суперпользователя, так что возможно
будет потребован ввод&amp;nbsp;пароля.&lt;/p&gt;
&lt;p&gt;Поскольку для проверки домена производитсья запрос на 80 порт,
то он должен быть&amp;nbsp;свободен.&lt;/p&gt;
&lt;p&gt;&lt;img alt="nginx error" class="center" src="/media/letsencrypt/nginx.png" /&gt;&lt;/p&gt;
&lt;p&gt;Что бы освободить порт, можно временно остановить &lt;code&gt;nginx&lt;/code&gt; выполнив&amp;nbsp;команду:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo service nginx stop
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;letsencrypt&lt;/code&gt; запрашивает адрес электронной почты для
уведомлений или востановления&amp;nbsp;ключей.&lt;/p&gt;
&lt;p&gt;&lt;img alt="email" class="center" src="/media/letsencrypt/email.png" /&gt;&lt;/p&gt;
&lt;p&gt;Далее, предлагают согласиться с условиями использования&amp;nbsp;сервиса.&lt;/p&gt;
&lt;p&gt;&lt;img alt="agree" class="center" src="/media/letsencrypt/agree.png" /&gt;&lt;/p&gt;
&lt;p&gt;После, нужно указать для какого домена создается сертифика. Включая
все необходимые&amp;nbsp;поддомены.&lt;/p&gt;
&lt;p&gt;&lt;img alt="domain" class="center" src="/media/letsencrypt/domain.png" /&gt;&lt;/p&gt;
&lt;p&gt;При успешном получении сертификата, выдается следующее&amp;nbsp;сообщение:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/you.domain.com/fullchain.pem. Your cert
   will expire on 2016-15-06. To obtain a new version of the
   certificate in the future, simply run Let&amp;#39;s Encrypt again.
 - If you like Let&amp;#39;s Encrypt, please consider supporting our work by:

   Donating to ISRG / Let&amp;#39;s Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;После успешного получения сертификата у вас будут следующие&amp;nbsp;файлы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cert.pem: Сертификат&amp;nbsp;домена&lt;/li&gt;
&lt;li&gt;chain.pem: Let&amp;#8217;s Encrypt&amp;nbsp;сертификат&lt;/li&gt;
&lt;li&gt;fullchain.pem: cert.pem и chain.pem&amp;nbsp;объединенные&lt;/li&gt;
&lt;li&gt;privkey.pem: Ключ&amp;nbsp;сертификата&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Что бы проверить что сертификаты успешно созданы и доступны,
выведите содержимое директории &lt;code&gt;/etc/letsencrypt/live/your_domain_name&lt;/code&gt;,
где &lt;code&gt;your_domain_name&lt;/code&gt; имя вашего&amp;nbsp;домена.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo ls /etc/letsencrypt/live/your_domain_name
&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id="podkliuchenie-sertifikata-v-nginx"&gt;&lt;a class="toclink" href="#podkliuchenie-sertifikata-v-nginx"&gt;Подключение сертификата в&amp;nbsp;nginx&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Что бы правильно настроить &lt;code&gt;ssl&lt;/code&gt; есть такая замечательная
&lt;a href="https://mozilla.github.io/server-side-tls/ssl-config-generator/"&gt;шпаргалка&lt;/a&gt;
от &lt;code&gt;mozilla&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В общем случае достаточно использовать следующие&amp;nbsp;настройки:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# certs sent to the client in handshake are concatenated in ssl_certificate&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/example.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_timeout&lt;/span&gt; &lt;span class="s"&gt;1d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_cache&lt;/span&gt; &lt;span class="s"&gt;shared:SSL:50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_tickets&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# secure configuration&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1&lt;/span&gt; &lt;span class="s"&gt;TLSv1.1&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Для перманентного перенаправления на защизенное соединение, нужно
создать дополнительный блок &lt;code&gt;server&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;После внесения изменений в настройки сервера, нужно не забыть перезагрузить
службу &lt;code&gt;nginx&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo service nginx restart
$ &lt;span class="c1"&gt;# or&lt;/span&gt;
$ sudo nginx -s reload
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Готово. Теперь вы можете открыть ваш сайт используя защищенное&amp;nbsp;соединение.&lt;/p&gt;
&lt;h2 id="avtomaticheskoe-obnovlenie-sertifikata"&gt;&lt;a class="toclink" href="#avtomaticheskoe-obnovlenie-sertifikata"&gt;Автоматическое обновление&amp;nbsp;сертификата&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Сертификаты, которые выдает &lt;code&gt;Let's Encrypt&lt;/code&gt;, действительны всего 90 дней.
В сравнениями с другими центрами сертификации, которые выдают
сертификаты на год.
Данный период кажется подозрительно коротким. Однако, в дальнейшем сроки
сертификатов планируют ещё сократить. Это сделано с целью уменьшения
угрозы от компрометации приватного ключа, а так же призывает повсеместно
использовать автопродление сертификатов, в связи с тем, что через год,
можно и забыть его&amp;nbsp;продлить.&lt;/p&gt;
&lt;p&gt;Автопродление сертификата проходит с помощью плагина &lt;code&gt;webroot&lt;/code&gt;, который для
верификации помещает специальный файл в директорию &lt;code&gt;./well-known&lt;/code&gt;
доступную для чтения&amp;nbsp;вебсервером.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;/.well-known&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;/path/to/root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;allow&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь мы можем использовать &lt;code&gt;letsencrypt-auto&lt;/code&gt; с дополнительным параметром
&lt;code&gt;webroot-path&lt;/code&gt;, передавая домены с помощью ключа &lt;code&gt;-d&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; /opt/letsencrypt
$ ./letsencrypt-auto certonly -a webroot --agree-tos --renew-by-default &lt;span class="se"&gt;\&lt;/span&gt;
    --webroot-path&lt;span class="o"&gt;=&lt;/span&gt;/path/to/root -d example.com -d www.example.com
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В результате обновления сертификата вы получите следующее&amp;nbsp;сообщение:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem. Your cert will
   expire on 2016-05-22. To obtain a new version of the certificate in
   the future, simply run Let&amp;#39;s Encrypt again.
 - If you like Let&amp;#39;s Encrypt, please consider supporting our work by:

   Donating to ISRG / Let&amp;#39;s Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Получив обновленный сертификат, для его использования нужно перезапустить &lt;code&gt;nginx&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo service nginx restart
$ &lt;span class="c1"&gt;# or&lt;/span&gt;
$ sudo nginx -s reload
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь создадим конфигурационный файл, для автоматической подстановки параметров.
Шаблон конфигурационного файла находится в примерах &lt;code&gt;letsencrypt&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ cat /opt/letsencrypt/examples/cli.ini
&lt;span class="c1"&gt;# This is an example of the kind of things you can do in a configuration file.&lt;/span&gt;
&lt;span class="c1"&gt;# All flags used by the client can be configured here. Run Let&amp;#39;s Encrypt with&lt;/span&gt;
&lt;span class="c1"&gt;# &amp;quot;--help&amp;quot; to learn more about the available options.&lt;/span&gt;

&lt;span class="c1"&gt;# Use a 4096 bit RSA key instead of 2048&lt;/span&gt;
rsa-key-size &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment and update to register with the specified e-mail address&lt;/span&gt;
&lt;span class="c1"&gt;# email = foo@example.com&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment and update to generate certificates for the specified&lt;/span&gt;
&lt;span class="c1"&gt;# domains.&lt;/span&gt;
&lt;span class="c1"&gt;# domains = example.com, www.example.com&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment to use a text interface instead of ncurses&lt;/span&gt;
&lt;span class="c1"&gt;# text = True&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment to use the standalone authenticator on port 443&lt;/span&gt;
&lt;span class="c1"&gt;# authenticator = standalone&lt;/span&gt;
&lt;span class="c1"&gt;# standalone-supported-challenges = tls-sni-01&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment to use the webroot authenticator. Replace webroot-path with the&lt;/span&gt;
&lt;span class="c1"&gt;# path to the public_html / webroot folder being served by your web server.&lt;/span&gt;
&lt;span class="c1"&gt;# authenticator = webroot&lt;/span&gt;
&lt;span class="c1"&gt;# webroot-path = /usr/share/nginx/html&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Скопируем его в директорию &lt;code&gt;/usr/local/etc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo cp /opt/letsencrypt/examples/cli.ini /usr/local/etc/le-renew-webroot.ini
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Далее отредактируем его, изменив параметры &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;domains&lt;/code&gt; и &lt;code&gt;webroot-path&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo nano /usr/local/etc/le-renew-webroot.ini

&lt;span class="c1"&gt;# This is an example of the kind of things you can do in a configuration file.&lt;/span&gt;
&lt;span class="c1"&gt;# All flags used by the client can be configured here. Run Let&amp;#39;s Encrypt with&lt;/span&gt;
&lt;span class="c1"&gt;# &amp;quot;--help&amp;quot; to learn more about the available options.&lt;/span&gt;

&lt;span class="c1"&gt;# Use a 4096 bit RSA key instead of 2048&lt;/span&gt;
rsa-key-size &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment and update to register with the specified e-mail address&lt;/span&gt;
&lt;span class="nv"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; user@example.com

&lt;span class="c1"&gt;# Uncomment and update to generate certificates for the specified&lt;/span&gt;
&lt;span class="c1"&gt;# domains.&lt;/span&gt;
&lt;span class="nv"&gt;domains&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; example.com, www.example.com

&lt;span class="c1"&gt;# Uncomment to use a text interface instead of ncurses&lt;/span&gt;
&lt;span class="c1"&gt;# text = True&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment to use the standalone authenticator on port 443&lt;/span&gt;
&lt;span class="c1"&gt;# authenticator = standalone&lt;/span&gt;
&lt;span class="c1"&gt;# standalone-supported-challenges = tls-sni-01&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment to use the webroot authenticator. Replace webroot-path with the&lt;/span&gt;
&lt;span class="c1"&gt;# path to the public_html / webroot folder being served by your web server.&lt;/span&gt;
&lt;span class="c1"&gt;# authenticator = webroot&lt;/span&gt;
webroot-path &lt;span class="o"&gt;=&lt;/span&gt; /path/to/root
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь, вместо того что бы указывать параметры с помощью ключей комманды
&lt;code&gt;letsencrypt&lt;/code&gt;, мы можем использовать конфигурационный&amp;nbsp;файл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; /opt/letsencrypt
$ ./letsencrypt-auto certonly -a webroot --renew-by-default &lt;span class="se"&gt;\&lt;/span&gt;
    --config /usr/local/etc/le-renew-webroot.ini
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Скачаем и сделаем исполнимым скрипт автообновления&amp;nbsp;сертификата.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo curl -L -o /usr/local/sbin/le-renew-webroot &lt;span class="se"&gt;\&lt;/span&gt;
    https://gist.githubusercontent.com/thisismitch/e1b603165523df66d5cc/raw/fbffbf358e96110d5566f13677d9bd5f4f65794c/le-renew-webroot
$ sudo chmod +x /usr/local/sbin/le-renew-webroot
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь если его выполнить, то будет выдано ссобщение, о том, что сертификат
не нуждается в&amp;nbsp;обновлении.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo le-renew-webroot
Checking expiration date &lt;span class="k"&gt;for&lt;/span&gt; example.com...
The certificate is up to date, no need &lt;span class="k"&gt;for&lt;/span&gt; renewal &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;89&lt;/span&gt; days left&lt;span class="o"&gt;)&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Для того что бы скрипт регулярно проверял состояние сертификата добавим его
запуск в таблицу&amp;nbsp;крона:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo crontab -e
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Добавим строчку, которая будет запускать скрипт каждое воскресенье в 5.30
утра, и логировать результат в файл &lt;code&gt;/var/log/le-renewal.log&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; * * &lt;span class="m"&gt;7&lt;/span&gt; /usr/local/sbin/le-renew-webroot &amp;gt;&amp;gt; /var/log/le-renewal.log
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь можно не беспокоится о сроке годности сертификата, он будет регулярно
обновлятся, когда будет приближаться срок истечения&amp;nbsp;сертификата.&lt;/p&gt;</summary><category term="letsencrypt"></category><category term="nginx"></category><category term="ssl"></category><category term="https"></category><category term="secure"></category><category term="git"></category></entry><entry><title>Трехсоточка!</title><link href="https://maks.live/articles/github/trekhsotochka/" rel="alternate"></link><published>2015-11-16T18:00:00+03:00</published><updated>2015-11-16T18:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-11-16:articles/github/trekhsotochka/</id><summary type="html">&lt;p&gt;&lt;img alt="streak-300" class="center" src="/media/streak-300/streak.png" /&gt;&lt;/p&gt;
&lt;p&gt;Пролетели очередные 100 дней. Вот уже до конца года осталось всего девять недель.
Финишь не за&amp;nbsp;горами.&lt;/p&gt;</summary><category term="github"></category><category term="streak"></category></entry><entry><title>Это Типограф!</title><link href="https://maks.live/articles/python/eto-tipograf/" rel="alternate"></link><published>2015-10-15T15:00:00+03:00</published><updated>2015-10-15T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-10-13:articles/python/eto-tipograf/</id><summary type="html">&lt;p&gt;В одном из проектов была поставлена задача типографировать текст
перед публикацией. Для этой задачи было решено использовать
&amp;laquo;Типограф&amp;raquo;&amp;nbsp;Лебедева.&lt;/p&gt;
&lt;p&gt;У студии Лебедева есть сервис типографирования текста, который
так и называется &lt;a href="http://www.artlebedev.ru/tools/typograf/"&gt;типограф&lt;/a&gt;.
Как заявляют сами разработчики, никто не напишет &amp;laquo;Типограф&amp;raquo;
лучше них, в связи с чем они предлагают готовые
&lt;a href="http://www.artlebedev.ru/tools/typograf/webservice/"&gt;клиенты&lt;/a&gt;
для работы c &lt;code&gt;api&lt;/code&gt; данного&amp;nbsp;сервиса.&lt;/p&gt;
&lt;h3 id="tipograf-klient"&gt;&lt;a class="toclink" href="#tipograf-klient"&gt;Типограф&amp;nbsp;клиент&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Я не смотрел клиенты для других языков, но клиент для &lt;code&gt;Python&lt;/code&gt;, который
студия Лебедева предлагает, откровенно сказать &amp;#8212; ужасен. В нем &lt;code&gt;xml&lt;/code&gt; запрос
создается с помощью простой конкатенации строк и сомнительно экранирует
входные данные, &lt;code&gt;socket&lt;/code&gt; подключается без таймлимита, что приводит к
бесконечно длительному ожиданию ответа, в случае если сервер недоступен.
Что уж говорить о рекомендациях &lt;code&gt;pep8&lt;/code&gt;, которые там впринципе не соблюдаются.
Но самым существенным было то, что последнее изменение было сделано не вчера,
и даже не на прошлой неделе, а всего-ничего, от 24 мая 2007 года,
еще до релиза &lt;code&gt;Python 3.0&lt;/code&gt;. Соответсвенно данный клиент не поддерживает
&lt;code&gt;Python 3.x&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Проект был на &lt;code&gt;Python 3.4.2&lt;/code&gt;, в связи с чем пришлось написать собсвтенный
клиент для работы с &amp;laquo;Типографом&amp;raquo;. Поскольку адекватного описания
&lt;code&gt;api&lt;/code&gt; взаимодействия клиент-сервер в &amp;laquo;Типографе&amp;raquo; не приводится.
Так что все было сделано по аналогии со старым клиентом, вплоть до
наименований методов, лишь за исключением, что ненавистный мне
верблюжийРегистр был заменен&amp;nbsp;змеиным_регистром.&lt;/p&gt;
&lt;p&gt;Получившийся клиент для типографа доступен в &lt;code&gt;pypi&lt;/code&gt;
&lt;a href="https://pypi.python.org/pypi/typograf"&gt;&lt;img alt="typograf version" src="https://badge.fury.io/py/typograf.svg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="sovmestimost-python-2-x-i-3-x"&gt;&lt;a class="toclink" href="#sovmestimost-python-2-x-i-3-x"&gt;Совместимость python 2.x и&amp;nbsp;3.x&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Поскольку клиет типографа предельно простой, не хотелось нагружать его
дополнительными зависимостями, вроде &lt;a href="https://pypi.python.org/pypi/six"&gt;six&lt;/a&gt;
для реализации совместимости версий &lt;code&gt;python&lt;/code&gt;а, так что было использовано
&lt;code&gt;sys.version.startswith&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;PY3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Единственными различиями между &lt;code&gt;py 2.x&lt;/code&gt; и &lt;code&gt;py 3.x&lt;/code&gt;, с которыми пришлось
столкнутся при написании клиента,&amp;nbsp;это:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Во втором питоне, в сокет и из него отправляются строковые объекты,
а в третьем&amp;nbsp;байтовые.&lt;/li&gt;
&lt;li&gt;Файлы в памяти во втором питоне представлены объектами &lt;code&gt;StringIO.StringIO&lt;/code&gt;,
а в третьем &lt;code&gt;io.BytesIO&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Так что вся совместимость версий, свелась к двум-трем условиям&amp;nbsp;вида:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ... import memory file stream&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;PY3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;StringIO&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StringIO&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ... calculate a length of request&lt;/span&gt;

&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soap_body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;PY3&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soap_body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ... convert to and from bytes for socket connection in py3&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;PY3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# convert to bytes&lt;/span&gt;
    &lt;span class="n"&gt;soap_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soap_request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="c1"&gt;# take a response via socket&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;PY3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# convert to str&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="socket-timeout"&gt;&lt;a class="toclink" href="#socket-timeout"&gt;Socket&amp;nbsp;timeout&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Библиотека &lt;a href="https://docs.python.org/library/socket.html"&gt;socket&lt;/a&gt;
предусматривает возможность установки максимального времени ожидания ответа
от сервера. Делается это с помощью метода &lt;code&gt;settimeout&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# send request use soket&lt;/span&gt;
&lt;span class="n"&gt;connector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;settimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soap_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# call for response&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;
&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В случае, если время ожидания превысит установленый таймаут, соединение будет
разорвано с вызовом исключения &lt;code&gt;socket.timeout: timed out&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="django-tipograf"&gt;&lt;a class="toclink" href="#django-tipograf"&gt;Django&amp;nbsp;Типограф&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;После реализации совместимого с &lt;code&gt;python 3.x&lt;/code&gt; клиента было необходимо
прикрутить его к&amp;nbsp;джанге.&lt;/p&gt;
&lt;h4 id="bystroe-reshenie"&gt;&lt;a class="toclink" href="#bystroe-reshenie"&gt;Быстрое&amp;nbsp;решение&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Так как проект был уже запущен в продакшен, нужно было быстро подключить
типографирование. Типографировать нужно было всего две модели, поэтому была
создана простая функция &lt;code&gt;make_typograf&lt;/code&gt;, вызов которой был повешен на сигнал
сохранения&amp;nbsp;моделей.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# helpers/service.py&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_typograf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; For each instance.field in fields - make typograf &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;typograf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RemoteTypograf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;typograf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;try_process_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# articles/signals.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pre_save&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;articles.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;helpers.service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_typograf&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;typograf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;make_typograf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;subtitle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;pre_save&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typograf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# cards/signals.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pre_save&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;cards.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;helpers.service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_typograf&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;typograf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;make_typograf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;person_profession&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;soldier_rank&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;history&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;pre_save&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typograf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Данный подход имел ряд&amp;nbsp;недостатков:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;В случае если бы было неоходимо добавить новую модель,
то потребовалось бы дописывать ещё один&amp;nbsp;сигнал.&lt;/li&gt;
&lt;li&gt;Модель &amp;#8220;типографировалась&amp;#8221; при каждом сохранении, не зависимо от изменения
самого текста, что приводило к лишним запросам на сервис Лебедева при каждом&amp;nbsp;сохранении.&lt;/li&gt;
&lt;li&gt;Наиболее существенный недостаток. Модераторы начали жаловаться на то,
что текст становится невозможно проверять, из за &lt;code&gt;html&lt;/code&gt; сущностей,
которые затрудняют чтение. (см. пример&amp;nbsp;ниже)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Пример исходного&amp;nbsp;текста:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;quot;Вы все еще кое-как верстаете в &amp;quot;Ворде&amp;quot;? - Тогда мы идем к вам!&amp;quot;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Пример типографированного&amp;nbsp;текста:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;laquo;&lt;/span&gt;Вы&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;все еще кое-как верстаете в&lt;span class="ni"&gt;&amp;amp;nbsp;&amp;amp;bdquo;&lt;/span&gt;Ворде&lt;span class="ni"&gt;&amp;amp;ldquo;&lt;/span&gt;?
&lt;span class="ni"&gt;&amp;amp;mdash;&amp;amp;nbsp;&lt;/span&gt;Тогда мы&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;идем к&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;вам!&lt;span class="ni"&gt;&amp;amp;raquo;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Пример&amp;nbsp;результата:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;p&gt;&amp;laquo;Вы&amp;nbsp;все еще кое-как верстаете в&amp;nbsp;&amp;bdquo;Ворде&amp;ldquo;?
&amp;mdash;&amp;nbsp;Тогда мы&amp;nbsp;идем к&amp;nbsp;вам!&amp;raquo;&lt;br /&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="nenaviazchivyi-tipograf"&gt;&lt;a class="toclink" href="#nenaviazchivyi-tipograf"&gt;Ненавязчивый&amp;nbsp;типограф&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Для решения указанных недостатков было решено создать пакет &lt;code&gt;django-typograf&lt;/code&gt;
&lt;a href="https://pypi.python.org/pypi/django-typograf"&gt;&lt;img alt="django-typograf version" src="https://badge.fury.io/py/django-typograf.svg" /&gt;&lt;/a&gt;, который позволил бы
автоматически типографировать указанные поля в моделях, делал это только
вслучае необходимости и не влиял на отображение исходного текста в
административном&amp;nbsp;интефейсе.&lt;/p&gt;
&lt;p&gt;Для того чтобы не влиять на исходный текст, будем использовать
дополнительные поля, которые будут хранить в себе оттипографированный текст.
Для того чтобы не отправлять на типографирование текст каждый раз, без
явной на то необходимости, будем хранить хешированное значение исходного
текста и сравнивать его&amp;nbsp;изменения.&lt;/p&gt;
&lt;h5 id="kheshsumma"&gt;&lt;a class="toclink" href="#kheshsumma"&gt;Хешсумма&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Поскольку нам не важно какой вид будет иметь хешсумма, сравним
производительность различных алгоритмов хеширования с целью выбора&amp;nbsp;оптимального.&lt;/p&gt;
&lt;p&gt;Затраты времени для &lt;code&gt;md5&lt;/code&gt; суммы:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;time&lt;/span&gt; python -c &lt;span class="s1"&gt;&amp;#39;from hashlib import md5&lt;/span&gt;
&lt;span class="s1"&gt;for i in xrange(int(1e+6)): md5(str(i)).hexdigest()&amp;#39;&lt;/span&gt;

real    0m1.015s
user    0m1.006s
sys 0m0.008s
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Затраты времени для &lt;code&gt;sha1&lt;/code&gt; суммы:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;time&lt;/span&gt; python -c &lt;span class="s1"&gt;&amp;#39;from hashlib import sha1&lt;/span&gt;
&lt;span class="s1"&gt;for i in xrange(int(1e+6)): sha1(str(i)).hexdigest()&amp;#39;&lt;/span&gt;

real    0m1.095s
user    0m1.090s
sys 0m0.004s
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Затраты времени для &lt;code&gt;crc32&lt;/code&gt; суммы:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;time&lt;/span&gt; python -c &lt;span class="s1"&gt;&amp;#39;from binascii import crc32&lt;/span&gt;
&lt;span class="s1"&gt;for i in xrange(int(1e+6)): crc32(str(i))&amp;#39;&lt;/span&gt;

real    0m0.473s
user    0m0.472s
sys 0m0.000s
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Затрат времени на стравнение&amp;nbsp;строк:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;time&lt;/span&gt; python -c &lt;span class="s1"&gt;&amp;#39;for i in xrange(int(1e+6)): &amp;quot;123&amp;quot; == &amp;quot;321&amp;quot;&amp;#39;&lt;/span&gt;

real    0m0.092s
user    0m0.087s
sys 0m0.008s
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Затрат времени на стравнение целых&amp;nbsp;чисел:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ &lt;span class="nb"&gt;time&lt;/span&gt; python -c &lt;span class="s1"&gt;&amp;#39;for i in xrange(int(1e+6)): 123 == 321&amp;#39;&lt;/span&gt;

real    0m0.088s
user    0m0.080s
sys 0m0.008s
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Таким образом, для хеширования используем алгоритм &lt;code&gt;crc32&lt;/code&gt; из библиотеки
&lt;a href="https://docs.python.org/library/binascii.html"&gt;binascii&lt;/a&gt;, так как это очень
быстрая хешсумма, а также результат вычислений можно сохранить в
целочисленное поле базы данных, что выгоднее чем строка, как с точки зрения
затрат памяти, так и с точки зрения времени&amp;nbsp;сравнения.&lt;/p&gt;
&lt;p&gt;Несмотря на то, что целочисленные значения дают дополнительное преимущество в
затратах скорости и памяти, на практике всеравно будем использовать строковое
представлениет. Так как, в &lt;code&gt;python 3.x&lt;/code&gt; алгоритм &lt;code&gt;crc32&lt;/code&gt; возвращает результат
в диапазоне &lt;code&gt;unsigned int: 0 .. 4294967295&lt;/code&gt;, а в &lt;code&gt;python 2.x&lt;/code&gt; в диапазоне
&lt;code&gt;int: -2147483648 .. 2147483647&lt;/code&gt;. Целочисленное поле &lt;code&gt;PostgreSQL&lt;/code&gt; способно
разместить только диапазон &lt;code&gt;int&lt;/code&gt;, даже джанговское поле &lt;code&gt;PositiveIntegerField&lt;/code&gt;
не расширяет диапазон до &lt;code&gt;2 ** 32 - 1&lt;/code&gt;, а сокращает его ровно в половину до
&lt;code&gt;0 .. 2147483647&lt;/code&gt;.&lt;/p&gt;
&lt;h5 id="skrytye-polia"&gt;&lt;a class="toclink" href="#skrytye-polia"&gt;Скрытые&amp;nbsp;поля&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Служебные поля для типографированного текста и хешсуммы будут создаваться
автоматически с помощью метода метакласса&amp;nbsp;предка.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_typograf_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_typograf_fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Create helpers to the local typografed fields &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;local_typograf_fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# check is text field&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;TypografFieldError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;Can&lt;/span&gt;&lt;span class="se"&gt;\&amp;#39;&lt;/span&gt;&lt;span class="s1"&gt;t be typografed field &amp;quot;{field}&amp;quot;.&amp;#39;&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39; This must be a text or char field.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="c1"&gt;# create fields for store typografed text and typografed hash&lt;/span&gt;
            &lt;span class="n"&gt;typograf_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;typograf_field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creation_counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.0001&lt;/span&gt;
            &lt;span class="n"&gt;typograf_field_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;typograf_field_hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creation_counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.0001&lt;/span&gt;
            &lt;span class="c1"&gt;# create fields name&amp;#39;s&lt;/span&gt;
            &lt;span class="n"&gt;typograf_field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_typograf_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;typograf_field_hash_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_typograf_hash_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# update attrs&lt;/span&gt;
            &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="n"&gt;typograf_field_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;typograf_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;typograf_field_hash_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;typograf_field_hash&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В данном методе для каждого поля из спика &lt;code&gt;local_typograf_fields&lt;/code&gt;,
который задается в атрибутах метакласса модели наследника, создается по два
служебных поля, вида &lt;code&gt;typograf_{field}&lt;/code&gt; и &lt;code&gt;typograf_{field}_hash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Для того чтобы эти служебные поля были недоступны в административном
интерфейсе, добавим внутренний метод &lt;code&gt;_exclude&lt;/code&gt;, который будет возвращать
списки скрытых&amp;nbsp;полей.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_typograf.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_typograf_field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_typograf_hash_field_name&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TypografAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Admin class for hide typograf fields from admin site &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Mark typograf fields as exclude &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
                &lt;span class="n"&gt;get_typograf_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typografed_fields&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
                &lt;span class="n"&gt;get_typograf_hash_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;typografed_fields&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;exclude&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_exclude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="ispolzovaniia-tipografa"&gt;&lt;a class="toclink" href="#ispolzovaniia-tipografa"&gt;Использования&amp;nbsp;типографа&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Теперь, для автоматического типографирования текста, достаточно установить
пакет &lt;code&gt;django_typograf&lt;/code&gt; из &lt;code&gt;pypi&lt;/code&gt;, наследовать модель от &lt;code&gt;TypografModel&lt;/code&gt;
и указать поля, которые необходимо&amp;nbsp;типографировать.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# articles/models.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_typograf.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TypografModel&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypografModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Model for articles &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;заголовок&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;subtitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;подзаголовок&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;site_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;URL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sortable&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;typograf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;subtitle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;статья&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;статьи&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;А также, в шаблоне, не забыть работать с &amp;#8220;типографированными&amp;#8221;&amp;nbsp;полями.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.typograf_title&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default_if_none&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;article.title&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;safe&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;article.typograf_subtitle&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default_if_none&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;article.subtitle&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;safe&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="python"></category><category term="artlebedev"></category><category term="typograf"></category><category term="django"></category></entry><entry><title>Определение IP адреса посетителя в отчетах Yandex Метрика</title><link href="https://maks.live/articles/drugoe/opredelenie-ip-adresa-posetitelia-v-otchetakh-yandex-metrika/" rel="alternate"></link><published>2015-09-22T15:00:00+03:00</published><updated>2015-09-22T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-09-22:articles/drugoe/opredelenie-ip-adresa-posetitelia-v-otchetakh-yandex-metrika/</id><summary type="html">&lt;p&gt;Обновленная метрика не отображает информацию об &lt;code&gt;ip&lt;/code&gt; адресе посетителей
сайта, сделано это с целью обезличивания статистики посещений. Данная
&lt;a href="http://clubs.ya.ru/metrika/replies.xml?item_no=10888"&gt;новость&lt;/a&gt; была
официально озвучена в клубе метрики. Обезличивание это здорово, но порой
хочется узнать действительно ли в статистике отображаются различные
посетители, или же это одно и тот же лицо, которое заходит с разных
браузеров или устройств. А может быть у Вас несколько счетчиков метрики, и Вам
интересно какие из посетителей ресурса &lt;em&gt;A&lt;/em&gt;, посещают также ресурсы &lt;em&gt;B&lt;/em&gt; или &lt;em&gt;C&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Также различным рекламщикам, может быть интересна информация об &lt;code&gt;ip&lt;/code&gt; адресах,
например, с целью вычисления ботов&amp;nbsp;скликеров. &lt;/p&gt;
&lt;p&gt;К счастью, метрика достаточно гибкая и позволяет устанавливать
пользовательские параметры, тем самым создавать специальный
персонализированный отчет о действиях пользователей. Благодаря чему,
мы можем привязать информацию об &lt;code&gt;ip&lt;/code&gt; адресе к конкретному&amp;nbsp;визиту. &lt;/p&gt;
&lt;h2 id="parametry-vizitov-yandex-metrika"&gt;&lt;a class="toclink" href="#parametry-vizitov-yandex-metrika"&gt;Параметры визитов&amp;nbsp;yandex.метрика&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Сервис &lt;a href="https://yandex.ru/support/metrika/reports/visit-params.xml"&gt;параметры визитов&lt;/a&gt;
позволяет прикрепить к информации о посещении &lt;code&gt;json&lt;/code&gt; словарь с дополнительными
параметрами. Для этого необходимо в конструктор счетчика добавить аргумент
&lt;code&gt;params&lt;/code&gt; с необходимыми&amp;nbsp;значениями.&lt;/p&gt;
&lt;p&gt;Например, если информация об &lt;code&gt;ip&lt;/code&gt; адресе будет содержаться в переменой
&lt;code&gt;userip&lt;/code&gt;, то словарь параметры может выглядеть следующим&amp;nbsp;образом:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ip&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Конструктор счетчика, в таком случае, будет выглядеть&amp;nbsp;так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yaCounterXXXXXX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ya&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Metrika&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;XXXXXX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;clickmap&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;trackLinks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;accurateTrackBounce&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;webvisor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ip&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Где &lt;code&gt;XXXXXX&lt;/code&gt; идентификатор&amp;nbsp;счетчика.&lt;/p&gt;
&lt;p&gt;Чтобы просмотреть полученные результаты необходимо перейти: &lt;code&gt;метрика&lt;/code&gt; &amp;rarr;
&lt;code&gt;отчеты&lt;/code&gt; &amp;rarr; &lt;code&gt;стандартные отчеты&lt;/code&gt; &amp;rarr; &lt;code&gt;содержание&lt;/code&gt; &amp;rarr;
&lt;code&gt;параметры визитов&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="visit params path" class="center shadow" src="/media/yametrika-ip/visit_params_path.png" title="Путь к отчету по параметрам визитов." /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="visit params detail" class="center shadow" src="/media/yametrika-ip/visit_params_detail.png" title="Отчет по параметрам визитов." /&gt;&lt;/p&gt;
&lt;p&gt;Но, сами по себе &lt;code&gt;ip&lt;/code&gt; адреса не столь информативны, интерестнее просмотреть
связь посещения и &lt;code&gt;ip&lt;/code&gt; адреса в вебвизоре. Для этого в вебвизор нужно добавить
столбец&amp;nbsp;&amp;#8220;Параметры&amp;#8221;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="webvisor col params" class="center shadow" src="/media/yametrika-ip/webvisor_col_params.png" title="Добавление столбца параметры в вебвизоре." /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="webvisor ip detail" class="center shadow" src="/media/yametrika-ip/webvisor_ip_detail.png" title="Параметры визитов в вебвизоре." /&gt;&lt;/p&gt;
&lt;h2 id="opredelenie-ip-adresa"&gt;&lt;a class="toclink" href="#opredelenie-ip-adresa"&gt;Определение &lt;span class="caps"&gt;IP&lt;/span&gt;&amp;nbsp;адреса&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Выше я упоминал о переменной в которой содержится &lt;code&gt;ip&lt;/code&gt; адрес посетителя.
Задать эту переменную легко если Вы имеете доступ к&amp;nbsp;бэкэнду.&lt;/p&gt;
&lt;p&gt;Например в &lt;code&gt;django&lt;/code&gt;, предварительно подключив
&lt;code&gt;django.core.context_processors.request&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;userip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ request.META.REMOTE_ADDR }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Или, если Вы используете&amp;nbsp;проксирование:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;userip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;{{ request.META.HTTP_X_REAL_IP }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Или, например, в &lt;code&gt;php&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;userip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;? echo $_SERVER[&amp;#39;REMOTE_ADDR&amp;#39;];?&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Но что если сайт статический и какой либо бэкэнд отсутствует? В таком случае
можно воспользоваться сервисом &lt;a href="https://l2.io"&gt;l2.io&lt;/a&gt;, который позволяет
получить &lt;code&gt;ip&lt;/code&gt; на клиенте. К примеру Ваш &lt;code&gt;ip&lt;/code&gt; адрес:
&lt;strong&gt;&lt;script type="text/javascript" src="https://www.l2.io/ip.js"&gt;&lt;/script&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Чтобы задать &lt;code&gt;ip&lt;/code&gt; адрес в переменную, нужно вставить такой&amp;nbsp;скрипт:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://www.l2.io/ip.js?var=userip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Полный фрагмент скрипта яндекс метрики будет выглядеть&amp;nbsp;так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- получаем ip адрес одним из указанных вариантов --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://www.l2.io/ip.js?var=userip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Yandex.Metrika counter --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yaCounterXXXXXX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ya&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Metrika&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;XXXXXX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;clickmap&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;trackLinks&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;accurateTrackBounce&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;webvisor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ip&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// продолжение счетчика&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- /Yandex.Metrika counter --&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="ip"></category><category term="javascript"></category><category term="yandex"></category><category term="metrika"></category><category term="watch"></category></entry><entry><title>Вот это поворот…</title><link href="https://maks.live/articles/bugstory/vot-eto-povorot/" rel="alternate"></link><published>2015-09-06T15:00:00+03:00</published><updated>2015-09-06T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-09-06:articles/bugstory/vot-eto-povorot/</id><summary type="html">&lt;p&gt;Однажды столкнулся с магическим багом, причину возникновения которого,
не удавалось отыскать почти на протяжении месяца. Да что там причину,
его даже воспроизвести никак не удавалось. Заказчик постоянно жаловался
что изображения обрезаются неправильно, да при том ещё непредсказуемо&amp;nbsp;поворачиваются&amp;#8230;&lt;/p&gt;
&lt;h2 id="neulovimaia-oshibka"&gt;&lt;a class="toclink" href="#neulovimaia-oshibka"&gt;Неуловимая&amp;nbsp;ошибка&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Суть проекта заключалась в том, что пользователи выкладывают фотографии,
сопровождая их коротенькими историями, далее модераторы проверяют контент
на соответствие тематике ресурса, обрезают фотографии по необходимому
соотношению сторон и публикуют эти&amp;nbsp;истории.&lt;/p&gt;
&lt;p&gt;Буквально с первых же дней выхода проекта в продакшн, начали поступать жалобы
от модераторов. Жаловались на то, что фотографии обрезаются не так как это
ожидается, иногда появляются черные полоски, иногда фотографии внезапно
поворачиваются, иногда вообще не происходит каких либо изменений после&amp;nbsp;обрезки.&lt;/p&gt;
&lt;p&gt;Для обрезки изображений использовалась библиотека
&lt;a href="https://pypi.python.org/pypi/django-image-cropping"&gt;django-image-cropping&lt;/a&gt;,
которая позволяет в админке джанго обрезать изображения при помощи &lt;code&gt;jQuery&lt;/code&gt;
плагина &lt;a href="http://deepliquid.com/content/Jcrop.html"&gt;Jcrop&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="lozhnye-obvineniia"&gt;&lt;a class="toclink" href="#lozhnye-obvineniia"&gt;Ложные&amp;nbsp;обвинения&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Поскольку ошибку никак не удавалось воспроизвести, стали полагать, что причина
прячется где то в клиентской части. За две с половиной недели поиска ошибки,
были перепробованы чуть менее чем все комбинации различных браузеров и
операционных систем, включая устаревшие версии браузеров. Но воспроизвести
ошибку все никак не&amp;nbsp;удавалось.&lt;/p&gt;
&lt;p&gt;Тем неменее, усердное тестирование обрезки изображения в различных браузерах
выявило сопутствующую ошибку. От браузера, кстати, независящую. Продакшен
сервер был оптимизирован под большое количество посетителей. Сам проект
представлял из себя регенерируемый, статический сайт, кешированный &lt;code&gt;nginx&lt;/code&gt;ом;
небольшое &lt;code&gt;api&lt;/code&gt; для добавления и поиска историй; и административный интерфейс
для премодерации историй и перегенерации статического&amp;nbsp;контента.&lt;/p&gt;
&lt;p&gt;Так получилось, что &lt;code&gt;nginx&lt;/code&gt; слишком усердно справлялся с кешированием,
и достаточно часто после обрезки изображения в админке показывал &lt;strong&gt;старое&lt;/strong&gt;
(не обрезанное изображение), после чего модератор снова и снова &lt;em&gt;обрезал&lt;/em&gt;
картинку, а в результате исходное изображение резалось по несуществующим
координатам. Соответственно на выходе получалось изкромсаное изображение с
черными полосками и когда кеш обновлялся то это становилось&amp;nbsp;заметно.&lt;/p&gt;
&lt;p&gt;Данную ошибку с кеширование легко исправить введя случайный &lt;code&gt;GET&lt;/code&gt; параметр в
адресе изображения перед выводом его в админке.
Примерно&amp;nbsp;так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;randint&lt;/span&gt;
&lt;span class="n"&gt;rand_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{url}?{num}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;data-thumbnail-url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rand_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Вследствии чего, проблема отображения &lt;strong&gt;старого&lt;/strong&gt; изображения после обрезки
исчезла. В админке выводился следующий &lt;code&gt;html&lt;/code&gt; код отображения&amp;nbsp;изображения:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;id_cropping-image&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/src/img/9b/c6/9bc62cfaaf70be365de538b415cbb8df.jpg.800x800_q85_detail_upscale.jpg?80339&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;width: 800px; height: 634px;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ошибка найдена и ликвидирована. Больше повторятся не должна.
Можно торжествовать&amp;nbsp;победу.&lt;/p&gt;
&lt;h2 id="nachinaem-snachala"&gt;&lt;a class="toclink" href="#nachinaem-snachala"&gt;Начинаем&amp;nbsp;сначала&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Буквально через неделю, после сабмита изменений в продакшн, от модераторов
вновь поступила жалоба на некорректную обрезку изображения. К счастью,
на этот раз они явно указали на каком именно изображении воспроизводится
некорректная обрезка и переслали данное изображение по электронной&amp;nbsp;почте.&lt;/p&gt;
&lt;p&gt;Открыв письмо, я открыл картинку в новой вкладке и сохранил её. Начал
тестировать в &lt;code&gt;vagrant&lt;/code&gt;е &amp;#8212; ошибка не воспроизводится, изображение обрезается
как нужно, ничего не поворачивается и не возникают черные полосы. Проверил
эту же картинку на продакшн сервере &amp;#8212; не воспроизводится.
Хитрость оказалась в том, что нужно было картинку именно &lt;strong&gt;скачать&lt;/strong&gt;, а не
&lt;strong&gt;сохранить&lt;/strong&gt; из письма. Получив исходное изображение, ошибка стала регулярно&amp;nbsp;воспроизводится.&lt;/p&gt;
&lt;h3 id="exif-metadannye"&gt;&lt;a class="toclink" href="#exif-metadannye"&gt;Exif&amp;nbsp;метаданные&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Поскольку существенную разницу поведения изображения вызывало &lt;strong&gt;сохранение&lt;/strong&gt;
и &lt;strong&gt;скачивание&lt;/strong&gt;, а так же &amp;#8212; тот факт, что после сохранения изображения в
графическом редакторе ошибка прекращала воспроизводится. Натолкнуло на мысль,
о том, что в данном изображении содержится дополнительная информация,
которая при сохранении&amp;nbsp;теряется.&lt;/p&gt;
&lt;p&gt;В графических изображениях дополнительную метаинформацию можно сохранять
в формате &lt;a href="http://www.exif.org/"&gt;&lt;span class="caps"&gt;EXIF&lt;/span&gt;&lt;/a&gt;. Где среди прочего может содержаться
информация об ориентации изображения. Выведя &lt;code&gt;exif&lt;/code&gt; данного файла,
сразу стало понятно почему ошибка воспроизводится именно на&amp;nbsp;нем.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PIL.ExifTags&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TAGS&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;photo.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;exif&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_getexif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;exif&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TAGS&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pprint&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pprint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PrettyPrinter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;   &lt;span class="s1"&gt;&amp;#39;ApertureValue&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4281&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1441&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ColorSpace&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ComponentsConfiguration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x01\x02\x03\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;DateTime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2012:09:08 18:10:40&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;DateTimeDigitized&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2012:09:08 18:10:40&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;DateTimeOriginal&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2012:09:08 18:10:40&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ExifImageHeight&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ExifImageWidth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ExifOffset&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;206&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ExifVersion&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0221&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ExposureMode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ExposureProgram&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ExposureTime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;FNumber&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Flash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;FlashPixVersion&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0100&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;FocalLength&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;GPSInfo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;   &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;N&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;****&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;****&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                   &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;E&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;****&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;****&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                   &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25453&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;182&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                   &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3588&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                   &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;M&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;44274&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;191&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ISOSpeedRatings&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Make&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Apple&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;MeteringMode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Model&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;iPhone 3GS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Orientation&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;ResolutionUnit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;SceneCaptureType&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;SensingMethod&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Sharpness&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;Software&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;5.1.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;SubjectLocation&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;767&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;614&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;614&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;WhiteBalance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;XResolution&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;YCbCrPositioning&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;YResolution&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Как видно из &lt;code&gt;exif&lt;/code&gt; данных, изображением является фотография сделанная на
&lt;code&gt;iPhone 3GS&lt;/code&gt; в сентябре 2012 года. Но это совершенно не важно,
главным является информация об ориентации изображения: &lt;code&gt;Orientation 6&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Exif Orientation&lt;/code&gt; тег описывает ориентацию изображения при отображении,
используя значения от 1 до&amp;nbsp;8:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;  1        2       3      4         5            6           7          8

######  ######      ##  ##      ##########  ##                  ##  ##########
##          ##      ##  ##      ##  ##      ##  ##          ##  ##      ##  ##
####      ####    ####  ####    ##          ##########  ##########          ##
##          ##      ##  ##
##          ##  ######  ######
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Наглядно на примере фотокамеры, могут возникать 4 варианта тега&amp;nbsp;ориентации:&lt;/p&gt;
&lt;p&gt;&lt;img alt="exif orient" class="center" src="/media/wrong-exif/orient.gif" /&gt;&lt;/p&gt;
&lt;h3 id="pil-exif"&gt;&lt;a class="toclink" href="#pil-exif"&gt;&lt;span class="caps"&gt;PIL&lt;/span&gt; &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt;&amp;nbsp;Exif&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;При сохранении изображения с помощью &lt;code&gt;PIL&lt;/code&gt; все метаданные &lt;code&gt;exif&lt;/code&gt; стираются,
поэтому сохраненная картинка без метаинформации уже не поворачивается при
отображении, а остается такая как есть. Тоесть с точки зрения пользователя
&amp;#8212; &lt;em&gt;непредсказуемо поворачиваются&lt;/em&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;saved.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;im_saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;saved.jpg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;exif&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im_saved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_getexif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;exif&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="povorot-pered-sokhraneniem"&gt;&lt;a class="toclink" href="#povorot-pered-sokhraneniem"&gt;Поворот перед&amp;nbsp;сохранением&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Поскольку &lt;code&gt;PIL&lt;/code&gt; стирает метаинформацию при сохранении изображения, то
будем поворачивать изображение при наличии тега ориентации&amp;nbsp;самостоятельно.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;orient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Check are this img oriented &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# check is has exif data&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;_getexif&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_getexif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="c1"&gt;# if has information - try rotate img&lt;/span&gt;
    &lt;span class="n"&gt;orientation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_getexif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x112&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rotate_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;270&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;orientation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rotate_values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rotate_values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;orientation&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Данная функция проверяет наличие информации о повороте изображения, и если она
присутствует, поворачивает картинку на соответствующий угол. Сохраняя
изображение метаинформация стирается, и поэтому, если даже &lt;code&gt;exif orientation&lt;/code&gt;
тег был не &lt;em&gt;фотоаппаратный&lt;/em&gt; (3, 6, 8), а &lt;em&gt;отраженный&lt;/em&gt; (2, 4, 5, 7). То картинка
будет такой как и есть в действительности, и в случае необходимости,
модераторы смогут её повернуть как им&amp;nbsp;нужно.&lt;/p&gt;
&lt;p&gt;Вот такой простой функцией решилась эта коварная&amp;nbsp;ошибка.&lt;/p&gt;
&lt;h2 id="nagliadnyi-primer"&gt;&lt;a class="toclink" href="#nagliadnyi-primer"&gt;Наглядный&amp;nbsp;пример&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Для большей наглядности, приведем пример неправильной обрезки изображения из-за
кеширования, а так же различного поведения изображения с &lt;code&gt;exif&lt;/code&gt; ориентацией
и без. В качестве исходного изображения будем использовать
знаменитую &lt;a href="http://lenna.org/"&gt;Лену&lt;/a&gt;, но для более заметных изменений, не её
канонический вариант 512x512, а прямоугольное изображение. Для работы с &lt;code&gt;exif&lt;/code&gt;
информацией воспользуемся консольной утилитой
&lt;a href="http://www.sno.phy.queensu.ca/~phil/exiftool/"&gt;exiftool&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="oshibka-keshirovaniia"&gt;&lt;a class="toclink" href="#oshibka-keshirovaniia"&gt;Ошибка&amp;nbsp;кеширования&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Возьмем портретно ориентированное изображение, и попробуем его дважды обрезать
на одном и том же превью&amp;nbsp;изображении.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna" class="center" src="/media/wrong-exif/lenna.jpg" title="Исходное изображение." /&gt;&lt;/p&gt;
&lt;p&gt;Модератор обрезает изображение, затем после обновления страницы повторно
загружается тоже-самое превью. Модератор обрезает изображение второй раз,
думая при этом, что изменения по какой либо причине не&amp;nbsp;сохранились.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna" class="center" src="/media/wrong-exif/lenna_cut.png" title="Превью обрезки изображения." /&gt;&lt;/p&gt;
&lt;p&gt;Но, на самом деле, изображение в первый раз обрезалось&amp;nbsp;корректно.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna" class="center" src="/media/wrong-exif/lenna_cache_crop_1.jpg" title="Правильно обрезанное изображение." /&gt;&lt;/p&gt;
&lt;p&gt;Получив повторные координаты для обрезки изображения, происходит&amp;nbsp;следующее.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna" class="center" src="/media/wrong-exif/lenna_cache_crop_2.jpg" title="Дважды обрезанное изображение." /&gt;&lt;/p&gt;
&lt;p&gt;И так далее, при каждой последующей обрезки исходное изображение будет
продолжать кромсаться. Поэтому случайный &lt;code&gt;GET&lt;/code&gt; параметр позволяет бороться с
кешированием и загружать необходимое превью для правильной обработки&amp;nbsp;изображения.&lt;/p&gt;
&lt;h3 id="exif-orientatsiia"&gt;&lt;a class="toclink" href="#exif-orientatsiia"&gt;Exif&amp;nbsp;ориентация&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Создадим исходное изображение, с ландшафтным соотношение сторон,
без заданного тега&amp;nbsp;ориентации.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna default" class="center" src="/media/wrong-exif/lenna_default.jpg" title="Исходное изображение, Orientation: 0." /&gt;&lt;/p&gt;
&lt;p&gt;Отобразим метаинформацию в данном&amp;nbsp;изображении.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ exiftool lenna_default.jpg
ExifTool Version Number         : &lt;span class="m"&gt;9&lt;/span&gt;.46
File Name                       : lenna_default.jpg
Directory                       : .
File Size                       : &lt;span class="m"&gt;79&lt;/span&gt; kB
File Modification Date/Time     : &lt;span class="m"&gt;2015&lt;/span&gt;:09:06 &lt;span class="m"&gt;10&lt;/span&gt;:40:30+03:00
File Access Date/Time           : &lt;span class="m"&gt;2015&lt;/span&gt;:09:06 &lt;span class="m"&gt;10&lt;/span&gt;:49:37+03:00
File Inode Change Date/Time     : &lt;span class="m"&gt;2015&lt;/span&gt;:09:06 &lt;span class="m"&gt;10&lt;/span&gt;:49:12+03:00
File Permissions                : rw-rw-r--
File Type                       : JPEG
MIME Type                       : image/jpeg
JFIF Version                    : &lt;span class="m"&gt;1&lt;/span&gt;.01
Exif Byte Order                 : Little-endian &lt;span class="o"&gt;(&lt;/span&gt;Intel, II&lt;span class="o"&gt;)&lt;/span&gt;
Orientation                     : Horizontal &lt;span class="o"&gt;(&lt;/span&gt;normal&lt;span class="o"&gt;)&lt;/span&gt;
X Resolution                    : &lt;span class="m"&gt;72&lt;/span&gt;
Y Resolution                    : &lt;span class="m"&gt;72&lt;/span&gt;
Resolution Unit                 : inches
Exif Version                    : &lt;span class="m"&gt;0210&lt;/span&gt;
Flashpix Version                : &lt;span class="m"&gt;0100&lt;/span&gt;
Color Space                     : Uncalibrated
Exif Image Width                : &lt;span class="m"&gt;463&lt;/span&gt;
Exif Image Height               : &lt;span class="m"&gt;584&lt;/span&gt;
Image Width                     : &lt;span class="m"&gt;584&lt;/span&gt;
Image Height                    : &lt;span class="m"&gt;463&lt;/span&gt;
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : &lt;span class="m"&gt;8&lt;/span&gt;
Color Components                : &lt;span class="m"&gt;3&lt;/span&gt;
Y Cb Cr Sub Sampling            : YCbCr4:2:0 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Image Size                      : 584x463
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Скопируем изображение и установим тег ориентации&amp;nbsp;6.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ cp lenna_default.jpg lenna_exif_6.jpg
$ exiftool -n -Orientation&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt; lenna_exif_6.jpg
    &lt;span class="m"&gt;1&lt;/span&gt; image files updated
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;img alt="lenna exif rotate" class="center" src="/media/wrong-exif/lenna_exif_6.jpg" title="Повернутое изображение, Orientation: 6." /&gt;
Чтобы увидеть поворот откройте изображение в новой&amp;nbsp;вкладке.&lt;/p&gt;
&lt;p&gt;Теперь посмотрим какая метаинформация содержится в этом&amp;nbsp;изображении.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ exiftool lenna_exif_6.jpg
ExifTool Version Number         : &lt;span class="m"&gt;9&lt;/span&gt;.46
File Name                       : lenna_exif_6.jpg
Directory                       : .
File Size                       : &lt;span class="m"&gt;79&lt;/span&gt; kB
File Modification Date/Time     : &lt;span class="m"&gt;2015&lt;/span&gt;:09:06 &lt;span class="m"&gt;10&lt;/span&gt;:41:52+03:00
File Access Date/Time           : &lt;span class="m"&gt;2015&lt;/span&gt;:09:06 &lt;span class="m"&gt;10&lt;/span&gt;:49:25+03:00
File Inode Change Date/Time     : &lt;span class="m"&gt;2015&lt;/span&gt;:09:06 &lt;span class="m"&gt;10&lt;/span&gt;:49:18+03:00
File Permissions                : rw-rw-r--
File Type                       : JPEG
MIME Type                       : image/jpeg
JFIF Version                    : &lt;span class="m"&gt;1&lt;/span&gt;.01
Exif Byte Order                 : Little-endian &lt;span class="o"&gt;(&lt;/span&gt;Intel, II&lt;span class="o"&gt;)&lt;/span&gt;
Orientation                     : Rotate &lt;span class="m"&gt;90&lt;/span&gt; CW
X Resolution                    : &lt;span class="m"&gt;72&lt;/span&gt;
Y Resolution                    : &lt;span class="m"&gt;72&lt;/span&gt;
Resolution Unit                 : inches
Exif Version                    : &lt;span class="m"&gt;0210&lt;/span&gt;
Flashpix Version                : &lt;span class="m"&gt;0100&lt;/span&gt;
Color Space                     : Uncalibrated
Exif Image Width                : &lt;span class="m"&gt;463&lt;/span&gt;
Exif Image Height               : &lt;span class="m"&gt;584&lt;/span&gt;
Image Width                     : &lt;span class="m"&gt;584&lt;/span&gt;
Image Height                    : &lt;span class="m"&gt;463&lt;/span&gt;
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : &lt;span class="m"&gt;8&lt;/span&gt;
Color Components                : &lt;span class="m"&gt;3&lt;/span&gt;
Y Cb Cr Sub Sampling            : YCbCr4:2:0 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Image Size                      : 584x463
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Как видно, в тег ориентации установлено значение &lt;code&gt;Rotate 90 CW&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Загрузив данные изображения на сервис - можно увидеть различное&amp;nbsp;поведение:&lt;/p&gt;
&lt;p&gt;Изображение без тега ориентации не поворачивается и отображается &lt;strong&gt;как есть&lt;/strong&gt;,
с ландшафтным соотношением&amp;nbsp;сторон.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna on service default" class="center" src="/media/wrong-exif/lenna_service_default.png" title="Изображение без тега ориентации отображается как есть." /&gt;&lt;/p&gt;
&lt;p&gt;Изображение с тегом ориентации при отображении поворачивается в портретное
соотношение&amp;nbsp;сторон.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna on service exif" class="center" src="/media/wrong-exif/lenna_service_exif_6.png" title="Изображение с тегом ориентации &amp;quot;поворачивается&amp;quot;." /&gt;&lt;/p&gt;
&lt;p&gt;Второе изображение выглядит как нужно и модератор без сомнений обрезает&amp;nbsp;изображение.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna crop" class="center" src="/media/wrong-exif/lenna_crop.png" title="Обрезка изображения с помощью Jcrop." /&gt;&lt;/p&gt;
&lt;p&gt;Обрезая изображение как показано на рисунке сверху, модератор ожидает получить
следующий&amp;nbsp;результат.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna correct crop" class="center" src="/media/wrong-exif/lenna_crop_correct.jpg" title="Правильно обрезанное изображение." /&gt;&lt;/p&gt;
&lt;p&gt;Однако, поскольку координаты заданы для портретной ориентации, а само
изображение имеет размеры альбомной ориентации, результат получается
неожиданным. Картинка поворачивается, и возникает черная полоса снизу&amp;nbsp;изображения.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna wrong crop" class="center" src="/media/wrong-exif/lenna_crop_wrong.jpg" title="Не правильно обрезанное изображение." /&gt;&lt;/p&gt;
&lt;p&gt;После использования предварительного поворота изображения, и удаления
метаинформации, данное неожиданное поведение исчезает, изображение отображается
таким какое оно есть на самом деле. Загружая картинку с тегом ориентации,
при сохранении на сервере она сразу сохраняется&amp;nbsp;поворачивается.&lt;/p&gt;
&lt;p&gt;Тоесть в случае, если модератор обрезает портретное&amp;nbsp;изображение.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna on service exif" class="center" src="/media/wrong-exif/lenna_service_exif_6.png" title="Правильное отображение исходного изображения." /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna crop" class="center" src="/media/wrong-exif/lenna_crop.png" title="Обрезка изображения с помощью Jcrop." /&gt;&lt;/p&gt;
&lt;p&gt;Он получает, правильный портретный&amp;nbsp;результат.&lt;/p&gt;
&lt;p&gt;&lt;img alt="lenna correct crop" class="center" src="/media/wrong-exif/lenna_crop_correct.jpg" title="Правильно обрезанное изображение." /&gt;&lt;/p&gt;</summary><category term="python"></category><category term="exif"></category><category term="bug"></category><category term="magic"></category><category term="crop"></category><category term="jpg"></category><category term="thumbnail"></category><category term="pil"></category><category term="image"></category><category term="lenna"></category><category term="best"></category></entry><entry><title>Автоматическое содержание в Pelican</title><link href="https://maks.live/articles/drugoe/avtomaticheskoe-soderzhanie-v-pelican/" rel="alternate"></link><published>2015-09-02T15:00:00+03:00</published><updated>2015-09-02T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-09-02:articles/drugoe/avtomaticheskoe-soderzhanie-v-pelican/</id><summary type="html">&lt;p&gt;Так получается, что некоторые статьи, например
&lt;a href="https://maks.live/articles/python/ralli-na-brauzerakh/"&gt;ралли на браузерах&lt;/a&gt;. Содержат в себе несколько
уровней заголовков. И в связи с этим, сделаем автоматическую навигацию
по&amp;nbsp;статье.&lt;/p&gt;
&lt;h2 id="iakoria-v-zagolovkakh-markdown"&gt;&lt;a class="toclink" href="#iakoria-v-zagolovkakh-markdown"&gt;Якоря в заголовках&amp;nbsp;markdown&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Для написания постов блога я использую 
&lt;a href="http://daringfireball.net/projects/markdown/"&gt;markdown&lt;/a&gt; разметку текста.
Для того, чтобы при генерации &lt;code&gt;html&lt;/code&gt; заголовкам присваивались якоря - необходимо
подключить расширение
&lt;a href="https://pythonhosted.org/Markdown/extensions/toc.html"&gt;Table of Contents&lt;/a&gt;.
Делается это элементарно прописав в &lt;code&gt;pelicanconf.py&lt;/code&gt; следующую&amp;nbsp;строчку:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;MD_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;codehilite(css_class=highlight)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;extra&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;markdown.extensions.toc&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь при генерации &lt;code&gt;html&lt;/code&gt; каждому заголовку будет присваиваться &lt;code&gt;id&lt;/code&gt;
содержащимся текстом.&amp;nbsp;Заголовок:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;##Markdown anchors
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Будет сгенерирован в такой &lt;code&gt;html&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;markdown-anchors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Markdown&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;anchors&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="ssylki-v-zagolovkakh"&gt;&lt;a class="toclink" href="#ssylki-v-zagolovkakh"&gt;Ссылки в&amp;nbsp;заголовках&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Чтобы заголовки были непросто тегами &lt;code&gt;h1&lt;/code&gt; &amp;#8230; &lt;code&gt;h6&lt;/code&gt;, а содержали ссылку на
самих себя нужно добавить аргумент &lt;code&gt;anchorlink&lt;/code&gt;. Теперь конфигурация
&lt;code&gt;MD_EXTENSIONS&lt;/code&gt; выглядит&amp;nbsp;так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;MD_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;codehilite(css_class=highlight)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;extra&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;markdown.extensions.toc(anchorlink=True)&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Заголовок:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;##Markdown anchors
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Будет сгенерирован в такой &lt;code&gt;html&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;markdown-anchors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;toclink&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#markdown-anchors&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Markdown&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;anchors&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="kirillitsa-v-zagolovkakh"&gt;&lt;a class="toclink" href="#kirillitsa-v-zagolovkakh"&gt;Кириллица в&amp;nbsp;заголовках&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;К сожалению, стандартный &lt;code&gt;slugify&lt;/code&gt; который используется в &lt;code&gt;markdown toc&lt;/code&gt; не
умеет обрабатывать кириллические символы, и поэтому&amp;nbsp;заголовок.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;##Ссылка в заголовке
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Будет сгенерирован в такой &lt;code&gt;html&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;toclink&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#_1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Ссылка в&lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;заголовке&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Что бы исправить это, можно воспользоваться библиотекой
&lt;a href="https://pypi.python.org/pypi/python-slugify"&gt;python slugify&lt;/a&gt;, задав
&lt;code&gt;TocExtension&lt;/code&gt; объект &lt;code&gt;slugify&lt;/code&gt;. Объект &lt;code&gt;slugify&lt;/code&gt; должен быть &lt;code&gt;callable&lt;/code&gt;
поэтому не обойтись просто строковым указанием аргументов, придется явно
импортировать и указывать в конструкторе аргументы для&amp;nbsp;расширения:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;markdown.extensions.toc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TocExtension&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;slugify&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;slugify&lt;/span&gt;
&lt;span class="n"&gt;MD_EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;codehilite(css_class=highlight)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;extra&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TocExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anchorlink&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slugify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id="meniu-avto-soderzhaniia"&gt;&lt;a class="toclink" href="#meniu-avto-soderzhaniia"&gt;Меню авто&amp;nbsp;содержания&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Для навигации по заголовкам будем использовать &lt;code&gt;jQuery plugin&lt;/code&gt;
&lt;a href="http://renaysha.me/anchorific-js/"&gt;Anchorific.js&lt;/a&gt;. Данный плагин умеет
самостоятельно присваивать &lt;code&gt;id&lt;/code&gt; заголовкам, но поскольку заголовки уже
сгенерированы с якорями, то создание ссылок джаваскриптом использоваться
не будет, в конструкторе укажем &lt;code&gt;null&lt;/code&gt; значения для текста и позиции ссылки
в&amp;nbsp;заголовке.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;article.content&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;anchorific&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;anchorClass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;anchorText&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;anchor&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Так же пришлось немного подправить напильником этот плагин, под конкретные задачи,
например заставить его искать не все заголовки, а только с&amp;nbsp;идентификаторами.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gd"&gt;- self.headers = self.$elem.find( &amp;#39;h1, h2, h3, h4, h5, h6&amp;#39; );&lt;/span&gt;
&lt;span class="gi"&gt;+ self.headers = self.$elem.find( &amp;#39;h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]&amp;#39; );&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="lipuchee-meniu"&gt;&lt;a class="toclink" href="#lipuchee-meniu"&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Липучее&amp;#8221;&amp;nbsp;меню&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Для того, что бы при скроллинге страницы меню навигации всегда оставалось
доступным будем использовать &lt;code&gt;position: fixed;&lt;/code&gt;, но присваивать его только
при достижении вершины объекта при&amp;nbsp;скроллинге.&lt;/p&gt;
&lt;p&gt;Создадим класс &lt;code&gt;sticky&lt;/code&gt; и будем навешивать его по событию&amp;nbsp;скролл.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sticky&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;$window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;$sticky&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;div.anchorific&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;sticky_top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$sticky&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;$window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;$sticky&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toggleClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sticky&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sticky_top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Получилось такое авто&amp;nbsp;содержание:&lt;/p&gt;
&lt;p&gt;&lt;img alt="nav" class="center" src="/media/md-headers/nav.png" /&gt;&lt;/p&gt;</summary><category term="python"></category><category term="pelican"></category><category term="markdown"></category><category term="md"></category><category term="autonav"></category><category term="anchor"></category><category term="javascript"></category><category term="css"></category></entry><entry><title>Загрузка видео на youtube</title><link href="https://maks.live/articles/python/zagruzka-video-na-youtube/" rel="alternate"></link><published>2015-08-26T18:00:00+03:00</published><updated>2015-08-26T18:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-08-22:articles/python/zagruzka-video-na-youtube/</id><summary type="html">&lt;p&gt;В одном из моих собственных проектов, возникла задача автоматической
загрузки видео на канал на &lt;code&gt;youtube&lt;/code&gt;. Делается это достаточно просто, при
помощи гугловского &lt;code&gt;api&lt;/code&gt; клиента для &lt;code&gt;python&lt;/code&gt;. Единственное затруднение
вызвало полумагическое получение ключей доступа к &lt;code&gt;api&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="avtorizatsiia"&gt;&lt;a class="toclink" href="#avtorizatsiia"&gt;Авторизация&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Все коды доступа и ключи авторизации, использованные в статье, вымышленные.&lt;br /&gt;
Любое совпадение с реально существующими или когда-либо существовавшими
ключами&amp;nbsp;случайно.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="sozdanie-novogo-proekta"&gt;&lt;a class="toclink" href="#sozdanie-novogo-proekta"&gt;Создание нового&amp;nbsp;проекта&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Для авторизации в сервисах &lt;code&gt;google&lt;/code&gt; с помощью протокола
&lt;a href="http://oauth.net/2/"&gt;oauth2&lt;/a&gt; необходимо зарегистрировать приложение и дать
ему соответсвующие права. Для этого нужно перейти в
&lt;a href="https://console.developers.google.com/project"&gt;консоль разработчика&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Нажимаем на кнопку &lt;code&gt;Create Project&lt;/code&gt;, выбираем имя и создаем новое приложение.
После того как приложение будет создано, нужно добавить ему необходимые
доступы к google &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Create Project" class="shadow center" src="/media/youtube-upload/create_proj.png" /&gt;&lt;/p&gt;
&lt;p&gt;Для загрузки видео на &lt;code&gt;youtube&lt;/code&gt; нужно добавить &lt;code&gt;YouTube Data API&lt;/code&gt;.
Для этого переходим во вкладку &lt;code&gt;APIs &amp;amp; auth&lt;/code&gt; &amp;rarr; &lt;code&gt;APIs&lt;/code&gt;.
Также во вкладке &lt;code&gt;APIs &amp;amp; auth&lt;/code&gt; &amp;rarr; &lt;code&gt;Credentials&lt;/code&gt; нужно добавить доступы
для &lt;code&gt;oauth2&lt;/code&gt; авторизации.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Add Oauth2" class="shadow center" src="/media/youtube-upload/oauth_cred.png" /&gt;&lt;/p&gt;
&lt;p&gt;Указываем тип приложения &lt;code&gt;Other&lt;/code&gt;.
Получаем доступы для авторизации: идентификатор и&amp;nbsp;пароль.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Client &lt;span class="caps"&gt;ID&lt;/span&gt; &lt;code&gt;230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Client secret &lt;code&gt;qawsWCd3J6HTRvnqsjYUpgH9&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="prava-dostupa-k-akkauntu"&gt;&lt;a class="toclink" href="#prava-dostupa-k-akkauntu"&gt;Права доступа к&amp;nbsp;аккаунту&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Получив данные для авторизации, нужно перейти по следующей ссылке, заменив в ней
параметр &lt;code&gt;client_id&lt;/code&gt; на тот, что Вы получили в предыдущем&amp;nbsp;шаге.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    https://accounts.google.com/o/oauth2/auth?
        client_id=230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com&amp;amp;
        redirect_uri=urn:ietf:wg:oauth:2.0:oob&amp;amp;
        scope=https://www.googleapis.com/auth/youtube&amp;amp;
        response_type=code&amp;amp;
        access_type=offline
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Далее выбираем к какому аккаунту гугл будет иметь доступ приложение,
и соответсвенно к какому каналу на&amp;nbsp;ютубе.&lt;/p&gt;
&lt;p&gt;&lt;img alt="account choice" class="shadow center" src="/media/youtube-upload/account_choice.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="ch choice" class="shadow center" src="/media/youtube-upload/ch_choice.png" /&gt;&lt;/p&gt;
&lt;p&gt;Соглашаемся с доступом к управлению&amp;nbsp;каналом.&lt;/p&gt;
&lt;p&gt;&lt;img alt="access" class="shadow center" src="/media/youtube-upload/access.png" /&gt;&lt;/p&gt;
&lt;p&gt;Получаем токен авторизации следующего вида
&lt;code&gt;4/Rw6A9raJQ3PrPWL0Q9z49guYu89FZoz322RySVFtzNc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="code" class="shadow center" src="/media/youtube-upload/code.png" /&gt;&lt;/p&gt;
&lt;h4 id="obnovliaemyi-token"&gt;&lt;a class="toclink" href="#obnovliaemyi-token"&gt;Обновляемый&amp;nbsp;токен&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;После этого необходимо получить, так называемый, &lt;code&gt;refresh_token&lt;/code&gt;, для этого нужно
отправить &lt;code&gt;POST&lt;/code&gt; запрос с токеном авторизации по адресу
&lt;code&gt;https://accounts.google.com/o/oauth2/token&lt;/code&gt;. Сделать это легко, при помощи
консольной утилиты &lt;a href="http://curl.haxx.se/"&gt;curl&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;code=4/Rw6A9raJQ3PrPWL0Q9z49guYu89FZoz322RySVFtzNc&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;client_id=230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;client_secret=qawsWCd3J6HTRvnqsjYUpgH9&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;redirect_uri=urn:ietf:wg:oauth:2.0:oob&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;grant_type=authorization_code&amp;quot;&lt;/span&gt;

curl --data &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://accounts.google.com/o/oauth2/token&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Токен авторизации сработает только один раз, при повторной попытке отправить
его будет получено &lt;code&gt;Code was already redeemed.&lt;/code&gt;.
В ответ на корректный запрос, гугл возвращает &lt;code&gt;json&lt;/code&gt; с временным токеном доступа
и постоянным обновляемым токеном (собственно он нам и&amp;nbsp;нужен).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;access_token&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ya29.1wGYJU7NP7Ul69c13aE1Vuvbx0LfxrsgMiBjXdNY3sU3tuE9LmuJ3nOGHeb3e_824LH0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;token_type&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bearer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;expires_in&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;quot;refresh_token&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1/g1ixyts83iMrtR71oFqwGp3LSGbHz6ByxsBThrHRWCNIgOrJDtdun6zK6XiATCKT&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="token-dostupa"&gt;&lt;a class="toclink" href="#token-dostupa"&gt;Токен&amp;nbsp;доступа&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Получив обновляемый токен, можем с его помощью каждый раз получать рабочий
токен доступа, который предоставляется временем на 3600&amp;nbsp;секунд.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;refresh_token=1/g1ixyts83iMrtR71oFqwGp3LSGbHz6ByxsBThrHRWCNIgOrJDtdun6zK6XiATCKT&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;client_id=230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;client_secret=qawsWCd3J6HTRvnqsjYUpgH9&amp;amp;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;&amp;quot;grant_type=refresh_token&amp;quot;&lt;/span&gt;

curl --data &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://accounts.google.com/o/oauth2/token&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В ответ гугл возвращает &lt;code&gt;json&lt;/code&gt; с временным токеном&amp;nbsp;доступа.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;access_token&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;ya29.2AHpPjacO0prQkip0svapohuZtoK0wqdh7u0ohH49l0WWwrSyss7CWiwzMy5wX967tWsjQ&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;token_type&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bearer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;quot;expires_in&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h5 id="avtomaticheskoe-poluchenie-tokena-dostupa"&gt;&lt;a class="toclink" href="#avtomaticheskoe-poluchenie-tokena-dostupa"&gt;Автоматическое получение токена&amp;nbsp;доступа&lt;/a&gt;&lt;/h5&gt;
&lt;p&gt;Получать этот токен доступа нужно будет каждые раз, при подключении к &lt;code&gt;api&lt;/code&gt;.
Для этого напишем простую функцию на &lt;code&gt;python 3&lt;/code&gt; с использованием стандартной
библиотеки &lt;code&gt;urllib.request&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib.request&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_auth_code&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Get access token for connect to youtube api &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;oauth_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://accounts.google.com/o/oauth2/token&amp;#39;&lt;/span&gt;
    &lt;span class="c1"&gt;# create post data&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_REFRESH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;grant_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;refresh_token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Content-Type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;application/x-www-form-urlencoded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Accept&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# make request and take response&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauth_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# get access_token from response&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;access_token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="oauth2-avtorizatsiia"&gt;&lt;a class="toclink" href="#oauth2-avtorizatsiia"&gt;Oauth2&amp;nbsp;авторизация&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Вот теперь, мы наконец подошли к самой &lt;code&gt;oauth2&lt;/code&gt; авторизации в сервисах гугл.
Для этого необходимо использовать следующие дополнительные&amp;nbsp;библиотеки:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pypi.python.org/pypi/httplib2"&gt;httplib2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.python.org/pypi/oauth2client"&gt;oauth2client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.python.org/pypi/google-api-python-client"&gt;google-api-python-client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Далее, используя выше описанную функцию получения временного токена, создаем
подключение к &lt;code&gt;youtube api&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Вообще в руководстве по работе с &lt;code&gt;youtube api&lt;/code&gt; рекомендуют использовать построение
&lt;code&gt;oauth2&lt;/code&gt; подключения с использованием объекта &lt;code&gt;flow_from_clientsecrets&lt;/code&gt;,
&lt;a href="https://developers.google.com/youtube/v3/guides/uploading_a_video"&gt;примерно так&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_authenticated_service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow_from_clientsecrets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CLIENT_SECRETS_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_UPLOAD_SCOPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MISSING_CLIENT_SECRETS_MESSAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;-oauth2.json&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_API_SERVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;YOUTUBE_API_VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Но, как выяснилось на практике, такой подход, требует при каждой загрузки,
давать разрешение на подключение к аккаунту &lt;code&gt;youtube&lt;/code&gt; вручную,
это не очень удобно. Учитывая, что можно замечательным образом получать
токен авторизации, из обновляемого токена, мы будем использовать для создания
&lt;code&gt;oauth2&lt;/code&gt; подключения - объект &lt;code&gt;AccessTokenCredentials&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httplib2&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;oauth2client.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AccessTokenCredentials&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;apiclient.discovery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_authenticated_service&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Create youtube oauth2 connection &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# make credentials with refresh_token auth&lt;/span&gt;
    &lt;span class="n"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AccessTokenCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_auth_code&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;user_agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my-awesome-project/1.0&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# create connection to youtube api&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;youtube&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;v3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Теперь мы имеем созданное подключение,
которое можно использовать для работы с &lt;code&gt;api&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="zagruzka-video"&gt;&lt;a class="toclink" href="#zagruzka-video"&gt;Загрузка&amp;nbsp;видео&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Имея готовое подключение к &lt;code&gt;api&lt;/code&gt; загрузка видео происходит&amp;nbsp;элементарно.&lt;/p&gt;
&lt;p&gt;Определим функцию инициализации загрузки, которая принимает в качестве
аргументов подключение к &lt;code&gt;youtube api&lt;/code&gt; и объект с информацией о&amp;nbsp;видео.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;apiclient.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MediaFileUpload&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;youtube&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Create youtube upload data &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# create video meta data&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;youtube_meta_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Call the API&amp;#39;s videos.insert method to create and upload the video&lt;/span&gt;
    &lt;span class="n"&gt;insert_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;youtube&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;media_body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MediaFileUpload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunksize&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resumable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# wait for file uploading&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resumable_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;insert_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Метод &lt;code&gt;youtube_meta_data&lt;/code&gt; должен возвращать словарь описания видео согласно
&lt;a href="https://developers.google.com/youtube/v3/docs/videos"&gt;формату&lt;/a&gt;,&amp;nbsp;например:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;snippet&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Summer vacation in California&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Had fun surfing in Santa Cruz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;tags&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;surfing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Santa Cruz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;categoryId&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;22&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;status&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;privacyStatus&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;private&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В моем случае данный метод имел следующий&amp;nbsp;вид:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;youtube_meta_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Create metadata dict for youtube video upload &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;snippet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_TITLE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coord&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_TAGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;categoryId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_CATEGORY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{desc}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;{site_url}/{card_id}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SITE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;privacyStatus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YOUTUBE_PRIVACY_STATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;recordingDetails&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;После инициализации загрузки, необходимо поддерживать соединение и дождаться
ответа от ютуба с идентификатором видео. Для этого будем использовать
следующую&amp;nbsp;функцию.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;http&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httplib2&lt;/span&gt;

&lt;span class="c1"&gt;# Explicitly tell the underlying HTTP transport library not to retry, since we are handling retry logic ourselves.&lt;/span&gt;
&lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# Maximum number of times to retry before giving up.&lt;/span&gt;
&lt;span class="n"&gt;MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="c1"&gt;# Always retry when these exceptions are raised.&lt;/span&gt;
&lt;span class="n"&gt;RETRIABLE_EXCEPTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpLib2Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;IOError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotConnected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncompleteRead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImproperConnectionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CannotSendRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CannotSendHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseNotReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BadStatusLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Always retry when an apiclient.errors.HttpError with one of these status codes is raised.&lt;/span&gt;
&lt;span class="n"&gt;RETRIABLE_STATUS_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resumable_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;insert_request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;insert_request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_chunk&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;HttpError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;RETRIABLE_STATUS_CODES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;RETRIABLE_EXCEPTIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_RETRIES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Maximum retry are fail&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;sleep_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sleep_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Таким образом, загрузка видео запускается функцией &lt;code&gt;initialize_upload&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;video_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initialize_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_authenticated_service&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Полный код загрузки видео можно посмотреть в
&lt;a href="https://gist.github.com/Samael500/278dcea3bbb7c167dc5e"&gt;gist&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="sanktsii"&gt;&lt;a class="toclink" href="#sanktsii"&gt;Санкции&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Поскольку капиталистический запад, в лице корпорации зла, наложил на меня
свои, безосновательные, &lt;a href="https://maks.live/articles/drugoe/ura-menia-v-gugle-zabanili/"&gt;санкции&lt;/a&gt;.
Ограничив тем самым мое право доступа к свободной информации.
Для работы с &lt;code&gt;youtube api&lt;/code&gt; мне необходимо использовать &lt;code&gt;vpn&lt;/code&gt; подключение.&lt;/p&gt;
&lt;h4 id="vpn-soedinenie"&gt;&lt;a class="toclink" href="#vpn-soedinenie"&gt;&lt;span class="caps"&gt;VPN&lt;/span&gt;&amp;nbsp;соединение&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;В качестве &lt;code&gt;vpn&lt;/code&gt; соединения я использую &lt;code&gt;ssh&lt;/code&gt; туннель и локальное &lt;code&gt;socks5&lt;/code&gt;
прокси на 1080 порту. Включаю/отключаю &lt;code&gt;ssh&lt;/code&gt; тунель при помощи библиотеки
&lt;code&gt;subprocess&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;

&lt;span class="c1"&gt;# init ssh connection&lt;/span&gt;
&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ssh&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-fN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-D&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1080&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;forward@vpn_connection&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# desctroy ssh connection&lt;/span&gt;
&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pkill&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;forward@vpn_connection&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="ne-pravilno"&gt;&lt;a class="toclink" href="#ne-pravilno"&gt;Не&amp;nbsp;правильно&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Что бы подключиться к локальному &lt;code&gt;socks5&lt;/code&gt; прокси, необходимо использовать
библиотеку &lt;a href="http://socksipy.sourceforge.net/"&gt;socksipy&lt;/a&gt;, как показано
в &lt;a href="https://code.google.com/p/httplib2/wiki/Examples#Proxies"&gt;примере&lt;/a&gt;
работы с &lt;code&gt;httplib2&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httplib2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;socks&lt;/span&gt;

&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProxyInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROXY_TYPE_SOCKS5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://l2.io/ip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="pravilno"&gt;&lt;a class="toclink" href="#pravilno"&gt;Правильно&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Но, вышеуказанный способ не работает.
Библиотека &lt;code&gt;socksipy&lt;/code&gt; не поддерживает &lt;code&gt;python 3&lt;/code&gt;, поэтому необходимо делать
&lt;a href="https://code.google.com/p/httplib2/issues/detail?id=205"&gt;по-другому&lt;/a&gt;.
Использовать библиотеку
&lt;a href="https://code.google.com/p/socksipy-branch/"&gt;socksipy-branch&lt;/a&gt;
(&lt;a href="https://gist.github.com/Samael500/5a53b01db96f812ac530"&gt;gist зеркало&lt;/a&gt;).
И оборачивать &lt;code&gt;httplib2&lt;/code&gt; с помощью метода &lt;code&gt;wrapmodule&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httplib2&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;socks&lt;/span&gt;

&lt;span class="n"&gt;socks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefaultproxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROXY_TYPE_SOCKS5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;socks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wrapmodule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://l2.io/ip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</summary><category term="python"></category><category term="youtube"></category><category term="oauth2"></category><category term="video upload"></category></entry><entry><title>200 дней кряду…</title><link href="https://maks.live/articles/github/200-dnei-kriadu/" rel="alternate"></link><published>2015-08-08T18:00:00+03:00</published><updated>2015-08-08T18:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-08-08:articles/github/200-dnei-kriadu/</id><summary type="html">&lt;p&gt;&lt;img alt="streak-200" class="center" src="/media/streak-200/streak.png" /&gt;&lt;/p&gt;
&lt;p&gt;Продолжаю ежедневно коммитить, вот уже 200 дней.
До конца года осталось меньше половины, а сначала казалось что это невозможно&amp;nbsp;долго.&lt;/p&gt;</summary><category term="github"></category><category term="streak"></category></entry><entry><title>Первый стодневный стреак</title><link href="https://maks.live/articles/github/pervyi-stodnevnyi-streak/" rel="alternate"></link><published>2015-04-30T21:50:00+03:00</published><updated>2015-04-30T21:50:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-04-30:articles/github/pervyi-stodnevnyi-streak/</id><summary type="html">&lt;p&gt;&lt;img alt="streak-100" class="center" src="/media/streak-100/streak.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Начиная с января месяца, удавалось комитить ежедневно, вот уже набежало 100 дней.
Думаю и дальше продолжать набивать последовательность и выполнить год&amp;nbsp;коммитов.&lt;/p&gt;</summary><category term="github"></category><category term="streak"></category></entry><entry><title>Ралли на браузерах</title><link href="https://maks.live/articles/python/ralli-na-brauzerakh/" rel="alternate"></link><published>2015-04-05T19:30:00+03:00</published><updated>2015-04-05T19:30:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-04-01:articles/python/ralli-na-brauzerakh/</id><summary type="html">&lt;p&gt;Собственный проект &lt;a href="http://wbtech.pro/"&gt;&lt;span class="caps"&gt;WB&lt;/span&gt;&amp;#8212;Tech&lt;/a&gt; по комментированию
скриншотов &lt;a href="http://coment.me/"&gt;coment.me&lt;/a&gt; на сегодняшний день, для получения
снимка сайта использует связку &lt;code&gt;selenium + firefox&lt;/code&gt;. Данный подход решает
задачи получения скриншота, однако тратит достаточно много памяти, и к тому же
со временем накапливается большое количество повисших процессов, что в свою
очередь приводит к подвисанию сервиса. В связи с этим, необходимо исследовать
доступные варианты и определить наилучший из браузеров для автоматического
создания&amp;nbsp;скриншотов.&lt;/p&gt;
&lt;p&gt;Критерииями для выбора победителя будут&amp;nbsp;являтся:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Качество&amp;nbsp;скриншотов&lt;/li&gt;
&lt;li&gt;Скорость&amp;nbsp;работы&lt;/li&gt;
&lt;li&gt;Затрачиваемые&amp;nbsp;ресурсы&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="uchastniki-sorevnovanii"&gt;&lt;a class="toclink" href="#uchastniki-sorevnovanii"&gt;Участники&amp;nbsp;соревнований&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;На участие в ралли были отобранны следующие&amp;nbsp;кандидаты:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firefox &lt;code&gt;36.0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Google Chrome &lt;code&gt;41.0.2272.89&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Chromium &lt;code&gt;Not tested&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://splash.readthedocs.org/en/latest/"&gt;Splash&lt;/a&gt; &lt;code&gt;1.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ghost-py.readthedocs.org/en/latest/"&gt;Ghost.py&lt;/a&gt; &lt;code&gt;0.1.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ryanpetrello/python-zombie/"&gt;Zombie.js&lt;/a&gt; &lt;code&gt;0.2.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://phantomjs.org/"&gt;Pantom.js&lt;/a&gt; &lt;code&gt;1.9.8&lt;/code&gt;, &lt;code&gt;2.0.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://slimerjs.org/"&gt;Slimer.js&lt;/a&gt; &lt;code&gt;0.9.5&lt;/code&gt;, &lt;code&gt;0.10.0pre&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Познакомимся с участниками&amp;nbsp;поближе:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Firefox&lt;/em&gt; &amp;#8212; Наиболее массовый не &lt;code&gt;WebKit&lt;/code&gt; браузер на сегодняшний&amp;nbsp;день.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Google Chrome&lt;/em&gt; / &lt;em&gt;Chromium&lt;/em&gt; &amp;#8212; Один из самых быстрых и популярных&amp;nbsp;браузеров.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Splash&lt;/em&gt; &amp;#8212; легкий браузер с поддержкой &lt;code&gt;javascript&lt;/code&gt; реализовыннй на
&lt;code&gt;python&lt;/code&gt;-е предоставляюший для управления &lt;code&gt;http api&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Ghost.py&lt;/em&gt; &amp;#8212; &lt;code&gt;python&lt;/code&gt; браузер с поддержкой &lt;code&gt;javascript&lt;/code&gt; ориентированный
на автоматическое функциональное&amp;nbsp;тестирование.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Zombie.js&lt;/em&gt; &amp;#8212; легкий и быстрый безголовый браузер для автоматического
тестирования основаный на &lt;code&gt;node.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Phantom.js&lt;/em&gt; &amp;#8212; быстрый безголовый браузер на движке &lt;code&gt;WebKit&lt;/code&gt; со
встроенной поддержкой &lt;code&gt;svg&lt;/code&gt;. Управляется при помощи &lt;code&gt;javascript api&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Slimer.js&lt;/em&gt; &amp;#8212; быстрый браузер, похожий на &lt;code&gt;phantom.js&lt;/code&gt;, однако использует
движок &lt;code&gt;Gecko&lt;/code&gt; от &lt;code&gt;firefox&lt;/code&gt;. Управляется при помощи &lt;code&gt;javascript api&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Итак, участники отобраны, и готовы показать себя во всей красе, что ж &amp;#8212;
приступим к&amp;nbsp;соревнованиям.&lt;/p&gt;
&lt;h2 id="trassa"&gt;&lt;a class="toclink" href="#trassa"&gt;Трасса&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="rally" src="/media/browsers/rally.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Нашим замечательным конкурсантам предстоит пройти трек по пересеченной
местности с четырьмя крутыми поворотами с 16-ю чекпоинтами, а именно:
показать свои навыки на следующих&amp;nbsp;ресурсах:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://google.com/"&gt;https://google.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.facebook.com/"&gt;https://www.facebook.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://habrahabr.ru/"&gt;http://habrahabr.ru/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://wbtech.pro/"&gt;http://wbtech.pro/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;при следующих разрешениях экранов по ширине: &lt;code&gt;240&lt;/code&gt;, &lt;code&gt;780&lt;/code&gt;, &lt;code&gt;1320&lt;/code&gt;, &lt;code&gt;1920&lt;/code&gt;
пикселей.&lt;/p&gt;
&lt;h2 id="zaezd-pervyi-kachestvo"&gt;&lt;a class="toclink" href="#zaezd-pervyi-kachestvo"&gt;Заезд первый &amp;#8212;&amp;nbsp;&amp;#8220;Качество&amp;#8221;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="proverka-kachestva-skrinshotov"&gt;&lt;a class="toclink" href="#proverka-kachestva-skrinshotov"&gt;Проверка качества&amp;nbsp;скриншотов&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Качество &amp;#8212; это делать что-либо правильно, даже когда никто не&amp;nbsp;смотрит.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Поскольку, в конечном счете, результат должен быть не хуже, чем имеющийся на
настоящий момент &amp;#8212; эталоном качества будут выступать снимки &lt;code&gt;firefox&lt;/code&gt;-а.&lt;/p&gt;
&lt;p&gt;На данном этапе сошли с дистанции сразу 4 участника. Причем, если бы я делал
ставки, то проиграл бы, ведь хром, которого я считал фаворитом
соревнований, оказался абсолютно некомпетентным&amp;nbsp;участником.&lt;/p&gt;
&lt;h4 id="tolko-vidimaia-oblast"&gt;&lt;a class="toclink" href="#tolko-vidimaia-oblast"&gt;Только видимая&amp;nbsp;область&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Для связки &lt;code&gt;selenium&lt;/code&gt;-а и &lt;code&gt;google chrome&lt;/code&gt;
необходимо использовать &lt;code&gt;chromedriver&lt;/code&gt; текущая стабильная версия &lt;code&gt;2.14&lt;/code&gt;.
И, как оказалось, в нем содержится баг, который тянется с 2013 года, известным
&lt;a href="https://code.google.com/p/chromedriver/issues/detail?id=294"&gt;issue&lt;/a&gt;.
Хром драйвер не пролистывает окно браузера при захвате изображения,
а делает снимок видимой&amp;nbsp;области.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="chrome fail" class="shadow" src="/media/browsers/chrome.png" /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Так что &lt;code&gt;Google Chrome&lt;/code&gt; и &lt;code&gt;Chromium&lt;/code&gt; не прошли данный&amp;nbsp;этап.&lt;/p&gt;
&lt;h4 id="nevernyi-resize"&gt;&lt;a class="toclink" href="#nevernyi-resize"&gt;Неверный&amp;nbsp;resize&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Безголовый браузер &lt;code&gt;Splash&lt;/code&gt;, запускается демоном и слушает &lt;code&gt;localhost:8050&lt;/code&gt;, по
которому предоставляет &lt;code&gt;http api&lt;/code&gt; управления браузером. Для сохранения скриншота
необходимо указать адрес сайта и ширину окна&amp;nbsp;браузера.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://localhost:8050/render.png?url={url}&amp;amp;width={res}&amp;amp;render_all=1&amp;amp;wait=1&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_call&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;curl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-o&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_as&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Однако, как оказалось, ширина окна браузера всегда 1024px, а параметр
&lt;code&gt;width&lt;/code&gt; влияет только на фактическую ширину полученного изображения,
к тому же сжатого как &lt;code&gt;thumbnail&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;240px&lt;br /&gt;&lt;img alt="splash" class="shadow" src="/media/browsers/splash.png" /&gt;
&lt;hr /&gt;
780px&lt;br /&gt;&lt;img alt="splash big" class="shadow" src="/media/browsers/splash_big.png" /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Так что &lt;code&gt;splash&lt;/code&gt; не прошел данный&amp;nbsp;этап.&lt;/p&gt;
&lt;h4 id="chto-ia-voobshche-sdes-delaiu-zombie"&gt;&lt;a class="toclink" href="#chto-ia-voobshche-sdes-delaiu-zombie"&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;Что я вообще сдесь делаю&amp;#8221; &amp;copy;&amp;nbsp;Zombie&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img alt="zombie" class="center" src="/media/browsers/zombie.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Как оказалось, Зомби вообще не умеет делать скриншоты, поэтому выбывает из&amp;nbsp;соревнований.&lt;/p&gt;
&lt;h4 id="falshivye-pasporta-staryi-gugl"&gt;&lt;a class="toclink" href="#falshivye-pasporta-staryi-gugl"&gt;Фальшивые паспорта &amp;#8212; старый&amp;nbsp;гугл&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Некоторые из участников соревнований, а именно &lt;code&gt;Phantom.js&lt;/code&gt; и &lt;code&gt;Slimer.js&lt;/code&gt;
не смогли бы пройти все этапы ралли, под своими именами, поэтому пришлось
выдать им фальшивые&amp;nbsp;паспорта.&lt;/p&gt;
&lt;p&gt;Google выдает различные версии сайта, в зависимости от того: какой &lt;code&gt;user agent&lt;/code&gt; у
браузера запрашивающего страницу, и если этот агент неизвестный или старый, то
выдается старая версия google, с черной полоской&amp;nbsp;меню.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Phantom.js 240px&lt;br /&gt;&lt;img alt="old google" class="shadow" src="/media/browsers/old_google_phantom.png" /&gt;
&lt;hr /&gt;
Slimer.js 240px&lt;br /&gt;&lt;img alt="old google 2" class="shadow" src="/media/browsers/old_google_slimer.png" /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Но при использовании поддельных паспортов, от &lt;code&gt;firefox&lt;/code&gt; результат такой как&amp;nbsp;нужно.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (X11; Linux x86_64) Gecko/20100101 Firefox/36.0&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="google ok" class="shadow" src="/media/browsers/google.png" /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="pikselizatsiia-vykoli-glaza"&gt;&lt;a class="toclink" href="#pikselizatsiia-vykoli-glaza"&gt;Пикселизация &amp;#8212; выколи&amp;nbsp;глаза.&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Ghost.py&lt;/code&gt; не очень хорошо умеет захватывать картинки, логотип google выглядит
похожим на&amp;nbsp;забор.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="ghost" class="shadow" src="/media/browsers/ghost.png" /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Хоть это и недопустимо, однако, ограничимся предупреждением, и пропустим
&lt;code&gt;Ghost.py&lt;/code&gt; в следующий&amp;nbsp;тур.&lt;/p&gt;
&lt;h3 id="rezultaty-pervogo-zaezda"&gt;&lt;a class="toclink" href="#rezultaty-pervogo-zaezda"&gt;Результаты первого&amp;nbsp;заезда&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Во второй тур прошли 4 участника и 4 участника покинули соревнования.
Турнирная таблица по окончанию первого&amp;nbsp;этапа.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Firefox&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pantom.js&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slimer.js&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Ghost.py&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;Chromium&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;Google Chrome&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;Splash&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;Zombie.js&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="zaezd-vtoroi-skorost"&gt;&lt;a class="toclink" href="#zaezd-vtoroi-skorost"&gt;Заезд второй &amp;#8212;&amp;nbsp;&amp;#8220;Скорость&amp;#8221;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="proverka-skorosti-raboty-brauzerov"&gt;&lt;a class="toclink" href="#proverka-skorosti-raboty-brauzerov"&gt;Проверка скорости работы&amp;nbsp;браузеров&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="speed" src="/media/browsers/speed.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;На данном этапе измерялось время, необходимое для создания браузера,
открытия нужной страницы, изменения ширины окна до заданной, сохранения страницы
как изображения &lt;code&gt;PNG&lt;/code&gt;, закрытия страницы и уничтожения объекта&amp;nbsp;браузера.&lt;/p&gt;
&lt;p&gt;Измерения проводились при помощи стандартной библиотеки &lt;code&gt;time&lt;/code&gt;, как разница
времени между началом запуска функции и её&amp;nbsp;окончания.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# call test browser fun()&lt;/span&gt;
    &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Легенда:&lt;/strong&gt;
    &lt;ul class="browsers1"&gt;&lt;/ul&gt;
&lt;/p&gt;

&lt;h5&gt;Time&lt;/h5&gt;

&lt;div&gt;&lt;canvas id="canvas-time"&gt;&lt;/canvas&gt;&lt;/div&gt;

&lt;h3 id="rezultaty-vtorogo-zaezda"&gt;&lt;a class="toclink" href="#rezultaty-vtorogo-zaezda"&gt;Результаты второго&amp;nbsp;заезда&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В ходе данного этапа участники заняли следующие&amp;nbsp;места:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Phantom.js &lt;code&gt;2.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;1.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ghost.py&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;1.x&lt;/code&gt; +&amp;nbsp;selenium&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;2.x&lt;/code&gt; +&amp;nbsp;selenium&lt;/li&gt;
&lt;li&gt;Slimer.js &lt;code&gt;9.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Slimer.js &lt;code&gt;10.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Firefox +&amp;nbsp;selenium&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;В результате, ни один из участников не оказался значительно хуже чем &lt;code&gt;firefox&lt;/code&gt;,
поэтому выбывших нет, все переходят к следующему&amp;nbsp;этапу.&lt;/p&gt;
&lt;h2 id="zaezd-tretii-resursy"&gt;&lt;a class="toclink" href="#zaezd-tretii-resursy"&gt;Заезд третий &amp;#8212;&amp;nbsp;&amp;#8220;Ресурсы&amp;#8221;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="analiz-zatrat-resursov-na-sozdanie-skrinshota"&gt;&lt;a class="toclink" href="#analiz-zatrat-resursov-na-sozdanie-skrinshota"&gt;Анализ затрат ресурсов на создание&amp;nbsp;скриншота&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="fuel" src="/media/browsers/fuel.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Учитывалась память, которую тратит главный процесс, и все его дочерние процессы.
Память измерялась при помощи функции &lt;code&gt;memory_usage&lt;/code&gt; библиотеки &lt;code&gt;memory_profiler&lt;/code&gt;
с указанием параметра &lt;code&gt;include_children&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Измерялась минимальная, средняя и максимальная память для каждого&amp;nbsp;скриншота.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;memory_profiler&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;memory_usage&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_usage&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;include_children&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;mins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;maxs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;avgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="nenasytnoe-prividenie"&gt;&lt;a class="toclink" href="#nenasytnoe-prividenie"&gt;Ненасытное&amp;nbsp;привидение&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Ghost.py&lt;/code&gt; оказался чрезвычайно прожорливым, занимая всю доступную
память, доходил до максимума и вылетал. Единственный из участников, кто не
сумел пройти все 16 чекпоинтов за один&amp;nbsp;подход.&lt;/p&gt;
&lt;p&gt;Учитывая вынесенное ранее предупреждение, призрак вылетает из&amp;nbsp;конкурса!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I ain&amp;#8217;t afraid of no&amp;nbsp;ghosts&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="ghostbusters" class="center" src="/media/browsers/ghostbusters.png" /&gt;&lt;/p&gt;
&lt;h4 id="dvulichnyi-khitrets"&gt;&lt;a class="toclink" href="#dvulichnyi-khitrets"&gt;Двуличный&amp;nbsp;хитрец&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Оказалось, что &lt;code&gt;Slimer.js&lt;/code&gt; притворяется: запускается дочерний процесс &lt;code&gt;slimerjs&lt;/code&gt;,
который потребляет не более 3&amp;nbsp;Mb памяти, но при этом запускает ещё один
дочерний процесс с именем &lt;code&gt;firefox&lt;/code&gt;, который уже добирает память до&amp;nbsp;300&amp;nbsp;Mb.&lt;/p&gt;
&lt;h4 id="obshchie-zatraty-pamiati"&gt;&lt;a class="toclink" href="#obshchie-zatraty-pamiati"&gt;Общие затраты&amp;nbsp;памяти&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Поскольку &lt;code&gt;Ghost.py&lt;/code&gt; потребляет уж слишком много ресурсов, на графиках не&amp;nbsp;указывается.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Легенда:&lt;/strong&gt;
    &lt;ul class="browsers2"&gt;&lt;/ul&gt;
&lt;/p&gt;

&lt;h5&gt;Memory&amp;nbsp;max&lt;/h5&gt;

&lt;div&gt;&lt;canvas id="canvas-memory_max"&gt;&lt;/canvas&gt;&lt;/div&gt;

&lt;h5&gt;Memory&amp;nbsp;avg&lt;/h5&gt;

&lt;div&gt;&lt;canvas id="canvas-memory_avg"&gt;&lt;/canvas&gt;&lt;/div&gt;

&lt;h3 id="rezultaty-tretego-zaezda"&gt;&lt;a class="toclink" href="#rezultaty-tretego-zaezda"&gt;Результаты третьего&amp;nbsp;заезда&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;В ходе данного этапа участники заняли следующие&amp;nbsp;места:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Phantom.js &lt;code&gt;1.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;2.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;1.x&lt;/code&gt; +&amp;nbsp;selenium&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;2.x&lt;/code&gt; +&amp;nbsp;selenium&lt;/li&gt;
&lt;li&gt;Slimer.js &lt;code&gt;9.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Slimer.js &lt;code&gt;10.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Firefox +&amp;nbsp;selenium&lt;/li&gt;
&lt;li&gt;Ghost.py&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;На этом этапе выбывает &lt;code&gt;Ghost.py&lt;/code&gt;, турнирная таблица принимает&amp;nbsp;вид:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pantom.js&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slimer.js&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firefox&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;Ghost.py&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="zaezd-chetvertyi-upravliaemost"&gt;&lt;a class="toclink" href="#zaezd-chetvertyi-upravliaemost"&gt;Заезд четвертый &amp;#8212;&amp;nbsp;&amp;#8220;Управляемость&amp;#8221;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="metody-upravlenie-brauzerami"&gt;&lt;a class="toclink" href="#metody-upravlenie-brauzerami"&gt;Методы управление&amp;nbsp;браузерами&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img alt="wet road" class="center" src="/media/browsers/wet_road.png" /&gt;&lt;/p&gt;
&lt;p&gt;Несмотря на то, что определилась тройка лидеров и уже можно подвести итоги,
рассмотрим как управлять безголовыми&amp;nbsp;браузерами.&lt;/p&gt;
&lt;h4 id="firefox"&gt;&lt;a class="toclink" href="#firefox"&gt;Firefox&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Firefox&lt;/code&gt; работает в связке с &lt;code&gt;selenium&lt;/code&gt;-ом и управляется достаточно просто,
единственная особенность &amp;#8212; это то, что браузер запускает графическую
оболочку, поэтому нужно использовать виртуальный&amp;nbsp;дисплей.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyvirtualdisplay&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Display&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xvfb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Browser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_window_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;save_as&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="phantom"&gt;&lt;a class="toclink" href="#phantom"&gt;Phantom&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Phantom.js&lt;/code&gt; можно использовать, как самостоятельный безголовый браузер, так и
в связке с &lt;code&gt;selenium&lt;/code&gt;-ом.
Работа с &lt;code&gt;selenium&lt;/code&gt;-ом аналогична &lt;code&gt;firefox&lt;/code&gt;, за тем лишь исключением, что нет
необходимости запускать виртуальный&amp;nbsp;дисплей.&lt;/p&gt;
&lt;p&gt;Собственная работа &lt;code&gt;phantom.js&lt;/code&gt;, заключается в вызове консольной команды,
и передаче ей скрипта для выполнения&amp;nbsp;браузером.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (X11; Linux x86_64) Firefox/36.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://wbtech.pro/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;img/phantomjs2-no_selenium/wbtech.pro-1920px.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;phantom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В &lt;code&gt;python&lt;/code&gt; запускаем браузер с помощью стандартной библиотеки &lt;code&gt;subprocess&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_call&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;phantom_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--ssl-protocol=any&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="slimer"&gt;&lt;a class="toclink" href="#slimer"&gt;Slimer&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Slimer.js&lt;/code&gt; работает точно так же, как и &lt;code&gt;phantom.js&lt;/code&gt; без &lt;code&gt;selenium&lt;/code&gt;-а,
но является не совсем безголовым, он запускает графическую оболочку, поэтому
требует виртуальный&amp;nbsp;дисплей.&lt;/p&gt;
&lt;p&gt;А так же, в ходе тестирования было &lt;a href="http://stackoverflow.com/questions/29280104/slimerjs-takes-a-snapshot-of-only-the-visible-area/"&gt;выявлено&lt;/a&gt;,
что для корректного скриншота нужно всегда, перед открытием страницы,
указывать базовую ширину окна&amp;nbsp;браузера.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;webpage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (X11; Linux x86_64) Firefox/36.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://wbtech.pro/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;img/slimerjs10/wbtech.pro-1920px.png&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;slimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В &lt;code&gt;python&lt;/code&gt; запускаем браузер с помощью стандартной библиотеки &lt;code&gt;subprocess&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyvirtualdisplay&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Display&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;visible&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xvfb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;check_call&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;slimer_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--ssl-protocol=any&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id="finish"&gt;&lt;a class="toclink" href="#finish"&gt;Финиш&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img alt="chess flag" src="/media/browsers/chess_flag.png" /&gt;&lt;/p&gt;
&lt;p&gt;Гонка завершилась, пришло время подвести итоги и определить&amp;nbsp;победителей.&lt;/p&gt;
&lt;p&gt;До финиша доехали всего три команды, так что тройка лидеров очевидна. Учитывая
затраты ресурсов и времени, участники занимают следующие&amp;nbsp;места:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Phantom.js &lt;code&gt;2.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;1.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;1.x&lt;/code&gt; +&amp;nbsp;selenium&lt;/li&gt;
&lt;li&gt;Phantom.js &lt;code&gt;2.x&lt;/code&gt; +&amp;nbsp;selenium&lt;/li&gt;
&lt;li&gt;Slimer.js &lt;code&gt;9.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Slimer.js &lt;code&gt;10.x&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Firefox +&amp;nbsp;selenium&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Безоговорочным лидером гонки стал &lt;code&gt;phantom.js&lt;/code&gt;, в качестве награды ему будет
предложено занять пост &lt;code&gt;firefox&lt;/code&gt;-а на сервисе &lt;a href="http://coment.me/"&gt;coment.me&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="pedestal" src="/media/browsers/pedestal.png" /&gt;&lt;/p&gt;
&lt;script type="text/javascript" src="/extra/browsers/chart.min.js"&gt;&lt;/script&gt;

&lt;script type="text/javascript" src="/extra/browsers/data.js"&gt;&lt;/script&gt;</summary><category term="python"></category><category term="browsers"></category><category term="wb-tech"></category><category term="best"></category></entry><entry><title>Ура, меня в гугле забанили…</title><link href="https://maks.live/articles/drugoe/ura-menia-v-gugle-zabanili/" rel="alternate"></link><published>2015-01-30T15:00:00+03:00</published><updated>2015-01-30T15:00:00+03:00</updated><author><name>Maks</name></author><id>tag:maks.live,2015-01-30:articles/drugoe/ura-menia-v-gugle-zabanili/</id><summary type="html">&lt;p&gt;&lt;img alt="google-ban" class="center" src="/media/google-ban/google_ban.png" /&gt;&lt;/p&gt;
&lt;p&gt;В связи с наложенными западными компаниями санкциями на Крым,
у меня перестала работать часть сервисов&amp;nbsp;гугла.&lt;/p&gt;
&lt;p&gt;Старый анекдот про &amp;#8220;в гугле забанили&amp;#8221; начинает быть похожим на&amp;nbsp;действительность.&lt;/p&gt;
&lt;p&gt;При попытке перейти получаем 403. Некоторые из заблокированных&amp;nbsp;ресурсов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.google.com/"&gt;https://code.google.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/"&gt;https://developers.google.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://console.developers.google.com/"&gt;https://console.developers.google.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/"&gt;https://developer.android.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/"&gt;https://cloud.google.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://appengine.google.com/"&gt;https://appengine.google.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://golang.org/"&gt;https://golang.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://angularjs.org/"&gt;https://angularjs.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Так же не работают в целом сайты размещенные на &lt;code&gt;gae&lt;/code&gt;,
например &lt;a href="https://www.udacity.com/"&gt;https://www.udacity.com/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Не работает обновление &lt;code&gt;google chrome&lt;/code&gt; и приложений из плей&amp;nbsp;маркета.&lt;/p&gt;
&lt;p&gt;Ну что ж, прокси и впн-ы никто не отменял,
при необходимости доступ к ресурсам получить будет&amp;nbsp;можно.&lt;/p&gt;</summary></entry></feed>