Backend разработка
Дмитрий Смаль
Что мы научимся делать?
Типичные задачи
● Обрабатывать GET и POST запросы
● Выводить HTML при помощи шаблонов
● Хранить данные в СУБД
● Отображение списка объектов
● Изменение (редактирование) объектов
● Wizards: последовательности страниц
Языки и технологии
Статические (+/-):
● СС++ модули к Web серверам.
● Java – Servlets, ApplicationServers
Динамически (+/-):
● Perl – CGI, mod_perl, PSGI
● PHP – mod_php, FastCGI (FPM)
● Ruby – rack, свой сервер (mongrel)
● Python – WSGI, свой сервер (Tornado)
● JavaScript – свой сервер (NodeJS)
CGI скрипт
#!/usr/bin/python2.7
import os
import sys
print "Content-type: text/html"
print "Status: 200"
print ""
print "<h1>Hello, world!</h1>"
for k, v in os.environ.items():
print "%s = %s<br>" % (k, v)
print >> sys.stderr, "Nice to meet you"
nph - CGI скрипт
#!/usr/bin/python2.7
print "HTTP/1.0 301 Found"
print "Location: http://go.mail.ru/"
print "Set-Cookie: name=value"
print ""
Как сервер определяет nph скрипт ?
● По имени файла nph-
● По первой строчке вывода скрипта
Обработка HTTP запросов
GET параметры
<a href=”/hello.cgi?name=me&greeting=hello”/>Hello!</a>
Гиперссылка
Переменная окружения
CGI скрипт
QUERY_STRING=name=me&greeting=hello
get_params = {}
qs = os.environ['QUERY_STRING']
for pair in qs.split('&'):
key, value = pair.split('=')
get_params[key] = value
POST параметры
<form method=”post” action=”/hello.cgi”>
<input name=”name” value=”me”/>
<input name=”greeting” value=”hi”/>
<input type=”submit”/>
</form>
Форма
Стандартный поток ввода STDIN
CGI скрипт
name=me&greeting=hello
qs = sys.stdin.read()
...
Файлы и не-ASCII
<form method=”post” action=”/hello.cgi”
enctype=”multipart/form-data”>
<input name=”name” value=”me”/>
<input name=”pic” type=”file”/>
<input type=”submit”/>
</form>
multipart/form-data
URI percent encoding
<a href=”/hello.cgi?name=%D0%B8%D0%BC%D1%8F”>привет</a>
Библиотеки CGI
#!/usr/bin/python2.7
import cgi
import cgitb
cgitb.enable()
form = cgi.FieldStorage()
if “greeting” not in form:
raise BaseException(“can't work”)
greeting = form[“greeting”]
names = form.getlist(“name”)
pic = forms[“pic”].file.read()
Обработка форм
if not re.match('[a-z]+', form[“name”]):
raise BaseException(“panic”)
1) Валидация (regex, code)
2) Очистка
3) Экранирование
story = re.sub('<[^>]+>', ' ', form[“story”])
story = form[“story”]
re.sub('<', '&lt;', story)
re.sub('>', '&gt', story)
Работа с базой данных
СУБД: таблицы
СУБД: SQL
INSERT INTO users (name, age)
VALUES ('petr', 10), ('masha', 25);
UPDATE users SET age = 10 WHERE name = 'petr';
DELETE FROM users WHERE name = 'masha';
SELECT * FROM users WHERE age > 10;
SELECT * FROM users WHERE name = 'masha';
SELECT max(age) FROM users;
SQL в python
import MySQLdb
db = MySQLdb.connect(**options)
cursor = db.cursor()
cursor.execute(“update users set age = age+1 ” 
”where name = ?”, form[“name”])
context = {}
cursor.execute(“select * from users”)
context['friends'] = cursor.fetchall()
cursor.execute(“select * from users where name = ?”,
form[“name”])
context['user'] = cursor.fetchone()
db.close()
Конфигурация
Конфигурация
1) hardcode. Настройки зашиты в код приложения
2) script. Настройки представляют собой скрипт на
целевом ЯП
3) YAML, XML, ini, Config::Apache
4) Переменные, разделение на несколько файлов
Генерация HTML страниц
Шаблонизаторы
print “<html><body><h1>” 
“%s</h1></body></html>” % name
VS
context = {
'user' : get_user(form['name']),
'friends' : get_friends(form['name'])
}
print render('tpl/home.html', context)
Шаблоны
<body>
<h1>{{ user.name }}</h1>
{% if user.sex == 'male' %}
<h2>{{ user.age }}</h2>
{% endif %}
{% for f in friends %}
<a href=”mailto:{{ f.email }}”>{{ f.name }}</a>
<p>{{ f.about|linebreaks }}</p>
{% endfor %}
</body>
Структура страницы
Подшаблоны
{% include 'inc/header.html' %}
<table><tr>
<td>{% include 'inc/left.html' %}</td>
<td> CONTENT </td>
<td>{% include 'inc/right.html' %}</td>
</tr></table>
{% include 'inc/footer.html' %}
Наследование (layouts)
<!-- base.html -->
<div>{% block header %} HEADER {% endblock %}</div>
<table><tr>
<td>LEFT</td>
<td>{% block content %} CONTENT {% endblock %}</td>
<td>RIGHT</td>
</tr></table>
<div>{% block footer %} FOOTER {% endblock %}</div>
Наследование (layouts)
<!-- page.html –->
{% extends 'base.html' %}
{% block header %}
Custom header
{% endblock %}
{% block content %}
<h1>{{ user.name }}</h1>
<div>{{ block.super }}</div>
{% endblock %}
Задача 1.
Листинг объектов
Листинг объектов
1) Параметры: фильтрация, сортировка, номер
страницы
2) /images/?order=created&page=3&limit=10
3) Результат работы скрипта: список объектов для
данной страницы и paginator
4) paginator – представляет положение в списке
страниц
Листинг объектов
#!/usr/bin/python
import cgi
import psycopg2
import settings
db = psycopg2.connect(**settings.db)
cursor = db.cursor()
form = cgi.FieldStorage()
order = form.getfirst('order', 'created')
if order not in ('created', 'size'):
raise BaseException('oops')
page = int(form.getfirst('page', 1))
limit = int(form.getfirst('limit', 10))
sql = 'select * from img order by %s limit %d offset %d'
% (order, limit, limit * (page – 1))
cursor.execute(sql)
Листинг объектов
context = {}
context['images'] = cursor.fetchall()
cursor.execute('select count(*) as cnt from images')
total = cursor.fetchone()[0]['cnt']
context['pager'] = calc_paginator(total, page, limit)
db.close()
print “Status: 200”
print “Content-Type: text/html”
print “”
print render('/images.html', context)
Paginator
Аргументы: total, page, limit
Результат:
{
'total': 100, 'page': 5, 'limit': 10,
'next_page': 6, 'prev_page': 4,
'next_page10': 10, 'prev_page10': 1,
'first_page':1, 'last_page': 10,
'pages' : [3, 4, 5, 6, 7]
}
Задача 2.
Изменение объекта
Изменение объекта
1) Два режима работы: отображение формы и
обновление объекта
2) Разделение по методу HTTP (GET | POST).
Кеширование запросов
3) Использование спец. параметра (action)
4) Отображение ошибок и результата действия
Изменение объекта
#!/usr/bin/python
import cgi; import psycopg2; import settings; import os
db = psycopg2.connect(**settings.db)
cursor = db.cursor()
form = cgi.FieldStorage()
if os.environ['HTTP_METHOD'] == 'POST”:
try:
cursor.execute('update users set name = ? where id = ?',
form['name'], form['id'])
redirect('/cgi-bin/object?id=%s&res=updated' % form['id'])
catch BaseException, e:
redirect('/cgi-bin/object?id=%s&fail=fail' % form['id'])
else:
context = {}
cursor.execute('select * from users where id = ?', form['id'])
context['object'] = cursor.fetchone()
render('object.html', context)
Изменение объекта
<form method=”POST” action=”/cgi-bin/object”>
{% if res %}
<p style=”color: green”>Объект обновлен:{{ res }}</p>
{% endif %}
{% if fail %}
<p style=”color: red”>Ошибка: {{ fail }}</p>
{% endif %}
<input type=”hidden” name=”id”
value=”{{ object.id }}”>
<input type=”text” name=”name”
value=”{{ object.name }}”>
<input type=”submit”>
</form>
Best Practice
1) Разделять методы GET – получение, POST –
обновление данных
2) Сообщать об ошибках и успехе
3) Проверять данные пользователя
а) на сервере – безопасность программы
б) на клиенте – удобство пользователя
4) Выделять неправильно введеные поля
Задача 3.
Wizard
Wizard
1) Statefull vs Stateless.
2) Как передать данные между страницами?
выбор товара → информация о клиенте
→ место доставки → подтверждение
3) Варианты:
- через URL
- через скрытые поля
- через Cookie
- через сессии
Скрытые поля
<!-- page2.html →
<form method=”POST” action=”/page3.html”>
<input type=”hidden” name=”item_id” value=”1”>
<input type=”hidden” name=”ammount” value=”4”>
<input type=”text” name=”name” value=””>
<input type=”text” name=”phone” value=””>
<input type=”submit”>
</form>
Cookie и Сессии
Set-Cookie: name=val; path=/; domain=domain.ru;
expires=Tue, 20 Mar 2012 11:52:54 GMT
Cookie: name=val;name2=val2;is_visited=2011-13-15
Cookie:
Сессии:
1) Ключ сессии – в cookie
2) Данные – на сервере (memcached, database, files)
Cookie в Python
import Cookie
cookie = Cookie.SimpleCookie()
cookie['name'] = 'val'
cookie['name']['path'] = '/path'
Установка:
Получение:
Установка:
import Cookie
cookie = Cookie.SimpleCookie()
cookie.load('a=b;c=d')
for name in cookie:
print '%s => %s' % (name, cookie[name])
Достоинства CGI
1) простая концепция “скриптов”
2) стандарт – поддержка на любом хостинге
3) последовательное исполнение
Недостатки CGI
Плюсы:
1) смешение кода и HTML → шаблонизаторы
2) повторение логики → вынесение кода в
библиотеки
3) pretty urls → RewriteEngine
4) производительность (fork, exec, parse, db.connect)
→ кеширование кода (FastCGI, mod_perl etc)
Спасибо за внимание
Дмитрий Смаль, smal@corp.mail.ru

