0

I have a Scalatra 2.3 application and it uses Dispatch to contact a legacy service for some data. Some of the API calls are using cookie based auth. I would like to avoid doing login separately for every request to those secure endpoints.

My first attempt was a companion object having a var for cookie and a function getCookie. This would either return stored cookie from var or do an authentication, store received cookie to var and return it. In case of invalid cookie the logic would clear the cookie, retrieve it and retry the call.

object LegacyService {
  var cookie : Option[Cookie] = None

  def getCookie() :  Option[Cookie] = {
    this.synchronized {
    if (!cookie.isDefined) {
      def loginRequest = dispatch.url...

      val result = (for (r <- Http(loginRequest)) yield r.getCookies).apply()
      if (result.isEmpty) {
        cookie = None
      } else {
        cookie = Some(result.get(0))
      }
    }
    cookie
  }
}

Things seemed to work fine until I was doing more than one parallel async calls requiring cookie to legacy service. For some reason this caused some kind of deadlock until app restart so with deadline ahead I ended up throwing this cookie storing code away.

Any advices how to do this properly?

Petteri H
  • 11,779
  • 12
  • 64
  • 94

1 Answers1

2
  1. I would try to do this cookie caching, with guava cache

https://code.google.com/p/guava-libraries/wiki/CachesExplained

    LoadingCache<Key, Cookie> graphs = CacheBuilder.newBuilder()
       .expireAfterAccess(10, TimeUnit.MINUTES)
       .build(
           new CacheLoader<Key, Cookie>() {
             public Cookie load(Key key) { // no checked exception
                def loginRequest = dispatch.url...

                val result = (for (r <- Http(loginRequest)) yield r.getCookies).apply()
                if (result.isEmpty) {
                    throw new RuntimeException();
                }

                return result.get(0)
            };
       });

It might solve deadlock issue, since all synchronization will be handled by guava.

  1. Another option is using some Scala wrap up for cache, for example mango (which wraps guava cache)

https://github.com/feijoas/mango

So this would look like:

import org.feijoas.mango.common.base.Suppliers._

// a supplier is just a function () => Cookie
val supplier = () => getCookie ...          //> supplier  : () => Int 
                                            //= function0
// convert to a Guava supplier
val gSupplier = supplier.asJava             //> gSupplier  : com.google.common.base.Supplier[Cookie] 
                                            //= AsGuavaSupplier(function0)

// create s supplier that memoize its return
// value for 10 seconds
val memSupplier = memoizeWithExpiration(supplier, 10, TimeUnit.SECONDS)
                                              //> memSupplier  : () => Cookie  
                                              //= Suppliers.memoizeWithExpiration(function0, 10, SECONDS)

Basically, what I'm trying to say, is instead of solving deadlock in your code, switch to commonly used tested Cache libraries like guava, which should not have any concurrent issues.

mavarazy
  • 7,562
  • 1
  • 34
  • 60