You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
14 KiB
315 lines
14 KiB
<!-- |
|
HTTP and IPP API introduction for CUPS. |
|
|
|
Copyright 2007-2012 by Apple Inc. |
|
Copyright 1997-2006 by Easy Software Products, all rights reserved. |
|
|
|
These coded instructions, statements, and computer programs are the |
|
property of Apple Inc. and are protected by Federal copyright |
|
law. Distribution and use rights are outlined in the file "LICENSE.txt" |
|
which should have been included with this file. If this file is |
|
file is missing or damaged, see the license at "http://www.cups.org/". |
|
--> |
|
|
|
<h2 class='title'><a name='OVERVIEW'>Overview</a></h2> |
|
|
|
<p>The CUPS HTTP and IPP APIs provide low-level access to the HTTP and IPP |
|
protocols and CUPS scheduler. They are typically used by monitoring and |
|
administration programs to perform specific functions not supported by the |
|
high-level CUPS API functions.</p> |
|
|
|
<p>The HTTP APIs use an opaque structure called |
|
<a href='#http_t'><code>http_t</code></a> to manage connections to |
|
a particular HTTP or IPP server. The |
|
<a href='#httpConnectEncrypt'><code>httpConnectEncrypt</code></a> function is |
|
used to create an instance of this structure for a particular server. |
|
The constant <code>CUPS_HTTP_DEFAULT</code> can be used with all of the |
|
<code>cups</code> functions to refer to the default CUPS server - the functions |
|
create a per-thread <a href='#http_t'><code>http_t</code></a> as needed.</p> |
|
|
|
<p>The IPP APIs use two opaque structures for requests (messages sent to the CUPS scheduler) and responses (messages sent back to your application from the scheduler). The <a href='#ipp_t'><code>ipp_t</code></a> type holds a complete request or response and is allocated using the <a href='#ippNew'><code>ippNew</code></a> or <a href='#ippNewRequest'><code>ippNewRequest</code></a> functions and freed using the <a href='#ippDelete'><code>ippDelete</code></a> function.</p> |
|
|
|
<p>The second opaque structure is called <a href='#ipp_attribute_t'><code>ipp_attribute_t</code></a> and holds a single IPP attribute which consists of a group tag (<a href='#ippGetGroupTag'><code>ippGetGroupTag</code></a>), a value type tag (<a href='#ippGetValueTag'><code>ippGetValueTag</code></a>), the attribute name (<a href='#ippGetName'><code>ippGetName</code></a>), and 1 or more values (<a href='#ippGetCount'><code>ippGetCount</code></a>, <a href='#ippGetBoolean'><code>ippGetBoolean</code></a>, <a href='#ippGetCollection'><code>ippGetCollection</code></a>, <a href='#ippGetDate'><code>ippGetDate</code></a>, <a href='#ippGetInteger'><code>ippGetInteger</code></a>, <a href='#ippGetRange'><code>ippGetRange</code></a>, <a href='#ippGetResolution'><code>ippGetResolution</code></a>, and <a href='#ippGetString'><code>ippGetString</code></a>). Attributes are added to an <a href='#ipp_t'><code>ipp_t</code></a> pointer using one of the <code>ippAdd</code> functions. For example, use <a href='#ippAddString'><code>ippAddString</code></a> to add the "printer-uri" and "requesting-user-name" string attributes to a request:</p> |
|
|
|
<pre class='example'> |
|
<a href='#ipp_t'>ipp_t</a> *request = <a href='#ippNewRequest'>ippNewRequest</a>(IPP_GET_JOBS); |
|
|
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", |
|
NULL, "ipp://localhost/printers/"); |
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", |
|
NULL, cupsUser()); |
|
</pre> |
|
|
|
<p>Once you have created an IPP request, use the <code>cups</code> functions to send the request to and read the response from the server. For example, the <a href='#cupsDoRequest'><code>cupsDoRequest</code></a> function can be used for simple query operations that do not involve files:</p> |
|
|
|
<pre class='example'> |
|
#include <cups/cups.h> |
|
|
|
|
|
<a href='#ipp_t'>ipp_t</a> *<a name='get_jobs'>get_jobs</a>(void) |
|
{ |
|
<a href='#ipp_t'>ipp_t</a> *request = <a href='#ippNewRequest'>ippNewRequest</a>(IPP_GET_JOBS); |
|
|
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", |
|
NULL, "ipp://localhost/printers/"); |
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", |
|
NULL, cupsUser()); |
|
|
|
return (<a href='#cupsDoRequest'>cupsDoRequest</a>(CUPS_HTTP_DEFAULT, request, "/")); |
|
} |
|
</pre> |
|
|
|
<p>The <a href='#cupsDoRequest'><code>cupsDoRequest</code></a> function frees the request and returns an IPP response or <code>NULL</code> pointer if the request could not be sent to the server. Once you have a response from the server, you can either use the <a href='#ippFindAttribute'><code>ippFindAttribute</code></a> and <a href='#ippFindNextAttribute'><code>ippFindNextAttribute</code></a> functions to find specific attributes, for example:</p> |
|
|
|
<pre class='example'> |
|
<a href='#ipp_t'>ipp_t</a> *response; |
|
<a href='#ipp_attribute_t'>ipp_attribute_t</a> *attr; |
|
|
|
attr = <a href='#ippFindAttribute'>ippFindAttribute</a>(response, "printer-state", IPP_TAG_ENUM); |
|
</pre> |
|
|
|
<p>You can also walk the list of attributes with a simple <code>for</code> loop like this:</p> |
|
|
|
<pre class='example'> |
|
<a href='#ipp_t'>ipp_t</a> *response; |
|
<a href='#ipp_attribute_t'>ipp_attribute_t</a> *attr; |
|
|
|
for (attr = <a href='#ippFirstAttribute'>ippFirstAttribute</a>(response); attr != NULL; attr = <a href='#ippNextAttribute'>ippNextAttribute</a>(response)) |
|
if (ippGetName(attr) == NULL) |
|
puts("--SEPARATOR--"); |
|
else |
|
puts(ippGetName(attr)); |
|
</pre> |
|
|
|
<p>The <code>for</code> loop approach is normally used when collecting attributes for multiple objects (jobs, printers, etc.) in a response. Attributes with <code>NULL</code> names indicate a separator between the attributes of each object. For example, the following code will list the jobs returned from our previous <a href='#get_jobs'><code>get_jobs</code></a> example code:</p> |
|
|
|
<pre class='example'> |
|
<a href='#ipp_t'>ipp_t</a> *response = <a href='#get_jobs'>get_jobs</a>(); |
|
|
|
if (response != NULL) |
|
{ |
|
<a href='#ipp_attribute_t'>ipp_attribute_t</a> *attr; |
|
const char *attrname; |
|
int job_id = 0; |
|
const char *job_name = NULL; |
|
const char *job_originating_user_name = NULL; |
|
|
|
puts("Job ID Owner Title"); |
|
puts("------ ---------------- ---------------------------------"); |
|
|
|
for (attr = <a href='#ippFirstAttribute'>ippFirstAttribute</a>(response); attr != NULL; attr = <a href='#ippNextAttribute'>ippNextAttribute</a>(response)) |
|
{ |
|
/* Attributes without names are separators between jobs */ |
|
attrname = ippGetName(attr); |
|
if (attrname == NULL) |
|
{ |
|
if (job_id > 0) |
|
{ |
|
if (job_name == NULL) |
|
job_name = "(withheld)"; |
|
|
|
if (job_originating_user_name == NULL) |
|
job_originating_user_name = "(withheld)"; |
|
|
|
printf("%5d %-16s %s\n", job_id, job_originating_user_name, job_name); |
|
} |
|
|
|
job_id = 0; |
|
job_name = NULL; |
|
job_originating_user_name = NULL; |
|
continue; |
|
} |
|
else if (!strcmp(attrname, "job-id") && ippGetValueTag(attr) == IPP_TAG_INTEGER) |
|
job_id = ippGetInteger(attr, 0); |
|
else if (!strcmp(attrname, "job-name") && ippGetValueTag(attr) == IPP_TAG_NAME) |
|
job_name = ippGetString(attr, 0, NULL); |
|
else if (!strcmp(attrname, "job-originating-user-name") && |
|
ippGetValueTag(attr) == IPP_TAG_NAME) |
|
job_originating_user_name = ippGetString(attr, 0, NULL); |
|
} |
|
|
|
if (job_id > 0) |
|
{ |
|
if (job_name == NULL) |
|
job_name = "(withheld)"; |
|
|
|
if (job_originating_user_name == NULL) |
|
job_originating_user_name = "(withheld)"; |
|
|
|
printf("%5d %-16s %s\n", job_id, job_originating_user_name, job_name); |
|
} |
|
} |
|
</pre> |
|
|
|
<h3><a name='CREATING_URI_STRINGS'>Creating URI Strings</a></h3> |
|
|
|
<p>To ensure proper encoding, the |
|
<a href='#httpAssembleURIf'><code>httpAssembleURIf</code></a> function must be |
|
used to format a "printer-uri" string for all printer-based requests:</p> |
|
|
|
<pre class='example'> |
|
const char *name = "Foo"; |
|
char uri[1024]; |
|
<a href='#ipp_t'>ipp_t</a> *request; |
|
|
|
<a href='#httpAssembleURIf'>httpAssembleURIf</a>(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, cupsServer(), |
|
ippPort(), "/printers/%s", name); |
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); |
|
</pre> |
|
|
|
<h3><a name='SENDING_REQUESTS_WITH_FILES'>Sending Requests with Files</a></h3> |
|
|
|
<p>The <a href='#cupsDoFileRequest'><code>cupsDoFileRequest</code></a> and |
|
<a href='#cupsDoIORequest'><code>cupsDoIORequest</code></a> functions are |
|
used for requests involving files. The |
|
<a href='#cupsDoFileRequest'><code>cupsDoFileRequest</code></a> function |
|
attaches the named file to a request and is typically used when sending a print |
|
file or changing a printer's PPD file:</p> |
|
|
|
<pre class='example'> |
|
const char *filename = "/usr/share/cups/data/testprint.ps"; |
|
const char *name = "Foo"; |
|
char uri[1024]; |
|
char resource[1024]; |
|
<a href='#ipp_t'>ipp_t</a> *request = <a href='#ippNewRequest'>ippNewRequest</a>(IPP_PRINT_JOB); |
|
<a href='#ipp_t'>ipp_t</a> *response; |
|
|
|
/* Use httpAssembleURIf for the printer-uri string */ |
|
<a href='#httpAssembleURIf'>httpAssembleURIf</a>(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, cupsServer(), |
|
ippPort(), "/printers/%s", name); |
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); |
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", |
|
NULL, cupsUser()); |
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", |
|
NULL, "testprint.ps"); |
|
|
|
/* Use snprintf for the resource path */ |
|
snprintf(resource, sizeof(resource), "/printers/%s", name); |
|
|
|
response = <a href='#cupsDoFileRequest'>cupsDoFileRequest</a>(CUPS_HTTP_DEFAULT, request, resource, filename); |
|
</pre> |
|
|
|
<p>The <a href='#cupsDoIORequest'><code>cupsDoIORequest</code></a> function |
|
optionally attaches a file to the request and optionally saves a file in the |
|
response from the server. It is used when using a pipe for the request |
|
attachment or when using a request that returns a file, currently only |
|
<code>CUPS_GET_DOCUMENT</code> and <code>CUPS_GET_PPD</code>. For example, |
|
the following code will download the PPD file for the sample HP LaserJet |
|
printer driver:</p> |
|
|
|
<pre class='example'> |
|
char tempfile[1024]; |
|
int tempfd; |
|
<a href='#ipp_t'>ipp_t</a> *request = <a href='#ippNewRequest'>ippNewRequest</a>(CUPS_GET_PPD); |
|
<a href='#ipp_t'>ipp_t</a> *response; |
|
|
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name", |
|
NULL, "laserjet.ppd"); |
|
|
|
tempfd = cupsTempFd(tempfile, sizeof(tempfile)); |
|
|
|
response = <a href='#cupsDoIORequest'>cupsDoIORequest</a>(CUPS_HTTP_DEFAULT, request, "/", -1, tempfd); |
|
</pre> |
|
|
|
<p>The example passes <code>-1</code> for the input file descriptor to specify |
|
that no file is to be attached to the request. The PPD file attached to the |
|
response is written to the temporary file descriptor we created using the |
|
<code>cupsTempFd</code> function.</p> |
|
|
|
<h3><a name='ASYNCHRONOUS_REQUEST_PROCESSING'>Asynchronous Request Processing</a></h3> |
|
|
|
<p>The <a href='#cupsSendRequest'><code>cupsSendRequest</code></a> and |
|
<a href='#cupsGetResponse'><code>cupsGetResponse</code></a> support |
|
asynchronous communications with the server. Unlike the other request |
|
functions, the IPP request is not automatically freed, so remember to |
|
free your request with the <a href='#ippDelete'><code>ippDelete</code></a> |
|
function.</p> |
|
|
|
<p>File data is attached to the request using the |
|
<a href='#cupsWriteRequestData'><code>cupsWriteRequestData</code></a> |
|
function, while file data returned from the server is read using the |
|
<a href='#cupsReadResponseData'><code>cupsReadResponseData</code></a> |
|
function. We can rewrite the previous <code>CUPS_GET_PPD</code> example |
|
to use the asynchronous functions quite easily:</p> |
|
|
|
<pre class='example'> |
|
char tempfile[1024]; |
|
int tempfd; |
|
<a href='#ipp_t'>ipp_t</a> *request = <a href='#ippNewRequest'>ippNewRequest</a>(CUPS_GET_PPD); |
|
<a href='#ipp_t'>ipp_t</a> *response; |
|
|
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name", |
|
NULL, "laserjet.ppd"); |
|
|
|
tempfd = cupsTempFd(tempfile, sizeof(tempfile)); |
|
|
|
if (<a href='#cupsSendRequest'>cupsSendRequest</a>(CUPS_HTTP_DEFAULT, request, "/") == HTTP_CONTINUE) |
|
{ |
|
response = <a href='#cupsGetResponse'>cupsGetResponse</a>(CUPS_HTTP_DEFAULT, "/"); |
|
|
|
if (response != NULL) |
|
{ |
|
ssize_t bytes; |
|
char buffer[8192]; |
|
|
|
while ((bytes = <a href='#cupsReadResponseData'>cupsReadResponseData</a>(CUPS_HTTP_DEFAULT, buffer, sizeof(buffer))) > 0) |
|
write(tempfd, buffer, bytes); |
|
} |
|
} |
|
|
|
/* Free the request! */ |
|
<a href='#ippDelete'>ippDelete</a>(request); |
|
</pre> |
|
|
|
<p>The <a href='#cupsSendRequest'><code>cupsSendRequest</code></a> function |
|
returns the initial HTTP request status, typically either |
|
<code>HTTP_CONTINUE</code> or <code>HTTP_UNAUTHORIZED</code>. The latter status |
|
is returned when the request requires authentication of some sort. The |
|
<a href='#cupsDoAuthentication'><code>cupsDoAuthentication</code></a> function |
|
must be called when your see <code>HTTP_UNAUTHORIZED</code> and the request |
|
re-sent. We can add authentication support to our example code by using a |
|
<code>do ... while</code> loop:</p> |
|
|
|
<pre class='example'> |
|
char tempfile[1024]; |
|
int tempfd; |
|
<a href='#ipp_t'>ipp_t</a> *request = <a href='#ippNewRequest'>ippNewRequest</a>(CUPS_GET_PPD); |
|
<a href='#ipp_t'>ipp_t</a> *response; |
|
http_status_t status; |
|
|
|
<a href='#ippAddString'>ippAddString</a>(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name", |
|
NULL, "laserjet.ppd"); |
|
|
|
tempfd = cupsTempFd(tempfile, sizeof(tempfile)); |
|
|
|
/* Loop for authentication */ |
|
do |
|
{ |
|
status = <a href='#cupsSendRequest'>cupsSendRequest</a>(CUPS_HTTP_DEFAULT, request, "/"); |
|
|
|
if (status == HTTP_UNAUTHORIZED) |
|
{ |
|
/* Try to authenticate, break out of the loop if that fails */ |
|
if (<a href='#cupsDoAuthentication'>cupsDoAuthentication</a>(CUPS_HTTP_DEFAULT, "POST", "/")) |
|
break; |
|
} |
|
} |
|
while (status != HTTP_CONTINUE && status != HTTP_UNAUTHORIZED); |
|
|
|
if (status == HTTP_CONTINUE) |
|
{ |
|
response = <a href='#cupsGetResponse'>cupsGetResponse</a>(CUPS_HTTP_DEFAULT, "/"); |
|
|
|
if (response != NULL) |
|
{ |
|
ssize_t bytes; |
|
char buffer[8192]; |
|
|
|
while ((bytes = <a href='#cupsReadResponseData'>cupsReadResponseData</a>(CUPS_HTTP_DEFAULT, buffer, sizeof(buffer))) > 0) |
|
write(tempfd, buffer, bytes); |
|
} |
|
} |
|
|
|
/* Free the request! */ |
|
<a href='#ippDelete'>ippDelete</a>(request); |
|
</pre>
|
|
|