« Работа с базой данных mysql из c++ | Доступ к базам данных из php. Часть 2 » |
ЧПУ (Человеко-понятные УРЛы) в java
Сегодня я расскажу о формировании ЧПУ для java-основанных веб-проектов на примере небольшого веб-магазинчика. Это будет очень простой магазинчик, все данные в котором помещаются в одной таблице. Информация же будет выводиться на нескольких страницах, в виде иерархии разделов каталога и, собственно, товаров внутри раздела.Очевидно, что “Настоящему” программисту нет никакого дела до того, как называется его страница:
index.jsp?sect=food&article=milk&date=freshНапример, такой адрес я назову ППУ – программисто-понятные урлы (yes!, я сделал это первым, я ввел новый термин).
или же
catalog/food/milk/freshА этот стиль адресов “обозвали” еще до меня как ЧПУ (человеко-понятные урлы)
В любом случае, в адресе содержится вся необходимая информация, чтобы понять, что посетитель сайта хочет получить информацию о “молоке”, находящееся в “продуктовом” разделе каталога и, желательно, свежем.
Другое дело, что для посетителя сайта второй вариант адреса более понятен и легче запоминается. Его проще вспомнить спустя пару дней и набрать без ошибок. Интуитивно ясно, что если в адресе, наш посетитель удалит фрагмент “fresh”, то он получит сведения только о молоке, если же удалить и часть “milk”, так чтобы остался “catalog/food”, то для клиента ожидаемо попасть, именно, в раздел со всеми продовольственными товарами. В Интернете иногда встречаются страшные байки о том, что поисковые системы не умеют качественно индексировать адреса страниц с передаваемыми после “?” переменными и их значениями – не верьте, это такой же фольклор как “летающие тарелки” и “каждой советской семье по квартире в 2000 году”. Так что, кроме фактора “usability” других оснований для внедрения ЧПУ нет – а разве этого мало?
Что самое приятное – добавление средств ЧПУ, даже к уже существующему сайту, занимает не более десятка минут. Вам вовсе не нужно перестраивать структуру каталогов свого сайта, даже не нужно переписывать код вашего jsp-файла или сервлета.
Виртуальный (не отображенный в физической структуре дерева каталогов сайта) адрес можно при поступлении запроса преобразовать (автоматически и абсолютно незаметно для, собственно, посетителя сайта) в старый стиль, с переменными и их значениями после знака “?”.
Как это сделать?
Для разработчиков сайтов использующих LAMP (linux&apache&mysql&php), известны две базовые методики обработки ЧПУ. Первая основана на том, что используется mod_rewrite. Этот модуль (расширение возможностей apache) срабатывает до вызова запрашиваемого файла. В файле .htaccess вы перечисляете шаблоны запрашиваемых адресов в стиле ЧПУ, каждому такому шаблону (при их записи используются регулярные выражения, чтобы придать шаблонам универсальность) ставится в соответствии новый адрес. Второй подход основан на возможности указать для сайта страницу, срабатывающую как обработчик ситуации “404 – страница не найдена”. Всякий раз, когда запрашивается ЧПУ (естественно, такой виртуальный адрес не находится) apache вызывает специальный файл 404. В этом файле пишется несложный код, который анализирует конфигурационные переменные сервера – в них хранится, в том числе, и запрошенный адрес ЧПУ – затем скрипт вычисляет, то какая страница (функция/метод класса) должна быть вызвана для показа запрошенной информации.
В мире java, если вы используете tomcat или иной java-сервер совместно с apache (надо сказать, что это довольно неплохая идея – использовать apache для отображения статических страниц, а tomcat для выполнения java-кода), то вы можете использовать mod_rewrite. Если же apache не доступен, то вы могли бы создать собственную функцию обработчик события 404, анализировать в ней запрошенный адрес и все как в прошлый раз. С другой стороны, в java есть интересная и полезная концепция – сервлеты-фильтры. Сервлеты-фильтры вызываются до того непосредственно вызова запрашиваемого файла и после этого. Сфера применения фильтров огромна – наиболее часто мы используем их для защиты страниц от несанкционированного доступа, работа с интернализацией и локализацией веб-страниц, создание хитрых хаков обеспечивающих корректную работу унаследованных решений. Важно, что даже если запрошенный адрес не существует, все равно сервлет-фильтр будет вызван, а значит, в нем можно выполнить подмену адреса. Написать кода такого сервлета не сложно и могло бы стать неплохой тренировкой для начинающего серверного java-разработчика. Но мы, естественно, пойдем другим путем: существует множество java-библиотек, решающих нужные нам задачи, так что остается выбрать одну из них. На странице wikipedia (http://en.wikipedia.org/ Rewrite_engine) в разделе Rewrite engines for Java 2 Platform, Enterprise Edition указаны следующие библиотеки для rewrite-инга.
- http://www.zlatkovic.com/httpredirectfilter.en.html
- http://tuckey.org/urlrewrite/
- http://software.softeu.cz/rewriter/
Я расскажу о последней из них (http://software.softeu.cz/rewriter/). Предположим, вы скачали с сайта разрабочика jar-архив с библиотекой. Теперь я создам пустое веб-приложение и размещу его в папке tomcat (я использую tomcat 6.1.0 под jdk 1.6, работоспособность библиотеки в других ситуациях я не тестировал). В папке веб-приложения WEB-INF я создам подпапку lib, в которую скопирую архив библиотеки, а также в файле web.xml, подключу сервлет cz.softeu.rewriter.RewriterFilter:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd"
version="2.5">
<display-name>TestRewrite machine</display-name>
<description> based on http://software.softeu.cz/rewriter/ </description>
<filter>
<filter-name>RewriterFilter</filter-name>
<filter-class>cz.softeu.rewriter.RewriterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RewriterFilter</filter-name>
<url-pattern>/catalog/*</url-pattern>
</filter-mapping>
</web-app>
Теперь необходимо разработать прототип страницы магазина shop.jsp. В нем я буду принимать входные переменные: sect, article, date. Переменные будут служить для поиска информации в таблице базы данных mysql следующей структуры (см. рис. 1), затем я заполнил таблицу данными о товарах нашего магазинчика (см. рис. 2):
Для работы jsp-файла с информацией в mysql-базе мне понадобится jdbc-драйвер, который я загрузил с сайта mysql.com и поместил внутрь каталога lib моего веб-приложения. База создана в кодировке utf8 и, следовательно, я указываю параметры кодировки при соединении. В объект Properties помещаются такие параметры, как имя пользователя для входа на сервер mysql, его пароль и кодировки.
Дальнейшие действия тривиальны: я проверяю, какое количество параметров было передано в jsp-страницу (совершенно не заботясь об будущих ЧПУ), затем делаю выборку нужной информации и отображаю ее в виде таблицы со ссылками. Ссылки же здесь в формате ЧПУ.
В ходе работы сприпта выявилась проблема: когда в адресной строке браузера запрашивается адрес вида: catalog/food, а в возвращенной странице присутствуют ссылки вида: catalog/food/2007.1.14/milk, то браузер считает (и надо сказать вполне правильно, что итоговый адрес для запроса должен быть catalog/catalog/food/2007.1.14/milk). Это рушит нам схему именования страниц и самым простым способом задавать адреса будет добавление в секцию head страницы специального тега base. Его назначение задать “базу” – основу, от которой будет выполняться отсчет адресов всех страниц размещенных на сайте. Например, так <base href="http://localhost:8080/" />
Вот полный код примера jsp-страницы:
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="java.sql.*" %>
<%@ page import="java.util.*" %>
<%@ page import="java.sql.Date" %>
<%--
Created by IntelliJ IDEA.
User: Programmer
Date: 03.11.2007
Time: 21:24:28
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Simple jsp page</title>
< base href="http://localhost:8080/" />
</head>
<body>
<%
Class.forName("com.mysql.jdbc.Driver").newInstance();
Properties props = System.getProperties();
props.setProperty("user" , "root");
props.setProperty("password" , "");
props.setProperty("useUnicode" , "true");
props.setProperty("characterEncoding" , "utf8");
props.setProperty("characterSetResults" , "utf8");
Connection coco = DriverManager.getConnection("jdbc:mysql://center/rewriteshop", props);
// создаем подключение к базе данных mysql с каталогом товаров
String sql_sect = "select distinct sect from shop";
String sql_dates = "select distinct article, date_of from shop where sect = ?";
String sql_article = "select * from shop where sect = ? and date_of = ? and article = ?";
// задали строки sql-запросов для различных ситуаций когда нашу страницу запросили
if (request.getParameter("sect") == null){
// первая ситуация когда не передано в страницу название секции, тогда следует
// отобрать и вывести список всех секций товаров
PreparedStatement pstmt_sects = coco.prepareStatement(sql_sect);
ResultSet rs = pstmt_sects.executeQuery();
out.print("<table border='1'>");
while (rs.next()){
String sect = rs.getString("sect");
out.print("<tr><td><a href='catalog/"+sect+"'>"+sect+"</a></td></tr>");
// печатаем строку таблицы с одной ячейкой и размещаем в ней ссылку на раздел каталога
}
rs.close();
out.print("</table>");
}
else {
if (request.getParameter("date") == null) {
String sect = request.getParameter("sect");
PreparedStatement pstmt_dates = coco.prepareStatement(sql_dates);
pstmt_dates.setString(1, sect);
ResultSet rs = pstmt_dates.executeQuery();
out.print("<table border='1'>");
while (rs.next()){
String date_of = rs.getString("date_of");
String article = rs.getString("article");
out.print("<tr><td><a href='catalog/"+sect+"/"+date_of+"/"+article+"'>"+sect+"/"+
date_of+"/"+article+"</a></td></tr>");
// печатаем строку таблицы с одной ячейкой и размещаем в ней ссылку на раздел каталога
}
rs.close();
out.print("</table>");
}
else {
if (request.getParameter("article") != null) {
String sect = request.getParameter("sect");
String date = request.getParameter("date");
String article = request.getParameter("article");
PreparedStatement pstmt_article = coco.prepareStatement(sql_article);
pstmt_article.setString(1, sect);
pstmt_article.setString(2, date);
pstmt_article.setString(3, article);
ResultSet rs = pstmt_article.executeQuery();
rs.next();
String info = rs.getString("info");
String image = rs.getString("img");
out.print("Article: " + article + "<br />");
out.print("Info: " + info + "<br />");
out.print("Image: <img src='pics/" + image + "'/><br />");
rs.close();
out.print("</table>");
}
}
}
%>
</body>
</html>
Теперь последний шаг: для того чтобы запрошенные адреса вида:
http://localhost:8080/catalog/manufacture/2006.1.12/screwdriverПреобразовались в следующие:
/index.jsp?sect=manufacture&date=2006.1.12&article=screwdriverНеобходимо в папке web-inf создать файл с настройками для сервлета-фильтра rewriter-а.
Имя файла должно быть: rewriter-config.xml. Его же внутреннее устройство очевидно: внутри корневого тега rewriter-config, мы задаем множество тегов b:regex, каждый из которых задает одно, отдельное, правило преобразования запрашиваемого адреса. Входной адрес соответствует регулярному выражению внутри тега “from”, а целевой адрес должен быть задан в теге “to”. Очевидно, что вы можете группировать части исходного адреса с помощью круглых скобок, а затем ссылаться на них внутри шаблона “to” используя запись “$цифра”. Цифра должна быть равна порядковому номеру части regexp-шаблона заданного внутри тега “from”. И вот пример кода файла rewriter-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<rewriter-config xmlns:b="http://rewriter.softeu.cz/basic/">
<b:regex>
<from>^/catalog/([^/]+)$</from>
<to>/index.jsp?sect=$1</to>
</b:regex>
<b:regex>
<from>^/catalog/([^/]+)/([^/]+)$</from>
<to>/index.jsp?sect=$1&date=$2</to>
</b:regex>
<b:regex>
<from>^/catalog/([^/]+)/([^/]+)/([^/]+)$</from>
<to>/index.jsp?sect=$1&date=$2&article=$3</to>
</b:regex>
</rewriter-config>
« Работа с базой данных mysql из c++ | Доступ к базам данных из php. Часть 2 » |