views:

58

answers:

1

I need make a GET call to a REST api which is rate limited. I can find out what the current rate limit is by making a call and checking the HTTP headers. If I've exceeded my rate limit, I should wait for a bit before retrying. I'd like to write something like:

val conn = connect(url, _.getHeaderField("X-RateLimit-Remaining").toInt > 0, 500)

I have a working solution using a var, a while loop and some repetitious code, but it feels clunky:

def connect(url: String, succeeded: URLConnection=>Boolean, waitMillis: Int) = {
  var conn = new URL(url).openConnection
  while (!succeeded(conn)) {
    Thread.sleep(waitMillis)
    conn = new URL(url).openConnection
  }
  conn
}

Is there a cleaner way to do this?

+2  A: 

You could make it tail-recursive:

def connect(url: String, succeeded: URLConnection=>Boolean, wait: Int): URLConnection = {
  val conn = new URL(url).openConnection
  if (succeeded(conn)) conn
  else {
    Thread.sleep(wait)
    connect(url,succeeded,wait)
  }
}

Or you could use an infinite iterator pattern, either raw:

def connect(url: String, succeeded: URLConnection=>Boolean, waitMillis: Int) = {
  val tries = Iterator.continually( new URL(url).openConnection )
  tries.dropWhile(
    conn => if (succeeded(conn)) false else { Thread.sleep(waitMillis); true }
  ).next
}

or by wrapping the URL call in a wait that returns an option (especially useful if you want option handling elsewhere; not sure whether you want to embed the wait there or outside):

def attemptConnect(url: String, succeeded: URLConnection=>Boolean, waitMillis: Int) = {
  val conn = new URL(url).openConnection
  if (succeeded(conn)) Some(conn)
  else { Thread.sleep(waitMillis); None }
}
def connect(url: String, succeeded: URLConnection=>Boolean, waitMillis: Int) = {
  val tries = Iterator.continually( attemptConnect(url,succeeded,waitMillis) )
  tries.dropWhile(_.isEmpty).next
}
Rex Kerr
That last one worked best for me. Thanks! Note though that you need to write "tries.dropWhile(\_.isEmpty).take(1)" or you're taking the first item _after_ the successful connection (dropWhile removes one extra item from the iterator itself, but returns an iterator including that item).
Steve
@Steve - Ack, indeed. How careless of me! Fixed now.
Rex Kerr
@Steve - Also, if you want a max number of tries, note that you can `tries.take(max_number).dropWhile(_.isEmpty).take(1).toList.flatten.headOption` to get either `Some(URLConnection)` or `None`, depending on whether the max number of tries was exhausted.
Rex Kerr
I'd use `tries.head`.
Daniel
@Daniel - I would if `.head` was defined for `Iterator`, or if I were using a stream.
Rex Kerr