Upload
technopark
View
174
Download
0
Embed Size (px)
Citation preview
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
CGI
Запрос:
● Параметры запроса – environ
● Тело запроса – stdin
● URI запроса – QUERY_STRING или argv[1]
Ответ:
● Тело ответа (с заголовками) - stdout
● Ошибки выполнения - stderr
● HTTP код – через псевдозаголовок Status
● JavaScript – свой сервер (NodeJS)
CGI – окружение
Переменные окружения
REQUEST_METHOD – метод (GET, POST, …)
REQUEST_URI – строка запроса
QUERY_STRING - строка параметров
REMOTE_ADDR – ip адрес клиента
SCRIPT_NAME – имя текущего скрипта
HTTP_COOKIE – заголовок Cookie:
HTTP_REFERER – заголовок Referer:
CGI скрипт
#!/usr/bin/python2.7
import osimport 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-
● По первой строчке вывода скрипта
В чем отличие от обычных CGI ?
Обработка 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 cgiimport 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('<', '<', story)re.sub('>', '>', 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/pythonimport cgiimport psycopg2import 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/pythonimport 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) Проверять данные пользователя
а) на сервере – безопасность программы
б) на клиенте – удобство пользователя
5) Выделять неправильно введеные поля
Задача 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 Cookiecookie = Cookie.SimpleCookie()cookie['name'] = 'val'cookie['name']['path'] = '/path'
Установка:
Получение:
Установка:
import Cookiecookie = 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)
WSGI – pep 3333
def application(environ, start_response): start_response('200 OK', [
('Content-Type', 'text/plain') ]) yield 'Hello World\n'
def application(environ, start_response): start_response('200 OK', [ ('Content-Type', 'text/plain')]) return [ 'Hello World\n' ]
Спасибо за вниманиеДмитрий Смаль, [email protected]