Backend разработка
Смаль Дмитрий[email protected]
Что мы научимся делать? Обрабатывать 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
print "Content-type: text/html"
print "Status: 200"
print ""
print "<h1>Hello, world!</h1>"
import os
import sys
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 ""
Передача GET параметров<a href=”/hello.cgi?
name=me&greeting=hello”/>Say hello</a>
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=name=me&greeting=hello
import sys
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>
<a href=”/hello.cgi?name=Boris&name=Ivan”>здорово</a>
<a href=”/hello.cgi?name=%D0%B8%D0%BC%D1%8F”>привет</a>
CGI библиотеки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()
Обработка входных данных
1) Валиадция (regex, code)
if not re.match('[a-z]+', form[“name”]):
raise BaseException(“panic”)
2) Очистка
story = re.sub('<[^>]+>', ' ', form[“story”])
3) Экранирование
story = form[“story”]
re.sub('<', '<', story)
re.sub('>', '>', story)
Шаблонизаторы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>
Структура страницы
Подшаблоны (includes)
{% 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>HEADER</div>
<table><tr>
<td>LEFT</td>
<td>
{% block content %} CONTENT {% endblock %}</td>
<td>RIGHT</td>
</tr></table>
<div>FOOTER</div>
Наследование (layouts)
<!-- page.html –->
{% extends 'base.html' %}
{% block content %}
<h1>{{ user.name }}</h1>
<div>{{ block.super }}</div>
{% endblock %}
СУБД, SQL
INSERT
UPDATE
DELETE
SELECT
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 в pythonimport 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) Переменные, разделение на несколько файлов
Задача №1. Листинг объектов
1) Параметры: фильтрация, сортировка, номер страницы
2) /images/?order=created&page=3&limit=10
3) Результат работы скрипта: список объектов для данной страницы и paginator
4) paginator – представляет положение в списке страниц
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 = form.getfirst('page', 1)
limit = form.getfirst('limit', 10)
sql = 'select * from images 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) Отображение ошибок и результата действия
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.cgiid=%s&res=updated' % form['id'])
catch BaseException, e:
redirect('/cgi-bin/object.cgi?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.cgi”>
{% 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. Wizard1) 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
1) Установка
Set-Cookie: name=val; path=/; domain=domain.ru; expires=Tue, 20 Mar 2012 11:52:54 GMT
2) Возврат
Cookie: name=val;name2=val2;is_visited=2011-13-15
Сессии1) Ключ сессии – в cookie
2) Данные – на сервере (memcached, database, files)
Cookie с помощью Python CGI1) Установка
import Cookie
cookie = Cookie.SimpleCookie()
cookie['name'] = 'val'
cookie['name']['path'] = '/path'
print cookie
2) Получение
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) последовательное исполнение
Минусы и способы решения:
1) смешение кода и HTML → шаблонизаторы
2) повторение логики → вынесение кода в библиотеки
3) pretty urls → RewriteEngine
4) производительность (fork, exec, parse, db.connect) → кеширование кода (FastCGI, mod_perl etc)
Pretty URLs
URL: /profile/mail/my
Скрипт: /cgi-bin/profile.cgi?email=my@ mail.ru
location ~* ^/profile/\w+/\w+/?$ {
rewrite ^/profile/(\w+)/(\w+)/?
/cgi-bin/profile.cgi?email=$2@$1.ru break;
proxy_pass http://backend;
}
КешированиеЧто такое кеширование ? Виды кешей:
1) HTTP cache - сокращает загрузку статики
2) proxy cache – кеш страницы целиком
(либо части, подключаемой через ssi)
3) buffer cache – кеширование данных в OS
4) memcached – распределенный кеш в памяти
5) генерация статических файлов
6) отложенное выполнение. crond.
Домашняя работа
1) реализовать calc_paginator
2) написать cgi скрипт для проверки. Список объектов – числа Фибоначчи до 10000
1) реализовать функцию redirect
2) написать cgi скрипт для проверки.
Например аналог bit.ly со статическими настройками.
Recommended