Http server python: http.server — HTTP servers — Python 3.7.9 documentation

Содержание

Запускаем простейший веб-сервер на Python и Flask

Также нам потребуется Flask – специальный инструмент, с помощью которого можно создавать сайты на Python. Это микрофреймворк, имеющий встроенный веб-сервер. Будем предполагать, что вы работаете под Linux-системой, поэтому команды для Windows в рамках этой статьи рассматривать не будем, при желании вы сами сможете найти аналоги. Вообще, если вам нужен качественный ресурс для конкретных целей (особенно коммерческих), то стоит заказать создание сайта у профессионалов. Они разработают для вас уникальный дизайн, продумают структуру, спланируют продвижение и т. д. Мы же рассматриваем просто азы создания веб-сервера.

Устанавливаем нужные библиотеки

Предположим, что Python, pip и virtualenv у вас уже установлены и настроены, соответствующие статьи есть на сайте. Теперь перейдет к загрузке Flask:


pip install flask

Если вам больше нравится использовать виртуальные окружениями, то зайдите в его директорию и уже там выполните следующую команду:


source venv/bin/activate

Для проверки правильности установки, можно создать файлик

server.py, в котором будет такое содержимое:


from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
    return "Hello World!"
if __name__ == "__main__":
    app.run()

Для его выполнения воспользуйтесь командой:


python server.py

Изначально для работы Flask используется порт 5000. Заходим в браузере на следующий адрес — http://localhost:5000. Если вы увидите такую фразу «Hello World!», значит все сделано правильно.

С помощью Flask можно делать немало интересных штук, к примеру, осуществлять обработку GET и POST параметров.

Теперь нам нужно переработать скрипт так, чтобы он смог принять имя пользователя, а затем вывел приветствие на экране:


from flask import Flask
app = Flask(__name__)
@app.route("/", methods=['GET'])
def index(username):
    return "Hello, %s!" % username
if __name__ == "__main__":
    app.run(host='0.0.0.0', port=4567)

Мы указали, что теперь для работы скрипта будет использоваться порт 4567. Также он будет через адресную строку принимать имя от пользователя. Открываем в браузере следующий адрес: http://localhost:4567/yourname. Ответ должен быть таким: «Hello, yourname». Этим подтверждается успешная работа сервера, он вернул нужную нам строку.

Настройка прокси

Если вы хотите, чтобы вашим сайтом могли пользоваться и другие люди, вам понадобится внешний IP адрес. Разбираетесь в этом или имеете VPS? Тогда у вас не возникнет проблем с этим. Но если это для вас что-то новенькое, то прибегнем к более легкому, но не особо универсальному способу – воспользуемся прокси сервером.

Для этого нам понадобится бесплатная программка ngrok. Он занимается поддержкой постоянного соединения и доставлением вам всех данных, полученных от других людей. Запускаем ее при помощи следующей команды, в параметре указав любой незанятый порт:


./ngrok http 4567

Ответ должен состоять из нескольких строк, а среди них должно быть что-то такое:


Forwarding http://7e9ea9dc.ngrok.io -> 127.0.0.1:4567

Вот именно это и есть тот самый адрес, перейдя по которому люди будут оказываться на вашем сайте. На этом запуск простейшего веб-сервера можно считать закаченным.

Программирование на Python: Часть 10. Сетевое программирование

Программирование на Python

Сергей Яковлев
Опубликовано 07.09.2010

Серия контента:

Этот контент является частью # из серии # статей: Программирование на Python

https://www.ibm.com/developerworks/ru/library/?series_title_by=**auto**

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Программирование на Python

Следите за выходом новых статей этой серии.

Компьютерный мир глобализируется на основе сетевых коммуникаций и протоколов. Интернет становится обязательным атрибутом повседневности. Все больше появляется приложений, ориентированных на сеть: это серверы баз данных, сетевые игры, различные сетевые протоколы, Web-серверы, апплеты, сервлеты, CGI-скрипты и т.д. Более того, сеть – это уже компьютер в том случае, когда используется распределенная кластерная архитектура вычислений. В этой статье речь пойдет о сетевом программировании на Python. Модуль socket предлагает простой интерфейс для использования сокетов. Программисты на C/C++ найдут, что здесь реализация сокетов значительно проще. В Python есть и другие сетевые модули: httplib, ftplib, telnetlib, smtplib, реализующие различные сетевые протоколы. Кроме того, в статье значительное внимание будет уделено инструментарию twisted, который еще в большей степени унифицирует рутинные операции, связанные с особенностями сетевого программирования.

Сегодня мы рассмотрим следующие темы.

  1. Как написать простой TCP клиент-сервер.
  2. Архитектура TCP-сервера.
  3. Что такое Twisted.
  4. Twisted протокол.
  5. Twisted фабрика.
  6. Twisted клиент-сервер.
  7. Twisted чат-сервер.

1. TCP клиент-сервер

TCP – стандартный протокол для межсетевого взаимодействия. Его основным достоинством является принцип гарантированной доставки – все пакеты, посланные сервером, будут доставлены клиенту.

Напишем простое клиент-серверное приложение. Для этого нам нужно импортировать класс socket из стандартной библиотеки, в котором есть все методы для организации соединения. Клиент посылает строку на сервер, сервер получает ее и отсылает клиенту обратно.

Код простого сервера: вначале мы создаем сокет, представляющий собой указатель на объект соединения. Этому сокету мы передаем два аргумента: первый аргумент говорит о том, что это интернет-сокет, второй – что мы используем TCP-протокол.

Первый метод, который мы используем – bind(), он инициализирует ip-адрес и порт. При этом проверяется, не занят ли порт другой программой.

Второй метод – listen() – устанавливает количество клиентских соединений, которые будет обслуживать операционная система.

Третья функция – accept() – блокирует приложение до тех пор, пока не придет сообщение от клиента. Функция возвращает кортеж из двух параметров – объект самого соединения и адрес клиента.

Четвертая функция – recv() – читает данные из сокета. Аргумент устанавливает максимальное количество байтов в сообщении.

Пятая функция – send() – отсылает данные клиенту.

Шестая функция – close() – закрывает сокет.

Функция raw_input() просто блокирует клавиатуру.

import socket import sys s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = 'localhost' port = 8007 s.bind((host), (port)) s.listen(1) conn, addr = s.accept() data = conn.recv(1000000) print 'client is at', addr , data conn.send(data) z = raw_input() conn.close()

Клиент вначале создает точно такой же сокет, что и сервер. Первый клиентский метод – connect() – позволяет соединиться с сервером. Второй метод – send() – отсылает данные на сервер. Третий метод – recv() – получает данные с сервера. Четвертый метод – close() – закрывает сокет.

import socket
import sys
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = 'localhost'
port = 8007
s.connect((host, port))
s.send('hello')  
data = s.recv(1000000) 
print 'received', data, len(data), 'bytes'
s.close()

Что стоит за этими простыми методами? По сути, они представляют собой оболочки (wrappers) для аналогичных системных вызовов. Так, метод socket.send() фактически вызывает вызов send() операционной системы. Посылаемые данные копируются в буфер операционной системы и при этом могут разбиваться на отдельные блоки (chunk). После того как последний блок будет скопирован в буфер, функция send() вернет управление программе, при этом совсем не факт, что все данные уже уйдут по назначению и будут получены на том конце клиентом. Клиент по дороге может обнаружить, что отдельные блоки пропали, и запросит их у сервера повторно. Но это уже не наша забота – за полную гарантированную отсылку данных отвечает операционная система, а не приложение.

2. Архитектура TCP-сервера

В предыдущем простом примере сервера вы успели заметить, что функция

accept() блокирует приложение. Пример банальный, рассчитан на одного клиента, и к реальной жизни имеет отдаленное отношение. Реальные серверы имеют нагрузку в несколько тысяч клиентов, и нужна более серьезная реализация. Для этого существует несколько различных подходов:

  1. использование отдельного потока на каждого клиента;
  2. использование неблокирующих сокетов;
  3. использование select/poll.

В Python неблокирующий сокет реализуется с помощью специального метода setblocking() с параметром, равным нулю.

Пример:

lstn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cs = []
nc = 2
for i in range(nc):
   (clnt,ap) = lstn.accept()
   clnt.setblocking(0)
   cs.append(clnt)

Недостаток этого метода в том, что нам вручную нужно проверять готовность каждого клиента. Третий вариант с использованием select()

позволяет переложить эту проверку на саму операционную систему. Более поздняя его вариация – функция poll().

3. Twisted

Twisted – кросс-платформенная сетевая библиотека, написанная на Python. Это асинхронный инструмент, который избавляет вас от необходимости использовать потоки. Он поддерживает работу с mail, web, news, chat, DNS, SSH, Telnet, RPC, и т.д.

Многие дистрибутивы Linux уже включают в себя twisted. Можно установить инструментарий из исходных текстов, которые лежат тут: http://twistedmatrix.com/projects/core/

В основе Twisted лежат события – event. Работой таких событий управляют специальные функции, называемые хэндлерами – event handler. Есть специальный бесконечный цикл по обработке событий – так называемый event loop. Он отлавливает события, после чего запускает соответствующие хэндлеры. После этого кошмар с последовательной обработкой событий заканчивается. За работу цикла event loop в twisted отвечает объект, называемый reactor, который находится в модуле twisted.internet . Для его запуска нужно вызвать команду:

  reactor.run()

4. Twisted Protocol

Для написания сетевого приложения нужно использовать класс Protocol, который находится в twisted.internet.protocol.Protocol. Большинство протоколов унаследованы от этого класса. Они никогда не ждут события, обрабатывая их по мере появления. Вот простой пример:

from twisted.internet.protocol import Protocol
  class Echo(Protocol):
    def dataReceived(self, data):
      self.transport.write(data)

Это простейший протокол. Он просто пишет назад то, что пишет ему клиент, и не отвечает ни на какие события. Вот пример протокола, отвечающего на событие connect:

from twisted.internet.protocol import Protocol
class Connector(Protocol):
  def connectionMade(self):
    self.transport.write("Connection made ... \r\n") 
    self.transport.loseConnection()

Этот протокол отвечает на соединение тем, что обрывает его. Событие connectionMade происходит при коннекте. Событие connectionLost срабатывает при разрыве коннекта.

5. Twisted Factory

Конфигурация поведения протокола прописывается в фабрике – классе Factory, который унаследован от twisted.internet.protocol.Factory. В программе может быть несколько протоколов, фабрика является для них организующим и конфигурационным компонентом. По умолчанию фабрика запускает каждый протокол, и устанавливает ему атрибут, называемый factory, который указывает на себя. Пример фабрики, которая позволяет протоколу писать лог-файл:

from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
 
class LoggingProtocol(LineReceiver):
    def lineReceived(self, line):
        self.factory.fp.write(line+'\n')

class LogfileFactory(Factory):
    protocol = LoggingProtocol
    def __init__(self, fileName):
        self.file = fileName
    def startFactory(self):
        self.fp = open(self.file, 'a')
    def stopFactory(self):
        self.fp.close()

6. Twisted Client-Server

Код простого сервера: на событие dataReceived сервер выводит полученные данные и тут же отсылает их обратно:

from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor
 
class Server(Protocol):
  def connectionMade(self):
    self.transport.write(self.factory.quote+'\r\n')
  def connectionLost(self, reason):
    print 'connection lost ...'
  def dataReceived(self, data):
    print data
    self.transport.write(data)
  
class ServerFactory(Factory):
  protocol = Server
  def __init__(self, quote=None):
    self.quote = quote 

reactor.listenTCP(8007, ServerFactory("quote"))
reactor.run()

В этом примере клиент делает два отложенных асинхронных сообщения с интервалом в одну секунду, используя метод реактора callLater:

from twisted.internet import reactor
from twisted.internet.protocol import Protocol, ClientCreator
 
class Client(Protocol):
  def sendMessage(self, msg):
    self.transport.write("%s\n" % msg)
    for i in range(1,5):
      self.transport.write("%d\n" % i)
  def dataReceived(self, data):
    print data
 
def gotProtocol(p):
    p.sendMessage("Hello")
    reactor.callLater(1, p.sendMessage, "world")
    reactor.callLater(2, p.transport.loseConnection)
 
c = ClientCreator(reactor, Client)
c.connectTCP("localhost", 8007).addCallback(gotProtocol)
reactor.run()

Вывод сервера:

Hello
1
2
3
4
world
1
2
3
4
connection lost ...

7. Twisted чат-сервер

Чат-сервер – это сервер, который делает широковещательную рассылку всех сообщений, которые были посланы клиентами. Сервер анализирует клиентские сообщения и в зависимости от их типа может сообщить остальным о том, что клиент зашел под своим именем, может просто разослать всем клиентское сообщение, либо отключает клиента. Список клиентов хранится в фабрике и называется clientProtocols. При каждом новом коннекте клиента в методе connectionMade протокола происходит добавление объекта ChatProtocol в этот список. Код простого чат-сервера:

from twisted.internet import reactor
from twisted.internet.protocol import ServerFactory 
from twisted.protocols.basic import LineOnlyReceiver 

class ChatProtocol(LineOnlyReceiver): 

    name = "" 

    def getName(self): 
        if self.name!="": 
            return self.name 
        return self.transport.getPeer().host 

    def connectionMade(self): 
        print "New connection from "+self.getName() 
        self.sendLine("Welcome to my my chat server.") 
        self.sendLine("Send '/NAME [new name]' to change your name.") 
        self.sendLine("Send '/EXIT' to quit.") 
        self.factory.sendMessageToAllClients(self.getName()+" has joined the party.") 
        self.factory.clientProtocols.append(self)

    def connectionLost(self, reason): 
        print "Lost connection from "+self.getName() 
        self.factory.clientProtocols.remove(self) 
        self.factory.sendMessageToAllClients(self.getName()+" has disconnected.") 

    def lineReceived(self, line): 
        print self.getName()+" said "+line 
        if line[:5]=="/NAME": 
            oldName = self.getName() 
            self.name = line[5:].strip() 
            self.factory.sendMessageToAllClients(oldName+" changed name 
				to "+self.getName()) 
        elif line=="/EXIT": 
            self.transport.loseConnection() 
        else: 
            self.factory.sendMessageToAllClients(self.getName()+" says "+line) 

    def sendLine(self, line): 
        self.transport.write(line+"\r\n") 

class ChatProtocolFactory(ServerFactory): 

    protocol = ChatProtocol 

    def __init__(self): 
        self.clientProtocols = [] 

    def sendMessageToAllClients(self, mesg): 
        for client in self.clientProtocols:
            client.sendLine(mesg) 

print "Starting Server"
factory = ChatProtocolFactory()
reactor.listenTCP(12345, factory)
reactor.run()

В качестве клиента можно использовать telnet:

>> telnet localhost 12345

Заключение

Сегодня мы узнали, что TCP протокол – это надежный сетевой протокол с гарантированной доставкой. При написании TCP клиент-серверных приложений функции протокола распределяются между приложением и самой операционной системой. В зависимости от архитектуры сервера, сокеты могут быть блокирующими и неблокирующими. Системные вызовы select() и poll() позволяют писать высоконагруженные серверы с подключением большого количества клиентов. Инструментарий twisted значительно облегчает труд по написанию сетевых приложений, предоставляя программисту возможность сфокусироваться на логике приложения, скрывая при этом низкоуровневые подробности сетевого протокола.

Код примеров проверялся на версии Python 2.6.

<< Предыдущая статьяСледующая статья >>

Ресурсы для скачивания

Django CMS Обучение: Python — Веб-сервер своими руками


Многие уже писали себе «сайты» или планируют ими заняться, но при этом плохо представляют себе что же это такое — сайт. Серия постов посвящена построению с нуля учебного (а не супер-быстрого или супер-фичастого) сервера, на примере которого, постараемся разобрать разные практические вопросы из жизни разработчика. Первая, вступительная, часть публичная т.к. возможно многим будет интересно «как это всё устроено». Код на питоне, но это практически псевдокод на английском, поэтому ни у кого тут сложностей возникнуть не должно. Остальные части скорее всего будут показываться только для участников питоноблога.

Сайт это такой многослойный торт, напичканый самыми разными видами крема кода. Давайте посмотрим, что происходит, когда пользователь набирает в браузере http://example.com/ и зачем.

Протокол HTTP работает поверх другого протокола — TCP, в котором никаких example.com нет, а есть 2.50.203.49, поэтому шаг 0 — используя службы DNS браузер получает IP-адрес хоста. Сервер об этом ничего не знает, поэтому и никакого кода этому этапу не соответствует.

Теперь можно установить соединение до сервера. IP нам сообщили, а порт берётся из схемы или задаётся вручную. Обычно это 80. Зная адрес и порт клиент создаёт сокет и открывает соединение, которое сервер принимает.

Для организации канала необходимо две стороны, два сокета. Клиент будет подключаться, а сервер находиться в состоянии ожидания:

import socket  # Всё, что нам нужно для этого лежит в модуле socket.

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # параметры для создания TCP-сокета
sock.bind(("", 8000))                                     # слушать на всех адресах, на порту 8000. Чтобы создать сервер на порту ниже 1024 нужен рут.
sock.listen(1)                                            # перевести сокет в режим ожидания входящих соединений
conn, addr = sock.accept()                                # ожидать установки соединения

(Если любопытно, можно заходить на сервер через telnet или netcat или, собственно, браузером и смотреть что происходит)

Когда клиент делает connect, а сервер accept они получают объект-соединение из которго можно читать и писать. Фактически это пара FIFO каналов.

Канал организован, теперь можно сообщить другой стороне о своих намерениях согласно протоколуHTTP.

Первым делом посылается запрос:


GET /some/stuff HTTP/1.1\r\n

Потом идут заголовки:


Запрос оканчивается пустой строкой:


Клиент своё дело сделал, дальше в игру вступает сервер. От него потребуется разобрать запрос на сегменты: собственно запрос, заголовки и тело.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
data = conn.recv(1024).split("\r\n")        # считываем запрос и бьём на строки
method, url, proto = data<0>.split(" ", 2)  # обрабатываем первую строку

headers = {}
for pos, line in enumerate(data<1:>):  # проходим по строкам и заодно запоминаем позицию
    if not line.strip():               # пустая строка = конец заголовков, начало тела**
        break
    key, value = line.split(": ", 1)   # разбираем строку с заголовком
    headers<key.upper> = value       # приводим ключ к "нормальному" виду чтобы обращение было регистронезависимым

# всё остальное - тело** запроса
body = "\r\n".join(data<pos>)</pos></key.upper>

Имея на руках URL, адрес ресурса**, канал для ответа и остальные запчасти мы можем ответить. Ответ сервера клиенту состоит из тех же трёх частей:


conn.send("HTTP/1.0 200 OK\r\n")           # мы не умеем никаких фишечек версии 1.1, поэтому будем сразу честны
conn.send("Server: OwnHands/0.1\r\n")      # Поехали заголовки...
conn.send("Content-Type: text/plain\r\n")  # разметка нам тут пока не нужна, поэтому говорим клиенту показывать всё как есть
conn.send("\r\n")                          # Кончились заголовки, всё остальное - тело ответа
conn.send("Hi there!")                     # привет мир
conn.close()                               # сбрасываем буфера, закрываем соединение

Устроив такое «короткое замыкание» браузеру и прогулявшись по стеку протоколов обеспечивающих транспорт, мы теперь можем разобрать саму начинку сервера. Тут уже пойдёт варенье вместо крема и мы вольны писать что угодно, не заглядывая в RFC 1945.

Сервер, в том виде, как сейчас обрабатывает всего один запрос и выходит. У такого поведения есть определённая полезность, но Настоящие Сервера так не делают. Продолжим.

Во время и после того, как соединение было принято, обработано и закрыто, с оригинальным сокетом ничего не произошло, он так и оставался в готовности принять новые соединения. И возможно даже уже что-то принял. Код, который будет повторяться завернём в вечный while:


while True:
    conn, addr = socket.accept()
    ...  # код работы с соединением
    conn.close()

Каждый раз пересоздавать сокет и выставлять его в режим ожидания не надо. Более того, не получится т.к. порт будет занят предыдущим сокетом и сервер навернётся с соответствующим исключением.

Итого. От сервера требуется:
создать сокет (socket.socket)
настроить его (bind, listen)
принять соединение (accept)
считать и распарсить запрос
придумать ответ
завернуть ответ в протокол
выдать его и закрыть соединение

Больше всего времени он проводит на этапе 5, и по большому счёту, это вообще не его дело, что там будет происходить. А происходить там может много чего. Например:
Тупо ответ как в примере какой-нибудь захардкоденой фигни. Малоприменимо, но в качестве упражнения сойдёт. Реальное применение — всякие empty_gif; у nginx.
Отдача файла с диска. Не барское это дело. Такими вещами должны заниматься очень сильно оптимизированые сервера типа того же nginx. Но мы всё равно попробуем, заодно разберём вопросы безопасности.
Обратное проксирование (сервер сам делает запрос и передаёт результат клиенту). Тоже работа для nginx, но делается просто, поэтому возьмём всё равно.
Выполнение другого скрипта (CGI). Морально устаревший метод вызова на каждый чих подпроцесса. Как-нибудь потом.
Передача обработчику (в стиле modphp, asp). Конёк Apache — в сервер вкорячивается интерпретатор чего угодно, который на каждый урл запускает скрипты. Попытка починить CGI, который «починить» невозможно т.к. он предназначен для другого.
Обработка запроса сервером приложений. Любимое дело Java серверов. Весь код 5го пункта живёт как часть сервера и запускается только один раз, всасывает всё в кэши, после чего как турбовеник только раскидывает запросы.

Кроме того, можно сразу выделить несколько интересных применений протокола, помимо «сайтиков». Фактичски, сайт это API для употребления человеком. Есть ещё несколько классов задач, которые часто встречаются на практике:
RPC, «удалённый вызов процедур». HTTP, благодаря своей минималистичности очень хорошо подходит в качестве транспорта для реализации службы доступа к удалённым объектам. Немного class-ной питонской магии и whatever.you_wish(to=»do») начинает выполнятся на сервере, а может даже и сразу на нескольких.
Обёртка вокруг сложных протоколов. Если есть какая-нибудь хитрая библиотека, которая там как-то сложно работает, а её нужно вызывать из кучи языков и сред то, передав в адресе все необходимые параметры, сервер выполнит всю грязную работу и не надо будет переписывать библиотеку для очередного недоязычка. HTTP-клиенты обычно есть везде.
Виртуальные файловые системы. Используя протокол WebDAV (расширение HTTP) можно подключить в виде сетевого диска что угодно — базу данных, поисковый индекс, список рассылки или форум. Что-то типа кроссплатформенного FUSE.

В следующем заходе приведём код в порядок и сделаем большой задел для реализации возможностей в пункте 5.



Один модуль, в котором лежит всё подряд это нормально для мини-серверков, которые просто делают одну функцию и не собираются становиться более универсальными. Используя код из первой частилюбой теперь может выполнять простейшие операции через свой «веб интерфейс», но даже это скоро станет очень тяжело поддерживать.

Поэтому сейчас мы поделим сервер на части и упакуем всё в коробку с бантиком, чтобы модуль можно было использовать в своих целях, не изменяя код самого сервера.

Для начала, применим принцип инкапсуляции и объединим всё барахло сервера и его код в один класс:


class HTTPServer(object):
    def __init__(self, host='', port=8000):
        """Распихиваем по карманам аргументы для старта"""
        self.host = host
        self.port = port

И заодно допишем к модулю специальный режим, чтобы он мог запускаться как скрипт и делать что-нибудь (условно) полезное:


if __name__ == '__main__':
    server = HTTPServer()
    server.serve()

Этот код будет выполняться только если модуль будет запущен напрямую (python s02.py) или через специальный режим для запуска модулей, лежащих где-то в недрах библиотеки (python -m s02).

Если(когда) вы столкнётесь с ошибкой «Address already in use» это значит, что ОС ещё не освободила адрес и ждёт завершения каких-то своих операций. Такое бывает если сервер обслуживал подключения, а потом крашнулся. В таком случае надо просто подождать несколько секунд.

Теперь можно втащить сюда и рабочий код сервера.


def serve(self):
        """Цикл ожидания входящих соединений"""
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind((self.host, self.port))  # параметры берём из нашего «контейнера», в который их положили при старте.
        sock.listen(50)                    # количество соединений в очереди, перед тем, как ОС откажется их принимать

        while True:
            conn, addr = sock.accept()     # есть контакт! следующая фаза — разбор чего же там пришло интересного
            self.on_connect(conn, addr)

self — обязательный параметр для всех методов класса через который передаётся конкретный егоэкземпляр.

Код делится на несколько стадий чтобы была возможность их заменять по требованию или вызывать по частям, например для тестирования.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def on_connect(self, conn, addr):
        """Соединение установлено, вычитываем запрос"""
        data = conn.recv(1024).split('\r\n')
        method, url, proto = data<0>.split(' ', 2)

        headers = {}
        for pos, line in enumerate(data<1:>):
            if not line.strip():
                break
            key, value = line.split(': ', 1)
            headers<key.upper> = value

        body = '\r\n'.join(data<pos>)
        self.on_request(method, url, headers, body, conn)</pos></key.upper>

Всё как и раньше, просто это один из логических участков работы с запросом и потенциально может быть переопределён самыми разными способами. Обратите внимание, что не смотря на то, что self передаётся, туда ничего не складывается — никаких соединений, никаких заголовков и всего такого. Оно просто передаётся следующему «работнику конвейера». Причин тут сразу несколько.
Во-первых, это нарушает принципиальное устройство класса: он называется «сервер», а прицеплять туда детали каждого конкретного соединения получается ни к селу ни к городу.
Во-вторых, сервер у нас один, а соединений много. Если обслуживаются сразу несколько соединений, то они будут постоянно перезаписывать разные участки данных друг друга. Это FAIL.
И, в любом случае, как подсказывает нам практика функционального программирования, чем меньше код создаёт побочных эффектов во время своей работы, тем меньше вероятность возникновения и распространения ошибок.


def on_request(self, method, url, headers, body, conn):
        """Обработка запроса"""
        print method, url, repr(body)

        conn.send("HTTP/1.0 200 OK\r\n")
        conn.send("Server: OwnHands/0.1\r\n")
        conn.send("Content-Type: text/plain\r\n")
        conn.send("\r\n")
        conn.send("Hi there!")
        conn.close()

Тут совершенно ничего фантастического пока что нет. Но скоро будет (:

Что же у нас на данный момент получилось? Сервер на любое соединение, на любой запрос без обработки просто по-быстрому отдаёт результат. Интересно, кстати, насколько быстро? Давайте проверим. Воспользуемся утилитой ab из apache-utils:


ab -n 5000 'http://localhost:8000/hellow/orld/?whatever=stuff&spam;=eggs'

На нормальном десктопе 5к запросов пролетает за пару секунд даже на таком «медленном» языке, как python. Из отчёта ab нам будут интересны несколько строк:
Requests per second: 4228.88 <#/sec> (mean)
Количество запросов в секунду, которое сервер может через себя пропустить. Если подавать на него меньшее количество, то он будет часть времени простаивать, а если больше — то запросы будут скапливаться в очереди в конце концов отваливаться с ошибкой, о чём станет извествно из соответствующих строк отчёта:
Complete requests: 5000
Failed requests: 0

Ещё хорошим показателем является то, что после окончаний пытаний сервера бенчмарком он остаётся работать и не вылетает на пол пути (=

А как оно будет себя если валить на него сразу много одновременных соединений? Мы ведь никакой явной параллелизации не делали. Давайте посмотрим:
ab -c 10 -n 5000 ‘http://localhost:8000/hellow/orld/?whatever=stuff&spam;=eggs’

Complete requests: 5000
Failed requests: 0

Requests per second: 11333.29 <#/sec> (mean)
Опаньки! При десяти (опция -c 10) одновременных соединений он выдаёт даже больше «попугаев» — аж в два раза. Это связано с тем, что ОС действует независимо от сервера. И пока сервер там делает свои дела, она в ядре обрабатывает установку соединений и все эти штучки на более низких уровнях стека протоколов. Готовые к употреблению соединения ОС укладывает в очередь, размер которой задаётся при переводе сокета в режим сервера: sock.listen(50)
Впрочем даже указав -c 1000 мне не удалось завалить свой сервер и ни один запрос небыл потерян :3

Запомним эти цифры, это базовый уровень скорости нашего сервера. В дальнейшем он будет работать всё тормознее и тормознее (8

(Полный код после упаковки в класс.)

А как быстро проверить, что оно вообще работает и будет работать после внесения дальнейших изменений? Для этого мы используем модульное тестирование с помощью nose, который надо `pip install`.

Сами тесты будут лежать в tests.py (так то!) и представлять собой несколько классов, содержащих код, проверяющий работоспособность собственно рабочего кода.

Проверяем:


from s02 import HTTPServer

class TestHttp(object):
    def test_serve(self):
        HTTPServer().serve()
        urlopen("http://localhost:8000/test/me/")

Oops! Сервер начинает слушать порт и пока его вечный цикл не завершится, код дальше не пойдёт. Халявы не вышло…

Давайте внимательно посмотрим что делает метод serve. Он создаёт сокет и ждёт… Ждёт он пока появится доступное соединение, которое он передаст дальше. Больше ничего полезного или хотя бы интересного тут не происходит. И по большому счёту, никакого нашего кода тут нет — все эти операции на сокетах делаются стандартной библиотекой питона, которая протестирована вдоль и поперёк. Попробуем обойтись без этого.


def on_connect(self, conn, addr):

Судя по сигнатуре, метод работы с соединением принимает что-то и ещё кое-что. Тоесть ему глубого фиолетово что там будут передавать. Давайте этим и воспользуемся.


def test_serve(self):
        server = HTTPServer()
        server.on_connect("i am a socket", None)

Да, это полная фигня и не должно работать в принципе. Но запустив тесты (nosetests tests.py) мы хотя бы узнаем что именно от нас требуется предоставить в качестве «сокета».


data = conn.recv(1024).split('\r\n')
AttributeError: 'str' object has no attribute 'recv'

Как минимум, объект должен иметь метод «recv», который получает размер буфера и возвращает строку (split это метод объектов-строк). Пробежися сразу по коду в поисках других обращений к этому conn. Это встречается аж в самом конце последнего метода, но сделаем всё сразу:


conn.send("Hi there!\n")
conn.close()

Итак, нам понадобится сделать объект, который будет эмулировать соединение и обладать тремя методами: recv, send и close. Такая методика называется «Mock Objects», «липовые объекты».


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class MockConnection(object):
    def __init__(self, data=''):
        """Создаём буферы для приёма и передачи"""
        self.read = data
        self.sent = ''

    def recv(self, buf_size=None):
        """HTTP читает всё сразу и один раз, поэтому на буфер пофиг"""
        return self.read

    def send(self, data):
        """Просто накапливаем отправленое"""
        self.sent += data

    def close(self):
        """Закрывать нечего, просто заглушка"""
        pass

Такой-вот примитивчик. Для тестов нам хватит, а там дальше что-нибудь придумаем…

Закорачиваем наш сервер на тестовое «соединение» и смотрим что получится:


def test_serve(self):
        server = HTTPServer()
        conn = MockConnection()  # типа стартовали и подключились
        server.on_connect(conn, None)

Получается, совсем не внезапно, а вполне ожидаемо, ошибка — ведь мы ещё ничего не отправили по соединению:
method, url, proto = data<0>.split(‘ ‘, 2)
ValueError: need more than 1 value to unpack

Пустую строку разделили и получили список из одной пустой строки (можете проверить в интерпретаторе: ».split(‘\r\n’)). Там, где по протоколу HTTP идёт 3 параметра, split вернул опять один и поломался код распаковки списка по переменным, который очень строго подсчитывает сколько куда должно попасть значений.

Давайте теперь подсунем туда реальный запрос от настоящего клиента. Для этого есть полезная UNIX-утилита netcat:


$ netcat -l 8000
GET /hellow/orld/ HTTP/1.0
User-Agent: Wget/1.12 (linux-gnu)
Accept: */*
Host: localhost:8000
Connection: Keep-Alive

То, что надо! Теперь мы знаем как представиться клиентом.

Но каждый раз вручную составлять все эти строки будет очень неудобно, поэтому сразу же напишем «липовый клиент», который сам «установит соединение с сервером» и вызовет его обработчик:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MockClient(object):
    def __init__(self, server):
        self.W HTTP-запрос
        data = "\r\n".join((request, headers, ''))
        data += body

        # Заворачиваем в соединение и пускаем в обработку
        return self.server.on_connect(MockConnection(data), None)

Лично я стараюсь сразу делать код, удобный в использовании. Кому-то не нравится магия-шмагия, а мне сильно приятней писать среди питонского кода в питонском же стиле. Поэтому заголовки передаются как именованые аргументы метода и кодом преобразовываются из «some_header_name=»value»» в каноничныйъ «Some-Header-Name: value». Можно было бы передавать туда сразу готовый словарь или даже список, но лично мне такое близкое общение с чужими протоколами не по нраву.

Теперь написание тестов будет попроще.


server = HTTPServer()
client = MockClient(server)
client('/hellow/orld/')

Запускаем тесты, оно работает!



Ну… Во всяком случае не вылетает. Но что конкретно работает? Об этом — в следующей части, а то и так уже несколько человек до сюда не дочитало (:

В предыдущей части мы сделали инстурменты для тестирования серверного кода без участия сокетов. Но это получился самый тривиальный из видов тестов ­— Smoke Test. Сервер запрос обработал, но что именно произошло остаётся загадкой.

Как мы помним из кода, липовое соединение содержит в себе буфер отправленного, в котором оказывается ответ сервера. Можно было бы его сравнить с эталонной строкой, но каждый раз её составлять неудобно и муторно. Поэтому неплохо было бы его распарсить.

Но один раз у нас уже кто-то что-то парсит, а именно — сервер, при получении запроса от клиента. Внимательно посмотрев на траффик можно обнаружить, что протокол практически симметричен. И клиент и сервер обмениваются «сообщениями», состоящими из одних и тех же элементов: строка запроса или ответа (формат одинаковый, немного отличается содержимое), заголовки (формат одинаковый) и тело (необязательное для клиента при GET и для сервера при всяких хитрых статусах).

В то же время, наш тестовый клиент уже содержит генератор запросов, преобразующий аргументы функции согласно протоколу.

Вынесем эти две части как из клиента, так и из сервера:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def parse_http(data):
    lines = data.split('\r\n')
    query = lines<0>.split(' ')

    headers = {}
    for pos, line in enumerate(lines<1:>):
        if not line.strip():
            break
        key, value = line.split(': ', 1)
        headers<key.upper> = value

    body = '\r\n'.join(lines<pos>)

    return query, headers, body


def encode_http(query, body='', **headers):
    data = <" ".join(query)>

    headers = "\r\n".join("%s: %s" %
        ("-".join(part.title() for part in key.split('_')), value)
        for key, value in sorted(headers.iteritems()))

    if headers:
        data.append(headers)

    data.append('')

    if body:
        data.append(body)

    return "\r\n".join(data)</pos></key.upper>

Прежде чем менять актуальный и уже рабочий код, сделаем простенькие тесты конкретно для этого элемента. Они будут лежать в своём отдельном контейнере:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class TestHTTP(object):
    def test_request(self):
        """тестирование в режиме запроса: клиент сериализует, сервер разбирает"""
        eq_('', encode_http(('GET', '/', 'HTTP/1.0'), user_agent="test/shmest"))
        eq_('', encode_http(('POST', '/', 'HTTP/1.0'), 'post=body', user_agent="test/shmest"))
        eq_((), parse_http('POST / HTTP/1.0\r\nUser-Agent: test/shmest\r\n\r\npost=body'))

    def test_response(self):
        """тестирование в режиме ответа: сервер сериализует, клиент разбирает"""
        data = 'HTTP/1.0 200 OK\r\nSpam: eggs\r\nTest-Me: please\r\n\r\nHellow, orld!\n'

        eq_(data, encode_http(('HTTP/1.0', '200', 'OK'), 'Hellow, orld!\n', test_me='please', spam="eggs"))

        reply, headers, body = parse_http(data)
        eq_(reply, 
)
        eq_(headers, {})
        eq_(body, '')

Это обычные заготовки тестов, которые при запуске будут фейлиться и сообщать, что получено не то, что ожидалось:
AssertionError: 'GET / HTTP/1.0\r\nUser-Agent: test/shmest\r\n' != ''

Убедившись «на глаз», что на выходе получается ровно то, что там должно быть согласно входным данным, копипастим (вдумчиво и внимательно!) значения в тесты.

Теперь, когда мы уверены, что всё работает, можно убрать дублирующийся код из клиента и сервера, а заодно добавить функционала.

Сервер сворачивается в один вызов parse и один encode и теперь готов к дальнейшему расширению без лишних усилий на ручное де/кодирование ответов:


def on_connect(self, conn, addr):
    """Соединение установлено, вычитываем запрос"""
    (method, url, proto), headers, body = parse_http(conn.recv(1024))
    self.on_request(method, url, headers, body, conn)

def on_request(self, method, url, headers, body, conn):
    """Обработка запроса"""
    print method, url, repr(body)
    conn.send(encode_http(("HTTP/1.0", "200", "OK"), "Hi there!\n", server="OwnHands/0.1"))
    conn.close()

Тест-клиент делает тоже самое, только в обратном порядке:


def __call__(self, url, method="GET", body='', **headers):
    conn = MockConnection(encode_http((method, url, "HTTP/1.0"), body, **headers))
    self.server.on_connect(conn, None)
    return parse_http(conn.sent)


Для новичков в питоне сразу поясню, что за странные «**» в сигнатуре функции и последующем вызове.Показать

Теперь, пнув сервер в лабораторных условиях, мы можем точно узнать его реакцию, дописав соответствующий тест:


server = HTTPServer()
client = MockClient(server)

reply, headers, body = client('/hellow/orld/')
eq_(reply, 
)
eq_(headers<'SERVER'>, '')
eq_(body, '')

Уже заполненые тесты лежат на гуглькоде, но я считаю, что намного полезней и интересней поиграться и изучить всё это самим.W тест через тест…»:


Тестирование в режиме запроса: клиент сериализует, сервер разбирает ... ok
Тестирование в режиме ответа: сервер сериализует, клиент разбирает ... ok
tests.TestServer.test_serve ... ok

Вот теперь мы готовы запилить что-нибудь полезное. Сервер выдающий один и тот же ответ мало интересен, поэтому надо сделать возможность расширения функционала. Тоесть потребуются какие-то обработчики и возможность их встраивать в код без наследования и перезаписи кусков кода сервера.

Можно было бы вешать код просто на URL, но это малоинтересно и не позволяет сделать какие-нибудь более продвинутые схемы. Сразу разделим обработчики на две фазы: pattern и handler. Первый занимается определением, надо ли вообще вызывать обработчик — получает всё, что сервер знает о запросе и выдаёт своё веское решение. Второй собственно знает, что его вызывают не просто так и пора заняться своей непосредственной работой — ответом.

Но сервер знает много чего, и это много передавать в виде аргументов очень неудобно. Поэтому завернём всё наше хозяйство в объект Request:


class Request(object):
    """Контейнер с данными текущего запроса и средством ответа на него"""

    def __init__(self, method, url, headers, body, conn):
        self.method = method
        self.url = url
        self.headers = headers
        self.body = body
        self.conn = conn

Пропишем сразу серверу в on_connect, чтобы он его использовал и передавал дальше уже всё готовенькое:


self.on_request(Request(method, url, headers, body, conn))

Сам же on_request теряет всю свою кучу аргументов и получает один (два, если вместе с self):


def on_request(self, request):
    """Обработка запроса"""
    print request

Хм.. При запросе сервер выводит какую-то нечитабельную лабуду в консоль. Это легко исправить. print пытается все свои аргументы привести сначала строковому виду, тоесть к типу str. Посмотреть что будет выводиться можно в терминале, сделав это вручную:


&gt;&gt;&gt; str(Request())
'<__main__.Request instance at 0x7f5a8a564488>'

В питоне всё-это-объект™ и у всех объектов может быть определён «волшебный» метод __str__ который будет в таких случаях вызываться. Там есть ещё много других интересных и странных методов, позволяющих сделать объект функцией или словарём или чёрти чем ещё. Пока что ограничимся просто читабельностью нашего контейнера и покажем пользователю немного содержимого:


def __str__(self):
    return "%s %s %r" % (self.method, self.url, self.headers)

Время разработчика очень ценно, а дублирование кода очень вредно. Поэтому, чтобы поймать сразу двух зайцев, скроем работу с соединением за функцией-помощником reply:


def reply(self, code='200', status='OK', body='', **headers):
    headers.setdefault('server', 'OwnHands/0.1')
    headers.setdefault('content_type', 'text/plain')
    headers.setdefault('content_length', len(body))
    headers.setdefault('connection', 'close')
    headers.setdefault('date', datetime.now().ctime())

    self.conn.send(encode_http(('HTTP/1.0', code, status), body, **headers))
    self.conn.close()

Она сразу выставит дефолтные заголовки, которые при желании можно передать самому, но они практически обязательны и совершенно нет смысла их формировать каждый раз вручную. При очень большом желании, обработчик может взять request.conn и ответить так, как ему надо. Но такое требуется редко.

Посылка готова, можно отправлять. Но ещё надо составить список возможных получателей. Добавим в конструктор сервера инициализацию списка обработчиков:



И метод их регистрации, в котором просто добавляем пару шаблон-обработчик в этот список:
def register(self, pattern, handler):
    self.handlers.append((pattern, handler))

Теперь on_request может стать диспетчером:


for pattern, handler in self.handlers:
    if pattern(request):  # aim!
        handler(request)  # fire!
        return True       # работа по запросу завершена, откидываемся

# никто не взялся ответить
request.reply('404', 'Not found', 'Письмо самурай получил\nТают следы на песке\nСтраница не найдена')

Обновим тесты, с учётом всех нововведений. Класс, содержащий сценарии тестирования будет иметь несколько методов, каждый из которых будет создавать сервер, клиент для него и дальше делать свои дела. Дублирование кода детектед! К счастью, методика модульного тестирования уже давно решила эту задачу. Собственно для этого мы и используем тут классы, а не просто функции test_something. Специальный метод setup позволяет делать одинаковую настройку для каждого последующего запуска серии тестов:


class TestServer(object):
    def setup(self):
        self.server = HTTPServer()
        self.client = MockClient(self.server)

Попробуем теперь протестировать поведение пустого сервера без обработчиков. Клиент уже создан и настроен, поэтому сразу выстреливаем запрос:


def test_404(self):
    reply, headers, body = self.client('/you/cant/find/me/?yet')
    eq_(reply, <'HTTP/1.0', '404', 'Not found'>)
    eq_(headers<'SERVER'>, 'OwnHands/0.1')

Всё в порядке, можем продолжать. Зарегистрируем пару обработчиков и попробуем наш API на вкус:


def test_handlers(self):
    self.server.register(lambda r: r.url.startswith('/hello/'), # pattern
                         lambda r: r.reply(body='hi'))          # handler

    reply, headers, body = self.client('/hello/world/')
    eq_(reply<1>, '200')
    eq_(body, 'hi')

Одна из самых удобных возможностей питона — передавать функции в качестве аргументов, укладывать их в списки и назначать в переменные. Безо всяких if/case/goto и подобной чертовщины. lambda это выражение для создания анонимной функции; сжатый аналог def, которую можно создавать на ходу и передавать дальше не отвлекаясь от структуры кода.

Как и обещалось, проверять можно не только урл, но и всё, что доступно в запросе:


self.server.register(lambda r: r.method == 'POST',    # отлавливать все посты
                     lambda r: r.reply(body=r.body))  # зеркалим тело запроса

reply, headers, body = self.client('/looking/for/a/POST/', 'POST', 'any url') # отправляем
eq_(reply<1>, '200') # всё в порядке
eq_(body, 'any url') # ловим то, что отправили

Тесты работают и можно приступать к реализации модулей, описаных в первой части.

PS: Специальный бонус для осиливших весь пост целиком \o/
Показать


Тесты это хорошо, очень хорошо. Но по ходу разрастания проекта хочется знать какие участки нотариально™ заверены, а какие ещё только предстоит покрыть.

У nose есть плагин, позволяющий оценить процент покрытия и отметить строки кода, в которые никто не заходил во время работы юниттестов. Ставится он из pip и называется nose-cov. При запуске с опцией —with-cover помимо отчётов об успешности будет выведена ещё таблица покрытия:

Name    Stmts   Miss  Cover   Missing
-------------------------------------
serve      66      8    88%   70-76, 99-100
-------------------------------------

70-76 это строки, где создаётся сокет и запускается вечный цикл обработки подключений.
99-100 это запуск дефолтного сервера, там тоже ничего интересного нет.

(Полный код тестов и сервера)



Переходим к более практической части. Хардкодом побаловались, далее по плану идёт раздача файлов.

Я пока смутно представляю какой должна быть реализация, но зато примерно знаю как можно проверить её правильность. Поэтому эти знания мы сейчас выразим в виде теста, к несуществующему пока коду. Такой подход называется Test-driven Development или TDD. Тоесть мы сначала строим измеритель выхлопа, а потом уже на другом конце собираем карбюратор, соответствующий заданым параметрам (= Контринтуитивный манёвр, но частенько помогает определиться с API ещё до написания кода, который потом, во время изменения задачи придётся переписывать. А зачем делать двойную работу?

Обработчики являются как бы плагинами к серверу, он от них никак не зависит, и поэтому должны лежать в отдельном модуле (handlers.py).


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class TestHandlers(object):
    def setup(self):
        self.server = HTTPServer()
        self.client = MockClient(self.server)

    def test_static(self):
        """Раздача файлов с диска"""

        self.server.register(lambda r: True, serve_static) # обслуживать все запросы как файл-сервер

        eq_('404', self.client('/give-me-nice-404')<0><1>)

        reply, headers, body = self.client('/handlers.py') # файл из каталога сервера
        data = open('handlers.py').read()                  # загружаем тот же файл вручную
        eq_(body, data)                                    # проверяем содержимое
        eq_(int(headers<'CONTENT-LENGTH'>), len(data))     # и заголовок с длиной, которую сервер должен нам посчитать

Функцию serve_static надо будет импортировать в начале тестов, а в handlers.py сделать заглушку:


def serve_static(request):
    request.reply(body="your file %s is being downloaded. wait 1 minute for a link or send SMS to numer 100500")

Тесты начнут дружно валиться, но теперь понятно что должно быть внутри функции:


def serve_static(request):
    try:
        data = open(request.url<1:>).read()  # отрезаем начальный / из адреса и считываем файлы
    except IOError as err:                   # если что-то не получилось, ловим ошибку
        if err.errno == 2:                   # ничего особенного, просто нет такого файла
            return request.reply('404', 'Not found', '%s: not found' % request.url)
        raise                                # всё остальное - не наше дело
    request.reply(body=data, content_length=len(data))

Теперь всё проходит. Можно сделать «однострочный веб сервер» для раздачи файлов из текущего каталога в баше, который можно вызывать через `python -m serve`. Пока оно не установлено в пути python из любого каталога конечно не прокатит, но из возле самого сервера работать вполне будет.


if __name__ == '__main__':
    server = HTTPServer()
    from handlers import serve_static
    server.register(lambda _: True, serve_static)
    server.serve()

Запускаем, пробуем:


curl http://localhost:8000/handlers.py
def serve_static(request):
    try:
...

Работает. Пробуем дальше:


curl http://localhost:8000/../../../../../../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
...

Ой! К счастью сервер запущен не от рута и /etc/shadow в безопасности. Но сервер при этом жёстко крашнется:


GET /../../../../../../../../../../../etc/shadow {'HOST': 'localhost:8000', 'ACCEPT': '*/*', 'USER-AGENT': 'curl/7.21.0 (x86_64-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18'}
Traceback (most recent call last):
...
  File "handlers.py", line 5, in serve_static
    data = open(request.url<1:>).read()
IOError: <errno> Permission denied: '../../../../../../../../../../../etc/shadow'</errno>

Сразу допишем в test_handlers вредный тест, который будет рушить «сервер» в комфортной обстановке:


self.server.register(lambda r: r.url == '/crash/me/', lambda r: no_you)
eq_('500', self.client('/crash/me/')<0><1>)

Тесты начали фэйлиться с «NameError: global name ‘no_you’ is not defined». Замечательно, то что нужно.

В каждом хэндлере всех ошибок не отловишь, да и полагаться на их будущих авторов тоже не стоит. «Хочешь чтобы было хорошо — сделай это сам!». Где у нас есть место, в котором можно раз и навсегда защитить сервер от крашей по причине ошибок хэндлеров? Они вызываются из диспетчера on_request, пристегнём его try-мнями безопасности:


try:  # всё что далее, под защитой
    for pattern, handler in self.handlers:
        if pattern(request):
            handler(request)
            return True
except Exception as err:                                              # ловим все ошибки
    request.reply('500', 'Infernal server error', 'Ай нанэ-нанэ...')  # и сообщаем в ответе
    return False  # обязательно заканчиваем выполнение после request.reply чтобы не слать уже в закрытое соединение
# соединение закрыто, сервер продолжает свою работу

Конечно, это не защитит от всяких фатальных ошибок типа вызывающих core dump, но уже что-то. Тесты теперь ловят свой законный «груз 500», но человек, заглянувший браузером останется в непонятках и будет зол. Особенно если это сам разработчик в поисках проблемы. Питон позволяет не просто отлавливать код и сообщение ошибки, но и ситуацию, в которой она возникла. А заполучить это нам поможет штатный модуль traceback.

Сделаем более детальный тест:


request, headers, body = self.client('/crash/me/')
eq_(request<1>, '500')
assert 'NameError' in body, body

Выражение assert это способ языка проверить очень-важное-условие. assert False — всегда будет вызывать исключение AssertionError. А чтобы сразу было видно, что не понравилось условию, вторым «аргументом» assert идёт тело ответа. Прямо сейчас нам оттуда просто выражают сожаление, но ничего конкретного не сообщают.

Немного изменим обработку ошибки:


request.reply('500', 'Infernal server error', traceback.format_exc())

curl стал показывать трейсбек и ошибку, что значительно облегчит написание и отладку обработчиков:


...
  File "handlers.py", line 5, in serve_static
    data = open(request.url<1:>).read()
IOError: <errno> Permission denied: '../../../../../../../../../../../etc/shadow'</errno>

Этому хаку уже сто лет 21 год в обед и нам ещё повезло, что open не умеет выполнять команды внутри скобок типа `mail [email protected] < /etc/shadow` и прочие шелловские штучки, которым были подвержены в детском возрасте многие демоны. Но всё же, даже такая штука весьма неприятна даже если сервер запущен от nobody:nogroup.

Сегодня — день TDD, поэтому сразу заготавливаем проверку. Это уже не для сервера в целом, а для конкретного хэндлера, поэтому и отправляется в его набор test_static:


eq_('404', self.client('/../../../../../../../../../../../etc/passwd')<0><1>)

Убедившись, что nosetests выдаёт наш «законный» AssertionError: ‘404’ != ‘200’, отправляемся писать фикс.

В библиотеке os.path есть много интересных функций, поэкспериментировав с которыми можно найти одну, которая выдаёт, что указаный путь выходит за уровень начального:


from os.path import relpath

def serve_static(request):
    path = request.url<1:>              # сразу отрезаем кусочек из урла, он нам дальше ещё везде пригодится
    if relpath(path).startswith('..'):  # побег! аларм!
        return request.reply('404', 'Not found', '%s: not found' % path)  # отказываемся обслуживать запрос

Что-то я уже подзадолбался писать «return request.reply()», тем более, что сервер в итоге и так ещё потом пытается ловить ошибки. А ведь 4хх и 5хх это именно ошибки и есть. Вынесем их в отдельный класс исключений, которые затем можно будет везде бросать и ловить:


class HTTPError(Exception):
    pass

И всё. Остальные могут его импортировать и пользоваться. Сделаем для него специальный способ обработки в диспетчере:


...
except HTTPError as error:
    err_code = error.args<0>  # передаём код ошибки в аргументе исключения
    reply = {             # сообщения стандартные
        404: 'Not found',
        403: 'Permission denied',
    }<err_code>               # питонский аналог switch/case
    request.reply(str(err_code), reply, "%s: %s" % (reply, request.url)) # формируем ответ
    return False                                                         # и отваливаемся
...</err_code>

После перевода ошибок на рельсы HTTPError оставшиеся ошибки из файловой системы транслируются в коды HTTP достаточно тривиально.

Выдавать файлы из текущего каталога это забавно, но хочется всё же указать привычный /var/www, а может даже не один. Тоесть понадобится система альясов url ? path, а значит нужна связь между паттерном и хэндлером. Можно было бы сделать специальный класс с конструктором (url, root) и методами pattern & handler, которые через экземпляр класса бы знали свои начальные параметры. Но «объекты это замыкания для бедных» © Norman Adams. Действительно, зачем городить целый класс, потом создавать его экземпляр, потом брать его методы и засовывать в сервер, когда нам нужно всего лишь объединить область видимости уже готовой функции с паттерном, который отловит соответствующий URL.

Первым делом, первым делом — юнит тесты… Завернём раздатчик статики так, чтобы функция выдавала две других функции и передадим это хозяйство через развёртывание позиционных аргументов:


Как настроить локальный сервер для тестирования? — Изучение веб-разработки

Этот перевод не завершён. Пожалуйста, помогите перевести эту статью с английского

Эта статья объясняет как установить простой локальный тестовый сервер на Вашем компьютере, а так же основы его использования.

Локальные и удаленные файлы

На протяжении всего обучения, вы будете открывать примеры непосредственно в браузере — двойным кликом по HTML файлу, перетаскиванием файла в окно браузера, или через меню File > Open… и указывая необходимый HTML файл. Существует множество способов как это сделать.

Если веб-адрес начинается с file:// в котором далее прописан путь к файлу на вашем локальном жестком диске, значит используется локальный файл. В противоположность этому, если вы откроете на просмотр один из наших примеров, расположенных на GitHub (или пример расположенный на любом другом удаленном сервере), веб-адресс будет начинаться с http:// или https://, что означает что файл был получен через HTTP.

Проблемы тестирования локальных файлов

Некоторые примеры могут не запуститься, если вы попробуете открыть их как локальные файлы. Это может произойти по нескольким причинам, самые распространенные из которых:

  • Они содержат ассинхронные запросы. Некоторые браузеры (включая Chrome) не будут запускать асинхронные запросы (см. Fetching data from the server), если вы просто запускаете пример из локального файла. Это связано с ограничениями безопасности (для получения дополнительной информации о безопасности в Интернете, ознакомьтесь с Website security).
  • Они имеют серверный язык. Серверные языки (например, PHP или Python) требуют специального сервера для интерпретации кода и предоставления результатов.

Запуск простого локального HTTP сервера

Чтобы обойти проблему ассинхронных запросов, нам нужно протестировать такие примеры, запустив их через локальный веб-сервер. Один из самых простых способов сделать это для наших целей — использовать модуль SimpleHTTPServer Python.

Для этого нужно:

  1. Установить Python. Если Вы пользуетесь Linux или Mac OS X, все уже готово в Вашей системе. Если Вы пользователь Windows, Вы можете скачать установочный файл с домашней страницы Python:

    • Зайдите на python.org
    • В секции загрузок, выберите линк для Python «3.xxx».
    • Внизу страницы выберите Windows x86 executable installer и скачайте его.
    • Послезагрузки файла запустите его.
    • На первой странице инсталлятора выберите чекбокс «Add Python 3.xxx to PATH».
    • Нажмите Install, затем нажмите Close когда установка закончится.
  2. Откройте командную строку (Windows)/ (OS X/Linux). Для проверки установки Python введите следующую команду:

    python -V
  3. Система вернет Вам номер версии установленной программы. В случае успешного выполнения команды python -V  нужно перейти в директорию с вашим проектом, используя команду cd:

    # include the directory name to enter it, for example
    cd Desktop
    # use two dots to jump up one directory level if you need to
    cd ..
  4. Введите команду для запуска сервера в том каталоге:

    # If Python version returned above is 3.X
    python -m http.server
    # If Python version returned above is 2.X
    python -m SimpleHTTPServer
  5. По умолчанию это приведет к запуску содержимого каталога на локальном веб-сервере на порту 8000. Вы можете перейти на этот сервер, перейдя на URL-адрес localhost: 8000 в своем веб-браузере. Здесь вы увидите содержимое указанного каталога — щелкните файл HTML, который вы хотите запустить.

Примечание. Если у вас уже есть что-то на порту 8000, вы можете выбрать другой порт, запустив команду сервера, за которой следует альтернативный номер порта, например: python -m http.server 7800 (Python 3.x) или python -m SimpleHTTPServer 7800 (Python 2.x). Затем вы можете получить доступ к своему контенту на localhost: 7800.

Запуск серверных языков локально

Модуль Python SimpleHTTPServer (python 2.0) http.server (python 3.0) полезен, но он не знает, как запускать код, написанный на таких языках, как Python, PHP или JavaScript. Чтобы справиться с этим, вам понадобится нечто большее — именно то, что вам нужно, зависит от языка сервера, который вы пытаетесь запустить. Вот несколько примеров:

  • Для запуска кода на стороне сервера Python вам необходимо использовать веб-инфраструктуру Python. Вы можете узнать, как использовать структуру Django, прочитав Django Web Framework (Python). Flask также является хорошей (чуть менее тяжелой) альтернативой Django. Чтобы запустить это, ознакомьтесь с install Python/PIP, а затем установите Flask с помощью pip3 install flask. На этом этапе вы сможете запустить примеры Python Flask, используя, например, python3 python-example.py, затем перейдя на localhost: 5000 в свой браузер.
  • Чтобы запустить серверный код Node.js (JavaScript), вам нужно использовать исходный узел или фреймворк, построенный поверх него. Express — хороший выбор — см. Express Web Framework (Node.js/JavaScript).
  • Чтобы запустить PHP-серверный код, вам понадобится настройка сервера, которая может интерпретировать PHP. Хорошими вариантами для локального тестирования PHP являются MAMP (Mac и Windows), AMPPS (Mac, Windows, Linux) и LAMP (Linux, Apache, MySQL и PHP / Python / Perl). Это полные пакеты, которые создают локальные настройки, позволяющие запускать базы данных Apache, PHP и MySQL.

Python 3 — Сетевое программирование

Python предоставляет два уровня доступа к сетевым сервисам. На низком уровне вы можете получить доступ к базовой поддержке сокетов в базовой операционной системе, которая позволяет реализовывать клиенты и серверы как для протоколов, ориентированных на соединение, так и для протоколов без установления соединения.

В Python также есть библиотеки, которые предоставляют высокоуровневый доступ к определенным сетевым протоколам прикладного уровня, таким как FTP, HTTP и так далее.

Эта глава дает вам понимание самой известной концепции в сети — Socket Programming.

Что такое сокеты?

Сокеты являются конечными точками двунаправленного канала связи. Сокеты могут связываться внутри процесса, между процессами на одной машине или между процессами на разных континентах.

Сокеты могут быть реализованы для нескольких различных типов каналов: доменные сокеты Unix, TCP, UDP и т. Д. Библиотека сокетов предоставляет специальные классы для обработки общих транспортов, а также универсальный интерфейс для обработки остальных.

Сокеты имеют свой собственный словарь —

Sr.No. Срок и описание
1

домен

Семейство протоколов, которое используется в качестве транспортного механизма. Эти значения являются константами, такими как AF_INET, PF_INET, PF_UNIX, PF_X25 и т. Д.

2

тип

Тип связи между двумя конечными точками, обычно SOCK_STREAM для протоколов, ориентированных на соединение, и SOCK_DGRAM для протоколов без установления соединения.

3

протокол

Обычно ноль, это может использоваться для идентификации варианта протокола в домене и типе.

4

имя хоста

Идентификатор сетевого интерфейса —

  • Строка, которая может быть именем хоста, четырехточечным адресом или IPV6-адресом в двоеточии (и, возможно, точечной) нотации.

  • Строка «<broadcast>», которая указывает адрес INADDR_BROADCAST.

  • Строка нулевой длины, которая указывает INADDR_ANY, или

  • Целое число, интерпретируемое как двоичный адрес в порядке байтов хоста.

5

порт

Каждый сервер прослушивает клиентов, звонящих на один или несколько портов. Порт может быть номером порта Fixnum, строкой с номером порта или названием службы.

домен

Семейство протоколов, которое используется в качестве транспортного механизма. Эти значения являются константами, такими как AF_INET, PF_INET, PF_UNIX, PF_X25 и т. Д.

тип

Тип связи между двумя конечными точками, обычно SOCK_STREAM для протоколов, ориентированных на соединение, и SOCK_DGRAM для протоколов без установления соединения.

протокол

Обычно ноль, это может использоваться для идентификации варианта протокола в домене и типе.

имя хоста

Идентификатор сетевого интерфейса —

Строка, которая может быть именем хоста, четырехточечным адресом или IPV6-адресом в двоеточии (и, возможно, точечной) нотации.

Строка «<broadcast>», которая указывает адрес INADDR_BROADCAST.

Строка нулевой длины, которая указывает INADDR_ANY, или

Целое число, интерпретируемое как двоичный адрес в порядке байтов хоста.

порт

Каждый сервер прослушивает клиентов, звонящих на один или несколько портов. Порт может быть номером порта Fixnum, строкой с номером порта или названием службы.

Модуль розетки

Чтобы создать сокет, вы должны использовать функцию socket.socket (), доступную в модуле сокета, который имеет общий синтаксис —

s = socket.socket (socket_family, socket_type, protocol = 0)

Вот описание параметров —

  • socket_family — это либо AF_UNIX, либо AF_INET, как объяснялось ранее.

  • socket_type — это либо SOCK_STREAM, либо SOCK_DGRAM.

  • протокол — обычно не указывается, по умолчанию 0.

socket_family — это либо AF_UNIX, либо AF_INET, как объяснялось ранее.

socket_type — это либо SOCK_STREAM, либо SOCK_DGRAM.

протокол — обычно не указывается, по умолчанию 0.

Если у вас есть объект сокета , вы можете использовать необходимые функции для создания вашей клиентской или серверной программы. Ниже приведен список необходимых функций —

Методы сокета сервера

Sr.No. Метод и описание
1

s.bind ()

Этот метод привязывает адрес (имя хоста, пара номеров портов) к сокету.

2

s.listen ()

Этот метод устанавливает и запускает прослушиватель TCP.

3

s.accept ()

Это пассивно принимает TCP-клиентское соединение, ожидая, пока соединение не будет установлено (блокируется).

s.bind ()

Этот метод привязывает адрес (имя хоста, пара номеров портов) к сокету.

s.listen ()

Этот метод устанавливает и запускает прослушиватель TCP.

s.accept ()

Это пассивно принимает TCP-клиентское соединение, ожидая, пока соединение не будет установлено (блокируется).

Методы клиентских сокетов

Sr.No. Метод и описание
1

s.connect ()

Этот метод активно инициирует подключение к серверу TCP.

s.connect ()

Этот метод активно инициирует подключение к серверу TCP.

Общие методы сокетов

Sr.No. Метод и описание
1

s.recv ()

Этот метод получает сообщение TCP

2

s.send ()

Этот метод передает сообщение TCP

3

s.recvfrom ()

Этот метод получает сообщение UDP

4

s.sendto ()

Этот метод передает сообщение UDP

5

s.close ()

Этот метод закрывает сокет

6

socket.gethostname ()

Возвращает имя хоста.

s.recv ()

Этот метод получает сообщение TCP

s.send ()

Этот метод передает сообщение TCP

s.recvfrom ()

Этот метод получает сообщение UDP

s.sendto ()

Этот метод передает сообщение UDP

s.close ()

Этот метод закрывает сокет

socket.gethostname ()

Возвращает имя хоста.

Простой сервер

Для написания интернет-серверов мы используем функцию сокета, доступную в модуле сокета, для создания объекта сокета. Затем объект сокета используется для вызова других функций для настройки сервера сокетов.

Теперь вызовите функцию bind (hostname, port), чтобы указать порт для вашего сервиса на данном хосте.

Затем вызовите метод accept возвращаемого объекта. Этот метод ожидает подключения клиента к указанному вами порту, а затем возвращает объект подключения , представляющий соединение с этим клиентом.

#!/usr/bin/python3           # This is server.py file
import socket                                         

# create a socket object
serversocket = socket.socket(
	        socket.AF_INET, socket.SOCK_STREAM) 

# get local machine name
host = socket.gethostname()                           

port = 9999                                           

# bind to the port
serversocket.bind((host, port))                                  

# queue up to 5 requests
serversocket.listen(5)                                           

while True:
   # establish a connection
   clientsocket,addr = serversocket.accept()      

   print("Got a connection from %s" % str(addr))
    
   msg = 'Thank you for connecting'+ "\r\n"
   clientsocket.send(msg.encode('ascii'))
   clientsocket.close()

Простой клиент

Давайте напишем очень простую клиентскую программу, которая открывает соединение с данным портом 12345 и данным хостом. Создать сокет-клиент очень просто с помощью функции модуля сокетов Python.

Socket.connect (имя хоста, порт) открывает TCP-соединение с именем хоста на порту . Когда у вас есть открытый сокет, вы можете читать из него, как любой объект ввода-вывода. Когда закончите, не забудьте закрыть его, как вы бы закрыли файл.

пример

Следующий код представляет собой очень простой клиент, который подключается к данному хосту и порту, читает все доступные данные из сокета и затем завершает работу.

#!/usr/bin/python3           # This is client.py file

import socket

# create a socket object
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

# get local machine name
host = socket.gethostname()                           

port = 9999

# connection to hostname on the port.
s.connect((host, port))                               

# Receive no more than 1024 bytes
msg = s.recv(1024)                                     

s.close()
print (msg.decode('ascii'))

Теперь запустите этот server.py в фоновом режиме, а затем запустите вышеуказанный client.py, чтобы увидеть результат.

# Following would start a server in background.
$ python server.py & 

# Once server is started run client as follows:
$ python client.py

Выход

Это даст следующий результат —

on server terminal
Got a connection from ('192.168.1.10', 3747)
On client terminal
Thank you for connecting

Интернет-модули Python

Список некоторых важных модулей в Python Network / Интернет-программировании приведен ниже —

протокол Общая функция Порт № Модуль Python
HTTP интернет страницы 80 httplib, urllib, xmlrpclib
NNTP Новости Usenet 119 nntplib
FTP Передача файлов 20 ftplib, urllib
SMTP Отправка электронной почты 25 smtplib
POP3 Получение электронной почты 110 poplib
IMAP4 Получение электронной почты 143 imaplib
Telnet Командные строки 23 telnetlib
суслик Передача документов 70 суслик, урлиб

Пожалуйста, проверьте все библиотеки, упомянутые выше, для работы с протоколами FTP, SMTP, POP и IMAP.

Дальнейшие чтения

Это было быстрое начало с программированием сокетов. Это обширная тема. Рекомендуется перейти по следующей ссылке, чтобы найти более подробную информацию —

Unix Socket Программирование .

Python Socket Library и модули .

Построение микросервиса в Python | Статьи о Python

Микросервисы в последние дни были самой горячей темой в технологии, а за микросервисной архитектурой следуют такие технологические гиганты, как Netflix, Twitter, Amazon, Walmart и т.д., а также несколько стартапов. Они идеально подходят для современного гибкого процесса разработки программного обеспечения, где происходят постоянные инновации, а продукты постоянно поставляются. Давайте разберем еще несколько деталей о микросервисах:

Основы микросервисов: что и почему?

По определению:

В микросервисных архитектурах приложения создаются и разворачиваются как простые, сильно разрозненные, сфокусированные сервисы. Они соединяются друг с другом с помощью облегченных механизмов связи, не зависящих от языка, которые часто означают простые API-интерфейсы HTTP и очереди сообщений и являются устойчивыми по своей природе.

Микросервисы могут содержать несколько независимых сервисов, которые создаются для обслуживания только одной конкретной бизнес-функции и обмена данными по упрощенному протоколу, например HTTP. Это несколько похоже на SOA (сервис-ориентированная архитектура), которая часто реализуется внутри монолитов развертывания, приводит к проблеме наличия монореполя. В монолитной архитектуре, которая является крупнейшим конкурентом архитектуры микросервисов, вся бизнес-логика присутствует в одном сервисе, который является чертовски беспорядочным, если речь идет о техническом обслуживании, тестируемости и развертывании.

Преимущества архитектуры:

  1. Непрерывная интеграция и развертывание (CI/CD). Микросервисы могут управляться группой небольших команд, поскольку они независимы и в значительной степени отделены друг от друга, что позволяет изменять, удалять или добавлять код, не затрагивая другие приложения.
  2. Независимость от языка программирования и структуры
  3. Контейнаризация
  4. Высокая масштабируемость, доступность и устойчивость

Теперь, когда мы собрали несколько базовых концепций микросервиса, давайте узнаем, как мы можем создать такую в python.

Использование Flask Framework — на уровне продакшена

Мы будем использовать Flask в качестве фреймворка вместе с его расширением Flask-Restplus, в котором добавлена поддержка быстрой сборки REST API, который в основном используется для документации Swagger. Простое веб-приложение может быть создано с использованием классов Api Flask и Flask Restplus с определенной конечной точкой, которая разрешается в ресурс маршрута и предоставляет REST API. Ниже приведен пример:


from flask import Flask
from flask_restplus import Resource, Api

app = Flask("app_name")

@app.route('/test')
@api.doc(params={})
class TestApp(Resource):
    def get():
        return 'Hello, World!'if __name__ == "__main__":

app.run(host=HOSTNAME, debug=True)

Здесь создается приложение, которое просто возвращает строку «Hello, World!» когда сделан запрос get с конечной точкой /test. Когда эта программа запущена, Flask использует встроенный веб-сервер, который будет работать и обслуживать любой запрос на хосте HOSTNAME. Но подождите, вы также увидите следующее сообщение:


WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.

Это означает, что веб-сервер по умолчанию не сможет обрабатывать параллелизм, обслуживать статические файлы и т.п. очень эффективно. Чтобы решить эту проблему, нам нужен готовый к работе веб-сервер, такой как Nginx, и сервер приложений, основанный на протоколе WSGI, например, uWSGI. uWSGI вызывает вызываемый объект Flask, то есть `app` в вышеприведенном примере программы, и может обмениваться данными через HTTP, а также через TCP-соединение на основе Unix-сокета с веб-сервером Nginx. Nginx действует как обратный прокси, кеширует, обслуживает статический контент и т.д.


uwsgi --http :9090 --wsgi-file app.py --master --processes 4 --threads 2

Полезная документация по настройке Nginx с Flask: Официальный Flask и Как обслуживать приложения Flask с помощью uWSGI и Nginx в Ubuntu 18.04.

Поток HTTP-запроса от клиентского браузера

Библиотеки Python

Чтобы эффективно выполнять бизнес-логику в среде Flask, нужно лишь несколько библиотек:

1. REQUESTS:

Для того, чтобы прочитать некоторый контент из другого URI с помощью HTTP-запроса, Requests — это все, что нам нужно. Он может выполнять любые HTTP-запросы GET/POST/etc. с надлежащей обработкой таймаута, исключениями и авторизацией. Мы также можем напрямую проанализировать ответ в содержимом объекта JSON. Пример:


import requests
response = requests.get("https://abc.com/def/hello") #простой GET запрос
json_response = response.json() #ответ в формате JSON

2. MARSHMELLOW:

This is a library which helps when serialization/deserialization are needed. For example, in the above requests HTTP call, we want to load the JSON response in a python class. Then, we can load the JSON response directly into a schema which is having post_load decorator which maps the entities into a class. For serializing the class so as to send it over the network, the python can be dumped in a schema. If a particular field should not be displayed in the response on some condition, we have to write a BaseSchema which removes a particular key in response to some condition.

Это библиотека, которая помогает, когда нужны сериализация/десериализация. Например, в приведенном выше запросе HTTP-запроса мы хотим загрузить ответ JSON в классе Python. Затем мы можем загрузить ответ JSON непосредственно в схему с декоратором post_load, который отображает сущности в класс. Для сериализации класса с целью отправки его по сети питон может быть выгружен в схему. Если конкретное поле не должно отображаться в ответе на какое-либо условие, мы должны написать BaseSchema, которая удаляет определенный ключ в ответ на некоторое условие.

3. Кэширование

Кэширование в приложении может быть достигнуто тремя способами в зависимости от варианта использования:

1. Кэширование в памяти [cachetools]: элементы хранятся в динамической памяти приложения. cachetools предоставляет декоратор, поддерживающий алгоритмы кэширования на основе LRU и TTL. В LRU, если кэш заполнен, элемент, который используется совсем недавно, будет отброшен, а в алгоритмах TTL элемент отбрасывается, когда он превышает определенный промежуток времени. Пример:


from cachetools import cached, LRUCache, TTLCache

@cached(cache=LRUCache(maxsize=32)) #item discarded if size exceeds 32 keys
def get_sum(arr):
    return sum(arr)@cached(cache=TTLCache(maxsize=1024, ttl=600))

def get_value(key):
    return some_fun(key)

2. Кэширование на основе файлов: этот кеш хранит элементы на диске. Класс werkzeug.contrib.cache.FileSystemCache принимает путь к каталогу файлов, в котором элементы будут храниться в системе, порог и время ожидания. Пример:


from werkzeug.contrib.cache import FileSystemCache

fs_cache = FileSystemCache("/tmp", threshold=64, timeout=120)
fs_cache.set(key)  # to set a key

3. Memcached Cache [pymemcache]: элементы хранятся в распределенных системах, где элементы могут храниться в большом количестве и иметь доступ с любого хоста. Класс werkzeug.contrib.cache.MemcachedCache принимает кортеж адресов сервера, время ожидания и ключ префикса, если таковые имеются. Pymemcache создан Pinterest, который реализует различные методы хеширования для хранения элементов на сервере.

4. Конкурентность и параллелизм

Конкурентность — это когда две или более задач могут запускаться, работать и завершаться в перекрывающиеся периоды времени, тогда как параллелизм — это когда задачи буквально выполняются одновременно, например, на многоядерном процессоре. Python имеет GIL (глобальную блокировку интерпретатора), которая позволяет только одному потоку контролировать интерпретатор Python. Если программа выполняется на одноядерном компьютере с одним потоком, то влияние не видно разработчикам, но когда она запускается на многоядерной системе, GIL становится узким местом, поскольку только один поток может работать на конкретном ядре, даже другие потоки могут быть запущены параллельно. Программа Python может быть двух типов: связанная с вводом/выводом и связанная с процессором. Связанные с CPU программы требуют значительных вычислений и требуют большей части времени CPU для выполнения инструкций. С другой стороны, процессы, связанные с вводом/выводом, в основном обмениваются данными с устройствами ввода/вывода, такими как диск, внешние накопители, сетевое соединение и т.д., т.е. CPU в течение этого периода не используется.

Многопоточность в Python против многопроцессорности

Из приведенного выше рисунка мы заключаем, что из-за GIL программа python запускается на одном ядре за раз, переключаясь с одного ядра на другое, сохраняя блокировку на остальных ядрах. Таким образом, даже имея несколько потоков, работающих на отдельном ядре, мы не можем достичь многопоточности. Итак, лучше выполнять многопоточность в случае процессов, связанных с вводом-выводом, и многопроцессорности для процессов, связанных с процессором. Python 3.7 предоставляет модуль concurrent.futures, который можно использовать для достижения многопроцессорности и многопоточности.

a) concurrent.futures.ThreadPoolExecutor: это следует использовать для программ, связанных с вводом / выводом, таких как сетевые вызовы. Он создает пул потоков, где определенное количество воркеров должно быть определено. Пример ниже взят из официальной документации для иллюстрации:


import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

Здесь делается четыре HTTP-запроса, что, очевидно, является операцией ввода-вывода, поэтому лучше всего применять многопоточность. Создано пять воркеров, 4 запроса load_url будут отправлены одновременно. Процессор продолжает изменять контекст из одного потока в другой, позволяя буферизовать данные во время выполнения других потоков. Метод as_completed предоставляет будущий порядок, в котором обработка завершена. result() дает вывод метода. Это можно понять из другого примера:

ThreadPoolExector: другой пример

b) concurrent.futures.ProcessPoolExecutor: это следует использовать для программ с привязкой к CPU, таких как выполнение достаточных вычислений CPU. Пример ниже взят из официальной документации для иллюстрации:


import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

Здесь проверяется число, является ли оно простым или нет, что требует вычислений CPU. У нас есть список из 6 чисел, поэтому мы передаем все 6 чисел для обработки и проверяем, является ли оно простым через ProcessPoolExecutor. Поскольку все выполнение будет выполняться на отдельных ядрах, и каждое ядро получает различный интерпретатор, поэтому блокировка GIL здесь не повлияет, и все executor.map будут работать параллельно.

Достаточно кодирования, давайте попробуем! потому что «что-то, что не проверено, сломано»

Python has a few popular testing libraries like unittestpytestetc. Unittest is what I have used most. We just need to extend the class unittest.Testcase in the unit test class and self.assertEquals or self.assertTrue etc. to assert any values. Mocking can also be done by using unittest.mock and unittest.patch decorator. A Test Suite can also be created after creating the unit tests in a single file just by extending unittest.TestSuite class. Sample test example:

В Python есть несколько популярных библиотек для тестирования, таких как unittest, pytest и т.д. Unittest — это то, что я использовал больше всего. Нам просто нужно расширить класс unittest.Testcase в классе модульного тестирования и self.assertEquals или self.assertTrue и т.д., чтобы проверять любые значения. Тестирование также можно выполнить с помощью unittest.mock и unittest.patch decorator. Test Suite также можно создать после создания модульных тестов в одном файле, просто расширив класс unittest.TestSuite. Тестовый пример:

Базовое тестирование образцов с использованием Unittest

Покрытие кода coverage

Библиотеку Python coverage можно использовать для проверки количества строк, написанных в модульном тесте. Пример команды ниже:

pytest --cov=main test/ --cov-report term-missing

coverage run -m pytest

Для проверки качества кода и выявления ошибок можно использовать Sonar, который выполняет сканирование кода Python.

Измерение времени отклика

In order to measure the response time of each method at the granular level, we can use the below decorator \@timed above any method. It returns the time taken to process any method.

Чтобы измерить время отклика каждого метода на уровне детализации, мы можем использовать приведенный ниже декоратор \@timed над любым методом. Возвращает время, затраченное на обработку любого метода.

Файловая структура проекта

Может быть несколько файлов, в которых может быть организована собственная структура, как показано ниже:


main/ controller/
      resources/
      service/
      util/test/ controller/ test_filename.py
      resources/
      service/
      util/
requirements.txt
Dockerfile
docker-componse.yml

Развертывание с использованием контейнеров

Должен быть создан Docker-контейнер, в который должны быть включены/загружены все стеки, такие как nginx, uwsgi и т.д., и точка входа для запуска сервера WSGI может быть предоставлена в Dockerfile. Для удобства можно использовать alpine как базовый образ, который очень легкий. Контейнеризация также помогает в развертывании приложения в кластере Kubernetes.

Поделитесь с другими:

Веб-сайт 101: Использование локальной среды разработки


Среды веб-разработки и локальный хостинг

При разработке веб-сайтов и веб-карт вы собираетесь работать в двух основных местах. Первое — это ваш веб-сервер. Ваш веб-сервер — это компьютер, который транслирует или «обслуживает» вашу веб-страницу и ее содержимое в Интернете. Он также известен как «производственный сервер» , ваш веб-хост или удаленный хост .На рабочем сервере у вас есть рабочий веб-сайт, доступный для всех в Интернете.

Второй — это ваш локальный компьютер, например ноутбук или настольный компьютер. Вы редко захотите активно редактировать файлы, которые находятся на вашем веб-сервере, поэтому рекомендуется делать это на своем собственном компьютере, не транслируя все ваши изменения в Интернет. Обычно это ваш «сервер разработки» или ваш localhost . Однако есть проблема, ваш персональный компьютер не является сервером.Если мы хотим разработать и протестировать наши сайты, мы запускаем веб-сервер локально на нашей машине, видимый только нам.

Настройка сервера разработки: используйте localhost

Решением этой проблемы является установка на вашем компьютере экземпляра, подобного серверу, который имитирует возможности вашего веб-сервера и позволяет обрабатывать запросы через HTTP. Этот «сервер разработки» может находиться на вашем локальном компьютере и быть видимым только вам. Одна из основных причин этого заключается в том, что это позволит загружать внешние данные и ресурсы, такие как файл, который вы хотите загрузить из другого места в Интернете, или набор данных из другого места на вашем сервере, в ваше приложение на спрос.Это называется AJAX или асинхронным JavaScript и XML. Страница, которая загружается сразу, — это синхронно , то есть она читает документы вашего веб-сайта и загружает все по порядку. Асинхронная загрузка требуется, если вы не хотите загружать внешний файл сразу при открытии сайта, а ждете, пока пользователь или другой процесс на сайте не скажет файлу загрузить.

Веб-серверы

сложны, и полное объяснение того, как они работают, выходит за рамки этого руководства, но создание локального сервера на нашем компьютере, который имитирует возможности веб-сервера, может быть довольно простым и очень важным для тестирования. и развитие.

Существует несколько хороших вариантов настройки серверов, на которых вы можете разрабатывать и создавать сайты локально, прежде чем размещать их в сети. В этом руководстве будут рассмотрены два варианта: очень простое решение с использованием Python, которое позволяет создавать статические HTML-сайты, и использование XAMPP, хорошей локальной среды разработки, поддерживающей Apache, MySQL, PHP и Perl.
  • Используйте простой HTTP-сервер Python (для сайтов со статическим HTML, не поддерживает никаких зависимостей)
    • Python будет имитировать то, что может обслуживать ваша папка Athena WWW.
  • Используйте XAMPP Server (программное обеспечение сервера разработки, используемое для статических HTML-сайтов и сайтов, требующих локального доступа к PHP и MySQL).
    • XAMPP будет имитировать то, что могут обслуживать сценарии MIT или коммерческий хостинг-провайдер.

Вариант 1. Используйте Python localhost Server

Python будет имитировать то, что может обслуживать ваша папка Athena WWW.

Если вам нужен быстрый веб-сервер, который может обрабатывать HTTP-запросы, но вы не хотите устанавливать XAMPP или другое программное обеспечение, тогда вам может помочь Python.Python поставляется с простым встроенным HTTP-сервером, который может запускать статические веб-страницы. С помощью этого небольшого HTTP-сервера вы можете превратить любой каталог в вашей системе в каталог вашего веб-сервера. Единственное, что вам нужно установить, это Python.


1. Проверьте, установлен ли Python на вашем компьютере.

Откройте командную строку, чтобы узнать, установлен ли Python.

  • В Windows выполните поиск Командная строка , затем, когда откроется окно, введите python –V .Нажмите Enter.
  • На Mac откройте терминал , затем введите python –V . Нажмите Enter.

Командная строка (Windows) и Терминал (Mac) обычно называются синонимами, взаимозаменяемыми или просто «командной строкой». Это способ получить доступ к своей машине, набирая команды, указывающие ей, что делать.

Ваша командная строка либо вернет версию Python, либо сообщит, что Python не распознается. Если он возвращает версию, у вас установлен Python, если он не распознается, вам необходимо установить Python.На следующем снимке экрана вы видите, что на моем компьютере установлен Python версии 2.7.5.

Для установки Python используйте следующие руководства.

После установки проверьте версию еще раз, чтобы убедиться, что она работает.


2. Запустите команду Python в своей веб-папке, чтобы запустить локальный сервер.

Когда Python работает и установлен, запуск простого локального сервера теперь сводится к переходу к расположению нашей веб-папки, а затем к запуску однострочной команды, которая запускает наш локальный сервер в этой папке.В следующих инструкциях предположим, что я хотел бы совместно использовать каталог / documents / mywebsite , а мой IP-адрес — 192.168.1.2 .

Откройте командную строку ( Терминал на Mac, Командная строка в Windows). Команда cd изменяет наш каталог. Введите cd , а затем укажите путь к нашей локальной веб-папке.

  $ cd / documents / mywebsite  

Чтобы просмотреть список файлов в вашем текущем местоположении, после изменения каталога введите dir в Windows или ls на Mac.

Зайдя в папку вашего веб-сайта, введите следующую команду

  $ python -m SimpleHTTPServer  

Вот и все! Теперь ваш http-сервер запустится через порт 8000. Вы получите сообщение:

  $ cd / document / mywebsite
$ python -m SimpleHTTPServer
Обслуживание HTTP на порте 0.0.0.0 8000 ...  

Теперь он может работать в фоновом режиме, больше ничего делать не нужно.

3. Откройте свой локальный веб-сайт в браузере.

Теперь откройте браузер и введите следующий адрес:
http: // localhost: 8000

Вы также можете получить к нему доступ по адресу:
http: // 192.168.1.2: 8000 или
http://127.0.0.1:8000

Если в каталоге есть файл с именем index.html, этот файл будет использоваться в качестве исходного файла. Если index.html отсутствует, будут перечислены файлы в каталоге.

Если вы хотите изменить порт

21.22. http.server — HTTP-серверы — документация Python v3.3.1

Исходный код: Lib / http / server.py


Этот модуль определяет классы для реализации HTTP-серверов (веб-серверов).

Один класс, HTTPServer, является подклассом socketserver.TCPServer. Он создает и прослушивает HTTP-сокет, отправляя запросы в обработчик. Код для создания и запуска сервера выглядит так:

 def run (server_class = HTTPServer, handler_class = BaseHTTPRequestHandler):
    server_address = ('', 8000)
    httpd = server_class (server_address, handler_class)
    httpd.serve_forever ()
 
класс http.server.HTTPServer ( адрес_сервера , RequestHandlerClass )

Этот класс основан на классе TCPServer, сохраняя сервер адрес как переменные экземпляра с именем server_name и порт сервера.Сервер доступен обработчику, обычно через переменную экземпляра сервера обработчика.

HTTPServer должен получить RequestHandlerClass при создании экземпляра, из которых этот модуль предоставляет три различных варианта:

класс http.server.BaseHTTPRequestHandler ( запрос , client_address , сервер )

Этот класс используется для обработки HTTP-запросов, поступающих на сервер.От сам по себе он не может отвечать ни на какие действительные HTTP-запросы; он должен быть подклассом для обработки каждого метода запроса (например, GET или POST). BaseHTTPRequestHandler предоставляет несколько классов и экземпляров переменные и методы для использования подклассами.

Обработчик проанализирует запрос и заголовки, а затем вызовет метод зависит от типа запроса. Имя метода состоит из запрос. Например, для метода запроса SPAM функция do_SPAM () метод будет вызываться без аргументов.Вся необходимая информация хранятся в переменных экземпляра обработчика. Подклассы не должны переопределить или расширить метод __init __ ().

BaseHTTPRequestHandler имеет следующие переменные экземпляра:

client_address

Содержит кортеж в форме (хост, порт), относящийся к клиентскому адрес.

сервер

Содержит экземпляр сервера.

команда

Содержит команду (тип запроса).Например, «ПОЛУЧИТЬ».

путь

Содержит путь запроса.

request_version

Содержит строку версии из запроса. Например, HTTP / 1.0.

заголовки

Содержит экземпляр класса, указанного в классе MessageClass. переменная. Этот экземпляр анализирует и управляет заголовками в HTTP запрос.

rfile

Содержит поток ввода, расположенный в начале необязательного ввода данные.

wfile

Содержит выходной поток для записи ответа обратно в клиент. Правильное соблюдение протокола HTTP должно использоваться при записи в этот поток.

BaseHTTPRequestHandler имеет следующие переменные класса:

server_version

Задает версию программного обеспечения сервера. Вы можете изменить это. В формат — это несколько строк, разделенных пробелами, где каждая строка имеет имя формы [/ версия].Например, BaseHTTP / 0.2.

sys_version

Содержит версию системы Python в форме, пригодной для использования version_string метод и класс server_version переменная. Например, «Python / 1.4».

error_message_format

Задает строку формата для построения ответа об ошибке клиенту. Это использует заключенные в скобки описатели формата с ключом, поэтому операнд формата должен быть словарь. Код Ключ должен быть целым числом, указывающим числовой Значение кода ошибки HTTP. сообщение должно быть строкой, содержащей (подробное) сообщение об ошибке того, что произошло, и объяснение должно быть объяснение номера кода ошибки. Сообщение по умолчанию и объясняют Значения можно найти в переменной класса ответов .

error_content_type

Задает HTTP-заголовок Content-Type ответов об ошибках, отправляемых на клиент. Значение по умолчанию — text / html.

protocol_version

Определяет версию протокола HTTP, используемую в ответах.Если установлено на «HTTP / 1.1», сервер разрешит постоянные соединения HTTP; однако ваш сервер должен иметь , а затем включать точную длину содержимого заголовок (используя send_header ()) во всех своих ответах клиентам. Для обратной совместимости значение по умолчанию — «HTTP / 1.0».

MessageClass

Задает класс, подобный email.message.Message, для синтаксического анализа HTTP. заголовки. Как правило, это не отменяется, и по умолчанию используется http.client.HTTPMessage.

отзывы

Эта переменная содержит отображение целых чисел кода ошибки в двухэлементные кортежи. содержащие короткое и длинное сообщение. Например, {код: (короткое сообщение, longmessage)}. Короткое сообщение обычно используется в качестве ключа сообщения в ответ об ошибке и длинное сообщение как объясняет ключ (см. error_message_format переменная класса).

Экземпляр BaseHTTPRequestHandler имеет следующие методы:

ручка ()

Вызывает handle_one_request () один раз (или, если постоянные соединения включен, несколько раз) для обработки входящих HTTP-запросов.Вам следует никогда не нужно его отменять; вместо этого реализуйте соответствующий do _ * () методы.

handle_one_request ()

Этот метод проанализирует и отправит запрос соответствующему do _ * () метод. Вам никогда не придется его отменять.

handle_expect_100 ()

Когда сервер, совместимый с HTTP / 1.1, получает сообщение Expect: 100-continue заголовок запроса, он отвечает 100 Continue, за которым следует 200 ОК заголовки.Этот метод можно переопределить, чтобы вызвать ошибку, если сервер не хочу, чтобы клиент продолжил. Например, сервер может выбрать отправку 417 Ожидание не выполнено в качестве заголовка ответа и вернуть False.

Новое в версии 3.2.

send_error ( код , сообщение = нет )

Отправляет и регистрирует полный ответ об ошибке клиенту. Цифровой код указывает код ошибки HTTP с сообщением как необязательный, более конкретный текст.А отправляется полный набор заголовков, за которым следует текст, составленный с использованием error_message_format переменная класса.

send_response (код , сообщение = нет )

Добавляет заголовок ответа в буфер заголовков и регистрирует принятые запрос. Строка ответа HTTP записывается во внутренний буфер, за которыми следуют заголовки Server и Date . Значения этих двух заголовков взяты из version_string () и date_time_string () соответственно.Если сервер не намереваются отправить любые другие заголовки с помощью метода send_header (), тогда за send_response () должен следовать end_headers () вызов.

Изменено в версии 3.3: заголовки хранятся во внутреннем буфере и end_headers () нужно вызывать явно.

send_header ( ключевое слово , значение )

Добавляет заголовок HTTP во внутренний буфер, который будет записан в выходной поток, когда используется end_headers () или flush_headers () вызван. ключевое слово должно указывать ключевое слово заголовка со значением с указанием его значения. Обратите внимание, что после выполнения вызовов send_header end_headers () ДОЛЖЕН БЫТЬ вызван для завершения операции.

Изменено в версии 3.2: Заголовки хранятся во внутреннем буфере.

send_response_only (код , сообщение = нет )

Отправляет только заголовок ответа, используемый для целей, когда 100 Ответ «Продолжить» отправляется сервером клиенту.Заголовки не буферизируется и отправляется напрямую в выходной поток. Если сообщение не указано, отправляется сообщение HTTP, соответствующее коду ответа .

Новое в версии 3.2.

end_headers ()

Добавляет пустую строку (с указанием конца HTTP-заголовков в ответе) в буфер заголовков и вызывает flush_headers ().

Изменено в версии 3.2: Буферизованные заголовки записываются в выходной поток.

flush_headers ()

Наконец, отправьте заголовки в выходной поток и очистите внутреннюю буфер заголовков.

Новое в версии 3.3.

log_request (код = ‘-‘ , размер = ‘-‘ )

Регистрирует принятый (успешный) запрос. код должен указывать числовой HTTP-код, связанный с ответом. Если размер ответа доступно, то его следует передать как параметр size .

log_error ()

Регистрирует ошибку, когда запрос не может быть выполнен. По умолчанию проходит сообщение для log_message (), поэтому он принимает те же аргументы ( формат и дополнительные значения).

log_message ( формат , )

Записывает произвольное сообщение в sys.stderr. Обычно это отменяется для создания настраиваемых механизмов регистрации ошибок. Формат аргумент — это стандартная строка формата printf, где дополнительные аргументы log_message () применяются в качестве входных данных для форматирования.Клиент IP-адрес, текущая дата и время добавляются к каждому зарегистрированному сообщению.

version_string ()

Возвращает строку версии программного обеспечения сервера. Это комбинация переменные класса server_version и sys_version.

date_time_string ( timestamp = None )

Возвращает дату и время, заданные меткой времени (которая должна быть None или в формат, возвращаемый временем.time ()), отформатированный для сообщения заголовок. Если отметка времени опущена, используется текущая дата и время.

Результат выглядит так: «Вс, 06 ноября 1994, 08:49:37 GMT».

log_date_time_string ()

Возвращает текущую дату и время, отформатированные для регистрации.

address_string ()

Возвращает адрес клиента.

Изменено в версии 3.3: Раньше выполнялся поиск по имени.Чтобы избежать разрешения имен задержки, теперь он всегда возвращает IP-адрес.

класс http.server.SimpleHTTPRequestHandler ( запрос , client_address , server )

Этот класс обслуживает файлы из текущего каталога и ниже, напрямую отображение структуры каталогов на HTTP-запросы.

Большая часть работы, например анализ запроса, выполняется базовым классом. BaseHTTPRequestHandler.Этот класс реализует do_GET () и функции do_HEAD ().

Следующие элементы определены как атрибуты уровня класса SimpleHTTPRequestHandler:

server_version

Это будет «SimpleHTTP /» + __version__, где __version__ — определяется на уровне модуля.

extension_map

Суффиксы отображения словаря в типы MIME. По умолчанию обозначается пустой строкой и считается приложение / октет-поток.Отображение используется без учета регистра, и поэтому должен содержать только ключи в нижнем регистре.

Класс SimpleHTTPRequestHandler определяет следующие методы:

do_HEAD ()

Этот метод обслуживает тип запроса ‘HEAD’: он отправляет заголовки ему отправит эквивалентный запрос GET. См. Do_GET () для более полного объяснения возможных заголовков.

do_GET ()

Запрос отображается в локальный файл путем интерпретации запроса как путь относительно текущего рабочего каталога.

Если запрос был сопоставлен с каталогом, каталог проверяется на наличие файл с именем index.html или index.htm (в указанном порядке). Если найден, возвращается содержимое файла; в противном случае создается список каталогов вызывая метод list_directory (). Этот метод использует os.listdir () для сканирования каталога и возвращает ошибку 404 ответ, если listdir () не работает.

Если запрос был сопоставлен с файлом, он открывается, и его содержимое вернулся. Любое исключение OSError при открытии запрошенного файла сопоставлен с ошибкой 404 «Файл не найден».В противном случае контент тип угадывается путем вызова метода guess_type (), который в свою очередь использует переменную extensions_map .

Выводится заголовок Content-type: с предполагаемым типом содержимого, за которым следует заголовок Content-Length: с размером файла и Заголовок Last-Modified: со временем модификации файла.

Затем следует пустая строка, обозначающая конец заголовков, а затем выводится содержимое файла. Если MIME-тип файла начинается с текст / файл открывается в текстовом режиме; в противном случае используется двоичный режим.

Например, см. Реализацию функции test () вызов в модуле http.server.

Класс SimpleHTTPRequestHandler можно использовать в следующих способ, чтобы создать очень простой веб-сервер, обслуживающий файлы относительно текущий каталог.

 импорт http.server
импортировать сервер сокетов

ПОРТ = 8000

Обработчик = http.server.SimpleHTTPRequestHandler

httpd = socketserver.TCPServer (("", ПОРТ), Обработчик)

print ("обслуживание в порту", ПОРТ)
httpd.serve_forever ()
 

http.server также может быть вызван напрямую с помощью -m переключатель интерпретатора с аргументом номера порта. Похожий на в предыдущем примере это обслуживает файлы относительно текущего каталога.

 питон -m http.server 8000
 
класс http.server.CGIHTTPRequestHandler ( запрос , client_address , server )

Этот класс используется для обслуживания файлов или вывода сценариев CGI из текущий каталог и ниже.Обратите внимание, что сопоставление иерархической структуры HTTP с структура локальных каталогов такая же, как в SimpleHTTPRequestHandler.

Примечание

CGI-скрипты, запущенные классом CGIHTTPRequestHandler, не могут выполняться перенаправляет (код HTTP 302), потому что код 200 (далее следует вывод сценария) отправляется до выполнения сценария CGI. Это предопределяет статус код.

Однако класс будет запускать сценарий CGI вместо того, чтобы обслуживать его как файл, если он догадывается, что это сценарий CGI. Используются только CGI на основе каталогов — другая распространенная конфигурация сервера — обрабатывать специальные расширения как обозначающие сценарии CGI.

Функции do_GET () и do_HEAD () изменены для запуска сценариев CGI. и обслуживать вывод вместо обслуживания файлов, если запрос приводит к где-то ниже пути cgi_directories.

CGIHTTPRequestHandler определяет следующий член данных:

cgi_directories

По умолчанию это [‘/ cgi-bin’, ‘/ htbin’] и описывает каталоги для рассматривать как содержащие сценарии CGI.

CGIHTTPRequestHandler определяет следующий метод:

do_POST ()

Этот метод обслуживает тип запроса POST, разрешенный только для CGI. скрипты.Ошибка 501 «Возможны только сценарии POST в CGI», выводится при попытке для POST на URL-адрес, отличный от CGI

Обратите внимание, что сценарии CGI будут запускаться с UID пользователя nobody в целях безопасности причины. Проблемы с CGI-скриптом будут переведены на ошибку 403.

CGIHTTPRequestHandler можно включить в командной строке, передав параметр —cgi .:

 питон -m http.server --cgi 8000
 

Статический веб-сервер на Python

Когда я начал работать над Code And Talk сайт Я не знал, что строил.На самом деле я начал со сбора данных о подкастах. Я не использовал базу данных, но хотел иметь какой-то дисплей.

Поэтому вместо создания динамического сайта я выбрал статический сайт, который может быть сгенерирован скрипт и несколько шаблонов.

Такой сайт можно обслуживать с помощью Apache или Nginx, но на моей машине разработки у меня их нет. Поэтому я написал небольшой веб-сервер, обслуживающий статические файлы.

примеров / python / static_server.ру

#! / usr / bin / env python3
из http.server импортировать HTTPServer, BaseHTTPRequestHandler
импорт ОС


класс StaticServer (BaseHTTPRequestHandler):

    def do_GET (сам):
        root = os.path.join (os.path.dirname (os.path.dirname (os.path.abspath (__ file__))), 'html')
        #print (self.path)
        если self.path == '/':
            имя файла = корень + '/index.html'
        еще:
            имя файла = корень + собственный путь

        self.send_response (200)
        если имя файла [-4:] == '.css ':
            self.send_header ('Content-type', 'текст / css')
        elif имя файла [-5:] == '.json':
            self.send_header ('Content-type', 'application / javascript')
        elif filename [-3:] == '.js':
            self.send_header ('Content-type', 'application / javascript')
        elif filename [-4:] == '.ico':
            self.send_header ('Тип содержимого', 'изображение / значок x')
        еще:
            self.send_header ('Content-type', 'текст / html')
        self.end_headers ()
        с open (filename, 'rb') как fh:
            html = fh.читать()
            #html = байты (html, 'utf8')
            self.wfile.write (HTML)

def run (server_class = HTTPServer, handler_class = StaticServer, порт = 8000):
    server_address = ('', порт)
    httpd = server_class (server_address, handler_class)
    print ('Запуск httpd на порту {}'. формат (порт))
    httpd.serve_forever ()

бегать()

# vim: expandtab
 
 

Все, что вам нужно знать о серверах как разработчику на Python

## Введение Серверы меня немного запутали. Я уверен, что найдется много разработчиков Python, которые попадают в такое же затруднительное положение.Итак, позвольте мне попытаться раскрыть туман замешательства, поделившись всем, что я знаю о серверах.

## HTTP: протокол, который управляет всемирной паутиной! HTTP (протокол передачи гипертекста) — это протокол связи, который используется для отправки и получения веб-страниц и других файлов данных в Интернете. Это набор правил и спецификаций, которые регулируют передачу веб-страниц и других файлов данных в Интернете.
Браузер является HTTP-клиентом, поскольку он отправляет запросы на HTTP-сервер (веб-сервер), который затем отправляет ответы обратно клиенту.Стандартный (и по умолчанию) порт для прослушивания HTTP-серверов — 80, хотя они могут использовать любой порт. Эта статья очень хорошо объясняет HTTP. Пожалуйста, пройдите через это.

Если вы хотите быть фанатиком, проверьте спецификации HTTP 1.1, которые были заменены несколькими RFC (7230-7237). Найдите эти RFC на ietf

## HTTP-сервер Итак, HTTP-запрос и ответ имеют формат! Когда пользователь заходит на веб-сайт, его браузер устанавливает соединение с веб-сервером сайта (это называется запросом).Сервер ищет файл в файловой системе и отправляет его обратно в браузер пользователя, который отображает его (это ответ). Примерно так работает базовый протокол HTTP. Кажется простым?

Динамические веб-сайты основаны не на файлах в файловой системе, а на программах, которые запускаются веб-сервером при поступлении запроса и которые генерируют контент, который возвращается пользователю. Они могут делать всевозможные полезные вещи, например отображать сообщения на доске объявлений, показывать вашу электронную почту, настраивать программное обеспечение или просто отображать текущее время.

Независимо от того, как был реализован клиент или сервер, всегда будет способ сформировать действительный HTTP-запрос для работы клиента, и точно так же сервер должен быть способен понимать отправленные ему HTTP-запросы и формировать действительный HTTP-запрос. Ответы на все поступившие HTTP-запросы. И клиентские, и серверные машины также должны быть оснащены возможностью установления соединения друг с другом (в данном случае это будет надежное TCP-соединение), чтобы иметь возможность передавать HTTP-запросы (клиент -> сервер) и HTTP. Ответы (сервер -> клиент).

Сервер Http (программа) примет этот запрос и позволит вашему сценарию python получить метод запроса Http и URI. HTTP-сервер будет обрабатывать множество запросов от изображений и статических ресурсов. А как насчет динамически генерируемых URL-адресов?

  @ app.route ('\ displaynews \ ', methods = ['GET'])
  

Вы могли использовать этот декоратор в Flask. Flask — это микрофреймворк для Python. Flask сопоставит этот маршрут с запросом из браузера.Но как flask разбирает http-запрос из браузера? Http-сервер передает динамически сгенерированные URL-адреса серверу приложений. Ого! погоди …. Какие сейчас серверы приложений?

Apache HTTPD и nginx — два распространенных веб-сервера, используемых с python.

## Серверы приложений Большинство HTTP-серверов написаны на C или C ++, поэтому они не могут выполнять код Python напрямую — необходим мост между сервером и программой. Эти мосты или, скорее, интерфейсы определяют, как программы взаимодействуют с сервером.Это сервер приложений. Динамически генерируемые URL-адреса передаются с веб-сервера на сервер приложений. Серверы приложений соответствуют URL-адресу и запускают сценарий для этого маршрута. Затем он возвращает ответ веб-серверу, который формулирует HTTP-ответ и возвращает его клиенту.

Существует множество серверов приложений для python. По этой ссылке перечислены различные серверы приложений.

Изначально разработчики python использовали низкоуровневые шлюзы для развертывания.

Общий интерфейс шлюза (CGI)

Этот интерфейс, который чаще всего называют «CGI», является самым старым и поддерживается практически каждым веб-сервером из коробки. Программы, использующие CGI для связи со своим веб-сервером, должны запускаться сервером для каждого запроса. Таким образом, каждый запрос запускает новый интерпретатор Python, для запуска которого требуется некоторое время, что делает весь интерфейс пригодным для использования только в ситуациях с низкой нагрузкой.

Если вы хотите научиться писать. Следуйте этому руководству Дж. М. Маршалла.

#### mod_python mod_python — это модуль HTTP-сервера Apache, который интегрирует язык программирования Python с сервером. В течение нескольких лет, в конце 1990-х и начале 2000-х, Apache, настроенный с помощью mod_python, запускал большинство веб-приложений Python. Однако mod_python не был стандартной спецификацией. При использовании mod_python было много проблем. Требовался согласованный способ выполнения кода Python для веб-приложений.

FastCgi и SCGI были другими низкоуровневыми шлюзами, используемыми для развертывания.Они пытались решить проблему производительности CGI.

Эти низкоуровневые интерфейсы шлюза не зависят от языка.

## Рост WSGI Сервер интерфейса шлюза веб-сервера (WSGI) реализует сторону веб-сервера интерфейса WSGI для запуска веб-приложений Python. WSGI масштабируется и может работать как в многопоточных, так и в многопроцессорных средах. Мы также можем писать промежуточное ПО с помощью WSGI. Промежуточное ПО полезно для обработки сеансов, аутентификации и многого другого. Вы можете прочитать о том, как написать свою реализацию WSGI, в блоге Армина.Сравнение различных реализаций WSGI приведено по этой ссылке.

#### Gunicorn и uWSGI Gunicorn и uWSGI — два разных сервера приложений.
Gunicorn ‘Green Unicorn’ — это HTTP-сервер Python WSGI для UNIX. Его очень просто настроить, он совместим со многими веб-фреймворками и работает довольно быстро. В этой статье от digitalocean показано, как настроить Gunicorn с помощью nginx.

uWSGI — еще один вариант для сервера приложений. uWSGI — это высокопроизводительный и мощный сервер WSGI.В uWSGI доступно множество вариантов конфигурации. В этой статье от digitalocean показано, как настроить uWSGI с nginx.

## Apache против Nginx Антурис довольно четко объяснил различия между ними в своем блоге. В этом посте объясняется, как работают apache и nginx.

Суммируем:

  1. Apache создает процессы и потоки для обработки дополнительных подключений. В то время как Nginx считается управляемым событиями, асинхронным и неблокирующим.
  2. Apache мощный, но Nginx быстрый.Nginx быстрее обрабатывает статический контент.
  3. Nginx включает расширенные возможности балансировки нагрузки и кэширования.
  4. Nginx намного легче Apache

Organicagency проверил производительность Apache и nginx. Результаты доступны здесь

## Что я использую Я использую Nginx, потому что он быстрый, легкий, и я считаю, что его настройка очень проста. Gunicorn очень просто настроить. Так что я использую пулемет. uWsgi также часто используется вместо gunicorn.

Расскажите, пожалуйста, что вы предпочитаете для своих приложений на Python?

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Theme: Overlay by Kaira Extra Text
Cape Town, South Africa