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