Skip to content

Instantly share code, notes, and snippets.

@tototoshi
Last active December 22, 2015 12:39
Show Gist options
  • Save tototoshi/6473958 to your computer and use it in GitHub Desktop.
Save tototoshi/6473958 to your computer and use it in GitHub Desktop.
Retrieve request token asynchronously
package com.github.tototoshi.oauth
import com.ning.http.client._
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.net.URLEncoder
import org.apache.commons.codec.binary.Base64
import scala.concurrent.{Future, Promise}
import com.github.kxbmap.configs._
import com.typesafe.config.ConfigFactory
import scala.util.parsing.combinator._
object Main {
private val config = ConfigFactory.load()
private val signatureMethod = "HMAC-SHA1"
private val oauthVersion = "1.0a"
private val consumerKey = config.get[String]("twitter.consumer.key")
private val consumerSecret = config.get[String]("twitter.consumer.secret")
private val requestTokenUrl = "https://api.twitter.com/oauth/request_token"
case class RequestToken(token: String, tokenSecret: String, callbackConfirmed: Boolean)
object RequestTokenParser extends RegexParsers {
def oauthToken = "oauth_token" ~ "=" ~> """\w+""".r
def oauthTokenSecret = "oauth_token_secret" ~ "=" ~> """\w+""".r
def t = "true" ^^ { _ => true }
def f = "false" ^^ { _ => false }
def oauthCallbackConfirmed = "oauth_callback_confirmed" ~ "=" ~> (t | f)
def response = oauthToken ~ ("&" ~> oauthTokenSecret) ~ ("&" ~> oauthCallbackConfirmed) ^^ {
case token ~ secret ~ confirmed => RequestToken(token, secret, confirmed)
}
def parse(in: String) = parseAll(response, in)
}
class RequestTokenParseException(message: String) extends Exception(message)
def main(args: Array[String]): Unit = {
import scala.concurrent.ExecutionContext.Implicits.global
val client = new AsyncHttpClient()
val token = retrieveRequestToken(client)
token.onSuccess {
case token => println(token)
}
token.onFailure {
case e => e.printStackTrace()
}
token.onComplete { _ =>
client.close()
}
}
def retrieveRequestToken(client: AsyncHttpClient): Future[RequestToken] = {
val unsignedParams = paramsForRequestToken
val signedRequestParams = sign(unsignedParams)
val url = requestTokenUrl + "?" + signedRequestParams
var result = Promise[RequestToken]()
client.prepareGet(url).execute(new AsyncCompletionHandler[Response]() {
override def onCompleted(response: Response): Response = {
val body = response.getResponseBody
val parseResult = RequestTokenParser.parse(body)
if (parseResult.successful) {
result.success(parseResult.get)
} else {
result.failure(new RequestTokenParseException("Parse Failed: " + body))
}
response
}
override def onThrowable(t: Throwable) = {
result.failure(t)
}
})
result.future
}
private def sign(unsigned: String): String = {
def getSignature(signatureBaseString: String): String = {
val algorithm = "HmacSHA1"
val mac = Mac.getInstance(algorithm)
val keyString = consumerSecret + "&"
val key = new SecretKeySpec(keyString.getBytes(), algorithm)
mac.init(key)
val digest = mac.doFinal(signatureBaseString.getBytes())
URLEncoder.encode(Base64.encodeBase64String(digest), "UTF-8")
}
val baseString =
"GET&" + URLEncoder.encode(requestTokenUrl, "UTF-8") + "&" + URLEncoder.encode(unsigned, "UTF-8")
unsigned + "&" + "oauth_signature" + "=" + getSignature(baseString)
}
private def paramsForRequestToken: String = {
def nonce = (1 to 8).map(_ => scala.util.Random.nextPrintableChar).mkString
val params = Map(
"oauth_consumer_key" -> consumerKey,
"oauth_nonce" -> nonce,
"oauth_signature_method" -> signatureMethod,
"oauth_timestamp" -> (System.currentTimeMillis() / 1000).toString,
"oauth_version" -> oauthVersion
)
params.toList.sorted.map{ case (k, v) => k + "=" + URLEncoder.encode(v, "UTF-8") }.mkString("&")
}
}
import sbt._
import sbt.Keys._
object ScalaoauthasyncBuild extends Build {
lazy val scalaoauthasync = Project(
id = "scala-oauth-async",
base = file("."),
settings = Project.defaultSettings ++ Seq(
name := "scala-oauth-async",
organization := "com.github.tototoshi",
version := "0.1-SNAPSHOT",
scalaVersion := "2.10.2",
libraryDependencies ++= Seq(
"com.ning" % "async-http-client" % "1.7.19",
"commons-codec" % "commons-codec" % "1.8",
"com.github.kxbmap" %% "configs" % "0.2.0"
)
)
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment