X-Git-Url: http://nitlanguage.org diff --git a/lib/curl/curl.nit b/lib/curl/curl.nit index d3d3ca5..18f05c8 100644 --- a/lib/curl/curl.nit +++ b/lib/curl/curl.nit @@ -14,233 +14,403 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Network functionnalities based on Curl_c module. +# Data transfer powered by the native curl library +# +# Download or upload data over HTTP with `CurlHTTPRequest` and send emails +# with `CurlMail`. Scripts can use the easier (but limited) services on `Text`, +# `http_get` and `http_download`, provided by `curl::extra`. module curl -import curl_c +import native_curl -# Top level of Curl -class Curl - protected var prim_curl: CCurl +# Curl library handle +private class Curl + super FinalizableOnce - init - do - self.prim_curl = new CCurl.easy_init - assert curlInstance:self.prim_curl.is_init else - print "Curl must be instancied to be used" - end - end + var native = new NativeCurl.easy_init - # Check for correct initialization - fun is_ok: Bool do return self.prim_curl.is_init + # Is this instance correctly initialized? + fun is_ok: Bool do return self.native.is_init - # Get an HTTP Request object to perform your own - fun http_request(url: String): nullable CurlRequest - do - var err - err = self.prim_curl.easy_setopt(new CURLOption.follow_location, 1) - if not err.is_ok then return null - - err = self.prim_curl.easy_setopt(new CURLOption.url, url) - if not err.is_ok then return null - - return new CurlHTTPRequest(url, self) - end - - # Get a MAIL Request Object - fun mail_request: nullable CurlMailRequest - do - var err: CURLCode - err = self.prim_curl.easy_setopt(new CURLOption.follow_location, 1) - if not err.is_ok then return null - - return new CurlMailRequest(self) - end - - # 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 writable = false - private var curl: nullable Curl = null + private var curl = new 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 CurlResponse + private fun perform: nullable CurlResponseFailed do if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized") 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 end # Intern method with return a failed answer with given code and message - private fun answer_failure(error_code: Int, error_msg: String): CurlResponse + private fun answer_failure(error_code: Int, error_msg: String): CurlResponseFailed do return new CurlResponseFailed(error_code, error_msg) end + + # Close low-level resources associated to this request + # + # Once closed, this request can't be used again. + # + # If this service isn't called explicitly, low-level resources + # may be freed automatically by the GC. + fun close do curl.finalize end -# CURL HTTP Request +# HTTP request builder +# +# The request itself is sent by either `execute` or `download_to_file`. +# The attributes of this class must be set before calling either of these two methods. +# +# ## Minimal usage example +# +# ~~~ +# var request = new CurlHTTPRequest("http://example.org/") +# var response = request.execute +# if response isa CurlResponseSuccess then +# print "Response status code: {response.status_code}" +# print response.body_str +# else if response isa CurlResponseFailed then +# print_error response.error_msg +# end +# ~~~ class CurlHTTPRequest super CurlRequest - super CCurlCallbacks - super CurlCallbacksRegisterIntern + super NativeCurlCallbacks + # Address of the remote resource to request var url: String - var datas: nullable HeaderMap writable = null - var headers: nullable HeaderMap writable = null - - init (url: String, curl: nullable Curl) - do - self.url = url - self.curl = curl - end - # Execute HTTP request with settings configured through attribute - redef fun execute + # Data for the body of a POST request + var data: nullable HeaderMap is writable + + # Raw body string + # + # Set this value to send raw data instead of the POST formatted `data`. + # + # If `data` is set, the body will not be sent. + var body: nullable String is writable + + # Header content of the request + var headers: nullable HeaderMap is writable + + # Delegates to customize the behavior when running `execute` + var delegate: nullable CurlCallbacks is writable + + # Set the user agent for all following HTTP requests + var user_agent: nullable String is writable + + # Set the Unix domain socket path to use + # + # When not null, enables using a Unix domain socket + # instead of a TCP connection and DNS hostname resolution. + var unix_socket_path: nullable String is writable + + # The HTTP method, GET by default + # + # Must be a capitalized string with request name complying with RFC7231 + var method: String = "GET" is optional, writable + + # Execute HTTP request + # + # By default, the response body is returned in an instance of `CurlResponse`. + # This behavior can be customized by setting a custom `delegate`. + fun execute: CurlResponse do + # Reset libcurl parameters as the lib is shared and options + # might affect requests from one another. if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized") var success_response = new CurlResponseSuccess var callback_receiver: CurlCallbacks = success_response - if self.delegate != null then callback_receiver = self.delegate.as(not null) + var err : CURLCode + + # Prepare request + err = prepare_request(callback_receiver) + if not err.is_ok then return answer_failure(err.to_i, err.to_s) + + # Perform request + var err_resp = perform + if err_resp != null then return err_resp + + 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 + # Internal function that sets cURL options and request' parameters + private fun prepare_request(callback_receiver: CurlCallbacks) : CURLCode + do var err - # Callbacks - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.header) - if not err.is_ok then return answer_failure(err.to_i, err.to_s) + # cURL options and delegates + err = set_curl_options + if not err.is_ok then return err - err = self.curl.prim_curl.register_callback(callback_receiver, new CURLCallbackType.body) - if not err.is_ok then return answer_failure(err.to_i, err.to_s) + # Callbacks + err = set_curl_callback(callback_receiver) + if not err.is_ok then return err # 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) - if not err.is_ok then return answer_failure(err.to_i, err.to_s) + err = set_curl_http_header + if not err.is_ok then return err + + # Set HTTP method and body + err = set_method + if not err.is_ok then return err + err = set_body + + return err + end + + # Set cURL parameters according to assigned HTTP method set in method + # attribute and body if the method allows it according to RFC7231 + private fun set_method : CURLCode + do + var err : CURLCode + + if self.method=="GET" then + err=self.curl.native.easy_setopt(new CURLOption.get, 1) + + else if self.method=="POST" then + err=self.curl.native.easy_setopt(new CURLOption.post, 1) + + else if self.method=="HEAD" then + err=self.curl.native.easy_setopt(new CURLOption.no_body,1) + + else + err=self.curl.native.easy_setopt(new CURLOption.custom_request,self.method) end + return err + 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) - if not err.is_ok then return answer_failure(err.to_i, err.to_s) + # Set request's body + private fun set_body : CURLCode + do + var err + var data = self.data + var body = self.body + + if data != null then + var postdatas = data.to_url_encoded(self.curl) + err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas) + if not err.is_ok then return err + else if body != null then + err = self.curl.native.easy_setopt(new CURLOption.postfields, body) + if not err.is_ok then return err end + return new CURLCode.ok + end - var err_resp = perform - if err_resp != null then return err_resp + # Set cURL options + # such as delegate, follow location, URL, user agent and address family + private fun set_curl_options : CURLCode + do + var err - 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 + err = self.curl.native.easy_setopt(new CURLOption.follow_location, 1) + if not err.is_ok then return err - return success_response + err = self.curl.native.easy_setopt(new CURLOption.url, url) + if not err.is_ok then return err + + 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 err + end + + var unix_socket_path = unix_socket_path + if unix_socket_path != null then + err = self.curl.native.easy_setopt(new CURLOption.unix_socket_path, unix_socket_path) + if not err.is_ok then return err + end + return err + end + + # Set cURL callback + private fun set_curl_callback(callback_receiver : CurlCallbacks) : CURLCode + do + var err + + if self.delegate != null then callback_receiver = self.delegate.as(not null) + + err = self.curl.native.register_callback_header(callback_receiver) + if not err.is_ok then return err + + err = self.curl.native.register_callback_body(callback_receiver) + if not err.is_ok then return err + + return err + end + + # Set cURL request header according to attribute headers + private fun set_curl_http_header : CURLCode + do + var headers = self.headers + if headers != null then + var headers_joined = headers.join_pairs(": ") + var err = self.curl.native.easy_setopt(new CURLOption.httpheader, headers_joined.to_curlslist) + if not err.is_ok then return err + end + return new CURLCode.ok end # Download to file given resource fun download_to_file(output_file_name: nullable String): CurlResponse do + if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized") + var success_response = new CurlFileResponseSuccess 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.register_callback(callback_receiver, new CURLCallbackType.header) + + 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.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.stream) + 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.native.register_callback_stream(callback_receiver) if not err.is_ok then return answer_failure(err.to_i, err.to_s) var opt_name if not output_file_name == null then opt_name = output_file_name else if not self.url.substring(self.url.length-1, self.url.length) == "/" then - opt_name = self.url.basename("") + opt_name = self.url.basename else 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 = "