The qDecoder Project

[svn] / releases / qDecoder-10.1.0 / src / qHttpClient.c

Parent Directory Parent Directory Revision Log Revision Log


Revision 539 - Download Blame
Wed Jan 27 19:58:31 2010 UTC (7 months, 1 week ago) by wolkykim
File size: 25115 byte(s)
qDecoder 10.1.0 Release
    1 /*
    2  * Copyright 2008 The qDecoder Project. All rights reserved.
    3  *
    4  * Redistribution and use in source and binary forms, with or without
    5  * modification, are permitted provided that the following conditions
    6  * are met:
    7  *
    8  * 1. Redistributions of source code must retain the above copyright
    9  *    notice, this list of conditions and the following disclaimer.
   10  * 2. Redistributions in binary form must reproduce the above copyright
   11  *    notice, this list of conditions and the following disclaimer in the
   12  *    documentation and/or other materials provided with the distribution.
   13  *
   14  * THIS SOFTWARE IS PROVIDED BY THE QDECODER PROJECT ``AS IS'' AND ANY
   15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   17  * DISCLAIMED. IN NO EVENT SHALL THE QDECODER PROJECT BE LIABLE FOR ANY
   18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   24  */
   25 
   26 /**
   27  * @file qHttpClient.c HTTP client API
   28  */
   29 
   30 #ifndef DISABLE_SOCKET
   31 
   32 #include <stdio.h>
   33 #include <stdlib.h>
   34 #include <stdbool.h>
   35 #include <string.h>
   36 #include <unistd.h>
   37 #include <errno.h>
   38 #include <sys/types.h>
   39 #include <sys/socket.h>
   40 #include <netinet/in.h>
   41 #include <arpa/inet.h>
   42 #include "qDecoder.h"
   43 #include "qInternal.h"
   44 
   45 #ifndef _DOXYGEN_SKIP
   46 
   47 static bool _open(Q_HTTPCLIENT *client);
   48 static void _setTimeout(Q_HTTPCLIENT *client, int timeoutms);
   49 static void _setKeepalive(Q_HTTPCLIENT *client, bool keepalive);
   50 static void _setUseragent(Q_HTTPCLIENT *client, const char *agentname);
   51 static bool _sendRequest(Q_HTTPCLIENT *client, const char *method, const char *uri, Q_ENTRY *reqheaders);
   52 static int _readResponse(Q_HTTPCLIENT *client, Q_ENTRY *resheaders);
   53 static bool _get(Q_HTTPCLIENT *client, const char *uri, int fd, off_t *savesize, int *rescode,
   54 		Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
   55 		bool (*callback)(void *userdata, off_t recvbytes), void *userdata);
   56 static bool _put(Q_HTTPCLIENT *client, const char *uri, int fd, off_t length, int *rescode,
   57 		Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
   58 		bool (*callback)(void *userdata, off_t sentbytes), void *userdata);
   59 static void *_cmd(Q_HTTPCLIENT *client, const char *method, const char *uri,
   60 		int *rescode, size_t *contentslength,
   61 		Q_ENTRY *reqheaders, Q_ENTRY *resheaders);
   62 static bool _close(Q_HTTPCLIENT *client);
   63 static void _free(Q_HTTPCLIENT *client);
   64 
   65 // internal usages
   66 
   67 #endif
   68 
   69 #define HTTP_NO_RESPONSE			(0)
   70 #define HTTP_CODE_CONTINUE			(100)
   71 #define HTTP_CODE_OK				(200)
   72 #define HTTP_CODE_CREATED			(201)
   73 #define	HTTP_CODE_NO_CONTENT			(204)
   74 #define HTTP_CODE_MULTI_STATUS			(207)
   75 #define HTTP_CODE_MOVED_TEMPORARILY		(302)
   76 #define HTTP_CODE_NOT_MODIFIED			(304)
   77 #define HTTP_CODE_BAD_REQUEST			(400)
   78 #define HTTP_CODE_FORBIDDEN			(403)
   79 #define HTTP_CODE_NOT_FOUND			(404)
   80 #define HTTP_CODE_METHOD_NOT_ALLOWED		(405)
   81 #define HTTP_CODE_REQUEST_TIME_OUT		(408)
   82 #define HTTP_CODE_REQUEST_URI_TOO_LONG		(414)
   83 #define HTTP_CODE_INTERNAL_SERVER_ERROR		(500)
   84 #define HTTP_CODE_NOT_IMPLEMENTED		(501)
   85 #define HTTP_CODE_SERVICE_UNAVAILABLE		(503)
   86 
   87 #define	HTTP_PROTOCOL_10			"HTTP/1.0"
   88 #define	HTTP_PROTOCOL_11			"HTTP/1.1"
   89 
   90 #define MAX_ATOMIC_DATA_SIZE			(32 * 1024)	/*< Maximum sending bytes, used in PUT method */
   91 
   92 /**
   93  * Initialize & create new HTTP client.
   94  *
   95  * @param hostname	remote IP or FQDN domain name
   96  * @param port		remote port number
   97  *
   98  * @return		HTTP client object if succcessful, otherwise returns NULL.
   99  *
  100  * @code
  101  *   // create new HTTP client
  102  *   Q_HTTPCLIENT *httpClient = qHttpClient("www.qdecoder.org", 80);
  103  *   if(httpClient == NULL) return;
  104  *
  105  *   // set options
  106  *   httpClient->setKeepalive(httpClient, true);
  107  *
  108  *   // make a connection
  109  *   if(httpClient->open(httpClient) == false) return;
  110  *
  111  *   // upload files
  112  *   httpClient->put(httpClient, ...);
  113  *   httpClient->put(httpClient, ...); // this will be done within same connection using KEEP-ALIVE
  114  *
  115  *   // close connection - not necessary, just for example
  116  *   httpClient->close(httpClient);
  117  *
  118  *   // de-allocate HTTP client object
  119  *   httpClient->free(httpClient);
  120  * @endcode
  121  */
  122 Q_HTTPCLIENT *qHttpClient(const char *hostname, int port) {
  123 	// get remote address
  124 	struct sockaddr_in addr;
  125 	if(qSocketGetAddr(&addr, hostname, port) == false) {
  126 		return NULL;
  127 	}
  128 
  129 	// allocate  object
  130 	Q_HTTPCLIENT *client = (Q_HTTPCLIENT *)malloc(sizeof(Q_HTTPCLIENT));
  131 	if(client == NULL) return NULL;
  132 	memset((void*)client, 0, sizeof(Q_HTTPCLIENT));
  133 
  134 	// initialize object
  135 	client->socket = -1;
  136 
  137 	memcpy((void*)&client->addr, (void*)&addr, sizeof(client->addr));
  138 	client->hostname = strdup(hostname);
  139 	client->port = port;
  140 
  141 	// member methods
  142 	client->setTimeout	= _setTimeout;
  143 	client->setKeepalive	= _setKeepalive;
  144 	client->setUseragent	= _setUseragent;
  145 
  146 	client->open		= _open;
  147 	client->sendRequest	= _sendRequest;
  148 	client->readResponse	= _readResponse;
  149 	client->get		= _get;
  150 	client->put		= _put;
  151 	client->cmd		= _cmd;
  152 	client->close		= _close;
  153 	client->free		= _free;
  154 
  155 	// init client
  156 	_setTimeout(client, 0);
  157 	_setKeepalive(client, false);
  158 	_setUseragent(client, _Q_PRGNAME "/" _Q_VERSION);
  159 
  160 	return client;
  161 }
  162 
  163 /**
  164  * Q_HTTPCLIENT->setTimeout(): Set connection wait timeout.
  165  *
  166  * @param client	Q_HTTPCLIENT object pointer
  167  * @param timeoutms	timeout mili-seconds. 0 for system defaults
  168  *
  169  * @code
  170  *   httpClient->setTimeout(httpClient, 0);    // default
  171  *   httpClient->setTimeout(httpClient, 5000); // 5 seconds
  172  * @endcode
  173  */
  174 static void _setTimeout(Q_HTTPCLIENT *client, int timeoutms) {
  175 	if(timeoutms <= 0) timeoutms = -1;
  176 	client->timeoutms = timeoutms;
  177 }
  178 
  179 /**
  180  * Q_HTTPCLIENT->setKeepalive(): Set KEEP-ALIVE feature on/off.
  181  *
  182  * @param client	Q_HTTPCLIENT object pointer
  183  * @param keepalive	true to set keep-alive on, false to set keep-alive off
  184  *
  185  * @code
  186  *   httpClient->setKeepalive(httpClient, true);  // keep-alive on
  187  *   httpClient->setKeepalive(httpClient, false); // keep-alive off
  188  * @endcode
  189  */
  190 static void _setKeepalive(Q_HTTPCLIENT *client, bool keepalive) {
  191 	client->keepalive = keepalive;
  192 }
  193 
  194 /**
  195  * Q_HTTPCLIENT->setUseragent(): Set user-agent string.
  196  *
  197  * @param client	Q_HTTPCLIENT object pointer
  198  * @param useragent	user-agent string
  199  *
  200  * @code
  201  *   httpClient->setUseragent(httpClient, "qDecoderAgent/1.0");
  202  * @endcode
  203  */
  204 static void _setUseragent(Q_HTTPCLIENT *client, const char *useragent) {
  205 	if(client->useragent != NULL) free(client->useragent);
  206 	client->useragent = strdup(useragent);
  207 }
  208 
  209 /**
  210  * Q_HTTPCLIENT->open(): Open(establish) connection to the remote host.
  211  *
  212  * @param client	Q_HTTPCLIENT object pointer
  213  *
  214  * @return	true if successful, otherwise returns false
  215  *
  216  * @note
  217  * Don't need to open a connection unless you definitely need to do this, because qHttpClient open a connection automatically when it's needed.
  218  * This function also can be used to veryfy a connection failure with remote host.
  219  *
  220  * @code
  221  *   if(httpClient->open(httpClient) == false) return;
  222  * @endcode
  223  */
  224 static bool _open(Q_HTTPCLIENT *client) {
  225 	if(client->socket >= 0) {
  226 		if(qIoWaitWritable(client->socket, 0) > 0) return true;
  227 		_close(client);
  228 	}
  229 
  230 	// create new socket
  231 	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  232 	if (sockfd < 0) {
  233 	 	DEBUG("sockfd creation failed.");
  234 		return false;
  235 	}
  236 
  237 	// set to non-block socket
  238 	int sockflag = 0;
  239 	if(client->timeoutms > 0) {
  240 		sockflag = fcntl(sockfd, F_GETFL, 0);
  241 		fcntl(sockfd, F_SETFL, sockflag | O_NONBLOCK);
  242 	}
  243 
  244 	// try to connect
  245 	int status = connect(sockfd, (struct sockaddr *)&client->addr, sizeof(client->addr));
  246 	if(status < 0 && (errno != EINPROGRESS || qIoWaitWritable(sockfd, client->timeoutms) <= 0) ) {
  247 	 	DEBUG("connection failed.");
  248 		close(client->socket);
  249 		return false;
  250 	}
  251 
  252 	// restore to block socket
  253 	if(client->timeoutms > 0) {
  254 		fcntl(sockfd, F_SETFL, sockflag);
  255 	}
  256 
  257 	// store socket descriptor
  258 	client->socket = sockfd;
  259 
  260 	return true;
  261 }
  262 
  263 /**
  264  * Q_HTTPCLIENT->sendRequest(): Send HTTP request to the remote host.
  265  *
  266  * @param client	Q_HTTPCLIENT object pointer
  267  * @param method	HTTP method name
  268  * @param uri		URI string for the method. ("/path" or "http://.../path")
  269  * @param reqheaders	Q_ENTRY pointer which contains additional user request headers. (can be NULL)
  270  *
  271  * @return	true if successful, otherwise returns false
  272  *
  273  * @note
  274  * 3 default headers(Host, User-Agent, Connection) will be sent if reqheaders does not have those headers in it.
  275  *
  276  * @code
  277  *   Q_ENTRY *reqheaders = qEntry();
  278  *   reqheaders->putStr(reqheaders,  "Date", qTimeGetGmtStaticStr(0), true);
  279  *
  280  *   httpClient->sendRequest(client, "DELETE", "/img/qdecoder.png", reqheaders);
  281  * @endcode
  282  */
  283 static bool _sendRequest(Q_HTTPCLIENT *client, const char *method, const char *uri, Q_ENTRY *reqheaders) {
  284 	if(_open(client) == false) {
  285 		return false;
  286 	}
  287 
  288 	// generate request headers if necessary
  289 	bool freeReqHeaders = false;
  290 	if(reqheaders == NULL) {
  291 		reqheaders = qEntry();
  292 		if(reqheaders == NULL) return false;
  293 		freeReqHeaders = true;
  294 	}
  295 
  296 	// append default headers
  297 	if(reqheaders->getCase(reqheaders, "Host", NULL, false) == NULL) {
  298 		reqheaders->putStrf(reqheaders, true, "Host", "%s:%d", client->hostname, client->port);
  299 	}
  300 	if(reqheaders->getCase(reqheaders, "User-Agent", NULL, false) == NULL) {
  301 		reqheaders->putStr(reqheaders, "User-Agent", client->useragent, true);
  302 	}
  303 	if(reqheaders->getCase(reqheaders, "Connection", NULL, false) == NULL) {
  304 		reqheaders->putStr(reqheaders, "Connection", (client->keepalive==true)?"Keep-Alive":"close", true);
  305 	}
  306 
  307 	// print out command
  308 	qIoPrintf(client->socket, client->timeoutms, "%s %s %s\r\n", method, uri, (client->keepalive==true)?HTTP_PROTOCOL_11:HTTP_PROTOCOL_10);
  309 
  310 	// print out headers
  311 	Q_NLOBJ_T obj;
  312 	memset((void*)&obj, 0, sizeof(obj)); // must be cleared before call
  313 	reqheaders->lock(reqheaders);
  314 	while(reqheaders->getNext(reqheaders, &obj, NULL, false) == true) {
  315 		qIoPrintf(client->socket, client->timeoutms, "%s: %s\r\n", obj.name, (char*)obj.data);
  316 	}
  317 	reqheaders->unlock(reqheaders);
  318 
  319 	qIoPrintf(client->socket, client->timeoutms, "\r\n");
  320 
  321 	// de-allocate
  322 	if(freeReqHeaders == true) reqheaders->free(reqheaders);
  323 
  324 	return true;
  325 }
  326 
  327 /**
  328  * Q_HTTPCLIENT->readResponse(): Read and parse HTTP response from the remote host.
  329  *
  330  * @param client	Q_HTTPCLIENT object pointer
  331  * @param resheaders	Q_ENTRY pointer for storing response headers. (can be NULL)
  332  *
  333  * @return	numeric HTTP response code if successful, otherwise returns 0.
  334  *
  335  * @code
  336  *   // send request
  337  *   httpClient->sendRequest(client, "DELETE", "/img/qdecoder.png", NULL);
  338  *
  339  *   // read response
  340  *   Q_ENTRY *resheaders = qEntry();
  341  *   int rescode = httpClient->readResponse(client, resheaders);
  342  * @endcode
  343  */
  344 static int _readResponse(Q_HTTPCLIENT *client, Q_ENTRY *resheaders) {
  345 	// read response
  346 	char buf[1024];
  347 	if(qIoGets(buf, sizeof(buf), client->socket, client->timeoutms) <= 0) return HTTP_NO_RESPONSE;
  348 
  349 	// parse response code
  350 	if(strncmp(buf, "HTTP/", CONST_STRLEN("HTTP/"))) return HTTP_NO_RESPONSE;
  351 	char *tmp = strstr(buf, " ");
  352 	if(tmp == NULL) return HTTP_NO_RESPONSE;
  353 	int rescode = atoi(tmp+1);
  354 	if(rescode == 0) return HTTP_NO_RESPONSE;
  355 
  356 	// read headers
  357 	while(qIoGets(buf, sizeof(buf), client->socket, client->timeoutms) > 0) {
  358 		if(buf[0] == '\0') break;
  359 		if(resheaders != NULL) {
  360 			// parse header
  361 			char *value = strstr(buf, ":");
  362 			if(value != NULL) {
  363 				*value = '\0';
  364 				value += 1;
  365 				qStrTrim(value);
  366 			} else {
  367 				// missing colon
  368 				value = "";
  369 			}
  370 			resheaders->putStr(resheaders, buf, value, true);
  371 		}
  372 	}
  373 
  374 	return rescode;
  375 }
  376 
  377 /**
  378  * Q_HTTPCLIENT->get(): Download file from remote host using GET method.
  379  *
  380  * @param client	Q_HTTPCLIENT object pointer.
  381  * @param uri		remote URL for downloading file. ("/path" or "http://.../path")
  382  * @param fd		opened file descriptor for writing.
  383  * @param savesize	if not NULL, the length of stored bytes will be stored. (can be NULL)
  384  * @param rescode	if not NULL, remote response code will be stored. (can be NULL)
  385  * @param reqheaders	Q_ENTRY pointer which contains additional user request headers. (can be NULL)
  386  * @param resheaders	Q_ENTRY pointer for storing response headers. (can be NULL)
  387  * @param callback	set user call-back function. (can be NULL)
  388  * @param userdata	set user data for call-back. (can be NULL)
  389  *
  390  * @return	true if successful, otherwise returns false
  391  *
  392  * @note
  393  * The call-back function will be called peridically whenever it send data as much as MAX_ATOMIC_DATA_SIZE.
  394  * To stop uploading, return false in the call-back function, then PUT process will be stopped immediately.
  395  * If a connection was not opened, it will open a connection automatically.
  396  *
  397  * @code
  398  *   struct userdata {
  399  *     ...
  400  *   };
  401  *
  402  *   static bool callback(void *userdata, off_t sentbytes) {
  403  *     struct userdata *pMydata = (struct userdata*)userdata;
  404  *     ...(codes)...
  405  *     if(need_to_cancel) return false; // stop file uploading immediately
  406  *     return true;
  407  *   }
  408  *
  409  *   main() {
  410  *     // create new HTTP client
  411  *     Q_HTTPCLIENT *httpClient = qHttpClient("www.qdecoder.org", 80);
  412  *     if(httpClient == NULL) return;
  413  *
  414  *     // open file
  415  *     int nFd = open("/tmp/test.data", O_WRONLY | O_CREAT, 0644);
  416  *
  417  *     // set additional custom headers
  418  *     Q_ENTRY *pReqHeaders = qEntry();
  419  *     Q_ENTRY *pResHeaders = qEntry();
  420  *
  421  *     // set userdata
  422  *     struct userdata mydata;
  423  *     ...(codes)...
  424  *
  425  *     // send file
  426  *     int nRescode = 0;
  427  *     off_t nSavesize = 0;
  428  *     bool bRet = httpClient->get(httpClient, "/img/qdecoder.png", nFd, &nSavesize, &nRescode,
  429  *                                 pReqHeaders, pResHeaders,
  430  *                                 callback, (void*)&mydata);
  431  *     // to print out request, response headers
  432  *     pReqHeaders->print(pReqHeaders, stdout, true);
  433  *     pResHeaders->print(pResHeaders, stdout, true);
  434  *
  435  *     // check results
  436  *     if(bRet == false) {
  437  *       ...(error occured)...
  438  *     }
  439  *
  440  *     // free resources
  441  *     httpClient->free(httpClient);
  442  *     pReqHeaders->free(pReqHeaders);
  443  *     pResHeaders->free(pResHeaders);
  444  *     close(nFd);
  445  *   }
  446  * @endcode
  447  */
  448 static bool _get(Q_HTTPCLIENT *client, const char *uri, int fd, off_t *savesize, int *rescode,
  449 		Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
  450 		bool (*callback)(void *userdata, off_t recvbytes), void *userdata) {
  451 
  452 	// reset rescode
  453 	if(rescode != NULL) *rescode = 0;
  454 	if(savesize != NULL) *savesize = 0;
  455 
  456 	// generate request headers if necessary
  457 	bool freeReqHeaders = false;
  458 	if(reqheaders == NULL) {
  459 		reqheaders = qEntry();
  460 		freeReqHeaders = true;
  461 	}
  462 
  463 	// add additional headers
  464 	reqheaders->putStr(reqheaders,  "Accept", "*/*", true);
  465 
  466 	// send request
  467 	bool sendRet = _sendRequest(client, "GET", uri, reqheaders);
  468 	if(freeReqHeaders == true) reqheaders->free(reqheaders);
  469 	if(sendRet == false) return false;
  470 
  471 	// generate response headers if necessary
  472 	bool freeResHeaders = false;
  473 	if(resheaders == NULL) {
  474 		resheaders = qEntry();
  475 		freeResHeaders = true;
  476 	}
  477 
  478 	// read response
  479 	int resno = _readResponse(client, resheaders);
  480 	if(rescode != NULL) *rescode = resno;
  481 
  482 	// parse contents-length
  483 	const char *clen_header = resheaders->getStrCase(resheaders, "Content-Length", false);
  484 	off_t length = 0;
  485 	if(clen_header != NULL) length = atoll(clen_header);
  486 
  487 	if(freeResHeaders == true) resheaders->free(resheaders);
  488 
  489 	// check response code
  490 	if(resno != HTTP_CODE_OK) {
  491 		_close(client);
  492 		if(freeResHeaders == true) resheaders->free(resheaders);
  493 		return false;
  494 	}
  495 
  496 	// retrieve data
  497 	off_t recv = 0;
  498 	if(callback != NULL) {
  499 		if(callback(userdata, recv) == false) {
  500 			_close(client);
  501 			return false;
  502 		}
  503 	}
  504 	if(length > 0) {
  505 		while(recv < length) {
  506 			size_t recvsize;	// this time receive size
  507 			if(length - recv < MAX_ATOMIC_DATA_SIZE) recvsize = length - recv;
  508 			else recvsize = MAX_ATOMIC_DATA_SIZE;
  509 
  510 			ssize_t ret = qIoSend(fd, client->socket, recvsize, client->timeoutms);
  511 			if(ret <= 0) break; // Connection closed by peer
  512 			recv += ret;
  513 			if(savesize != NULL) *savesize = recv;
  514 
  515 			if(callback != NULL) {
  516 				if(callback(userdata, recv) == false) {
  517 					_close(client);
  518 					return false;
  519 				}
  520 			}
  521 		}
  522 
  523 		if(recv != length) {
  524 			_close(client);
  525 			return false;
  526 		}
  527 
  528 		if(callback != NULL) {
  529 			if(callback(userdata, recv) == false) {
  530 				_close(client);
  531 				return false;
  532 			}
  533 		}
  534 	}
  535 
  536 	// close connection
  537 	if(client->keepalive == false) {
  538 		_close(client);
  539 	}
  540 
  541 	return true;
  542 }
  543 
  544 /**
  545  * Q_HTTPCLIENT->put(): Upload file to remote host using PUT method.
  546  *
  547  * @param client	Q_HTTPCLIENT object pointer.
  548  * @param uri		remote URL for uploading file. ("/path" or "http://.../path")
  549  * @param fd		opened file descriptor for reading.
  550  * @param length	send size.
  551  * @param rescode	if not NULL, remote response code will be stored. (can be NULL)
  552  * @param reqheaders	Q_ENTRY pointer which contains additional user request headers. (can be NULL)
  553  * @param resheaders	Q_ENTRY pointer for storing response headers. (can be NULL)
  554  * @param callback	set user call-back function. (can be NULL)
  555  * @param userdata	set user data for call-back. (can be NULL)
  556  *
  557  * @return	true if successful, otherwise returns false
  558  *
  559  * @note
  560  * The call-back function will be called peridically whenever it send data as much as MAX_ATOMIC_DATA_SIZE.
  561  * To stop uploading, return false in the call-back function, then PUT process will be stopped immediately.
  562  * If a connection was not opened, it will open a connection automatically.
  563  *
  564  * @code
  565  *   struct userdata {
  566  *     ...
  567  *   };
  568  *
  569  *   static bool callback(void *userdata, off_t sentbytes) {
  570  *     struct userdata *pMydata = (struct userdata*)userdata;
  571  *     ...(codes)...
  572  *     if(need_to_cancel) return false; // stop file uploading immediately
  573  *     return true;
  574  *   }
  575  *
  576  *   main() {
  577  *     // create new HTTP client
  578  *     Q_HTTPCLIENT *httpClient = qHttpClient("www.qdecoder.org", 80);
  579  *     if(httpClient == NULL) return;
  580  *
  581  *     // open file
  582  *     int nFd = open(...);
  583  *     off_t nFileSize = ...;
  584  *     char *pFileMd5sum = ...;
  585  *     time_t nFileDate = ...;
  586  *
  587  *     // set additional custom headers
  588  *     Q_ENTRY *pReqHeaders = qEntry();
  589  *     pReqHeaders->putStr(pReqHeaders, "X-FILE-MD5SUM", pFileMd5sum, true);
  590  *     pReqHeaders->putInt(pReqHeaders, "X-FILE-DATE", nFileDate, true);
  591  *
  592  *     // set userdata
  593  *     struct userdata mydata;
  594  *     ...(codes)...
  595  *
  596  *     // send file
  597  *     int nRescode = 0;
  598  *     Q_ENTRY *pResHeaders = qEntry();
  599  *     bool bRet = httpClient->put(httpClient, "/img/qdecoder.png", nFd, nFileSize, &nRescode,
  600  *                                      pReqHeaders, pResHeaders,
  601  *                                      callback, (void*)&mydata);
  602  *     // to print out request, response headers
  603  *     pReqHeaders->print(pReqHeaders, stdout, true);
  604  *     pResHeaders->print(pResHeaders, stdout, true);
  605  *
  606  *     // check results
  607  *     if(bRet == false) {
  608  *       ...(error occured)...
  609  *     }
  610  *
  611  *     // free resources
  612  *     httpClient->free(httpClient);
  613  *     pReqHeaders->free(pReqHeaders);
  614  *     pResHeaders->free(pResHeaders);
  615  *     close(nFd);
  616  *   }
  617  * @endcode
  618  */
  619 static bool _put(Q_HTTPCLIENT *client, const char *uri, int fd, off_t length, int *rescode,
  620 		Q_ENTRY *reqheaders, Q_ENTRY *resheaders,
  621 		bool (*callback)(void *userdata, off_t sentbytes), void *userdata) {
  622 
  623 	// reset rescode
  624 	if(rescode != NULL) *rescode = 0;
  625 
  626 	// generate request headers
  627 	bool freeReqHeaders = false;
  628 	if(reqheaders == NULL) {
  629 		reqheaders = qEntry();
  630 		freeReqHeaders = true;
  631 	}
  632 
  633 	// add additional headers
  634 	reqheaders->putStrf(reqheaders, true, "Content-Length", "%jd", length);
  635 	reqheaders->putStr(reqheaders,  "Expect", "100-continue", true);
  636 
  637 	// send request
  638 	bool sendRet =_sendRequest(client, "PUT", uri, reqheaders);
  639 	if(freeReqHeaders == true) reqheaders->free(reqheaders);
  640 	if(sendRet == false) return false;
  641 
  642 	// wait 100-continue
  643 	if(qIoWaitReadable(client->socket, client->timeoutms) <= 0) {
  644 		DEBUG("timed out %d", client->timeoutms);
  645 		_close(client);
  646 		return false;
  647 	}
  648 
  649 	// read response
  650 	int resno = _readResponse(client, resheaders);
  651 	if(resno != HTTP_CODE_CONTINUE) {
  652 		if(rescode != NULL) *rescode = resno;
  653 		_close(client);
  654 		return false;
  655 	}
  656 
  657 	// send data
  658 	off_t sent = 0;
  659 	if(callback != NULL) {
  660 		if(callback(userdata, sent) == false) {
  661 			_close(client);
  662 			return false;
  663 		}
  664 	}
  665 	if(length > 0) {
  666 		while(sent < length) {
  667 			size_t sendsize;	// this time sending size
  668 			if(length - sent < MAX_ATOMIC_DATA_SIZE) sendsize = length - sent;
  669 			else sendsize = MAX_ATOMIC_DATA_SIZE;
  670 
  671 			ssize_t ret = qIoSend(client->socket, fd, sendsize, client->timeoutms);
  672 			if(ret <= 0) break; // Connection closed by peer
  673 			sent += ret;
  674 
  675 			if(callback != NULL) {
  676 				if(callback(userdata, sent) == false) {
  677 					_close(client);
  678 					return false;
  679 				}
  680 			}
  681 		}
  682 
  683 		if(sent != length) {
  684 			_close(client);
  685 			return false;
  686 		}
  687 
  688 		if(callback != NULL) {
  689 			if(callback(userdata, sent) == false) {
  690 				_close(client);
  691 				return false;
  692 			}
  693 		}
  694 	}
  695 
  696 	// read response
  697 	resno = _readResponse(client, resheaders);
  698 	if(rescode != NULL) *rescode = resno;
  699 
  700 	if(resno == HTTP_NO_RESPONSE) {
  701 		_close(client);
  702 		return false;
  703 	}
  704 
  705 	if(resno != HTTP_CODE_CREATED) {
  706 		_close(client);
  707 		return false;
  708 	}
  709 
  710 	// close connection
  711 	if(client->keepalive == false) {
  712 		_close(client);
  713 	}
  714 
  715 	return true;
  716 }
  717 
  718 /**
  719  * Q_HTTPCLIENT->cmd(): Send custom method to remote host.
  720  *
  721  * @param client	Q_HTTPCLIENT object pointer.
  722  * @param method	method name.
  723  * @param uri		remote URL for uploading file. ("/path" or "http://.../path")
  724  * @param rescode	if not NULL, remote response code will be stored. (can be NULL)
  725  * @param contentslength if not NULL, the contents length will be stored. (can be NULL)
  726  * @param reqheaders	Q_ENTRY pointer which contains additional user request headers. (can be NULL)
  727  * @param resheaders	Q_ENTRY pointer for storing response headers. (can be NULL)
  728  *
  729  * @return	malloced contents data if successful, otherwise returns NULL
  730  *
  731  * @code
  732  *   int nResCode;
  733  *   size_t nContentsLength;
  734  *   void *contents = httpClient->cmd(httpClient, "DELETE" "/img/qdecoder.png",
  735  *                                      &nRescode, &nContentsLength
  736  *                                      NULL, NULL);
  737  *   if(contents == NULL) {
  738  *     ...(error occured)...
  739  *   } else {
  740  *     printf("Response code : %d\n", nResCode);
  741  *     printf("Contents length : %zu\n", nContentsLength);
  742  *     printf("Contents : %s\n", (char*)contents);  // if contents is printable
  743  *     free(contents);  // de-allocate
  744  *   }
  745  * @endcode
  746  */
  747 static void *_cmd(Q_HTTPCLIENT *client, const char *method, const char *uri,
  748 		int *rescode, size_t *contentslength,
  749 		Q_ENTRY *reqheaders, Q_ENTRY *resheaders) {
  750 
  751 	// reset rescode
  752 	if(rescode != NULL) *rescode = 0;
  753 	if(contentslength != NULL) *contentslength = 0;
  754 
  755 	// send request
  756 	if(_sendRequest(client, method, uri, reqheaders) == false) {
  757 		return NULL;
  758 	}
  759 
  760 	// read response
  761 	bool freeResHeaders = false;
  762 	if(resheaders == NULL) {
  763 		resheaders = qEntry();
  764 		freeResHeaders = true;
  765 	}
  766 
  767 	int resno = _readResponse(client, resheaders);
  768 	if(rescode != NULL) *rescode = resno;
  769 
  770 	// parse contents-length
  771 	size_t length = resheaders->getInt(resheaders, "Content-Length");
  772 	if(freeResHeaders == true) resheaders->free(resheaders);
  773 
  774 	// malloc data
  775 	void *contents = NULL;
  776 	if(length > 0) {
  777 		contents = malloc(length + 1);
  778 		if(contents != NULL) {
  779 			if(qIoRead(contents, client->socket, length, client->timeoutms) != length) {
  780 				free(contents);
  781 				contents = NULL;
  782 			} else {
  783 				*(char*)(contents + length) = '\0';
  784 			}
  785 		}
  786 	}
  787 
  788 	// close connection
  789 	if(client->keepalive == false) {
  790 		_close(client);
  791 	}
  792 
  793 	if(contents == NULL) {
  794 		contents = strdup("");
  795 		length = strlen(contents);
  796 	}
  797 	if(contentslength != NULL) *contentslength = length;
  798 
  799 	return contents;
  800 }
  801 
  802 /**
  803  * Q_HTTPCLIENT->close(): Close the connection.
  804  *
  805  * @param Q_HTTPCLIENT	HTTP object pointer
  806  *
  807  * @return	true if successful, otherwise returns false
  808  *
  809  * @code
  810  *   httpClient->close(httpClient);
  811  * @endcode
  812  */
  813 static bool _close(Q_HTTPCLIENT *client) {
  814 	if(client->socket < 0) return true;
  815 
  816 	// shutdown connection
  817 	qSocketClose(client->socket);
  818 	client->socket = -1;
  819 
  820 	return true;
  821 }
  822 
  823 /**
  824  * Q_HTTPCLIENT->free(): De-allocate object.
  825  *
  826  * @param Q_HTTPCLIENT	HTTP object pointer
  827  *
  828  * @note
  829  * If the connection was not closed, it will close the connection first prior to de-allocate object.
  830  *
  831  * @code
  832  *   httpClient->free(httpClient);
  833  * @endcode
  834  */
  835 static void _free(Q_HTTPCLIENT *client) {
  836 	if(client->socket >= 0) {
  837 		client->close(client);
  838 	}
  839 
  840 	if(client->hostname != NULL) free(client->hostname);
  841 	if(client->useragent != NULL) free(client->useragent);
  842 
  843 	free(client);
  844 }
  845 
  846 #endif /* DISABLE_SOCKET */

Home | About | Examples | Changes | Download | SVN Repository | Install | Reference