Grails on Google App Engine – Part 5: Conclusion

October 17, 2010 at 5:12 pm | Posted in GAE, Grails, Groovy, Java, Web | 4 Comments
Tags: , , , , , , , ,

Thanks to the App Engine plugin you can get Grails running on App Engine. Yeah, you got your favorite web application framework deployed on the cloud. But wait…there’s a problem. Grails comes with a lot of libraries that are bundled with the framework. You got Spring, Hibernate, Sitemesh and all the other libraries you added to your application. This is how we’re used to build applications. However, building applications for Google App Engine is different. The platform puts your application into sleep mode when you don’t get enough traffic. This is understandable from Google’s view on scalability but not from a software developer standpoint. Calling the application after a period of no usage will reinitialize your classes, Spring application contexts and libraries. If the application cannot serve the request within 30 seconds the user will get a com.google.apphosting.api.DeadlineExceededException. The cold startup time will even be longer the bigger your WAR file is and the more libraries you use. This gets extremely painful with Grails. Sometimes you have to call your application three times in a row, run into an initialization error and try again until Grails can serve your request. Google was talking about “reserved instances” that would not put your application into sleep mode for paying customers. However, this has not been realized yet. The GAE/J forum is full of frustrated users complaining about this issue. You can work around the cold startup time by setting up a cron job in cron.xml that pings your application every minute. From an architectural view this not only is unnecessary and useless traffic for your application it also will not fully solve the problem. I still ran into issues with requests that couldn’t finish with the deadline time period.

<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
   <cron>
      <url>/user/ping</url>
      <description>Keep the app alive by ping every 1 minutes</description>
      <schedule>every 1 minutes</schedule>
   </cron>
</cronentries>

Clearly, App Engine calls for a lightweight application framework until the cold startup issues have been resolved by Google in some sort of form. This puts Grails out of the running as a production-ready framework on App Engine. No company can afford exceptions being thrown on paying customers. This is not an issue solely to Grails. I am sure the startup times for full-fledged Spring applications on App Engine are not extremely better. An alternative for a Groovy-based web framework can be seen in Gaelyk. Gaelyk is not as feature-rich as Grails but will give you the benefit of faster startup times. Implementing non-trivial web applications with Gaeylk can be painful to develop because it’s not as feature-rich as Grails is. I guess in the end there’s always a tradeoff.

More on this topic:

Advertisements

Grails on Google App Engine – Part 4: Third-Party Libraries

October 17, 2010 at 5:11 pm | Posted in GAE, Grails, Groovy, Java, Web | Leave a comment
Tags: , , , , , , , , ,

Certain aspects of web application development have already been solved by third-party libraries. Why implement a solution yourself when it’s already out there?! Not every existing library will work with App Engine though. Google published a page listing compatible libraries and frameworks for their platform. I will describe two very common use cases that I chose to use in my application: Security with Spring Security and making HTTP calls using HttpClient.

Security

Spring Security fits right into the Grails technology stack and it a proven solution for authenticating/authorizing users. This section will only describe the steps necessary to specifically integrate Spring Security with Grails on App Engine. For a detailed configuration description please refer to the Spring Security documentation. I set up my application with version 3.0.3 by dropping in the JAR files into the lib directory. Spring Security in it’s default configuration requires your container to support sessions. This is no problem for App Engine. You have to make sure though that you enable them. The configuration file appengine-web.xml has to have this entry to make it work:

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
   ...
   <sessions-enabled>true</sessions-enabled> 
   ...
</appengine-web-app>

First of all I declared my domain objects User and Role. Please see the Spring Security database schema for more information.

package com.favalike

import com.google.appengine.api.datastore.Key
import javax.jdo.annotations.*

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
class User {
   @PrimaryKey
   @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   Key key

   @Persistent
   String username

   @Persistent
   String password

   @Persistent
   boolean enabled

   @Persistent(defaultFetchGroup = "true")
   List<Role> authorities = new ArrayList<Role>()
}
package com.favalike

import com.google.appengine.api.datastore.Key
import javax.jdo.annotations.*

@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
class Role {
   @PrimaryKey
   @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   Key key

   @Persistent
   String authority

   @Persistent
   User user
}

Because Spring Security doesn’t define a JDO UserDetailsService implementation that would be compatible with App Engine you have to write it yourself.

package com.favalike.security

import org.apache.commons.lang.StringUtils
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.context.support.MessageSourceAccessor
import org.springframework.dao.DataAccessException
import org.springframework.dao.DataRetrievalFailureException
import org.springframework.security.core.SpringSecurityMessageSource
import org.springframework.security.core.authority.GrantedAuthorityImpl
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.authentication.BadCredentialsException

class JdoUserDetailsService implements UserDetailsService {
   static final Log log = LogFactory.getLog(JdoUserDetailsService)
   def userService
   MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor()

   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
      if(StringUtils.isBlank(username)) {
         log.error "Username was empty. Authentication failed."
         throw new BadCredentialsException(messages.getMessage("login.username.blank", [] as Object[], "Username may not be empty"), username)
      }

      def user = null

      try {
         user = userService.findUserByUsername(username)
      }
      catch(Exception e) {
         log.error "Problem retrieving user from database", e
         throw new DataRetrievalFailureException(messages.getMessage("login.database.error", [] as Object[], "User could not be retrieved from database. Please try later"))
      }

      if(user == null) {
         log.error "User for username '${username}' not found in database. Authentication failed."
         throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.notFound", [username] as Object[], "Username {0} not found"), username)
      }

