{"id":1664,"date":"2010-02-04T15:31:41","date_gmt":"2010-02-04T14:31:41","guid":{"rendered":"http:\/\/blog.jteam.nl\/?p=1664"},"modified":"2010-02-04T15:31:41","modified_gmt":"2010-02-04T14:31:41","slug":"free-java-hosting-with-the-google-app-engine","status":"publish","type":"post","link":"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/","title":{"rendered":"Free Java hosting with the Google App Engine"},"content":{"rendered":"<p>Lately I have been looking into and playing around with the <a href=\"http:\/\/code.google.com\/appengine\">Google App Engine<\/a>. In this post I want to give a little introduction to the Google App Engine, why it can be interesting and how to work with it.<br \/>\n<!--more--><\/p>\n<h2>Introduction<\/h2>\n<p>First I want to describe what the Google App Engine is in a few sentences. But nobody does a better job at explaining than Wikipedia so I&#8217;ll just give you a quote instead \ud83d\ude42<\/p>\n<blockquote><p>\nGoogle App Engine is a platform for developing and hosting web applications in Google-managed data centers. It was first released as a beta version in April 2008.<br \/>\nGoogle App Engine is cloud computing technology. It virtualizes applications across multiple servers and data centers.[1] Other cloud-based platforms include offerings such as Amazon Web Services and Microsoft&#8217;s Azure Services Platform.<br \/>\nGoogle App Engine is free up to a certain level of used resources. Fees are charged for additional storage, bandwidth, or CPU cycles required by the application.\n<\/p><\/blockquote>\n<p>Source: <a href=\"http:\/\/en.wikipedia.org\/wiki\/Google_App_Engine\">http:\/\/en.wikipedia.org\/wiki\/Google_App_Engine<\/a><\/p>\n<p>To sum this quote up in a few words: Google App Engine is free hosting for Java! For so long technologies like <a href=\"http:\/\/www.php.net\">PHP<\/a> have been very cheap (if not free) to host. To host a Java web application on the other hand, you always have to pay good money for it. Probably the biggest reason is that for PHP, it&#8217;s easy to have many websites on one server since those websites are just a bunch of standalone scripts. A Java website on the other hand, is an entire application, so the best way to host this is to get a dedicated server with an application server installed. Dedicating an entire server to host your website usually isn&#8217;t as cheap as hosting a PHP website.<\/p>\n<h2>Why use the Google App Engine?<\/h2>\n<p>Next to the fact that Google App Engine is free to use, what else is interesting about it, you ask? Well, let me tell you! Actually, Google App Engine offers you a lot of features which can help your application in different ways. Below are a few examples of features I find the most helpful.<\/p>\n<h3>The dashboard<\/h3>\n<p>The first thing you will see when you start using the Google App Engine is the Dashboard. The dashboard has both an online version and a local development version.<\/p>\n<p>The online version of the dashboard allows you to see all sorts of information about your deployed application. You can for example see application logging (filtered by INFO, WARN, etcetera), but also configured cron jobs, the content of your data store and quota details.<\/p>\n<p>The local development dashboard is kind of a stripped down version of the online one. It helps you control your locally running instance of the application. Using the dashboard you can for example simulate incoming emails, simulate incoming XMPP messages and view running cron jobs.<\/p>\n<h3>Image service<\/h3>\n<p>Google offers you a service to easily manipulate images. The most common image operations are supported, like: resizing, cropping, flipping, enhancing colors and contrast.<\/p>\n<h3>Data store<\/h3>\n<p>Using Google&#8217;s JPA or JDO implementation you can access the datastore. You can also use an SQL-like query language to access the store directly.<\/p>\n<h3>Cron jobs \/ task queues<\/h3>\n<p>Instead of using a framework like <a href=\"http:\/\/www.quartz-scheduler.org\/\">Quartz<\/a> to schedule jobs, Google App Engine takes care of executing jobs for you. You simply enter a cron-like expression and a URL to call and your job is configured.<\/p>\n<p>You also have a task queue at your disposal. Your application code can add tasks to a task queue which will be executed later in the future, asynchronously. An example use case is that you don&#8217;t want clients to wait for an email to be sent before he sees the next page. Instead you can put the email task on the task queue and the email will be sent asynchronously.<\/p>\n<h3>Security using Google accounts<\/h3>\n<p>Why use a security framework like <a href=\"http:\/\/static.springsource.org\/spring-security\/site\/index.html\">Spring Security<\/a> when Google lets you use their own authentication system? When you configure your application to use Google authentication, only people with a Google account are able to login to the application. You can also configure one or more accounts to be the administrator. Administrators could have access to certain parts of the application other users can&#8217;t access.<\/p>\n<h3>Service for receiving email<\/h3>\n<p>Google offers a service that allows your application to receive emails! When an email comes in, a URL is called with the email in the POST body. The body of the request is just the plain mime message text as it was received.<\/p>\n<h2>Applications on the Google App Engine<\/h2>\n<p>To run your own application on the Google App Engine, it has to follow some guidelines in order to properly function. When I was building a sample application, I sometimes found myself writing workarounds to work with for example the JPA and mailing facilities of the Google App Engine. For the record, I was trying to use the Spring framework with JPA for persistence and Spring MVC for the frontend. I want to share the challenges I faced and how I handled them to get things working.<\/p>\n<p>Sometimes I had to write custom classes to get things working, I combined all those classes in a tiny open source project called app-engine-workarounds. The project can be found at <a href=\"http:\/\/code.google.com\/p\/app-engine-workarounds\/\">http:\/\/code.google.com\/p\/app-engine-workarounds\/<\/a>. Below I will sometimes refer to classes in this project.<\/p>\n<h3>Spring and the Java Mail Sender<\/h3>\n<p>When you&#8217;re using Spring and want to send an email from within your application, you will use the <a href=\"http:\/\/static.springsource.org\/spring\/docs\/3.0.0.RELEASE\/api\/org\/springframework\/mail\/javamail\/JavaMailSenderImpl.html\">JavaMailSenderImpl<\/a> which is packaged with Spring. But when running an application the Google App Engine, you can&#8217;t use this class out-of-the-box. The reason is that Google has its own email implementation and does not use SMTP servers for instance. This is why you have to interact differently with the javax.mail.* framework. Luckily you can easily extend the JavaMailSenderImpl to make adjustments. The <a href=\"http:\/\/code.google.com\/p\/app-engine-workarounds\/\">app-engine-workarounds<\/a>s library actually contains an adjusted JavaMailSender which is called the GaeJavaMailSender. This class is a drop-in replacement for the original JavaMailSenderImpl.<\/p>\n<h3>Handling file uploads in Spring MVC<\/h3>\n<p>Handling file uploads in Spring MVC requires an implementation of the <a href=\"http:\/\/static.springsource.org\/spring\/docs\/3.0.0.RELEASE\/api\/org\/springframework\/web\/multipart\/MultipartResolver.html\">MultipartResolver<\/a>. The only implementation available in Spring MVC 3.0 is the <a href=\"http:\/\/static.springsource.org\/spring\/docs\/3.0.0.RELEASE\/api\/org\/springframework\/web\/multipart\/commons\/CommonsMultipartResolver.html\">CommonsMultipartResolver<\/a> which uses <a href=\"http:\/\/commons.apache.org\/fileupload\/\">commons-fileupload<\/a> to do its job. Unfortunately the way Spring uses commons-fileupload, is not supported by the Google App Engine because it uses the file system, which Google App Engine does not provide. To make this work, you will have to utilize the <a href=\"http:\/\/commons.apache.org\/fileupload\/streaming.html\">streaming API<\/a> part of commons-fileupload framework, which does not need the filesystem. I created my own custom implementation of the MultipartResolver that actually uses this streaming API of commons-fileupload. I put this implementation in the <a href=\"http:\/\/code.google.com\/p\/app-engine-workarounds\/\">app-engine-workarounds<\/a> library too.<\/p>\n<h3>Spring and JPA with transactions<\/h3>\n<p>You can use JPA with Spring almost like you&#8217;re used to, except that the EntityManager factory bean is configured differently. In the section &#8220;Other workarounds&#8221; on <a href=\"http:\/\/code.google.com\/p\/app-engine-workarounds\/\">http:\/\/code.google.com\/p\/app-engine-workarounds\/<\/a> I put a description of how to configure JPA in Spring for the Google App Engine.<\/p>\n<p>Using JPA with the Google App Engine has its limitations. I only tried the JPA implementation and I soon found out that a lot of JPA features are not (yet) supported. Most of these features are related to relationships between entities. I used a relatively simply data model and already encountered the following issues:<\/p>\n<ul>\n<li>Multiple ManyToOne relations to the same entity is not supported<\/li>\n<li>OneToMany relation can only be used with entities that use the GAE &#8220;Key&#8221; class as the unique identifier<\/li>\n<li>Can&#8217;t operate on multiple entities within the same transaction<\/li>\n<li>In a query, you can&#8217;t use &#8220;OR&#8221; filtering referring to two different properties of an entity<\/li>\n<\/ul>\n<h3>Parsing inbound emails<\/h3>\n<p>Google App Engine offers the very useful feature of receiving emails in your application. This way users can just send an email to some address and your application processes it. When an email is received Google App Engine does a post on an URL in your application you configured. The HTTP body of the POST request contains the exact mime message as it was received by Google. To parse this mime message you can use the <a href=\"http:\/\/java.sun.com\/products\/javamail\/javadocs\/javax\/mail\/internet\/MimeMessage.html\">MimeMessage<\/a> class provided by the JDK.<\/p>\n<p>Unfortunately this class is not easy to use and sometimes not that straightforward at all. Usually all you want is to extract the sender, subject, body (html or plain text version) and attachments from the mime message. I created a few classes that help you to easily parse that information from a mime message received by your application. And guess what? I put these classes in the <a href=\"http:\/\/code.google.com\/p\/app-engine-workarounds\/\">app-engine-workarounds<\/a> project too! Example of how to parse an incoming email message using this class:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\nSession session = Session.getDefaultInstance(new Properties(), null);\nMimeMessage mimeMessage = new MimeMessage(session, request.getInputStream());\nEmailMessage email = new EmailMessage(mimeMessage);\n\n\/\/ Now you can use the EmailMessage instance to easily extract information\nString subject = email.getSubject();\nString plainTextBody = email.getPlainTextBody();\nString htmlBody = email.getHtmlBody();\nAddress fromAddress = email.getFromAddress();\nList&lt;Attachment&gt; attachments = email.getAttachments();\n\/\/ ... etcetera\n<\/pre>\n<h3>Cronjob to keep your application &#8220;hot&#8221;<\/h3>\n<p>When you deploy your application it doesn&#8217;t always keep running. After like 5 minutes of inactivity, the application shuts down. Now when a user tries to access a URL, the application starts up again. So the first user that tries to open a page after a period of inactivity has to wait 30 seconds before he gets a response. Google does this to save CPU power of the servers in their &#8220;cloud&#8221;.<\/p>\n<p>Especially during development, this is very annoying because it slows you down trying to test your application. What you can do is configure a cronjob with the expression &#8220;every 1 minutes&#8221; which calls the URL &#8220;\/ping&#8221; (for example). When this is in place, your application always stays &#8220;hot&#8221;. In other words, it never shuts down and will always quickly respond to requests. I&#8217;m not sure if this violates any usage policy of Google, but I&#8217;m sure it&#8217;s not that bad when you only use this during development, when it&#8217;s the most annoying.<\/p>\n<h3>Google services as Spring beans<\/h3>\n<p>Google exposes a few services for you to use, but you can only get a reference to them using a static factory method. In a Spring application this is not a nice thing to do, especially because this is hard to unit test. Instead, you can configure Google services as Spring beans like this:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;bean class=&quot;com.google.appengine.api.images.ImagesServiceFactory&quot;\n      factory-method=&quot;getImagesService&quot;\/&gt;\n&lt;bean class=&quot;com.google.appengine.api.users.UserServiceFactory&quot;\n      factory-method=&quot;getUserService&quot;\/&gt;\n...\n<\/pre>\n<p>Then you can wire one into the class that needs that service and you&#8217;re done!<\/p>\n<h3>Time zones<\/h3>\n<p>When you use JPA, any date field you&#8217;re trying to persist will get saved using the &#8220;GMT&#8221; timezone. So whenever you want to display dates you have to keep in mind to apply your own timezone to them. When you for example use the  tag which comes with <a href=\"http:\/\/java.sun.com\/products\/jsp\/jstl\/\">JSTL<\/a>, you can configure the time zone in your web.xml like this:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n    &lt;context-param&gt;\n        &lt;param-name&gt;javax.servlet.jsp.jstl.fmt.timeZone&lt;\/param-name&gt;\n        &lt;param-value&gt;GMT+1:00&lt;\/param-value&gt;\n    &lt;\/context-param&gt;\n<\/pre>\n<h2>Conclusion<\/h2>\n<p>On one hand Google App Engine provides a rich set of helpful features and services to help you build better applications. On the other hand you do need to compromise by working around certain issues because the Google App Engine doesn&#8217;t entirely behave as a regular application server. Also the JPA implementation in Google App Engine is very limited. But, with the workarounds in place (which I described above) you should be able to get rid of most of the frustrations you will encounter. This way you can start developing your own application like you&#8217;re used to, with the tools you&#8217;re used to but now using all the cool features the app engine offers!<\/p>\n<p>Unfortunately, some frameworks won&#8217;t work at all on the Google App Engine, not even with a workaround. To quickly find out whether a certain framework will work on the app engine, take a look at this page <a href=\"http:\/\/groups.google.com\/group\/google-appengine-java\/web\/will-it-play-in-app-engine\">http:\/\/groups.google.com\/group\/google-appengine-java\/web\/will-it-play-in-app-engine<\/a>. As long as you&#8217;re not using any frameworks on the &#8220;black list&#8221; you will be fine ^^<\/p>\n<p>If anyone else has workarounds or other helpful classes for the Google App Engine, let me know by posting a comment below. I will include them to this blog post and\/or to the <a href=\"http:\/\/code.google.com\/p\/app-engine-workarounds\/\">app-engine-workarounds<\/a> project.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lately I have been looking into and playing around with the Google App Engine. In this post I want to give a little introduction to the Google App Engine, why it can be interesting and how to work with it.<\/p>\n","protected":false},"author":115,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":""},"categories":[10],"tags":[22,194,11,9,95,30],"class_list":["post-1664","post","type-post","status-publish","format-standard","hentry","category-development","tag-best-practices","tag-google-app-engine","tag-java","tag-open-source","tag-spring-framework","tag-support"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Free Java hosting with the Google App Engine - Trifork Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Free Java hosting with the Google App Engine - Trifork Blog\" \/>\n<meta property=\"og:description\" content=\"Lately I have been looking into and playing around with the Google App Engine. In this post I want to give a little introduction to the Google App Engine, why it can be interesting and how to work with it.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/\" \/>\n<meta property=\"og:site_name\" content=\"Trifork Blog\" \/>\n<meta property=\"article:published_time\" content=\"2010-02-04T14:31:41+00:00\" \/>\n<meta name=\"author\" content=\"Tom van Zummeren\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Tom van Zummeren\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/\",\"url\":\"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/\",\"name\":\"Free Java hosting with the Google App Engine - Trifork Blog\",\"isPartOf\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#website\"},\"datePublished\":\"2010-02-04T14:31:41+00:00\",\"author\":{\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/ae1748819f078cd2f65813bb689e9e0b\"},\"breadcrumb\":{\"@id\":\"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/trifork.nl\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Free Java hosting with the Google App Engine\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/trifork.nl\/blog\/#website\",\"url\":\"https:\/\/trifork.nl\/blog\/\",\"name\":\"Trifork Blog\",\"description\":\"Keep updated on the technical solutions Trifork is working on!\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/trifork.nl\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/ae1748819f078cd2f65813bb689e9e0b\",\"name\":\"Tom van Zummeren\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/882552d346390f25c0ceacb5b6076623?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/882552d346390f25c0ceacb5b6076623?s=96&d=mm&r=g\",\"caption\":\"Tom van Zummeren\"},\"url\":\"https:\/\/trifork.nl\/blog\/author\/tom\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Free Java hosting with the Google App Engine - Trifork Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/","og_locale":"en_US","og_type":"article","og_title":"Free Java hosting with the Google App Engine - Trifork Blog","og_description":"Lately I have been looking into and playing around with the Google App Engine. In this post I want to give a little introduction to the Google App Engine, why it can be interesting and how to work with it.","og_url":"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/","og_site_name":"Trifork Blog","article_published_time":"2010-02-04T14:31:41+00:00","author":"Tom van Zummeren","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Tom van Zummeren","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/","url":"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/","name":"Free Java hosting with the Google App Engine - Trifork Blog","isPartOf":{"@id":"https:\/\/trifork.nl\/blog\/#website"},"datePublished":"2010-02-04T14:31:41+00:00","author":{"@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/ae1748819f078cd2f65813bb689e9e0b"},"breadcrumb":{"@id":"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/trifork.nl\/blog\/free-java-hosting-with-the-google-app-engine\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/trifork.nl\/blog\/"},{"@type":"ListItem","position":2,"name":"Free Java hosting with the Google App Engine"}]},{"@type":"WebSite","@id":"https:\/\/trifork.nl\/blog\/#website","url":"https:\/\/trifork.nl\/blog\/","name":"Trifork Blog","description":"Keep updated on the technical solutions Trifork is working on!","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/trifork.nl\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/ae1748819f078cd2f65813bb689e9e0b","name":"Tom van Zummeren","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/trifork.nl\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/882552d346390f25c0ceacb5b6076623?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/882552d346390f25c0ceacb5b6076623?s=96&d=mm&r=g","caption":"Tom van Zummeren"},"url":"https:\/\/trifork.nl\/blog\/author\/tom\/"}]}},"_links":{"self":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/1664","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/users\/115"}],"replies":[{"embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/comments?post=1664"}],"version-history":[{"count":0,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/posts\/1664\/revisions"}],"wp:attachment":[{"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/media?parent=1664"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/categories?post=1664"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/trifork.nl\/blog\/wp-json\/wp\/v2\/tags?post=1664"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}