Как известно, поисковики не очень любят динамически создаваемые страницы, а страница (приложение) GWT как раз является динамической.
Чтобы поисковый робот смог проиндексировать некий контент, этот контент должен быть доступен в html странице на момент ее загрузки, а в типичных GWT приложениях контент запрашивается с сервера путем RPC запроса и формирует интерфейс.
Для обхождения этой проблемы может быть применен способ сериализации в страницу того контента, который запрашивается у сервера при загрузке приложения.
Дополнительный бонус данного способа — ускорения старта приложения, т.к. нет необходимости ходить на сервер за данными.
Чтобы поисковый робот смог проиндексировать некий контент, этот контент должен быть доступен в html странице на момент ее загрузки, а в типичных GWT приложениях контент запрашивается с сервера путем RPC запроса и формирует интерфейс.
Для обхождения этой проблемы может быть применен способ сериализации в страницу того контента, который запрашивается у сервера при загрузке приложения.
Дополнительный бонус данного способа — ускорения старта приложения, т.к. нет необходимости ходить на сервер за данными.
Принцип действия такой:
- Для DTO, которые передаются с сервера, добавляется дополнительный метод(например, путем добавления абстрактного родительского класса, который преобразует содержимое в вид, предпочтительный для поисковиков.
public abstract class GWTBootstrapDTO implements IsSerializable { public abstract String getBootstrap(); }
- При формировании хост-страницы на сервере, делается запрос к сервлету с RPC интерфейсом и полученный контент сериализуется (о сериализации чуть позже) непосредственно в страницу.
- Далее в сформированную страницу в тег <noscript> загружается контент для индексирования.
- После того, как страница была получена пользователем, стартует приложение, а все первоначальные данные десериализуются из хост-страницы.
- Данные для поисковиков так же присутствуют в странице и доступны для индексирования.
Исходные данные.
Сервис:
package com.oshift.ui.client.forummanamegemt; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; import com.oshift.ui.shared.forum.GWTForum; import java.util.ArrayList; @RemoteServiceRelativePath("forummanagementservice") public interface ForumManagementService extends RemoteService { public ArrayList<GWTForum> getForums(); }
Сервлет:
package com.oshift.ui.server.forummanamegemt; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.oshift.ui.client.forummanamegemt.ForumManagementService; import com.oshift.ui.shared.forum.GWTForum; import com.oshift.ws.db.forummanagement.Forum; import com.oshift.ws.db.forummanagement.Mapper; import com.oshift.ws.ejb.ForumManagerBean; import java.util.ArrayList; import javax.ejb.EJB; import javax.servlet.ServletException; public class ForumManagementServiceImpl extends RemoteServiceServlet implements ForumManagementService { public static ForumManagementServiceImpl instance = null; //сохраняем ссылку на сервлет @Override public void init() throws ServletException { super.init(); instance = this; } //каким-то образом нужно получить данные, например, с помощью EJB @EJB private ForumManagerBean forumBean; @Override public ArrayList<GWTForum> getForums() { ArrayList<GWTForum> res = new ArrayList<GWTForum>(); for (Forum f : forumBean.getForums()) { GWTForum toGwt = Mapper.ForumMap.toGwt(f); res.add(toGwt); } return res; } }
Объекты DTO для сериализации:
*Важный момент, для избавления себя от мук настройки SerializationPolicy, данные объекты реализуют интерфейс IsSerializable.
package com.oshift.ui.shared.forum; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.client.rpc.IsSerializable; import java.util.ArrayList; public class GWTForum extends GWTBootstrapDTO implements IsSerializable { public String forumName = "просто форум"; public ArrayList<GWTTopic> topics = new ArrayList<GWTTopic>(); public GWTForum() { } @Override public String getBootstrap() { String res = "Forum name: " + forumName + "<br>"; for (GWTTopic t : topics) { res += t.getBootstrap(); } //эскейп, мало ли что там SafeHtml fromString = SafeHtmlUtils.fromString(res); return fromString.asString(); } } package com.oshift.ui.shared.forum; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.client.rpc.IsSerializable; public class GWTTopic extends GWTBootstrapDTO implements IsSerializable { public String topicName = ""; @Override public String getBootstrap() { String res = "Название топика: " + topicName + "</br>"; SafeHtml fromString = SafeHtmlUtils.fromString(res); return fromString.asString(); } }
Процесс сериализации на стороне сервера(ради простоты, непосредственно в jsp):
<%@page import="com.google.gwt.safehtml.shared.SafeHtmlUtils"%> <%@page import="com.oshift.ws.ejb.ForumManagerBean"%> <%@page import="javax.ejb.EJB"%> <%@page import="java.lang.reflect.Method"%> <%@page import="com.oshift.ui.client.forummanamegemt.ForumManagementService"%> <%@page import="com.google.gwt.user.server.rpc.RPC"%> <%@page import="java.util.ArrayList"%> <%@page import="com.oshift.ui.shared.forum.GWTForum"%> <%@page import="com.oshift.ui.server.forummanamegemt.ForumManagementServiceImpl"%> <%@page contentType="text/html" pageEncoding="UTF-8"%> <% //получаем сервис ArrayList<GWTForum> forumsList = ForumManagementServiceImpl.instance.getForums(); //получаем сервисный метод Method m = ForumManagementService.class.getMethod("getForums"); //получаем сериализованный экранированный контент для вставки в хост-страницу String forums = SafeHtmlUtils.fromString(RPC.encodeResponseForSuccess(m, forumsList)).asString(); //получаем контент для noscript тега StringBuilder noscriptSb = new StringBuilder(); for (GWTForum f : forumsList) { noscriptSb.append(f.getBootstrap()); } String noscript = noscriptSb.toString(); %> <!DOCTYPE html> <html> <head> <script type="text/javascript" language="javascript"> var forums='<%=forums%>'; </script> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Forums index</title> <link href="res/gfStyle.css" type="text/css" rel="stylesheet"/> <link href="res/login.css" type="text/css" rel="stylesheet"/> <meta name='gwt:module' content='com.oshift.ui.index=com.oshift.ui.index'> <script type="text/javascript" src="com.oshift.ui.index/com.oshift.ui.index.nocache.js"></script> </head> <body> ... <noscript> <%=noscript%> </noscript>
Процесс десериализации на стороне клиента:
package com.oshift.ui.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.shared.GWT; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.client.rpc.SerializationStreamFactory; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.oshift.ui.client.forummanamegemt.ForumManagementServiceAsync; import com.oshift.ui.client.mainpage.ForumComposite; import com.oshift.ui.shared.forum.GWTForum; import java.util.ArrayList; public class IndexEntryPoint implements EntryPoint { //Сервис private final ForumManagementServiceAsync svc = ServicesFactory.getForumManagementServiceAsync(); @Override public void onModuleLoad() { //Необходимо сделать unescape для сериализованного контента String forums = new HTML(getForums()).getText(); SerializationStreamFactory ssf = (SerializationStreamFactory) svc; try { ArrayList<GWTForum> readObject = (ArrayList<GWTForum>) ssf.createStreamReader(forums).readObject(); for (GWTForum f : readObject) { //делаем что нам нужно с полученным объектом process(f); } } catch (SerializationException ex) { //Обработка ошибки Window.alert("Не удалось десериализовать со страницы форумы для отображения: " + ex); } ...продолжаем делать что обычно } //Получаем сериализованный контент со страницы private native String getForums()/*-{ return eval("$wnd.forums"); }-*/; }
Сам контент на странице выглядит так:
<!DOCTYPE html> <html> <head> <script type="text/javascript" language="javascript"> //эти данные заэскейплены, поверьте. var forums='//OK[7,4,6,4,5,4,3,1,3,2,1,1,["java.util.ArrayList/4159755760","com.oshift.ui.shared.forum.GWTForum/1236332786","форум1","com.oshift.ui.shared.forum.GWTTopic/1653537274","некий топик1","некий топик2","некий топик3"],0,7]'; </script> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Forums index</title> <link href="res/gfStyle.css" type="text/css" rel="stylesheet"/> <link href="res/login.css" type="text/css" rel="stylesheet"/> <meta name='gwt:module' content='com.oshift.ui.index=com.oshift.ui.index'> <script type="text/javascript" src="com.oshift.ui.index/com.oshift.ui.index.nocache.js"></script> </head> <body> ... <noscript> Forum name: форум1<br>Название топика: некий топик1</br>Название топика: некий топик2</br>Название топика: некий топик3</br> </noscript> </div> </body> </html>
Надеюсь, моя статья станет кому-то полезной.
ссылка на оригинал статьи http://habrahabr.ru/post/163917/
Добавить комментарий