PageRenderTime 33ms CodeModel.GetById 14ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/scala/dispatch/Http.scala

http://scouchdb.googlecode.com/
Scala | 226 lines | 161 code | 28 blank | 37 comment | 6 complexity | a617c6d81a4be109607990b7649d2b26 MD5 | raw file
  1package dispatch
  2
  3import java.io.{InputStream,OutputStream,BufferedInputStream,BufferedOutputStream}
  4
  5import org.apache.http._
  6import org.apache.http.client._
  7import org.apache.http.impl.client.DefaultHttpClient
  8import org.apache.http.client.methods._
  9import org.apache.http.client.entity.UrlEncodedFormEntity
 10import org.apache.http.client.utils.URLEncodedUtils
 11
 12import org.apache.http.entity.StringEntity
 13import org.apache.http.entity.ByteArrayEntity
 14import org.apache.http.message.BasicNameValuePair
 15import org.apache.http.protocol.{HTTP, HttpContext}
 16import org.apache.http.params.{HttpProtocolParams, BasicHttpParams}
 17import org.apache.http.util.EntityUtils
 18import org.apache.http.auth.{AuthScope, UsernamePasswordCredentials}
 19
 20case class StatusCode(code: Int, contents:String)
 21  extends Exception("Exceptional resoponse code: " + code + "\n" + contents)
 22
 23class Http(
 24  val host: Option[HttpHost], 
 25  val headers: List[(String, String)],
 26  val creds: Option[(String, String)]
 27) {
 28  def this(host: HttpHost) = this(Some(host), Nil, None)
 29  def this(hostname: String, port: Int) = this(new HttpHost(hostname, port))
 30  def this(hostname: String) = this(new HttpHost(hostname))
 31  lazy val client = new ConfiguredHttpClient {
 32    for (h <- host; (name, password) <- creds) {
 33      getCredentialsProvider.setCredentials(
 34        new AuthScope(h.getHostName, h.getPort), 
 35        new UsernamePasswordCredentials(name, password)
 36      )
 37    }
 38  }
 39
 40  /** Uses bound host server in HTTPClient execute. */
 41  def execute(req: HttpUriRequest):HttpResponse = {
 42//    Http.log.info("%s %s", req.getMethod, req.getURI)
 43    host match {
 44      case None => client.execute(req)
 45      case Some(host) => client.execute(host, req)
 46    }
 47  }
 48  /** Sets authentication credentials for bound host. */
 49  def as (name: String, pass: String) = new Http(host, headers, Some((name, pass)))
 50  /** Add header */
 51  def << (k: String, v: String) = new Http(host, (k,v) :: headers, creds)
 52    
 53  /** eXecute wrapper */
 54  def x [T](req: HttpUriRequest) = new {
 55    /** handle response codes, response, and entity in block */
 56    def apply(block: (Int, HttpResponse, Option[HttpEntity]) => T) = {
 57      val res = execute(req)
 58      val ent = res.getEntity match {
 59        case null => None
 60        case ent => Some(ent)
 61      }
 62      try { 
 63        block(res.getStatusLine.getStatusCode, res, ent) }
 64      finally { ent foreach (_.consumeContent) }
 65    }
 66    
 67    /** Handle reponse entity in block if reponse code returns true from chk. */
 68    def when(chk: Int => Boolean)(block: (HttpResponse, Option[HttpEntity]) => T) = this { (code, res, ent) => 
 69      if (chk(code)) block(res, ent)
 70      else {
 71        throw StatusCode(code,
 72        ent.map(EntityUtils.toString(_, HTTP.UTF_8)).getOrElse("")
 73        )
 74      }
 75    }
 76    
 77    /** Handle reponse entity in block when response code is 200 - 204 */
 78    def ok = (this when {code => (200 to 204) contains code}) _
 79  }
 80  
 81  /** Generally for the curried response function of Responder: >>, j$, <>, etc. */
 82  def apply[T](block: Http => T) = block(this)
 83}
 84
 85/** Extension point for static request definitions. */
 86class /(path: String) extends Request("/" + path) {
 87  def this(req: Request) = this(req.req.getURI.toString.substring(1))
 88}
 89
 90/** Factory for requests from the root. */
 91object / {
 92  def apply(path: String) = new Request("/" + path)
 93}
 94
 95/** Wrapper to handle common requests, preconfigured as response wrapper for a 
 96  * get request but defs return other method responders. */
 97class Request(val req: HttpUriRequest) extends Responder {
 98  
 99  /** Start with GET by default. */
100  def this(uri: String) = this(new HttpGet(uri))
101
102  /** Append an element to this request's path */
103  def / (path: String) = new Request(req.getURI + "/" + path)
104
105  /** Put the given object.toString and return response wrapper. */
106  def <<< (body: Any) = {
107    val m = new HttpPut(req.getURI)
108    m setEntity new StringEntity(body.toString, HTTP.UTF_8)
109    HttpProtocolParams.setUseExpectContinue(m.getParams, false)
110    new Request(m)
111  }
112  /** Put the given object.toString and return response wrapper. */
113  def <<< (body: Array[byte], contentType: String) = {
114    val m = new HttpPut(req.getURI)
115    val e = new ByteArrayEntity(body)
116    e setContentType(contentType)
117    m setEntity e
118    HttpProtocolParams.setUseExpectContinue(m.getParams, false)
119    new Request(m)
120  }
121  /** Post the given key value sequence and return response wrapper. */
122  def << (values: Map[String, Any]) = {
123    val m = new HttpPost(req.getURI)
124    m setEntity new UrlEncodedFormEntity(Http.map2ee(values), HTTP.UTF_8)
125    new Request(m)
126  }
127  /** Post the given object as string and return response wrapper. */
128  def << (body: Any) = {
129      val m = new HttpPost(req.getURI)
130      m setEntity new StringEntity(body.toString, HTTP.UTF_8)
131      new Request(m)
132    }
133  
134  import org.apache.http.message._
135  def << (body: Any, contentType: String) = {
136    val m = new HttpPost(req.getURI)
137    val e = new StringEntity(body.toString)
138    e setContentType(contentType)
139    m setEntity e
140    new Request(m)
141  }
142  
143  /** Use <<? instead: the high precedence of ? is problematic. */
144  @deprecated def ?< (values: Map[String, Any]) = <<? (values)
145  /** Get with query parameters */
146  def <<? (values: Map[String, Any]) = if(values.isEmpty) this else
147    new Request(new HttpGet(req.getURI + Http ? (values)))
148
149  /** HTTP Delete request. */
150  def <--() = new Request(new HttpDelete(req.getURI))
151}
152
153trait Responder {
154  val req: HttpUriRequest
155  /** Execute and process response in block */
156  def apply [T] (block: (Int, HttpResponse, Option[HttpEntity]) => T)(http: Http) = {
157    http.headers foreach { case (k, v) => req.addHeader(k, v) }
158    (http x req) (block)
159  }
160  /** Handle response and entity in block if OK. */
161  def ok [T] (block: (HttpResponse, Option[HttpEntity]) => T)(http: Http) = {
162    http.headers foreach { case (k, v) => req.addHeader(k, v) }
163    (http x req) ok (block)
164  }
165  /** Handle response entity in block if OK. */
166  def okee [T] (block: HttpEntity => T) = ok { 
167    case (_, Some(ent)) => block(ent)
168    case (res, _) => error("response has no entity: " + res)
169  } _
170  /** Handle InputStream in block if OK. */
171  def >> [T] (block: InputStream => T) = okee (ent => block(ent.getContent))
172  /** Return response in String if OK. (Don't blow your heap, kids.) */
173  def as_str = okee { EntityUtils.toString(_, HTTP.UTF_8) }
174  /** Write to the given OutputStream. */
175  def >>> [OS <: OutputStream](out: OS)(http: Http) = { okee { _.writeTo(out) } (http); out }
176  /** Process response as XML document in block */
177  def <> [T] (block: xml.NodeSeq => T) = >> { stm => block(xml.XML.load(stm)) }
178  
179  /** Process response as JsValue in block */
180  def ># [T](block: json.Js.JsF[T]) = >> (stm => block(json.Js(stm)))
181
182  /** Use ># instead: $ is forbidden. */
183  @deprecated def $ [T](block: json.Js.JsF[T]) = >#(block)
184  
185  /** Ignore response body if OK. */
186  def >| = ok ((r,e) => ()) _
187}
188
189
190class ConfiguredHttpClient extends DefaultHttpClient { 
191  override def createHttpParams = {
192    val params = new BasicHttpParams
193    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1)
194    HttpProtocolParams.setContentCharset(params, HTTP.UTF_8)
195    HttpProtocolParams.setUseExpectContinue(params, false)
196    params
197  }
198}
199
200/** May be used directly from any thread. */
201object Http extends Http(None, Nil, None) {
202  import org.apache.http.conn.scheme.{Scheme,SchemeRegistry,PlainSocketFactory}
203  import org.apache.http.conn.ssl.SSLSocketFactory
204  import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager
205  
206  // import to support e.g. Http("http://example.com/" >>> System.out)
207  implicit def str2req(str: String) = new Request(str)
208
209  override lazy val client = new ConfiguredHttpClient {
210    override def createClientConnectionManager() = {
211      val registry = new SchemeRegistry()
212      registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80))
213      registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443))
214      new ThreadSafeClientConnManager(getParams(), registry)
215    }
216  }
217//  val log = net.lag.logging.Logger.get
218  /** Convert repeating name value tuples to list of pairs for httpclient */
219  def map2ee(values: Map[String, Any]) = 
220    new java.util.ArrayList[BasicNameValuePair](values.size) {
221      values.foreach { case (k, v) => add(new BasicNameValuePair(k, v.toString)) }
222    }
223  /** Produce formatted query strings from a Map of parameters */
224  def ?(values: Map[String, Any]) = if (values.isEmpty) "" else 
225    "?" + URLEncodedUtils.format(map2ee(values), HTTP.UTF_8)
226}