From: Jean Privat Date: Wed, 10 Jun 2015 01:47:05 +0000 (-0400) Subject: Merge: Work on the Curl module X-Git-Tag: v0.7.6~52 X-Git-Url: http://nitlanguage.org?hp=6886542a95de9eda99ecb65e95bd69dd803398ca Merge: Work on the Curl module Changes: * Simplify callbacks so they do not break light FFI only engines. * Remove about 8 classes and 3 C structures. * Remove the custom implementation of NativeFile. * Update style to the latest best practices. Note that it was one of the first large module based on the FFI, a lot has changed since then. * Revamp and rename CurlMail, its API is now nicer to use: ~~~ import curl var mail = new CurlMail("sender@example.org", ["to@example.org"], cc=["bob@example.org"]) mail.subject = "Hello From My Nit Program" mail.body = "

Here you can write HTML stuff.

" mail.headers_body["Content-Type:"] = """text/html; charset="UTF-8"""" mail.headers_body["Content-Transfer-Encoding:"] = "quoted-printable" # Set mail server var error = mail.set_outgoing_server("smtps://smtp.example.org:465", "user@example.org", "mypassword") assert error == null # Send error = mail.execute assert error == null ~~~ Thoughts on what's to do next: * Extract the request API so Curl is only an implementation. The abstract API could also be implemented on Android where we can use Java services instead. * Clean up `CurlHTTPRequest`. * Use `Error` and differentiate between internal Curl errors and normal transfert failures. * Replace the C structure linked to reading data and add tests for its usage. * Add dead simple services: `Text::download: nullable String`, `Text::download_to_file(filepath: Text)`, `Text::download_to_stream(stream: Writer)` or something like that. * Make more classes of native_curl private as we cannot make the module private because it defines the user-customizable callbacks. Some modules (neo4j) use what would be private, so it requires updating them also. --- Should fix #936 and other problems when importing but not using Curl. Also helps #1443 correctly report unsupported programs. Pull-Request: #1445 Reviewed-by: Alexandre Terrasa Reviewed-by: Lucas Bajolet Reviewed-by: Jean Privat --- diff --git a/contrib/benitlux/src/benitlux_daily.nit b/contrib/benitlux/src/benitlux_daily.nit index bb6d1fa..1c1f4d6 100644 --- a/contrib/benitlux/src/benitlux_daily.nit +++ b/contrib/benitlux/src/benitlux_daily.nit @@ -129,14 +129,11 @@ class Benitlux # Fetch the Web page at `url` fun download_html_page: String do - var curl = new Curl - - var request = new CurlHTTPRequest(url, curl) + var request = new CurlHTTPRequest(url) var response = request.execute if response isa CurlResponseSuccess then var body = response.body_str - curl.destroy return body else if response isa CurlResponseFailed then print "Failed downloading URL '{url}' with: {response.error_msg} ({response.error_code})" diff --git a/contrib/rss_downloader/src/rss_downloader.nit b/contrib/rss_downloader/src/rss_downloader.nit index eacaf79..ed5fc72 100644 --- a/contrib/rss_downloader/src/rss_downloader.nit +++ b/contrib/rss_downloader/src/rss_downloader.nit @@ -62,16 +62,12 @@ class Element # Download this element to `path` fun download_to(path: Text) do - var curl = new Curl - - var request = new CurlHTTPRequest(link, curl) + var request = new CurlHTTPRequest(link) var response = request.download_to_file(path.to_s) - if response isa CurlFileResponseSuccess then - curl.destroy - else if response isa CurlResponseFailed then + if response isa CurlResponseFailed then sys.stderr.write "Failed downloading URL '{link}' with: {response.error_msg} ({response.error_code})\n" - else abort + end end # Get an unique identifier for this element, uses `Config::unique_pattern` @@ -232,16 +228,13 @@ redef class Text # Get the content of the RSS feed at `self` fun fetch_rss_content: Text do - var curl = new Curl - if sys.verbose then print "\n# Downloading RSS file from '{self}'" - var request = new CurlHTTPRequest(to_s, curl) + var request = new CurlHTTPRequest(to_s) var response = request.execute if response isa CurlResponseSuccess then var body = response.body_str - curl.destroy if sys.verbose then print "Download successful" return body else if response isa CurlResponseFailed then diff --git a/lib/curl/curl.nit b/lib/curl/curl.nit index dbfc157..b68623b 100644 --- a/lib/curl/curl.nit +++ b/lib/curl/curl.nit @@ -14,37 +14,39 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Network functionnalities based on Curl_c module. +# Curl services: `CurlHTTPRequest` and `CurlMail` module curl -import curl_c +import native_curl -# Top level of Curl +redef class Sys + # Shared Curl library handle + # + # Usually, you do not have to use this attribute, it instancied by `CurlHTTPRequest` and `CurlMail`. + # But in some cases you may want to finalize it to free some small resources. + # However, if Curl services are needed once again, this attribute must be manually set. + var curl: Curl = new Curl is lazy, writable +end + +# Curl library handle, it is initialized and released with this class class Curl - protected var prim_curl = new CCurl.easy_init + super FinalizableOnce - init - do - assert curlInstance:self.prim_curl.is_init else - print "Curl must be instancied to be used" - end - end + private var native = new NativeCurl.easy_init # Check for correct initialization - fun is_ok: Bool do return self.prim_curl.is_init + fun is_ok: Bool do return self.native.is_init - # Release Curl instance - fun destroy do self.prim_curl.easy_clean + redef fun finalize_once do if is_ok then native.easy_clean end # CURL Request class CurlRequest - var verbose: Bool = false is writable - private var curl: nullable Curl = null + private var curl: Curl = sys.curl - # Launch request method - fun execute: CurlResponse is abstract + # Shall this request be verbose? + var verbose: Bool = false is writable # Intern perform method, lowest level of request launching private fun perform: nullable CurlResponseFailed @@ -53,10 +55,10 @@ class CurlRequest var err - err = self.curl.prim_curl.easy_setopt(new CURLOption.verbose, self.verbose) + err = self.curl.native.easy_setopt(new CURLOption.verbose, self.verbose) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.easy_perform + err = self.curl.native.easy_perform if not err.is_ok then return answer_failure(err.to_i, err.to_s) return null @@ -72,26 +74,18 @@ end # CURL HTTP Request class CurlHTTPRequest super CurlRequest - super CCurlCallbacks - super CurlCallbacksRegisterIntern + super NativeCurlCallbacks var url: String - var datas: nullable HeaderMap = null is writable - var headers: nullable HeaderMap = null is writable + var datas: nullable HeaderMap is writable + var headers: nullable HeaderMap is writable + var delegate: nullable CurlCallbacks is writable # Set the user agent for all following HTTP requests - fun user_agent=(name: String) - do - curl.prim_curl.easy_setopt(new CURLOption.user_agent, name) - end - - init (url: String, curl: nullable Curl) is old_style_init do - self.url = url - self.curl = curl - end + var user_agent: nullable String is writable # Execute HTTP request with settings configured through attribute - redef fun execute + fun execute: CurlResponse do if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized") @@ -101,38 +95,46 @@ class CurlHTTPRequest var err - err = self.curl.prim_curl.easy_setopt(new CURLOption.follow_location, 1) + err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.easy_setopt(new CURLOption.url, url) + err = self.curl.native.easy_setopt(new CURLOption.url, url) if not err.is_ok then return answer_failure(err.to_i, err.to_s) + var user_agent = user_agent + if user_agent != null then + err = curl.native.easy_setopt(new CURLOption.user_agent, user_agent) + if not err.is_ok then return answer_failure(err.to_i, err.to_s) + end + # Callbacks - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header) + err = self.curl.native.register_callback_header(callback_receiver) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.body) + err = self.curl.native.register_callback_body(callback_receiver) if not err.is_ok then return answer_failure(err.to_i, err.to_s) # HTTP Header - if self.headers != null then - var headers_joined = self.headers.join_pairs(": ") - err = self.curl.prim_curl.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist) + var headers = self.headers + if headers != null then + var headers_joined = headers.join_pairs(": ") + err = self.curl.native.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist) if not err.is_ok then return answer_failure(err.to_i, err.to_s) end # Datas - if self.datas != null then - var postdatas = self.datas.to_url_encoded(self.curl.prim_curl) - err = self.curl.prim_curl.easy_setopt(new CURLOption.postfields, postdatas) + var datas = self.datas + if datas != null then + var postdatas = datas.to_url_encoded(self.curl) + err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas) if not err.is_ok then return answer_failure(err.to_i, err.to_s) end var err_resp = perform if err_resp != null then return err_resp - var st_code = self.curl.prim_curl.easy_getinfo_long(new CURLInfoLong.response_code) - if not st_code == null then success_response.status_code = st_code.response + var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code) + if not st_code == null then success_response.status_code = st_code return success_response end @@ -147,16 +149,16 @@ class CurlHTTPRequest var err - err = self.curl.prim_curl.easy_setopt(new CURLOption.follow_location, 1) + err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.easy_setopt(new CURLOption.url, url) + err = self.curl.native.easy_setopt(new CURLOption.url, url) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header) + err = self.curl.native.register_callback_header(callback_receiver) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.stream) + err = self.curl.native.register_callback_stream(callback_receiver) if not err.is_ok then return answer_failure(err.to_i, err.to_s) var opt_name @@ -168,71 +170,109 @@ class CurlHTTPRequest return answer_failure(0, "Unable to extract file name, please specify one") end - success_response.i_file = new OFile.open(opt_name.to_cstring) - if not success_response.i_file.is_valid then - success_response.i_file.close + success_response.file = new FileWriter.open(opt_name) + if not success_response.file.is_writable then return answer_failure(0, "Unable to create associated file") end var err_resp = perform if err_resp != null then return err_resp - var st_code = self.curl.prim_curl.easy_getinfo_long(new CURLInfoLong.response_code) - if not st_code == null then success_response.status_code = st_code.response + var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code) + if not st_code == null then success_response.status_code = st_code - var speed = self.curl.prim_curl.easy_getinfo_double(new CURLInfoDouble.speed_download) - if not speed == null then success_response.speed_download = speed.response + var speed = self.curl.native.easy_getinfo_double(new CURLInfoDouble.speed_download) + if not speed == null then success_response.speed_download = speed - var size = self.curl.prim_curl.easy_getinfo_double(new CURLInfoDouble.size_download) - if not size == null then success_response.size_download = size.response + var size = self.curl.native.easy_getinfo_double(new CURLInfoDouble.size_download) + if not size == null then success_response.size_download = size - var time = self.curl.prim_curl.easy_getinfo_double(new CURLInfoDouble.total_time) - if not time == null then success_response.total_time = time.response + var time = self.curl.native.easy_getinfo_double(new CURLInfoDouble.total_time) + if not time == null then success_response.total_time = time - success_response.i_file.close + success_response.file.close return success_response end end # CURL Mail Request -class CurlMailRequest +# +# ~~~ +# # Craft mail +# var mail = new CurlMail("sender@example.org", +# to=["to@example.org"], cc=["bob@example.org"]) +# +# mail.headers_body["Content-Type:"] = """text/html; charset="UTF-8"""" +# mail.headers_body["Content-Transfer-Encoding:"] = "quoted-printable" +# +# mail.body = "

Here you can write HTML stuff.

" +# mail.subject = "Hello From My Nit Program" +# +# # Set mail server +# var error = mail.set_outgoing_server("smtps://smtp.example.org:465", +# "user@example.org", "mypassword") +# if error != null then +# print "Mail Server Error: {error}" +# exit 0 +# end +# +# # Send +# error = mail.execute +# if error != null then +# print "Transfer Error: {error}" +# exit 0 +# end +# ~~~ +class CurlMail super CurlRequest - super CCurlCallbacks - - var headers: nullable HeaderMap = null is writable - var headers_body: nullable HeaderMap = null is writable - var from: nullable String = null is writable - var to: nullable Array[String] = null is writable - var cc: nullable Array[String] = null is writable - var bcc: nullable Array[String] = null is writable - var subject: nullable String = "" is writable - var body: nullable String = "" is writable - private var supported_outgoing_protocol: Array[String] = ["smtp", "smtps"] - - init (curl: nullable Curl) is old_style_init do - self.curl = curl - end + super NativeCurlCallbacks + + # Address of the sender + var from: nullable String is writable + + # Main recipients + var to: nullable Array[String] is writable + + # Subject line + var subject: nullable String is writable + + # Text content + var body: nullable String is writable + + # CC recipients + var cc: nullable Array[String] is writable + + # BCC recipients (hidden from other recipients) + var bcc: nullable Array[String] is writable - # Helper method to add conventional space while building entire mail - private fun add_conventional_space(str: String):String do return "{str}\n" end + # HTTP header + var headers = new HeaderMap is lazy, writable + + # Content header + var headers_body = new HeaderMap is lazy, writable + + # Protocols supported to send mail to a server + # + # Default value at `["smtp", "smtps"]` + var supported_outgoing_protocol: Array[String] = ["smtp", "smtps"] # Helper method to add pair values to mail content while building it (ex: "To:", "address@mail.com") - private fun add_pair_to_content(str: String, att: String, val: nullable String):String + private fun add_pair_to_content(str: String, att: String, val: nullable String): String do if val != null then return "{str}{att}{val}\n" return "{str}{att}\n" end # Helper method to add entire list of pairs to mail content - private fun add_pairs_to_content(content: String, pairs: HeaderMap):String + private fun add_pairs_to_content(content: String, pairs: HeaderMap): String do for h_key, h_val in pairs do content = add_pair_to_content(content, h_key, h_val) return content end # Check for host and protocol availability - private fun is_supported_outgoing_protocol(host: String):CURLCode + private fun is_supported_outgoing_protocol(host: String): CURLCode do var host_reach = host.split_with("://") if host_reach.length > 1 and supported_outgoing_protocol.has(host_reach[0]) then return once new CURLCode.ok @@ -250,14 +290,16 @@ class CurlMailRequest # Host & Protocol err = is_supported_outgoing_protocol(host) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.easy_setopt(new CURLOption.url, host) + + err = self.curl.native.easy_setopt(new CURLOption.url, host) if not err.is_ok then return answer_failure(err.to_i, err.to_s) # Credentials if not user == null and not pwd == null then - err = self.curl.prim_curl.easy_setopt(new CURLOption.username, user) + err = self.curl.native.easy_setopt(new CURLOption.username, user) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.easy_setopt(new CURLOption.password, pwd) + + err = self.curl.native.easy_setopt(new CURLOption.password, pwd) if not err.is_ok then return answer_failure(err.to_i, err.to_s) end @@ -265,78 +307,88 @@ class CurlMailRequest end # Execute Mail request with settings configured through attribute - redef fun execute + fun execute: nullable CurlResponseFailed do if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized") - var success_response = new CurlMailResponseSuccess - var content = "" + var lines = new Array[String] + # Headers - if self.headers != null then - content = add_pairs_to_content(content, self.headers.as(not null)) + var headers = self.headers + if not headers.is_empty then + for k, v in headers do lines.add "{k}{v}" end # Recipients - var g_rec = new Array[String] - if self.to != null and self.to.length > 0 then - content = add_pair_to_content(content, "To:", self.to.join(",")) - g_rec.append(self.to.as(not null)) + var all_recipients = new Array[String] + var to = self.to + if to != null and to.length > 0 then + lines.add "To:{to.join(",")}" + all_recipients.append to end - if self.cc != null and self.cc.length > 0 then - content = add_pair_to_content(content, "Cc:", self.cc.join(",")) - g_rec.append(self.cc.as(not null)) + + var cc = self.cc + if cc != null and cc.length > 0 then + lines.add "Cc:{cc.join(",")}" + all_recipients.append cc end - if self.bcc != null and self.bcc.length > 0 then g_rec.append(self.bcc.as(not null)) - if g_rec.length < 1 then return answer_failure(0, "The mail recipients can not be empty") + var bcc = self.bcc + if bcc != null and bcc.length > 0 then all_recipients.append bcc - var err + if all_recipients.is_empty then return answer_failure(0, "There must be at lease one recipient") - err = self.curl.prim_curl.easy_setopt(new CURLOption.follow_location, 1) + var err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.easy_setopt(new CURLOption.mail_rcpt, g_rec.to_curlslist) + err = self.curl.native.easy_setopt(new CURLOption.mail_rcpt, all_recipients.to_curlslist) if not err.is_ok then return answer_failure(err.to_i, err.to_s) # From - if not self.from == null then - content = add_pair_to_content(content, "From:", self.from) - err = self.curl.prim_curl.easy_setopt(new CURLOption.mail_from, self.from.as(not null)) + var from = self.from + if not from == null then + lines.add "From:{from}" + + err = self.curl.native.easy_setopt(new CURLOption.mail_from, from) if not err.is_ok then return answer_failure(err.to_i, err.to_s) end # Subject - content = add_pair_to_content(content, "Subject:", self.subject) + var subject = self.subject + if subject == null then subject = "" # Default + lines.add "Subject: {subject}" # Headers body - if self.headers_body != null then - content = add_pairs_to_content(content, self.headers_body.as(not null)) + var headers_body = self.headers_body + if not headers_body.is_empty then + for k, v in headers_body do lines.add "{k}{v}" end # Body - content = add_conventional_space(content) - content = add_pair_to_content(content, "", self.body) - content = add_conventional_space(content) - err = self.curl.prim_curl.register_callback(self, once new CURLCallbackType.read) + var body = self.body + if body == null then body = "" # Default + + lines.add "" + lines.add body + lines.add "" + + err = self.curl.native.register_callback_read(self) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.register_read_datas_callback(self, content) + + var content = lines.join("\n") + err = self.curl.native.register_read_datas_callback(self, content) if not err.is_ok then return answer_failure(err.to_i, err.to_s) var err_resp = perform if err_resp != null then return err_resp - return success_response + return null end end # Callbacks Interface, allow you to manage in your way the different streams interface CurlCallbacks - super CCurlCallbacks -end - -# Callbacks attributes -abstract class CurlCallbacksRegisterIntern - var delegate: nullable CurlCallbacks = null is writable + super NativeCurlCallbacks end # Abstract Curl request response @@ -349,6 +401,8 @@ class CurlResponseFailed var error_code: Int var error_msg: String + + redef fun to_s do return "{error_msg} ({error_code})" end # Success Abstract Response Success Class @@ -382,64 +436,63 @@ class CurlResponseSuccess end end -# Success Response Class of mail request -class CurlMailResponseSuccess - super CurlResponseSuccessIntern -end - # Success Response Class of a downloaded File class CurlFileResponseSuccess super CurlResponseSuccessIntern var status_code = 0 - var speed_download = 0 - var size_download = 0 - var total_time = 0 - private var i_file: nullable OFile = null + var speed_download = 0.0 + var size_download = 0.0 + var total_time = 0.0 + private var file: nullable FileWriter = null # Receive bytes stream from request due to stream callback registering - redef fun stream_callback(buffer, size, count) + redef fun stream_callback(buffer) do - self.i_file.write(buffer, size, count) + file.write buffer end end -# Pseudo map associating Strings to Strings, -# each key can have multiple associations -# and the order of insertion is important. +# Pseudo map associating `String` to `String` for HTTP exchanges +# +# This structure differs from `Map` as each key can have multiple associations +# and the order of insertion is important to some services. class HeaderMap - private var arr = new Array[Couple[String, String]] + private var array = new Array[Couple[String, String]] - fun []=(k, v: String) do arr.add(new Couple[String, String](k, v)) + # Add a `value` associated to `key` + fun []=(key, value: String) + do + array.add new Couple[String, String](key, value) + end + # Get a list of the keys associated to `key` fun [](k: String): Array[String] do var res = new Array[String] - for c in arr do if c.first == k then res.add(c.second) + for c in array do if c.first == k then res.add c.second return res end + # Iterate over all the associations in `self` fun iterator: MapIterator[String, String] do return new HeaderMapIterator(self) - # Convert Self to a single string used to post http fields - fun to_url_encoded(curl: CCurl): String + # Get `self` as a single string for HTTP POST + # + # Require: `curl.is_ok` + fun to_url_encoded(curl: Curl): String do - assert curlNotInitialized: curl.is_init else - print "to_url_encoded required a valid instance of CCurl Object." - end - var str = "" - var length = self.length - var i = 0 + assert curl.is_ok + + var lines = new Array[String] for k, v in self do - if k.length > 0 then - k = curl.escape(k) - v = curl.escape(v) - str = "{str}{k}={v}" - if i < length-1 then str = "{str}&" - end - i += 1 + if k.length == 0 then continue + + k = curl.native.escape(k) + v = curl.native.escape(v) + lines.add "{k}={v}" end - return str + return lines.join("&") end # Concatenate couple of 'key value' separated by 'sep' in Array @@ -450,15 +503,18 @@ class HeaderMap return col end - fun length: Int do return arr.length - fun is_empty: Bool do return arr.is_empty + # Number of values in `self` + fun length: Int do return array.length + + # Is this map empty? + fun is_empty: Bool do return array.is_empty end -class HeaderMapIterator +private class HeaderMapIterator super MapIterator[String, String] - private var iterator: Iterator[Couple[String, String]] - init(map: HeaderMap) is old_style_init do self.iterator = map.arr.iterator + var map: HeaderMap + var iterator: Iterator[Couple[String, String]] = map.array.iterator is lazy redef fun is_ok do return self.iterator.is_ok redef fun next do self.iterator.next diff --git a/lib/curl/examples/curl_http.nit b/lib/curl/examples/curl_http.nit index 948ba38..d19ee96 100644 --- a/lib/curl/examples/curl_http.nit +++ b/lib/curl/examples/curl_http.nit @@ -14,100 +14,91 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Sample of the Curl module. +# Example use of the Curl module module curl_http import curl -# Small class to represent an Http Fetcher +# Custom delegate to receive callbacks from a Curl transfer class MyHttpFetcher super CurlCallbacks - var curl: Curl - var our_body: String = "" + # Body of the downloaded file + var fetched_body = "" - init(curl: Curl) do self.curl = curl - - # Release curl object - fun destroy do self.curl.destroy - - # Header callback redef fun header_callback(line) do # We keep this callback silent for testing purposes - #if not line.has_prefix("Date:") then print "Header_callback : {line}" + #if not line.has_prefix("Date:") then print "Header_callback: {line}" end - # Body callback - redef fun body_callback(line) do self.our_body = "{self.our_body}{line}" + redef fun body_callback(line) do self.fetched_body += line - # Stream callback - Cf : No one is registered - redef fun stream_callback(buffer, size, count) do print "Stream_callback : {buffer} - {size} - {count}" + redef fun stream_callback(buffer) do print "Stream_callback: {buffer}" end +private fun print_usage do print "Usage: curl_http [POST|GET|GET_FILE] url" -# Program if args.length < 2 then - print "Usage: curl_http " -else - var curl = new Curl - var url = args[1] - var request = new CurlHTTPRequest(url, curl) + print_usage + exit 1 +end + +var url = args[1] +var request = new CurlHTTPRequest(url) +request.verbose = false # Set to `true` to debug +if args[0] == "GET" then # HTTP Get Request - if args[0] == "GET" then - request.verbose = false - var getResponse = request.execute - - if getResponse isa CurlResponseSuccess then - print "Status code : {getResponse.status_code}" - print "Body : {getResponse.body_str}" - else if getResponse isa CurlResponseFailed then - print "Error code : {getResponse.error_code}" - print "Error msg : {getResponse.error_msg}" - end + var response = request.execute + + if response isa CurlResponseSuccess then + print "Status code: {response.status_code}" + print "Body: {response.body_str}" + else if response isa CurlResponseFailed then + print "Error code: {response.error_code}" + print "Error msg: {response.error_msg}" + end +else if args[0] == "POST" then # HTTP Post Request - else if args[0] == "POST" then - var myHttpFetcher = new MyHttpFetcher(curl) - request.delegate = myHttpFetcher - - var postDatas = new HeaderMap - postDatas["Bugs Bunny"] = "Daffy Duck" - postDatas["Batman"] = "Robin likes special characters @#ùà!è§'(\"é&://,;<>∞~*" - postDatas["Batman"] = "Yes you can set multiple identical keys, but APACHE will consider only once, the last one" - request.datas = postDatas - request.verbose = false - var postResponse = request.execute - - print "Our body from the callback : {myHttpFetcher.our_body}" - - if postResponse isa CurlResponseSuccess then - print "*** Answer ***" - print "Status code : {postResponse.status_code}" - print "Body should be empty, because we decided to manage callbacks : {postResponse.body_str.length}" - else if postResponse isa CurlResponseFailed then - print "Error code : {postResponse.error_code}" - print "Error msg : {postResponse.error_msg}" - end + var my_http_fetcher = new MyHttpFetcher + request.delegate = my_http_fetcher + + var post_datas = new HeaderMap + post_datas["Bugs Bunny"] = "Daffy Duck" + post_datas["Batman"] = "Robin likes special characters @#ùà!è§'(\"é&://,;<>∞~*" + post_datas["Batman"] = "Yes you can set multiple identical keys, but APACHE will consider only one, the last one" + request.datas = post_datas + var response = request.execute + + print "Our body from the callback: {my_http_fetcher.fetched_body}" + + if response isa CurlResponseSuccess then + print "*** Answer ***" + print "Status code: {response.status_code}" + print "Body should be empty, because we decided to manage callbacks: {response.body_str.length}" + else if response isa CurlResponseFailed then + print "Error code: {response.error_code}" + print "Error msg: {response.error_msg}" + end +else if args[0] == "GET_FILE" then # HTTP Get to file Request - else if args[0] == "GET_FILE" then - var headers = new HeaderMap - headers["Accept"] = "Moo" - request.headers = headers - request.verbose = false - var downloadResponse = request.download_to_file(null) - - if downloadResponse isa CurlFileResponseSuccess then - print "*** Answer ***" - print "Status code : {downloadResponse.status_code}" - print "Size downloaded : {downloadResponse.size_download}" - else if downloadResponse isa CurlResponseFailed then - print "Error code : {downloadResponse.error_code}" - print "Error msg : {downloadResponse.error_msg}" - end - # Program logic - else - print "Usage : Method[POST, GET, GET_FILE]" + var headers = new HeaderMap + headers["Accept"] = "Moo" + request.headers = headers + var response = request.download_to_file(null) + + if response isa CurlFileResponseSuccess then + print "*** Answer ***" + print "Status code: {response.status_code}" + print "Size downloaded: {response.size_download}" + else if response isa CurlResponseFailed then + print "Error code: {response.error_code}" + print "Error msg: {response.error_msg}" end + +else + print_usage + exit 1 end diff --git a/lib/curl/examples/curl_mail.nit b/lib/curl/examples/curl_mail.nit deleted file mode 100644 index d791611..0000000 --- a/lib/curl/examples/curl_mail.nit +++ /dev/null @@ -1,59 +0,0 @@ -# This file is part of NIT ( http://www.nitlanguage.org ). -# -# Copyright 2013 Matthieu Lucas -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Mail sender sample using the Curl module -module curl_mail - -import curl - -var curl = new Curl -var mail_request = new CurlMailRequest(curl) - -# Networks -var response: nullable CurlResponse = mail_request.set_outgoing_server("smtps://smtp.example.org:465", "user@example.org", "mypassword") -if response isa CurlResponseFailed then - print "Error code : {response.error_code}" - print "Error msg : {response.error_msg}" -end - -# Headers -mail_request.from = "Billy Bob" -mail_request.to = ["user@example.org"] -mail_request.cc = ["bob@example.org"] -mail_request.bcc = null - -var headers_body = new HeaderMap -headers_body["Content-Type:"] = "text/html; charset=\"UTF-8\"" -headers_body["Content-Transfer-Encoding:"] = "quoted-printable" -mail_request.headers_body = headers_body - -# Content -mail_request.body = "

Here you can write HTML stuff.

" -mail_request.subject = "Hello From My Nit Program" - -# Others -mail_request.verbose = false - -# Send mail -response = mail_request.execute -if response isa CurlResponseFailed then - print "Error code : {response.error_code}" - print "Error msg : {response.error_msg}" -else if response isa CurlMailResponseSuccess then - print "Mail Sent" -else - print "Unknown Curl Response type" -end diff --git a/lib/curl/curl_c.nit b/lib/curl/native_curl.nit similarity index 67% rename from lib/curl/curl_c.nit rename to lib/curl/native_curl.nit index fe0a446..29ab34d 100644 --- a/lib/curl/curl_c.nit +++ b/lib/curl/native_curl.nit @@ -15,23 +15,14 @@ # limitations under the License. # Binding of C libCurl which allow us to interact with network. -module curl_c is pkgconfig("libcurl") +module native_curl is pkgconfig "libcurl" + +intrude import standard::file +import standard in "C header" `{ #include - typedef enum { - CURLcallbackTypeHeader, - CURLcallbackTypeBody, - CURLcallbackTypeStream, - CURLcallbackTypeRead, - } CURLcallbackType; - - typedef struct { - CCurlCallbacks delegate; - CURLcallbackType type; - } CURLCallbackDatas; - typedef struct { char *data; int len; @@ -44,25 +35,6 @@ in "C body" `{ #include #include - // Callbacks method for Header, Body, Stream. - size_t nit_curl_callback_func(void *buffer, size_t size, size_t count, CURLCallbackDatas *datas){ - if(datas->type == CURLcallbackTypeHeader){ - char *line_c = (char*)buffer; - String line_o = NativeString_to_s_with_copy(line_c); - CCurlCallbacks_header_callback(datas->delegate, line_o); - } - else if(datas->type == CURLcallbackTypeBody){ - char *line_c = (char*)buffer; - String line_o = NativeString_to_s_with_copy(line_c); - CCurlCallbacks_body_callback(datas->delegate, line_o); - } - else if(datas->type == CURLcallbackTypeStream){ - char *line_c = (char*)buffer; - String line_o = NativeString_to_s(line_c); - CCurlCallbacks_stream_callback(datas->delegate, line_o, size, count); - } - return count; - } // Callback method to read datas from buffer. size_t nit_curl_callback_read_func(void *buffer, size_t size, size_t count, CURLCallbackReadDatas *datas){ int len = datas->len - datas->pos; @@ -73,119 +45,155 @@ in "C body" `{ } `} +redef extern class NativeString + private fun native_callback_header(size, count: Int, target: NativeCurlCallbacks): Int + do + target.header_callback to_s_with_length(size*count) + + # FIXME we probably should return a value from the user + return count + end + + private fun native_callback_body(size, count: Int, target: NativeCurlCallbacks): Int + do + target.body_callback to_s_with_length(size*count) + + return count + end + + private fun native_callback_stream(size, count: Int, target: NativeCurlCallbacks): Int + do + target.stream_callback to_s_with_length(size*count) + + return count + end +end + # CURL Extern Type, reproduce CURL low level behaviors -extern class CCurl `{ CURL * `} +extern class NativeCurl `{ CURL * `} # Constructor, CURL low level initializer new easy_init `{ return curl_easy_init(); `} + # Check for correct initialization - fun is_init:Bool `{ return (self != NULL); `} + fun is_init: Bool `{ return (self != NULL); `} + # Easy Clean / Release CURL instance fun easy_clean `{ curl_easy_cleanup( self ); `} + # Perform the transfer described by setted options - fun easy_perform:CURLCode `{ return curl_easy_perform( self ); `} - # Set options to tell CURL how to behave. Obj parameter type can be Int, Bool, String, OFile, CURLSList. - fun easy_setopt(opt: CURLOption, obj: Object):CURLCode + fun easy_perform: CURLCode `{ return curl_easy_perform( self ); `} + + # Set options to tell CURL how to behave. Obj parameter type can be Int, Bool, String, FileWriter, CURLSList. + fun easy_setopt(opt: CURLOption, obj: Object): CURLCode do - if obj isa Int then return i_setopt_int(opt, obj) - if obj isa Bool and obj == true then return i_setopt_int(opt, 1) - if obj isa Bool and obj == false then return i_setopt_int(opt, 0) - if obj isa String then return i_setopt_string(opt, obj) - if obj isa OFile then return i_setopt_file(opt, obj) - if obj isa CURLSList then return i_setopt_slist(opt, obj) + if obj isa Int then return native_setopt_int(opt, obj) + if obj isa Bool and obj == true then return native_setopt_int(opt, 1) + if obj isa Bool and obj == false then return native_setopt_int(opt, 0) + if obj isa String then return native_setopt_string(opt, obj) + if obj isa FileWriter then return native_setopt_file(opt, obj._file.as(not null)) + if obj isa CURLSList then return native_setopt_slist(opt, obj) return once new CURLCode.unknown_option end - # Internal method to set options to CURL using OFile parameter. - private fun i_setopt_file(opt: CURLOption, fl: OFile):CURLCode `{ return curl_easy_setopt( self, opt, fl); `} + + # Internal method to set options to CURL using NativeFile parameter. + private fun native_setopt_file(opt: CURLOption, file: NativeFile): CURLCode `{ + return curl_easy_setopt( self, opt, file); + `} + # Internal method to set options to CURL using Int parameter. - private fun i_setopt_int(opt: CURLOption, num: Int):CURLCode `{ return curl_easy_setopt( self, opt, num); `} + private fun native_setopt_int(opt: CURLOption, num: Int): CURLCode `{ return curl_easy_setopt( self, opt, num); `} + # Internal method to set options to CURL using CURLSList parameter. - private fun i_setopt_slist(opt: CURLOption, list: CURLSList):CURLCode `{ return curl_easy_setopt( self, opt, list); `} + private fun native_setopt_slist(opt: CURLOption, list: CURLSList): CURLCode `{ return curl_easy_setopt( self, opt, list); `} + # Internal method to set options to CURL using String parameter. - private fun i_setopt_string(opt: CURLOption, str: String):CURLCode import String.to_cstring `{ + private fun native_setopt_string(opt: CURLOption, str: String): CURLCode import String.to_cstring `{ char *rStr = String_to_cstring(str); return curl_easy_setopt( self, opt, rStr); `} + # Request Chars internal information from the CURL session - fun easy_getinfo_chars(opt: CURLInfoChars):nullable CURLInfoResponseString + fun easy_getinfo_chars(opt: CURLInfoChars): nullable String do - var answ = new CURLInfoResponseString - if not i_getinfo_chars(opt, answ).is_ok then return null - return answ + var answ = new Container[NativeString]("".to_cstring) + if not native_getinfo_chars(opt, answ).is_ok then return null + if answ.item.address_is_null then return null + return answ.item.to_s end + # Internal method used to get String object information initially knowns as C Chars type - private fun i_getinfo_chars(opt: CURLInfoChars, res: CURLInfoResponseString):CURLCode import CURLInfoResponseString.response=, NativeString.to_s_with_copy `{ - char *r = NULL; + private fun native_getinfo_chars(opt: CURLInfoChars, res: Container[NativeString]): CURLCode + import Container[NativeString].item= `{ + char *r; CURLcode c = curl_easy_getinfo( self, opt, &r); - if((c == CURLE_OK) && r != NULL){ - String ro = NativeString_to_s_with_copy(r); - CURLInfoResponseString_response__assign( res, ro); - } + if (c == CURLE_OK) Container_of_NativeString_item__assign(res, r); return c; `} + # Request Long internal information from the CURL session - fun easy_getinfo_long(opt: CURLInfoLong):nullable CURLInfoResponseLong + fun easy_getinfo_long(opt: CURLInfoLong): nullable Int do - var answ = new CURLInfoResponseLong - if not i_getinfo_long(opt, answ).is_ok then return null - return answ + var answ = new Container[Int](0) + if not native_getinfo_long(opt, answ).is_ok then return null + return answ.item end + # Internal method used to get Int object information initially knowns as C Long type - private fun i_getinfo_long(opt: CURLInfoLong, res: CURLInfoResponseLong):CURLCode import CURLInfoResponseLong.response= `{ - long *r = NULL; - r = malloc(sizeof(long)); - CURLcode c = curl_easy_getinfo( self, opt, r); - if((c == CURLE_OK) && r != NULL) CURLInfoResponseLong_response__assign( res, *r); - free(r); + private fun native_getinfo_long(opt: CURLInfoLong, res: Container[Int]): CURLCode + import Container[Int].item= `{ + long r; + CURLcode c = curl_easy_getinfo( self, opt, &r); + if (c == CURLE_OK) Container_of_Int_item__assign(res, r); return c; `} + # Request Double internal information from the CURL session - fun easy_getinfo_double(opt: CURLInfoDouble):nullable CURLInfoResponseDouble + fun easy_getinfo_double(opt: CURLInfoDouble): nullable Float do - var answ = new CURLInfoResponseDouble - if not i_getinfo_double(opt, answ).is_ok then return null - return answ + var answ = new Container[Float](0.0) + if not native_getinfo_double(opt, answ).is_ok then return null + return answ.item end + # Internal method used to get Int object information initially knowns as C Double type - private fun i_getinfo_double(opt: CURLInfoDouble, res: CURLInfoResponseDouble):CURLCode import CURLInfoResponseDouble.response= `{ - double *r = NULL; - r = malloc(sizeof(double)); - CURLcode c = curl_easy_getinfo( self, opt, r); - if((c == CURLE_OK) && r != NULL) CURLInfoResponseDouble_response__assign( res, *r); - free(r); + private fun native_getinfo_double(opt: CURLInfoDouble, res: Container[Float]): CURLCode + import Container[Float].item= `{ + double r; + CURLcode c = curl_easy_getinfo(self, opt, &r); + if (c == CURLE_OK) Container_of_Float_item__assign(res, r); return c; `} + # Request SList internal information from the CURL session - fun easy_getinfo_slist(opt: CURLInfoSList):nullable CURLInfoResponseArray + fun easy_getinfo_slist(opt: CURLInfoSList): nullable Array[String] do - var answ = new CURLInfoResponseArray - if not i_getinfo_slist(opt, answ).is_ok then return null - answ.response = answ.prim_response.to_a - answ.prim_response.destroy - return answ + var answ = new Container[CURLSList](new CURLSList) + if not native_getinfo_slist(opt, answ).is_ok then return null + + var native = answ.item + var nity = native.to_a + native.destroy + return nity end + # Internal method used to get Array[String] object information initially knowns as C SList type - private fun i_getinfo_slist(opt: CURLInfoSList, res: CURLInfoResponseArray):CURLCode import CURLInfoResponseArray.prim_response=`{ - struct curl_slist* csl = NULL; - CURLcode ce = curl_easy_getinfo( self, opt, &csl); - CURLInfoResponseArray_prim_response__assign(res, csl); - return ce; + private fun native_getinfo_slist(opt: CURLInfoSList, res: Container[CURLSList]): CURLCode + import Container[CURLSList].item= `{ + struct curl_slist* csl; + CURLcode c = curl_easy_getinfo(self, opt, &csl); + if (c == CURLE_OK) Container_of_CURLSList_item__assign(res, csl); + return c; `} - # Register delegate to get callbacks about the CURL transfer performed - fun register_callback(delegate: CCurlCallbacks, cbtype: CURLCallbackType):CURLCode - do - if once [new CURLCallbackType.header, new CURLCallbackType.body, new CURLCallbackType.stream, new CURLCallbackType.read].has(cbtype) then - return i_register_callback(delegate, cbtype) - end - return once new CURLCode.unknown_option - end + # Register delegate to read datas from given buffer - fun register_read_datas_callback(delegate: CCurlCallbacks, datas: String):CURLCode + fun register_read_datas_callback(delegate: NativeCurlCallbacks, datas: String): CURLCode do - if datas.length > 0 then return i_register_read_datas_callback(delegate, datas, datas.length) + if datas.length > 0 then return native_register_read_datas_callback(delegate, datas, datas.length) return once new CURLCode.unknown_option end + # Internal method used to configure read callback - private fun i_register_read_datas_callback(delegate: CCurlCallbacks, datas: String, size: Int):CURLCode import String.to_cstring `{ + private fun native_register_read_datas_callback(delegate: NativeCurlCallbacks, datas: String, size: Int): CURLCode import String.to_cstring `{ CURLCallbackReadDatas *d = NULL; d = malloc(sizeof(CURLCallbackReadDatas)); d->data = (char*)String_to_cstring(datas); @@ -193,34 +201,56 @@ extern class CCurl `{ CURL * `} d->pos = 0; return curl_easy_setopt( self, CURLOPT_READDATA, d); `} - # Internal method used to configure callbacks in terms of given type - private fun i_register_callback(delegate: CCurlCallbacks, cbtype: CURLCallbackType):CURLCode is extern import CCurlCallbacks.header_callback, CCurlCallbacks.body_callback, CCurlCallbacks.stream_callback, NativeString.to_s_with_copy, NativeString.to_s `{ - CURLCallbackDatas *d = malloc(sizeof(CURLCallbackDatas)); - CCurlCallbacks_incr_ref(delegate); - d->type = cbtype; - d->delegate = delegate; + + # Register `delegate` to get callbacks about the CURL transfer + fun register_callback_header(delegate: NativeCurlCallbacks): CURLCode + import NativeString.native_callback_header `{ CURLcode e; - switch(cbtype){ - case CURLcallbackTypeHeader: - e = curl_easy_setopt( self, CURLOPT_HEADERFUNCTION, (curl_write_callback)&nit_curl_callback_func); - if(e != CURLE_OK) return e; - e = curl_easy_setopt( self, CURLOPT_WRITEHEADER, d); - break; - case CURLcallbackTypeBody: - case CURLcallbackTypeStream: - e = curl_easy_setopt( self, CURLOPT_WRITEFUNCTION, (curl_write_callback)&nit_curl_callback_func); - if(e != CURLE_OK) return e; - e = curl_easy_setopt( self, CURLOPT_WRITEDATA, d); - break; - case CURLcallbackTypeRead: - e = curl_easy_setopt( self, CURLOPT_READFUNCTION, (curl_write_callback)&nit_curl_callback_read_func); - default: - break; - } + NativeCurlCallbacks_incr_ref(delegate); // FIXME deallocated these when download completes? + + e = curl_easy_setopt(self, CURLOPT_HEADERFUNCTION, (curl_write_callback)&NativeString_native_callback_header); + if(e != CURLE_OK) return e; + + e = curl_easy_setopt(self, CURLOPT_WRITEHEADER, delegate); return e; `} + + # Register `delegate` to get callbacks about the CURL transfer + fun register_callback_body(delegate: NativeCurlCallbacks): CURLCode + import NativeString.native_callback_body `{ + CURLcode e; + NativeCurlCallbacks_incr_ref(delegate); + + e = curl_easy_setopt(self, CURLOPT_WRITEFUNCTION, (curl_write_callback)&NativeString_native_callback_body); + if(e != CURLE_OK) return e; + + e = curl_easy_setopt(self, CURLOPT_WRITEDATA, delegate); + return e; + `} + + # Register `delegate` to get callbacks about the CURL transfer + fun register_callback_stream(delegate: NativeCurlCallbacks): CURLCode + import NativeString.native_callback_stream `{ + CURLcode e; + NativeCurlCallbacks_incr_ref(delegate); + + e = curl_easy_setopt(self, CURLOPT_WRITEFUNCTION, (curl_write_callback)&NativeString_native_callback_stream); + if(e != CURLE_OK) return e; + + e = curl_easy_setopt(self, CURLOPT_WRITEDATA, delegate); + return e; + `} + + # Register `delegate` to get callbacks about the CURL transfer + fun register_callback_read(delegate: NativeCurlCallbacks): CURLCode + import NativeString.native_callback_stream `{ + NativeCurlCallbacks_incr_ref(delegate); + + return curl_easy_setopt(self, CURLOPT_READFUNCTION, (curl_write_callback)&nit_curl_callback_read_func); + `} + # Convert given string to URL encoded string - fun escape(url: String):String import String.to_cstring, NativeString.to_s_with_copy `{ + fun escape(url: String): String import String.to_cstring, NativeString.to_s_with_copy `{ char *orig_url, *encoded_url = NULL; orig_url = String_to_cstring(url); encoded_url = curl_easy_escape( self, orig_url, strlen(orig_url)); @@ -230,44 +260,11 @@ extern class CCurl `{ CURL * `} `} end -# FILE Extern type, reproduce basic FILE I/O -extern class OFile `{ FILE* `} - # Open / Create a file from given name - new open(str: NativeString) `{ return fopen(str, "wb"); `} - # Check for File validity - fun is_valid:Bool `{ return self != NULL; `} - # Internal method to write to the current file - private fun n_write(buffer: NativeString, size: Int, count: Int):Int `{ return fwrite(buffer, size, count, self); `} - # Write datas to the current file - fun write(buffer: String, size: Int, count: Int):Int - do - if is_valid == true then return n_write(buffer.to_cstring, size, count) - return 0 - end - # Internal method to close the current file - private fun n_close:Int `{ return fclose(self); `} - # Close the current file - fun close:Bool - do - if is_valid == true then return n_close == 0 - return false - end -end - # Interface for internal information callbacks methods -interface CCurlCallbacks - fun header_callback(line: String) is abstract - fun body_callback(line: String) is abstract - fun stream_callback(buffer: String, size: Int, count: Int) is abstract -end - -# Extern Type to reproduce Enum of available Callback type -extern class CURLCallbackType `{ CURLcallbackType `} - new header `{ return CURLcallbackTypeHeader; `} - new body `{ return CURLcallbackTypeBody; `} - new stream `{ return CURLcallbackTypeStream; `} - new read `{ return CURLcallbackTypeRead; `} - fun to_i:Int `{ return self; `} +interface NativeCurlCallbacks + fun header_callback(buffer: String) do end + fun body_callback(buffer: String) do end + fun stream_callback(buffer: String) do end end # CURL Code binding and helpers @@ -276,11 +273,12 @@ extern class CURLCode `{ CURLcode `} new unsupported_protocol `{ return CURLE_UNSUPPORTED_PROTOCOL; `} new ok `{ return CURLE_OK; `} new failed_init `{ return CURLE_FAILED_INIT; `} - fun code:Int `{ return self; `} - fun is_ok:Bool `{ return self == CURLE_OK; `} - fun is_valid_protocol:Bool `{ return self == CURLE_UNSUPPORTED_PROTOCOL; `} - fun is_valid_init:Bool `{ return self == CURLE_FAILED_INIT; `} - fun to_i:Int do return code end + + fun code: Int `{ return self; `} + fun is_ok: Bool `{ return self == CURLE_OK; `} + fun is_valid_protocol: Bool `{ return self == CURLE_UNSUPPORTED_PROTOCOL; `} + fun is_valid_init: Bool `{ return self == CURLE_FAILED_INIT; `} + fun to_i: Int do return code end redef fun to_s import NativeString.to_s_with_copy `{ char *c = (char*)curl_easy_strerror(self); return NativeString_to_s_with_copy(c); @@ -291,39 +289,48 @@ end extern class CURLSList `{ struct curl_slist * `} # Empty constructor which allow us to avoid the use of Nit NULLABLE type private new `{ return NULL; `} + # Constructor allow us to get list instancied by appending an element inside. new with_str(s: String) import String.to_cstring `{ struct curl_slist *l = NULL; l = curl_slist_append(l, String_to_cstring(s)); return l; `} + # Check for initialization - fun is_init:Bool `{ return (self != NULL); `} + fun is_init: Bool `{ return (self != NULL); `} + # Append an element in the linked list fun append(key: String) import String.to_cstring `{ char *k = String_to_cstring(key); curl_slist_append(self, (char*)k); `} + # Internal method to check for reachability of current data - private fun i_data_reachable(c: CURLSList):Bool `{ return (c != NULL && c->data != NULL); `} + private fun native_data_reachable(c: CURLSList): Bool `{ return (c != NULL && c->data != NULL); `} + # Internal method to check for reachability of next element - private fun i_next_reachable(c: CURLSList):Bool `{ return (c != NULL && c->next != NULL); `} + private fun native_next_reachable(c: CURLSList): Bool `{ return (c != NULL && c->next != NULL); `} + # Internal method to get current data - private fun i_data(c: CURLSList):String import NativeString.to_s `{ return NativeString_to_s(c->data); `} + private fun native_data(c: CURLSList): String import NativeString.to_s `{ return NativeString_to_s(c->data); `} + # Internal method to get next element - private fun i_next(c: CURLSList):CURLSList `{ return c->next; `} + private fun native_next(c: CURLSList): CURLSList `{ return c->next; `} + # Convert current low level List to an Array[String] object - fun to_a:Array[String] + fun to_a: Array[String] do var r = new Array[String] var cursor = self loop - if i_data_reachable(cursor) != true then break - r.add(i_data(cursor)) - cursor = i_next(cursor) + if native_data_reachable(cursor) != true then break + r.add(native_data(cursor)) + cursor = native_next(cursor) end return r end + # Release allocated memory fun destroy `{ curl_slist_free_all(self); `} end @@ -345,34 +352,13 @@ redef class Collection[E] end end -# Array Response type of CCurl.easy_getinfo method -class CURLInfoResponseArray - var response:Array[String] = new Array[String] - private var prim_response:CURLSList = new CURLSList -end - -# Long Response type of CCurl.easy_getinfo method -class CURLInfoResponseLong - var response:Int=0 -end - -# Double Response type of CCurl.easy_getinfo method -class CURLInfoResponseDouble - var response:Int=0 -end - -# String Response type of CCurl:easy_getinfo method -class CURLInfoResponseString - var response:String = "" -end - -# Reproduce Enum of available CURL SList information, used for CCurl.easy_getinfo +# Reproduce Enum of available CURL SList information, used for NativeCurl.easy_getinfo extern class CURLInfoSList `{ CURLINFO `} new ssl_engines `{ return CURLINFO_SSL_ENGINES; `} new cookielist `{ return CURLINFO_COOKIELIST; `} end -# Reproduce Enum of available CURL Long information, used for CCurl.easy_getinfo +# Reproduce Enum of available CURL Long information, used for NativeCurl.easy_getinfo extern class CURLInfoLong `{ CURLINFO `} new response_code `{ return CURLINFO_RESPONSE_CODE; `} new header_size `{ return CURLINFO_HEADER_SIZE; `} @@ -394,7 +380,7 @@ extern class CURLInfoLong `{ CURLINFO `} new rtsp_cseq_self `{ return CURLINFO_RTSP_CSEQ_RECV; `} end -# Reproduce Enum of available CURL Double information, used for CCurl.easy_getinfo +# Reproduce Enum of available CURL Double information, used for NativeCurl.easy_getinfo extern class CURLInfoDouble `{ CURLINFO `} new total_time `{ return CURLINFO_TOTAL_TIME; `} new namelookup_time `{ return CURLINFO_NAMELOOKUP_TIME; `} @@ -411,7 +397,7 @@ extern class CURLInfoDouble `{ CURLINFO `} new content_length_upload `{ return CURLINFO_CONTENT_LENGTH_UPLOAD; `} end -# Reproduce Enum of available CURL Chars information, used for CCurl.easy_getinfo +# Reproduce Enum of available CURL Chars information, used for NativeCurl.easy_getinfo extern class CURLInfoChars `{ CURLINFO `} new content_type `{ return CURLINFO_CONTENT_TYPE; `} new effective_url `{ return CURLINFO_EFFECTIVE_URL; `} @@ -462,22 +448,26 @@ extern class CURLStatusCode `{ int `} new service_unavailable `{ return 503; `} new gateway_timeout `{ return 504; `} new http_version_not_supported `{ return 505; `} - fun to_i:Int `{ return self; `} + fun to_i: Int `{ return self; `} end -# Reproduce Enum of CURL Options usable, used for CCurl.easy_setopt +# Reproduce Enum of CURL Options usable, used for NativeCurl.easy_setopt extern class CURLOption `{ CURLoption `} # Behavior options # Display verbose information. new verbose `{ return CURLOPT_VERBOSE; `} + # Include the header in the body output. new header `{ return CURLOPT_HEADER; `} + # Shut off the progress meter. new no_progress `{ return CURLOPT_NOPROGRESS; `} + # Do not install signal handlers. new no_signal `{ return CURLOPT_NOSIGNAL; `} + # Transfer multiple files according to a file name pattern. new wild_card_match `{ return CURLOPT_WILDCARDMATCH; `} @@ -485,6 +475,7 @@ extern class CURLOption `{ CURLoption `} # Callback for writing data. new write_function `{ return CURLOPT_WRITEFUNCTION; `} + # Data pointer to pass to the write callback. new write_data `{ return CURLOPT_WRITEDATA; `} @@ -576,8 +567,10 @@ extern class CURLOption `{ CURLoption `} # Accept-Encoding and automatic decompressing data. new accept_encoding `{ return CURLOPT_ACCEPT_ENCODING; `} + # Request Transfer-Encoding. new transfert_encoding `{ return CURLOPT_TRANSFER_ENCODING; `} + # Follow HTTP redirects. new follow_location `{ return CURLOPT_FOLLOWLOCATION; `} @@ -587,8 +580,10 @@ extern class CURLOption `{ CURLoption `} # Issue a HTTP PUT request. new put `{ return CURLOPT_PUT; `} + # Issue a HTTP POS request. new post `{ return CURLOPT_POST; `} + # Send a POST with this data. new postfields `{ return CURLOPT_POSTFIELDS; `} @@ -600,6 +595,7 @@ extern class CURLOption `{ CURLoption `} # User-Agent: header. new user_agent `{ return CURLOPT_USERAGENT; `} + # Custom HTTP headers. new httpheader `{ return CURLOPT_HTTPHEADER; `} @@ -624,6 +620,7 @@ extern class CURLOption `{ CURLoption `} # Address of the sender. new mail_from `{ return CURLOPT_MAIL_FROM; `} + # Address of the recipients. new mail_rcpt `{ return CURLOPT_MAIL_RCPT; `} @@ -642,6 +639,7 @@ extern class CURLOption `{ CURLoption `} # List only. new dir_list_only `{ return CURLOPT_DIRLISTONLY; `} + # Append to remote file. new append `{ return CURLOPT_APPEND; `} diff --git a/lib/github/github_curl.nit b/lib/github/github_curl.nit index 8e70600..62776e6 100644 --- a/lib/github/github_curl.nit +++ b/lib/github/github_curl.nit @@ -21,7 +21,6 @@ import json::static # Specific Curl that know hot to talk to the github API class GithubCurl - super Curl # Headers to use on all requests var header: HeaderMap is noinit @@ -42,7 +41,7 @@ class GithubCurl # and check for Github errors. fun get_and_check(uri: String): nullable Jsonable do - var request = new CurlHTTPRequest(uri, self) + var request = new CurlHTTPRequest(uri) request.user_agent = user_agent request.headers = header var response = request.execute @@ -73,7 +72,7 @@ class GithubCurl # are reported as `GithubError`. fun get_and_parse(uri: String): nullable Jsonable do - var request = new CurlHTTPRequest(uri, self) + var request = new CurlHTTPRequest(uri) request.user_agent = user_agent request.headers = header var response = request.execute diff --git a/lib/neo4j/curl_json.nit b/lib/neo4j/curl_json.nit index e458eb1..17df2fc 100644 --- a/lib/neo4j/curl_json.nit +++ b/lib/neo4j/curl_json.nit @@ -20,31 +20,11 @@ intrude import curl # An abstract request that defines most of the standard options for Neo4j REST API abstract class JsonCurlRequest - super CurlRequest - super CCurlCallbacks - super CurlCallbacksRegisterIntern - - # REST API service URL - var url: String - - init (url: String, curl: nullable Curl) do - self.url = url - self.curl = curl - - init_headers - end + super CurlHTTPRequest # OAuth token var auth: nullable String is writable - # User agent (is used by github to contact devs in case of problems) - # Eg. "Awesome-Octocat-App" - var user_agent: nullable String is writable - - # HTTP headers to send - var headers: nullable HeaderMap = null is writable - - # init HTTP headers for Neo4j REST API protected fun init_headers do headers = new HeaderMap @@ -54,6 +34,8 @@ abstract class JsonCurlRequest if auth != null then headers["Authorization"] = "token {auth.to_s}" end + + # User agent (is used by github to contact devs in case of problems) if user_agent != null then headers["User-Agent"] = user_agent.to_s end @@ -61,6 +43,7 @@ abstract class JsonCurlRequest redef fun execute do init_headers + if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized") end @@ -69,28 +52,25 @@ abstract class JsonCurlRequest var callback_receiver: CurlCallbacks = success_response if self.delegate != null then callback_receiver = self.delegate.as(not null) - var err - - err = self.curl.prim_curl.easy_setopt(new CURLOption.follow_location, 1) + var err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.easy_setopt(new CURLOption.http_version, 1) + err = self.curl.native.easy_setopt(new CURLOption.http_version, 1) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - - err = self.curl.prim_curl.easy_setopt(new CURLOption.url, url) + err = self.curl.native.easy_setopt(new CURLOption.url, url) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header) + err = self.curl.native.register_callback_header(callback_receiver) if not err.is_ok then return answer_failure(err.to_i, err.to_s) - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.body) + err = self.curl.native.register_callback_body(callback_receiver) if not err.is_ok then return answer_failure(err.to_i, err.to_s) # HTTP Header if self.headers != null then var headers_joined = self.headers.join_pairs(": ") - err = self.curl.prim_curl.easy_setopt( + err = self.curl.native.easy_setopt( new CURLOption.httpheader, headers_joined.to_curlslist) if not err.is_ok then return answer_failure(err.to_i, err.to_s) end @@ -101,8 +81,8 @@ abstract class JsonCurlRequest var err_resp = perform if err_resp != null then return err_resp - var st_code = self.curl.prim_curl.easy_getinfo_long(new CURLInfoLong.response_code) - if not st_code == null then success_response.status_code = st_code.response + var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code) + if not st_code == null then success_response.status_code = st_code return success_response end @@ -116,7 +96,7 @@ class JsonGET super JsonCurlRequest redef fun execute_hook do - var err = self.curl.prim_curl.easy_setopt(new CURLOption.get, true) + var err = self.curl.native.easy_setopt(new CURLOption.get, true) if not err.is_ok then return answer_failure(err.to_i, err.to_s) return null end @@ -134,12 +114,12 @@ class JsonPOST end redef fun execute_hook do - var err = self.curl.prim_curl.easy_setopt(new CURLOption.post, true) + var err = self.curl.native.easy_setopt(new CURLOption.post, true) if not err.is_ok then return answer_failure(err.to_i, err.to_s) if self.data != null then var postdatas = self.data.to_json - err = self.curl.prim_curl.easy_setopt(new CURLOption.postfields, postdatas) + err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas) if not err.is_ok then return answer_failure(err.to_i, err.to_s) end return null @@ -151,7 +131,7 @@ class JsonDELETE super JsonCurlRequest redef fun execute_hook do - var err = self.curl.prim_curl.easy_setopt(new CURLOption.custom_request, "DELETE") + var err = self.curl.native.easy_setopt(new CURLOption.custom_request, "DELETE") if not err.is_ok then return answer_failure(err.to_i, err.to_s) return null end @@ -169,12 +149,12 @@ class JsonPUT end redef fun execute_hook do - var err = self.curl.prim_curl.easy_setopt(new CURLOption.custom_request, "PUT") + var err = self.curl.native.easy_setopt(new CURLOption.custom_request, "PUT") if not err.is_ok then return answer_failure(err.to_i, err.to_s) if self.data != null then var postdatas = self.data.to_json - err = self.curl.prim_curl.easy_setopt(new CURLOption.postfields, postdatas) + err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas) if not err.is_ok then return answer_failure(err.to_i, err.to_s) end return null diff --git a/lib/neo4j/neo4j.nit b/lib/neo4j/neo4j.nit index 4694f8d..88c151a 100644 --- a/lib/neo4j/neo4j.nit +++ b/lib/neo4j/neo4j.nit @@ -320,14 +320,14 @@ class Neo4jClient # GET JSON data from `url` fun get(url: String): Jsonable do - var request = new JsonGET(url, curl) + var request = new JsonGET(url) var response = request.execute return parse_response(response) end # POST `params` to `url` fun post(url: String, params: Jsonable): Jsonable do - var request = new JsonPOST(url, curl) + var request = new JsonPOST(url) request.data = params var response = request.execute return parse_response(response) @@ -335,7 +335,7 @@ class Neo4jClient # PUT `params` at `url` fun put(url: String, params: Jsonable): Jsonable do - var request = new JsonPUT(url, curl) + var request = new JsonPUT(url) request.data = params var response = request.execute return parse_response(response) @@ -343,7 +343,7 @@ class Neo4jClient # DELETE `url` fun delete(url: String): Jsonable do - var request = new JsonDELETE(url, curl) + var request = new JsonDELETE(url) var response = request.execute return parse_response(response) end @@ -912,7 +912,7 @@ class NeoBatch # Execute the batch and update local nodes fun execute: List[NeoError] do - var request = new JsonPOST(client.batch_url, client.curl) + var request = new JsonPOST(client.batch_url) # request.headers["X-Stream"] = "true" var json_jobs = new JsonArray for job in jobs.values do json_jobs.add job.to_rest diff --git a/tests/sav/curl_http.res b/tests/sav/curl_http.res index e4d9464..5118616 100644 --- a/tests/sav/curl_http.res +++ b/tests/sav/curl_http.res @@ -1 +1 @@ -Usage: curl_http +Usage: curl_http [POST|GET|GET_FILE] url diff --git a/tests/sav/curl_http_args1.res b/tests/sav/curl_http_args1.res index c47c136..587f274 100644 --- a/tests/sav/curl_http_args1.res +++ b/tests/sav/curl_http_args1.res @@ -1,5 +1,5 @@ -Status code : 200 -Body : +Status code: 200 +Body: Example Domain diff --git a/tests/sav/curl_http_args2.res b/tests/sav/curl_http_args2.res index a7b3f76..7fa85c5 100644 --- a/tests/sav/curl_http_args2.res +++ b/tests/sav/curl_http_args2.res @@ -1,4 +1,4 @@ -Our body from the callback : +Our body from the callback: Example Domain @@ -50,5 +50,5 @@ Our body from the callback : *** Answer *** -Status code : 200 -Body should be empty, because we decided to manage callbacks : 0 +Status code: 200 +Body should be empty, because we decided to manage callbacks: 0 diff --git a/tests/sav/curl_http_args3.res b/tests/sav/curl_http_args3.res index 0c72a82..bf03427 100644 --- a/tests/sav/curl_http_args3.res +++ b/tests/sav/curl_http_args3.res @@ -1,3 +1,3 @@ *** Answer *** -Status code : 200 -Size downloaded : 1270 +Status code: 200 +Size downloaded: 1270.0 diff --git a/tests/sav/curl_mail.res b/tests/sav/curl_mail.res deleted file mode 100644 index 5e63d7f..0000000 --- a/tests/sav/curl_mail.res +++ /dev/null @@ -1,2 +0,0 @@ -Error code : 6 -Error msg : Couldn't resolve host name diff --git a/tests/test_curl.nit b/tests/test_curl.nit index 1b8bc03..757c963 100644 --- a/tests/test_curl.nit +++ b/tests/test_curl.nit @@ -14,7 +14,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -module test_curl import curl @@ -28,7 +27,7 @@ fun error_manager(err: CURLCode) do if not err.is_ok then print err var url = "http://example.org/" -var curl = new CCurl.easy_init +var curl = new NativeCurl.easy_init if not curl.is_init then print "failed init" var error:CURLCode @@ -40,15 +39,14 @@ error_manager(error) #error_manager(error) var cbManager = new CallbackManager -error = curl.register_callback(cbManager, new CURLCallbackType.body) +error = curl.register_callback_body(cbManager) error_manager(error) error = curl.easy_perform error_manager(error) # Long set -var info:nullable CURLInfoResponseLong -info = curl.easy_getinfo_long(new CURLInfoLong.header_size) +var info = curl.easy_getinfo_long(new CURLInfoLong.header_size) assert infoResp:info != null info = curl.easy_getinfo_long(new CURLInfoLong.response_code) @@ -103,8 +101,7 @@ info = curl.easy_getinfo_long(new CURLInfoLong.rtsp_cseq_self) assert infoResp:info != null # Double -var infoDouble: nullable CURLInfoResponseDouble -infoDouble = curl.easy_getinfo_double(new CURLInfoDouble.total_time) +var infoDouble = curl.easy_getinfo_double(new CURLInfoDouble.total_time) assert infoResp:infoDouble != null infoDouble = curl.easy_getinfo_double(new CURLInfoDouble.namelookup_time) @@ -144,15 +141,15 @@ infoDouble = curl.easy_getinfo_double(new CURLInfoDouble.content_length_upload) assert infoResp:infoDouble != null # String set -var infoStr:nullable CURLInfoResponseString -infoStr = curl.easy_getinfo_chars(new CURLInfoChars.content_type) +var infoStr = curl.easy_getinfo_chars(new CURLInfoChars.content_type) assert infoResp:infoStr != null infoStr = curl.easy_getinfo_chars(new CURLInfoChars.effective_url) assert infoResp:infoStr != null +# follow_location not set, so returns null infoStr = curl.easy_getinfo_chars(new CURLInfoChars.redirect_url) -assert infoResp:infoStr != null +assert infoStr == null infoStr = curl.easy_getinfo_chars(new CURLInfoChars.primary_ip) assert infoResp:infoStr != null @@ -160,18 +157,20 @@ assert infoResp:infoStr != null infoStr = curl.easy_getinfo_chars(new CURLInfoChars.local_ip) assert infoResp:infoStr != null +# Not connecting to FTP so `null` infoStr = curl.easy_getinfo_chars(new CURLInfoChars.ftp_entry_path) -assert infoResp:infoStr != null +assert infoStr == null +# opt private not set nor implemented, so returns null infoStr = curl.easy_getinfo_chars(new CURLInfoChars.private_data) -assert infoResp:infoStr != null +assert infoStr == null +# Not an RTSP connection so `null` infoStr = curl.easy_getinfo_chars(new CURLInfoChars.rtsp_session_id) -assert infoResp:infoStr != null +assert infoStr == null # CURLSList set -var infoList:nullable CURLInfoResponseArray -infoList = curl.easy_getinfo_slist(new CURLInfoSList.ssl_engines) +var infoList = curl.easy_getinfo_slist(new CURLInfoSList.ssl_engines) assert infoResp:infoList != null infoList = curl.easy_getinfo_slist(new CURLInfoSList.cookielist) @@ -211,4 +210,4 @@ var hashMapRefined = new HeaderMap hashMapRefined["hello"] = "toto" hashMapRefined["hello"] = "tata" hashMapRefined["allo"] = "foo" -print hashMapRefined.to_url_encoded(new CCurl.easy_init) +print hashMapRefined.to_url_encoded(sys.curl)