1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2013 Matthieu Lucas <lucasmatthieu@gmail.com>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Network functionnalities based on Curl_c module.
25 protected var prim_curl
: CCurl
29 self.prim_curl
= new CCurl.easy_init
30 assert curlInstance
:self.prim_curl
.is_init
else
31 print
"Curl must be instancied to be used"
35 # Check for correct initialization
36 fun is_ok
: Bool do return self.prim_curl
.is_init
38 # Get an HTTP Request object to perform your own
39 fun http_request
(url
: String): nullable CurlRequest
42 err
= self.prim_curl
.easy_setopt
(new CURLOption.follow_location
, 1)
43 if not err
.is_ok
then return null
45 err
= self.prim_curl
.easy_setopt
(new CURLOption.url
, url
)
46 if not err
.is_ok
then return null
48 return new CurlHTTPRequest(url
, self)
51 # Release Curl instance
52 fun destroy
do self.prim_curl
.easy_clean
60 var headers
: nullable HeaderMap writable = null
61 var datas
: nullable HeaderMap writable = null
62 var delegate
: nullable CurlCallbacks writable = null
63 var verbose
: Bool writable = false
64 private var curl
: nullable Curl
66 # Launch request method
67 fun execute
: CurlResponse is abstract
69 # Intern perform method, lowest level of request launching
70 private fun perform
: nullable CurlResponse
72 if not self.curl
.is_ok
then return answer_failure
(0, "Curl instance is not correctly initialized")
76 if self.datas
!= null then
77 var postdatas
= self.datas
.to_url_encoded
(self.curl
.prim_curl
)
78 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.postfields
, postdatas
)
79 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
82 if self.headers
!= null then
83 var headers_joined
= self.headers
.join_pairs
(": ")
84 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.httpheader
, headers_joined
.to_curlslist
)
85 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
88 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.verbose
, self.verbose
)
89 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
91 err
= self.curl
.prim_curl
.easy_perform
92 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
97 # Intern method with return a failed answer with given code and message
98 private fun answer_failure
(error_code
: Int, error_msg
: String): CurlResponse
100 return new CurlResponseFailed(error_code
, error_msg
)
105 class CurlHTTPRequest
108 # Execute HTTP request with settings configured through attribute
109 redef fun execute
: CurlResponse
111 if not self.curl
.is_ok
then return answer_failure
(0, "Curl instance is not correctly initialized")
113 var success_response
: CurlResponseSuccess = new CurlResponseSuccess
115 var callback_receiver
: CurlCallbacks = success_response
116 if self.delegate
!= null then callback_receiver
= self.delegate
.as(not null)
120 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.header
)
121 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
123 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.body
)
124 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
126 var err_resp
= perform
127 if err_resp
!= null then return err_resp
129 var st_code
= self.curl
.prim_curl
.easy_getinfo_long
(new CURLInfoLong.response_code
)
130 if not st_code
== null then success_response
.status_code
= st_code
.response
132 return success_response
135 # Download to file given resource
136 fun download_to_file
(output_file_name
: nullable String): CurlResponse
138 var success_response
: CurlFileResponseSuccess = new CurlFileResponseSuccess
140 var callback_receiver
: CurlCallbacks = success_response
141 if self.delegate
!= null then callback_receiver
= self.delegate
.as(not null)
144 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.header
)
145 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
147 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.stream
)
148 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
150 var opt_name
:nullable String
151 if not output_file_name
== null then
152 opt_name
= output_file_name
153 else if not self.url
.substring
(self.url
.length-1
, self.url
.length
) == "/" then
154 opt_name
= self.url
.basename
("")
156 return answer_failure
(0, "Unable to extract file name, please specify one")
159 success_response
.i_file
= new OFile.open
(opt_name
.to_cstring
)
160 if not success_response
.i_file
.is_valid
then
161 success_response
.i_file
.close
162 return answer_failure
(0, "Unable to create associated file")
165 var err_resp
= perform
166 if err_resp
!= null then return err_resp
168 var st_code
= self.curl
.prim_curl
.easy_getinfo_long
(new CURLInfoLong.response_code
)
169 if not st_code
== null then success_response
.status_code
= st_code
.response
171 var speed
= self.curl
.prim_curl
.easy_getinfo_double
(new CURLInfoDouble.speed_download
)
172 if not speed
== null then success_response
.speed_download
= speed
.response
174 var size
= self.curl
.prim_curl
.easy_getinfo_double
(new CURLInfoDouble.size_download
)
175 if not size
== null then success_response
.size_download
= size
.response
177 var time
= self.curl
.prim_curl
.easy_getinfo_double
(new CURLInfoDouble.total_time
)
178 if not time
== null then success_response
.total_time
= time
.response
180 success_response
.i_file
.close
182 return success_response
186 # Callbacks Interface, allow you to manage in your way the different streams
187 interface CurlCallbacks
191 # Abstract Curl request response
192 abstract class CurlResponse
195 # Failed Response Class returned when errors during configuration are raised
196 class CurlResponseFailed
200 var error_msg
: String
202 init (err_code
: Int, err_msg
: String)
204 self.error_code
= err_code
205 self.error_msg
= err_msg
209 # Success Abstract Response Success Class
210 abstract class CurlResponseSuccessIntern
214 var headers
: HashMap[String, String] = new HashMap[String, String]
215 var status_code
: Int = 0
217 # Receive headers from request due to headers callback registering
218 redef fun header_callback
(line
: String)
220 var splitted
= line
.split_with
(':')
221 if splitted
.length
> 1 then
222 var key
= splitted
.shift
223 self.headers
[key
] = splitted
.to_s
228 # Success Response Class of a basic response
229 class CurlResponseSuccess
230 super CurlResponseSuccessIntern
232 var body_str
: String = ""
234 # Receive body from request due to body callback registering
235 redef fun body_callback
(line
: String)
237 self.body_str
= "{self.body_str}{line}"
241 # Success Response Class of a downloaded File
242 class CurlFileResponseSuccess
243 super CurlResponseSuccessIntern
245 var speed_download
: Int = 0
246 var size_download
: Int = 0
247 var total_time
: Int = 0
248 private var i_file
: nullable OFile = null
250 # Receive bytes stream from request due to stream callback registering
251 redef fun stream_callback
(buffer
: String, size
: Int, count
: Int)
253 self.i_file
.write
(buffer
, size
, count
)
257 # Pseudo map associating Strings to Strings,
258 # each key can have multiple associations
259 # and the order of insertion is important.
261 private var arr
= new Array[Couple[String, String]]
263 fun []=(k
, v
: String) do arr
.add
(new Couple[String, String](k
, v
))
265 fun [](k
: String): Array[String]
267 var res
= new Array[String]
268 for c
in arr
do if c
.first
== k
then res
.add
(c
.second
)
272 fun iterate
!each
(k
, v
: String)
277 each
(item
.first
, item
.second
)
282 # Convert Self to a single string used to post http fields
283 fun to_url_encoded
(curl
: CCurl): String
285 assert curlNotInitialized
: curl
.is_init
else
286 print
"to_url_encoded required a valid instance of CCurl Object."
289 var length
= self.length
296 if i
< length-1
then str
= "{str}&"
303 # Concatenate couple of 'key value' separated by 'sep' in Array
304 fun join_pairs
(sep
: String): Array[String]
306 var col
= new Array[String]
307 for k
, v
in self do col
.add
("{k}{sep}{v}")
311 fun length
: Int do return arr
.length
312 fun is_empty
: Bool do return arr
.is_empty