Сериализация GWT RPC в запрашиваемую страницу для индексирования приложения поисковиками и ускорения загрузки

от автора

Как известно, поисковики не очень любят динамически создаваемые страницы, а страница (приложение) GWT как раз является динамической.

Чтобы поисковый робот смог проиндексировать некий контент, этот контент должен быть доступен в html странице на момент ее загрузки, а в типичных GWT приложениях контент запрашивается с сервера путем RPC запроса и формирует интерфейс.
Для обхождения этой проблемы может быть применен способ сериализации в страницу того контента, который запрашивается у сервера при загрузке приложения.
Дополнительный бонус данного способа — ускорения старта приложения, т.к. нет необходимости ходить на сервер за данными.

Принцип действия такой:

  • Для DTO, которые передаются с сервера, добавляется дополнительный метод(например, путем добавления абстрактного родительского класса, который преобразует содержимое в вид, предпочтительный для поисковиков.
    public abstract class GWTBootstrapDTO implements IsSerializable {      public abstract String getBootstrap(); } 

  • При формировании хост-страницы на сервере, делается запрос к сервлету с RPC интерфейсом и полученный контент сериализуется (о сериализации чуть позже) непосредственно в страницу.
  • Далее в сформированную страницу в тег &ltnoscript&gt загружается контент для индексирования.
  • После того, как страница была получена пользователем, стартует приложение, а все первоначальные данные десериализуются из хост-страницы.
  • Данные для поисковиков так же присутствуют в странице и доступны для индексирования.

Исходные данные.
Сервис:

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&lt;/br&gt;Название топика: некий топик2&lt;/br&gt;Название топика: некий топик3&lt;/br&gt;             </noscript>         </div>     </body> </html> 

Надеюсь, моя статья станет кому-то полезной.

ссылка на оригинал статьи http://habrahabr.ru/post/163917/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *