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.
24 protected var prim_curl
: CCurl
28 self.prim_curl
= new CCurl.easy_init
29 assert curlInstance
:self.prim_curl
.is_init
else
30 print
"Curl must be instancied to be used"
34 # Check for correct initialization
35 fun is_ok
: Bool do return self.prim_curl
.is_init
37 # Release Curl instance
38 fun destroy
do self.prim_curl
.easy_clean
44 var verbose
: Bool writable = false
45 private var curl
: nullable Curl = null
47 # Launch request method
48 fun execute
: CurlResponse is abstract
50 # Intern perform method, lowest level of request launching
51 private fun perform
: nullable CurlResponse
53 if not self.curl
.is_ok
then return answer_failure
(0, "Curl instance is not correctly initialized")
57 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.verbose
, self.verbose
)
58 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
60 err
= self.curl
.prim_curl
.easy_perform
61 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
66 # Intern method with return a failed answer with given code and message
67 private fun answer_failure
(error_code
: Int, error_msg
: String): CurlResponse
69 return new CurlResponseFailed(error_code
, error_msg
)
77 super CurlCallbacksRegisterIntern
80 var datas
: nullable HeaderMap writable = null
81 var headers
: nullable HeaderMap writable = null
83 # Set the user agent for all following HTTP requests
84 fun user_agent
=(name
: String)
86 curl
.prim_curl
.easy_setopt
(new CURLOption.user_agent
, name
)
89 init (url
: String, curl
: nullable Curl)
95 # Execute HTTP request with settings configured through attribute
98 if not self.curl
.is_ok
then return answer_failure
(0, "Curl instance is not correctly initialized")
100 var success_response
= new CurlResponseSuccess
101 var callback_receiver
: CurlCallbacks = success_response
102 if self.delegate
!= null then callback_receiver
= self.delegate
.as(not null)
106 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.follow_location
, 1)
107 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
109 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.url
, url
)
110 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
113 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.header
)
114 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
116 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.body
)
117 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
120 if self.headers
!= null then
121 var headers_joined
= self.headers
.join_pairs
(": ")
122 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.httpheader
, headers_joined
.to_curlslist
)
123 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
127 if self.datas
!= null then
128 var postdatas
= self.datas
.to_url_encoded
(self.curl
.prim_curl
)
129 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.postfields
, postdatas
)
130 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
133 var err_resp
= perform
134 if err_resp
!= null then return err_resp
136 var st_code
= self.curl
.prim_curl
.easy_getinfo_long
(new CURLInfoLong.response_code
)
137 if not st_code
== null then success_response
.status_code
= st_code
.response
139 return success_response
142 # Download to file given resource
143 fun download_to_file
(output_file_name
: nullable String): CurlResponse
145 var success_response
= new CurlFileResponseSuccess
147 var callback_receiver
: CurlCallbacks = success_response
148 if self.delegate
!= null then callback_receiver
= self.delegate
.as(not null)
152 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.follow_location
, 1)
153 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
155 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.url
, url
)
156 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
158 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.header
)
159 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
161 err
= self.curl
.prim_curl
.register_callback
(callback_receiver
, new CURLCallbackType.stream
)
162 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
165 if not output_file_name
== null then
166 opt_name
= output_file_name
167 else if not self.url
.substring
(self.url
.length-1
, self.url
.length
) == "/" then
168 opt_name
= self.url
.basename
("")
170 return answer_failure
(0, "Unable to extract file name, please specify one")
173 success_response
.i_file
= new OFile.open
(opt_name
.to_cstring
)
174 if not success_response
.i_file
.is_valid
then
175 success_response
.i_file
.close
176 return answer_failure
(0, "Unable to create associated file")
179 var err_resp
= perform
180 if err_resp
!= null then return err_resp
182 var st_code
= self.curl
.prim_curl
.easy_getinfo_long
(new CURLInfoLong.response_code
)
183 if not st_code
== null then success_response
.status_code
= st_code
.response
185 var speed
= self.curl
.prim_curl
.easy_getinfo_double
(new CURLInfoDouble.speed_download
)
186 if not speed
== null then success_response
.speed_download
= speed
.response
188 var size
= self.curl
.prim_curl
.easy_getinfo_double
(new CURLInfoDouble.size_download
)
189 if not size
== null then success_response
.size_download
= size
.response
191 var time
= self.curl
.prim_curl
.easy_getinfo_double
(new CURLInfoDouble.total_time
)
192 if not time
== null then success_response
.total_time
= time
.response
194 success_response
.i_file
.close
196 return success_response
201 class CurlMailRequest
205 var headers
: nullable HeaderMap writable = null
206 var headers_body
: nullable HeaderMap writable = null
207 var from
: nullable String writable = null
208 var to
: nullable Array[String] writable = null
209 var cc
: nullable Array[String] writable = null
210 var bcc
: nullable Array[String] writable = null
211 var subject
: nullable String writable = ""
212 var body
: nullable String writable = ""
213 private var supported_outgoing_protocol
: Array[String] = ["smtp", "smtps"]
215 init (curl
: nullable Curl)
220 # Helper method to add conventional space while building entire mail
221 private fun add_conventional_space
(str
: String):String do return "{str}\n" end
223 # Helper method to add pair values to mail content while building it (ex: "To:", "address@mail.com")
224 private fun add_pair_to_content
(str
: String, att
: String, val
: nullable String):String
226 if val
!= null then return "{str}{att}{val}\n"
227 return "{str}{att}\n"
230 # Helper method to add entire list of pairs to mail content
231 private fun add_pairs_to_content
(content
: String, pairs
: HeaderMap):String
233 for h_key
, h_val
in pairs
do content
= add_pair_to_content
(content
, h_key
, h_val
)
237 # Check for host and protocol availability
238 private fun is_supported_outgoing_protocol
(host
: String):CURLCode
240 var host_reach
= host
.split_with
("://")
241 if host_reach
.length
> 1 and supported_outgoing_protocol
.has
(host_reach
[0]) then return once
new CURLCode.ok
242 return once
new CURLCode.unsupported_protocol
245 # Configure server host and user credentials if needed.
246 fun set_outgoing_server
(host
: String, user
: nullable String, pwd
: nullable String):nullable CurlResponse
248 # Check Curl initialisation
249 if not self.curl
.is_ok
then return answer_failure
(0, "Curl instance is not correctly initialized")
254 err
= is_supported_outgoing_protocol
(host
)
255 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
256 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.url
, host
)
257 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
260 if not user
== null and not pwd
== null then
261 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.username
, user
)
262 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
263 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.password
, pwd
)
264 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
270 # Execute Mail request with settings configured through attribute
273 if not self.curl
.is_ok
then return answer_failure
(0, "Curl instance is not correctly initialized")
275 var success_response
= new CurlMailResponseSuccess
278 if self.headers
!= null then
279 content
= add_pairs_to_content
(content
, self.headers
.as(not null))
283 var g_rec
= new Array[String]
284 if self.to
!= null and self.to
.length
> 0 then
285 content
= add_pair_to_content
(content
, "To:", self.to
.join
(","))
286 g_rec
.append
(self.to
.as(not null))
288 if self.cc
!= null and self.cc
.length
> 0 then
289 content
= add_pair_to_content
(content
, "Cc:", self.cc
.join
(","))
290 g_rec
.append
(self.cc
.as(not null))
292 if self.bcc
!= null and self.bcc
.length
> 0 then g_rec
.append
(self.bcc
.as(not null))
294 if g_rec
.length
< 1 then return answer_failure
(0, "The mail recipients can not be empty")
298 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.follow_location
, 1)
299 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
301 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.mail_rcpt
, g_rec
.to_curlslist
)
302 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
305 if not self.from
== null then
306 content
= add_pair_to_content
(content
, "From:", self.from
)
307 err
= self.curl
.prim_curl
.easy_setopt
(new CURLOption.mail_from
, self.from
.as(not null))
308 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
312 content
= add_pair_to_content
(content
, "Subject:", self.subject
)
315 if self.headers_body
!= null then
316 content
= add_pairs_to_content
(content
, self.headers_body
.as(not null))
320 content
= add_conventional_space
(content
)
321 content
= add_pair_to_content
(content
, "", self.body
)
322 content
= add_conventional_space
(content
)
323 err
= self.curl
.prim_curl
.register_callback
(self, once
new CURLCallbackType.read
)
324 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
325 err
= self.curl
.prim_curl
.register_read_datas_callback
(self, content
)
326 if not err
.is_ok
then return answer_failure
(err
.to_i
, err
.to_s
)
328 var err_resp
= perform
329 if err_resp
!= null then return err_resp
331 return success_response
335 # Callbacks Interface, allow you to manage in your way the different streams
336 interface CurlCallbacks
340 # Callbacks attributes
341 abstract class CurlCallbacksRegisterIntern
342 var delegate
: nullable CurlCallbacks writable = null
345 # Abstract Curl request response
346 abstract class CurlResponse
349 # Failed Response Class returned when errors during configuration are raised
350 class CurlResponseFailed
354 var error_msg
: String
356 init (err_code
: Int, err_msg
: String)
358 self.error_code
= err_code
359 self.error_msg
= err_msg
363 # Success Abstract Response Success Class
364 abstract class CurlResponseSuccessIntern
368 var headers
= new HashMap[String, String]
370 # Receive headers from request due to headers callback registering
371 redef fun header_callback
(line
)
373 var splitted
= line
.split_with
(':')
374 if splitted
.length
> 1 then
375 var key
= splitted
.shift
376 self.headers
[key
] = splitted
.to_s
381 # Success Response Class of a basic response
382 class CurlResponseSuccess
383 super CurlResponseSuccessIntern
388 # Receive body from request due to body callback registering
389 redef fun body_callback
(line
: String)
391 self.body_str
= "{self.body_str}{line}"
395 # Success Response Class of mail request
396 class CurlMailResponseSuccess
397 super CurlResponseSuccessIntern
400 # Success Response Class of a downloaded File
401 class CurlFileResponseSuccess
402 super CurlResponseSuccessIntern
405 var speed_download
= 0
406 var size_download
= 0
408 private var i_file
: nullable OFile = null
410 # Receive bytes stream from request due to stream callback registering
411 redef fun stream_callback
(buffer
, size
, count
)
413 self.i_file
.write
(buffer
, size
, count
)
417 # Pseudo map associating Strings to Strings,
418 # each key can have multiple associations
419 # and the order of insertion is important.
421 private var arr
= new Array[Couple[String, String]]
423 fun []=(k
, v
: String) do arr
.add
(new Couple[String, String](k
, v
))
425 fun [](k
: String): Array[String]
427 var res
= new Array[String]
428 for c
in arr
do if c
.first
== k
then res
.add
(c
.second
)
432 fun iterator
: MapIterator[String, String] do return new HeaderMapIterator(self)
434 # Convert Self to a single string used to post http fields
435 fun to_url_encoded
(curl
: CCurl): String
437 assert curlNotInitialized
: curl
.is_init
else
438 print
"to_url_encoded required a valid instance of CCurl Object."
441 var length
= self.length
448 if i
< length-1
then str
= "{str}&"
455 # Concatenate couple of 'key value' separated by 'sep' in Array
456 fun join_pairs
(sep
: String): Array[String]
458 var col
= new Array[String]
459 for k
, v
in self do col
.add
("{k}{sep}{v}")
463 fun length
: Int do return arr
.length
464 fun is_empty
: Bool do return arr
.is_empty
467 class HeaderMapIterator
468 super MapIterator[String, String]
470 private var iterator
: Iterator[Couple[String, String]]
471 init(map
: HeaderMap) do self.iterator
= map
.arr
.iterator
473 redef fun is_ok
do return self.iterator
.is_ok
474 redef fun next
do self.iterator
.next
475 redef fun item
do return self.iterator
.item
.second
476 redef fun key
do return self.iterator
.item
.first