      if(log.infoEnabled) {
         log.info "Found user for username '${username}': ${user}"
      }

      def grantedAuthorities = []

      user.authorities.each { role ->
         grantedAuthorities << new GrantedAuthorityImpl(role.authority)
      }

      if(grantedAuthorities.size() == 0) {
         log.error "User needs to have at least one granted authority!"
         throw new UsernameNotFoundException(messages.getMessage("JdbcDaoImpl.noAuthority", [username] as Object[], "User {0} has no GrantedAuthority"), username)
      }

      if(log.infoEnabled) {
         log.info "User found for username '${username}'."
      }

      return new User(user.username, user.password, user.enabled, true, true, true, grantedAuthorities)
   }
}

I was able to put all the Spring wiring for Spring Security in grails-app/conf/spring/resources.groovy. My configuration is pretty much the ordinary security setup you would have to define for every application using Spring Security except that I defined it in Groovy.

beans = {
   // Security
   xmlns security: 'http://www.springframework.org/schema/security'

   jdoUserDetailsService(com.favalike.security.JdoUserDetailsService) {
      userService = ref('userService')
   }

   passwordEncoder(org.springframework.security.authentication.encoding.Md5PasswordEncoder)

   security.'http'('auto-config': true, 'access-denied-page': '/user/index') {
      security.'intercept-url'('pattern': '/user/index*', 'filters': 'none')
      security.'intercept-url'('pattern': '/user/signup', 'filters': 'none')
      security.'intercept-url'('pattern': '/favicon.ico', 'filters': 'none')
      security.'intercept-url'('pattern': '/**/*.html', 'access': 'ROLE_USER')
      security.'intercept-url'('pattern': '/**/*.js', 'access': 'ROLE_USER')
      security.'intercept-url'('pattern': '/**/*.gsp', 'access': 'ROLE_USER')
      security.'intercept-url'('pattern': '/**', 'access': 'ROLE_USER')
      security.'form-login'('authentication-failure-url': '/user/index?login_error=1', 'default-target-url': '/', 'login-page': '/user/index')
      security.'remember-me'('key': '99sdfll74soq')
      security.'logout'('logout-success-url': '/user/index')
   }

   security.'authentication-manager'('alias': 'authenticationManager') {
      security.'authentication-provider'('user-service-ref': 'jdoUserDetailsService') {
         security.'password-encoder'('hash': 'md5')
      }
   }
}

HTTP Calls

App Engine provides you with the URL Fetch API right out of the box. Even though the API does its job it is relatively feature-less. If you need more advanced feature sets like support for cookies you can turn to HttpClient. All you need to do is to download version 4 of the HttpComponents and drop the libraries into your lib directory. HttpClient needs some additional work to make it run on App Engine. It usually works with java.net package which is not allowed on App Engine. This posting shows you a way to work around it.

package com.favalike.http

import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.apache.http.Header
import org.apache.http.HttpEntity
import org.apache.http.client.HttpClient
import org.apache.http.client.methods.HttpGet
import org.apache.http.client.methods.HttpUriRequest
import org.apache.http.conn.ClientConnectionManager
import org.apache.http.impl.client.DefaultHttpClient
import org.apache.http.params.BasicHttpParams
import org.apache.http.params.CoreConnectionPNames
import org.apache.http.params.CoreProtocolPNames
import org.apache.http.params.HttpParams
import org.apache.http.util.EntityUtils

class CommonsHttpRequestExecutor implements HttpRequestExecutor {
   static final Log log = LogFactory.getLog(CommonsHttpRequestExecutor)
   final Integer timeout

   CommonsHttpRequestExecutor(timeout) {
      this.timeout = timeout
   }

   def HttpResponse sendGetRequest(String url) {
      HttpGet httpGet = new HttpGet(url);

      if(log.debugEnabled) {
         log.debug("Sending GET request to URL '${httpget.getURI()}'")
      }

      executeHttpRequest(httpGet);
   }

   def executeHttpRequest(HttpUriRequest httpUriRequest) {
      HttpResponse httpResponse = new HttpResponse()
      HttpClient httpclient = getDefaultHttpClient()

      try {
         def response = httpclient.execute(httpUriRequest)
         populateResponseData(response, httpResponse)
      }
      catch(Exception e) {
         log.error("Error accessing site for url '${httpUriRequest.getURI()}'", e)
         httpResponse.errorMessage = e.message
         httpUriRequest.abort()
      }
      finally {
         httpclient.getConnectionManager().shutdown();
      }

      httpResponse
   }

   def getDefaultHttpClient() {
      ClientConnectionManager clientConnectionManager = new GAEConnectionManager();
      HttpParams params = new BasicHttpParams();
      params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
      params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8");    
      new DefaultHttpClient(clientConnectionManager, params)
   }

   def populateResponseData(actualResponse, httpResponse) {
      httpResponse.statusCode = actualResponse.statusLine.statusCode
      httpResponse.reasonPhrase = actualResponse.statusLine.reasonPhrase
      httpResponse.statusLine = actualResponse.statusLine.toString()
      HttpEntity entity = actualResponse.getEntity();
      
      if(entity != null) {
         byte[] content = EntityUtils.toByteArray(entity)
         Header header = entity.getContentType() 
         httpResponse.responseBody = new String(content)
      }
   }
}

More on this topic:

Create a free website or blog at WordPress.com.
Entries and comments feeds.