Skip to content

Commit 8dd1d7f

Browse files
committed
initial commit
0 parents  commit 8dd1d7f

File tree

9 files changed

+892
-0
lines changed

9 files changed

+892
-0
lines changed

.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
target/
2+
*.iml
3+
.idea
4+
.settings
5+
.classpath
6+
.project
7+
.cache
8+
.sbtserver
9+
project/.sbtserver
10+
tags
11+
nohup.out
12+
out

build.sc

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import mill._, scalalib._
2+
3+
object requests extends ScalaModule {
4+
def scalaVersion = "2.12.6"
5+
object test extends Tests{
6+
def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.6.3")
7+
def testFrameworks = Seq("utest.runner.Framework")
8+
}
9+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package requests
2+
3+
class TimeoutException(val url: String, val readTimeout: Int, val connectTimeout: Int)
4+
extends Exception(s"Request to $url timed out. (readTimeout: $readTimeout, connectTimout: $connectTimeout)")
5+
6+
class UnknownHostException(val url: String, val host: String)
7+
extends Exception(s"Unknown host $host in url $url")
8+
9+
class InvalidCertException(val url: String, cause: Throwable)
10+
extends Exception(s"Unable to validate SSL certificates for $url", cause)

requests/src/requests/Hello.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import java.net.HttpCookie
2+
3+
import scala.collection.mutable
4+
5+
package object requests extends _root_.requests.BaseSession {
6+
def cookies = mutable.Map.empty[String, HttpCookie]
7+
8+
val headers = BaseSession.defaultHeaders
9+
10+
def auth = RequestAuth.Empty
11+
12+
def proxy = null
13+
14+
def maxRedirects: Int = 5
15+
16+
def persistCookies = false
17+
18+
def readTimeout: Int = 10 * 1000
19+
20+
def connectTimeout: Int = 10 * 1000
21+
22+
def verifySslCerts: Boolean = true
23+
24+
def autoDecompress: Boolean = true
25+
26+
def compress: Compress = Compress.Gzip
27+
}

requests/src/requests/Model.scala

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package requests
2+
3+
import java.io.FileInputStream
4+
import java.net.HttpCookie
5+
import java.util.UUID
6+
7+
import collection.JavaConverters._
8+
import java.io.OutputStream
9+
import java.util.zip.{DeflaterOutputStream, GZIPOutputStream}
10+
11+
/**
12+
* Mechanisms for compressing the upload stream; supports Gzip and Deflate
13+
* by default
14+
*/
15+
trait Compress{
16+
def wrap(x: OutputStream): OutputStream
17+
}
18+
object Compress{
19+
object Gzip extends Compress{
20+
def wrap(x: OutputStream) = new GZIPOutputStream(x)
21+
}
22+
object Deflate extends Compress{
23+
def wrap(x: OutputStream) = new DeflaterOutputStream(x)
24+
}
25+
object None extends Compress{
26+
def wrap(x: OutputStream) = x
27+
}
28+
}
29+
30+
/**
31+
* The equivalent of configuring a [[Requester.apply]] or [[Requester.stream]]
32+
* call, but without invoking it. Useful if you want to further customize it
33+
* and make the call later via the overloads of `apply`/`stream` that take a
34+
* [[Request]].
35+
*/
36+
case class Request(url: String,
37+
auth: RequestAuth = null,
38+
params: Iterable[(String, String)] = Nil,
39+
headers: Iterable[(String, String)] = Nil,
40+
readTimeout: Int = 0,
41+
connectTimeout: Int = 0,
42+
proxy: (String, Int) = null,
43+
cookies: Map[String, String] = Map(),
44+
maxRedirects: Int = 5,
45+
verifySslCerts: Boolean = true,
46+
autoDecompress: Boolean = true,
47+
compress: Compress = Compress.Gzip)
48+
49+
/**
50+
* Wraps the array of bytes returned in the body of a HTTP response
51+
*/
52+
class ResponseBlob(val bytes: Array[Byte]){
53+
override def toString = s"ResponseBlob(${bytes.length} bytes)"
54+
def string = new String(bytes)
55+
}
56+
57+
/**
58+
* Represents the different things you can upload in the body of a HTTP
59+
* request. By default allows form-encoded key-value pairs, arrays of bytes,
60+
* strings, files, and inputstreams. These types can be passed directly to
61+
* the `data` parameter of [[Requester.apply]] and will be wrapped automatically
62+
* by the implicit constructors.
63+
*/
64+
trait RequestBlob{
65+
def headers: Seq[(String, String)] = Nil
66+
def write(out: java.io.OutputStream): Unit
67+
}
68+
object RequestBlob{
69+
trait SizedBlob extends RequestBlob{
70+
override def headers: Seq[(String, String)] = Seq(
71+
"Content-Length" -> length.toString
72+
)
73+
def length: Long
74+
}
75+
object EmptyRequestBlob extends RequestBlob{
76+
def write(out: java.io.OutputStream): Unit = ()
77+
}
78+
object SizedBlob{
79+
implicit class BytesRequestBlob(val x: Array[Byte]) extends SizedBlob{
80+
def length = x.length
81+
def write(out: java.io.OutputStream) = out.write(x)
82+
}
83+
implicit class StringRequestBlob(val x: String) extends SizedBlob{
84+
val serialized = x.getBytes()
85+
def length = serialized.length
86+
def write(out: java.io.OutputStream) = out.write(serialized)
87+
}
88+
implicit class FileRequestBlob(val x: java.io.File) extends SizedBlob{
89+
def length = x.length()
90+
def write(out: java.io.OutputStream) = Util.transferTo(new FileInputStream(x), out)
91+
}
92+
implicit class NioFileRequestBlob(val x: java.nio.file.Path) extends SizedBlob{
93+
def length = java.nio.file.Files.size(x)
94+
def write(out: java.io.OutputStream) = Util.transferTo(java.nio.file.Files.newInputStream(x), out)
95+
}
96+
}
97+
98+
implicit class InputStreamRequestBlob(val x: java.io.InputStream) extends RequestBlob{
99+
def write(out: java.io.OutputStream) = Util.transferTo(x, out)
100+
}
101+
implicit class FormEncodedRequestBlob(val x: Iterable[(String, String)]) extends SizedBlob {
102+
val serialized = Util.urlEncode(x).getBytes
103+
def length = serialized.length
104+
override def headers = super.headers ++ Seq(
105+
"Content-Type" -> "application/x-www-form-urlencoded",
106+
)
107+
def write(out: java.io.OutputStream) = {
108+
out.write(serialized)
109+
}
110+
}
111+
112+
implicit class MultipartFormRequestBlob(val parts: Iterable[MultiItem]) extends RequestBlob{
113+
val boundary = UUID.randomUUID().toString
114+
val crlf = "\r\n"
115+
val pref = "--"
116+
117+
val ContentDisposition = "Content-Disposition: form-data; name=\""
118+
val filenameSnippet = "\"; filename=\""
119+
120+
// encode params up front for the length calculation
121+
122+
val partBytes = parts.map(p => (p.name.getBytes(), if (p.filename == null) Array[Byte]() else p.filename.getBytes(), p))
123+
124+
// we need to pre-calculate the Content-Length of this HttpRequest because most servers don't
125+
// support chunked transfer
126+
val totalBytesToSend: Long = {
127+
128+
val partsLength = partBytes.map{
129+
case (name, filename, part) =>
130+
pref.length + boundary.length + crlf.length +
131+
ContentDisposition.length +
132+
name.length +
133+
(if(filename.nonEmpty) filenameSnippet.length + filename.length else 0) +
134+
"\"".length + crlf.length + crlf.length +
135+
part.data.length +
136+
crlf.length
137+
}
138+
val finaleBoundaryLength = (pref.length * 2) + boundary.length + crlf.length
139+
140+
partsLength.sum + finaleBoundaryLength
141+
}
142+
143+
override def headers = Seq(
144+
"Content-Type" -> s"multipart/form-data; boundary=$boundary",
145+
"Content-Length" -> totalBytesToSend.toString
146+
)
147+
148+
def write(out: java.io.OutputStream) = {
149+
def writeBytes(s: String): Unit = out.write(s.getBytes())
150+
151+
partBytes.foreach {
152+
case(name, filename, part) =>
153+
writeBytes(pref + boundary + crlf)
154+
writeBytes(ContentDisposition)
155+
out.write(name)
156+
if (filename.nonEmpty){
157+
writeBytes(filenameSnippet)
158+
out.write(filename)
159+
}
160+
writeBytes("\"" + crlf + crlf)
161+
part.data.write(out)
162+
writeBytes(crlf)
163+
}
164+
165+
writeBytes(pref + boundary + pref + crlf)
166+
167+
out.flush()
168+
out.close()
169+
}
170+
}
171+
}
172+
173+
case class MultiPart(items: MultiItem*) extends RequestBlob.MultipartFormRequestBlob(items)
174+
case class MultiItem(name: String,
175+
data: RequestBlob.SizedBlob,
176+
filename: String = null)
177+
178+
/**
179+
* Represents a HTTP response
180+
*
181+
* @param url the URL that the original request was made to
182+
* @param statusCode the status code of the response
183+
* @param statusMessage the status message of the response
184+
* @param headers the raw headers the server sent back with the response
185+
* @param data the response body; may contain HTML, JSON, or binary or textual data
186+
* @param history the response of any redirects that were performed before
187+
* arriving at the current response
188+
*/
189+
case class Response(url: String,
190+
statusCode: Int,
191+
statusMessage: String,
192+
headers: Map[String, Seq[String]],
193+
data: ResponseBlob,
194+
history: Option[Response]){
195+
196+
def cookies: Map[String, HttpCookie] = headers
197+
.get("Set-Cookie")
198+
.iterator
199+
.flatten
200+
.flatMap(java.net.HttpCookie.parse(_).asScala)
201+
.map(x => x.getName -> x)
202+
.toMap
203+
204+
def contentType = headers.get("Content-Type").flatMap(_.headOption)
205+
def location = headers.get("Location").flatMap(_.headOption)
206+
}
207+
208+
/**
209+
* Different ways you can authorize a HTTP request; by default, HTTP Basic
210+
* auth and Proxy auth are supported
211+
*/
212+
trait RequestAuth{
213+
def header: Option[String]
214+
}
215+
216+
object RequestAuth{
217+
object Empty extends RequestAuth{
218+
def header = None
219+
}
220+
implicit def implicitBasic(x: (String, String)) = new Basic(x._1, x._2)
221+
class Basic(username: String, password: String) extends RequestAuth{
222+
def header = Some("Basic " + java.util.Base64.getEncoder.encode((username + ":" + password).getBytes()))
223+
}
224+
case class Proxy(username: String, password: String) extends RequestAuth{
225+
def header = Some("Proxy-Authorization " + java.util.Base64.getEncoder.encode((username + ":" + password).getBytes()))
226+
}
227+
}

0 commit comments

Comments
 (0)