Scala - Twitter API returns 401 for oauth

Scala - Twitter API returns 401 for oauth

我正在 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 returns 我的 401 代码响应。

显然,我做错了什么。你能指出我的错误在哪里吗?

我建议使用更好的库来发出 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。