我正在Scala中迈出第一步,并尝试实现使用Twitter流API的应用程序。下面是我的代码(隐藏用户令牌)。从main函数,我调用getStreamData
函数,调用makeAPIrequest
。
package com.myname.myapp
import java.net.URL
import javax.net.ssl.HttpsURLConnection
import java.io.InputStream
import java.io.OutputStream;
import scala.io.Source
import java.net.URLEncoder
import java.util.Base64
import java.nio.charset.StandardCharsets
import scala.collection.immutable.HashMap
import java.util.Calendar
import java.io.Serializable
import scala.collection.immutable.TreeMap
import javax.crypto
import java.security.SecureRandom
import java.math.BigInteger
import scala.util.Random
object TwitterConnector {
private val AUTH_URL: String = "https://api.twitter.com/oauth2/token"
private val CONSUMER_KEY: String = "mykey"
private val CONSUMER_SECRET: String = "mysecret"
private val STREAM_URL: String = "https://stream.twitter.com/1.1/statuses/filter.json"
private var TOKEN: String = "mytoken"
private var TOKEN_SECRET: String = "mytokensecret"
def getStreamData {
val data = "track=" + "twitter"
makeAPIrequest(HTTPmethod("POST"), "https://stream.twitter.com/1.1/statuses/filter.json", None, Option(data))
}
private def makeAPIrequest(method: HTTPmethod, url:String, urlParams:Option[String], data:Option[String]){
//form oauth parameters
val oauth_nonce = Random.alphanumeric.take(32).mkString
val oauth_signature_method: String = "HMAC-SHA1"
val oauth_version: String = "1.0"
val oauth_timestamp = (Calendar.getInstance.getTimeInMillis/1000).toString()
var signatureData = scala.collection.mutable.Map(("oauth_consumer_key", CONSUMER_KEY), ("oauth_token", TOKEN), ("oauth_signature_method", oauth_signature_method), ("oauth_nonce", oauth_nonce), ("oauth_timestamp", oauth_timestamp), ("oauth_version", oauth_version))
//find keys for parameters
val getParams = (parameter: String) => {
val arr = parameter.split("=")
if(arr.length == 1) return
val key = arr(0).asInstanceOf[String]
val value = arr(1).asInstanceOf[String]
signatureData(key) = value
}
val params = urlParams match {
case Some(value) => {
val result = urlParams.get
result.split("&").foreach {getParams}
result
}
case None => ""
}
val postData = data match {
case Some(value) => {
val result = data.get
result.split("&").foreach {getParams}
result
}
case None => ""
}
//url-encode headers data
signatureData.foreach { elem => {
signatureData.remove(elem._1)
signatureData(urlEnc(elem._1)) = urlEnc(elem._2)
}
}
println(signatureData)
//sort headers data
val sortedSignatureData = TreeMap(signatureData.toSeq:_*)
println("Sorted: " + sortedSignatureData)
//form output string
var parameterString = ""
sortedSignatureData.foreach(elem => {
if(parameterString.length() > 0){
parameterString += "&"
}
parameterString += elem._1 + "=" + elem._2
})
val outputString = method.method.toUpperCase() + "&" + urlEnc(url) + "&" + urlEnc(parameterString)
val signingKey = urlEnc(CONSUMER_SECRET) + "&" + urlEnc(TOKEN_SECRET)
println(outputString)
println(signingKey)
val SHA1 = "HmacSHA1";
val key = new crypto.spec.SecretKeySpec(bytes(signingKey), SHA1)
val oauth_signature = {
val mac = crypto.Mac.getInstance(SHA1)
mac.init(key)
new String(base64(mac.doFinal(bytes(outputString)).toString()))
}
println("Signature: " + oauth_signature)
val authHeader: String = "OAuth oauth_consumer_key=\"" + urlEnc(CONSUMER_KEY) + "\", oauth_nonce=\"" + urlEnc(oauth_nonce) + "\", oauth_signature=\"" + urlEnc(oauth_signature) + "\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"" + urlEnc(oauth_timestamp) + "\", oauth_token=\"" + urlEnc(TOKEN) + "\", oauth_version=\"1.0\""
println(authHeader)
var text = url
if(params.length > 0){
text += "?"
}
val apiURL: URL = new URL(text + params)
val apiConnection: HttpsURLConnection = apiURL.openConnection.asInstanceOf[HttpsURLConnection]
apiConnection.setRequestMethod(method.method)
apiConnection.setRequestProperty("Authorization", authHeader)
apiConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
if(method.method == "POST" && postData.length() > 0){
println("POSTING ", postData)
apiConnection.setDoOutput(true)
val outStream: OutputStream = apiConnection.getOutputStream
outStream.write(postData.getBytes())
}
val inStream: InputStream = apiConnection.getInputStream
val serverResponse = Source.fromInputStream(inStream).mkString
println(serverResponse)
}
private def bytes(str: String) = str.getBytes("UTF-8")
private def urlEnc(str: String) = URLEncoder.encode(str, "UTF-8").replace(" ", "%20")
private def base64(str: String) = Base64.getEncoder.encodeToString(str.getBytes(StandardCharsets.UTF_8))
}
Twitter返回401代码响应。
显然,我做错了什么。你能指出我的错误在哪里吗?
答案 0 :(得分:1)
我建议使用更好的库来发出Web请求,例如Play Framework中的WS库。现在,你有点在Scala中编写Java。以下是WS库的示例用法:
val clientConfig = new DefaultWSClientConfig()
val secureDefaults: com.ning.http.client.AsyncHttpClientConfig = new NingAsyncHttpClientConfigBuilder(clientConfig).build()
val builder = new com.ning.http.client.AsyncHttpClientConfig.Builder(secureDefaults)
builder.setCompressionEnabled(true)
val secureDefaultsWithSpecificOptions: com.ning.http.client.AsyncHttpClientConfig = builder.build()
implicit val implicitClient = new play.api.libs.ws.ning.NingWSClient(secureDefaultsWithSpecificOptions)
val oauthCalc = OAuthCalculator(ConsumerKey(TwitterConfig.consumerKey, TwitterConfig.consumerSecret), RequestToken(TwitterConfig.accessKey, TwitterConfig.accessSecret))
def lookup(ids: List[String]): Future[List[Tweet]] =
WS.clientUrl(`statuses/lookup`)
.withQueryString("id" -> ids.mkString(","))
.sign(oauthCalc)
.get()
.map { r =>
JsonHelper.deserialize[List[Tweet]](r.body)
}
您应该可以非常轻松地修改此示例以使用流API。