{"id":1530,"date":"2012-07-29T16:47:53","date_gmt":"2012-07-29T14:47:53","guid":{"rendered":"http:\/\/doanduyhai.wordpress.com\/?p=1530"},"modified":"2015-01-31T13:13:14","modified_gmt":"2015-01-31T13:13:14","slug":"gwt-rpc-integration-with-spring","status":"publish","type":"post","link":"https:\/\/www.doanduyhai.com\/blog\/?p=1530","title":{"rendered":"GWT RPC integration with Spring"},"content":{"rendered":"<p>Recently I started studying GWT, a new web framework for my curriculum. The main idea of GWT is to let you code the GUI part in Java (with static compilation and type checking) then translate this code into Javascript.<\/p>\n<p> For the backend, GWT relies on an RPC system using plain old Servlet. Each RPC service is published as a distinct servlet so you end up having as many servlets are there are distinct RPC services.<\/p>\n<p> Not only this approach is not optimal for resources management server-side wise, it also pollutes your <strong>web.xml<\/strong> with many servlet declarations.<\/p>\n<p> Ideally we should have an unique servlet serving as a router and dispatching RPC calls to appropriate service beans managed by Spring. Spring is an appropriate framework for managing business in the backend due to its industry-wide adoption and its mature extensions portfolio. Needless to say that this design can be easily adapted for JEE containers too.<\/p>\n<blockquote><p><strong>Disclaimer<\/strong>: the code presented below has been inspired from projects like <a href=\"http:\/\/code.google.com\/p\/spring4gwt\/\" title=\"http:\/\/code.google.com\/p\/spring4gwt\/\" target=\"_blank\">spring4GWT<\/a> and <a href=\"http:\/\/code.google.com\/p\/gwtrpc-spring\/\" title=\"http:\/\/code.google.com\/p\/gwtrpc-spring\/\" target=\"_blank\">gwtrpc-spring<\/a>. I took the same approach and modified the URL mapping part so the original credits go for them.<\/p><\/blockquote>\n<p> <!--more--><\/p>\n<h1>I SpringRPCDispatcherServlet<\/h1>\n<p> RPC processing with GWT not only consist of calling the appropriate service in the backend. It also involves some plumbing tasks like <strong>object serialization\/deserialization<\/strong> to pass parameters back and forth. Instead of re-inventing the wheel and re-coding everything from scratch, it is wiser to re-use the existing GWT infrastructure and extends the <strong>RemoteServiceServlet<\/strong> class to adapt it to our needs.<\/p>\n<pre class=\"brush: java; highlight: [6,9,12,27,33,44,60,72,75,82]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\npublic class SpringRPCDispatcherServlet extends RemoteServiceServlet\r\n{\r\n\tprivate static final long serialVersionUID = 1L;\r\n\tprivate static final Log logger = LogFactory.getLog(SpringRPCDispatcherServlet.class);\r\n\tprivate static final String SERVICE_URL_MAPPER = &quot;serviceURLMapper&quot;;\r\n\tprivate static final UrlPathHelper pathHelper = new UrlPathHelper();\r\n\r\n\tprivate WebApplicationContext applicationContext;\r\n\tprivate Map&lt;String, RemoteService&gt; springRPCServices = new HashMap&lt;String, RemoteService&gt;();\r\n\r\n\t@Override\r\n\tpublic void init()\r\n\t{\r\n\t\tapplicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());\r\n\t\tif (applicationContext == null)\r\n\t\t{\r\n\t\t\tthrow new IllegalStateException(&quot;No Spring web application context found&quot;);\r\n\t\t}\r\n\r\n\t\tif (StringUtils.isEmpty(this.getInitParameter(SERVICE_URL_MAPPER)))\r\n\t\t{\r\n\t\t\tthrow new IllegalArgumentException(&quot;The servlet SpringRPCDispatcherServlet should have a '&quot; + SERVICE_URL_MAPPER + &quot;' parameter defined&quot;);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tString beanName = this.getInitParameter(SERVICE_URL_MAPPER);\r\n\t\t\tthis.initServiceURLMapper(beanName);\r\n\t\t}\r\n\t\tlogger.info(&quot;SpringRPCDispatcherServlet deployed&quot;);\r\n\t}\r\n\r\n\t@SuppressWarnings(&quot;unchecked&quot;)\r\n\tprivate void initServiceURLMapper(String beanName)\r\n\t{\r\n\r\n\t\tthis.springRPCServices = (Map&lt;String, RemoteService&gt;) applicationContext.getBean(beanName, Map.class);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String processCall(String payload) throws SerializationException\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tRemoteService handler = retrieveSpringBean(getThreadLocalRequest());\r\n\t\t\tRPCRequest rpcRequest = RPC.decodeRequest(payload, handler.getClass(), this);\r\n\t\t\tonAfterRequestDeserialized(rpcRequest);\r\n\t\t\tif (logger.isDebugEnabled())\r\n\t\t\t{\r\n\t\t\t\tlogger.debug(&quot;Invoking &quot; + handler.getClass().getName() + &quot;.&quot; + rpcRequest.getMethod().getName());\r\n\t\t\t}\r\n\t\t\treturn RPC.invokeAndEncodeResponse(handler, rpcRequest.getMethod(), rpcRequest.getParameters(), rpcRequest.getSerializationPolicy());\r\n\t\t}\r\n\t\tcatch (IncompatibleRemoteServiceException ex)\r\n\t\t{\r\n\t\t\tlogger.error(&quot;An IncompatibleRemoteServiceException was thrown while processing this call.&quot;, ex);\r\n\t\t\treturn RPC.encodeResponseForFailure(null, ex);\r\n\t\t}\r\n\t}\r\n\r\n\tprotected RemoteService retrieveSpringBean(HttpServletRequest request)\r\n\t{\r\n\t\tString serviceURL = extractServiceURL(request);\r\n\t\tRemoteService bean = getBeanByServiceURL(serviceURL);\r\n\r\n\t\tif (logger.isDebugEnabled())\r\n\t\t{\r\n\t\t\tlogger.debug(&quot;Bean for service &quot; + serviceURL + &quot; is &quot; + bean.getClass());\r\n\t\t}\r\n\t\treturn bean;\r\n\t}\r\n\r\n\tprivate String extractServiceURL(HttpServletRequest request)\r\n\t{\r\n\t\tString service = pathHelper.getPathWithinServletMapping(request);\r\n\t\tif (logger.isDebugEnabled())\r\n\t\t{\r\n\t\t\tlogger.debug(&quot;Service for URL &quot; + request.getRequestURI() + &quot; is &quot; + service);\r\n\t\t}\r\n\t\treturn service;\r\n\t}\r\n\r\n\tprivate RemoteService getBeanByServiceURL(String serviceURL)\r\n\t{\r\n\t\tif (!this.springRPCServices.containsKey(serviceURL))\r\n\t\t{\r\n\t\t\t{\r\n\t\t\t\tthrow new IllegalArgumentException(&quot;Spring bean not found for service URL: &quot; + serviceURL);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn this.springRPCServices.get(serviceURL);\r\n\t}\r\n}\r\n<\/pre>\n<p>Let&#8217;s analyze the code step-by-step.<\/p>\n<p>As private fields, we declare a <strong>UrlPathHelper<\/strong> (<strong>line 6<\/strong>) to extract paths from URL (necessary to determine which service bean to call) and a map linking a <strong>serviceURL<\/strong> to the appropriate Spring bean (<strong>line 9<\/strong>).<\/p>\n<p>First in the <em>init()<\/em> method (<strong>line 12<\/strong>) we get a reference on the current Spring Web application context. We check for the presence of the &#8220;<em>serviceURLMapper<\/em>&#8221; servlet parameter. If it&#8217;s set we initialize the map of <strong>serviceURL<\/strong> by calling the method <em>initServiceURLMapper()<\/em> (<strong>line 27<\/strong>). This method simply retrieve the map from Spring context.<\/p>\n<p>At runtime, when a request is sent to the servlet, the <em>processCall(String payload)<\/em> method is called. This method is copied from the original <strong>RemoteServiceServlet<\/strong> class and I introduced some modifications.<\/p>\n<ul>\n<li><em>retrieveSpringBean()<\/em> is called to get the appropriate Spring service bean for the current request (<strong>line 44<\/strong>)<\/li>\n<li><em>retrieveSpringBean()<\/em> first calls <em>extractServiceURL()<\/em> which relies on the <strong>UrlPathHelper<\/strong> class to extract the path from servlet context (<strong>line 74<\/strong>)\n<\/li>\n<li>then a lookup is performed by <em>getBeanByServiceURL()<\/em> in the <em>springRPCServices<\/em> map to retrieve the correct Spring bean to service the request<\/li>\n<\/ul>\n<p> Please notice that the <strong>UrlPathHelper<\/strong> is of great help here to extract the path excluding the leading servlet context. The Javadoc of this class gives some examples to clarify its operation:<\/p>\n<blockquote><p>\nservlet mapping = &#8220;\/test\/*&#8221;; request URI = &#8220;\/test\/a&#8221;, result =  &#8220;\/a&#8221;<br \/>\nservlet mapping = &#8220;\/test&#8221;; request URI = &#8220;\/test&#8221;, result =  &#8220;&#8221;<br \/>\nservlet mapping = &#8220;\/*.test&#8221;; request URI = &#8220;\/a.test&#8221;, result =  &#8220;&#8221;\n<\/p><\/blockquote>\n<p>Now that we&#8217;ve created the servlet, let&#8217;s see how it is configured with GWT<\/p>\n<p>&nbsp;<\/p>\n<h1>II Configuration<\/h1>\n<h3>A web.xml<\/h3>\n<p> A typical <strong>web.xml<\/strong> configuration for the <strong>SpringRPCDispatcherServlet<\/strong> is<\/p>\n<pre class=\"brush: xml; highlight: [17,18,26]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n&lt;web-app xmlns:xsi=&quot;http:\/\/www.w3.org\/2001\/XMLSchema-instance&quot; xmlns=&quot;http:\/\/java.sun.com\/xml\/ns\/javaee&quot; xmlns:web=&quot;http:\/\/java.sun.com\/xml\/ns\/javaee\/web-app_2_5.xsd&quot; xsi:schemaLocation=&quot;http:\/\/java.sun.com\/xml\/ns\/javaee http:\/\/java.sun.com\/xml\/ns\/javaee\/web-app_2_5.xsd&quot; version=&quot;2.5&quot;&gt;\r\n\r\n  &lt;!-- Spring classical web application context declaration --&gt;\r\n  &lt;context-param&gt;\r\n    &lt;param-name&gt;contextConfigLocation&lt;\/param-name&gt;\r\n    &lt;param-value&gt;classpath:applicationContext.xml&lt;\/param-value&gt;\r\n  &lt;\/context-param&gt;\r\n  &lt;listener&gt;\r\n    &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;\/listener-class&gt;\r\n  &lt;\/listener&gt;\r\n\r\n   &lt;!-- SpringRPCDispatcherServlet declaration --&gt;\r\n  &lt;servlet&gt;\r\n    &lt;servlet-name&gt;springBackendServletDispatcher&lt;\/servlet-name&gt;\r\n    &lt;servlet-class&gt;com.google.gwt.sample.stockwatcher_rpc.utils.SpringRPCDispatcherServlet&lt;\/servlet-class&gt;\r\n    &lt;init-param&gt;\r\n      &lt;param-name&gt;serviceURLMapper&lt;\/param-name&gt;\r\n      &lt;param-value&gt;serviceURLMapper&lt;\/param-value&gt;\r\n    &lt;\/init-param&gt;\r\n    &lt;load-on-startup&gt;1&lt;\/load-on-startup&gt;\r\n  &lt;\/servlet&gt;\r\n\r\n  &lt;!-- SpringRPCDispatcherServlet servlet mapping --&gt;\r\n  &lt;servlet-mapping&gt;\r\n    &lt;servlet-name&gt;springBackendServletDispatcher&lt;\/servlet-name&gt;\r\n    &lt;url-pattern&gt;\/stockWatcherRPC\/rpc\/*&lt;\/url-pattern&gt;\r\n  &lt;\/servlet-mapping&gt;\r\n...\r\n&lt;\/web-app&gt;\r\n<\/pre>\n<p>The first part of this <strong>web.xml<\/strong> is a classical Spring web application context declaration, nothing special. <\/p>\n<p> Next we define our special servlet, providing the serviceURLMapper parameter. This value should point to an existing bean id in the Spring context (see below) (<strong>lines 17-18<\/strong>).<\/p>\n<p> Finally we associate this servlet with the url pattern &#8220;<em>\/stockWatcherRPC\/rpc\/*<\/em>&#8221;  (<strong>line 26<\/strong>). Only RPC calls using this pattern will be handled by our custom servlet.<\/p>\n<p>&nbsp;<\/p>\n<h3>B applicationContext.xml<\/h3>\n<p>Below is the declaration of Spring beans servicing RPC requests from the GUI:<\/p>\n<pre class=\"brush: xml; highlight: [5,6,7]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n&lt;bean id=&quot;stockPriceRPCService&quot; class=&quot;com.google.gwt.sample.stockwatcher_rpc.service.StockPriceServiceImpl&quot;\/&gt;\r\n\r\n&lt;bean id=&quot;randomizeRPCService&quot; class=&quot;com.google.gwt.sample.stockwatcher_rpc.service.RandomizeServiceImpl&quot;\/&gt;\r\n\r\n&lt;util:map id=&quot;serviceURLMapper&quot; key-type=&quot;java.lang.String&quot; value-type=&quot;com.google.gwt.user.client.rpc.RemoteService&quot;&gt;\r\n\t&lt;entry key=&quot;\/stockPrices&quot; value-ref=&quot;stockPriceRPCService&quot;\/&gt;\r\n\t&lt;entry key=&quot;\/randomize&quot; value-ref=&quot;randomizeRPCService&quot;\/&gt;\r\n&lt;\/util:map&gt;\r\n\r\n<\/pre>\n<p>The &#8220;<strong>serviceURLMapper<\/strong>&#8221; is just a map with key of String type representing the <em>serviceURL<\/em> and value of <strong>com.google.gwt.user.client.rpc.RemoteService<\/strong> type representing the corresponding Spring service bean. All server-side services for GWT RPC should implement an user-defined interface which implements itself the generic <strong>RemoteService<\/strong> from the GWT SDK so setting it as a supertype for map value type is fine.<\/p>\n<p> The map bean is quite straightforward, associating a service path with a declared bean. Please note that in the above example:<\/p>\n<ul>\n<li><strong>stockPriceRPCService<\/strong> will service all incoming RPC requests with URL <em>\/stockWatcherRPC\/rpc\/stockPrices<\/em><\/li>\n<li><strong>randomizeRPCService<\/strong> will service all incoming RPC requests with URL <em>\/stockWatcherRPC\/rpc\/randomize<\/em><\/li>\n<\/ul>\n<p> Please note that the service URL is the map is the absolute URL removed of the servlet path defined earlier in <strong>web.xml<\/strong> (<em>\/stockWatcherRPC\/rpc\/*<\/em>)<\/p>\n<p>&nbsp;<\/p>\n<h3>C GWT configuration<\/h3>\n<p>From the GWT side the configuration is quite simple:<\/p>\n<p>StockWatcherRPC.gwt.xml:<\/p>\n<pre class=\"brush: xml; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n&lt;module rename-to=&quot;stockWatcherRPC&quot;&gt;\r\n\r\n<\/pre>\n<p>com.google.gwt.sample.stockwatcher_rpc.client.StockPriceService<\/p>\n<pre class=\"brush: java; highlight: [1]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n@RemoteServiceRelativePath(&quot;rpc\/stockPrices&quot;)\r\npublic interface StockPriceService extends RemoteService\r\n\r\n<\/pre>\n<p>com.google.gwt.sample.stockwatcher_rpc.client.RandomizeService<\/p>\n<pre class=\"brush: java; highlight: [1]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n@RemoteServiceRelativePath(&quot;rpc\/randomize&quot;)\r\npublic interface RandomizeService extends RemoteService\r\n\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h3>D URL mapping summary<\/h3>\n<p> Below is a picture summarizing the way URL are handled by our servlet.<\/p>\n<p><a href=\"http:\/\/doanduyhai.files.wordpress.com\/2012\/07\/gwt_rpc_integration_spring.png\"><img loading=\"lazy\" src=\"http:\/\/doanduyhai.files.wordpress.com\/2012\/07\/gwt_rpc_integration_spring.png\" alt=\"GWT_RPC_integration_Spring_URL_Mapping\" title=\"GWT_RPC_integration_Spring_URL_Mapping\" width=\"630\" height=\"496\" class=\"aligncenter size-full wp-image-1543\" srcset=\"https:\/\/www.doanduyhai.com\/blog\/wp-content\/uploads\/2012\/07\/gwt_rpc_integration_spring.png 750w, https:\/\/www.doanduyhai.com\/blog\/wp-content\/uploads\/2012\/07\/gwt_rpc_integration_spring-300x236.png 300w\" sizes=\"(max-width: 630px) 100vw, 630px\" \/><\/a><\/p>\n<p> Please notice that the GWT module name (here <strong>stockWatcherRPC<\/strong>) should be included in the servlet mapping url. Most of RPC issues come from erronous service URLs.<\/p>\n<p> Another remark about URL path segregation. We could have declare the Spring servlet mapping as <em>\/stockWatcherRPC\/*<\/em> and the url in the map as <em>\/rpc\/stockPrices<\/em>. <\/p>\n<p>However associating the Spring servlet mapping to the root of GWT module name (<em>\/stockWatcherRPC<\/em>) is not a good idea because any request from the GUI to retrieve static resources (images, JS files&#8230;) will trigger our custom servlet, which we do not want. <\/p>\n<p>That&#8217;s why it&#8217;s a better practice to separate RPC call with a dedicated URL pattern (here <em>rpc\/*<\/em>)<br \/>\n&nbsp;<\/p>\n<h3>E Alternate URL pattern<\/h3>\n<p>In the above example, we use the <em>\/rpc<\/em> pattern in the URL string to distinguish between client RPC calls and normal resources requests.<\/p>\n<p>But another approach for RPC URL mapping is possible. What if we distinguish all RPC calls with a particular extension, like <em>*.rpc<\/em> ?<\/p>\n<p>Spring servlet mapping:<\/p>\n<pre class=\"brush: xml; highlight: [3]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n  &lt;servlet-mapping&gt;\r\n    &lt;servlet-name&gt;springBackendServletDispatcher&lt;\/servlet-name&gt;\r\n    &lt;url-pattern&gt;\/*.rpc&lt;\/url-pattern&gt;\r\n  &lt;\/servlet-mapping&gt;\r\n\r\n<\/pre>\n<p>Spring service URL map<\/p>\n<pre class=\"brush: xml; highlight: [2,3]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\n&lt;util:map id=&quot;serviceURLMapper&quot; key-type=&quot;java.lang.String&quot; value-type=&quot;com.google.gwt.user.client.rpc.RemoteService&quot;&gt;\r\n\t&lt;entry key=&quot;stockPrices.rpc&quot; value-ref=&quot;stockPriceRPCService&quot;\/&gt;\r\n\t&lt;entry key=&quot;randomize.rpc&quot; value-ref=&quot;randomizeRPCService&quot;\/&gt;\r\n&lt;\/util:map&gt;\r\n\r\n<\/pre>\n<p><a href=\"http:\/\/doanduyhai.files.wordpress.com\/2012\/07\/gwt_rpc_integration_spring_alternate_url_mapping.png\"><img loading=\"lazy\" src=\"http:\/\/doanduyhai.files.wordpress.com\/2012\/07\/gwt_rpc_integration_spring_alternate_url_mapping.png\" alt=\"GWT_RPC_integration_Spring_Alternate_URL_Mapping\" title=\"GWT_RPC_integration_Spring_Alternate_URL_Mapping\" width=\"630\" height=\"492\" class=\"aligncenter size-full wp-image-1547\" srcset=\"https:\/\/www.doanduyhai.com\/blog\/wp-content\/uploads\/2012\/07\/gwt_rpc_integration_spring_alternate_url_mapping.png 758w, https:\/\/www.doanduyhai.com\/blog\/wp-content\/uploads\/2012\/07\/gwt_rpc_integration_spring_alternate_url_mapping-300x234.png 300w\" sizes=\"(max-width: 630px) 100vw, 630px\" \/><\/a><\/p>\n<p> In this case, we just need to modify our servlet method extractServiceURL() to extract the trailing part with .rpc extension in the absolute URL with an RegExp:<\/p>\n<pre class=\"brush: java; highlight: [3]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nprivate String extractServiceURL(HttpServletRequest request)\r\n{\r\n\tString service = request.getRequestURI().replaceFirst(&quot;^.*\/([^\/]+.&quot;+this.rpcExtension+&quot;)$&quot;, &quot;$1&quot;);\r\n\tif (logger.isDebugEnabled())\r\n\t{\r\n\t\tlogger.debug(&quot;Service for URL &quot; + request.getRequestURI() + &quot; is &quot; + service);\r\n\t}\r\n\treturn service;\r\n}\r\n<\/pre>\n<p><strong>this.rpcExtension<\/strong> represents the URL extension for RPC calls (<em>.rpc<\/em> here). It should be injected in the servlet as init parameter.<br \/>\n&nbsp;<\/p>\n<h1>III Demo<\/h1>\n<p>As a sample application, I took the original <a href=\"http:\/\/code.google.com\/p\/google-web-toolkit\/downloads\/detail?name=Tutorial-GettingStarted-2.1.zip\" title=\"http:\/\/code.google.com\/p\/google-web-toolkit\/downloads\/detail?name=Tutorial-GettingStarted-2.1.zip\" target=\"_blank\">StockWatcher<\/a> from Google tutorial and modified it to use Spring RPC beans in the backend.<\/p>\n<p><a href=\"http:\/\/doanduyhai.files.wordpress.com\/2012\/07\/gwt_rpc_integration_spring_demo.png\"><img loading=\"lazy\" src=\"http:\/\/doanduyhai.files.wordpress.com\/2012\/07\/gwt_rpc_integration_spring_demo.png\" alt=\"GWT_RPC_integration_Spring_Demo\" title=\"GWT_RPC_integration_Spring_Demo\" width=\"542\" height=\"330\" class=\"aligncenter size-full wp-image-1549\" srcset=\"https:\/\/www.doanduyhai.com\/blog\/wp-content\/uploads\/2012\/07\/gwt_rpc_integration_spring_demo.png 542w, https:\/\/www.doanduyhai.com\/blog\/wp-content\/uploads\/2012\/07\/gwt_rpc_integration_spring_demo-300x183.png 300w\" sizes=\"(max-width: 542px) 100vw, 542px\" \/><\/a><\/p>\n<p>The main &#8220;<em>Add<\/em>&#8221; button is calling the <strong>stockPrices<\/strong> service. I enriched the GUI with a &#8220;<em>Randomize<\/em>&#8221; button and introduced a new <strong>randomize<\/strong> service to demonstrate the servlet URL mapping feature.<\/p>\n<p>All source code for this project can be found on Github at <a href=\"https:\/\/github.com\/doanduyhai\/StockWatcherRPC\" title=\"https:\/\/github.com\/doanduyhai\/StockWatcherRPC\" target=\"_blank\">StockWatcherRPC<\/a><\/p>\n<p>&nbsp;<br \/>\n&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently I started studying GWT, a new web framework for my curriculum. The main idea of GWT is to let you code the GUI part in Java (with static compilation and type checking) then translate this code into Javascript. For&#8230;<br \/><a class=\"read-more-button\" href=\"https:\/\/www.doanduyhai.com\/blog\/?p=1530\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[25,14],"tags":[],"_links":{"self":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1530"}],"collection":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1530"}],"version-history":[{"count":1,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1530\/revisions"}],"predecessor-version":[{"id":1753,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1530\/revisions\/1753"}],"wp:attachment":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}