Современный Торнадо: распределённый хостинг картинок в 30 строк кода
Впервые слышите о tornado? Слышали, но боялись асинхронности? Смотрели на него более полугода назад? Тогда я посвящаю эту статью вам :)ПодготовкаПисать будем на третьем питоне. Если он не установлен, советую воспользоваться pyenv. Кроме tornado нам понадобится motor — асинхронный драйвер к mongodb: pip3 install tornado motor Импортируем необходимые модули import bson import motor from tornado import web, gen, ioloop Подключаемся к gridfs Как распределённое хранилище будем использовать gridfs: db = motor.MotorClient ().habr_tornado gridfs = motor.MotorGridFS (db) В первой строке мы подключаемся к mongodb и выбираем базу 'habr_tornado'. Далее подключаемся к gridfs (по умолчанию это будет коллекция fs).Upload handler class UploadHandler (web.RequestHandler): @gen.coroutine def get (self): files = yield gridfs.find ({}).sort («uploadDate», -1).to_list (20) self.render ('upload.html', files=files)
@gen.coroutine def post (self): file = self.request.files['file'][0] gridin = yield gridfs.new_file (content_type=file.content_type) yield gridin.write (file.body) yield gridin.close () self.redirect ('') Мы относледовались от tornado.web.RequestHandler. И теперь переопределяя методы get и post пишем обработчики соответствующих http запросов.Декоратор tornado.gen.coroutine позволяет вместо асинхронных колбэков использовать генераторы. Сточка files = yield gridfs … визуально мало чем отлечается от синхронного files = gridfs. Но функциональное различие огромно. В случае yield произойдёт асинхронный запрос к базе и ожидание его завершания. То есть пока база данных будет «думать», сайт сможет заниматься обработкой других запросов.
Итак в методе get, мы асинхронно получаем из gridfs мета-информацию о последних загруженных файлах. И направляем её в шаблон.
В методе post мы достаём отправленный (с помощью формы отрисованной в шаблоне) файл изображения. Затем асинхронно открыаем gridfs-файл, сохраняем туда картинку и закрываем его. После этого делаем редирект на ту же страницу для отображения обновлённого списка файлов.
ShowImageHandler Теперь нам нужно достать из gridfs и отобразить полученное изображение: class ShowImageHandler (web.RequestHandler): @gen.coroutine def get (self, img_id): try: gridout = yield gridfs.get (bson.objectid.ObjectId (img_id)) except (bson.errors.InvalidId, motor.gridfs.NoFile): raise web.HTTPError (404) self.set_header ('Content-Type', gridout.content_type) self.set_header ('Content-Length', gridout.length) yield gridout.stream_to_handler (self) Здесь мы обрабатываем только GET хттп запрос. Сначала мы асинхронно получаем файл из gridfs по id. Этот id уникален и был автоматически сгенерирован при сохрании изображения в UploadHandler. Если в процессе возникают исключения (некорректный id или отсутствует файл) — показываем 404-ю страницу. Далее устанавливаем соответствующие заголовки, чтобы браузер идентифицировал ответ как изображение. И асинхронно отдаём тело картинки.
Роутинг Для привязки наших обработчиков (UploadHandler и ShowImageHandler) к url, создадим экземпляр tornado.web.Application: app = web.Application ([ web.url (r'/', UploadHandler), web.url (r'/imgs/([\w\d]+)', ShowImageHandler, name='show_image'), ]) Параметром мы передаём список описывающий отображение url-регулярок на их обработчики. Группа регулярки ([\w\d]+) как раз и будет передаваться в ShowImageHandler.get как img_id. А параметр name='show_image' мы будем использовать в шаблоне для генерации урл.Запускаем сервер app.listen (8000) ioloop.IOLoop.instance ().start () Теперь результат можно наблюдать в браузере: http://localhost:8000/Шаблон
Upload an image