Сучасний Торнадо: розподілене хостинг картинок в 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/
 
 Шаблон
 
<!DOCTYPE html>
<html>
    <h1>Upload an image</h1>
    <form action="" method="post" enctype="multipart/form-data">
        <input type="file" name="file" accept="image/*" onchange="javascript:this.form.submit()">
    </form>

    <h2>Recent uploads</h2>
    {% for file in files %}
        {% set url = reverse_url('show_image', file['_id']) %}
        <a href="{{ url }}"><img src="{{ url }}" style="max-width: 50px;"></a>
    {% end %}
</html>

Тут вам все повинно бути знайоме по django або jinja. Єдина відмінність:
end
замість
endfor
:)
 
 Результат
Отже ми отримали швидкий, масшабіруемий, асинхронний по своїй суті, але написаний в псевдо-синхронному стилі хостинг картинок. А головне, тепер ви знаєте як влаштовані: роутинг, обробники запитів і шаблони в tornado . А так же вмієте асинхронно працювати з mongodb і gridfs зокрема.
 
 Але…
Ви напевно помітили одне вузьке місце:
file = self.request.files['file'][0]
. Так, дійсно, ми вантажимо весь файл зображення в пам'ять перш ніж записати його в базу. І ви напевно, роздумуєте що можна скористатися чимось типу NginxHttpUploadModule . Однак тепер це можна зробити і засобами tornado: tornado.web.stream_request_body . Можливо, це ми і зробимо в одному з наступних уроків.
 
 Посилання
  
 Ваша думка
Чи сподобалося? Чи варто продовжувати? Виправлення? Побажання?
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.