├── .gitignore
├── .gitmodules
├── .travis.yml
├── LICENSE.txt
├── README.markdown
├── core
└── src
│ └── main
│ ├── ls
│ ├── 0.8.6.json
│ ├── 0.8.7.json
│ ├── 0.8.8.json
│ └── 0.8.9.json
│ └── scala
│ ├── callbacks.scala
│ ├── executor.scala
│ ├── handlers.scala
│ ├── logging.scala
│ ├── package.scala
│ └── requests.scala
├── docs
├── 00.markdown
├── 01.markdown
├── 02.markdown
├── 03.markdown
├── 04.markdown
├── 05
│ ├── 00.markdown
│ ├── a.markdown
│ └── b.markdown
├── 06
│ ├── 00.markdown
│ ├── a.markdown
│ ├── b.markdown
│ └── c.markdown
├── 07
│ ├── 00.markdown
│ └── a.markdown
├── 08.markdown
├── 09.markdown
├── 99.markdown
├── dispatch.css
└── template.properties
├── futures
└── src
│ └── main
│ ├── ls
│ ├── 0.8.6.json
│ ├── 0.8.7.json
│ ├── 0.8.8.json
│ └── 0.8.9.json
│ └── scala
│ └── Futures.scala
├── http+json
└── src
│ ├── main
│ ├── ls
│ │ ├── 0.8.6.json
│ │ ├── 0.8.7.json
│ │ ├── 0.8.8.json
│ │ └── 0.8.9.json
│ └── scala
│ │ └── JsHttp.scala
│ └── test
│ └── scala
│ └── JsonSpec.scala
├── http-gae
└── src
│ └── main
│ ├── ls
│ ├── 0.8.6.json
│ ├── 0.8.7.json
│ ├── 0.8.8.json
│ └── 0.8.9.json
│ └── scala
│ ├── AppEngineHttp.scala
│ └── gae.scala
├── http
└── src
│ ├── main
│ ├── ls
│ │ ├── 0.8.6.json
│ │ ├── 0.8.7.json
│ │ ├── 0.8.8.json
│ │ └── 0.8.9.json
│ └── scala
│ │ ├── ConfiguredHttpClient.scala
│ │ ├── Http.scala
│ │ ├── https.scala
│ │ └── thread
│ │ └── thread.scala
│ └── test
│ └── scala
│ └── HttpSpec.scala
├── json
└── src
│ ├── main
│ ├── ls
│ │ ├── 0.8.6.json
│ │ ├── 0.8.7.json
│ │ ├── 0.8.8.json
│ │ └── 0.8.9.json
│ └── scala
│ │ ├── Json.scala
│ │ └── JsonExtractor.scala
│ └── test
│ ├── resources
│ └── test.json
│ └── scala
│ └── JsonSpec.scala
├── jsoup
└── src
│ ├── main
│ ├── ls
│ │ ├── 0.8.7.json
│ │ ├── 0.8.8.json
│ │ └── 0.8.9.json
│ └── scala
│ │ └── JSoupHttp.scala
│ └── test
│ ├── resources
│ ├── Human.html
│ └── test.html
│ └── scala
│ └── JSoupSpec.scala
├── mime
└── src
│ └── main
│ ├── ls
│ ├── 0.8.6.json
│ ├── 0.8.7.json
│ ├── 0.8.8.json
│ └── 0.8.9.json
│ └── scala
│ └── Mime.scala
├── nio
└── src
│ └── main
│ ├── ls
│ ├── 0.8.6.json
│ ├── 0.8.7.json
│ ├── 0.8.8.json
│ └── 0.8.9.json
│ └── scala
│ └── nio.scala
├── notes
├── 0.7.0.markdown
├── 0.7.1.markdown
├── 0.7.2.markdown
├── 0.7.3.markdown
├── 0.7.4.markdown
├── 0.7.5.markdown
├── 0.7.6.markdown
├── 0.7.7.markdown
├── 0.7.8.markdown
├── 0.8.0.Beta1.markdown
├── 0.8.0.Beta3.markdown
├── 0.8.0.Beta4.markdown
├── 0.8.0.Beta5.markdown
├── 0.8.0.markdown
├── 0.8.1.markdown
├── 0.8.10.markdown
├── 0.8.2.markdown
├── 0.8.3.markdown
├── 0.8.4.markdown
├── 0.8.5.markdown
├── 0.8.6.markdown
├── 0.8.7.markdown
├── 0.8.8.markdown
├── 0.8.9.markdown
└── about.markdown
├── oauth
└── src
│ ├── main
│ ├── ls
│ │ ├── 0.8.6.json
│ │ ├── 0.8.7.json
│ │ ├── 0.8.8.json
│ │ └── 0.8.9.json
│ └── scala
│ │ ├── OAuth.scala
│ │ ├── foursquare.scala
│ │ └── twitter.scala
│ └── test
│ └── scala
│ ├── OAuthSpec.scala
│ └── foursquare.scala
├── project
├── build.properties
├── build.scala
└── plugins.sbt
├── tagsoup
└── src
│ ├── main
│ ├── ls
│ │ ├── 0.8.7.json
│ │ ├── 0.8.8.json
│ │ └── 0.8.9.json
│ └── scala
│ │ └── TagSoupHttp.scala
│ └── test
│ ├── resources
│ ├── Human.html
│ └── test.html
│ └── scala
│ └── TagSoupSpec.scala
└── temp
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .project
3 | .settings
4 | target
5 | scratch.scala
6 | lib_managed
7 | project/boot
8 | project/plugins/project
9 | src_managed
10 | *.test.properties
11 | .idea
12 | *.iml
13 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "databinder-dispatch/tasks"]
2 | path = databinder-dispatch/tasks
3 | url = technically.us:db-buildr-tasks
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 |
3 | scala:
4 | - 2.9.0
5 | - 2.9.0-1
6 | - 2.9.1
7 | - 2.9.1-1
8 | - 2.9.2
9 | - 2.9.3
10 | - 2.10.4
11 | - 2.11.1
12 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | This is the classic version of Dispatch and is unmaintained.
2 |
3 | Find Dispatch Classic's documentation at http://dispatch-classic.databinder.net/
4 |
5 | If you're looking for the modern Dispatch, check out https://github.com/dispatch/reboot
6 |
--------------------------------------------------------------------------------
/core/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-core",
5 | "version":"0.8.6",
6 | "description":"Core interfaces, applied by dispatch-http and dispatch-nio executors",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/core/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-core",
5 | "version":"0.8.7",
6 | "description":"Core interfaces, applied by dispatch-http and dispatch-nio executors",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/core/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-core",
5 | "version":"0.8.8",
6 | "description":"Core interfaces, applied by dispatch-http and dispatch-nio executors",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | }],
20 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
21 | "sbt": false
22 | }
--------------------------------------------------------------------------------
/core/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-core",
4 | "version" : "0.8.9",
5 | "description" : "Core interfaces, applied by dispatch-http and dispatch-nio executors",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | } ],
15 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
16 | "licenses" : [ {
17 | "name" : "LGPL v3",
18 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
19 | } ],
20 | "sbt" : false
21 | }
--------------------------------------------------------------------------------
/core/src/main/scala/callbacks.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic
2 |
3 | import org.apache.http.HttpResponse
4 | import util.control.Exception._
5 |
6 | object Callback {
7 | type Function = (HttpResponse, Array[Byte], Int) => Unit
8 | type Finish[T] = HttpResponse => T
9 |
10 | def apply[T](request: Request,
11 | function: Callback.Function,
12 | finish: Callback.Finish[T]): Callback[T] =
13 | Callback(request, function, finish, nothingCatcher)
14 |
15 |
16 | def apply(request: Request, function: Callback.Function): Callback[Unit] =
17 | Callback(request, function, { _ => () })
18 |
19 | def strings[T](req: Request, block: (String => Unit)) = {
20 | @volatile var charset: Option[String] = None
21 | Callback(
22 | req,
23 | (res, bytes, len) => {
24 | charset orElse {
25 | charset = (for {
26 | ct <- res.getHeaders("Content-Type").headOption
27 | elem <- ct.getElements.headOption
28 | param <- Option(elem.getParameterByName("charset"))
29 | } yield param.getValue()) orElse Some(Request.factoryCharset)
30 | charset
31 | } map { cs => block(new String(bytes, 0, len, cs)) }
32 | }
33 | )
34 | }
35 |
36 | /** Divide input up by given regex. Buffers across inputs so strings are
37 | * only split on the divider, and handles any leftovers in finish. Skips
38 | * empty strings. */
39 | def stringsBy[T](divider: String)
40 | (req: Request, block: (String => Unit)) = {
41 | var buffer = ""
42 | strings(
43 | req,
44 | { string =>
45 | val strings = (buffer + string).split(divider, -1)
46 | strings.take(strings.length - 1).filter { !_.isEmpty }.foreach(block)
47 | buffer = strings.last
48 | }
49 | ) ^> { res =>
50 | if (!buffer.isEmpty) block(buffer)
51 | }
52 | }
53 | /** callback transformer for strings split on the newline character, newline removed */
54 | def lines[T] = stringsBy[T]("[\n\r]+")_
55 | }
56 |
57 | case class Callback[T](request: Request,
58 | function: Callback.Function,
59 | finish: Callback.Finish[T],
60 | listener: ExceptionListener) {
61 | def ^> [T](finish: Callback.Finish[T]) = copy(finish={ res =>
62 | this.finish(res)
63 | finish(res)
64 | })
65 | /** Set an exception listener */
66 | def ^!(listener: ExceptionListener) = this.copy(listener = listener)
67 | }
68 |
69 | trait ImplicitCallbackVerbs {
70 | implicit def toCallbackVerbs(req: Request) = new CallbackVerbs(req)
71 | implicit def stringToCallbackVerbs(str: String) = new CallbackVerbs(new Request(str))
72 | }
73 |
74 | class CallbackVerbs(subject: Request) {
75 | import Callback._
76 | def ^[T](callback: Function) = Callback(subject, callback)
77 | def ^-[T](callback: String => Unit) = strings(subject, callback)
78 | /** strings split on the newline character, newline removed */
79 | def ^--[T](callback: String => Unit) = lines(subject, callback)
80 | }
81 |
--------------------------------------------------------------------------------
/core/src/main/scala/executor.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic
2 |
3 | import org.apache.http.{HttpHost,HttpRequest,HttpResponse,HttpEntity}
4 | import org.apache.http.message.AbstractHttpMessage
5 | import org.apache.http.util.EntityUtils
6 | import org.apache.http.client.methods._
7 |
8 | /** Defines request execution and response status code behaviors. Implemented methods are finalized
9 | as any overrides would be lost when instantiating delegate executors, is in Threads#future.
10 | Delegates should chain to parent `pack` and `execute` implementations. */
11 | trait HttpExecutor extends RequestLogging {
12 | /** Type of value returned from request execution */
13 | type HttpPackage[T]
14 | /** Execute the request against an HttpClient */
15 | def execute[T](host: HttpHost,
16 | creds: Option[Credentials],
17 | req: HttpRequestBase,
18 | block: HttpResponse => T,
19 | listener: ExceptionListener): HttpPackage[T]
20 |
21 | def executeWithCallback[T](host: HttpHost, credsopt: Option[Credentials],
22 | req: HttpRequestBase, block: Callback[T]): HttpPackage[T]
23 |
24 | @deprecated("Use x[T](hand: Handler[T]) instead. Construct a Handler if needed.", "0.8.0")
25 | final def x[T](req: Request)(block: Handler.F[T]): HttpPackage[T] = {
26 | x(Handler(req, block))
27 | }
28 | /** Execute full request-response handler, response in package. */
29 | final def x[T](hand: Handler[T]): HttpPackage[T] = {
30 | val req = hand.request
31 | val request = make_message(hand.request)
32 | log.info("%s %s", req.host.getHostName, request.getRequestLine)
33 | req.headers.reverse.foreach {
34 | case (key, value) => request.addHeader(key, value)
35 | }
36 | execute(req.host, req.creds, request, { res =>
37 | val ent = res.getEntity match {
38 | case null => None
39 | case ent => Some(ent)
40 | }
41 | val result = hand.block(res.getStatusLine.getStatusCode, res, ent)
42 | consumeContent(ent)
43 | result
44 | }, hand.listener)
45 | }
46 | /** Allow executor to release any resources for an entity */
47 | def consumeContent(entity: Option[HttpEntity]): Unit
48 | /** Apply Response Handler if reponse code returns true from chk. */
49 | final def when[T](chk: Int => Boolean)(hand: Handler[T]) =
50 | x(hand.copy(block= {
51 | case (code, res, ent) if chk(code) => hand.block(code, res, ent)
52 | case (code, _, Some(ent)) =>
53 | throw StatusCode(code, EntityUtils.toString(ent, hand.request.defaultCharset))
54 | case (code, _, _) =>
55 | throw StatusCode(code, "[no entity]")
56 | }))
57 |
58 | /** Apply handler block when response code is 200 - 204 */
59 | final def apply[T](hand: Handler[T]): HttpPackage[T] =
60 | (this when {code => (200 to 204) contains code})(hand)
61 |
62 | def make_message(req: Request) = {
63 | req.method.toUpperCase match {
64 | case HttpGet.METHOD_NAME => new HttpGet(req.path)
65 | case HttpHead.METHOD_NAME => new HttpHead(req.path)
66 | case HttpDelete.METHOD_NAME => new HttpDelete(req.path)
67 | case HttpOptions.METHOD_NAME => new HttpOptions(req.path)
68 | case method =>
69 | val message = method match {
70 | case HttpPost.METHOD_NAME => new HttpPost(req.path)
71 | case HttpPut.METHOD_NAME => new HttpPut(req.path)
72 | }
73 | req.body.foreach(message.setEntity)
74 | message
75 | }
76 | }
77 |
78 | /** Apply handler block when response code is 200 - 204 */
79 | final def apply[T](callback: Callback[T]) = {
80 | val req = callback.request
81 | val request = make_message(req)
82 | log.info("%s %s", req.host.getHostName, request.getRequestLine)
83 | req.headers.reverse.foreach {
84 | case (key, value) => request.addHeader(key, value)
85 | }
86 | executeWithCallback(req.host, req.creds, request, callback)
87 | }
88 | private var isShutdown = false
89 | /** Release resources held by the executor. */
90 | def shutdown() {
91 | shutdownClient();
92 | isShutdown = true
93 | }
94 | protected def shutdownClient(): Unit
95 | /** Call shutdown if not already shutdown, issue warning */
96 | override def finalize() {
97 | if (!isShutdown) {
98 | log.warn(
99 | "Shutting down garbage-collected HttpExecutor--" +
100 | "Call shutdown() explicitly to avoid resource leaks!"
101 | )
102 | shutdown()
103 | }
104 | super.finalize()
105 | }
106 | }
107 |
108 | trait BlockingCallback { self: HttpExecutor =>
109 | def executeWithCallback[T](host: HttpHost,
110 | credsopt: Option[Credentials],
111 | req: HttpRequestBase,
112 | callback: Callback[T]): HttpPackage[T] =
113 | execute(host, credsopt, req, { res =>
114 | res.getEntity match {
115 | case null => callback.finish(res)
116 | case entity =>
117 | val stm = entity.getContent
118 | val buf = new Array[Byte](8 * 1024)
119 | var count = 0
120 | while ({count = stm.read(buf); count} > -1)
121 | callback.function(res, buf, count)
122 | stm.close()
123 | callback.finish(res)
124 | }
125 | }, callback.listener)
126 | }
127 |
128 | case class StatusCode(code: Int, contents:String)
129 | extends Exception("Unexpected response code: " + code + "\n" + contents)
130 |
131 | /** Simple info and warn logger */
132 | trait Logger {
133 | def info(msg: String, items: Any*)
134 | def warn(msg: String, items: Any*)
135 | }
136 |
137 | trait RequestLogging {
138 | lazy val log: Logger = make_logger
139 |
140 | /** Logger for this executor, logs to console. */
141 | def make_logger =
142 | new Logger {
143 | def info(msg: String, items: Any*) {
144 | println("INF: [console logger] dispatch: " +
145 | msg.format(items: _*))
146 | }
147 | def warn(msg: String, items: Any*) {
148 | println("WARN: [console logger] dispatch: " +
149 | msg.format(items: _*))
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/core/src/main/scala/handlers.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic
2 |
3 | import org.apache.http.{HttpResponse,HttpEntity}
4 | import org.apache.http.util.EntityUtils
5 | import java.util.zip.GZIPInputStream
6 | import java.io.{InputStream,OutputStream,InputStreamReader}
7 | import javax.xml.parsers.SAXParserFactory
8 | import scala.io.Source
9 | import util.control.Exception._
10 |
11 | /** Request handler, contains request descriptor and a function to transform the result. */
12 | case class Handler[T](
13 | request: Request,
14 | block: Handler.F[T],
15 | listener: ExceptionListener
16 | ) {
17 | /** @return new Handler composing after with this Handler's block */
18 | def ~> [R](after: T => R) = copy(block=(code, res, ent) => after(block(code,res,ent)))
19 | /** Set an exception listener */
20 | def >!(listener: ExceptionListener) = this.copy(listener = listener)
21 | /** Create a new handler with block that receives all response parameters and
22 | this handler's block converted to parameterless function. */
23 | def apply[R](next: (Int, HttpResponse, Option[HttpEntity], () => T) => R) =
24 | copy(block={(code, res, ent) =>
25 | next(code, res, ent, () => block(code, res, ent))
26 | })
27 | }
28 |
29 | object Handler {
30 | type F[T] = (Int, HttpResponse, Option[HttpEntity]) => T
31 | /** Turns a simple entity handler in into a full response handler that fails if no entity */
32 | def apply[T](req: Request, block: F[T]): Handler[T] = Handler(
33 | req, block, nothingCatcher)
34 | def apply[T](req: Request, block: HttpEntity => T): Handler[T] =
35 | Handler(req, { (code, res, ent) => ent match {
36 | case Some(ent) => block(ent)
37 | case None => sys.error("""
38 | | Response has no HttpEntity: %s
39 | | If no response body is expected, use a handler such as
40 | | HandlerVerbs#>| that does not require one.""".stripMargin.format(res))
41 | } } )
42 | // retain factory to use with XML.load; its newInstance method is not thread-safe
43 | lazy val saxParserFactory = {
44 | val spf = SAXParserFactory.newInstance()
45 | spf.setNamespaceAware(false)
46 | spf
47 | }
48 | }
49 |
50 | trait ImplicitHandlerVerbs {
51 | implicit def toHandlerVerbs(req: Request) = new HandlerVerbs(req)
52 | implicit def stringToHandlerVerbs(str: String) = new HandlerVerbs(new Request(str))
53 | }
54 |
55 | class HandlerVerbs(request: Request) {
56 | /** Handle InputStream in block, handle gzip if so encoded. Passes on any charset
57 | header value from response, otherwise the default charset. (See Request#>\) */
58 | def >> [T] (block: (InputStream, String) => T) = Handler(request, { ent =>
59 | val stm = (ent.getContent, ent.getContentEncoding) match {
60 | case (stm, null) => stm
61 | case (stm, enc) if enc.getValue == "gzip" => new GZIPInputStream(stm)
62 | case (stm, _) => stm
63 | }
64 | val charset = EntityUtils.getContentCharSet(ent) match {
65 | case null => request.defaultCharset
66 | case charset => charset
67 | }
68 | try { block(stm, charset) }
69 | finally { stm.close() }
70 | } )
71 | /** Handle InputStream in block, handle gzip if so encoded. */
72 | def >> [T] (block: InputStream => T): Handler[T] = >> { (stm, charset) => block(stm) }
73 | /** Handle response as a scala.io.Source, in a block. Note that Source may fail if the
74 | character set it receives (determined in >>) is incorrect. To process resources
75 | that have incorrect charset headers, use >> ((InputStream, String) => T). */
76 | def >~ [T] (block: Source => T) = >> { (stm, charset) =>
77 | block(Source.fromInputStream(stm, charset))
78 | }
79 | /** Return response as a scala.io.Source. Charset note in >~ applies. */
80 | def as_source = >~ { so => so }
81 | /** Handle some non-huge response body as a String, in a block. Charset note in >~ applies. */
82 | def >- [T] (block: String => T) = >~ { so => block(so.mkString) }
83 | /** Return some non-huge response as a String. Charset note in >~ applies.*/
84 | def as_str = >- { s => s }
85 | /** Handle response as a java.io.Reader */
86 | def >>~ [T] (block: InputStreamReader => T) = >> { (stm, charset) =>
87 | block(new InputStreamReader(stm, charset))
88 | }
89 | /** Write to the given OutputStream. */
90 | def >>> [OS <: OutputStream](out: OS) = Handler(request, { ent => ent.writeTo(out); out })
91 | /** Process response as XML document in block */
92 | def <> [T] (block: xml.Elem => T) = >>~ { reader =>
93 | block(xml.XML.withSAXParser(Handler.saxParserFactory.newSAXParser).load(reader))
94 | }
95 | /** Process header as Map in block. Map returns empty set for header
96 | * name misses. */
97 | def >:> [T] (block: Map[String, Set[String]] => T) = {
98 | Handler(request, { (_, res, _) =>
99 | val st = Map.empty[String, Set[String]].withDefaultValue(Set.empty)
100 | block((st /: res.getAllHeaders) { (m, h) =>
101 | m + (h.getName -> (m(h.getName) + h.getValue))
102 | })
103 | })
104 | }
105 |
106 | /** Process headers as a Map of strings to sequences of *lowercase*
107 | * strings, to facilitate case-insensetive header lookup. */
108 | def headers_> [T] (block: Map[String, Seq[String]] => T) = {
109 | Handler(request, { (_, res, _) =>
110 | val st = Map.empty[String, Seq[String]].withDefaultValue(Seq.empty)
111 | block((st /: res.getAllHeaders) { (m, h) =>
112 | val key = h.getName.toLowerCase
113 | m + (key -> (m(key) :+ h.getValue))
114 | })
115 | })
116 | }
117 |
118 | /** Combination header and request chaining verb. Headers are
119 | * converted to lowercase for case insensitive access.
120 | */
121 | def >:+ [T] (block: (Map[String, Seq[String]], Request) =>
122 | Handler[T]) =
123 | >+> { req =>
124 | req headers_> { hs => block(hs, req) }
125 | }
126 |
127 | /** Ignore response body. */
128 | def >| = Handler(request, (code, res, ent) => ())
129 |
130 | /** Split into two request handlers, return results of each in tuple. */
131 | def >+ [A, B] (block: Request => (Handler[A], Handler[B])) = {
132 | Handler(request, { (code, res, opt_ent) =>
133 | val (a, b) = block(request)
134 | (a.block(code, res, opt_ent), b.block(code,res,opt_ent))
135 | } )
136 | }
137 | /** Chain two request handlers. First handler returns a second, which may use
138 | values obtained by the first. Both are run on the same request. */
139 | def >+> [T] (block: Request => Handler[Handler[T]]) = {
140 | Handler( request, { (code, res, opt_ent) =>
141 | (block(request)).block(code, res, opt_ent).block(code, res, opt_ent)
142 | } )
143 | }
144 | }
145 |
146 |
147 | trait ImplicitXhtmlHandlerVerbs {
148 | implicit def toXhtmlHandlerVerbs(req: Request) = new XhtmlHandlerVerbs(req)
149 | implicit def stringToXhtmlHandlerVerbs(str: String) = new XhtmlHandlerVerbs(new Request(str))
150 | }
151 |
152 | /** Import this object's methods to get the > verb, or see the
153 | * jsoup and tagsoup modules. */
154 | object XhtmlParsing extends ImplicitXhtmlHandlerVerbs
155 |
156 | class XhtmlHandlerVerbs(request: Request) {
157 | /** Process response as XHTML document in block, more lenient than <>
158 | * but still not great. See the jsoup and tagsoup modules for more
159 | * capable html parsers. */
160 | def > [T] (block: xml.NodeSeq => T) = request >~ { src =>
161 | block(xml.parsing.XhtmlParser(src))
162 | }
163 | }
164 |
165 |
--------------------------------------------------------------------------------
/core/src/main/scala/logging.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic
2 |
3 | /** Mix in to Http if you want JDK logging */
4 | trait JdkLogging extends HttpExecutor {
5 | override def make_logger = new dispatch.classic.Logger {
6 | val jdklog = java.util.logging.Logger.getLogger("dispatch")
7 | def info(msg: String, items: Any*) {
8 | jdklog.info(msg.format(items: _*))
9 | }
10 | def warn(msg: String, items: Any*) {
11 | jdklog.warning(msg.format(items: _*))
12 | }
13 | }
14 | }
15 |
16 | /**
17 | * Mix in to Http if you want no logging from Dispatch.
18 | * Note that HttpClient logs separately:
19 | * http://hc.apache.org/httpcomponents-client/logging.html
20 | */
21 | trait NoLogging extends HttpExecutor {
22 | override def make_logger = new dispatch.classic.Logger {
23 | def info(msg: String, items: Any*) { }
24 | def warn(msg: String, items: Any*) { }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/main/scala/package.scala:
--------------------------------------------------------------------------------
1 | package dispatch
2 | package object classic {
3 | /** Exception listener is called in addition to an HttpExecutor's
4 | * regular exception handling (throwing/logging/ignoring it). */
5 | type ExceptionListener = util.control.Exception.Catcher[Unit]
6 | }
7 |
--------------------------------------------------------------------------------
/docs/00.markdown:
--------------------------------------------------------------------------------
1 | Dispatch
2 | ========
3 |
4 | *Dispatch* is a library for conducting HTTP interaction. You tell it
5 | what to do in pure and uninhibited Scala, and Dispatch carries out your
6 | orders using its trusty [HttpClient][hc] backend.
7 |
8 | > [Version $version$ Release Notes]($version_link$)
9 |
10 |
11 | [hc]: http://hc.apache.org/httpcomponents-client/
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/01.markdown:
--------------------------------------------------------------------------------
1 | Try Dispatch
2 | ------------
3 |
4 | ### Getting a Console
5 |
6 | The simplest way to try Dispatch is to clone the Twine example
7 | application from github.
8 |
9 | git clone https://github.com/n8han/dispatch-twine.git
10 |
11 | > If you don't have git available, you can download and extract the
12 | project's current
13 | [zip archive](https://github.com/n8han/dispatch-twine/zipball/master).
14 |
15 | [zip]: https://github.com/n8han/dispatch-twine/zipball/master
16 |
17 | Twine is built with sbt 0.10. If you haven't yet [setup sbt][sbt], now
18 | is a good time to do that. Once you have `sbt` on your executable search
19 | path, you can enter its interactive console for the Twine project.
20 |
21 | [sbt]: https://github.com/harrah/xsbt/wiki/Setup
22 |
23 | cd dispatch-twine
24 | sbt
25 |
26 | In sbt's console, call the `console` task.
27 |
28 | console
29 |
30 | On the first run this task will download Dispatch dependencies and
31 | compile the Twine app. After that, you should see a message welcoming
32 | you to the console and a friendly `scala>` prompt.
33 |
34 | ### Your First Request
35 |
36 | First, import the main Dispatch classes and objects into scope.
37 |
38 | ```scala
39 | import dispatch._
40 | ```
41 | Then, we'll need an HTTP *executor* to carry out our requests.
42 |
43 | ```scala
44 | val h = new Http
45 | ```
46 | Requests are described with `dispatch.Request` objects, and one way to
47 | construct them is with a URL.
48 |
49 | ```scala
50 | val req = url("http://www.scala-lang.org/")
51 | ```
52 | This works because you've imported an object called `url` from the
53 | `dispatch` package, and the object is itself a function that creates
54 | request objects.
55 |
56 | Now, we have an executor and a request. Dispatch needs to know how to
57 | handle the request.
58 |
59 | ```scala
60 | val handler = req >>> System.out
61 | ```
62 | With this complete request-response handler, Dispatch can execute the
63 | request. Assuming you have a network connection, that is.
64 |
65 | ```scala
66 | h(handler)
67 | ```
68 | And that was the source of the Scala home page. Of course, we don't
69 | usually assign all these component parts to values unless we need to
70 | reuse them. Typically, the handler above would be written in one line.
71 |
72 | ```scala
73 | h(url("http://www.scala-lang.org/") >>> System.out)
74 | ```
75 |
76 | If you can't wait to see more Dispatch *verbs* like `>>>`, here's a
77 | [colorful cheat sheet][table].
78 |
79 | [gh]: https://github.com/n8han/dispatch-twine#readme
80 | [table]: http://www.flotsam.nl/dispatch-periodic-table.html
81 |
--------------------------------------------------------------------------------
/docs/02.markdown:
--------------------------------------------------------------------------------
1 | Choose an Executor
2 | ------------------
3 |
4 | Dispatch supports arbitrarily many types of HTTP *executors*--you could
5 | make your own, and you wouldn't be the first to do so. Executors carry
6 | out your request *handlers* and *callbacks*.
7 |
8 | Because executors are defined separately from HTTP interaction,
9 | you can model HTTP interaction without demanding a particular
10 | implementation. For example, the same interface to a web API can be
11 | used with an NIO executor and a Google App Engine executor.
12 |
13 | Dispatch includes several executors, defined in various modules and
14 | packages. These executors are all called "Http", so application code
15 | typically imports only the base `dispatch` package and refers to
16 | executors by their subpackage name, as seen below.
17 |
18 | > To follow along with the examples below, use a console of the Twine
19 | example app described in the preceding page.
20 |
21 | ### Current Thread Blocking (dispatch-http)
22 |
23 | The traditional and most common Dispatch executor is located in the
24 | base `dispatch` package. It performs its operations in the current
25 | program thread, allowing it to return whatever value the handler
26 | builds. If you request a string response body with `as_str`, the
27 | executor's return type is `String`.
28 |
29 | ```scala
30 | import dispatch._
31 | val h = new Http
32 | h(url("http://www.scala-lang.org/") as_str)
33 | ```
34 | You may notice in the Scala console that the last expression above
35 | reports a return type of `HttpPackage`. This wrapping type is required
36 | to generalize the executor model, but in this executor it is defined
37 | as the wrapped type itself. It can be used any place where the wrapped
38 | type is expected.
39 |
40 | ```scala
41 | val s: String = h(url("http://www.scala-lang.org/") as_str)
42 | ```
43 | An important caveat of the executor defined above is that it is not
44 | thread-safe. You should not share references to it across
45 | threads. Instead, Dispatch applications commonly define a function in
46 | some convenient object that returns a newly constructed executor. This
47 | also allows you replace it with a customized executor, later.
48 |
49 | ```scala
50 | def http = new Http
51 | ```
52 | Another option is to use a thread-safe executor, which maintains a
53 | shared connection pool and may (or may not) provide better
54 | performance. Dispatch provides a trait for this behavior:
55 |
56 | ```scala
57 | val http = new Http with thread.Safety
58 | ```
59 | The `Http` singleton object is an executor constructed with this
60 | trait, so you can use it from any thread.
61 |
62 | ```scala
63 | Http(url("http://www.scala-lang.org/") as_str)
64 | ```
65 | ### Background Thread Blocking (dispatch-http)
66 |
67 | Another executor that does not depart from the traditional model is
68 | the background thread executor.
69 |
70 | ```scala
71 | import dispatch._
72 | val h = new thread.Http
73 | val f = h(url("http://www.scala-lang.org/") as_str)
74 | ```
75 | This executor application evaluates to a *future* for the string value
76 | to be constructed by the handler. A future in Dispatch is a function.
77 |
78 | ```scala
79 | type Future[T] = Function0[T] {
80 | def isSet: Boolean
81 | }
82 | ```
83 | When applied, the future waits until its underlying value is
84 | available, and `isSet` reports this availability. These are useful
85 | when handling a batch of requests, for example.
86 |
87 | ```scala
88 | val urls: Traversable[String] = < some urls I need to get >
89 | val futures = urls.map { u => h(url(u) as_str) }
90 |
91 | < do something else that's slow... >
92 |
93 | val (ready, working) = futures.partition { _.isSet }
94 | consume(ready.map { _() }) // mapped to Traversable[String]
95 | ```
96 | ### Native I/O (dispatch-nio)
97 |
98 | This executor uses Java NIO and does not block a current or background
99 | thread while carrying out requests. You can have *n* active requests
100 | without tying up *n* threads or waiting for available threads from a
101 | pool.
102 |
103 | ```scala
104 | import dispatch._
105 | val h = new nio.Http
106 | val f = h(url("http://www.scala-lang.org/") as_str)
107 | ```
108 | Like the background thread executor, the NIO executor returns a
109 | Dispatch future; you can operate on collection as in the previous
110 | example. One caveat of this executor is that it must be shutdown.
111 |
112 | ```scala
113 | h.shutdown()
114 | ```
115 | If you're using this executor in a console and forget to shut it down,
116 | the console may not close when asked.
117 |
118 | ### Google App Engine (dispatch-gae)
119 |
120 | This executor operates on the current thread, like `dispatch.Http`,
121 | but it is specially modified to work with Google App Engine which
122 | does not permit the direct use of sockets.
123 |
124 | ```scala
125 | import dispatch._
126 | val h = new gae.Http
127 | val s = h(url("http://www.scala-lang.org/") as_str)
128 | ```
129 | Note that this executor is not on Twine's classpath and should only be
130 | used with App Engine.
131 |
132 | ### Means of Execution
133 |
134 | The examples above all use `apply` which will execute the handler if
135 | the resulting HTTP statusCode is in the range 200 to 204. This range
136 | allows the standard handlers to fail-fast in situations where the response
137 | wouldn't be handled. If you need finer control please review the docs
138 | for the `when` and `x` methods on `trait HttpExecutor`.
139 |
--------------------------------------------------------------------------------
/docs/03.markdown:
--------------------------------------------------------------------------------
1 | Project Setup
2 | -------------
3 |
4 | ### Modules and Artifacts
5 |
6 | Dispatch is divided into a number of modules so that client
7 | applications need only depend on the parts of Dispatch they use. Some
8 | of the modules depend on other modules, and dependencies managers will
9 | add these transitive dependencies automatically.
10 |
11 | Each module is [cross-built][sbt] against several versions of Scala
12 | and [published to the scala-tools][st] repository with the
13 | organization-id "net.databinder". The modules have the Scala version
14 | they are built against appended. For Scala $scala$, the full artifact
15 | names are as follows:
16 |
17 | * dispatch-http_$scala$
18 | * dispatch-nio_$scala$
19 | * dispatch-core_$scala$
20 | * dispatch-gae_$scala$
21 | * dispatch-futures_$scala$
22 | * dispatch-mime_$scala$
23 | * dispatch-json_$scala$
24 | * dispatch-http-json_$scala$
25 | * dispatch-oauth_$scala$
26 |
27 | [dn]: http://databinder.net/repo/
28 | [st]: http://scala-tools.org/repo-releases/net/databinder/
29 | [sbt]: http://code.google.com/p/simple-build-tool/wiki/CrossBuild
30 |
31 | ### Source Dependencies
32 |
33 | Because each web API and third party Scala library has its own release
34 | timeline, some modules are not part of the main Dispatch build and
35 | distribution. Instead, they are external modules hosted on github
36 | and tagged for release as needed.
37 |
38 | Since these modules are build from source, binary compatibility with
39 | Scala and Dispatch versions is not an issue. More importantly, they
40 | can be trivially initted, forked, and used by anyone.
41 |
42 | If a module is lacking an API method that you need right now, you can
43 | fork it, fix it, tag it, and push it without waiting on anyone
44 | else. At that point it's "released" under your name. You should also
45 | fork this documentation to add your integration module to the list:
46 |
47 | * [dispatch/dispatch-lift-json](https://github.com/dispatch/dispatch-lift-json) -- The lift-json parsing and serialization library
48 | * [n8han/dispatch-aws-s3](https://github.com/n8han/dispatch-aws-s3) -- Amazon S3 signing and bucket interaction
49 | * [n8han/dispatch-couch](https://github.com/n8han/dispatch-couch) -- Basic CouchDB integration module, rather outdated
50 | * [n8han/dispatch-google-clientlogin](https://github.com/n8han/dispatch-google-clientlogin) -- Google's ClientLogin
51 | * [n8han/dispatch-meetup](https://github.com/n8han/dispatch-meetup) -- Meetup Groups and Everywhere APIs
52 | * [n8han/dispatch-twitter](https://github.com/n8han/dispatch-twitter) -- Basic Twitter integration for Dispatch, pls fork!
53 |
54 | ### Build Tools
55 |
56 | #### Configuring sbt Projects
57 |
58 | When using sbt with Scala **binary dependencies**, it's best to have the
59 | Scala version [automatically appended][sbt] so it will always match
60 | your project's. In a sbt 0.11.x `build.sbt`:
61 |
62 | ```scala
63 | libraryDependencies ++= Seq(
64 | "net.databinder" %% "dispatch-http" % "$version$"
65 | )
66 | ```
67 |
68 | To use **source dependencies** with sbt, create a project build such
69 | as `project/build.scala`. This is an example using dispatch-lift-json:
70 |
71 | ```scala
72 | import sbt._
73 | object MyApp extends Build
74 | {
75 | lazy val root =
76 | Project("root", file(".")) dependsOn(dispatchLiftJson)
77 | lazy val dispatchLiftJson =
78 | uri("git://github.com/dispatch/dispatch-lift-json#0.1.0")
79 | }
80 | ```
81 |
82 | The [Twine example application][twine] uses both source and
83 | binary Dispatch dependencies in its sbt project.
84 |
85 | [twine]: /Try+Dispatch.html
86 |
87 | #### Maven
88 |
89 | With Maven, you can depend on Dispatch's binary modules by specifying
90 | the full artifact id with Scala version:
91 |
92 | ```xml
93 |
94 | net.databinder
95 | dispatch-http_$scala$
96 | $version$
97 |
98 | ```
99 |
100 | To use source dependencies with Maven, your best bet is to check out
101 | the project as a submodule.
102 |
--------------------------------------------------------------------------------
/docs/04.markdown:
--------------------------------------------------------------------------------
1 | Community
2 | ---------
3 |
4 | Dispatch (including this documentation) lives [on GitHub][gh], and
5 | many of its features and bug fixes originate in forks of the
6 | project there. Dispatch's documentation is generated via
7 | [Pamflet][pamflet]. We also have [a mailing list][list].
8 |
9 | [gh]: https://github.com/dispatch/dispatch
10 | [pamflet]: http://pamflet.databinder.net/Pamflet.html
11 | [list]: https://groups.google.com/forum/?hl=en#!forum/dispatch-scala
12 |
--------------------------------------------------------------------------------
/docs/05/00.markdown:
--------------------------------------------------------------------------------
1 | A Simple Request
2 | ----------------
3 |
4 | This section explores basic request definition and response handling
5 | with Dispatch. You'll need a Scala [console with Dispatch][try] on the
6 | classpath to follow along.
7 |
8 | [try]: Try+Dispatch.html
9 |
--------------------------------------------------------------------------------
/docs/05/a.markdown:
--------------------------------------------------------------------------------
1 | URLs and Paths
2 | --------------
3 |
4 | Hosts are defined with the `:/` verb. It should remind you (a
5 | little) of the characters that come before hosts in URLs.
6 |
7 | ```scala
8 | import dispatch._
9 | val sl = :/("www.scala-lang.org")
10 | ```
11 |
12 | This value `sl` is a request to the root path of the Scala home
13 | domain. What if we want some sub-path of it? There's an obvious verb
14 | for that: `/`
15 |
16 | ```scala
17 | val learnScala = sl / "node" / "1305"
18 | ```
19 |
20 | Now we have a reference to a page (the "Learning Scala" page
21 | actually), and `sl` is unchanged.
22 |
23 | Request definitions such as these are immutable; by appending a path
24 | to one you are creating a new request object, similar to string
25 | concatenation in Java. If we don't need to use the host for different
26 | requests, we would probably have defined this all at once:
27 |
28 | ```scala
29 | val learnScala2 = :/("www.scala-lang.org") / "node" / "1305"
30 | ```
31 |
32 | But—can't you define this with its actual URL? Of course!
33 |
34 | ```scala
35 | val learnScala3 = url("http://www.scala-lang.org/node/1305")
36 | ```
37 |
38 | The `url` verb is also imported from the `dispatch` package and it
39 | produces a request object like the others. It is most useful when
40 | dealing with URLs stored externally, or discovered at runtime. For
41 | fixed requests in your application code, building up from host and
42 | path components is usually preferred as it lends itself to reuse.
43 |
--------------------------------------------------------------------------------
/docs/05/b.markdown:
--------------------------------------------------------------------------------
1 | Response Bodies
2 | ---------------
3 |
4 | Request definitions, by themselves, are not enough information for
5 | Dispatch to do its job. The response that the server returns may have
6 | no body at all—or it could contain too much data to hold in memory.
7 |
8 | Before Dispatch makes a request, then, you must tell it how to handle
9 | the server's response. For this we have *handlers*. Handlers are
10 | created from request definitions using handler verbs. Taking up the
11 | request defined on the last page, we could simply ignore the response.
12 |
13 | ```scala
14 | val ignore = learnScala >|
15 | ```
16 |
17 | This would be very unusual for a GET request in particular, which
18 | shouldn't have any side effects. (It's also not speaking well of our
19 | commitment to learning Scala.)
20 |
21 | To use a handler, you pass it to a Dispatch [executor][executor].
22 |
23 | [executor]: Choose+an+Executor.html
24 |
25 | ```scala
26 | Http(ignore)
27 | ```
28 |
29 | Okay, but let's say you actually want to do something with the
30 | response. Assuming it's text of a reasonable size, you could retrieve
31 | it as a string:
32 |
33 | ```scala
34 | Http(learnScala >- { str =>
35 | str.length
36 | })
37 | ```
38 |
39 | Like most handlers this one takes a function from the transformed
40 | response body to any type. Here the function is of type `String =>
41 | Int`. It merely returns the string length of the response.
42 |
43 | How about trying something more interesting with the body, like
44 | extracting the page's title?
45 |
46 | ```scala
47 | import XhtmlParsing._
48 | Http(learnScala > { nodes =>
49 | (nodes \\\\ "title").text
50 | })
51 | ```
52 |
53 | The `>` handler processes the response as XHTML and passes a
54 | `scala.xml.NodeSeq` to the supplied function. The `\\\\` projection
55 | function finds the `
` node, and `text` gives its contents.
56 |
57 | > Note: The parser imported here is very brittle. See the next section
58 | for alternatives that support real-world HTML.
59 |
--------------------------------------------------------------------------------
/docs/06/00.markdown:
--------------------------------------------------------------------------------
1 | Parsing Responses
2 | -----------------
3 |
4 | While you can easily wield Dispatch's stream and reader handlers to
5 | bind to any parser you like, several binding modules are available to
6 | make it even simpler.
7 |
--------------------------------------------------------------------------------
/docs/06/a.markdown:
--------------------------------------------------------------------------------
1 | Lift JSON
2 | ---------
3 |
4 | Lift-JSON is developed and supported by the [Lift][lift]
5 | framework. These are some basic instructions for using it with
6 | Dispatch.
7 |
8 | [lift]: http://liftweb.net/
9 |
10 | ### Example Usage
11 |
12 | Using Lift-JSON is very similar to using Dispatch's internal JSON
13 | representation. As always, start by importing the main Dispatch
14 | methods.
15 |
16 | ```scala
17 | import dispatch._
18 | ```
19 | But instead of importing the internal JSON methods, import Dispatch's
20 | interface to lift-json:
21 |
22 | ```scala
23 | import dispatch.liftjson.Js._
24 | ```
25 |
26 | We will now be able to use the `>#` operator in our handlers. For
27 | example, if we wanted to search for scala podcasts, we might use
28 | something like
29 |
30 | ```scala
31 | import net.liftweb.json.JsonAST._
32 |
33 | val http = new Http()
34 | val u = url("http://gpodder.net/search.json") < Map("q" -> "scala")
35 | http(u ># { json =>
36 | (json \ "title" children) flatMap( _ match {
37 | case JField("title", JString(d)) => Some(d)
38 | case JString(d) => Some(d)
39 | case _ => None
40 | })
41 | })
42 | ```
43 |
44 | This script starts by importing lift's JSON values (for pattern
45 | matching) and then creates the http executor and the gpodder url. We
46 | then execute with a handler which accepts a lift JValue and returns a
47 | list of the podcast titles. Simple, no?
48 |
--------------------------------------------------------------------------------
/docs/06/b.markdown:
--------------------------------------------------------------------------------
1 | TagSoup
2 | -------
3 |
4 | [TagSoup][tagsoup] is a SAX-compliant parser, that parses HTML instead
5 | of well formed XML. TagSoup is very resilient, allowing you to load HTML
6 | as found in the wild, to be processed as a scala NodeSeq.
7 |
8 | [tagsoup]: http://ccil.org/~cowan/XML/tagsoup/
9 |
10 | ### Example Usage
11 |
12 | To process the response with TagSoup you first have to make the handler
13 | verbs available. This can be done in several ways.
14 |
15 | ```scala
16 | import dispatch.tagsoup.TagSoupHttp._
17 | ```
18 | Now we can use the operators `>`, `tagsouped` and `as_tagsouped` in
19 | our handlers. If we want to find the title of a HTML page it can look
20 | something like this.
21 |
22 | ```scala
23 | import dispatch.tagsoup.TagSoupHttp._
24 |
25 | val title = Http(:/("example.org") > { ns =>
26 | (ns \\ "title").text
27 | })
28 | ```
29 | TagSoup let's you work with the HTML as a `scala.xml.NodeSeq` and as a
30 | convenience you can use `as_tagsouped` to retrieve it.
31 |
32 | ```scala
33 | val ns = Http(:/("example.com") as_tagsouped)
34 | ```
35 |
36 |
--------------------------------------------------------------------------------
/docs/06/c.markdown:
--------------------------------------------------------------------------------
1 | JSoup
2 | -----
3 |
4 | [JSoup][jsoup] is a library for working with real-world HTML. It parses HTML
5 | and provides a convenient API for extracting and manipulating data.
6 | JSoup is similar to TagSoup as they both lets you work with real-world HTML,
7 | but JSoup provide more functionality for working with the extracted result.
8 |
9 | [jsoup]: http://jsoup.org/
10 |
11 | ### Example Usage
12 |
13 | To use JSoup you have to make it handler verbs available. This is easiest done
14 | by importing them from JSoupHttp.
15 |
16 | ```scala
17 | import dispatch.jsoup.JSoupHttp._
18 | ```
19 | Now we can use the operators `>`, `jsouped`, `as_jsouped` and `as_jsoupedNodeSeq`
20 | in our handlers. As a start we can extract the title from a HTML page.
21 |
22 | ```scala
23 | import dispatch.jsoup.JSoupHttp._
24 |
25 | val title = Http(:/("example.org") > { doc =>
26 | doc.title
27 | })
28 | ```
29 | JSoup parse the HTML to a DOM like structure `org.jsoup.nodes.Document`. There
30 | is a rich set of find and search methods returning `org.jsoup.nodes.Elements`.
31 | Elements implements java List so they are very easy to use with scala
32 | collections, just make the usual `import scala.collection.JavaConversions._`.
33 |
34 | So, to extract all links from a page and put them in a list as absolute paths
35 | looks like this:
36 |
37 | ```scala
38 | import dispatch.jsoup.JSoupHttp._
39 | import scala.collection.JavaConversions._
40 |
41 | val request = :/("example.org") / "test.html"
42 | val list = Http(request > { doc =>
43 | doc.select("a[href]").map(_.attr("abs:href"))
44 | })
45 | ```
46 |
47 | JSoup is a great api for processing HTML, and scala makes it even better.
48 | To learn more of it's capabilities take a look in the [JSoup Cookbook][cookbook].
49 |
50 | [cookbook]: http://jsoup.org/cookbook/
51 |
--------------------------------------------------------------------------------
/docs/07/00.markdown:
--------------------------------------------------------------------------------
1 | Putting It All Together
2 | -----------------------
3 |
4 | Once you've mastered the basic request and response verbs, you can
5 | compose almost any kind of HTTP interaction.
6 |
--------------------------------------------------------------------------------
/docs/07/a.markdown:
--------------------------------------------------------------------------------
1 | Two Handlers Are Better Than One
2 | --------------------------------
3 |
4 | Dispatch has so many handy response handlers--but what if you want to
5 | use two with the same request? You *can't* do that with a stream of
6 | the response body, since this can only be consumed once, but for
7 | **headers** it's essential and straightforward.
8 |
9 | ### Header Chaining
10 |
11 | The recommended header-handling verb `>:+` provides a header `Map` and
12 | a request object to chain a second handler for the body.
13 |
14 | ```scala
15 | http(:/("dispatch.databinder.net") >:+ { (headers, req) =>
16 | headers("content-type").filter {
17 | _.contains("text/html")
18 | }.headOption.map { _ =>
19 | req > { nodes => (nodes \\\\ "h1").text }
20 | }.getOrElse {
21 | req >> { _ => "unknown content type" }
22 | }
23 | })
24 | ```
25 |
26 | > To facilitate case-insensitive handling, the keys in the header
27 | > `Map` are uniformly lowercase.
28 |
29 | The function passed to `>:+` should produce a second handler for the
30 | body of the response. In this case, it produces different response
31 | handlers depending on the headers present.
32 |
--------------------------------------------------------------------------------
/docs/08.markdown:
--------------------------------------------------------------------------------
1 | Dispatch in Android
2 | -------------------
3 |
4 | Many people are writing Android applications in Scala and want to do
5 | their HTTP communications with Dispatch. Unfortunately, this is not as
6 | easy as it should be.
7 |
8 | ### Android's HttpClient
9 |
10 | The Android platform ships with the Apache HttpClient library. It is
11 | packaged in the same JAR as the "Java" standard library. From an application
12 | perspective it is just there, always.
13 |
14 | When packaging for Android, you should be sure not to include any
15 | HttpClient jars; the classloader will always find the stock client's
16 | classes first. This allows apps that require HTTP communication—nearly
17 | all apps—to save space.
18 |
19 | The downside of shipping libraries in the standard classpath is that
20 | the environment becomes rigidly coupled to them. If you want to take
21 | advantage of a new feature in a library, too bad. You'd have to wait
22 | until all of your users upgrade their operating system to the new
23 | version—if it's ever made available for their devices.
24 |
25 | But wait, it gets worse! Updates to the HttpClient libraries in
26 | Android stalled. There was a procedural issue with binary
27 | compatibility; see [this thread][thread] for the gory details. The
28 | short of it is, we can rule out just waiting for those parties to fix
29 | the situation.
30 |
31 | [thread]: http://old.nabble.com/HttpClient-in-Android-td27915358.html
32 |
33 | ### Dealing with It
34 |
35 | Versions of Dispatch through 0.7.x can work with Android's random old
36 | version of HttpClient just fine. Version 0.8.x however depends on newer
37 | major versions of the HttpComponents libraries and certain interfaces
38 | that are not compatible with the code shipped with Android.
39 |
40 | It is *possible* to include the newer HttpComponents artifacts in
41 | the application package and to trick the classloader into finding
42 | them. How? You can obfuscate the class and package names with
43 | ProGuard, eliminating the namespace collisions.
44 |
45 | But really: isn't programming hard enough without some bytecode
46 | analyzer changing the names of all your symbols prior to runtime? For
47 | most of us, yes. That's why it's recommended here that you **use
48 | Dispatch 0.7.x with Android**. That works without any tricks and it's
49 | still a lot nicer than having to code to the client directly.
50 |
51 | ### What's in Store
52 |
53 | This unfortunate problem underscores the need for Dispatch to be
54 | independent of any single backend. The primary advancement of 0.8 was
55 | to support HttpComponet's async NIO client in addition to its standard
56 | blocking client, but that (like the rest of 0.8) is unusable on
57 | Android without some serious hackery.
58 |
59 | Dispatch 0.9 will therefore need to support entirely different
60 | backends such as Netty or one of the higher level async clients built
61 | on Netty. And main reason to choose a backend like that for Andorid
62 | apps will be, ironically enough, its absence in the platform
63 | distribution.
64 |
--------------------------------------------------------------------------------
/docs/09.markdown:
--------------------------------------------------------------------------------
1 | API Reference
2 | -------------
3 |
4 | ### Dispatch Periodic Table
5 |
6 | [This clever and attractive table][table] lists Dispatch's request and
7 | response verbs all in one place.
8 |
9 | [table]: http://www.flotsam.nl/dispatch-periodic-table.html
10 |
11 | ### Scaladocs
12 |
13 | [Generated documentation][scaladoc] is available for the latest
14 | version of Dispatch. Since most Dispatch interaction is defined by
15 | *verbs* that are added implicitly to requests, the interesting
16 | classes are `RequestVerbs`, `CallbackVerbs`, and so on.
17 |
18 | [scaladoc]: http://databinder.net/dispatch-doc/
19 |
20 |
21 | [sxr]: http://github.com/harrah/browse/tree/master
22 | [doc]: /dispatch-doc/
23 |
--------------------------------------------------------------------------------
/docs/99.markdown:
--------------------------------------------------------------------------------
1 | Who's Using Dispatch?
2 | ---------------------
3 |
4 | These are some projects and companies that have used Dispatch. Are
5 | *you* using Dispatch?
6 | [Fork this page](https://github.com/n8han/Databinder-Dispatch/blob/master/docs/99.markdown)
7 | on github.
8 |
9 | #### Novus
10 |
11 | Dispatch forms the foundation of an in-house SOAP client at [Novus](https://www.novus.com/) together with [scalaxb](http://scalaxb.org/). Thanks to these tools our standard response to "Can you do SOAP?" is "Bring it on!" instead of "Ew, what's this on my shoe?!"
12 |
13 | #### MLB.com
14 |
15 | In a past life, [maxaf](https://github.com/maxaf) wrote an internal baseball stats REST API client that was used to feed data into some very grueling PDF generation code. Dispatch was used to cleanly produce HTTP requests & parse responses without hurting engineer morale.
16 |
17 | #### conscript
18 |
19 | [conscript](https://github.com/n8han/conscript) uses Dispatch to fetch
20 | program descriptors from github.
21 |
22 | #### giter8
23 |
24 | [giter8](https://github.com/n8han/giter8) uses Dispatch to fetch
25 | templates from github.
26 |
27 | #### kiln2rally
28 |
29 | [kiln2rally](https://github.com/eed3si9n/kiln2rally) uses Dispatch along with [lift-json](https://github.com/lift/lift/tree/master/framework/lift-base/lift-json/) to hit Rally's awesome [web API](https://rally1.rallydev.com/slm/doc/webservice/index.jsp).
30 |
31 | #### posterous-sbt
32 |
33 | The [posterous-sbt](https://github.com/n8han/posterous-sbt) plugin
34 | uses Dispatch to publish project release notes.
35 |
36 | #### Unfiltered
37 |
38 | [Unfiltered](http://unfiltered.databinder.net/) uses Dispatch in its
39 | system tests.
40 |
--------------------------------------------------------------------------------
/docs/dispatch.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;
3 | }
4 | body p, h1, h2, h3, h4, h5, h6, blockquote, a, a.page, li {
5 | color: #444;
6 | }
--------------------------------------------------------------------------------
/docs/template.properties:
--------------------------------------------------------------------------------
1 | version=0.8.10
2 | version_link=http://notes.implicit.ly/post/38089150571/dispatch-classic-0-8-9
3 | scala=2.10.2
4 | github=dispatch/dispatch
5 |
--------------------------------------------------------------------------------
/futures/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-futures",
5 | "version":"0.8.6",
6 | "description":"Common interface to Java and Scala futures",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [],
13 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
14 | "sbt": false
15 | }
--------------------------------------------------------------------------------
/futures/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-futures",
5 | "version":"0.8.7",
6 | "description":"Common interface to Java and Scala futures",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [],
13 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
14 | "sbt": false
15 | }
--------------------------------------------------------------------------------
/futures/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-futures",
5 | "version":"0.8.8",
6 | "description":"Common interface to Java and Scala futures",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [],
16 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
17 | "sbt": false
18 | }
--------------------------------------------------------------------------------
/futures/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-futures",
4 | "version" : "0.8.9",
5 | "description" : "Common interface to Java and Scala futures",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ ],
11 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
12 | "licenses" : [ {
13 | "name" : "LGPL v3",
14 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
15 | } ],
16 | "sbt" : false
17 | }
--------------------------------------------------------------------------------
/futures/src/main/scala/Futures.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.futures
2 |
3 | import java.util.concurrent.{Executors,Callable,ExecutorService}
4 |
5 | object Futures extends AvailableFutures {
6 | /** Structural type coinciding with scala.actors.Future */
7 | type Future[T] = Function0[T] {
8 | def isSet: Boolean
9 | }
10 | }
11 | object DefaultFuture extends JucFuture {
12 | lazy val futureExecutor = Executors.newCachedThreadPool
13 | }
14 | trait AvailableFutures {
15 | /** @return values of futures that have completed their processing */
16 | def available[T](fs: Iterable[Futures.Future[T]]) = fs filter { _.isSet } map { _() } toList
17 | }
18 | /** Interface to futures functionality */
19 | trait Futures {
20 | def future[T](result: => T): Futures.Future[T]
21 | }
22 | /** A java.util.concurrent Future accessor, executor undefined */
23 | trait JucFuture extends Futures {
24 | def future[T](result: => T) = new WrappedJucFuture(
25 | futureExecutor.submit(new Callable[T]{
26 | def call = result
27 | })
28 | )
29 | /** Implement to customize the java.util.concurrent Executor, defaults to Executors.newCachedThreadPool */
30 | val futureExecutor: ExecutorService
31 | }
32 | /** Wraps java.util.concurrent.Future */
33 | class WrappedJucFuture[T](delegate: java.util.concurrent.Future[T]) extends (() => T) {
34 | def isSet = delegate.isDone
35 | def apply() = delegate.get()
36 | }
37 |
38 | /** Future accessor using a scala.actors future */
39 | object ActorsFuture extends Futures {
40 | def future[T](result: => T) = scala.actors.Futures.future(result)
41 | }
42 |
43 | trait StoppableFuture[T] extends (() => T) {
44 | def isSet: Boolean
45 | def stop()
46 | }
47 |
--------------------------------------------------------------------------------
/http+json/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-http-json",
5 | "version":"0.8.6",
6 | "description":"Adds JSON handler verbs to Dispatch",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/http+json/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-http-json",
5 | "version":"0.8.7",
6 | "description":"Adds JSON handler verbs to Dispatch",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/http+json/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-http-json",
5 | "version":"0.8.8",
6 | "description":"Adds JSON handler verbs to Dispatch",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | }],
20 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
21 | "sbt": false
22 | }
--------------------------------------------------------------------------------
/http+json/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-http-json",
4 | "version" : "0.8.9",
5 | "description" : "Adds JSON handler verbs to Dispatch",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | } ],
15 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
16 | "licenses" : [ {
17 | "name" : "LGPL v3",
18 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
19 | } ],
20 | "sbt" : false
21 | }
--------------------------------------------------------------------------------
/http+json/src/main/scala/JsHttp.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.json
2 | import dispatch.classic._
3 |
4 | trait ImplicitJsHandlers {
5 | /** Add JSON-processing method ># to dispatch.Request */
6 | implicit def handlerToJsHandlers(r: HandlerVerbs) = new JsHandlers(r)
7 | implicit def requestToJsHandlers(r: Request) = new JsHandlers(r)
8 | implicit def stringToJsHandlers(r: String) = new JsHandlers(new Request(r))
9 | }
10 |
11 | trait JsHttp extends ImplicitJsHandlers with Js
12 |
13 | object JsHttp extends JsHttp
14 |
15 | class JsHandlers(subject: HandlerVerbs) {
16 | /** Process response as JsValue in block */
17 | def ># [T](block: json.Js.JsF[T]) = subject >> { (stm, charset) =>
18 | block(json.Js(stm, charset))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/http+json/src/test/scala/JsonSpec.scala:
--------------------------------------------------------------------------------
1 | import org.specs2.mutable.Specification
2 |
3 | object JsonSpec extends Specification {
4 | import dispatch.classic.json._
5 | import JsHttp._
6 |
7 | val js = Js(""" { "a": {"a": "a string", "b": {"pi": 3.14159265 } }, "b": [1,2,3] } """)
8 | val expected_map = Map(
9 | JsString('a) -> JsObject(Map(
10 | JsString('a) -> JsString("a string"),
11 | JsString('b) -> JsObject(Map(
12 | JsString('pi) -> JsNumber(BigDecimal("3.14159265")))
13 | )
14 | )),
15 | JsString('b) -> JsArray(List(JsNumber(1), JsNumber(2), JsNumber(3)))
16 | )
17 | val js_list = Js("[1,2,3]")
18 | val expected_list = List(JsNumber(1), JsNumber(2), JsNumber(3))
19 |
20 | /** mock ># of Http#Response */
21 | object res { def ># [T](f: JsF[T]) = f(js) }
22 |
23 | "Parsed Json" should {
24 | "equal expected map" in {
25 | js.self must_== expected_map
26 | }
27 | "equal expected list" in {
28 | js_list.self must_== expected_list
29 | }
30 | "equal itself serilized and reparsed" in {
31 | js must_== JsValue.fromString(JsValue.toJson(js))
32 | }
33 | }
34 | "Nested extractor object" should {
35 | object TestExtractor extends Js {
36 | val a = new Obj('a) {
37 | val a = ('a ? str)
38 | val b = new Obj('b) {
39 | val pi = 'pi ? num
40 | }
41 | }
42 | val b = 'b ? (list ! num)
43 | }
44 | "match against top level object" in {
45 | val TestExtractor.a(a) = js
46 | a must_== expected_map(JsString('a))
47 | }
48 | "match against second level string" in {
49 | val TestExtractor.a.a(a) = js
50 | a must_== "a string"
51 | }
52 | "match against third level number" in {
53 | val TestExtractor.a.b.pi(p) = js
54 | p.toString must_== "3.14159265"
55 | }
56 | "match against a numeric list" in {
57 | val TestExtractor.b(b) = js
58 | b must_== List(1,2,3)
59 | }
60 | "replace second level string" in {
61 | res ># (TestExtractor.a.a << "land, ho") must_== (Js(
62 | """ { "a": {"a": "land, ho", "b": {"pi": 3.14159265 } }, "b": [1,2,3] } """
63 | ))
64 | }
65 | }
66 | "Flat extractors" should {
67 | val a = 'a ? obj
68 | val aa = 'a ? str
69 | val b = 'b ? obj
70 | val pi = 'pi ? num
71 | val l = 'b ? list
72 | "extract a top level object" in {
73 | val a(a0) = js
74 | a0 must_== expected_map(JsString('a))
75 | }
76 | "deeply extract a third level number" in {
77 | val a(b(pi(pi0))) = js
78 | pi0.toString must_== "3.14159265"
79 | }
80 | "match against an unextracted list" in {
81 | val l(l0) = js
82 | l0 must_== List(JsValue(1), JsValue(2), JsValue(3))
83 | }
84 | val num_list = list ! num
85 | "match for an unenclosed Json list" in {
86 | val num_list(l0) = js_list
87 | l0 must_== List(1,2,3)
88 | }
89 | "pattern-match correct elements" in {
90 | (js match {
91 | case b(b0) => b0
92 | case a(a0) => a0
93 | }) must_== expected_map(JsString('a))
94 | }
95 | "awkwardly replace second level string" in {
96 | val a(a0) = js
97 | res ># (a << (aa << "barnacles, ahoy")(a0)) must_== (Js(
98 | """ { "a": {"a": "barnacles, ahoy", "b": {"pi": 3.14159265 } }, "b": [1,2,3] } """
99 | ))
100 | }
101 | }
102 | "Function extractor" should {
103 | "extract a top level object" in {
104 | res ># ('a ! obj) must_== expected_map(JsString('a))
105 | }
106 | "extract a tuple of top level objects" in {
107 | res ># %('a ! obj, 'b ! list, 'b ! list) must_==
108 | (expected_map(JsString('a)), expected_list, expected_list)
109 | }
110 | "extract a second level string" in {
111 | res ># { ('a ! obj) andThen ('a ! str) } must_== "a string"
112 | }
113 | "extract a third level number" in {
114 | (res ># { ('a ! obj) andThen ('b ! obj) andThen ('pi ! num) }).toString must_== "3.14159265"
115 | }
116 | "work with map" in {
117 | List(js, js, js).map ('b ! (list ! num)) must_== (1 to 3).map{ _ => List(1,2,3) }.toList
118 | }
119 | def fun_l[T](ext: JsF[T]) = ext(js_list)
120 | "extract unenclosed Json list" in {
121 | fun_l(list ! num) must_== List(1,2,3)
122 | }
123 | }
124 | "assertion inserting" should {
125 | "replace second level string" in {
126 | res ># ('a << ('a << "barnacles, ahoy")) must_== (Js(
127 | """ { "a": {"a": "barnacles, ahoy", "b": {"pi": 3.14159265 } }, "b": [1,2,3] } """
128 | ))
129 | }
130 | "replace a second level object with a string" in {
131 | res ># ('a << ('b << "bonzai!")) must_== (Js(
132 | """ { "a": {"a": "a string", "b": "bonzai!" } , "b": [1,2,3] } """
133 | ))
134 | }
135 | }
136 | }
137 |
138 |
--------------------------------------------------------------------------------
/http-gae/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-gae",
5 | "version":"0.8.6",
6 | "description":"Executor with a modified Apache HttpClient for Google App Engine",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"com.google.appengine",
18 | "name": "appengine-api-1.0-sdk",
19 | "version": "1.5.5"
20 | }],
21 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
22 | "sbt": false
23 | }
--------------------------------------------------------------------------------
/http-gae/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-gae",
5 | "version":"0.8.7",
6 | "description":"Executor with a modified Apache HttpClient for Google App Engine",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"com.google.appengine",
18 | "name": "appengine-api-1.0-sdk",
19 | "version": "1.5.5"
20 | }],
21 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
22 | "sbt": false
23 | }
--------------------------------------------------------------------------------
/http-gae/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-gae",
5 | "version":"0.8.8",
6 | "description":"Executor with a modified Apache HttpClient for Google App Engine",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | },{
20 | "organization":"com.google.appengine",
21 | "name": "appengine-api-1.0-sdk",
22 | "version": "1.5.5"
23 | }],
24 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
25 | "sbt": false
26 | }
--------------------------------------------------------------------------------
/http-gae/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-gae",
4 | "version" : "0.8.9",
5 | "description" : "Executor with a modified Apache HttpClient for Google App Engine",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | }, {
15 | "organization" : "com.google.appengine",
16 | "name" : "appengine-api-1.0-sdk",
17 | "version" : "1.5.5"
18 | } ],
19 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
20 | "licenses" : [ {
21 | "name" : "LGPL v3",
22 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
23 | } ],
24 | "sbt" : false
25 | }
--------------------------------------------------------------------------------
/http-gae/src/main/scala/AppEngineHttp.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.gae
2 |
3 | import org.apache.http.params.HttpParams
4 | import org.apache.http.conn.ClientConnectionManager
5 |
6 | class AppEngineConfiguredClient(
7 | credentials: dispatch.classic.Http.CurrentCredentials
8 | ) extends dispatch.classic.ConfiguredHttpClient(credentials) {
9 | /** @return GAEConnectionManager for non-socket based connections */
10 | override def createClientConnectionManager = GAEConnectionManager
11 | /** No need for proxy support on app engine. */
12 | override protected def configureProxy(params: HttpParams) = params
13 | }
14 |
15 | object Http extends Http
16 |
17 | class Http extends dispatch.classic.Http {
18 | override def make_client = new AppEngineConfiguredClient(credentials)
19 | }
20 |
--------------------------------------------------------------------------------
/http-gae/src/main/scala/gae.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.gae
2 |
3 | import java.net._
4 | import java.util.concurrent.TimeUnit
5 | import org.apache.http.conn._
6 | import org.apache.http.params._
7 | import org.apache.http.conn.routing.HttpRoute
8 | import org.apache.http.conn.scheme._
9 |
10 | object GAEConnectionManager extends GAEConnectionManager
11 |
12 | // adapted from this Java impl: http://esxx.blogspot.com/2009/06/using-apaches-httpclient-on-google-app.html
13 |
14 | class GAEConnectionManager extends ClientConnectionManager {
15 | val no_socket_factory = new SchemeSocketFactory {
16 | def connectSocket(sock: Socket, remote:InetSocketAddress, local: InetSocketAddress, params: HttpParams): Socket = null
17 | def createSocket(params: HttpParams): Socket = null
18 | def isSecure(s: Socket): Boolean = false
19 | }
20 |
21 | protected val schemeRegistry = new SchemeRegistry
22 | schemeRegistry.register(new Scheme("http", 80, no_socket_factory))
23 | schemeRegistry.register(new Scheme("https", 443, no_socket_factory))
24 |
25 | override def getSchemeRegistry: SchemeRegistry = schemeRegistry
26 |
27 | override def requestConnection(route: HttpRoute, state: AnyRef): ClientConnectionRequest = {
28 | val mgr = this
29 | new ClientConnectionRequest {
30 | def abortRequest = {}
31 | def getConnection(timeout: Long, tunit: TimeUnit): ManagedClientConnection = {
32 | new GAEClientConnection(mgr, route, state)
33 | }
34 | }
35 | }
36 |
37 | override def releaseConnection(conn: ManagedClientConnection, validDuration: Long, timeUnit: TimeUnit) = {}
38 | override def closeIdleConnections(idletime: Long, tunit: TimeUnit) = {}
39 | override def closeExpiredConnections = {}
40 | override def shutdown = {}
41 | }
42 |
43 | import java.io._
44 | import java.net._
45 | import java.util.concurrent.TimeUnit
46 | import org.apache.http._
47 | import org.apache.http.conn._
48 | import org.apache.http.conn.routing.HttpRoute
49 | import org.apache.http.entity.ByteArrayEntity
50 | import org.apache.http.message.BasicHttpResponse
51 | import org.apache.http.params._
52 | import org.apache.http.protocol._
53 |
54 | import com.google.appengine.api.urlfetch._
55 |
56 | object GAEClientConnection {
57 | private var urlFS: URLFetchService = URLFetchServiceFactory.getURLFetchService
58 | }
59 |
60 | class GAEClientConnection(var cm: ClientConnectionManager, var route: HttpRoute, var state: AnyRef, var closed: Boolean) extends ManagedClientConnection {
61 | import GAEClientConnection._
62 |
63 | def this(cm: ClientConnectionManager, route: HttpRoute, state: AnyRef) =
64 | this(cm, route, state, true)
65 |
66 | private var request: HTTPRequest = _
67 | private var response: HTTPResponse = _
68 | private var reusable: Boolean = _
69 |
70 | override def isSecure: Boolean = route.isSecure
71 | override def getRoute: HttpRoute = route
72 | override def getSSLSession: javax.net.ssl.SSLSession = null
73 |
74 | override def open(route: HttpRoute, context: HttpContext, params: HttpParams) = {
75 | close
76 | this.route = route
77 | }
78 |
79 | override def tunnelTarget(secure: Boolean, params: HttpParams) = throw new IOException("tunnelTarget not supported")
80 | override def tunnelProxy(next: HttpHost, secure: Boolean, params: HttpParams) = throw new IOException("tunnelProxy not supported")
81 | override def layerProtocol(context: HttpContext, params: HttpParams) = throw new IOException("layerProtocol not supported")
82 | override def markReusable = reusable = true
83 | override def unmarkReusable = reusable = false
84 | override def isMarkedReusable: Boolean = reusable
85 | override def setState(state: AnyRef) = this.state = state
86 | override def getState: AnyRef = state
87 | override def setIdleDuration(duration: Long, unit: TimeUnit) = {}
88 | override def isResponseAvailable(timeout: Int): Boolean = response != null
89 |
90 | override def sendRequestHeader(request: HttpRequest) = {
91 | try {
92 | val host = route.getTargetHost
93 | val port = if (host.getPort() == -1) "" else ":" + host.getPort()
94 | val uri = new URI(host.getSchemeName
95 | + "://"
96 | + host.getHostName
97 | + port
98 | + request.getRequestLine.getUri)
99 |
100 | this.request = new HTTPRequest(uri.toURL,
101 | HTTPMethod.valueOf(request.getRequestLine.getMethod),
102 | FetchOptions.Builder.disallowTruncate.doNotFollowRedirects)
103 | }
104 | catch {
105 | case ex: URISyntaxException =>
106 | throw new IOException("Malformed request URI: " + ex.getMessage)
107 | case ex: IllegalArgumentException =>
108 | throw new IOException("Unsupported HTTP method: " + ex.getMessage)
109 | }
110 |
111 | for (h <- request.getAllHeaders)
112 | this.request.addHeader(new HTTPHeader(h.getName, h.getValue))
113 | }
114 |
115 | override def sendRequestEntity(request: HttpEntityEnclosingRequest ) = {
116 | val baos = new ByteArrayOutputStream
117 | if (request.getEntity != null)
118 | request.getEntity.writeTo(baos)
119 | this.request.setPayload(baos.toByteArray)
120 | }
121 |
122 | override def receiveResponseHeader: HttpResponse = {
123 | if (this.response == null) {
124 | flush
125 | }
126 |
127 | val response: HttpResponse = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1),
128 | this.response.getResponseCode,
129 | null)
130 |
131 | val i = this.response.getHeaders.iterator
132 | while (i.hasNext) {
133 | val h = i.next
134 | response.addHeader(h.getName, h.getValue)
135 | }
136 |
137 | response
138 | }
139 |
140 | override def receiveResponseEntity(response: HttpResponse) = {
141 | if (this.response == null)
142 | throw new IOException("receiveResponseEntity called on closed connection")
143 |
144 | val bae = new ByteArrayEntity(this.response.getContent)
145 | bae.setContentType(response.getFirstHeader("Content-Type"))
146 | response.setEntity(bae)
147 |
148 | //response = null
149 | }
150 |
151 | override def flush = {
152 | if (request != null) {
153 | response = urlFS.fetch(request)
154 | request = null
155 | }
156 | else {
157 | response = null
158 | }
159 | }
160 |
161 | override def close {
162 | request = null
163 | response = null
164 | closed = true
165 | }
166 |
167 | override def isOpen: Boolean = (request != null) || (response != null)
168 | override def isStale: Boolean = !isOpen && !closed
169 |
170 | override def setSocketTimeout(timeout: Int) = {}
171 | override def getSocketTimeout: Int = -1
172 |
173 | override def shutdown = close
174 | override def getMetrics: HttpConnectionMetrics = null
175 |
176 | override def getLocalAddress: InetAddress = null
177 | override def getLocalPort: Int = 0
178 | override def getRemoteAddress: InetAddress = null
179 | override def getRemotePort: Int = {
180 | val host: HttpHost = route.getTargetHost
181 | cm.getSchemeRegistry.getScheme(host).resolvePort(host.getPort)
182 | }
183 |
184 | override def releaseConnection = cm.releaseConnection(this, java.lang.Long.MAX_VALUE, TimeUnit.MILLISECONDS)
185 |
186 | override def abortConnection = {
187 | unmarkReusable
188 | shutdown
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/http/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-http",
5 | "version":"0.8.6",
6 | "description":"Standard HTTP executor, uses Apache DefaultHttpClient",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/http/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-http",
5 | "version":"0.8.7",
6 | "description":"Standard HTTP executor, uses Apache DefaultHttpClient",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/http/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-http",
5 | "version":"0.8.8",
6 | "description":"Standard HTTP executor, uses Apache DefaultHttpClient",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | }],
20 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
21 | "sbt": false
22 | }
--------------------------------------------------------------------------------
/http/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-http",
4 | "version" : "0.8.9",
5 | "description" : "Standard HTTP executor, uses Apache DefaultHttpClient",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | } ],
15 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
16 | "licenses" : [ {
17 | "name" : "LGPL v3",
18 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
19 | } ],
20 | "sbt" : false
21 | }
--------------------------------------------------------------------------------
/http/src/main/scala/ConfiguredHttpClient.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic
2 |
3 | import org.apache.http.{HttpHost,HttpVersion,HttpRequest,HttpResponse}
4 | import org.apache.http.auth.{AuthScope, NTCredentials, UsernamePasswordCredentials}
5 | import org.apache.http.impl.client.{DefaultHttpClient, BasicCredentialsProvider}
6 | import org.apache.http.conn.params.ConnRouteParams
7 | import org.apache.http.params.{HttpProtocolParams, BasicHttpParams, HttpParams}
8 | import org.apache.http.client.params.AuthPolicy
9 |
10 | /** Basic extension of DefaultHttpClient defaulting to Http 1.1, UTF8,
11 | * and no Expect-Continue. Scopes authorization credentials to particular
12 | * requests thorugh a DynamicVariable. */
13 | class ConfiguredHttpClient(
14 | credentials: Http.CurrentCredentials
15 | ) extends DefaultHttpClient {
16 | protected var proxyScope: Option[AuthScope] = None
17 | protected var proxyBasicCredentials: Option[UsernamePasswordCredentials] = None
18 | protected var proxyNTCredentials: Option[NTCredentials] = None
19 | protected def configureProxy(params: HttpParams) = {
20 | val sys = System.getProperties()
21 | val host = sys.getProperty("https.proxyHost",
22 | sys.getProperty("http.proxyHost"))
23 | val port = sys.getProperty("https.proxyPort",
24 | sys.getProperty("http.proxyPort"))
25 | val user = sys.getProperty("https.proxyUser",
26 | sys.getProperty("http.proxyUser"))
27 | val password = sys.getProperty("https.proxyPassword",
28 | sys.getProperty("http.proxyPassword"))
29 | val domain = sys.getProperty("https.auth.ntlm.domain",
30 | sys.getProperty("http.auth.ntlm.domain"))
31 | if (host != null && !host.trim.isEmpty && port != null && !port.trim.isEmpty) {
32 | ConnRouteParams.setDefaultProxy(params,
33 | new HttpHost(host, port.toInt))
34 | proxyScope = Some(new AuthScope(host, port.toInt))
35 | }
36 | if (user != null && !user.trim.isEmpty && password != null && !password.trim.isEmpty) {
37 | proxyBasicCredentials =
38 | Some(new UsernamePasswordCredentials(user, password))
39 | // We should pass our hostname, actually
40 | // Also, we ought to support "domain/user" syntax
41 | proxyNTCredentials = Some(new NTCredentials(
42 | user, password, "", Option(domain) getOrElse ""))
43 | }
44 | params
45 | }
46 |
47 | override def createHttpParams = {
48 | val params = new BasicHttpParams
49 | HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1)
50 | HttpProtocolParams.setContentCharset(params, Request.factoryCharset)
51 | HttpProtocolParams.setUseExpectContinue(params, false)
52 | configureProxy(params)
53 | }
54 | /** Follow response redirect regardless of request method */
55 | setRedirectStrategy(new org.apache.http.impl.client.DefaultRedirectStrategy {
56 | import org.apache.http.protocol.HttpContext
57 | import org.apache.http.HttpStatus._
58 | override def isRedirected(req: HttpRequest, res: HttpResponse, ctx: HttpContext) =
59 | (SC_MOVED_TEMPORARILY :: SC_MOVED_PERMANENTLY :: SC_TEMPORARY_REDIRECT ::
60 | SC_SEE_OTHER :: Nil) contains res.getStatusLine.getStatusCode
61 | })
62 | setCredentialsProvider(new BasicCredentialsProvider {
63 | override def getCredentials(scope: AuthScope) = credentials.value flatMap {
64 | case (auth_scope, Credentials(n, p)) if scope.`match`(auth_scope) >= 0 =>
65 | Some(new UsernamePasswordCredentials(n, p))
66 | case _ => None
67 | } orElse {
68 | // This test probably returns true even if only the port matches
69 | if (proxyScope exists (scope.`match`(_) >= 0)) {
70 | if (scope.getScheme() == AuthPolicy.NTLM) proxyNTCredentials
71 | else proxyBasicCredentials
72 | } else None
73 | } orNull
74 | })
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/http/src/main/scala/Http.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic
2 |
3 | import collection.Map
4 | import collection.immutable.{Map => IMap}
5 | import java.net.URI
6 |
7 | import org.apache.http.{HttpHost,HttpRequest,HttpResponse,HttpEntity}
8 | import org.apache.http.client.HttpClient
9 | import org.apache.http.client.entity.UrlEncodedFormEntity
10 | import org.apache.http.client.methods.HttpRequestBase
11 | import org.apache.http.client.utils.URLEncodedUtils
12 | import org.apache.http.auth.AuthScope
13 | import org.apache.http.params.HttpProtocolParams
14 |
15 | import org.apache.http.entity.{StringEntity,FileEntity}
16 | import org.apache.http.message.BasicNameValuePair
17 | import org.apache.http.conn.params.ConnRouteParams
18 | import org.apache.http.conn.ClientConnectionManager
19 |
20 | /** Http access point. Standard instances to be used by a single thread. */
21 | class Http extends BlockingHttp {
22 | /** Unadorned handler return type */
23 | type HttpPackage[T] = T
24 | /** Synchronously access and return plain result value */
25 | def pack[T](req: { def abort() }, result: => T) = result
26 | }
27 |
28 | /** May be used directly from any thread. */
29 | object Http extends Http with thread.Safety {
30 | type CurrentCredentials = util.DynamicVariable[Option[(AuthScope, Credentials)]]
31 | }
32 |
33 | trait BlockingHttp extends HttpExecutor with BlockingCallback {
34 | import util.control.{Exception => Exc}
35 | /** This reference's underlying value is updated for the current thread when a
36 | * request specifies credentials. Typically passed to ConfiguredHttpClient. */
37 | val credentials = new Http.CurrentCredentials(None)
38 | /** Reference to the underlying client. Override make_client define your own. */
39 | final val client = make_client
40 | /** Defaults to dispatch.ConfiguredHttpClient(credentials), override to customize. */
41 | def make_client: HttpClient = new ConfiguredHttpClient(credentials)
42 |
43 | /** Execute method for the given host. */
44 | private def execute(host: HttpHost, req: HttpRequestBase): HttpResponse = {
45 | client.execute(host, req)
46 | }
47 | /** Execute for given optional parameters, with logging. Creates local scope for credentials. */
48 | def execute[T](host: HttpHost,
49 | credsopt: Option[Credentials],
50 | req: HttpRequestBase,
51 | block: HttpResponse => T,
52 | listener: ExceptionListener) =
53 | pack(req, try {
54 | block(credsopt.map { creds =>
55 | credentials.withValue(Some((
56 | new AuthScope(host.getHostName, host.getPort), creds)
57 | ))(execute(host, req))
58 | } getOrElse { execute(host, req) })
59 | } catch {
60 | case e => listener.lift(e); throw e
61 | })
62 | /** Make sure the entity is consumed. */
63 | def consumeContent(entity: Option[HttpEntity]) {
64 | entity.foreach(org.apache.http.util.EntityUtils.consume)
65 | }
66 | /** Potentially wraps payload, e.g. in a Future */
67 | def pack[T](req: { def abort() }, result: => T): HttpPackage[T]
68 |
69 | /** Shutdown connection manager, threads. */
70 | def shutdownClient() = client.getConnectionManager.shutdown()
71 | }
72 |
--------------------------------------------------------------------------------
/http/src/main/scala/https.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic
2 |
3 | /** Trust all TLS certs */
4 | trait HttpsLeniency { self: BlockingHttp =>
5 | import java.net.Socket
6 | import javax.net.ssl.{X509TrustManager, SSLContext}
7 | import java.security.KeyStore
8 | import java.security.cert.X509Certificate
9 | import org.apache.http.conn.scheme.Scheme
10 | import org.apache.http.conn.ssl.SSLSocketFactory
11 |
12 | private def socket_factory = {
13 | val truststore = KeyStore.getInstance(KeyStore.getDefaultType())
14 | truststore.load(null, null)
15 | val context = SSLContext.getInstance(SSLSocketFactory.TLS)
16 | val manager = new X509TrustManager {
17 | def checkClientTrusted(xcs: Array[X509Certificate], string: String) {}
18 | def checkServerTrusted(xcs: Array[X509Certificate], string: String) {}
19 | def getAcceptedIssuers = null
20 | }
21 | context.init(null, Array(manager), null)
22 | new SSLSocketFactory(context,
23 | SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
24 | }
25 |
26 | client.getConnectionManager.getSchemeRegistry.register(
27 | new Scheme("https", 443, socket_factory)
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/http/src/main/scala/thread/thread.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.thread
2 |
3 | import dispatch.classic._
4 | import dispatch.classic.futures._
5 | import org.apache.http.client.HttpClient
6 |
7 | /** Http with a thread-safe client */
8 | trait Safety { self: BlockingHttp =>
9 | /** Maximum number of connections in pool, default is 50 */
10 | def maxConnections = 50
11 | /** Maximum number of connections one one route, default is maxConnections */
12 | def maxConnectionsPerRoute = maxConnections
13 | /** Shutdown connection manager if no longer in use. */
14 |
15 | override def make_client: HttpClient = new ThreadSafeHttpClient(
16 | credentials, maxConnections, maxConnectionsPerRoute)
17 | }
18 |
19 | /** Wraps each call in a (threaded) future. */
20 | trait Future extends Safety { self: BlockingHttp =>
21 | type HttpPackage[T] = StoppableFuture[T]
22 | def pack[T](request: { def abort() }, result: => T) = new StoppableFuture[T] {
23 | val delegate = DefaultFuture.future(result)
24 | def apply() = delegate.apply()
25 | def isSet = delegate.isSet
26 | def stop() = request.abort()
27 | }
28 | }
29 |
30 | class Http extends BlockingHttp with Future
31 |
32 | /** Client with a ThreadSafeClientConnManager */
33 | class ThreadSafeHttpClient(
34 | credentials: Http.CurrentCredentials,
35 | maxConnections: Int,
36 | maxConnectionsPerRoute: Int
37 | ) extends ConfiguredHttpClient(credentials) {
38 | import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager
39 | override def createClientConnectionManager = {
40 | val cm = new ThreadSafeClientConnManager()
41 | cm.setMaxTotal(maxConnections)
42 | cm.setDefaultMaxPerRoute(maxConnectionsPerRoute)
43 | cm
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/http/src/test/scala/HttpSpec.scala:
--------------------------------------------------------------------------------
1 | import org.apache.http.client.methods.HttpUriRequest
2 | import org.apache.http.client.{ResponseHandler, HttpClient}
3 | import org.apache.http.protocol.HttpContext
4 | import org.apache.http.{HttpRequest, HttpHost}
5 | import org.specs2.mutable.Specification
6 |
7 | object HttpSpec extends Specification {
8 | import dispatch.classic._
9 |
10 | import org.apache.http.protocol.HTTP.CONTENT_ENCODING
11 |
12 | val jane = "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.\n"
13 |
14 | "Singleton Http test get" should {
15 | val req = new Request("http://technically.us/test.text")
16 | "not throw exception if credentials are specified without explicit host" in {
17 | Http (req as ("user", "pass") as_str) must_== (jane)
18 | }
19 | get_specs(req)
20 | }
21 | "Bound host get" should {
22 | val req = :/("technically.us") / "test.text"
23 | "not throw exception if credentials are specified with explicit host" in {
24 | Http (req as ("user", "pass") as_str) must_== (jane)
25 | }
26 | get_specs(req)
27 | }
28 | "Combined request get" should {
29 | get_specs(:/("technically.us") <& /("test.text"))
30 | }
31 | "Backwards combined request get" should {
32 | get_specs(/("test.text") <& :/("technically.us"))
33 | }
34 |
35 | "Http" should {
36 | class SimpleDelegatingHttpClient(realClient: HttpClient) extends HttpClient {
37 | def getParams = realClient.getParams
38 | def getConnectionManager = realClient.getConnectionManager
39 | def execute(request: HttpUriRequest) = realClient.execute(request)
40 | def execute(request: HttpUriRequest, context: HttpContext) = realClient.execute(request, context)
41 | def execute(target: HttpHost, request: HttpRequest) = realClient.execute(target, request)
42 | def execute(target: HttpHost, request: HttpRequest, context: HttpContext) = realClient.execute(target, request, context)
43 | def execute[T](request: HttpUriRequest, responseHandler: ResponseHandler[_ <: T]) = realClient.execute(request, responseHandler)
44 | def execute[T](request: HttpUriRequest, responseHandler: ResponseHandler[_ <: T], context: HttpContext) = realClient.execute(request, responseHandler, context)
45 | def execute[T](target: HttpHost, request: HttpRequest, responseHandler: ResponseHandler[_ <: T]) = realClient.execute(target, request, responseHandler)
46 | def execute[T](target: HttpHost, request: HttpRequest, responseHandler: ResponseHandler[_ <: T], context: HttpContext) = realClient.execute(target, request, responseHandler, context)
47 | }
48 |
49 | "allow override" in {
50 | val http = new Http with thread.Safety {
51 | override def make_client: HttpClient = new SimpleDelegatingHttpClient(super.make_client)
52 | }
53 | http must not beNull; // i.e. this code should compile
54 | http.shutdown()
55 | success
56 | }
57 | }
58 |
59 | val http = new Http
60 | val httpfuture = new thread.Http
61 | def get_specs(test: Request) = {
62 | // start some connections as futures
63 | val stream = httpfuture(test >> { stm =>
64 | // the nested scenario here contrived fails with actors.Futures
65 | httpfuture((test >> { stm =>
66 | scala.io.Source.fromInputStream(stm).mkString
67 | }) ~> { string =>
68 | string // identity function
69 | })
70 | })
71 | val string = httpfuture(test as_str)
72 | val bytes = httpfuture(test >>> new java.io.ByteArrayOutputStream)
73 | // test a few other things
74 | "throw status code exception when applied to non-existent resource" in {
75 | http (test / "do_not_want" as_str) must throwA[StatusCode]
76 | }
77 | "allow any status code with x" in {
78 | (http x (test / "do_not_want" as_str) {
79 | case (404, _, _, out) => out()
80 | case _ => "success is failure"
81 | }) must contain ("404 Not Found")
82 | }
83 | "serve a gzip header" in {
84 | http(test.gzip >:> { _(CONTENT_ENCODING) }) must_== (Set("gzip"))
85 | }
86 | // check back on the futures
87 | "equal expected string" in {
88 | string() must_== jane
89 | }
90 | "stream to expected sting" in {
91 | stream()() must_== jane
92 | }
93 | "write to expected sting bytes" in {
94 | bytes().toByteArray.toList must_== jane.getBytes.toList
95 | }
96 |
97 | "equal expected string with gzip encoding, using future" in {
98 | httpfuture(test.gzip >+ { r => (r as_str, r >:> { _(CONTENT_ENCODING) }) } )() must_== (jane, Set("gzip"))
99 | }
100 | val h = new Http// single threaded Http instance
101 | "equal expected string with a gzip defaulter" in {
102 | val my_defaults = /\.gzip
103 | h(my_defaults <& test >+ { r => (r as_str, r >:> { _(CONTENT_ENCODING) }) } ) must_== (jane, Set("gzip"))
104 | }
105 |
106 | "process html page" in {
107 | import XhtmlParsing._
108 | h(url("http://technically.us/") > { xml =>
109 | (xml \\ "title").text
110 | }) must_== "technically.us"
111 | }
112 |
113 | "process xml response" in {
114 | h(url("http://technically.us/test.xml") <> { xml =>
115 | (xml \ "quote").text.trim
116 | }) must_== jane.trim
117 | }
118 |
119 | "equal expected string without gzip encoding, with handler chaining" in {
120 | h(test >+> { r => r >:> { headers =>
121 | r >- { (_, headers(CONTENT_ENCODING)) }
122 | } }) must_== (jane, Set())
123 | }
124 | "equal expected string with gzip encoding, with >:+" in {
125 | h(test.gzip >:+ { (headers, r) =>
126 | r >- { (_, headers(CONTENT_ENCODING.toLowerCase)) }
127 | }) must_== (jane, Seq("gzip"))
128 | }
129 | }
130 | "Path building responses" should {
131 | // using singleton Http, will need to shut down after all tests
132 | val test2 = "and they were both ever sensible of the warmest gratitude\n"
133 | "work with chaining" in {
134 | Http( :/("technically.us") / "test" / "test.text" as_str ) must_== test2
135 | }
136 | "work with factories" in {
137 | Http( :/("technically.us") <& /("test") <& /("test.text") as_str ) must_== test2
138 | }
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/json/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-json",
5 | "version":"0.8.6",
6 | "description":"A JSON parser",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [],
13 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
14 | "sbt": false
15 | }
--------------------------------------------------------------------------------
/json/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-json",
5 | "version":"0.8.7",
6 | "description":"A JSON parser",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [],
13 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
14 | "sbt": false
15 | }
--------------------------------------------------------------------------------
/json/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-json",
5 | "version":"0.8.8",
6 | "description":"A JSON parser",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [],
16 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
17 | "sbt": false
18 | }
--------------------------------------------------------------------------------
/json/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-json",
4 | "version" : "0.8.9",
5 | "description" : "A JSON parser",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ ],
11 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
12 | "licenses" : [ {
13 | "name" : "LGPL v3",
14 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
15 | } ],
16 | "sbt" : false
17 | }
--------------------------------------------------------------------------------
/json/src/main/scala/Json.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.json
2 |
3 | // http://paste.pocoo.org/raw/106688/
4 |
5 | import scala.util.parsing.combinator._
6 | import scala.util.parsing.combinator.syntactical._
7 | import scala.util.parsing.combinator.lexical._
8 | import scala.util.parsing.input.{CharSequenceReader,CharArrayReader}
9 | import java.io.{InputStream, InputStreamReader}
10 |
11 | class JsonParser extends StdTokenParsers with ImplicitConversions {
12 | type Tokens = scala.util.parsing.json.Lexer
13 | val lexical = new Tokens
14 |
15 | lexical.reserved ++= List("true", "false", "null")
16 | lexical.delimiters ++= List("{", "}", "[", "]", ":", ",")
17 |
18 | def jsonObj = "{" ~> repsep(objPair, ",") <~ "}" ^^ (JsObject.apply _)
19 | def jsonArr = "[" ~> repsep(jsonVal, ",") <~ "]" ^^ (JsArray.apply _)
20 | def objPair = jsonStr ~ (":" ~> jsonVal) ^^ { case x ~ y => (x, y) }
21 | def jsonVal: Parser[JsValue] =
22 | (jsonObj | jsonArr | jsonStr | jsonNum | "true" ^^^ JsTrue | "false" ^^^ JsFalse | "null" ^^^ JsNull)
23 | def jsonStr = accept("string", { case lexical.StringLit(n) => JsString(n)})
24 | def jsonNum = accept("number", { case lexical.NumericLit(n) => JsNumber(n) })
25 |
26 | def apply(input: CharSequenceReader): JsValue =
27 | phrase(jsonVal)(new lexical.Scanner(input)) match {
28 | case Success(result, _) => result
29 | case _ => throw new Exception("Illegal JSON format")
30 | }
31 | }
32 |
33 | object JsonParser {
34 | def apply(input: CharSequenceReader): JsValue = {
35 | val parse = new JsonParser
36 | parse(input)
37 | }
38 | }
39 |
40 | sealed trait JsValue {
41 | type T
42 | def self: T
43 | override def toString = JsValue.toJson(this)
44 | }
45 |
46 | case class JsString(override val self: String) extends JsValue {
47 | type T = String
48 | }
49 |
50 | /**
51 | * This can also be implemented with as a Double, even though BigDecimal is
52 | * more loyal to the json spec.
53 | * NOTE: Subtle bugs can arise, i.e.
54 | * BigDecimal(3.14) != BigDecimal("3.14")
55 | * such are the perils of floating point arithmetic.
56 | */
57 | case class JsNumber(override val self: BigDecimal) extends JsValue {
58 | type T = BigDecimal
59 | }
60 |
61 | // This can extend scala.collection.MapProxy to implement Map interface
62 | case class JsObject(override val self: Map[JsString, JsValue]) extends JsValue {
63 | type T = Map[JsString, JsValue]
64 | }
65 |
66 | // This can extend scala.SeqProxy to implement Seq interface
67 | case class JsArray(override val self: List[JsValue]) extends JsValue {
68 | type T = List[JsValue]
69 | }
70 |
71 | sealed abstract case class JsBoolean(b: Boolean) extends JsValue {
72 | type T = Boolean
73 | val self = b
74 | }
75 |
76 | object JsTrue extends JsBoolean(true)
77 | object JsFalse extends JsBoolean(false)
78 | case object JsNull extends JsValue {
79 | type T = Null
80 | val self = null
81 | }
82 |
83 | object JsObject {
84 | def apply() = new JsObject(Map())
85 | def apply(xs: Seq[(JsString, JsValue)]) = new JsObject(Map() ++ xs)
86 | }
87 |
88 | object JsNumber {
89 | def apply(n: Int) = new JsNumber(BigDecimal(n))
90 | def apply(n: Long) = new JsNumber(BigDecimal(n))
91 | def apply(n: Float) = new JsNumber(BigDecimal(n))
92 | def apply(n: Double) = new JsNumber(BigDecimal(n))
93 | def apply(n: BigInt) = new JsNumber(BigDecimal(n))
94 | def apply(n: String) = new JsNumber(BigDecimal(n))
95 | }
96 |
97 | object JsString {
98 | def apply(x: Any): JsString = x match {
99 | case s: Symbol => new JsString(s.name)
100 | case s: String => new JsString(s)
101 | // This is a hack needed for JsObject.
102 | // The other option is to throw an exception here.
103 | case _ => new JsString(x.toString)
104 | }
105 | }
106 |
107 | object JsValue {
108 | def apply(x: Any): JsValue = x match {
109 | case null => JsNull
110 | case j: JsValue => j
111 | case true => JsTrue
112 | case false => JsFalse
113 | case s @ (_: String | _: Symbol) => JsString(s)
114 | case n: Int => JsNumber(n)
115 | case n: Long => JsNumber(n)
116 | case n: Float => JsNumber(n)
117 | case n: Double => JsNumber(n)
118 | case n: BigInt => JsNumber(n)
119 | case n: BigDecimal => JsNumber(n)
120 | case xs: List[_] => JsArray(xs.map(JsValue.apply))
121 | case m: scala.collection.Map[_, _] => JsObject(
122 | Map.empty ++ (for ((key, value) <- m) yield (JsString(key), JsValue(value)))
123 | )
124 | case xs: Seq[_] => JsArray(xs.map(JsValue.apply).toList)
125 | }
126 |
127 | def fromString(s: String) = JsonParser(new CharArrayReader(s.toCharArray))
128 | def fromStream(s: InputStream, charset: String = "utf-8") =
129 | JsonParser(new CharArrayReader(io.Source.fromInputStream(s, charset).mkString.toCharArray))
130 |
131 | def toJson(x: JsValue): String = {
132 | val w = new java.lang.StringBuilder
133 | writeJson(x, w)
134 | w.toString
135 | }
136 |
137 | def writeJson(x: JsValue, w: Appendable) {
138 | x match {
139 | case JsNull => w.append("null")
140 | case JsBoolean(b) => w.append(b.toString)
141 | case JsNumber(n) => w.append(n.toString)
142 | case JsString(s) => {
143 | w.append("\"")
144 | s foreach {
145 | case '\\' => w.append("\\\\")
146 | case '\n' => w.append("\\n")
147 | case '\r' => w.append("\\r")
148 | case '\t' => w.append("\\t")
149 | case '"' => w.append("\\\"")
150 | case c if c <= 0x1F => w.append("\\u%04x".format(c.toInt))
151 | case c => w.append(c)
152 | }
153 | w.append("\"")
154 | }
155 | case JsArray(ys) => {
156 | w.append("[")
157 | var first = true
158 | for (y <- ys) {
159 | if (first) {
160 | first = false
161 | } else {
162 | w.append(", ")
163 | }
164 | writeJson(y, w)
165 | }
166 | w.append("]")
167 | }
168 | case JsObject(m) => {
169 | w.append("{")
170 | var first = true
171 | for ((k,v) <- m) {
172 | if (first) {
173 | first = false
174 | } else {
175 | w.append(", ")
176 | }
177 | writeJson(k, w)
178 | w.append(": ")
179 | writeJson(v, w)
180 | }
181 | w.append("}")
182 | }
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/json/src/main/scala/JsonExtractor.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.json
2 |
3 | /** Json Extractor, extracts a value of type T from the given JsValue. */
4 | trait Extract[T] {
5 | def unapply(js: JsValue): Option[T]
6 | override def toString = getClass.getName
7 | }
8 | /** Json Inserter, adds or replaces properties in the given JsValue
9 | (error if not a JsObject) */
10 | trait Insert[T] extends Extract[T] {
11 | /** Insert value into JsValue given after */
12 | def << (t: T)(js: JsValue): JsObject
13 | /** Insert value into empty JsValue */
14 | def <<| (t: T) = <<(t)(Js())
15 | }
16 | /** Json Property Extractor, extracts a value of type T assigned to
17 | the property sym in a given JsObject (checks any JsValue). Additionally,
18 | can replace the property sym with a new value T in the << opertion. */
19 | case class Property[T](sym: Symbol, ext: Extract[T]) extends Extract[T] with Insert[T] {
20 | def unapply(js: JsValue) = js match {
21 | case js: JsObject => js.self.get(JsString(sym)) flatMap ext.unapply
22 | case _ => None
23 | }
24 | /** Adds or replaces the property sym in the given JsValue (JsObject) */
25 | def << (t: T)(js: JsValue) = js match {
26 | case JsObject(m) => JsObject(m + (JsString(sym) -> JsValue(t)))
27 | case js => sys.error("Unable to replace property in " + js)
28 | }
29 | override def toString = "%s ! %s" format (sym, ext)
30 | }
31 | /** Extractor that resolves first by its parent extractor, if present. */
32 | case class Child[T, E <: Insert[T]](parent: Option[Obj], self: E) extends Extract[T] with Insert[T] {
33 | def unapply(js: JsValue) = parent map { parent => js match {
34 | case parent(self(t)) => Some(t)
35 | } } getOrElse { js match {
36 | case self(t) => Some(t)
37 | case _ => None
38 | }
39 | }
40 | /** Inserts the value t in self and replaces self in parent, if any. */
41 | def << (t: T)(js: JsValue) = parent map { parent => js match {
42 | case parent(my_js) => (parent << (self << t)(my_js))(js)
43 | } } getOrElse (self << t)(js)
44 | }
45 | /** Obj extractor, respects current parent context and sets a new context to itself. */
46 | class Obj(sym: Symbol)(implicit parent: Option[Obj])
47 | extends Child[JsObject, Property[JsObject]](parent, Property(sym, Js.obj)) {
48 | implicit val ctx = Some(this)
49 | }
50 | /** Namespace and context for Json extraction. Client extraction
51 | objects, e.g. dispatch.twitter.Search, may extend this trait to
52 | receive all implicit functions and values. */
53 | trait Js {
54 | object str extends Extract[String] {
55 | def unapply(js: JsValue) = js match {
56 | case JsString(v) => Some(v)
57 | case _ => None
58 | }
59 | }
60 | object num extends Extract[BigDecimal] {
61 | def unapply(js: JsValue) = js match {
62 | case JsNumber(v) => Some(v)
63 | case _ => None
64 | }
65 | }
66 | object bool extends Extract[Boolean] {
67 | def unapply(js: JsValue) = js match {
68 | case JsBoolean(v) => Some(v)
69 | case _ => None
70 | }
71 | }
72 | // keep in wrapped type to allow nested extractors
73 | object obj extends Extract[JsObject] {
74 | def unapply(js: JsValue) = js match {
75 | case JsObject(v) => Some(JsObject(v))
76 | case _ => None
77 | }
78 | }
79 | object list extends Extract[List[JsValue]] {
80 | def unapply(js: JsValue) = js match {
81 | case JsArray(v) => Some(v)
82 | case _ => None
83 | }
84 | def ! [T](ext: Extract[T]) = new Extract[List[T]] {
85 | def unapply(js: JsValue) = js match {
86 | case list(l) => Some(l map { _ match { case ext(v) => v } } )
87 | case _ => None
88 | }
89 | }
90 | }
91 |
92 | /** The parent Obj context, defaults to None for top level definitions. */
93 | implicit val ctx: Option[Obj] = None
94 |
95 | /** Assertion extracting function, error if expected Js type is not present. */
96 | type JsF[T] = JsValue => T
97 |
98 | /** Assertion inserting function, puts any type value into the given JsValue. */
99 | type JsIF = JsF[JsObject]
100 |
101 | /** Add operators to Symbol. */
102 | implicit def sym_add_operators[T](sym: Symbol) = new SymOp(sym)
103 | /** For ! , ? , and << operators on Symbol. */
104 | case class SymOp(sym: Symbol) {
105 | /** @return an extractor, respects current Obj context */
106 | def ? [T](cst: Extract[T])(implicit parent: Option[Obj]) =
107 | new Child[T, Property[T]](parent, Property(sym, cst))
108 |
109 | /** @return an assertion extracting function (JsF) */
110 | def ! [T](cst: Extract[T]): JsF[T] = Js.ext2fun(new Property(sym, cst))
111 |
112 | /** @return a optional extracting function */
113 | def ?? [T](cst: Extract[T]): JsValue => Option[T] = jsv => new Property(sym,cst).unapply(jsv)
114 |
115 | /** @return new JsObject with the given sym property replaced by t */
116 | def << [T](t: T): JsIF = _ match {
117 | case JsObject(m) => JsObject(m + (JsString(sym) -> JsValue(t)))
118 | case js => sys.error("Unable to replace property in " + js)
119 | }
120 | /** Chain this to another assertion inserting function to replace deep properties */
121 | def << (f: JsIF): JsIF = js => (this << f((this ! obj)(js)))(js)
122 | /** Insert value into empty JsValue */
123 | def <<| [T] (t: T) = <<(t)(Js())
124 | }
125 | /** Combines assertion extracting functions into a single function returning a tuple, when curried. */
126 | def %[A,B](a: JsF[A], b: JsF[B])(js: JsValue) = (a(js), b(js))
127 | /** Combines assertion extracting functions into a single function returning a tuple, when curried. */
128 | def %[A,B,C](a: JsF[A], b: JsF[B], c: JsF[C])(js: JsValue) = (a(js), b(js), c(js))
129 | /** Combines assertion extracting functions into a single function returning a tuple, when curried. */
130 | def %[A,B,C,D](a: JsF[A], b: JsF[B], c: JsF[C], d: JsF[D])(js: JsValue) = (a(js), b(js), c(js), d(js))
131 | /** Converts a Json extrator to an assertion extracting function (JsF). */
132 | implicit def ext2fun[T](ext: Extract[T]): JsF[T] = jsv => ext.unapply(jsv).getOrElse {
133 | sys.error("Extractor %s does not match JSON: %s" format (ext, jsv))
134 | }
135 | }
136 |
137 | /** Factory for JsValues as well as a global access point for
138 | implicit functions and values. This object extends the Js
139 | trait so that `import Js._` brings implicits into scope. */
140 | object Js extends Js {
141 | def apply(): JsValue = JsObject()
142 | def apply(stream: java.io.InputStream): JsValue = JsValue.fromStream(stream)
143 | def apply(stream: java.io.InputStream, charset: String) =
144 | JsValue.fromStream(stream, charset)
145 | def apply(string: String): JsValue = JsValue.fromString(string)
146 | }
147 |
--------------------------------------------------------------------------------
/json/src/test/resources/test.json:
--------------------------------------------------------------------------------
1 | {"completed_in":0.113,"max_id":107131941547016193,"max_id_str":"107131941547016193","next_page":"?page=2&max_id=107131941547016193&q=dispatch","page":1,"query":"dispatch","refresh_url":"?since_id=107131941547016193&q=dispatch","results":[{"created_at":"Fri, 26 Aug 2011 16:46:59 +0000","from_user":"brgiddens","from_user_id":212561564,"from_user_id_str":"212561564","geo":null,"id":107131941547016193,"id_str":"107131941547016193","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a2.twimg.com/profile_images/1199478603/20100808-mthood_bombbardier_normal.jpg","source":"<a href="http://twitter.com/tweetbutton" rel="nofollow">Tweet Button</a>","text":"Pointers And Clicks: HCSC Photo Workshop Dispatch | SnowboarderMag.com: http://t.co/xypNWYn via @AddThis","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:46:56 +0000","from_user":"0Solace0","from_user_id":76246707,"from_user_id_str":"76246707","geo":null,"id":107131928448208897,"id_str":"107131928448208897","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a1.twimg.com/profile_images/800077064/twitterProfilePhoto_normal.jpg","source":"<a href="http://twitter.com/">web</a>","text":"RT @WSJ: 150k to evacuate from Ocean City, Maryland. Continuing hurricane coverage: http://t.co/vJGgFkr","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:46:45 +0000","from_user":"MarieLongchamps","from_user_id":7348058,"from_user_id_str":"7348058","geo":null,"id":107131879995617281,"id_str":"107131879995617281","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a3.twimg.com/profile_images/1367339809/Sans_titre_normal.png","source":"<a href="http://www.hootsuite.com" rel="nofollow">HootSuite</a>","text":"RT @GlobalPost: The Search For #Canada's War Criminals. http://ow.ly/6dJrl","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:46:20 +0000","from_user":"regulaHoli","from_user_id":95202500,"from_user_id_str":"95202500","geo":null,"id":107131776249507840,"id_str":"107131776249507840","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a3.twimg.com/profile_images/1487978368/regulaHoli_normal.jpg","source":"<a href="http://twitter.com/">web</a>","text":"RT @WSJ: 150k to evacuate from Ocean City, Maryland. Continuing hurricane coverage: http://t.co/vJGgFkr","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:46:07 +0000","from_user":"elina_smith","from_user_id":81761364,"from_user_id_str":"81761364","geo":null,"id":107131722709209088,"id_str":"107131722709209088","iso_language_code":"da","metadata":{"result_type":"recent"},"profile_image_url":"http://a2.twimg.com/sticky/default_profile_images/default_profile_3_normal.png","source":"<a href="http://www.facebook.com/twitter" rel="nofollow">Facebook</a>","text":"TANF and Drug Testing http://t.co/I2Ts3hB","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:46:07 +0000","from_user":"aiodts","from_user_id":813085,"from_user_id_str":"813085","geo":null,"id":107131721304121344,"id_str":"107131721304121344","iso_language_code":"da","metadata":{"result_type":"recent"},"profile_image_url":"http://a1.twimg.com/profile_images/1493283103/access_normal.jpg","source":"<a href="http://www.merchantcircle.com/" rel="nofollow">MerchantCircle</a>","text":"TANF and Drug Testing http://t.co/OIRHukv","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:45:53 +0000","from_user":"colourmusic","from_user_id":12998154,"from_user_id_str":"12998154","geo":null,"id":107131662030221312,"id_str":"107131662030221312","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a2.twimg.com/profile_images/1449592439/image_normal.jpg","source":"<a href="http://twitter.com/">web</a>","text":"RT @colourrevolt: DENVER - youre the first stop on this tour, lets make it count. Sept 14 All Ages, no excuses. Get tickets, tell yr pals http://t.co/QLhptJa","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:45:39 +0000","from_user":"arsyfajriar","from_user_id":140570122,"from_user_id_str":"140570122","geo":null,"id":107131606073999360,"id_str":"107131606073999360","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a3.twimg.com/profile_images/1511613883/Photo_on_2011-08-25_at_00.34__4_normal.jpg","source":"<a href="http://blackberry.com/twitter" rel="nofollow">Twitter for BlackBerry\u00AE</a>","text":"Wow ! RT @WSJ: 150k to evacuate from Ocean City, Maryland. Continuing hurricane coverage: http://t.co/0cb1QkF","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:45:30 +0000","from_user":"meghanfrazer","from_user_id":221578256,"from_user_id_str":"221578256","geo":null,"id":107131567050199040,"id_str":"107131567050199040","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a2.twimg.com/profile_images/1246622571/2657_58724352636_615132636_1563869_2721974_n_2__normal.jpg","source":"<a href="http://twitter.com/tweetbutton" rel="nofollow">Tweet Button</a>","text":"Bob Hunter commentary: Crew Stadium issue must be addressed | The Columbus Dispatch http://t.co/7jigYCg #fb","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:45:30 +0000","from_user":"edwinbeall","from_user_id":151559257,"from_user_id_str":"151559257","geo":null,"id":107131564894326785,"id_str":"107131564894326785","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a1.twimg.com/profile_images/1094751511/Edwin_Beall_normal.jpg","source":"<a href="http://twitterfeed.com" rel="nofollow">twitterfeed</a>","text":"RT @WSJ: 150k to evacuate from Ocean City, Maryland. Continuing hurricane coverage: http://t.co/DeGD36M","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:45:29 +0000","from_user":"audiarau","from_user_id":241246580,"from_user_id_str":"241246580","geo":null,"id":107131561996066816,"id_str":"107131561996066816","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a2.twimg.com/profile_images/1361359483/image_normal.jpg","source":"<a href="http://twitter.com/">web</a>","text":"RT @WSJ: 150k to evacuate from Ocean City, Maryland. Continuing hurricane coverage: http://t.co/vJGgFkr","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:45:05 +0000","from_user":"GloryDaysGNV","from_user_id":7823654,"from_user_id_str":"7823654","geo":null,"id":107131460988833792,"id_str":"107131460988833792","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a3.twimg.com/profile_images/118112819/Glory_Days_Logo_-_Lo_Res_normal.jpg","source":"<a href="http://twitter.com/">web</a>","text":"REMINDER! Tix on sale NOW for Nov 9 ALL TIME LOW @ FL Theater of Gville! Ppl already scrambling for em! Get yers today! http://t.co/XuGCuZy","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:45:03 +0000","from_user":"dnjuniorg","from_user_id":81005422,"from_user_id_str":"81005422","geo":null,"id":107131451853651969,"id_str":"107131451853651969","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a2.twimg.com/profile_images/1507511422/image_normal.jpg","source":"<a href="http://twitter.com/">web</a>","text":"RT @WSJ: 150k to evacuate from Ocean City, Maryland. Continuing hurricane coverage: http://t.co/vJGgFkr","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:44:42 +0000","from_user":"thorneultimatum","from_user_id":152400457,"from_user_id_str":"152400457","geo":null,"id":107131364482101248,"id_str":"107131364482101248","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a2.twimg.com/profile_images/1325450112/twitter_profile_normal.JPG","source":"<a href="http://ubersocial.com" rel="nofollow">\u00DCberSocial for BlackBerry</a>","text":"My sale goodies are here! Massive kudos to @modelsown for speedy dispatch and beautiful packaging. http://t.co/VJCdwiD","to_user_id":null,"to_user_id_str":null},{"created_at":"Fri, 26 Aug 2011 16:44:41 +0000","from_user":"KitRoupe","from_user_id":137679406,"from_user_id_str":"137679406","geo":null,"id":107131361424457728,"id_str":"107131361424457728","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http://a0.twimg.com/profile_images/1082541445/roupe_37_web_normal.jpg","source":"<a href="http://www.hootsuite.com" rel="nofollow">HootSuite</a>","text":"RT @wsj: 150k to evacuate from Ocean City, Maryland. Continuing hurricane coverage: http://t.co/Y6AX2ia","to_user_id":null,"to_user_id_str":null}],"results_per_page":15,"since_id":0,"since_id_str":"0"}
--------------------------------------------------------------------------------
/json/src/test/scala/JsonSpec.scala:
--------------------------------------------------------------------------------
1 | import org.specs2.mutable.Specification
2 |
3 | object JsValueSpec extends Specification {
4 | import dispatch.classic.json._
5 | import java.io.ByteArrayInputStream
6 |
7 | val testFile = getClass.getClassLoader.getResourceAsStream("test.json")
8 | val json = scala.io.Source.fromInputStream(testFile, "utf-8").mkString
9 |
10 | def time[T](block: => T): (T, Long) = {
11 | val start = System.currentTimeMillis
12 | (block, System.currentTimeMillis - start)
13 | }
14 |
15 | def reparse(x: Any) = Js(JsValue(x).toString)
16 |
17 | "JS parsing" should {
18 | "parse null" in {
19 | Js("null") must be equalTo(JsNull)
20 | }
21 | "parse true" in {
22 | Js("true") must be equalTo(JsTrue)
23 | }
24 | "parse false" in {
25 | Js("false") must be equalTo(JsFalse)
26 | }
27 | "parse number" in {
28 | Js("2394.3") must be equalTo(JsNumber(BigDecimal(2394.3)))
29 | }
30 | "parse string" in {
31 | Js("\"foobie bletch\"") must be equalTo(JsString("foobie bletch"))
32 | }
33 | "parse escaped string" in {
34 | Js("\"hello\\u203D\"") must be equalTo(JsString("hello\u203d"))
35 | }
36 | "parse array" in {
37 | Js("[\"foo\", \"bar\"]") must be equalTo(JsArray(List(JsString("foo"), JsString("bar"))))
38 | }
39 | "parse object" in {
40 | Js("{\"foo\": true}") must be equalTo(JsObject(Map(JsString("foo") -> JsTrue)))
41 | }
42 | }
43 |
44 | "JS round-tripping" should {
45 | "round-trip null" in {
46 | reparse(null) must be equalTo(JsNull)
47 | }
48 | "round-trip string" in {
49 | reparse("HACKEM MUCHE") must be equalTo(JsString("HACKEM MUCHE"))
50 | }
51 | "round-trip array" in {
52 | reparse(List("foo", false)) must be equalTo(JsArray(List(JsString("foo"), JsFalse)))
53 | }
54 | "round-trip object" in {
55 | reparse(Map("scrolls" -> List("identify"), "name" -> "foo")) must be equalTo(
56 | JsObject(Map(JsString("scrolls") -> JsArray(List(JsString("identify"))),
57 | JsString("name") -> JsString("foo"))))
58 | }
59 | "round-trip Unicode string" in {
60 | reparse("control \u0008") must be equalTo(JsString("control \u0008"))
61 | }
62 | }
63 |
64 | "JsValue.fromString and JsValue.fromStream" should {
65 | val maxTime = 1000L
66 | "not become slow under alternate calling" in {
67 | val (_, t1) = time { JsValue.fromString(json) }
68 | t1 must be_<=(maxTime) // normally finish within 1s
69 | val (_, t2) = time { JsValue.fromStream(new ByteArrayInputStream(json.getBytes("utf-8"))) }
70 | t2 must be_<=(maxTime)
71 | }
72 |
73 | "work properly under multi-thread calling" in {
74 | import java.util.concurrent._
75 | val numOfThreads = 20
76 | val executor = Executors.newFixedThreadPool(numOfThreads)
77 | (1 to numOfThreads).map{i =>
78 | val f = new FutureTask(new Callable[(JsValue, Long)]{
79 | def call =
80 | if (i % 2 == 0) time { JsValue.fromString(json) }
81 | else time { JsValue.fromStream(new ByteArrayInputStream(json.getBytes("utf-8"))) }
82 | })
83 | executor.execute(f)
84 | f
85 | }.map{_.get._2}.toList must not contain{(_: Long) > maxTime}
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/jsoup/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-jsoup",
5 | "version":"0.8.7",
6 | "description":"Adds JSoup handler verbs to Dispatch",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"org.jsoup",
18 | "name": "jsoup",
19 | "version": "1.6.1"
20 | }],
21 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
22 | "sbt": false
23 | }
--------------------------------------------------------------------------------
/jsoup/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-jsoup",
5 | "version":"0.8.8",
6 | "description":"Adds JSoup handler verbs to Dispatch",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | },{
20 | "organization":"org.jsoup",
21 | "name": "jsoup",
22 | "version": "1.6.1"
23 | }],
24 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
25 | "sbt": false
26 | }
--------------------------------------------------------------------------------
/jsoup/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-jsoup",
4 | "version" : "0.8.9",
5 | "description" : "Adds JSoup handler verbs to Dispatch",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | }, {
15 | "organization" : "org.jsoup",
16 | "name" : "jsoup",
17 | "version" : "1.6.1"
18 | } ],
19 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
20 | "licenses" : [ {
21 | "name" : "LGPL v3",
22 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
23 | } ],
24 | "sbt" : false
25 | }
--------------------------------------------------------------------------------
/jsoup/src/main/scala/JSoupHttp.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.jsoup
2 |
3 | import dispatch.classic._
4 | import org.jsoup.nodes.Document
5 | import org.jsoup.Jsoup
6 | import io.Source
7 | import xml.NodeSeq
8 |
9 | trait ImplicitJSoupHandlers {
10 | implicit def requestToJSoupHandlers(req: Request) = new JSoupHandlers(req);
11 | implicit def stringToJSoupHandlers(str: String) = new JSoupHandlers(new Request(str));
12 | }
13 |
14 | object JSoupHttp extends ImplicitJSoupHandlers
15 |
16 | class JSoupHandlers(request: Request) {
17 | /** Process response with JSoup html processor in block */
18 | def jsouped [T] (block: (Document) => T) = request >> { (stm, charset) =>
19 | block(Jsoup.parse(stm, charset, request.to_uri.toString))
20 | }
21 | /** Alias for verb jsouped */
22 | def > [T] (block: (Document) => T) = jsouped(block)
23 | /** Conveniences handler for retrieving a org.jsoup.nodes.Document */
24 | def as_jsouped: Handler[Document] = jsouped { dom => dom }
25 | /** Conveniences handler for retrieving a NodeSeq */
26 | def as_jsoupedNodeSeq: Handler[NodeSeq] = jsouped { dom: Document => {
27 | xml.parsing.XhtmlParser(Source.fromString(dom.html))
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/jsoup/src/test/resources/Human.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Human
6 |
7 |
8 | To err is human, to forgive divine
9 |
10 | The verb "err" means to do something wrong; to make a mistake is "to err".
11 | "To err is human" because all people ("humans") make mistakes.
12 |
"To err is human, to forgive divine" says we should try hard to forgive others because all
13 | people are human and make mistakes.
14 |
Example: "I am still angry about what my manager did yesterday!" Answer: "It is best to just
15 | let it go; to err is human, to forgive divine." The world of people is "human"
16 | and the world of God is "divine".
17 |
God's special power to forgive people for their mistakes is called "divine" mercy.
18 | When we forgive other people we are acting "divine". "To err is human, to forgive
19 | divine" says that we are all human and we all make mistakes so we should all try hard to forgive other
20 | people when they make mistakes.
21 |
Example: "I will never forgive my mother for what she has done!"
22 |
Answer:
23 |
"Don't be angry at her. To err is human, to forgive divine." (Because we use the
24 | word "is" in the first phrase "to err is human" we do not use "is" again in the second phrase:
25 | "to forgive divine".) We are all people who make mistakes so to forgive others when they make
26 | mistakes is the right thing to do; "to err is human, to forgive divine."
27 |
28 |
29 |
--------------------------------------------------------------------------------
/jsoup/src/test/resources/test.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | ApA
6 |
7 |
8 | Ape
9 |
Apes are Old World anthropoid mammals, more specifically a clade of tailless catarrhine primates,
10 | belonging to the biological superfamily Hominoidea. The apes are native to Africa and South-east Asia,
11 | although in relatively recent times humans have spread all over the world.
12 | Apes are the world's largest primates; the orangutan, an ape, is the world's largest living arboreal animal.
13 | Hominoids are traditionally forest dwellers, although chimpanzees may range into savanna, and the extinct
14 | australopithecines are famous for being savanna inhabitants, inferred from their morphology. Humans inhabit
15 | almost every terrestrial habitat.
16 | Hominoidea contains two families of living (extant) species:
17 |
18 | - Hylobatidae consists of four genera and sixteen species of gibbon, including the lar gibbon and the siamang.
19 | They are commonly referred to as lesser apes.
20 | - Hominidae consists of orangutans, gorillas, common chimpanzees, bonobos and humans. Alternatively, the
21 | hominidae family are collectively described as the great apes.
22 |
23 |
24 |
--------------------------------------------------------------------------------
/jsoup/src/test/scala/JSoupSpec.scala:
--------------------------------------------------------------------------------
1 | import org.eclipse.jetty.server.handler.{DefaultHandler, HandlerList, ResourceHandler}
2 | import org.eclipse.jetty.server.nio.SelectChannelConnector
3 | import org.eclipse.jetty.server.Server
4 | import org.specs2.mutable.Specification
5 | import dispatch.classic._
6 |
7 | object JSoupSpec extends Specification with ServedByJetty {
8 | val port = 9990
9 | val resourceBase = "./jsoup/src/test/resources"
10 |
11 | object JSoupHtml extends Request(:/("localhost", port))
12 |
13 | "JSoup html parser" should {
14 | import dispatch.classic.jsoup.JSoupHttp._
15 | "successfully parse good html" in {
16 | withResourceServer { _ =>
17 | val request = JSoupHtml / "test.html"
18 | Http(request as_jsouped) must not (throwA[Exception])
19 | }
20 | }
21 | "successfully parse bad html" in {
22 | withResourceServer { _ =>
23 | val request = JSoupHtml / "Human.html"
24 | Http(request as_jsouped) must not (throwA[Exception])
25 | }
26 | }
27 | }
28 |
29 | "Using JSoup Document" should {
30 | import dispatch.classic.jsoup.JSoupHttp._
31 | "find Elements by tag" in {
32 | withResourceServer { _ =>
33 | val request = JSoupHtml / "test.html"
34 | val elements = Http(request jsouped { doc =>
35 | doc.getElementsByTag("title");
36 | })
37 |
38 | elements.size must be_==(1)
39 | elements.get(0).text must be_==("ApA")
40 | }
41 | }
42 |
43 | "make Elements processable as scala collections" in {
44 | withResourceServer { _ =>
45 | val request = JSoupHtml / "Human.html"
46 | val elements = Http(request jsouped { doc =>
47 | doc.getElementsByTag("h1");
48 | })
49 |
50 | import scala.collection.JavaConversions._
51 | val hOnes = elements.iterator.toList.map(e => e.text.reverse)
52 | hOnes.head must be_==("enivid evigrof ot ,namuh si rre oT")
53 | }
54 | }
55 |
56 | "make resolved links absolute" in {
57 | withResourceServer { _ =>
58 | import scala.collection.JavaConversions._
59 | val request = JSoupHtml / "test.html"
60 | val links = Http(request jsouped { doc =>
61 | doc.select("a[href]").toList;
62 | })
63 |
64 | links.head.attr("href") must be_==("/Human.html")
65 | links.head.attr("abs:href") must be_==("http://localhost:9990/Human.html")
66 | }
67 | }
68 | }
69 |
70 | """Using the verb >""" should {
71 | import dispatch.classic.jsoup.JSoupHttp._
72 | "do the same thing as the verb jsouped" in {
73 | withResourceServer { _ =>
74 | val request = JSoupHtml / "test.html"
75 |
76 | val title1 = Http(request > {doc =>
77 | doc.title
78 | })
79 | val title2 = Http(request jsouped {doc =>
80 | doc.title
81 | })
82 |
83 | title1 must be_==(title2)
84 | }
85 | }
86 | }
87 |
88 | "Using the verb as_jsoupedNodeSeq" should {
89 | import dispatch.classic.jsoup.JSoupHttp._
90 | "use JSoup to retrieve a NodeSeq" in {
91 | withResourceServer { _ =>
92 | val request = JSoupHtml / "Human.html"
93 |
94 | val ns = Http(request as_jsoupedNodeSeq)
95 |
96 | (ns \\ "title").text must be_==("Human")
97 | }
98 | }
99 | }
100 | }
101 |
102 | trait ServedByJetty {
103 | val port: Int
104 | val resourceBase: String
105 |
106 | def withResourceServer[A](op: Unit => A): A = {
107 | // Configure Jetty server
108 | val connector = new SelectChannelConnector
109 | connector.setHost("localhost")
110 | connector.setPort(port)
111 |
112 | val handler = new ResourceHandler
113 | handler.setDirectoriesListed(true)
114 | handler.setResourceBase(resourceBase)
115 | val handlers = new HandlerList
116 | handlers.setHandlers(Array(handler, new DefaultHandler))
117 |
118 | val server = new Server
119 | server.addConnector(connector)
120 | server.setHandler(handlers)
121 |
122 | // Run server for test and then stop
123 | try {
124 | server.start
125 | op(())
126 | } finally {
127 | server.stop
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/mime/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-mime",
5 | "version":"0.8.6",
6 | "description":"Support for multipart MIME POSTs",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"org.apache.httpcomponents",
18 | "name": "httpmime",
19 | "version": "4.1.2"
20 | },{
21 | "organization":"commons-logging",
22 | "name": "commons-logging",
23 | "version": "1.1.1"
24 | },{
25 | "organization":"org.apache.james",
26 | "name": "apache-mime4j",
27 | "version": "0.7.1"
28 | }],
29 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
30 | "sbt": false
31 | }
--------------------------------------------------------------------------------
/mime/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-mime",
5 | "version":"0.8.7",
6 | "description":"Support for multipart MIME POSTs",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"org.apache.httpcomponents",
18 | "name": "httpmime",
19 | "version": "4.1.2"
20 | },{
21 | "organization":"commons-logging",
22 | "name": "commons-logging",
23 | "version": "1.1.1"
24 | },{
25 | "organization":"org.apache.james",
26 | "name": "apache-mime4j",
27 | "version": "0.7.1"
28 | }],
29 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
30 | "sbt": false
31 | }
--------------------------------------------------------------------------------
/mime/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-mime",
5 | "version":"0.8.8",
6 | "description":"Support for multipart MIME POSTs",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | },{
20 | "organization":"org.apache.httpcomponents",
21 | "name": "httpmime",
22 | "version": "4.1.2"
23 | },{
24 | "organization":"commons-logging",
25 | "name": "commons-logging",
26 | "version": "1.1.1"
27 | },{
28 | "organization":"org.apache.james",
29 | "name": "apache-mime4j-core",
30 | "version": "0.7.2"
31 | }],
32 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
33 | "sbt": false
34 | }
--------------------------------------------------------------------------------
/mime/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-mime",
4 | "version" : "0.8.9",
5 | "description" : "Support for multipart MIME POSTs",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | }, {
15 | "organization" : "org.apache.httpcomponents",
16 | "name" : "httpmime",
17 | "version" : "4.1.2"
18 | }, {
19 | "organization" : "commons-logging",
20 | "name" : "commons-logging",
21 | "version" : "1.1.1"
22 | }, {
23 | "organization" : "org.apache.james",
24 | "name" : "apache-mime4j-core",
25 | "version" : "0.7.2"
26 | } ],
27 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
28 | "licenses" : [ {
29 | "name" : "LGPL v3",
30 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
31 | } ],
32 | "sbt" : false
33 | }
--------------------------------------------------------------------------------
/mime/src/main/scala/Mime.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.mime
2 | import dispatch.classic._
3 | import java.io.{FilterOutputStream, OutputStream}
4 | import org.apache.http.HttpEntity
5 | import org.apache.http.entity.HttpEntityWrapper
6 | import org.apache.http.entity.mime.{FormBodyPart, MultipartEntity}
7 |
8 | import java.io.{File, InputStream}
9 | import java.nio.charset.Charset
10 | import org.apache.http.entity.mime.content._
11 |
12 | /** Mime module for multipart form posting. Note that when using an InputStream generator,
13 | chuncked encoding will be used with no Content-Length header and the stream will be closed
14 | after posting. It is therefore highly recommended that your generator always return a new
15 | stream instance, or a Request descriptor referencing it will fail after its first use. */
16 | object Mime {
17 | /** Adds multipart operators to Request */
18 | implicit def Request2ExtendedRequest(r: Request) = new MimeRequestTerms(r)
19 |
20 | type Headers = Map[String, List[String]]
21 | type MultipartBlock[T] = (Headers, InputStream) => T
22 |
23 | /** Request derivative with multipart operators */
24 | class MimeRequestTerms(r: Request) {
25 | /** Process parts of a multipart response in a block. The block is called once for each part
26 | with a Map[String,List[String]] of its headers and an InputStream of the body. */
27 | def >--> [T] (multipart_block: MultipartBlock[T]) = r >+> { r2 =>
28 | r2 >:> { headers =>
29 | r2 >> headers("Content-Type").find { h => true }.map(mime_stream_parser(multipart_block)).get
30 | }
31 | }
32 |
33 | /** Add file to multipart post, will convert other post methods to multipart */
34 | def <<* (name: String, file: File) =
35 | add(name, new FileBody(file))
36 | /** Add file with content-type to multipart post, will convert other post methods to multipart */
37 | def <<* (name: String, file: File, content_type: String) =
38 | add(name, new FileBody(file, content_type))
39 |
40 | /** Add stream generator to multipart post, will convert other post methods to multipart. */
41 | def <<* (name: String, file_name: String, stream: () => InputStream, content_type: String) =
42 | add(name, new InputStreamBody(stream(), content_type, file_name))
43 |
44 | /** Add stream generator with content-type to multipart post, will convert other post methods to multipart */
45 | def <<* (name: String, file_name: String, stream: () => InputStream) =
46 | add(name, new InputStreamBody(stream(), file_name))
47 |
48 | /** Add byte array to multipart post, will convert other post methods to multipart */
49 | def <<* (name: String, file_name: String, bytes: Array[Byte]) =
50 | add(name, new ByteArrayBody(bytes, file_name))
51 |
52 | /** Add byte array with content-type to multipart post, will convert other post methods to multipart */
53 | def <<* (name: String, file_name: String, bytes: Array[Byte], content_type: String) =
54 | add(name, new ByteArrayBody(bytes, content_type, file_name))
55 |
56 | private def mime_ent: Mime.Entity = {
57 | def newent = new MultipartEntity with Mime.Entity {
58 | val charset = Charset.forName(r.defaultCharset)
59 | }
60 | r.body.map {
61 | case ent: Mime.Entity => ent
62 | case orig: FormEntity => newent.add(orig.oauth_params)
63 | case ent => sys.error("trying to add multipart content to entity: " + ent)
64 | } getOrElse newent
65 | }
66 | def add(name: String, content: => ContentBody) = {
67 | val ent = mime_ent
68 | ent.addPart(name, content)
69 | r.POST.copy(body=Some(ent))
70 | }
71 | /** Add a listener function to be called as bytes are uploaded */
72 | def >?> (listener_f: ListenerF) = r.copy(
73 | body=Some(new CountingMultipartEntity(mime_ent, listener_f))
74 | )
75 | }
76 | /** Post listener function. Called once with the total bytes; the function returned is
77 | called with the bytes uploaded at each kilobyte boundary, and when complete. */
78 | type ListenerF = Long => Long => Unit
79 | trait Entity extends HttpEntity with FormEntity {
80 | def addPart(name: String, body: ContentBody)
81 | def add(values: Traversable[(String, String)]) = {
82 | for ((name,value) <- values)
83 | addPart(name, new StringBody(value, charset))
84 | this
85 | }
86 | def oauth_params = Nil
87 | def charset: Charset
88 | }
89 |
90 | def mime_stream_parser[T](multipart_block: MultipartBlock[T])(content_type: String)(stm: InputStream) = {
91 | import org.apache.james.mime4j.stream.{MimeTokenStream, EntityState}
92 | val m = new MimeTokenStream()
93 | m.parseHeadless(stm, content_type)
94 | val empty_headers = Map.empty[String, List[String]]
95 | def walk(state: EntityState,
96 | headers: Headers,
97 | outs: List[T]): List[T] = {
98 | import EntityState._
99 | state match {
100 | case T_END_OF_STREAM => outs
101 | case T_FIELD =>
102 | val added = headers + ((m.getField.getName,
103 | m.getField.getBody :: headers.getOrElse(m.getField.getName, Nil)))
104 | walk(m.next(), added, outs)
105 | case T_BODY =>
106 | val output = multipart_block(headers, m.getInputStream)
107 | walk(m.next(), empty_headers, output :: outs)
108 | case state =>
109 | walk(m.next(), headers, outs)
110 | }
111 | }
112 | walk(m.getState, empty_headers, Nil).reverse
113 | }
114 | }
115 |
116 | /** Byte-counting entity writer used when a listener function is passed in to the MimeRequest. */
117 | class CountingMultipartEntity(delegate: Mime.Entity,
118 | listener_f: Mime.ListenerF) extends HttpEntityWrapper(delegate) with Mime.Entity {
119 | def addPart(name: String, body: ContentBody) { delegate.addPart(name, body) }
120 | def charset = delegate.charset
121 | override def writeTo(out: OutputStream) {
122 | import scala.actors.Actor._
123 | super.writeTo(new FilterOutputStream(out) {
124 | var transferred = 0L
125 | val total = delegate.getContentLength
126 | val sent = listener_f(total)
127 | val listener = actor { loop { react {
128 | case l: Long => {
129 | sent(l)
130 | if (l == total) exit()
131 | }
132 | } } }
133 | override def write(b: Int) {
134 | super.write(b)
135 | transferred += 1
136 | if (transferred % 1024 == 0 || transferred == total)
137 | listener ! transferred
138 | }
139 | })
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/nio/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-nio",
5 | "version":"0.8.6",
6 | "description":"NIO HTTP executor, uses Apache DefaultHttpAsyncClient",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"org.apache.httpcomponents",
18 | "name": "httpasyncclient",
19 | "version": "4.0-alpha1"
20 | }],
21 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
22 | "sbt": false
23 | }
--------------------------------------------------------------------------------
/nio/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-nio",
5 | "version":"0.8.7",
6 | "description":"NIO HTTP executor, uses Apache DefaultHttpAsyncClient",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"org.apache.httpcomponents",
18 | "name": "httpasyncclient",
19 | "version": "4.0-alpha1"
20 | }],
21 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
22 | "sbt": false
23 | }
--------------------------------------------------------------------------------
/nio/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-nio",
5 | "version":"0.8.8",
6 | "description":"NIO HTTP executor, uses Apache DefaultHttpAsyncClient",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | },{
20 | "organization":"org.apache.httpcomponents",
21 | "name": "httpasyncclient",
22 | "version": "4.0-alpha1"
23 | }],
24 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
25 | "sbt": false
26 | }
--------------------------------------------------------------------------------
/nio/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-nio",
4 | "version" : "0.8.9",
5 | "description" : "NIO HTTP executor, uses Apache DefaultHttpAsyncClient",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | }, {
15 | "organization" : "org.apache.httpcomponents",
16 | "name" : "httpasyncclient",
17 | "version" : "4.0-alpha1"
18 | } ],
19 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
20 | "licenses" : [ {
21 | "name" : "LGPL v3",
22 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
23 | } ],
24 | "sbt" : false
25 | }
--------------------------------------------------------------------------------
/nio/src/main/scala/nio.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.nio
2 |
3 | import dispatch.classic.{Callback,Request,ExceptionListener}
4 | import org.apache.http.{HttpHost,HttpRequest,HttpResponse,HttpEntity,HttpException}
5 | import org.apache.http.message.BasicHttpEntityEnclosingRequest
6 | import org.apache.http.protocol._
7 | import org.apache.http.impl.nio.client.{DefaultHttpAsyncClient,BasicHttpAsyncRequestProducer=>Producer}
8 | import org.apache.http.client.methods._
9 | import org.apache.http.params.CoreConnectionPNames
10 | import org.apache.http.params.CoreProtocolPNames
11 | import org.apache.http.nio.{ContentDecoder,IOControl,NHttpConnection}
12 | import org.apache.http.nio.entity.{ConsumingNHttpEntity,BufferingNHttpEntity}
13 | import org.apache.http.nio.client.HttpAsyncResponseConsumer
14 | import org.apache.http.nio.concurrent.FutureCallback
15 | import org.apache.http.nio.util.HeapByteBufferAllocator
16 | import java.util.concurrent.Future
17 | import java.io.IOException
18 |
19 | object Http {
20 | val socket_buffer_size = 8 * 1024
21 | }
22 |
23 | class Http extends dispatch.classic.HttpExecutor {
24 | lazy val client = {
25 | val cl = make_client
26 | cl.start()
27 | cl
28 | }
29 | def make_client = new DefaultHttpAsyncClient
30 |
31 | type HttpPackage[T] = dispatch.classic.futures.StoppableFuture[T]
32 |
33 | abstract class StoppableConsumer[T](
34 | listener: ExceptionListener
35 | ) extends HttpAsyncResponseConsumer[T] {
36 | @volatile var stopping = false
37 | @volatile var exception: Option[Exception] = None
38 | private def setException(e: Exception) {
39 | exception = Some(e)
40 | listener.lift(e)
41 | }
42 | final override def consumeContent(decoder: ContentDecoder, ioctrl: IOControl) {
43 | try {
44 | if (stopping || exception.isDefined) {
45 | ioctrl.shutdown()
46 | cancel()
47 | }
48 | else consume(decoder, ioctrl)
49 | } catch {
50 | case e: Exception =>
51 | stopping = true
52 | setException(e)
53 | }
54 | }
55 | @volatile var response: Option[HttpResponse] = None
56 | def responseReceived(res: HttpResponse) {
57 | response = Some(res)
58 | }
59 | def consume(decoder: ContentDecoder, ioctrl: IOControl)
60 | def stop() { stopping = true }
61 | @volatile var result: Option[T] = None
62 | def responseCompleted() {
63 | try {
64 | result = Some(completeResult(response.getOrElse {
65 | sys.error("responseCompleted called but response unset")
66 | }))
67 | } catch {
68 | case e: Exception => setException(e)
69 | }
70 | }
71 | def completeResult(response: HttpResponse): T
72 | // asynchttpclient would a lot rather we return null here than throw an exception
73 | def getResult: T = result.getOrElse(null.asInstanceOf[T])
74 | def failed(ex: Exception) {
75 | setException(ex)
76 | }
77 | }
78 | class EmptyCallback[T] extends FutureCallback[T] {
79 | def cancelled() { }
80 | def completed(res: T) { }
81 | def failed(ex: Exception) { }
82 | }
83 | class ConsumerFuture[T](
84 | underlying: Future[T],
85 | consumer: StoppableConsumer[T]
86 | ) extends dispatch.classic.futures.StoppableFuture[T] {
87 | def apply() = {
88 | val res = underlying.get()
89 | consumer.exception.foreach { throw _ }
90 | res
91 | }
92 | def isSet = consumer.exception.isDefined || underlying.isDone
93 | def stop() {
94 | consumer.stop()
95 | underlying.cancel(true)
96 | }
97 | }
98 | /* substitute future used for blocking consumers */
99 | trait SubstituteFuture[T] extends dispatch.classic.futures.StoppableFuture[T] {
100 | def isSet = true
101 | def stop() { }
102 | }
103 | class ExceptionFuture[T](e: Throwable) extends SubstituteFuture[T] {
104 | def apply() = throw e
105 | }
106 |
107 | def execute[T](host: HttpHost,
108 | credsopt: Option[dispatch.classic.Credentials],
109 | req: HttpRequestBase,
110 | block: HttpResponse => T,
111 | listener: ExceptionListener) = {
112 | credsopt.map { creds =>
113 | sys.error("Not yet implemented, but you can force basic auth with as_!")
114 | } getOrElse {
115 | try {
116 | val consumer = new StoppableConsumer[T](listener) {
117 | @volatile var entity: Option[ConsumingNHttpEntity] = None
118 | def consume(decoder: ContentDecoder, ioctrl: IOControl) { synchronized {
119 | entity = entity.orElse {
120 | for {
121 | res <- response
122 | ent <- Option(res.getEntity)
123 | } yield (new BufferingNHttpEntity(ent, new HeapByteBufferAllocator))
124 | }
125 | entity.map { _.consumeContent(decoder, ioctrl) }
126 | } }
127 | def completeResult(res: HttpResponse) = {
128 | for (ent <- entity) {
129 | res.setEntity(ent)
130 | ent.finish()
131 | }
132 | block(res)
133 | }
134 | def cancel() {
135 | entity.map { _.finish() }
136 | }
137 | }
138 | new ConsumerFuture(
139 | client.execute(new Producer(host, req), consumer, new EmptyCallback[T]),
140 | consumer
141 | )
142 | } catch {
143 | case e =>
144 | listener.lift(e)
145 | new ExceptionFuture(e)
146 | }
147 | }
148 | }
149 |
150 | def executeWithCallback[T](host: HttpHost, credsopt: Option[dispatch.classic.Credentials],
151 | req: HttpRequestBase, callback: Callback[T]) = {
152 | credsopt.map { creds =>
153 | sys.error("Not yet implemented, but you can force basic auth with as_!")
154 | } getOrElse {
155 | val ioc = DecodingCallback(callback)
156 | val consumer = new StoppableConsumer[T](callback.listener) {
157 | override def responseReceived(res: HttpResponse) {
158 | response = Some(res)
159 | }
160 | def consume(decoder: ContentDecoder, ioctrl: IOControl) {
161 | ioc.with_decoder(response.get, decoder)
162 | }
163 | def completeResult(response: HttpResponse) =
164 | callback.finish(response)
165 | def cancel() { }
166 | }
167 | new ConsumerFuture(
168 | client.execute(new Producer(host, req), consumer, new EmptyCallback[T]),
169 | consumer
170 | )
171 | }
172 | }
173 | /** Does nothing, NIO executor always consumes entities it creates */
174 | def consumeContent(entity: Option[HttpEntity]) { }
175 | def shutdownClient() {
176 | client.shutdown()
177 | }
178 | }
179 |
180 | case class DecodingCallback[T](callback: dispatch.classic.Callback[T]) {
181 | def with_decoder(response: HttpResponse, decoder: ContentDecoder) {
182 | val buffer = java.nio.ByteBuffer.allocate(Http.socket_buffer_size)
183 | val length = decoder.read(buffer)
184 | if (length > 0)
185 | callback.function(response, buffer.array(), length)
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/notes/0.7.0.markdown:
--------------------------------------------------------------------------------
1 | The big news is built-in support for asynchronous HTTP interaction. Although asynchronous execution can be implemented fairly directly in Scala, the new interface guarantees that a thread-safe instance of HttpClient is in use and simplifies exception handling.
2 |
3 | The foundation of this support is in a new module `dispatch-futures`, which has no dependencies and may be used from other libraries. It defines a structural type `dispatch.futures.Futures.Future` that corresponds with `scala.actors.Future`, though its current default implementation is a `java.util.concurrent.Future`. The `dispatch-http` module now depends on `dispatch-futures` and includes a `dispatch.Threads` mix-in for the base `dispatch.Http` class that enables asynchronous interaction. It can be used as follows:
4 |
5 | import dispatch._
6 | val http = new Http with Threads
7 | val fut_str = http.future(:/("example.com") as_str)
8 | // returns immediately. If we later need that string...
9 | fut_str() // blocks until it is available
10 |
11 | If you *won't* ever access the results from the main thread, you may want to process any exception it throws:
12 |
13 | http on_error {
14 | case e => println(e.getMessage)
15 | } future (:/("example.com") >- { str =>
16 | // do something with this string as soon as it's available
17 | })
18 |
19 | If you want to describe future-interaction with a fully defined request `Handler` like the one returned by `dispatch.meetup.Auth.access_token()`, you can extend it with the new `~>` operator defined on `Handler`:
20 |
21 | http.future(Auth.access_token(consumer, request_token, verifier) ~> { access_token =>
22 | // I'm in the vault!
23 | })// <- returns immediately
24 |
25 | And [by request][forum], the `<<` operator on Request is now overloaded to support plain string POSTs.
26 |
27 | [forum]: http://n2.nabble.com/How-to-POST-XML-td4457822.html
--------------------------------------------------------------------------------
/notes/0.7.1.markdown:
--------------------------------------------------------------------------------
1 | * No code changes from last release; republishing with [sbt 0.7.1][sbt] for a corrected pom.xml structure.
2 |
3 | [sbt]: http://implicit.ly/simple-build-tool-071
--------------------------------------------------------------------------------
/notes/0.7.2.markdown:
--------------------------------------------------------------------------------
1 | * Use `defaultCharset` charset established by `>\` for POST `<<` and PUT `<<<`, [requested on the forum][charset]
2 | * Handler chaining with the `>+>` operator, to use more than one handler against the same request with overlapping scopes
3 | * Multipart response handling with the [`>-->` operator][mime] in `dispatch-mime`, to support [Riak link-walking and map-reduce][riak]
4 |
5 | [charset]: http://n2.nabble.com/Charset-issue-td4664456.html#a4664456
6 | [riak]: http://blog.basho.com/2010/02/24/link-walking-by-example/
7 | [mime]: http://databinder.net/dispatch-doc/dispatch/mime/Mime$object.MimeRequest.html
--------------------------------------------------------------------------------
/notes/0.7.3.markdown:
--------------------------------------------------------------------------------
1 | * Proxy support contributed by [Max Afonov][maxaf], uses [standard system properties][jp].
2 | * lift-json dependency updated to [2.0-M4][m4]
3 | * Cross-published for [Scala 2.8.0.RC1][rc1]
4 |
5 | [maxaf]: http://github.com/maxaf
6 | [jp]: http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html
7 | [m4]: http://implicit.ly/ann-lift-20-m4-release
8 | [rc1]: http://www.scala-lang.org/node/5982
--------------------------------------------------------------------------------
/notes/0.7.4.markdown:
--------------------------------------------------------------------------------
1 | * Deprecate `Http#also` since `Http#x` with `Handler#apply` can do the same thing, but better.
2 | * Deprecate <<< (a: Any) in favor of <<< (s: String)
3 | * Improve error message for [missing entity error][noentity]
4 | * [Encode asterisks][aster] according to the OAuth spec
5 | * Depend on lift-json M5
6 | * Cross-publish for Scala 2.8.0.RC3
7 |
8 | [noentity]: http://databinder.3617998.n2.nabble.com/No-Entity-Error-td5036081.html
9 | [aster]: http://github.com/n8han/Databinder-Dispatch/commit/24b4d7542cc59d582a2a3f06de5b08147ba9e7ce
--------------------------------------------------------------------------------
/notes/0.7.5.markdown:
--------------------------------------------------------------------------------
1 | * [Meetup Everywhere API][everywhere] wrapper in [Everywhere.scala][source]
2 | * Cross-built for Scala 2.7.5, 2.7.6, 2.7.7, *and* 2.8.0
3 |
4 | [everywhere]: http://www.meetup.com/meetup_api/everywhere
5 | [source]: http://sourced.implicit.ly/net.databinder/dispatch-meetup/0.7.5/dispatch/Everywhere.scala.html
--------------------------------------------------------------------------------
/notes/0.7.6.markdown:
--------------------------------------------------------------------------------
1 | * Enabled redirect handling for all request methods
2 | * Dropped compilation for Scala 2.7.5, 2.7.6
3 | * dispatch-lift-json depends lift-json 2.1-M1
4 | * Support for Google [ClientLogin][ClientLogin] authorization by [Chris Lewis][Chris Lewis]
5 | * Optional Json extractor `??` contributed by [musk][musk]
6 | * Added the optional "oauth_version" parameter to the OAuth Authorization header, because stream.twitter.com [requires it][stream].
7 |
8 | [ClientLogin]: http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html
9 | [Chris Lewis]: http://github.com/chrislewis
10 | [musk]: http://github.com/musk
11 | [stream]: http://databinder.3617998.n2.nabble.com/OAuth-fails-for-stream-twitter-com-td5448011.html#a5448011
12 |
--------------------------------------------------------------------------------
/notes/0.7.7.markdown:
--------------------------------------------------------------------------------
1 | This release introduces an **Amazon S3** interface.
2 |
3 | The `aws-s3` module provides basic support for interacting with
4 | Amazon's S3 service. It supplies Dispatch handlers that sign requests
5 | in accordance with the [S3 authentication specifications][1] and a
6 | convenience class for interacting with S3 Buckets. See the
7 | [module README][readme] for more information.
8 |
9 | [1]: http://docs.amazonwebservices.com/AmazonS3/index.html?RESTAuthentication.html
10 | [readme]: http://github.com/n8han/Databinder-Dispatch/tree/master/aws-s3/#readme
11 |
--------------------------------------------------------------------------------
/notes/0.7.8.markdown:
--------------------------------------------------------------------------------
1 | Http
2 | ----
3 |
4 | Uses `Source.fromInputStream` for [>~][stm] instead of loading stream into
5 | a String first (it's a long story).
6 |
7 | [stm]: http://sourced.implicit.ly/net.databinder/dispatch-http/0.7.8/dispatch/Http.scala.html#20006
8 |
9 | OAuth
10 | -----
11 |
12 | Transfers oauth_callback in header (with all other OAuth parameters)
13 | rather than post method body. The consumer-only signing application of
14 | the [<@][sign] operator is now deprecated. ([softprops][softprops])
15 |
16 | [sign]: http://sourced.implicit.ly/net.databinder/dispatch-oauth/0.7.8/dispatch/OAuth.scala.html#21274
17 | [softprops]: https://github.com/softprops/
18 |
19 | Service Interfaces
20 | ------------------
21 |
22 | * Meetup: Added support for the Open Events method.
23 | * Twitter: Added a [UserStream][us] interface (sans Json schema for now),
24 | and using api.twitter.com endpoint.
25 |
26 | [me]: http://sourced.implicit.ly/net.databinder/dispatch-meetup/0.7.8/dispatch/Meetup.scala.html#9449
27 | [us]: http://sourced.implicit.ly/net.databinder/dispatch-twitter/0.7.8/dispatch/Streaming.scala.html#9344
28 |
29 | Twine
30 | -----
31 |
32 | Updated to work from its [own repository][twine], and also to use UserStream.
33 |
34 | [twine]: https://github.com/n8han/dispatch-twine
35 |
36 | Other
37 | -----
38 |
39 | Source JARs now published to scala-tools. (Nadav Wiener)
40 |
--------------------------------------------------------------------------------
/notes/0.8.0.Beta1.markdown:
--------------------------------------------------------------------------------
1 | Welcome to the Dispatch 0.8.0 track! This line depends on HttpComponents 4.1, currently in alpha2. To keep things simple we're just doing beta incrementals until they release a final.
2 |
3 | Unless you need an 0.8 feature *now*, you can save yourself some trouble by staying with the 0.7 line, where all bug fixes as well as all new features that don't require HttpComponents 4.1 will land until the final release of 0.8.0.
4 |
5 | * New `http-gae` module for Google App Engine support, contributed by [maxaf][maxaf]
6 | * Configurable max pooled connections in [dispatch.Threads][threads]
7 |
8 | [maxaf]: http://github.com/maxaf
9 | [threads]: http://sourced.implicit.ly/net.databinder/dispatch-http/0.8.0.Beta1/Threads.scala.html#6804
10 |
--------------------------------------------------------------------------------
/notes/0.8.0.Beta3.markdown:
--------------------------------------------------------------------------------
1 | This beta release updates Dispatch's HttpClient dependency to 4.1
2 | final, and adds support for the NIO [HttpAsyncClient][async]. Support
3 | for Scala 2.7.x is dropped in this line.
4 |
5 | [async]: http://hc.apache.org/httpcomponents-asyncclient-dev/index.html
6 |
7 | There are now four standard varieties of http executors available in
8 | different modules:
9 |
10 | dispatch.Http
11 | dispatch.nio.Http
12 | dispatch.thread.Http
13 | dispatch.gae.Http
14 |
15 | Typical usage would be:
16 |
17 | import dispatch._
18 | val http = new nio.Http
19 |
20 | [Request][Request] itself has been substantially refactored. It is now only a
21 | class with properties and a copy method. All of the request-building
22 | operations ("verbs") are [added via implicit conversions][requests],
23 | putting the core module on the same plane as internal and external
24 | extension modules.
25 |
26 | [Request]: http://sourced.implicit.ly/net.databinder/dispatch-core/0.8.0.Beta3/dispatch/requests.scala.html#9128
27 | [requests]: http://sourced.implicit.ly/net.databinder/dispatch-core/0.8.0.Beta3/dispatch/requests.scala.html#9131
28 |
29 | ### Callbacks and futures
30 |
31 | The `thread` and `nio` executors return [stoppable futures][stoppable]
32 | from their `apply` and other request executions, allowing persistent
33 | connections to be easily closed. A new alternative to request handlers
34 | is [dispatch.Callback][callback], which defines a callback function to
35 | receive request data as it arrives. For example, the fundamental
36 | response callback accepting bytes:
37 |
38 | [stoppable]: http://sourced.implicit.ly/net.databinder/dispatch-futures/0.8.0.Beta3/Futures.scala.html#9092
39 | [callback]: http://sourced.implicit.ly/net.databinder/dispatch-core/0.8.0.Beta3/dispatch/callbacks.scala.html
40 |
41 | h(:/("example.com") ^ (response, bytes, length) => { ... } )
42 |
43 | There are more convenient callbacks that convert the bytes to strings,
44 | and buffer them into single lines:
45 |
46 | h(:/("example.com") ^-- println)
47 |
48 | You can also return a value from the stoppable future produced, by
49 | defining a finishing function:
50 |
51 | h(:/("example.com") ^-- println ^> { res => "all done!" })
52 |
53 | But if what you want is a finished response object, it's much easier
54 | to just use the standard response handlers. These work with the NIO
55 | interface by buffering the response in memory. So this works with any
56 | of the executors:
57 |
58 | import dispatch.liftjson.Js._
59 | h(:/("example.com") ># { js => ... })
60 |
61 | With the `thread` and `nio` executors, the current thread continues
62 | while the request is performed in a background or IO thread. The
63 | handler function is called with the completed request object (in this
64 | case a JValue) as soon as it is available.
65 |
66 | The [080 branch][twine] of the twine example application uses the new
67 | stoppable future / callback interface with an NIO executor to connect
68 | to Twitter [User Streams](http://dev.twitter.com/pages/user_streams).
69 |
70 | [twine]: https://github.com/n8han/dispatch-twine/blob/080/src/main/scala/twine/Twine.scala#L68
71 |
72 | ### Breaking changes
73 |
74 | The `Http` object is no longer accessible from all modules, so the
75 | implicit conversions that were found there are now in `Request`.
76 |
77 | One change that is likely to break some client code (sorry!) is
78 | the `<<` and `<` verbs which previously took a `Map[String, Any]`
79 | now take an `Iterable[(String, String)]`. So you can still use a Map
80 | if you like, it just has to be a map of strings to strings or you'll
81 | get compilation errors. The more permissive typing, which made use of
82 | `Any#toString`, was the source of common errors in application code
83 | where non-trivial objects (often, functions) were unknowingly added.
84 |
85 | And lastly, it was necessary to rename the method for adding OAuth
86 | tokens to a query string from `<` to [with_token][with_token], and
87 | all of the [Mime-posting methods][mime] from `<<` to `<<*`. In Scala
88 | 2.8.0 the implicit conversion fails to resolve the overlapping names
89 | with the refactored Request structure. This is fixed in 2.8.1, but
90 | we'd like to support 2.8.0.
91 |
92 | [with_token]: http://sourced.implicit.ly/net.databinder/dispatch-oauth/0.8.0.Beta3/dispatch/OAuth.scala.html#21179
93 | [mime]: http://sourced.implicit.ly/net.databinder/dispatch-mime/0.8.0.Beta3/dispatch/Mime.scala.html#9592
94 |
--------------------------------------------------------------------------------
/notes/0.8.0.Beta4.markdown:
--------------------------------------------------------------------------------
1 | * Restored the [<<(s: String)][post] term
2 | * Provide lift-json's parser a Reader instead of String
3 | * Fix double logging in Http
4 | * Support PUT for encoded form values
5 | * Exception listeners `>!` for handlers and `^!` for callbacks
6 | * [HttpsLeniency][hl] trait to trust all SSL certs (men-in-the-middle rejoice)
7 |
8 | [post]: http://sourced.implicit.ly/net.databinder/dispatch-core/0.8.0.Beta4/dispatch/requests.scala.html#26247
9 | [hl]: http://sourced.implicit.ly/net.databinder/dispatch-http/0.8.0.Beta4/dispatch/https.scala.html#9369
10 |
--------------------------------------------------------------------------------
/notes/0.8.0.Beta5.markdown:
--------------------------------------------------------------------------------
1 | OAuth signature in prior betas did not account for POST parameters,
2 | [reported][report] in the forum and fixed with this release. Published
3 | for Scala 2.8.0, 2.8.1, and 2.9.0.RC1.
4 |
5 | [report]: http://databinder.3617998.n2.nabble.com/Can-t-post-a-message-from-Dispatch-Twine-branch-080-td6209559.html
6 |
--------------------------------------------------------------------------------
/notes/0.8.0.markdown:
--------------------------------------------------------------------------------
1 | Overview
2 | --------
3 |
4 | This is a significant upgrade to Dispatch, adding preliminary support
5 | for the NIO HttpAsyncClient as a backend. Dispatch's internals have
6 | been refactored to provide the needed flexibility, with some breaking
7 | changes. And lastly, Scala 2.7.x is not supported in Dispatch 0.8.x.
8 |
9 | For these reasons, the Dispatch 0.7 line will continue as long as
10 | needed by client applications. Bug fixes will be released in new 0.7
11 | versions (by request) and 0.7 binaries will be back-published for use
12 | with new versions of Scala.
13 |
14 | Core Module and NIO Support
15 | ---------------------------
16 |
17 | In previous versions of Dispatch the lowest level HTTP module was
18 | `dispatch-http`, which depends on HttpComponents's HttpClient
19 | library. Since 0.8.0 adds support for the HttpComponents's nio-based
20 | [HttpAsyncClient][async], there is a new core module that further abstracts
21 | request definitions and handlers/callbacks from request executors.
22 |
23 | [async]: http://hc.apache.org/httpcomponents-asyncclient-dev/index.html
24 |
25 | * dispatch-core - requests, handlers, callbacks, and abstract
26 | executors
27 | * dispatch-http - Traditional Dispatch client interface, depends on
28 | core, uses threads for futures and callbacks
29 | * dispatch-nio - New Dispatch client interface, depends on core, uses
30 | NIO for futures and callbacks
31 |
32 | The dispatch-nio module is relatively unproven compared to
33 | dispatch-http, and its underlying HttpAsyncClient library is a
34 | prerelease version. Just, fyi.
35 |
36 | API Refactorings
37 | ----------------
38 |
39 | The module reorganization motivated a number of refactorings that
40 | would have been a good idea any, but some of them will require changes
41 | to client applications.
42 |
43 | ### import Request._
44 |
45 | If you used to import `Http._` you will probably need to change that
46 | to `Request._`.
47 |
48 | This will enable all the useful implicit conversions that used to be
49 | in dispatch.Http (which is not in the core module). Because these are
50 | in the Request module, most implicit conversions will happen even if
51 | you don't import them. But for migrations the simplest thing is to
52 | change your import; there are no implicits on Http so there is no
53 | reason to leave that import in code.
54 |
55 | ### Parameters are Traversable[(String, String)]
56 |
57 | Sorry, this one's going to sting a little. Parameters used to be
58 | Map[String, Any]. Traversable is more general, no problem there, but
59 | anywhere that your code is passing in a parameter value (e.g. to
60 | `<<` or `<`) whose is type `Any` you'll get a compiler error. If the
61 | value not actually a String just call toString on it, like Dispatch
62 | was doing before.
63 |
64 | The convenience of the old `Any` conversion was not worth the bugs it
65 | invited into compiled code. Specifically, discovering via a trace that
66 | you are POSTing something awful like
67 |
68 | [Ljava.lang.Object;@3c9076d
69 |
70 | to a remote service.
71 |
72 | ### Simpler Request class, corralled verbs
73 |
74 | Request is now a pretty chill class. All of the request *verbs*, like
75 | `<<` and `<`, are implemented in a separate class, as are handler
76 | and callback verbs. This puts these on a level playing field with
77 | extensions in other modules; the only difference is that `Request`
78 | can not implicitly convert to objects it doesn't know about, so you
79 | will still need to import e.g. `OAuth._` to use its `<@` verb.
80 |
81 | Speaking of OAuth, its `<(token: Token)` method has been renamed
82 | `with_token`, and similarly multipart-post methods in `Mime` are
83 | all now `<<*` instead of `<<`. (It turns out that implicitly
84 | overloaded methods are great for finding compiler bugs and bad
85 | for API continuity.)
86 |
87 | Callbacks
88 | ---------
89 |
90 | A new [Callback][callback] interface serves as an alternative to
91 | handlers, for applications that process responses as they arrive, line
92 | by line or by some custom division. Callbacks abstract across the
93 | implementation differences between the underlying `InputStream` and
94 | NIO interfaces and simply access to streaming APIs.
95 |
96 | [callback]: http://sourced.implicit.ly/net.databinder/dispatch-core/0.8.0/callbacks.scala.html#17814
97 |
98 | The [dispatch-meetup][meetup] and [dispatch-twitter][twitter] modules
99 | both use callbacks to handle streaming API responses. The example
100 | application [Twine][twine] has been adapted to demonstrate callbacks
101 | with Twitter's user stream.
102 |
103 | [meetup]: http://sourced.implicit.ly/net.databinder/dispatch-meetup/0.8.0/Streaming.scala.html
104 | [twitter]: http://sourced.implicit.ly/net.databinder/dispatch-twitter/0.8.0/Streaming.scala.html
105 | [twine]: http://github.com/n8han/dispatch-twine
106 |
107 | Google App Engine
108 | -----------------
109 |
110 | Dispatch can be used under GAE with the new `http-gae` module and
111 | `dispatch.gae.Http` executor, contributed by [max4f][max4f].
112 |
113 | [max4f]: http://twitter.com/#!/max4f
114 |
--------------------------------------------------------------------------------
/notes/0.8.1.markdown:
--------------------------------------------------------------------------------
1 | Dependency Updates
2 |
3 | * lift-json 2.3
4 | * Google App Engine API 1.4.3
5 |
6 | Fixes
7 |
8 | * Use request default charset for multipart mime posts ([issue 24](https://github.com/n8han/Databinder-Dispatch/issues/24))
9 |
--------------------------------------------------------------------------------
/notes/0.8.10.markdown:
--------------------------------------------------------------------------------
1 | Some of the versioned binaries (e.g. `dispatch_http_2.9.1-0.8.9`) for
2 | Dispatch Classic 0.8.9 were incorrectly published and included classes
3 | in both the `dispatch` and `dispatch.classic` package. This release
4 | corrects the packaging; there are no other changes.
5 |
--------------------------------------------------------------------------------
/notes/0.8.2.markdown:
--------------------------------------------------------------------------------
1 | #### Changes
2 |
3 | * New XHTML handler verb `>` to handle imperfect inputs that the
4 | xml handler `<>` would choke on.
5 | * [dispatch.Logger](http://sourced.implicit.ly/net.databinder/dispatch-core/0.8.2/executor.scala.html#9104)
6 | trait declares a `warn` method. Any custom
7 | implementations will need to define it.
8 | * The base `HttpExecutor` defines a [shutdown()](http://sourced.implicit.ly/net.databinder/dispatch-core/0.8.2/executor.scala.html#18266)
9 | method which should be
10 | called when finished with any instance.
11 | * `HttpExecutor` defines a `finalize()` method that shuts down the
12 | instance if needed, and issues a warning.
13 | * Removed dynamic call to Configgy logging, which fails on Scala 2.9
14 | if its incompatible classes are on the classpath.
15 |
16 | #### Shutdown Notes
17 |
18 | Although Dispatch 0.7 did not require single-connection client
19 | instances to be shutdown after use, changes in the underlying Apache
20 | HttpComponents library introduced in Dispatch 0.8 effectively require
21 | shutdown for any client instance.
22 |
23 | If an application creates many instances (a few hundred) of a client
24 | and does not shut them down, resources held by the underlying library
25 | will be exhausted and calls to new clients will block indefinitely. To
26 | mitigate the problem Dispatch invokes `shutdown()` from its
27 | `finalize()` method, but an application should not depend on the
28 | garbage collector to keep up with a fluctuating pool of executors.
29 |
30 | If you are using a single-connection executor, Dispatch recommends
31 | that you retain it for a thread of execution (instead of creating new
32 | instances for each call) and shut it down before the thread
33 | terminates. Alternatively, consider sharing a thread-safe client
34 | across your application, which can be easily mixed in to blocking
35 | executors with the [dispatch.thread.Safety](http://sourced.implicit.ly/net.databinder/dispatch-http/0.8.2/thread/thread.scala.html#9391)
36 | trait.
37 |
--------------------------------------------------------------------------------
/notes/0.8.3.markdown:
--------------------------------------------------------------------------------
1 | * Fixed regression in NIO handler futures introduced in 0.8.2. An
2 | exception was thrown after otherwise successful buffered stream
3 | request handling.
4 | * Fixed bug in NIO handler that would in some cases allow `apply()` to
5 | return null after exceptions were recorded. Applying the NIO future
6 | will now throw any recorded exceptions, as intended.
7 |
--------------------------------------------------------------------------------
/notes/0.8.4.markdown:
--------------------------------------------------------------------------------
1 | Botched release, skip to 0.8.5.
2 |
--------------------------------------------------------------------------------
/notes/0.8.5.markdown:
--------------------------------------------------------------------------------
1 | * Converted the build to sbt 0.10, [issue 35](https://github.com/n8han/Databinder-Dispatch/issues/35)
2 | * Extracted API integration modules into external git-dependencies: see updated [project setup documentation](http://dispatch.databinder.net/Project+Setup.html)
3 | * Fixed typing conflict that prevented JSON extension verbs from working with a split request handler, [issue 40](https://github.com/n8han/Databinder-Dispatch/issues/40)
4 |
5 | ### API Module Migration
6 |
7 | To minimize the disruption of this point release, the popular
8 | `dispatch.twitter.Auth` object has been kept in the main distribution,
9 | specifically in the dispatch-oauth module. If you're using only this
10 | object, you won't need to set up a git-dependency on the external
11 | Twitter module.
12 |
--------------------------------------------------------------------------------
/notes/0.8.6.markdown:
--------------------------------------------------------------------------------
1 | ### Features
2 |
3 | * Support for proxy authentication, contributed by [dcsobral](https://github.com/dispatch/dispatch/commit/826c80298067d5a4f93e76dc20d58154cf088ff7)
4 | * Easier to use [header handler >:+](http://dispatch.databinder.net/Two+Handlers+Are+Better+Than+One.html)
5 |
6 | ### Fixes
7 |
8 | * Explicit return type for `make_client` to fix override problem,
9 | contributed by [ostewart](https://github.com/dispatch/dispatch/commit/af44119013455a6bcc4eff293fe42a3b7530f933)
10 | * Avoid slow stream reader usage in json parsing, contributed by [pomu0325](https://github.com/dispatch/dispatch/pull/49)
11 |
12 | ### Dependencies
13 |
14 | * dispatch-gae updated to 1.5.5 of appengine-api
15 | * dispatch-http updated to 4.1.2 of Apache httpclient
16 | * dispatch-lift-json is now an [external source dependency](https://github.com/dispatch/dispatch-lift-json). See the [project setup docs](http://dispatch.databinder.net/Project+Setup.html) for usage information.
17 |
18 | ### News
19 |
20 | The Dispatch repository has moved: [https://github.com/dispatch/dispatch](https://github.com/dispatch/dispatch)
21 |
--------------------------------------------------------------------------------
/notes/0.8.7.markdown:
--------------------------------------------------------------------------------
1 | ### Breaking Changes
2 |
3 | If using the `>` verb to parse XML, you must now bring it into scope
4 | explicitly:
5 |
6 | import dispatch.XhtmlParsing._
7 |
8 | There's a good reason for this! (See below.)
9 |
10 | ### JSoup and TagSoup modules
11 |
12 | Thanks to a [big contribution from daros][soup], Dispatch now
13 | integrates with parsers that can handle real-world HTML. The handler
14 | verb `>` is used across all HTML parsers, resolved by its imported
15 | implicit conversions. For usage instructions and examples, see the
16 | Dispatch documentation for [TagSoup][tagsoup] and [JSoup][jsoup].
17 |
18 | [soup]: https://github.com/dispatch/dispatch/pull/62
19 | [tagsoup]: http://dispatch.databinder.net/TagSoup.html
20 | [jsoup]: http://dispatch.databinder.net/JSoup.html
21 |
22 | ### Support byte arrays for POST
23 |
24 | This [contribution by dyross][ary] (no relation?) adds
25 |
26 | def << (contents: Array[Byte])
27 |
28 | to the standard set of request verbs.
29 |
30 | [ary]: https://github.com/dispatch/dispatch/pull/63
31 |
--------------------------------------------------------------------------------
/notes/0.8.8.markdown:
--------------------------------------------------------------------------------
1 | * JsonParser improvements (pull requests [66][66],
2 | [68][68]) { [elehack][elehack] }
3 | * Improved system-property handling for proxies (pull request [71][71]) {
4 | [iron9light][iron9light] }
5 | * Correction to mime4j dependency, which was failing to resolve for
6 | Maven builds; now points directly to the apache-mime4j-core.
7 | * Updated the httpclient dependency to version 4.1.3.
8 |
9 |
10 | [elehack]: https://github.com/elehack
11 | [iron9light]: https://github.com/iron9light
12 | [68]: https://github.com/dispatch/dispatch/pull/68
13 | [66]: https://github.com/dispatch/dispatch/pull/66
14 | [71]: https://github.com/dispatch/dispatch/pull/71
15 |
--------------------------------------------------------------------------------
/notes/0.8.9.markdown:
--------------------------------------------------------------------------------
1 | ### Forwards-compatibility with Dispatch 0.9+ (reboot)
2 |
3 | This release **changes the base package** of the 0.8 line from
4 | `dispatch` to `dispatch.classic`, so that Dispatch 0.8.9 and Dispatch
5 | 0.9.x may be used on the same classpath.
6 |
7 | This will require code changes in any project using Dispatch 0.8.
8 | Typically, it will require changes to imports and any fully qualified
9 | classname; e.g. `dispatch.Http` becomes `dispatch.classic.Http`.
10 |
11 | ### Enhancements
12 |
13 | * Integration with the Foursquare API [contributed by adamdecaf][fsq]
14 | * Ability to use a byte-array to define a multipart-post body,
15 | [contributed by steppenwells][byte]
16 | * Support for OPTIONS requests, [contributed by stephenjudkins][opt]
17 | * Ability to specify content-type with `<<<`,
18 | [contributed by telmomenezes][typ]
19 |
20 | [fsq]: https://github.com/dispatch/dispatch/pull/75
21 | [byte]: https://github.com/dispatch/dispatch/pull/74
22 | [opt]: https://github.com/dispatch/dispatch/pull/73
23 | [typ]: https://github.com/dispatch/dispatch/pull/72
24 |
--------------------------------------------------------------------------------
/notes/about.markdown:
--------------------------------------------------------------------------------
1 | [Dispatch][1] is a library for HTTP interaction, from asynchronous GETs to multi-part OAuth-enticated POSTs.
2 |
3 | [1]: http://dispatch-classic.databinder.net/
4 |
--------------------------------------------------------------------------------
/oauth/src/main/ls/0.8.6.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-oauth",
5 | "version":"0.8.6",
6 | "description":"OAuth 1.0a signing for Dispatch requests",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/oauth/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-oauth",
5 | "version":"0.8.7",
6 | "description":"OAuth 1.0a signing for Dispatch requests",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | }],
17 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
18 | "sbt": false
19 | }
--------------------------------------------------------------------------------
/oauth/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-oauth",
5 | "version":"0.8.8",
6 | "description":"OAuth 1.0a signing for Dispatch requests",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | }],
20 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
21 | "sbt": false
22 | }
--------------------------------------------------------------------------------
/oauth/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-oauth",
4 | "version" : "0.8.9",
5 | "description" : "OAuth 1.0a signing for Dispatch requests",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | } ],
15 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
16 | "licenses" : [ {
17 | "name" : "LGPL v3",
18 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
19 | } ],
20 | "sbt" : false
21 | }
--------------------------------------------------------------------------------
/oauth/src/main/scala/OAuth.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.oauth
2 | import dispatch.classic._
3 | import Request.{encode_%, decode_%}
4 |
5 | import collection.Map
6 | import collection.immutable.{TreeMap, Map=>IMap}
7 |
8 | import javax.crypto
9 | import java.net.URI
10 |
11 | import org.apache.http.protocol.HTTP.UTF_8
12 |
13 | case class Consumer(key: String, secret: String)
14 | case class Token(value: String, secret: String)
15 | object Token {
16 | def apply[T <: Any](m: Map[String, T]): Option[Token] = List("oauth_token", "oauth_token_secret").flatMap(m.get) match {
17 | case value :: secret :: Nil => Some(Token(value.toString, secret.toString))
18 | case _ => None
19 | }
20 | }
21 |
22 | /** Import this object's methods to add signing operators to dispatch.Request */
23 | object OAuth {
24 | /** @return oauth parameter map including signature */
25 | def sign(method: String, url: String, user_params: Map[String, Any], consumer: Consumer,
26 | token: Option[Token], verifier: Option[String], callback: Option[String]) = {
27 | val oauth_params = IMap(
28 | "oauth_consumer_key" -> consumer.key,
29 | "oauth_signature_method" -> "HMAC-SHA1",
30 | "oauth_timestamp" -> (System.currentTimeMillis / 1000).toString,
31 | "oauth_nonce" -> System.nanoTime.toString,
32 | "oauth_version" -> "1.0"
33 | ) ++ token.map { "oauth_token" -> _.value } ++
34 | verifier.map { "oauth_verifier" -> _ } ++
35 | callback.map { "oauth_callback" -> _ }
36 |
37 | val encoded_ordered_params = (
38 | new TreeMap[String, String] ++ (user_params ++ oauth_params map %%)
39 | ) map { case (k, v) => k + "=" + v } mkString "&"
40 |
41 | val message =
42 | %%(method.toUpperCase :: url :: encoded_ordered_params :: Nil)
43 |
44 | val SHA1 = "HmacSHA1"
45 | val key_str = %%(consumer.secret :: (token map { _.secret } getOrElse "") :: Nil)
46 | val key = new crypto.spec.SecretKeySpec(bytes(key_str), SHA1)
47 | val sig = {
48 | val mac = crypto.Mac.getInstance(SHA1)
49 | mac.init(key)
50 | new String(Request.encode_base64(mac.doFinal(bytes(message))))
51 | }
52 | oauth_params + ("oauth_signature" -> sig)
53 | }
54 |
55 | /** Out-of-band callback code */
56 | val oob = "oob"
57 |
58 | /** Map with oauth_callback set to the given url */
59 | def callback(url: String) = IMap("oauth_callback" -> url)
60 |
61 | //normalize to OAuth percent encoding
62 | private def %% (str: String): String = {
63 | val remaps = ("+", "%20") :: ("%7E", "~") :: ("*", "%2A") :: Nil
64 | (encode_%(str) /: remaps) { case (str, (a, b)) => str.replace(a,b) }
65 | }
66 | private def %% (s: Seq[String]): String = s map %% mkString "&"
67 | private def %% (t: (String, Any)): (String, String) = (%%(t._1), %%(t._2.toString))
68 |
69 | private def bytes(str: String) = str.getBytes(UTF_8)
70 |
71 | /** Add OAuth operators to dispatch.Request */
72 | implicit def Request2RequestSigner(r: Request) = new RequestSigner(r)
73 | /** Add String conversion since Http#str2req implicit will not chain. */
74 | implicit def Request2RequestSigner(r: String) = new RequestSigner(new Request(r))
75 |
76 | class RequestSigner(r: Request) {
77 | @deprecated("use <@ (consumer, callback) to pass the callback in the header for a request-token request", "0.7.8")
78 | def <@ (consumer: Consumer): Request = sign(consumer, None, None, None)
79 | /** sign a request with a callback, e.g. a request-token request */
80 | def <@ (consumer: Consumer, callback: String): Request = sign(consumer, None, None, Some(callback))
81 | /** sign a request with a consumer, token, and verifier, e.g. access-token request */
82 | def <@ (consumer: Consumer, token: Token, verifier: String): Request =
83 | sign(consumer, Some(token), Some(verifier), None)
84 | /** sign a request with a consumer and a token, e.g. an OAuth-signed API request */
85 | def <@ (consumer: Consumer, token: Token): Request = sign(consumer, Some(token), None, None)
86 |
87 | /** add token value as a query string parameter, for user authorization redirects */
88 | def with_token (token: Token) = // implicit use of < here causes compiler error
89 | r < (("oauth_token" -> token.value) :: Nil)
90 |
91 | /** Sign request by reading Post (<<) and query string parameters */
92 | private def sign(consumer: Consumer, token: Option[Token], verifier: Option[String], callback: Option[String]) = {
93 | val oauth_url = r.to_uri.toString.split('?')(0)
94 | val query_params = split_decode(r.to_uri.getRawQuery)
95 | val form_params = r.body.toList.flatMap {
96 | case ent: FormEntity => ent.oauth_params
97 | case _ => Nil
98 | }
99 | val oauth_params = OAuth.sign(r.method, oauth_url,
100 | query_params ++ form_params,
101 | consumer, token, verifier, callback)
102 | r <:< IMap("Authorization" -> ("OAuth " + oauth_params.map {
103 | case (k, v) => (encode_%(k)) + "=\"%s\"".format(encode_%(v))
104 | }.mkString(",") ))
105 | }
106 |
107 | def >% [T] (block: IMap[String, String] => T) = r >- ( split_decode andThen block )
108 | def as_token = r >% { Token(_).getOrElse { sys.error("Token parameters not found in given map") } }
109 |
110 | val split_decode: (String => IMap[String, String]) = {
111 | case null => IMap.empty
112 | case query => IMap.empty ++ query.trim.split('&').map { nvp =>
113 | nvp.split("=").map(decode_%) match {
114 | case Array(name) => name -> ""
115 | case Array(name, value) => name -> value
116 | }
117 | }
118 | }
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/oauth/src/main/scala/foursquare.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.foursquare
2 | import dispatch.classic._
3 |
4 | object Auth {
5 | private val svc: Request = :/ ("api.foursquare.com") secure
6 |
7 | def apply(key: String, secret: String)(request: Request): Request =
8 | apply((key, secret))(request)
9 |
10 | def apply(creds: (String, String))(request: Request): Request = {
11 | svc <& request < Map("client_id" -> creds._1, "client_secret" -> creds._2)
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/oauth/src/main/scala/twitter.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.twitter
2 | import dispatch.classic._
3 |
4 | import oauth._
5 | import oauth.OAuth._
6 |
7 | object Auth {
8 | val svc = :/("api.twitter.com") / "oauth"
9 |
10 | /** Get a request token with no callback URL, out-of-band
11 | * authorization assumed */
12 | def request_token(consumer: Consumer): Handler[Token] =
13 | request_token(consumer, OAuth.oob)
14 |
15 | def request_token(consumer: Consumer, callback_url: String) =
16 | svc.secure.POST / "request_token" <@ (consumer, callback_url) as_token
17 |
18 | def authorize_url(token: Token) =
19 | svc / "authorize" with_token token
20 | def authenticate_url(token: Token) =
21 | svc / "authenticate" with_token token
22 |
23 | def access_token(consumer: Consumer, token: Token, verifier: String) =
24 | svc.secure.POST / "access_token" <@ (consumer, token, verifier) >% {
25 | m => (Token(m).get, m("user_id"), m("screen_name"))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/oauth/src/test/scala/OAuthSpec.scala:
--------------------------------------------------------------------------------
1 | import org.specs2.mutable.Specification
2 |
3 | object OAuthSpec extends Specification {
4 | import dispatch.classic._
5 | import oauth._
6 | import Request._
7 | import OAuth._
8 |
9 | val svc = :/("term.ie") / "oauth" / "example"
10 | val consumer = Consumer("key", "secret")
11 |
12 | "OAuth test host" should {
13 | "echo parameters from protected service" in {
14 | pending
15 | val h = new Http
16 | val request_token = h(svc / "request_token.php" <@ consumer as_token)
17 | val access_token = h(svc / "access_token" <@ (consumer, request_token) as_token)
18 | val payload = Map("identité" -> "caché", "identity" -> "hidden", "アイデンティティー" -> "秘密",
19 | "pita" -> "-._~*")
20 | h(
21 | svc / "echo_api.php" < payload <@ (consumer, access_token) >% {
22 | _ must_== (payload)
23 | }
24 | )
25 | success
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/oauth/src/test/scala/foursquare.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.foursquare
2 |
3 | import dispatch.classic._
4 | import org.specs2.mutable.Specification
5 |
6 | object FoursquareSpec extends Specification {
7 |
8 | private val req = /\ / "v2" / "venues" / "search" < Map("ll" -> "123.123,456.456", "v" -> "19700101")
9 |
10 | "A basic foursquare request that requires auth" should {
11 | "be able to be formed normally" in {
12 | val builtReq = Auth(("key", "secret"))(req).to_uri.toString
13 |
14 | builtReq.contains("api.foursquare.com/v2/venues/search") must_== true
15 | builtReq.contains("client_id=key") must_== true
16 | builtReq.contains("client_secret=secret") must_== true
17 | builtReq.contains("ll=123.123%2C456.456") must_== true
18 | builtReq.contains("v=19700101") must_== true
19 | builtReq.contains("https://") must_== true
20 | }
21 |
22 | "be able to be formed normally through other apply" in {
23 | val builtReq = Auth("key", "secret")(req).to_uri.toString
24 |
25 | builtReq.contains("api.foursquare.com/v2/venues/search") must_== true
26 | builtReq.contains("client_id=key") must_== true
27 | builtReq.contains("client_secret=secret") must_== true
28 | builtReq.contains("ll=123.123%2C456.456") must_== true
29 | builtReq.contains("v=19700101") must_== true
30 | builtReq.contains("https://") must_== true
31 | }
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.1
2 |
--------------------------------------------------------------------------------
/project/build.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 |
4 | object Dispatch extends Build {
5 | val shared = Defaults.defaultSettings ++ ls.Plugin.lsSettings ++ Seq(
6 | organization := "net.databinder",
7 | version := "0.8.10",
8 | scalaVersion := "2.10.4",
9 | parallelExecution in Test := false,
10 | testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "sequential", "true"),
11 | scalacOptions ++= "-deprecation" :: Nil,
12 | crossScalaVersions :=
13 | Seq("2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2", "2.9.3", "2.10.4", "2.11.1"),
14 | libraryDependencies <++= (scalaVersion) { sv => Seq(
15 | sv.split("[.-]").toList match {
16 | case "2" :: "9" :: _ =>
17 | "org.specs2" % "specs2_2.9.2" % "1.12.4" % "test"
18 | case _ =>
19 | "org.specs2" %% "specs2" % "2.3.12" % "test"
20 | })
21 | },
22 | publishMavenStyle := true,
23 | publishTo <<= version { (v: String) =>
24 | val nexus = "https://oss.sonatype.org/"
25 | if (v.trim.endsWith("SNAPSHOT"))
26 | Some("snapshots" at nexus + "content/repositories/snapshots")
27 | else
28 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
29 | },
30 | credentials += Credentials(Path.userHome / ".ivy2" / ".credentials"),
31 | homepage :=
32 | Some(new java.net.URL("http://dispatch-classic.databinder.net/")),
33 | publishArtifact in Test := false,
34 | licenses := Seq("LGPL v3" -> url("http://www.gnu.org/licenses/lgpl.txt")),
35 | pomExtra := (
36 |
37 | git@github.com:dispatch/reboot.git
38 | scm:git:git@github.com:dispatch/reboot.git
39 |
40 |
41 |
42 | n8han
43 | Nathan Hamblen
44 | http://twitter.com/n8han
45 |
46 | )
47 | )
48 | val httpShared = shared ++ Seq(
49 | libraryDependencies +=
50 | "org.apache.httpcomponents" % "httpclient" % "4.1.3"
51 | )
52 | lazy val dispatch =
53 | Project("Dispatch", file("."), settings = shared ++ Seq(
54 | sources in (Compile, doc) <<=
55 | (thisProjectRef, buildStructure) flatMap (aggregateTask(sources)),
56 | dependencyClasspath in (Compile, doc) <<=
57 | (thisProjectRef, buildStructure) flatMap
58 | aggregateTask(dependencyClasspath),
59 | ls.Plugin.LsKeys.skipWrite := true
60 | )) aggregate(
61 | futures, core, http, nio, mime, json, http_json, oauth, gae, tagsoup,
62 | jsoup
63 | )
64 | lazy val futures =
65 | Project("dispatch-futures", file("futures"), settings = shared ++ Seq(
66 | description := "Common interface to Java and Scala futures",
67 | // https://github.com/harrah/xsbt/issues/85#issuecomment-1687483
68 | unmanagedClasspath in Compile += Attributed.blank(new java.io.File("doesnotexist")),
69 | actorsDependency
70 | ))
71 | lazy val core =
72 | Project("dispatch-core", file("core"), settings = httpShared ++ Seq(
73 | description :=
74 | "Core interfaces, applied by dispatch-http and dispatch-nio executors",
75 | xmlDependency
76 | ))
77 | lazy val http =
78 | Project("dispatch-http", file("http"), settings = httpShared ++ Seq(
79 | description :=
80 | "Standard HTTP executor, uses Apache DefaultHttpClient",
81 | sources in Test := {
82 | if (scalaVersion.value.startsWith("2.9.")) Nil
83 | else (sources in Test).value
84 | }
85 | )) dependsOn(
86 | core, futures)
87 | lazy val gae =
88 | Project("dispatch-gae", file("http-gae"), settings = httpShared ++ Seq(
89 | description :=
90 | "Executor with a modified Apache HttpClient for Google App Engine",
91 | libraryDependencies +=
92 | "com.google.appengine" % "appengine-api-1.0-sdk" % "1.5.5"
93 | )) dependsOn(http)
94 | lazy val nio =
95 | Project("dispatch-nio", file("nio"), settings = httpShared ++ Seq(
96 | description :=
97 | "NIO HTTP executor, uses Apache DefaultHttpAsyncClient",
98 | libraryDependencies +=
99 | ("org.apache.httpcomponents" % "httpasyncclient" % "4.0-alpha1")
100 | )) dependsOn(core, futures)
101 | lazy val mime =
102 | Project("dispatch-mime", file("mime"), settings = httpShared ++ Seq(
103 | description :=
104 | "Support for multipart MIME POSTs",
105 | libraryDependencies ++= Seq(
106 | "org.apache.httpcomponents" % "httpmime" % "4.1.2" intransitive(),
107 | "commons-logging" % "commons-logging" % "1.1.1",
108 | "org.apache.james" % "apache-mime4j-core" % "0.7.2"
109 | ),
110 | actorsDependency
111 | )) dependsOn(core)
112 | lazy val json =
113 | Project("dispatch-json", file("json"), settings = shared ++ Seq(
114 | description := "A JSON parser",
115 | sources in Test := {
116 | if (scalaVersion.value.startsWith("2.9.")) Nil
117 | else (sources in Test).value
118 | },
119 | // https://github.com/harrah/xsbt/issues/85#issuecomment-1687483
120 | unmanagedClasspath in Compile += Attributed.blank(new java.io.File("doesnotexist")),
121 | parserDependency
122 | ))
123 | lazy val http_json =
124 | Project("dispatch-http-json", file("http+json"),
125 | settings = httpShared ++ Seq(
126 | description := "Adds JSON handler verbs to Dispatch"
127 | )) dependsOn(core, json)
128 | lazy val oauth =
129 | Project("dispatch-oauth", file("oauth"), settings = httpShared ++ Seq(
130 | description := "OAuth 1.0a signing for Dispatch requests"
131 | )) dependsOn(
132 | core, http)
133 | lazy val tagsoup =
134 | Project("dispatch-tagsoup", file("tagsoup"), settings = httpShared ++ Seq(
135 | description := "Adds TagSoup handler verbs to Dispatch",
136 | libraryDependencies ++= Seq(
137 | "org.ccil.cowan.tagsoup" % "tagsoup" % "1.2.1",
138 | "org.eclipse.jetty.aggregate" % "jetty-server" % "7.5.4.v20111024" % "test"
139 | )
140 | )) dependsOn(core, http)
141 | lazy val jsoup =
142 | Project("dispatch-jsoup", file("jsoup"), settings = httpShared ++ Seq(
143 | description := "Adds JSoup handler verbs to Dispatch",
144 | libraryDependencies ++= Seq(
145 | "org.jsoup" % "jsoup" % "1.6.1",
146 | "org.eclipse.jetty.aggregate" % "jetty-server" % "7.5.4.v20111024" % "test"
147 | )
148 | )) dependsOn(core, http)
149 |
150 | def aggregateTask[T](key: TaskKey[Seq[T]])
151 | (proj: ProjectRef, struct: BuildStructure) = {
152 | def collectProjects(op: ResolvedProject => Seq[ProjectRef])
153 | (projRef: ProjectRef,
154 | struct: BuildStructure): Seq[ProjectRef] = {
155 | val delg = Project.getProject(projRef, struct).toSeq.flatMap(op)
156 | // Dependencies/aggregates might have their own dependencies/aggregates
157 | // so go recursive and do distinct.
158 | delg.flatMap(ref => ref +: collectProjects(op)(ref, struct)).distinct
159 | }
160 | collectProjects(_.aggregate)(proj, struct).flatMap(
161 | key in (_, Compile, doc) get struct.data
162 | ).join.map(_.flatten)
163 | }
164 |
165 | lazy val actorsDependency = libraryDependencies <<= (libraryDependencies, scalaVersion){
166 | (dependencies, scalaVersion) =>
167 | if(scalaVersion.startsWith("2.10") || scalaVersion.startsWith("2.11"))
168 | ("org.scala-lang" % "scala-actors" % scalaVersion) +: dependencies
169 | else
170 | dependencies
171 | }
172 |
173 | lazy val xmlDependency = libraryDependencies <<= (libraryDependencies, scalaVersion){
174 | (dependencies, scalaVersion) =>
175 | if(scalaVersion.startsWith("2.11"))
176 | ("org.scala-lang.modules" %% "scala-xml" % "1.0.1") +: dependencies
177 | else
178 | dependencies
179 | }
180 | lazy val parserDependency = libraryDependencies <<= (libraryDependencies, scalaVersion){
181 | (dependencies, scalaVersion) =>
182 | if(scalaVersion.startsWith("2.11"))
183 | ("org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1") +: dependencies
184 | else
185 | dependencies
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | resolvers ++= Seq(
2 | "less is" at "http://repo.lessis.me",
3 | "coda" at "http://repo.codahale.com"
4 | )
5 |
6 | addSbtPlugin("me.lessis" % "ls-sbt" % "0.1.3")
7 |
8 | scalacOptions += "-deprecation"
9 |
--------------------------------------------------------------------------------
/tagsoup/src/main/ls/0.8.7.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-tagsoup",
5 | "version":"0.8.7",
6 | "description":"Adds TagSoup handler verbs to Dispatch",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [],
11 | "resolvers": ["http://scala-tools.org/repo-releases"],
12 | "dependencies": [{
13 | "organization":"org.apache.httpcomponents",
14 | "name": "httpclient",
15 | "version": "4.1.2"
16 | },{
17 | "organization":"org.ccil.cowan.tagsoup",
18 | "name": "tagsoup",
19 | "version": "1.2.1"
20 | }],
21 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
22 | "sbt": false
23 | }
--------------------------------------------------------------------------------
/tagsoup/src/main/ls/0.8.8.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "organization":"net.databinder",
4 | "name":"dispatch-tagsoup",
5 | "version":"0.8.8",
6 | "description":"Adds TagSoup handler verbs to Dispatch",
7 | "site":"http://dispatch.databinder.net/",
8 | "tags":[],
9 | "docs":"",
10 | "licenses": [{
11 | "name": "LGPL v3",
12 | "url": "http://www.gnu.org/licenses/lgpl.txt"
13 | }],
14 | "resolvers": ["http://scala-tools.org/repo-releases"],
15 | "dependencies": [{
16 | "organization":"org.apache.httpcomponents",
17 | "name": "httpclient",
18 | "version": "4.1.3"
19 | },{
20 | "organization":"org.ccil.cowan.tagsoup",
21 | "name": "tagsoup",
22 | "version": "1.2.1"
23 | }],
24 | "scalas": ["2.8.0","2.8.1","2.8.2","2.9.0","2.9.0-1","2.9.1"],
25 | "sbt": false
26 | }
--------------------------------------------------------------------------------
/tagsoup/src/main/ls/0.8.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "organization" : "net.databinder",
3 | "name" : "dispatch-tagsoup",
4 | "version" : "0.8.9",
5 | "description" : "Adds TagSoup handler verbs to Dispatch",
6 | "site" : "http://dispatch.databinder.net/",
7 | "tags" : [ ],
8 | "docs" : "",
9 | "resolvers" : [ "https://oss.sonatype.org/content/repositories/releases" ],
10 | "dependencies" : [ {
11 | "organization" : "org.apache.httpcomponents",
12 | "name" : "httpclient",
13 | "version" : "4.1.3"
14 | }, {
15 | "organization" : "org.ccil.cowan.tagsoup",
16 | "name" : "tagsoup",
17 | "version" : "1.2.1"
18 | } ],
19 | "scalas" : [ "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.0-1", "2.9.1", "2.9.1-1", "2.9.2" ],
20 | "licenses" : [ {
21 | "name" : "LGPL v3",
22 | "url" : "http://www.gnu.org/licenses/lgpl.txt"
23 | } ],
24 | "sbt" : false
25 | }
--------------------------------------------------------------------------------
/tagsoup/src/main/scala/TagSoupHttp.scala:
--------------------------------------------------------------------------------
1 | package dispatch.classic.tagsoup
2 |
3 | import dispatch.classic.{HandlerVerbs, Request}
4 | import xml.parsing.NoBindingFactoryAdapter
5 |
6 | import java.io.InputStreamReader
7 | import org.xml.sax.InputSource
8 | import org.ccil.cowan.tagsoup.jaxp.SAXFactoryImpl
9 |
10 | trait ImplicitTagSoupHandlers {
11 | implicit def handlerToTagSoupHandlers(h: HandlerVerbs) = new TagSoupHandlers(h)
12 | implicit def requestToTagSoupHandlers(req: Request) = new TagSoupHandlers(req);
13 | implicit def stringToTagSoupHandlers(str: String) = new TagSoupHandlers(new Request(str));
14 | }
15 |
16 | object TagSoupHttp extends ImplicitTagSoupHandlers
17 |
18 | class TagSoupHandlers(subject: HandlerVerbs) {
19 | lazy val parserFactory = new SAXFactoryImpl
20 | /** Process response with TagSoup html processor in block */
21 | def tagsouped [T] (block: (xml.NodeSeq) => T) = subject >> { (stm, charset) =>
22 | block( new NoBindingFactoryAdapter().loadXML(new InputSource(new InputStreamReader(stm, charset)), parserFactory.newSAXParser()) )
23 | }
24 | /** Alias for verb tagsouped */
25 | def > [T] (block: (xml.NodeSeq) => T) = tagsouped (block)
26 | /** Conveniences handler for retrieving a NodeSeq */
27 | def as_tagsouped = tagsouped {ns => ns}
28 | }
29 |
--------------------------------------------------------------------------------
/tagsoup/src/test/resources/Human.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | Human
6 |
7 |
8 | To err is human, to forgive divine
9 |
10 | The verb "err" means to do something wrong; to make a mistake is "to err".
11 | "To err is human" because all people ("humans") make mistakes.
12 |
"To err is human, to forgive divine" says we should try hard to forgive others because all
13 | people are human and make mistakes.
14 |
Example: "I am still angry about what my manager did yesterday!" Answer: "It is best to just
15 | let it go; to err is human, to forgive divine." The world of people is "human"
16 | and the world of God is "divine".
17 |
God's special power to forgive people for their mistakes is called "divine" mercy.
18 | When we forgive other people we are acting "divine". "To err is human, to forgive
19 | divine" says that we are all human and we all make mistakes so we should all try hard to forgive other
20 | people when they make mistakes.
21 |
Example: "I will never forgive my mother for what she has done!"
22 |
Answer:
23 |
"Don't be angry at her. To err is human, to forgive divine." (Because we use the
24 | word "is" in the first phrase "to err is human" we do not use "is" again in the second phrase:
25 | "to forgive divine".) We are all people who make mistakes so to forgive others when they make
26 | mistakes is the right thing to do; "to err is human, to forgive divine."
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tagsoup/src/test/resources/test.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | ApA
6 |
7 |
8 | Ape
9 |
Apes are Old World anthropoid mammals, more specifically a clade of tailless catarrhine primates,
10 | belonging to the biological superfamily Hominoidea. The apes are native to Africa and South-east Asia,
11 | although in relatively recent times humans have spread all over the world.
12 | Apes are the world's largest primates; the orangutan, an ape, is the world's largest living arboreal animal.
13 | Hominoids are traditionally forest dwellers, although chimpanzees may range into savanna, and the extinct
14 | australopithecines are famous for being savanna inhabitants, inferred from their morphology. Humans inhabit
15 | almost every terrestrial habitat.
16 | Hominoidea contains two families of living (extant) species:
17 |
18 | - Hylobatidae consists of four genera and sixteen species of gibbon, including the lar gibbon and the siamang.
19 | They are commonly referred to as lesser apes.
20 | - Hominidae consists of orangutans, gorillas, common chimpanzees, bonobos and humans. Alternatively, the
21 | hominidae family are collectively described as the great apes.
22 |
23 |
24 |
--------------------------------------------------------------------------------
/tagsoup/src/test/scala/TagSoupSpec.scala:
--------------------------------------------------------------------------------
1 | import java.io.File
2 | import org.eclipse.jetty.server.handler.{DefaultHandler, HandlerList, ResourceHandler}
3 | import org.eclipse.jetty.server.Server
4 | import org.eclipse.jetty.server.nio.SelectChannelConnector
5 | import org.specs2.mutable.Specification
6 | import dispatch.classic._
7 | import dispatch.classic.tagsoup._
8 |
9 | object TagSoupSpec extends Specification with ServedByJetty {
10 | val port = 9980
11 | val resourceBase = "./tagsoup/src/test/resources"
12 |
13 | object BadHtml_with_ImplicitTagSoupHandlers
14 | extends Request(:/("localhost", port) / "Human.html")
15 | with ImplicitTagSoupHandlers
16 |
17 | class BadHtmlClass1(request: Request = (:/("localhost", port) / "Human.html"))
18 | extends Request(request: Request)
19 | with ImplicitTagSoupHandlers
20 |
21 | class BadHtmlClass2(request: Request = (:/("localhost", port) / "Human.html"))
22 | extends Request(request: Request)
23 |
24 | object BadHtml2
25 | extends Request(:/("localhost", port) / "Human.html")
26 | with ImplicitTagSoupHandlers
27 |
28 | object BadHtml
29 | extends Request(:/("localhost", port) / "Human.html")
30 |
31 | "Using >" should {
32 | "fail to parse resource" in {
33 | withResourceServer { _ =>
34 | import XhtmlParsing._
35 | val request = :/("localhost", port) / "Human.html"
36 |
37 | Http(request > { nodes =>
38 | (nodes \\ "title").text
39 | }) must throwA[scala.xml.parsing.FatalError]
40 | }
41 | }
42 | }
43 |
44 | """Using >""" should {
45 | import TagSoupHttp._
46 | "successfully parse resource" in {
47 | withResourceServer { _ =>
48 | val request = :/("localhost", port) / "Human.html"
49 | Http(request > { nodes =>
50 | (nodes \\ "title").text
51 | }) must not (throwA[scala.xml.parsing.FatalError])
52 | }
53 | }
54 | }
55 |
56 | "Extending implicit TagSoup" should {
57 | "make BadHtml parsable" in {
58 | withResourceServer { _ =>
59 | val request = BadHtml_with_ImplicitTagSoupHandlers
60 | val title = Http(request tagsouped { nodes =>
61 | (nodes \\ "title").text
62 | })
63 | title must be_==("Human")
64 | }
65 | }
66 |
67 | "make BadHtmlClass1 (class extend implicit) parsable, though this is ugly" in {
68 | withResourceServer { _ =>
69 | var request = new BadHtmlClass1()
70 | val title = Http(request.requestToTagSoupHandlers(request) tagsouped { nodes =>
71 | (nodes \\ "title").text
72 | })
73 | title must be_==("Human")
74 | }
75 | }
76 |
77 | "make BadHtmlClass2 (instance extends implicit) parsable, though this is ugly" in {
78 | withResourceServer { _ =>
79 | var request = new BadHtmlClass2() with ImplicitTagSoupHandlers
80 | val title = Http(request.requestToTagSoupHandlers(request) tagsouped { nodes =>
81 | (nodes \\ "title").text
82 | })
83 |
84 | title must be_==("Human")
85 | }
86 | }
87 | }
88 |
89 | "Implicit TagSoupHttp converters in scope" should {
90 | import TagSoupHttp._
91 | "make Request parsable" in {
92 | withResourceServer { _ =>
93 | val request = :/("localhost", port) / "Human.html"
94 | val title = Http(request tagsouped { nodes =>
95 | (nodes \\ "title").text
96 | })
97 |
98 | title must be_==("Human")
99 | }
100 | }
101 |
102 | "make BadHtmlClass1 (class extend implicit) parsable" in {
103 | withResourceServer { _ =>
104 | var request = new BadHtmlClass1()
105 | val title = Http(request tagsouped { nodes =>
106 | (nodes \\ "title").text
107 | })
108 |
109 | title must be_==("Human")
110 | }
111 | }
112 |
113 | "make BadHtmlClass2 (instance extends implicit) parsable" in {
114 | withResourceServer { _ =>
115 | var request = new BadHtmlClass2() with ImplicitTagSoupHandlers
116 | val title = Http(request tagsouped { nodes =>
117 | (nodes \\ "title").text
118 | })
119 |
120 | title must be_==("Human")
121 | }
122 | }
123 |
124 | "make BadHtmlClass2 parsable" in {
125 | withResourceServer { _ =>
126 | var request = new BadHtmlClass2()
127 | val title = Http(request tagsouped { nodes =>
128 | (nodes \\ "title").text
129 | })
130 |
131 | title must be_==("Human")
132 | }
133 | }
134 |
135 | "make BadHtml (object) parsable" in {
136 | withResourceServer { _ =>
137 | val request = BadHtml
138 | val title = Http(request tagsouped { nodes =>
139 | (nodes \\ "title").text
140 | })
141 |
142 | title must be_==("Human")
143 | }
144 | }
145 | }
146 |
147 | """Using the verb >""" should {
148 | "do the same thing as the verb tagsouped" in {
149 | withResourceServer { _ =>
150 | val request = BadHtml_with_ImplicitTagSoupHandlers
151 |
152 | val title1 = Http(request > { nodes =>
153 | (nodes \\ "title").text
154 | })
155 | val title2 = Http(request tagsouped { nodes =>
156 | (nodes \\ "title").text
157 | })
158 |
159 | title1 must be_==(title2)
160 | }
161 | }
162 | }
163 |
164 | "Using the verb as_tagsouped" should {
165 | "return the nodes" in {
166 | withResourceServer { _ =>
167 | val request = BadHtml_with_ImplicitTagSoupHandlers
168 | val ns = Http(request as_tagsouped)
169 |
170 | (ns \\ "title").text must be_==("Human")
171 | }
172 | }
173 | }
174 | }
175 |
176 | trait ServedByJetty {
177 | val port: Int
178 | val resourceBase: String
179 |
180 | def withResourceServer[A](op: Unit => A): A = {
181 | // Configure Jetty server
182 | val connector = new SelectChannelConnector
183 | connector.setHost("localhost")
184 | connector.setPort(port)
185 |
186 | val handler = new ResourceHandler
187 | handler.setDirectoriesListed(true)
188 | handler.setResourceBase(resourceBase)
189 | val handlers = new HandlerList
190 | handlers.setHandlers(Array(handler, new DefaultHandler))
191 |
192 | val server = new Server
193 | server.addConnector(connector)
194 | server.setHandler(handlers)
195 |
196 | // Run server for test and then stop
197 | try {
198 | server.start
199 | op(())
200 | } finally {
201 | server.stop
202 | }
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/temp:
--------------------------------------------------------------------------------
1 | Adding Dispatch to a Project
2 | ----------------------------
3 |
4 | The latest release of Databinder Dispatch is [$version$][notes].
5 |
6 | [notes]: http://implicit.ly/dispatch-080
7 |
8 | ### Modules
9 |
10 | The library is divided into separate modules so that client
11 | applications may depend only on the parts of Dispatch they use. Each
12 | module is [cross-built][sbt] against several versions of Scala and
13 | [published to the scala-tools][st] repository with the organization-id
14 | "net.databinder". The modules have the Scala version they are built
15 | against appended; for Scala $scala$:
16 |
17 | * dispatch-http_$scala$
18 | * dispatch-core_$scala$
19 | * dispatch-nio_$scala$
20 | * dispatch-futures_$scala$
21 | * dispatch-mime_$scala$
22 | * dispatch-json_$scala$
23 | * dispatch-http-json_$scala$
24 | * dispatch-lift-json_$scala$
25 | * dispatch-oauth_$scala$
26 | * dispatch-meetup_$scala$
27 | * dispatch-couch_$scala$
28 | * dispatch-twitter_$scala$
29 | * dispatch-s3_$scala$
30 | * dispatch-google_$scala$
31 | * dispatch-gae_$scala$
32 |
33 | [dn]: http://databinder.net/repo/
34 | [st]: http://scala-tools.org/repo-releases/net/databinder/
35 | [sbt]: http://code.google.com/p/simple-build-tool/wiki/CrossBuild
36 |
37 | With simple-build-tool it's best to have the Scala version
38 | [automatically appended][sbt] so it will always match your
39 | project's. For Maven or Ivy + Ant, specify a full artifact-id like
40 | those given above.
41 |
42 | Module dependencies are transitive, so you only need to depend on
43 | dispatch-twitter to have that module as well as -oauth, -json, and
44 | -http on the classpath.
45 |
46 | ### Source
47 |
48 | Dispatch $version$'s full source is available: { [zip][zip] | [tar.bz2][tar.bz2] | [github][gh] }
49 |
50 | [zip]: http://technically.us/git?p=dispatch.git;a=snapshot;h=$version$;sf=zip
51 | [tar.bz2]: http://technically.us/git?p=dispatch.git;a=snapshot;h=$version$;sf=tbz2
52 |
53 | [gh]: https://github.com/n8han/Databinder-Dispatch
54 |
--------------------------------------------------------------------------------