Web осень 2012 лекция 4

  • 1.
  • 2.
    Что мы научимсяделать? Типичные задачи ● Обрабатывать GET и POST запросы ● Выводить HTML при помощи шаблонов ● Хранить данные в СУБД ● Отображение списка объектов ● Изменение (редактирование) объектов ● Wizards: последовательности страниц
  • 3.
    Языки и технологии Статические(+/-): ● СС++ модули к Web серверам. ● Java – Servlets, ApplicationServers Динамически (+/-): ● Perl – CGI, mod_perl, PSGI ● PHP – mod_php, FastCGI (FPM) ● Ruby – rack, свой сервер (mongrel) ● Python – WSGI, свой сервер (Tornado) ● JavaScript – свой сервер (NodeJS)
  • 4.
    CGI скрипт #!/usr/bin/python2.7 import os importsys print "Content-type: text/html" print "Status: 200" print "" print "<h1>Hello, world!</h1>" for k, v in os.environ.items(): print "%s = %s<br>" % (k, v) print >> sys.stderr, "Nice to meet you"
  • 5.
    nph - CGIскрипт #!/usr/bin/python2.7 print "HTTP/1.0 301 Found" print "Location: http://go.mail.ru/" print "Set-Cookie: name=value" print "" Как сервер определяет nph скрипт ? ● По имени файла nph- ● По первой строчке вывода скрипта
  • 6.
  • 7.
    GET параметры <a href=”/hello.cgi?name=me&greeting=hello”/>Hello!</a> Гиперссылка Переменнаяокружения CGI скрипт QUERY_STRING=name=me&greeting=hello get_params = {} qs = os.environ['QUERY_STRING'] for pair in qs.split('&'): key, value = pair.split('=') get_params[key] = value
  • 8.
    POST параметры <form method=”post”action=”/hello.cgi”> <input name=”name” value=”me”/> <input name=”greeting” value=”hi”/> <input type=”submit”/> </form> Форма Стандартный поток ввода STDIN CGI скрипт name=me&greeting=hello qs = sys.stdin.read() ...
  • 9.
    Файлы и не-ASCII <formmethod=”post” action=”/hello.cgi” enctype=”multipart/form-data”> <input name=”name” value=”me”/> <input name=”pic” type=”file”/> <input type=”submit”/> </form> multipart/form-data URI percent encoding <a href=”/hello.cgi?name=%D0%B8%D0%BC%D1%8F”>привет</a>
  • 10.
    Библиотеки CGI #!/usr/bin/python2.7 import cgi importcgitb cgitb.enable() form = cgi.FieldStorage() if “greeting” not in form: raise BaseException(“can't work”) greeting = form[“greeting”] names = form.getlist(“name”) pic = forms[“pic”].file.read()
  • 11.
    Обработка форм if notre.match('[a-z]+', form[“name”]): raise BaseException(“panic”) 1) Валидация (regex, code) 2) Очистка 3) Экранирование story = re.sub('<[^>]+>', ' ', form[“story”]) story = form[“story”] re.sub('<', '&lt;', story) re.sub('>', '&gt', story)
  • 12.
  • 13.
  • 14.
    СУБД: SQL INSERT INTOusers (name, age) VALUES ('petr', 10), ('masha', 25); UPDATE users SET age = 10 WHERE name = 'petr'; DELETE FROM users WHERE name = 'masha'; SELECT * FROM users WHERE age > 10; SELECT * FROM users WHERE name = 'masha'; SELECT max(age) FROM users;
  • 15.
    SQL в python importMySQLdb db = MySQLdb.connect(**options) cursor = db.cursor() cursor.execute(“update users set age = age+1 ” ”where name = ?”, form[“name”]) context = {} cursor.execute(“select * from users”) context['friends'] = cursor.fetchall() cursor.execute(“select * from users where name = ?”, form[“name”]) context['user'] = cursor.fetchone() db.close()
  • 16.
  • 17.
    Конфигурация 1) hardcode. Настройкизашиты в код приложения 2) script. Настройки представляют собой скрипт на целевом ЯП 3) YAML, XML, ini, Config::Apache 4) Переменные, разделение на несколько файлов
  • 18.
  • 19.
    Шаблонизаторы print “<html><body><h1>” “%s</h1></body></html>”% name VS context = { 'user' : get_user(form['name']), 'friends' : get_friends(form['name']) } print render('tpl/home.html', context)
  • 20.
    Шаблоны <body> <h1>{{ user.name }}</h1> {%if user.sex == 'male' %} <h2>{{ user.age }}</h2> {% endif %} {% for f in friends %} <a href=”mailto:{{ f.email }}”>{{ f.name }}</a> <p>{{ f.about|linebreaks }}</p> {% endfor %} </body>
  • 21.
  • 22.
    Подшаблоны {% include 'inc/header.html'%} <table><tr> <td>{% include 'inc/left.html' %}</td> <td> CONTENT </td> <td>{% include 'inc/right.html' %}</td> </tr></table> {% include 'inc/footer.html' %}
  • 23.
    Наследование (layouts) <!-- base.html--> <div>{% block header %} HEADER {% endblock %}</div> <table><tr> <td>LEFT</td> <td>{% block content %} CONTENT {% endblock %}</td> <td>RIGHT</td> </tr></table> <div>{% block footer %} FOOTER {% endblock %}</div>
  • 24.
    Наследование (layouts) <!-- page.html–-> {% extends 'base.html' %} {% block header %} Custom header {% endblock %} {% block content %} <h1>{{ user.name }}</h1> <div>{{ block.super }}</div> {% endblock %}
  • 25.
  • 26.
    Листинг объектов 1) Параметры:фильтрация, сортировка, номер страницы 2) /images/?order=created&page=3&limit=10 3) Результат работы скрипта: список объектов для данной страницы и paginator 4) paginator – представляет положение в списке страниц
  • 27.
    Листинг объектов #!/usr/bin/python import cgi importpsycopg2 import settings db = psycopg2.connect(**settings.db) cursor = db.cursor() form = cgi.FieldStorage() order = form.getfirst('order', 'created') if order not in ('created', 'size'): raise BaseException('oops') page = int(form.getfirst('page', 1)) limit = int(form.getfirst('limit', 10)) sql = 'select * from img order by %s limit %d offset %d' % (order, limit, limit * (page – 1)) cursor.execute(sql)
  • 28.
    Листинг объектов context ={} context['images'] = cursor.fetchall() cursor.execute('select count(*) as cnt from images') total = cursor.fetchone()[0]['cnt'] context['pager'] = calc_paginator(total, page, limit) db.close() print “Status: 200” print “Content-Type: text/html” print “” print render('/images.html', context)
  • 29.
    Paginator Аргументы: total, page,limit Результат: { 'total': 100, 'page': 5, 'limit': 10, 'next_page': 6, 'prev_page': 4, 'next_page10': 10, 'prev_page10': 1, 'first_page':1, 'last_page': 10, 'pages' : [3, 4, 5, 6, 7] }
  • 30.
  • 31.
    Изменение объекта 1) Дварежима работы: отображение формы и обновление объекта 2) Разделение по методу HTTP (GET | POST). Кеширование запросов 3) Использование спец. параметра (action) 4) Отображение ошибок и результата действия
  • 32.
    Изменение объекта #!/usr/bin/python import cgi;import psycopg2; import settings; import os db = psycopg2.connect(**settings.db) cursor = db.cursor() form = cgi.FieldStorage() if os.environ['HTTP_METHOD'] == 'POST”: try: cursor.execute('update users set name = ? where id = ?', form['name'], form['id']) redirect('/cgi-bin/object?id=%s&res=updated' % form['id']) catch BaseException, e: redirect('/cgi-bin/object?id=%s&fail=fail' % form['id']) else: context = {} cursor.execute('select * from users where id = ?', form['id']) context['object'] = cursor.fetchone() render('object.html', context)
  • 33.
    Изменение объекта <form method=”POST”action=”/cgi-bin/object”> {% if res %} <p style=”color: green”>Объект обновлен:{{ res }}</p> {% endif %} {% if fail %} <p style=”color: red”>Ошибка: {{ fail }}</p> {% endif %} <input type=”hidden” name=”id” value=”{{ object.id }}”> <input type=”text” name=”name” value=”{{ object.name }}”> <input type=”submit”> </form>
  • 34.
    Best Practice 1) Разделятьметоды GET – получение, POST – обновление данных 2) Сообщать об ошибках и успехе 3) Проверять данные пользователя а) на сервере – безопасность программы б) на клиенте – удобство пользователя 4) Выделять неправильно введеные поля
  • 35.
  • 36.
    Wizard 1) Statefull vsStateless. 2) Как передать данные между страницами? выбор товара → информация о клиенте → место доставки → подтверждение 3) Варианты: - через URL - через скрытые поля - через Cookie - через сессии
  • 37.
    Скрытые поля <!-- page2.html→ <form method=”POST” action=”/page3.html”> <input type=”hidden” name=”item_id” value=”1”> <input type=”hidden” name=”ammount” value=”4”> <input type=”text” name=”name” value=””> <input type=”text” name=”phone” value=””> <input type=”submit”> </form>
  • 38.
    Cookie и Сессии Set-Cookie:name=val; path=/; domain=domain.ru; expires=Tue, 20 Mar 2012 11:52:54 GMT Cookie: name=val;name2=val2;is_visited=2011-13-15 Cookie: Сессии: 1) Ключ сессии – в cookie 2) Данные – на сервере (memcached, database, files)
  • 39.
    Cookie в Python importCookie cookie = Cookie.SimpleCookie() cookie['name'] = 'val' cookie['name']['path'] = '/path' Установка: Получение: Установка: import Cookie cookie = Cookie.SimpleCookie() cookie.load('a=b;c=d') for name in cookie: print '%s => %s' % (name, cookie[name])
  • 40.
    Достоинства CGI 1) простаяконцепция “скриптов” 2) стандарт – поддержка на любом хостинге 3) последовательное исполнение
  • 41.
    Недостатки CGI Плюсы: 1) смешениекода и HTML → шаблонизаторы 2) повторение логики → вынесение кода в библиотеки 3) pretty urls → RewriteEngine 4) производительность (fork, exec, parse, db.connect) → кеширование кода (FastCGI, mod_perl etc)
  • 42.