Rev 8115 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title></title><link rel="stylesheet" type="text/css" href="../OmegaTech.css"></link><meta name="generator" content="DocBook XSL Stylesheets V1.76.1"></meta><meta name="keywords" content="HTTP, RCurl, R"></meta></head><body class="yui-skin-sam"><div class="article"><div class="titlepage"><div><div><div class="author"><h3 class="author"><span class="firstname">Duncan</span> <span class="surname">Temple Lang</span></h3><div class="affiliation"><span class="orgname">Department of Statistics, UC Davis<br></br></span></div></div></div></div><hr></hr></div><div class="section" title="Overview"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723591729"></a>Overview</h2></div></div></div><p></p>The <a class="ulink" href="https://www.omegahat.net/RCurl/" target="_top">RCurl package</a> provideshigh-level facilities in R to communicate with HTTP servers. Simply,it allows us to download URLs, submit forms in different ways, andgenerally compose HTTP requests. It supports HTTPS, the secure HTTP;handles authentication using passwords; and can use FTP to downloadfiles. It also handles escaping characters in requests, binary data,and file uploads. Users can override or provide additional headers inthe HTTP request in order to customize the communication. Theresponse from the HTTP server is processed as a stream and chunkencoding automatically handled. While the default mechanism simplyreturns the text from a request, one can specify S functions toprocess the response as it is received, redirecting it or processingin an application specific manner.<p></p>All of this could be written in R. We could use socket connections towrite requests to HTTP servers and receive the response. To supportHTTPS, we would have to add SSL connections. To get the same behavioras RCurl, we would have to implement the HTTP protocol. This involveswriting the HTTP headers correctly and flexibly, escaping characters,providing authentication, following redirection commands, handlingresponses by decomposing "chunked" content, binary files, etc. Tosubmit forms, we have to compose the body of the request by computingboundary strings and creating the "Content-Disposition" elements. Inshort, there is a lot of work to do and significant potential forerror. Rather than doing this in R, RCurl uses an existingimplementation that is provided in a widely used C library -<a class="ulink" href="https://curl.se/" target="_top">libcurl</a>.This has several benefits:<div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"> libcurl is well tested and very portable. As a result itis available on many platforms and bugs have been identified andfixed.</li><li class="listitem">Many people and applications use libcurl which means that it hassupport for common features that are used in various contexts.</li><li class="listitem">libcurl is in C and so is fast.</li></ul></div>On the negative side, it is hard to extend libcurl. We can only hopethat the hooks to customize requests are adequate for our needs. Wecan expect that others have run into these extensibility issues andthese needs have been fed back to the designers of libcurl.<p></p>R already provides its own way to download URIs.Functions like <pre xmlns="" class="rfunction">download.url</pre><br xmlns="">and connection constructors such as <pre xmlns="" class="rfunction">url</pre><br xmlns="">.<p></p> Since the libcurl library provides opaque data structures forimplementing requests and connections, we cannot easily adapt it tofit our special needs in R. Specifically, it is harder to merge itwith our view of connections in S (R and S-Plus). Similarly, it isharder to integrate it with the event-loop in R so that we can listenfor input pending and essentially put the connection <span class="emphasis"><em>in thebackground</em></span>. The support for threads in libcurl might make thiseasier if/when we support threads in R.</div><div class="section" title="A Quick Tour"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723591796"></a>A Quick Tour</h2></div></div></div>This document aims to provide a basic overview of the RCurl package.It doesn't try to provide all the details. The R function help filesand the libcurl documentation have all the relevant information.Since the package is an interface to libcurl, it is important to usethe documentation for it regarding features, options, etc.<!-- You can consult the <a class="ulink" href="https://curl.se/docs"target="_top">libcurl documentation</a> and <a class="ulink"href="http://curl.planetmirror.com/libcurl/c/example.html"target="_top">libcurl examples</a> (in C code). --><p></p>The RCurl package provides three primary high-level entry points.These allow us to fetch a URL and submit forms. The functions are<pre xmlns="" class="rfunction">getURL</pre><br xmlns="">, <pre xmlns="" class="rfunction">getForm</pre><br xmlns=""> and<pre xmlns="" class="rfunction">postForm</pre><br xmlns="">. The first is relativelystraightforward, given the name; it allows us to fetch the contents ofa URI. The other two functions provide ways to submit a form usingthe GET or POST methods. These are quite different internally, butfor users, both require a set of name-value pairs giving theparameters for the form submission. The difference is in how the formis submitted and the POST method allows us to submit/upload files,binary content, etc.<p></p>Let us look at the <pre xmlns="" class="rfunction">getURL</pre><br xmlns="">function.At it simplest, this is just likethe <pre xmlns="" class="rfunction">download.url</pre><br xmlns="">function in the standard R.We can fetch a URI with the command something like<pre xmlns="" class="S">getURL("https://www.omegahat.net/RCurl/index.html")</pre><br xmlns="">The idea is that we specify the URI.There are several other arguments to this function,but for the most part we don't need them.<p></p>We can use HTTPS to fetch URIs securely.For example,<pre xmlns="" class="S">getURL("https://sourceforge.net")</pre><br xmlns="">This is already more than we can do with theregular connections or built-in <pre xmlns="" class="rfunction">download.url</pre><br xmlns="">in R. (Using an external program allows HTTPS access.)<p></p> There are three different sets of arguments for the<pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> function. One is named<i xmlns="" class="rarg">curl</i> and we will cover this in section <a class="xref" href="#CURLHandle" title="CURL Handles">the section called “CURL Handles”</a>. This is merely a way to cumulate requests on asingle connection with shared options.<p></p>The <i xmlns="" class="rarg">write</i> function is again rather specialized.It allows us to specify an R function that is called each timelibcurl has some text as part of the HTTP response.It hands this text (as a sequence of bytes) to the functionso that it can process it in whatever way it deems fit.This corresponds to the <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">writefunction</font> optionfor the libcurl operation described next. We have it as an explicitargument simply because we need to use it to get the return valuein a single action as the default behavior.<p></p> The third set of arguments is the most general and is handledby the ... in the <pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> function. Withthis, one can specify name-value pairs governing the actual request.There are numerous possible settings that one can specify. The basicidea is that one can set options provided by the<i xmlns="">curl_easy_setopt</i> routine. These allow us toset parameters for many different aspects of the request. Forexample, we can specify additional headers for the HTTP request, orinclude a password for the Web site.The set of possible options can be determinedvia the function<pre xmlns="" class="rfunction">getCurlOptionConstants</pre><br xmlns="">.and the set of names for the different optionscan be found via the command<pre xmlns="" class="S">names(getCurlOptionsConstants())</pre><br xmlns="">This is a collection of names of options that are understoodby many of the functions in the RCurl package.<p></p>At present, there are 113 possible options.<pre xmlns="" class="routput">sort(names(getCurlOptionsConstants()))[1] "autoreferer" "buffersize"[3] "cainfo" "capath"[5] "closepolicy" "connecttimeout"[7] "cookie" "cookiefile"[9] "cookiejar" "cookiesession"[11] "crlf" "customrequest"[13] "debugdata" "debugfunction"[15] "dns.cache.timeout" "dns.use.global.cache"[17] "egdsocket" "encoding"[19] "errorbuffer" "failonerror"[21] "file" "filetime"[23] "followlocation" "forbid.reuse"[25] "fresh.connect" "ftp.create.missing.dirs"[27] "ftp.response.timeout" "ftp.ssl"[29] "ftp.use.eprt" "ftp.use.epsv"[31] "ftpappend" "ftplistonly"[33] "ftpport" "header"[35] "headerfunction" "http.version"[37] "http200aliases" "httpauth"[39] "httpget" "httpheader"[41] "httppost" "httpproxytunnel"[43] "infile" "infilesize"[45] "infilesize.large" "interface"[47] "ipresolve" "krb4level"[49] "low.speed.limit" "low.speed.time"[51] "maxconnects" "maxfilesize"[53] "maxfilesize.large" "maxredirs"[55] "netrc" "netrc.file"[57] "nobody" "noprogress"[59] "nosignal" "port"[61] "post" "postfields"[63] "postfieldsize" "postfieldsize.large"[65] "postquote" "prequote"[67] "private" "progressdata"[69] "progressfunction" "proxy"[71] "proxyauth" "proxyport"[73] "proxytype" "proxyuserpwd"[75] "put" "quote"[77] "random.file" "range"[79] "readfunction" "referer"[81] "resume.from" "resume.from.large"[83] "share" "ssl.cipher.list"[85] "ssl.ctx.data" "ssl.ctx.function"[87] "ssl.verifyhost" "ssl.verifypeer"[89] "sslcert" "sslcertpasswd"[91] "sslcerttype" "sslengine"[93] "sslengine.default" "sslkey"[95] "sslkeypasswd" "sslkeytype"[97] "sslversion" "stderr"[99] "tcp.nodelay" "telnetoptions"[101] "timecondition" "timeout"[103] "timevalue" "transfertext"[105] "unrestricted.auth" "upload"[107] "url" "useragent"[109] "userpwd" "verbose"[111] "writefunction" "writeheader"[113] "writeinfo"</pre>Each of these and what it controls is described in the libcurl man(ual) pagefor <i xmlns="">curl_easy_setopt</i>and that is the authoritative documentation.Anything we provide here is merely repetition or additionalexplanation.<p></p>The names of the options require a slight explanation. Thesecorrespond to symbolic names in the C code of libcurl. For example,the option <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">url</font> in R corresponds to<i xmlns="" xmlns:c="http://www.C.org" xmlns:cpp="http://www.cplusplus.org" class="cenumValue">CURLOPT_URL</i> in C. Firstly, uppercaseletters are annoying to type and read, so we have mapped them to lowercase letters in R. We have also removed the prefix "CURLOPT_" sincewe know the context in which they option names are being used. Andlastly, any option names that have a _ (after we have removed theCURLOPT_ prefix) are changed to replace the '_' with a '.' so we cantype them in R without having to quote them. For example, combiningthese three rules, "CURLOPT_URL" becomes <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">url</font> and<i xmlns="" xmlns:c="http://www.C.org" xmlns:cpp="http://www.cplusplus.org" class="cenumValue">CURLOPT_NETRC_FILE</i> becomes <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">netrc.file</font>.That is the mapping scheme.<p></p>The code that handles options in RCurl automaticallymaps the user's inputs to lower case. This meansthat you can use any mixture of upper-casethat makes your code more readable to you and others.For example, we mightwrite<code xmlns="" class="Sexpression">writeFunction = basicTextGatherer()</code>or<code xmlns="" class="Sexpression">HTTPHeader = c(Accept="text/html")</code><p></p>We specify one or more options by using the names. To makeinteractive use easier, we perform partial matching on the namesrelative to the set of know names. So, for example, we could specify<pre xmlns="" class="S">getURL("https://www.omegahat.net/RCurl/testPassword",verbose = TRUE)</pre><br xmlns="">or, more succinctly,<pre xmlns="" class="S">getURL("https://www.omegahat.net/RCurl/testPassword",v = TRUE)</pre><br xmlns="">Obviously, the first is more readable and less ambiguous.Please use the full form when writing"software". But you might use the abbreviated form whenworking interactively.<p></p>Each option expects a certain type of value from R.For example, the following optionsexpect a number or logical value.<pre xmlns="" class="routput">[1] "autoreferer" "buffersize"[3] "closepolicy" "connecttimeout"[5] "cookiesession" "crlf"[7] "dns.cache.timeout" "dns.use.global.cache"[9] "failonerror" "followlocation"[11] "forbid.reuse" "fresh.connect"[13] "ftp.create.missing.dirs" "ftp.response.timeout"[15] "ftp.ssl" "ftp.use.eprt"[17] "ftp.use.epsv" "ftpappend"[19] "ftplistonly" "header"[21] "http.version" "httpauth"[23] "httpget" "httpproxytunnel"[25] "infilesize" "ipresolve"[27] "low.speed.limit" "low.speed.time"[29] "maxconnects" "maxfilesize"[31] "maxredirs" "netrc"[33] "nobody" "noprogress"[35] "nosignal" "port"[37] "post" "postfieldsize"[39] "proxyauth" "proxyport"[41] "proxytype" "put"[43] "resume.from" "ssl.verifyhost"[45] "ssl.verifypeer" "sslengine.default"[47] "sslversion" "tcp.nodelay"[49] "timecondition" "timeout"[51] "timevalue" "transfertext"[53] "unrestricted.auth" "upload"[55] "verbose"</pre>The <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">connecttimeout</font> gives the maximum numberof seconds the connection should take beforeraising an error, so this is a number.The <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">header</font> option, on the other hand,is merely a flag to indicate whether header informationfrom the response should be included.So this can be a logical value (or a number that is0 to say FALSE or non-zero for TRUE.)At present, all numbers passed from R are converted to<i xmlns="" class="$class">long</i> when used in libcurl.<p></p>Many options are specified as strings.For example, we can specifythe user password for a URI as<pre xmlns="" class="S">getURL("https://www.omegahat.net/RCurl/testPassword/index.html", userpwd = "bob:duncantl", verbose = TRUE)</pre><br xmlns="">Note that we also turned on the "verbose" option so that we can see what libcurl is doing.This is extremely convenient when trying to understand why things aren'tworking (or are working in a particular way!).<p></p>Another example of using strings is tospecify a <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">referer</font> URI and a user-agent.<pre xmlns="" class="S">getURL("https://www.omegahat.net/RCurl/index.html", useragent="RCurl", referer="https://www.omegahat.net")</pre><br xmlns="">(Again, you might want to turn on the "verbose" optionto see what libcurl is doing with this information.)<p></p>The libcurl facilities allow us to not only set our own values forfields used in the HTTP request header (such as the <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">referer</font> oruser-agent), but it also allows us to set an entire collection of newfields or replacements for any existing field. We do this in R usingthe <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">httpheader</font> option for libcurl and we specify a value which is anamed character vector.For example, suppose we want to provide a valuefor the Accept field and add a new field named,say, Made-up-field.We could do this in the request as<pre xmlns="" class="S">getURL("https://www.omegahat.net/RCurl", httpheader = c(Accept="text/html", 'Made-up-field' = "bob"))</pre><br xmlns="">If you turn on the verbose option again for this request, you will see thesefields being set.<pre xmlns="" class="routput">> getURL("https://www.omegahat.net", httpheader = c(Accept="text/html", 'Made-up-field' = "bob"), verbose = TRUE)* About to connect() to www.omegahat.net port 80* Connected to www.omegahat.net (169.237.46.32) port 80> GET / HTTP/1.1Host: www.omegahat.netPragma: no-cacheAccept: text/htmlMade-up-field: bob</pre>(Note that not all servers will tolerate setting header fields arbitrarilyand may return an error.)<p></p>The key thing to note is that headers are specified as name-valuepairs in a character vector. R takes these and pastes the name andvalue together and passes the resulting character vector to libcurl.So while it is convenient to express the headers as<pre xmlns="" class="S">c(name = "value", name = "value")</pre><br xmlns="">if you already have the data in the form<pre xmlns="" class="S">c("name: value", "name: value")</pre><br xmlns="">you can use that directly.<p></p>Some of the libcurl options expect a C routine. For example, whenlibcurl is receiving the response from the HTTP server, it will callthe C routine specified via the option <i xmlns="" xmlns:c="http://www.C.org" xmlns:cpp="http://www.cplusplus.org" class="cenumValue">CURLOPT_WRITEFUNCTION</i> each timeit has a full buffer of bytes. While it is possible for us to be ableto specify a C routine from R (using<pre xmlns="" class="rfunction">getNativeSymbolInfo</pre><br xmlns="">), we currently don'tsupport this. Instead, it is more natural to specify an R functionwhich is to be called when appropriate. And this is indeed how we dothings in RCurl. One can specify a function for the <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">writefunction</font><font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">writeheader</font> and <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">debugfunction</font> options. (We can add support forthe others such as <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">readfunction</font>.) To use these is quite simple. Weexpect an R function that takes a single argument which is thecharacter of bytes to process. The function can do what it wants withthis argument. Typically, it will accumulate it in a persistentvariable (e.g. using closures) or process it on-the-fly such as addingto a plot, passing it to an HTML parser, ....<p></p>The function <pre xmlns="" class="rfunction">basicTextGatherer</pre><br xmlns=""> is an exampleof the idea and this mechanism is used in<pre xmlns="" class="rfunction">getURL</pre><br xmlns="">. Suppose, for some reason, we wantedto read the header information that was returned by HTTP server in theresponse to our request. (This has interesting things like cookies,content type, etc. that libcurl uses internally, but we may also wantto process.) Then we would firstly use the <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">header</font> option to turn onthe libcurl facility to report the response header information.If we just do this, the header information will be included inthe text that <pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> returns.This is fine, but we will have to separate it outby finding the first line, etc.Instead, it is easier to ask libcurl to hand the headerinformation to use separate from the text/body of the response.We can do this by creating a callback function via the<pre xmlns="" class="rfunction">basicTextGatherer</pre><br xmlns=""> function.<pre xmlns="" class="S">h = basicTextGatherer()txt = getURL("https://www.omegahat.net/RCurl", header = TRUE, headerfunction = h$update)</pre><br xmlns="">All we have done is create a collection of functions (stored in<b xmlns="" class="$" title="">h</b>) and passed the update callback to libcurl. Eachtime libcurl receives more of the headers, it calls this function withthe header text. It may call this just once or several times. Thisdepends on how large the header information is, how libcurl buffersthe information, etc.<p></p>Having called <pre xmlns="" class="rfunction">getURL</pre><br xmlns="">, we have the textfrom the URI. The header information is available from<b xmlns="" class="$" title="">h</b>, specifically its <b xmlns="" class="$" title="">value</b>function element.<pre xmlns="" class="S">h$value()</pre><br xmlns=""><p></p>The <pre xmlns="" class="rfunction">debugGatherer</pre><br xmlns=""> is another example of acallback that can be used with libcurl. If we set the "verbose"option to <i xmlns=""><code>TRUE</code></i>, libcurl will provide a lot of information aboutits actions. By default, these will be written on the console(e.g. stderr). In some cases, we would not want these to be on thescreen but instead, for example, displayed in a GUI or stored in avariable for closer examination. We can do this by providing acallback function for the debugging output via the <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">debugfunction</font>option for libcurl.The <pre xmlns="" class="rfunction">debugGatherer</pre><br xmlns=""> is a simpleone that merely cumulates its inputs in differentcategories and makes them available viathe <b xmlns="" class="$" title="">value</b> function.The setup is easy:<pre xmlns="" class="S">d = debugGatherer()x = getURL("https://www.omegahat.net/RCurl", debugfunction=d$update, verbose = TRUE)</pre><br xmlns="">At the end of the request, again we have the text from the URI in<b xmlns="" class="$" title="">x</b>, but we also have the debugging information.libcurl has called our <b xmlns="" class="$" title="">update</b> functioneach time it has some information (either from theHTTP server or from its own internal dialog).<pre xmlns="" class="routput">(R) names(d$value())[1] "text" "headerIn" "headerOut" "dataIn" "dataOut"</pre>The headerIn and headerOut fields report thetext of the header for the response from the Web serverand for our request respectively.Similarly, the dataIn and dataOut fields givethe body of the response and request.And the text is just messages from libcurl.<p></p>We should note that not all options are (currently)) meaningful in R.For example, it is not <span class="emphasis"><em>currently</em></span> possible to redirectstandard error for libcurl to a different <i xmlns="" class="$class">FILE*</i> viathe "stderr" option. (In the future, we may be able to specify an Rfunction for writing errors from libcurl, but we have not put that inyet.)<div class="section" title="Forms"><div class="titlepage"><div><div><h3 class="title"><a id="id1170723592176"></a>Forms</h3></div></div></div> The RCurl package provides many additionalmechanisms for downloading URIs that R does not currently havebuilt-in. But perhaps the most pressing reason for developing theRCurl package was the need to submit forms. The <a class="xref" href="#bib:odbAccess" title="The RHTMLForms package: creating S functions from HTML forms.">odbAccess</a> package isa package that can read an HTML page with one or more forms and createan S function for each form that allows S users to submit the formprogrammatically rather than requiring interactively browsing thepage, saving the result to a file and then loading it into R.In order for these functions to work, we need to be able to submit thecontents of the form from S as if it came from a regular browser.We use RCurl to do this.<p></p>There are two mechanisms used for submitting HTML forms: GET and POST.Both take a set of name-value pairs giving the argumentsto parameterize the call.The difference between the mechanisms is how thesename-value pairs are delivered to the HTTP server.The GET method puts the name-value pairs of parametersat the end of the URI name,e.g.<div class="literallayout"><p><br></br><br></br>https://www.omegahat.net/cgi-bin/form.pl?a=1&b=2<br></br><br></br></p></div>The POST method expects the name-value pairs to besent as the body of the HTTP request,each put in its own "paragraph" or stanza.This is more complicated but supports sending binary data, etc.<p></p>Which of the GET and POST mechanism is appropriate is specified with the HTML form itself via the<font xmlns="" xmlns:html="http://www.w3.org/TR/html401" class="htmlAttribute">action</font> attribute of the<font xmlns="" xmlns:html="http://www.w3.org/TR/html401" class="htmlTag">FORM</font> itself. To the user, however, the browsertakes care of figuring out the correct way to deliver the name-valuepairs specified by the user when interacting with the components ofthe form. In RCurl, we don't have access to the original HTML form sowe cannot tell what mechanism to use. It is up to the caller todetermine whether to use <pre xmlns="" class="rfunction">getForm</pre><br xmlns=""> or<pre xmlns="" class="rfunction">postForm</pre><br xmlns=""> depending on the value of the<font xmlns="" xmlns:html="http://www.w3.org/TR/html401" class="htmlAttribute">action</font> attribute in the original HTMLfile.<p></p>After determining whether to usePOST or GET, the interface to the functions is typically the same to theuser. Essentially, she need only specify the name-value pairs foreach of the form elements. We do this via a named list or namedcharacter vector. (The list simply allows us to have objects ofdifferent type other than strings!) We must specify all the fields,including the hidden fields, if the the processor on the HTTP serveris to make sense of it. RCurl doesn't try to interpret the name-valuepairs, but just transports them.<p></p>Let's look at an example of sending a query to Google(via HTTP rather than its API).<pre xmlns="" class="S">getForm("http://www.google.com/search", hl="en", lr="", ie="ISO-8859-1", q="RCurl", btnG="Search")</pre><br xmlns="">The result is the HTML you would ordinarily see in your browser.You might use<pre xmlns="" class="rfunction">htmlTreeParse</pre><br xmlns="">to parse it.What is important in the example is that we are specifying the required fieldsin the query as named arguments to R.<pre xmlns="" class="rfunction">getForm</pre><br xmlns=""> takes care of bringing them together and constructingthe full URI name. Note that libcurl also handles escaping thespecial characters, e.g. converting a space to %20.Note that if you wanted to explicitly do this escaping on a stringrather than having libcurl implicitly do it, you canuse <pre xmlns="" class="rfunction">curlEscape</pre><br xmlns="">.Similarly, there is a function<pre xmlns="" class="rfunction">curlUnescape</pre><br xmlns=""> to reverse the escaping and make a string "human-readable".<p></p><pre xmlns="" class="rfunction">postForm</pre><br xmlns=""> is almost identical. Let's submit a POST formto <code>http://www.speakeasy.org/~cgires/perl_form.cgi</code> (nolonger available).<!-- <a class="ulink" href="http://www.speakeasy.org/~cgires/perl_form.cgi" target="_top">http://www.speakeasy.org/~cgires/perl_form.cgi</a><pre xmlns="" class="S"> -->postForm("http://www.speakeasy.org/~cgires/perl_form.cgi","some_text" = "Duncan","choice" = "Ho","radbut" = "eep","box" = "box1, box2")</pre><br xmlns="">Here, the form elements are named some_text, choice, radbut, box. Wehave simply provided values for them. Again, the result is theregular response from the HTTP server.<p></p>Sometimes we already have the arguments in a list. It is slightlymore complex then to pass them to the function via the <b xmlns="">...</b>argument. The two form submission functions in RCurl(<pre xmlns="" class="rfunction">getForm</pre><br xmlns=""> and<pre xmlns="" class="rfunction">postForm</pre><br xmlns="">) also accept the name-valuearguments via the <b xmlns="">...</b> parameter. This arises in programmaticaccess to the functions rather than interactive use.<p></p>Since we use <b xmlns="">...</b> for the name-value pairs of the form, we cannotspecify the libcurl options (unambiguously) in this way and we requirethan any such options to control the HTTP request at the libcurl-levelbe passed via the <i xmlns="" class="rarg">.opts</i> parameter. RCurl and libcurlconstruct the HTTP request and after that, the request is just like aregular URI download. All of the usual techniques for reading theresponse, its header, etc. work.</div></div><div class="section" title="CURL Handles"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="CURLHandle"></a>CURL Handles</h2></div></div></div>The functions we have presented above are the high-level entry pointsthat allow R users to make the common-style HTTP requests. The RCurlpackage is capable of more however. It provides access to the basiclibcurl primitives which one can use to compose more complicated andnon-standard HTTP requests. For the most part, one merely specifieslibcurl options by name to the different functions and these takeeffect for that call. An alternative model (used more in C code) isthat we first create a libcurl object to represent the HTTP request,then we customize it by setting options and then we invoke therequest. This is far more involved than we need in R. There is asimplicity about the <pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> function thatremoves the need to know about the internal C structure representingthe call. However, there are occasions when it is useful to knowabout this and exploit it. Specifically, one can create an instanceof this libcurl "handle" and use it in several requests. This has theadvantage that we do not have to set the options in each call, butrather can do this just once. This saves a marginal amount of time inR by reducing the computations, but it will be essentially negligiblerelative to the network latency involved in the request itself. Whatis more important is that if the sequence of requests are to the sameserver, the libcurl engine can maintain the connection to the serverand avoid having to reestablish it each time. This handshaking isquite expensive, so reusing the "handle" in such situations can yieldnon-trivial performance gains. It is also even possible to "pipeline"requests by sending multiple requests before getting the answer backfor the first one. This again can improve performance.<p></p>Now that we both know about the internal libcurl structures and knowwhy we might be interested in reusing them across requests, thequestion remains how do we do this. It is quite easy. Each of the"action" functions in the package (i.e. that work with libcurldirectly) have a parameter named <i xmlns="" class="rarg">curl</i>. For each ofthese functions, the default value is<pre xmlns="" class="rfunction">getCurlHandle</pre><br xmlns=""> and what this means is that, ifno value is given for <i xmlns="" class="rarg">curl</i>, a new handle is created forthe duration of this call.So it is easy for us to create such a handle before callingone of these functions and then pass that as the valuefor <i xmlns="" class="rarg">curl</i>.For example, we can make two requests to thewww.omegahat.net site using the same handleas follows:<pre xmlns="" class="S">handle = getCurlHandle()a = getURL("https://www.omegahat.net/RCurl", curl = handle)b = getURL("https://www.omegahat.net/", curl = handle)</pre><br xmlns=""><p></p>It is important to remember that if we set any options in any of thecalls, these will be set in the libcurl handle and these will persistacross requests unless they are reset. For example, if we had set the<code xmlns="" class="Sexpression">header=TRUE</code> option in the first callabove, it would remain set for the second call. This can be sometimesinconvenient. In such cases, either use separate libcurl handles, orreset the options.<p></p>The function<pre xmlns="" class="rfunction">dupCurlHandle</pre><br xmlns=""> allows us tocreate a new libcurl handle that is an exact copy of theexisting one. This allows us to quickly reuseexisting settings without having them affectother requests.(The data in the option values are not copied).See <i xmlns="">curl_easy_duphandle</i>.<p></p>By reusing libcurl handles, we avoid reallocatinga new one and potentially benefit from improved connectivity.One downside, however, when reusing handles is that the options we set inR need to be copied as C data since they will persist acrossR function calls in the libcurl handle itself.As a result, there are additional computations needed.Again, this is negligible in almost all casesand will be dominated by the network speed.<p></p>libcurl doesn't have any explicit function for fetching aURL. Instead, it uses a powerful but simple interface which involvesmerely setting the options in the libcurl handle as desired and theninvoking the request. So one just prepares the request and forces itto be sent. This is done via the <pre xmlns="" class="rfunction">curlPerform</pre><br xmlns="">function in R. This is how <pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> is actuallyimplemented.</div><div class="section" title="Statistics on the Request"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723592381"></a>Statistics on the Request</h2></div></div></div>S is a statistical programming language and environmentso why not gather data when we can.The function<pre xmlns="" class="rfunction">getCurlInfo</pre><br xmlns="">allows us to find out information about thelast request made for a given libcurl handle.(Of course, this assumes we have explicitlycreated the handle rather than used the defaultvalue for <i xmlns="" class="rarg">curl</i> and so lost it.)<pre xmlns="" class="S">h = getCurlHandle()getURL("https://www.omegahat.net", curl = h)names(getCurlInfo(h))</pre><br xmlns="">The names of the resulting elements are<pre xmlns="" class="routput">[1] "effective.url" "response.code"[3] "total.time" "namelookup.time"[5] "connect.time" "pretransfer.time"[7] "size.upload" "size.download"[9] "speed.download" "speed.upload"[11] "header.size" "request.size"[13] "ssl.verifyresult" "filetime"[15] "content.length.download" "content.length.upload"[17] "starttransfer.time" "content.type"[19] "redirect.time" "redirect.count"[21] "private" "http.connectcode"[23] "httpauth.avail" "proxyauth.avail"</pre>These provide us the actual name of the URI downloaded afterredirections, etc.; information about the transfer speed, etc.; etc.See the man page for <i xmlns="">curl_easy_getinfo</i>.</div><div class="section" title="libcurl Version Information"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723592415"></a>libcurl Version Information</h2></div></div></div>The RCurl package provides a way to obtainreflectance information about libcurl itself.The function <pre xmlns="" class="rfunction">curlVersion</pre><br xmlns="">returns the contents of the<b xmlns="" xmlns:c="http://www.C.org" xmlns:cpp="http://www.cplusplus.org" class="cstruct">curl_version_info_data</b> structure.<p></p>For my installation,the return value from <pre xmlns="" class="rfunction">curlVersion</pre><br xmlns=""> is<pre xmlns="" class="routput">$age[1] 2$version[1] "7.12.0"$vesion_num[1] 461824$host[1] "powerpc-apple-darwin7.4.0"$featuresipv6 ssl libz ntlm largefile1 4 8 16 512$ssl_version[1] " OpenSSL/0.9.7b"$ssl_version_num[1] 9465903$libz_version[1] "1.2.1"$protocols[1] "ftp" "gopher" "telnet" "dict" "ldap" "http" "file" "https"[9] "ftps"$ares[1] ""$ares_num[1] 0$libidn[1] ""</pre>The help page for the R functionexplains the fields which are hopefullyclear from the names.The only ones that might be obscure are<b xmlns="" class="$" title="">ares</b> and <b xmlns="" class="$" title="">libidn</b>.<b xmlns="" class="$" title="">ares</b> refers to asynchronous domain name server (DNS) lookupfor resolving the IP address (e.g. 128.41.12.2) corresponding to a machine name (e.g. www.omegahat.net)."GNU Libidn is an implementation of the Stringprep, Punycode and IDNA specifications defined by the IETF Internationalized Domain Names (IDN)"(taken from <a class="ulink" href="http://www.gnu.org/software/libidn/" target="_top">http://www.gnu.org/software/libidn/</a>).</div><div class="section" title="Initialization"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723592460"></a>Initialization</h2></div></div></div> As with most C libraries, one musttypically initialize it to get the basic run-time structureestablished. Fortunately, when we call any of the R functions, thisis taken care of implicitly; libcurl handles this when we create a newhandle. However, sometimes it is important to explicitly specifyoptions to control how the library is initialized. RCurl provides away to do this via the <pre xmlns="" class="rfunction">curlGlobalInit</pre><br xmlns="">function. The only argument is a flag that indicates what features toinitialize. For the most part, the defaults work best and we canleave libcurl to perform this initialization. However, we may need tobe careful that we are not re-initializing a setting that R (orgenerally the host application) has already set. This may happen onWindows as libcurl initialize the Win32 socket library.We can avoid this, if necessary, but telling libcurl to initializeonly the SSL facilities.<p></p>The argument to <pre xmlns="" class="rfunction">curlGlobalInit</pre><br xmlns=""> is typically acharacter vector of names of features to turn on. The possible namescan be obtained from <b xmlns="" class="$" title="">CurlGlobalBits</b> which is a namedinteger vector:<pre xmlns="" class="routput">none ssl win32 all0 1 2 3attr(,"class")[1] "CurlGlobalBits" "BitIndicator"</pre>We would call <pre xmlns="" class="rfunction">curlGlobalInit</pre><br xmlns=""> as<pre xmlns="" class="S">curlGlobalInit(c("ssl", "win32"))</pre><br xmlns="">or<pre xmlns="" class="S">curlGlobalInit(c("ssl"))</pre><br xmlns="">to activate both SSL and Win32 sockets,or just SSL respectively.<p></p>One can specify integer values directly, but this isless readable to others (or yourself in a few weeks!).The names are converted and combined to a flag using<pre xmlns="" class="rfunction">setBitIndicators</pre><br xmlns="">.</div><div class="section" title="Options"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723592509"></a>Options</h2></div></div></div>It is hopefully clear that it is the libcurl options that make thisinterface work and allow us to make interesting queries. Fromspecifying the URI to how to read the text, to providing passwords,it is the options that are critical. For the most part, theseoptions are passed by name to functions in RCurl via the <b xmlns="">...</b>mechanism in R and the <i xmlns="" class="rarg">.opts</i> argument.These two collections of arguments are merged, with thosein <b xmlns="">...</b> overriding corresponding ones in the<i xmlns="" class="rarg">.opts</i> object.<p></p>Why do we have the <i xmlns="" class="rarg">.opts</i> argument?The reason is similar to the <i xmlns="" class="rarg">.params</i>in the form functions: often we have the optionsin a list and it is not as convenient to usethe <b xmlns="">...</b> approach.Having both allows the caller/programmer to usewhichever is most convenient.<p></p>One case in which the <i xmlns="" class="rarg">.opts</i> argument is useful is if wewant to prepare a set of options that are to be used in all (or a setof) calls. We can combine these arguments into a list just once andthen pass them to each HTTP request easily by simply using thatvariable. Since we merge the values in <b xmlns="">...</b> and<i xmlns="" class="rarg">.opts</i>, this works nicely.<p></p>To create such a list of options, we can use the function<pre xmlns="" class="rfunction">curlOpts</pre><br xmlns="">. This creates an S3-style objectwith class <i xmlns="">CURLOptions</i>. This function neverinvolves libcurl, but sorts out the names of the options by usingpartial matching (via the <pre xmlns="" class="rfunction">mapCurlOptNames</pre><br xmlns="">function) and returns an R object with the options as name-value pairs in a list.The fact that this is a class means that if we access any elements, thefull names are used, even when we set an element. This means that thenames are kept resolved as we use it in R and correspond unambiguouslyto real libcurl options.<p></p>We can use this function something like the following.<pre xmlns="" class="S">opts = curlOptions(header = TRUE, userpwd = "bob:duncantl", netrc = TRUE)getURL("https://www.omegahat.net/RCurl/testPassword/index.html", verbose = TRUE, .opts = opts)</pre><br xmlns="">Here we create the options ahead of time and use them in a call whilespecifying additional options (i.e. "verbose").<p></p>Some readers will have noticed that we could achieve the same effectof having a set of fixed options that are used in a collection ofcalls by reusing a libcurl handle. We could create the handle, setthe common options, and then use that handle in the set of calls.This is indeed a natural and often good way to do things.The following code does what we want.<pre xmlns="" class="S">h = getCurlHandle(header = TRUE, userpwd = "bob:duncantl", netrc = TRUE)getURL("https://www.omegahat.net/RCurl/testPassword/index.html", verbose = TRUE, curl = h)</pre><br xmlns="">The first line creates a new handle and fills in the three"persistent" options. These are in the handle itself, not in R atthis stage. Now, when we perform the request via<pre xmlns="" class="rfunction">getURL</pre><br xmlns="">, we specify this libcurl handle andprovide the "verbose" option.<p></p>The function <pre xmlns="" class="rfunction">curlSetOpt</pre><br xmlns=""> is used implicitly inthe code above and this actually sets the option-values in a libcurlhandle. It can also be used to simply resolve them.</div><div class="section" title="Example Application"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723592603"></a>Example Application</h2></div></div></div>In this section, we will outline a more complex use of the RCurlfacilities. Specifically, we will use it to send SOAP <a class="xref" href="#bib:SOAP" title="Programming Web Services with SOAP">SOAP</a> requests.SOAP uses HTTP to send XML content that encodes a method invocation.We have to add the appropriate fields to the HTTP header to identifythe SOAP call and then insert the XML that defines the method callin the body of the request.Using our favorite client SOAP facility(e.g. SOAP::Lite, <a class="ulink" href="https://www.omegahat.net/SSOAP/" target="_top">SSOAP</a>),we can send the SOAP request that performs the HTTP request.We can find out what the actual HTTP request looks like usingfacilities in those tools or actually sniffing the packets as they go across the wire usingtcpdump or ethereal or some such tool.This is what we see.The HTTP header in the request is<div class="literallayout"><p><br></br><br></br>POST /hibye.cgi HTTP/1.1<br></br>Connection: close<br></br>Accept: text/xml<br></br>Accept: multipart/*<br></br>Host: services.soaplite.com<br></br>User-Agent: SOAP::Lite/Perl/0.55<br></br>Content-Length: 450<br></br>Content-Type: text/xml; charset=utf-8<br></br>SOAPAction: "http://www.soaplite.com/Demo#hi"<br></br><br></br></p></div>The body of the request is<div class="literallayout"><p><br></br><br></br><?xml version="1.0" encoding="UTF-8"?><br></br><SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" <br></br>xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <br></br>xmlns:xsd="http://www.w3.org/1999/XMLSchema" <br></br>xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" <br></br>xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"><br></br><SOAP-ENV:Body><br></br><namesp1:hi xmlns:namesp1="http://www.soaplite.com/Demo"/><br></br></SOAP-ENV:Body><br></br></SOAP-ENV:Envelope><br></br><br></br></p></div>So we need to add fields to the HTTP header.Specifically, we need<div class="literallayout"><p><br></br>Accept: text/xml<br></br>Accept: multipart/*<br></br>SOAPAction: "http://www.soaplite.com/Demo#hi"<br></br>Content-Type: text/xml; charset=utf-8<br></br></p></div>libcurl should take care of the Content-Length field.The body is specified for the HTTP request using the <font xmlns="" xmlns:curl="https://curl.se/" color="red" class="curlOpt">postfields</font> option.To do this using the <i xmlns=""><a href="https://cran.r-project.org/package=RCurl">RCurl</a></i> package, we usethe following code.<pre xmlns="" class="S">body = '<?xml version="1.0" encoding="UTF-8"?>\<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" \xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" \xmlns:xsd="http://www.w3.org/1999/XMLSchema" \xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" \xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">\<SOAP-ENV:Body>\<namesp1:hi xmlns:namesp1="http://www.soaplite.com/Demo"/>\</SOAP-ENV:Body>\</SOAP-ENV:Envelope>\n'curlPerform(url="http://services.soaplite.com/hibye.cgi",httpheader=c(Accept="text/xml", Accept="multipart/*", SOAPAction='"http://www.soaplite.com/Demo#hi"','Content-Type' = "text/xml; charset=utf-8"),postfields=body,verbose = TRUE)</pre><br xmlns="">Note that this similar to calling <pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> and we have used it toillustrate how we can use <pre xmlns="" class="rfunction">curlPerform</pre><br xmlns="">directly. The only difference is that the result is printed to theconsole, not returned to us as a character vector. This is a problemwhen we really want to process the response. So for that, we wouldsimply replace the call to <pre xmlns="" class="rfunction">curlPerform</pre><br xmlns=""> with<pre xmlns="" class="rfunction">getURL</pre><br xmlns="">:<pre xmlns="" class="S">curlPerform(url="http://services.soaplite.com/hibye.cgi",httpheader=c(Accept="text/xml", Accept="multipart/*", SOAPAction='"http://www.soaplite.com/Demo#hi"','Content-Type' = "text/xml; charset=utf-8"),postfields=body,verbose = TRUE)</pre><br xmlns=""></div><div class="section" title="Additional Notes"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id1170723592682"></a>Additional Notes</h2></div></div></div>These were written before the package was finished.They are left here, but may not be helpful.<div class="section" title="Providing Text Handlers"><div class="titlepage"><div><div><h3 class="title"><a id="id1170723592688"></a>Providing Text Handlers</h3></div></div></div>One can provide a different text handler to consume/process the textthat is received by the libcurl engine from the HTTP response. We cando this with either an S function or with a C routine. At the lowestlevel, there is a C routine, but R users will typically be mostcomfortable with a higher-level function. Functions are more robust,and also provide a more obvious way of maintaining state. We useclosures and environments in R to endow a function with its own localvariables that persist across calls.<p></p>One can envisage a scenario in which the text handler would want to beable to access the CURL handle that was being used in the request. Forexample, it might use this to find the base URL, to determine theactual host, or identify settings that are in effect, or simply reusethe handle (by duplicating it). Rather than make the curl objectexplicitly available, one can initialize it separately ahead of thecall to <pre xmlns="" class="rfunction">getURL</pre><br xmlns="">, and then make it available to the Rfunction as a variable in the function's environment.A more realistic example is the following. Suppose we want to parseHTML files and follow the links within those files to find theirlinks. This is a spider or 'bot. We can fetch the entire document via<pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> and then parse it (using<pre xmlns="" class="rfunction">htmlTreeParse</pre><br xmlns="">). Alternatively, we can use<pre xmlns="" class="rfunction">htmlTreeParse</pre><br xmlns=""> and provide it with a connection fromwhich the HTML/XML parser requests content as it is needed. We canthen setup this connection from which the XML parser reads by havingit be supplied by the <pre xmlns="" class="rfunction">getURL</pre><br xmlns=""> text gatherer. We can parse a documentand collect the names of all the links to which it refers and thenprocess each of them. An alternative is to process an individual linkwhen it is discovered. There are trade-offs between the twoapproaches. However, it is good to be able to do both.<p></p>In order to do this, we might want to have access to the CURLhandle within the XML parser. When we handle an HREF element(i.e. <a href=>), we would then duplicate the handle and startanother HTML parser.<p></p>Obviously, this example is also somewhat contrived as the XML/HTMLparsing facilities have their own HTTP facilities. However, they donot understand all the finer points of HTTP such as SSL, FTP, passwords,etc.<div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="http://docbook.sourceforge.net/release/xsl/current/images/note.svg"></img></td><th align="left">Note</th></tr><tr><td align="left" valign="top">This is not a very compelling example anymore!</td></tr></table></div></div><div class="section" title="Alternatives"><div class="titlepage"><div><div><h3 class="title"><a id="id1170723592740"></a>Alternatives</h3></div></div></div><p>Using libcurl is by no means the onlyapproach to getting HTTP access in R. Firstly, we have HTTP access inR via the facilities incorporated from libxml (nanohttp and nanoftp).These are, as the names suggest, basic implementations of the protocolsand do not provide all the bells and whistles we might need generally.Also, they are not customizable from within R. Specifically, we cannotadd header fields, handle binary data, set the body of the request, etc.</p><p>We can use R's socket connections and implement the details of HTTPourselves. There is a great deal of work in this as we have discussedbefore. Also, we currently don't have secure sockets (i.e. using SSL)in R<sup>[<a id="id1170723592757" href="#ftn.id1170723592757" class="footnote">1</a>]</sup> I initially started using this approachso that I could discover the nuances of HTTP. It quickly getsoverwhelming to handle all the details. It is more tedious thantechnically challenging, especially when others have done it alreadyin C libraries and done it well. The code that I have is in anunreleased package named <i xmlns="">httpClient</i>. If anyone is interested, pleasecontact me. Using R's sockets is also used in the <a class="ulink" href="https://cran.r-project.org/package=httpRequest" target="_top">httpRequest</a>package on CRAN. This allows submitting forms and retrieving URIs.It is useful and, as the authors state, a "basic HTTP request"implementation. It doesn't escape characters, handle chunkedresponses, do redirects, support SSL, etc. It is flexible but leavesa lot to the user to do to setup the request and process the response.RCurl inherits many, many good features for "free" from libcurl.</p><p>libcurl is not the only C-level library that we could have used.Alternative libraries include <a class="ulink" href="https://dev.w3.org/libwww/Library//" target="_top">libwww</a> from the W3 group. We<span class="emphasis"><em>may</em></span> find that that is more suitable, but libcurlwill definitely suffice for the present.<!-- </p></div><div class="section" title="Notes"><div class="titlepage"><div><div><h3 class="title"><a id="id1170723592792"></a>Notes</h3></div></div></div><p> --><!-- libcurl can use <a class="ulink" href="ftp://athena-dist.mit.edu/pub/ATHENA/ares/" target="_top">ares</a> for --><!-- asynchronous DNS resolution. --></p></div></div><div class="section"><div class="titlepage"></div><div class="bibliography" title="Bibliography"><div class="titlepage"><div><div><h3 class="title"><a id="id1170723592811"></a>Bibliography</h3></div></div></div><div class="biblioentry" title="The RHTMLForms package: creating S functions from HTML forms."><a id="bib:odbAccess"></a><p>[1] <span class="authorgroup"><span class="firstname">Duncan</span> <span class="surname">Temple Lang</span>, <span class="firstname">Sandrine</span> <span class="surname">Dudoit</span>, and <span class="firstname">Sunduz</span> <span class="surname">Keles</span>. </span><span class="title"><em>The RHTMLForms package: creating S functions from HTML forms.</em>. </span><a class="ulink" href="https://www.omegahat.net/RHTMLForms/" target="_top">RHTMLForms</a></p></div><div class="biblioentry" title="Programming Web Services with SOAP"><a id="bib:SOAP"></a><p>[2] <span class="authorgroup"><span class="firstname">James</span> <span class="surname">Snell</span>, <span class="firstname">Doug</span> <span class="surname">Tidwell</span>, and <span class="firstname">Pavel</span> <span class="surname">Kulchenko</span>. </span><span class="title"><em>Programming Web Services with SOAP</em>. </span><a class="ulink" href="http://www.oreilly.com/catalog/progwebsoap/" target="_top">O'Reilly</a></p></div></div></div><div class="footnotes"><br></br><hr width="100" align="left"></hr><div class="footnote"><p><sup>[<a id="ftn.id1170723592757" href="#id1170723592757" class="para">1</a>] </sup>I have a local version (not with SSL) but they are notconnections since the connection data structure is not exposed in theR API, yet!</p></div></div></div></body></html>