All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalaj.http.Http.scala Maven / Gradle / Ivy

package scalaj.http

import java.net.{HttpURLConnection, URL, URLEncoder, URLDecoder}
import java.io.{DataOutputStream, InputStream, BufferedReader, InputStreamReader, ByteArrayOutputStream}
import org.apache.commons.codec.binary.Base64
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
import javax.net.ssl.HostnameVerifier
import java.security.cert.X509Certificate
import scala.xml.{Elem, XML}


object HttpOptions {
  type HttpOption = HttpURLConnection => Unit
  
  def method(method: String):HttpOption = c => c.setRequestMethod(method)
  def connTimeout(timeout: Int):HttpOption = c => c.setConnectTimeout(timeout)
  def readTimeout(timeout: Int):HttpOption = c => c.setReadTimeout(timeout)
  def allowUnsafeSSL:HttpOption = c => c match {
    case httpsConn: HttpsURLConnection => 
      val hv = new HostnameVerifier() {
        def verify(urlHostName: String, session: SSLSession) = true
      }
      httpsConn.setHostnameVerifier(hv)
      
      val trustAllCerts = Array[TrustManager](new X509TrustManager() {
        def getAcceptedIssuers: Array[X509Certificate] = null
        def checkClientTrusted(certs: Array[X509Certificate], authType: String){}
        def checkServerTrusted(certs: Array[X509Certificate], authType: String){}
      })

      val sc = SSLContext.getInstance("SSL")
      sc.init(null, trustAllCerts, new java.security.SecureRandom())
      httpsConn.setSSLSocketFactory(sc.getSocketFactory())
    case _ => // do nothing
  }
  def sslSocketFactory(sslSocketFactory: SSLSocketFactory): HttpOption = c => c match {
    case httpsConn: HttpsURLConnection =>
      httpsConn.setSSLSocketFactory(sslSocketFactory) 
    case _ => // do nothing
  }
}

object MultiPart {
  def apply(name: String, filename: String, mime: String, data: String): MultiPart = MultiPart(name, filename, mime, data.getBytes(Http.charset))
}

case class MultiPart(val name: String, val filename: String, val mime: String, val data: Array[Byte])

case class HttpException(val code: Int, val message: String, val body: String) extends RuntimeException(code + ": " + message)

object Http {
  def apply(url: String):Request = get(url)
  
  type HttpExec = (Request, HttpURLConnection) => Unit
  type HttpUrl = Request => URL
  
  object Request {
    def apply(exec: HttpExec, url: HttpUrl, method:String):Request = Request(method, exec, url, Nil, Nil, HttpOptions.method(method)::defaultOptions)
  }
  case class Request(method: String, exec: HttpExec, url: HttpUrl, params: List[(String,String)], headers: List[(String,String)], options: List[HttpOptions.HttpOption]) {
    def params(p: (String, String)*):Request = params(p.toList)
    def params(p: List[(String,String)]):Request = Request(method, exec,url, p, headers,options)
    def headers(h: (String,String)*):Request = headers(h.toList)
    def headers(h: List[(String,String)]):Request = Request(method,exec,url, params, h ++ headers,options)
    def param(key: String, value: String):Request = Request(method,exec,url,(key,value)::params, headers,options)
    def header(key: String, value: String):Request = Request(method,exec,url,params, (key,value)::headers,options)
    def options(o: HttpOptions.HttpOption*):Request = options(o.toList)
    def options(o: List[HttpOptions.HttpOption]):Request = Request(method,exec, url, params, headers, o ++ options)
    def option(o: HttpOptions.HttpOption):Request = Request(method,exec,url, params, headers,o::options)
    
    def auth(user: String, password: String) = header("Authorization", "Basic " + base64(user + ":" + password))
    
    def oauth(consumer: Token):Request = oauth(consumer, None, None)
    def oauth(consumer: Token, token: Token):Request = oauth(consumer, Some(token), None)
    def oauth(consumer: Token, token: Token, verifier: String):Request = oauth(consumer, Some(token), Some(verifier))
    def oauth(consumer: Token, token: Option[Token], verifier: Option[String]):Request = OAuth.sign(this, consumer, token, verifier)
    
    def getUrl: URL = url(this)
    
    def apply[T](parser: InputStream => T): T = process((conn:HttpURLConnection) => tryParse(conn.getInputStream(), parser))
    
    def process[T](processor: HttpURLConnection => T): T = {

      url(this).openConnection match {
        case conn:HttpURLConnection =>
          conn.setInstanceFollowRedirects(true)
          headers.reverse.foreach{case (name, value) => 
            conn.setRequestProperty(name, value)
          }
          options.reverse.foreach(_(conn))

          exec(this, conn)
          try {
            processor(conn)
          } catch {
            case e: java.io.IOException =>
              throw new HttpException(conn.getResponseCode, conn.getResponseMessage, tryParse(conn.getErrorStream(), readString))
          }
      }
    }
    
    def getResponseHeaders(conn: HttpURLConnection): Map[String, String] = {
      // according to javadoc, there can be a headerField value where the HeaderFieldKey is null
      // at the 0th row in some implementations.  In that case it's the http status line
      Stream.from(0).map(i => i -> conn.getHeaderField(i)).takeWhile(_._2 != null).map{ case (i, value) =>
        Option(conn.getHeaderFieldKey(i)).getOrElse("Status") -> value
      }.toMap
    }
    
    def responseCode: Int = process((conn:HttpURLConnection) => conn.getResponseCode)
    
    def asCodeHeaders: (Int, Map[String, String]) = process { conn: HttpURLConnection => 
      (conn.getResponseCode, getResponseHeaders(conn))
    }
    
    def asHeadersAndParse[T](parser: InputStream => T): (Int, Map[String, String], T) = process { conn: HttpURLConnection =>
      (conn.getResponseCode, getResponseHeaders(conn), tryParse(conn.getInputStream(), parser))
    }
    
    
    def asBytes: Array[Byte] = apply(readBytes)
    
    def asString: String = apply(readString)
    
    def asXml: Elem = apply(is => XML.load(is))
    
    def asParams: List[(String,String)] = {
      asString.split("&").flatMap(_.split("=") match {
        case Array(k,v) => Some(urlDecode(k), urlDecode(v))
        case _ => None
      }).toList
    }
    
    def asParamMap: Map[String, String] = Map(asParams:_*)
    
    def asToken: Token = {
      val params = asParamMap
      Token(params("oauth_token"), params("oauth_token_secret"))
    }
  }
  
  def tryParse[E](is: InputStream, parser: InputStream => E):E = try {
    parser(is)
  } finally {
    is.close
  }
  
  /**
   * [lifted from lift]
   */
  def readString(is: InputStream): String = {
    val in = new InputStreamReader(is, charset)
    val bos = new StringBuilder
    val ba = new Array[Char](4096)

    def readOnce {
      val len = in.read(ba)
      if (len > 0) bos.append(ba, 0, len)
      if (len >= 0) readOnce
    }

    readOnce

    bos.toString
  }
  
  
  /**
   * [lifted from lift]
   * Read all data from a stream into an Array[Byte]
   */
  def readBytes(in: InputStream): Array[Byte] = {
    val bos = new ByteArrayOutputStream
    val ba = new Array[Byte](4096)

    def readOnce {
      val len = in.read(ba)
      if (len > 0) bos.write(ba, 0, len)
      if (len >= 0) readOnce
    }

    readOnce

    bos.toByteArray
  }

  val defaultOptions = List(HttpOptions.connTimeout(100), HttpOptions.readTimeout(500))
  
  def urlEncode(name: String): String = URLEncoder.encode(name, charset)
  def urlDecode(name: String): String = URLDecoder.decode(name, charset)
  def base64(bytes: Array[Byte]) = new String(Base64.encodeBase64(bytes))
  def base64(in: String): String = base64(in.getBytes(charset))
  
  def toQs(params: List[(String,String)]) = params.map(p => urlEncode(p._1) + "=" + urlEncode(p._2)).mkString("&")
  def appendQs(url:String, params: List[(String,String)]) = url + (if(params.isEmpty) "" else {
    (if(url.contains("?")) "&" else "?") + toQs(params)
  })
  
  def appendQsHttpUrl(url: String): HttpUrl = r => new URL(appendQs(url, r.params))
  def noopHttpUrl(url :String): HttpUrl = r => new URL(url)
  
  def get(url: String): Request = {
    val getFunc: HttpExec = (req,conn) => conn.connect
    
    Request(getFunc, appendQsHttpUrl(url), "GET")
  }
  
  
  val CrLf = "\r\n"
  val Pref = "--"
  val Boundary = "gc0pMUlT1B0uNdArYc0p"
 
  def multipart(url: String, parts: MultiPart*): Request = {
     val postFunc: Http.HttpExec = (req,conn) => {

       conn.setDoOutput(true)
       conn.setDoInput(true)
       conn.setUseCaches(false)
       conn.setRequestMethod("POST")
       conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + Boundary)
       conn.setRequestProperty("MIME-Version", "1.0")

       val out = new DataOutputStream(conn.getOutputStream())

       req.params.foreach {
         case (name, value) =>
           out.writeBytes(Pref + Boundary + CrLf)
           out.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"")
           out.writeBytes(CrLf + CrLf + value.toString + CrLf)
       }

       parts.foreach { part =>
         out.writeBytes(Pref + Boundary + CrLf)
         out.writeBytes("Content-Disposition: form-data; name=\"" + part.name + "\"; filename=\"" + part.filename + "\"" + CrLf)
         out.writeBytes("Content-Type: " + part.mime + CrLf + CrLf)

         out.write(part.data)

         out.writeBytes(CrLf + Pref + Boundary + Pref + CrLf)
       }


       out.flush()
       out.close()
     }
     Http.Request(postFunc, Http.noopHttpUrl(url), "POST")
  }
  
  def postData(url: String, data: String): Request = postData(url, data.getBytes(charset))
  def postData(url: String, data: Array[Byte]): Request = {
    val postFunc: HttpExec = (req,conn) => {
      conn.setDoOutput(true)
      conn.connect
      conn.getOutputStream.write(data)
    }
    Request(postFunc, noopHttpUrl(url), "POST")
  }
  
  def post(url: String): Request = {
    val postFunc: HttpExec = (req,conn) => {
      conn.setDoOutput(true)
      conn.connect
      conn.getOutputStream.write(toQs(req.params).getBytes(charset))
    }
    Request(postFunc, noopHttpUrl(url), "POST").header("content-type", "application/x-www-form-urlencoded")
  }
  val charset = "UTF-8"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy