实现HTTP协议Get、Post和文件上传功能——使用libcurl接口实现

之前我们已经详细介绍了WinHttp接口如何实现Http的相关功能。本文我将主要讲解如何使用libcurl库去实现相关功能。(转载请指明出于breaksoftware的csdn博客)

libcurl在http://curl.haxx.se/libcurl/有详细的介绍,有兴趣的朋友可以去读下。本文我只是从实际使用的角度讲解其中的一些功能。

libcurl中主要有两个接口类型:CURL和CURLM。CURL又称easy interface,它接口简单、使用方便,但是它是一个同步接口,我们不能使用它去实现异步的功能——比如下载中断——其实也是有办法的(比如对写回调做点手脚)。相应的,CURLM又称multi interface,它是异步的。可以想下,我们使用easy interface实现一个HTTP请求过程,如果某天我们需要将其改成multi interface接口的,似乎需要对所有接口都要做调整。其实不然,libcurl使用一种优雅的方式去解决这个问题——multi
interface只是若干个easy interface的集合。我们只要把easy interface指针加入到multi interface中即可。

CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *easy_handle);

本文将使用multi interface作为最外层的管理者,具体下载功能交给easy interface。在使用easy interface之前,我们需要对其初始化

初始化

初始化easy interface

bool CHttpRequestByCurl::Prepare() {
	bool bSuc = false;
	do {
		if (!m_pCurlEasy) {
			m_pCurlEasy = curl_easy_init();
		}
		if (!m_pCurlEasy) {
			break;
		}

初始化multi interface

            if (!m_pCurlMulti){
                m_pCurlMulti = curl_multi_init();
            }
            if (!m_pCurlMulti) {
                break;
            }

设置

设置过程回调

过程回调用于体现数据下载了多少或者上传了多少

		CURLcode easycode;
		easycode = curl_easy_setopt( m_pCurlEasy, CURLOPT_NOPROGRESS, 0 );
		CHECKCURLEASY_EROORBREAK(easycode);
		easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_PROGRESSFUNCTION, progresscallback);
		CHECKCURLEASY_EROORBREAK(easycode);
		easycode = curl_easy_setopt( m_pCurlEasy, CURLOPT_PROGRESSDATA, this );
		CHECKCURLEASY_EROORBREAK(easycode);

设置CURLOPT_NOPROGRESS代表我们需要使用过程回调这个功能。设置CURLOPT_PROGRESSFUNCTION为progresscallback是设置回调函数的指针,我们将通过静态函数progresscallback反馈过程状态。注意一下这儿,因为libcurl是一个C语言API库,所以它没有类的概念,这个将影响之后我们对各种静态回调函数的设置。此处要求progresscallback是一个静态函数——它也没有this指针,但是libcurl设计的非常好,它留了一个用户自定义参数供我们使用,这样我们便可以将对象的this指针通过CURLOPT_PROGRESSDATA传过去。

	int CHttpRequestByCurl::progresscallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ) {
		if (clientp) {
			CHttpRequestByCurl* pThis = (CHttpRequestByCurl*)clientp;
			return pThis->ProcessCallback(dltotal, dlnow);
		}
		else {
			return -1;
		}
	}

    int CHttpRequestByCurl::ProcessCallback( double dltotal, double dlnow ) {
        if ( m_CallBack ) {
            const DWORD dwMaxEslapeTime = 500;
            std::ostringstream os;
            os << (unsigned long)dlnow;
            std::string strSize = os.str();

            std::ostringstream ostotal;
            ostotal << (unsigned long)dltotal;
            std::string strContentSize = ostotal.str();
            DWORD dwTickCount = GetTickCount();
            if ( ( 0 != ((unsigned long)dltotal)) && ( strSize == strContentSize || dwTickCount - m_dwLastCallBackTime > dwMaxEslapeTime ) ) {
                m_dwLastCallBackTime = dwTickCount;
                m_CallBack( strContentSize, strSize );
            }
        }
        return 0;
    }

此处progresscallback只是一个代理功能——它是静态的,它去调用clientp传过来的this指针所指向对象的ProcessCallback成员函数。之后我们的其他回调函数也是类似的,比如写结果的回调设置

设置写结果回调

		easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEFUNCTION, writefilecallback);
		CHECKCURLEASY_EROORBREAK(easycode);
		easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_WRITEDATA, this);
		CHECKCURLEASY_EROORBREAK(easycode);
	size_t CHttpRequestByCurl::writefilecallback( void *buffer, size_t size, size_t nmemb, void *stream ) {
		if (stream) {
			 CHttpRequestByCurl* pThis = (CHttpRequestByCurl*)stream;
			 return pThis->WriteFileCallBack(buffer, size, nmemb);
		}
		else {
			return size * nmemb;
		}
	}

    size_t CHttpRequestByCurl::WriteFileCallBack( void *buffer, size_t size, size_t nmemb ) {
        if (!m_pCurlEasy) {
            return 0;
        }

        int nResponse = 0;
        CURLcode easycode = curl_easy_getinfo(m_pCurlEasy, CURLINFO_RESPONSE_CODE, &nResponse);
        if ( CURLE_OK != easycode || nResponse >= 400 ) {
            return 0;
        }

        return Write(buffer, size, nmemb);
    }

在WriteFileCallBack函数中,我们使用curl_easy_getinfo判断了easy interface的返回值,这是为了解决接收返回结果时服务器中断的问题。

设置读回调

读回调我们并没有传递this指针过去。

            easycode = curl_easy_setopt( m_pCurlEasy,  CURLOPT_READFUNCTION,  read_callback);
            CHECKCURLEASY_EROORBREAK(easycode);

我们看下回调就明白了

    size_t CHttpRequestByCurl::read_callback( void *ptr, size_t size, size_t nmemb, void *stream ) {
       return ((ToolsInterface::LPIMemFileOperation)(stream))->MFRead(ptr, size, nmemb);
    }

这次用户自定义指针指向了一个IMemFileOperation对象指针,它是在之后的其他步奏里传递过来的。这儿有个非常有意思的地方——即MFRead的返回值和libcurl要求的read_callback返回值是一致的——并不是说类型一致——而是返回值的定义一致。这就是统一成标准接口的好处。

设置URL

            easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_URL, m_strUrl.c_str());
            CHECKCURLEASY_EROORBREAK(easycode);

设置超时时间

            easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_TIMEOUT_MS, m_nTimeout);
            CHECKCURLEASY_EROORBREAK(easycode);

设置Http头

            for ( ToolsInterface::ListStrCIter it = m_listHeaders.begin(); it != m_listHeaders.end(); it++ ) {
                m_pHeaderlist = curl_slist_append(m_pHeaderlist, it->c_str());
            }
            if (m_pHeaderlist) {
                curl_easy_setopt(m_pCurlEasy, CURLOPT_HTTPHEADER, m_pHeaderlist);
            }

这儿需要注意的是m_pHeaderlist在整个请求完毕后需要释放

		if (m_pHeaderlist) {
			curl_slist_free_all (m_pHeaderlist);
			m_pHeaderlist = NULL;
		}

设置Agent

            if (!m_strAgent.empty()) {
                easycode = curl_easy_setopt(m_pCurlEasy, CURLOPT_USERAGENT, m_strAgent.c_str());
                CHECKCURLEASY_EROORBREAK(easycode);
            }

设置Post参数

            if ( ePost == GetType() ) {
                easycode = ModifyEasyCurl(m_pCurlEasy, m_Params);
                CHECKCURLEASY_EROORBREAK(easycode);
            }

之后我们将讲解ModifyEasyCurl的实现。我们先把整个调用过程将完。

将easy interface加入到multi interface

            CURLMcode multicode = curl_multi_add_handle( m_pCurlMulti, m_pCurlEasy );
            CHECKCURLMULTI_EROORBREAK(multicode);

            bSuc = true;
      } while (0);
      return bSuc;
}

运行

    EDownloadRet CHttpRequestByCurl::Curl_Multi_Select(CURLM* pMultiCurl)
    {
        EDownloadRet ERet = EContinue;

        do {
            struct timeval timeout;
            fd_set fdread;
            fd_set fdwrite;
            fd_set fdexcep;

            CURLMcode multicode;
            long curl_timeo = -1;

            /* set a suitable timeout to fail on */
            timeout.tv_sec = 30; /* 30 seconds */
            timeout.tv_usec = 0;
            multicode = curl_multi_timeout(pMultiCurl, &curl_timeo);
            if ( CURLM_OK == multicode && curl_timeo >= 0 ) {
                timeout.tv_sec = curl_timeo / 1000;
                if (timeout.tv_sec > 1) {
                    timeout.tv_sec = 0;
                }
                else {
                    timeout.tv_usec = (curl_timeo % 1000) * 1000;
                }
            }

            int nMaxFd = -1;

            while ( -1 == nMaxFd ) {

                FD_ZERO(&fdread);
                FD_ZERO(&fdwrite);
                FD_ZERO(&fdexcep);

                multicode = curl_multi_fdset( m_pCurlMulti, &fdread, &fdwrite, &fdexcep, &nMaxFd );
                CHECKCURLMULTI_EROORBREAK(multicode);
                if ( -1 != nMaxFd ) {
                    break;
                }
                else {
                    if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 100)) {
                        ERet = EInterrupt;
                        break;
                    }
                    int nRunning = 0;
                    CURLMcode multicode = curl_multi_perform( m_pCurlMulti, &nRunning );
                    CHECKCURLMULTI_EROORBREAK(multicode);
                }
            }

            if ( EContinue == ERet ) {
                int nSelectRet = select( nMaxFd + 1, &fdread, &fdwrite, &fdexcep, &timeout );

                if ( -1 == nSelectRet ){
                    ERet = EFailed;
                }
            }
            if ( EInterrupt == ERet ) {
                break;
            }
        } while (0);

        return ERet;
    }

    DWORD CHttpRequestByCurl::StartRequest() {
        Init();
        EDownloadRet eDownloadRet = ESuc;
        do {
            if (!Prepare()) {
                break;
            }

            int nRunning = -1;
            while( CURLM_CALL_MULTI_PERFORM == curl_multi_perform(m_pCurlMulti, &nRunning) ) {
                if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 10)) {
                    eDownloadRet = EInterrupt;
                    break;
                }
            }

            if ( EInterrupt == eDownloadRet ) {
                break;
            }

            while(0 != nRunning) {
                EDownloadRet nSelectRet = Curl_Multi_Select(m_pCurlMulti);
                if ( EFailed == nSelectRet || EInterrupt == nSelectRet || ENetError == nSelectRet ) {
                    eDownloadRet = nSelectRet;
                    break;
                }
                else {
                    CURLMcode multicode = curl_multi_perform(m_pCurlMulti, &nRunning);
                    if (CURLM_CALL_MULTI_PERFORM == multicode) {
                        if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 10)) {
                            eDownloadRet = EInterrupt;
                            break;
                        }
                    }
                    else if ( CURLM_OK == multicode ) {
                    }
                    else {
                        if (WAIT_TIMEOUT != WaitForSingleObject(m_hStop, 100)) {
                            eDownloadRet = EInterrupt;
                        }
                        break;
                    }
                }

                if ( EInterrupt == eDownloadRet ) {
                    break;
                }
            } // while

            if ( EInterrupt == eDownloadRet ) {
                break;
            }

            int msgs_left;
            CURLMsg*  msg;
            while((msg = curl_multi_info_read(m_pCurlMulti, &msgs_left))) {
                if (CURLMSG_DONE == msg->msg) {
                    if ( CURLE_OK != msg->data.result ) {
                        eDownloadRet = EFailed;
                    }
                }
                else {
                    eDownloadRet = EFailed;
                }
            }

        } while (0);

        Unint();

        m_bSuc = ( ESuc == eDownloadRet ) ? true : false;
        return eDownloadRet;
    }

可以见得运行的主要过程就是不停的调用curl_multi_perform。

实现Post、文件上传功能

对于MultiPart格式数据,我们要使用curl_httppost结构体保存参数

组装上传文件

    CURLcode CPostByCurl::ModifyEasyCurl_File( CURL* pEasyCurl, const FMParam& Param ) {

        Param.value->MFSeek(0L, SEEK_END);
        long valuesize = Param.value->MFTell();
        Param.value->MFSeek(0L, SEEK_SET);

        curl_formadd((curl_httppost**)&m_pFormpost,
            (curl_httppost**)&m_pLastptr,
            CURLFORM_COPYNAME, Param.strkey.c_str(),
            CURLFORM_STREAM, Param.value,
            CURLFORM_CONTENTSLENGTH, valuesize,
            CURLFORM_FILENAME, Param.fileinfo.szfilename,
            CURLFORM_CONTENTTYPE, "application/octet-stream",
            CURLFORM_END);

        return CURLE_OK;
    }

我们使用CURLFORM_STREAM标记数据的载体,此处我们传递的是一个IMemFileOperation指针,之前我们定义的readcallback回调将会将该参数作为第一个参数被调用。CURLFORM_CONTENTSLENGTH也是个非常重要的参数。如果我们不设置CURLFORM_CONTENTSLENGTH,则传递的数据长度是数据起始至\0结尾。所以我们在调用curl_formadd之前先计算了数据的长度——文件的大小。然后指定CURLFORM_FILENAME为服务器上保存的文件名。

组装上传数据

    CURLcode CPostByCurl::ModifyEasyCurl_Mem( CURL* pEasyCurl, const FMParam& Param ) {
        if (Param.meminfo.bMulti) {
            Param.value->MFSeek(0L, SEEK_END);
            long valuesize = Param.value->MFTell();
            Param.value->MFSeek(0L, SEEK_SET);
            curl_formadd(&m_pFormpost, &m_pLastptr,
                CURLFORM_COPYNAME, Param.strkey.c_str(),
                CURLFORM_STREAM, Param.value,
                CURLFORM_CONTENTSLENGTH, valuesize,
                CURLFORM_CONTENTTYPE, "application/octet-stream",
                CURLFORM_END );
        }
        else {
            if (!m_strCommonPostData.empty()) {
                m_strCommonPostData += "&";
            }
            std::string strpostvalue;
            while(!Param.value->MFEof()) {
                char buffer[1024] = {0};
                size_t size = Param.value->MFRead(buffer, 1, 1024);
                strpostvalue.append(buffer, size);
            }
            m_strCommonPostData += Param.strkey;
            m_strCommonPostData += "=";
            m_strCommonPostData += strpostvalue;
        }
        return CURLE_OK;
    }

对于需要MultiPart格式发送的数据,我们发送的方法和文件发送相似——只是少了CURLFORM_FILENAME设置——因为没有文件名。

对于普通Post数据,我们使用m_strCommonPostData拼接起来。待之后一并发送。

设置数据待上传

CURLcode CPostByCurl::ModifyEasyCurl( CURL* pEasyCurl, const FMParams& Params ) {
        for (FMParamsCIter it = m_PostParam.begin(); it != m_PostParam.end(); it++ ) {
            if (it->postasfile) {
                ModifyEasyCurl_File(pEasyCurl, *it);
            }
            else {
                ModifyEasyCurl_Mem(pEasyCurl, *it);
            }
        }

        if (m_pFormpost){
            curl_easy_setopt(pEasyCurl, CURLOPT_HTTPPOST, m_pFormpost);
        }

        if (!m_strCommonPostData.empty()) {
            curl_easy_setopt(pEasyCurl, CURLOPT_COPYPOSTFIELDS, m_strCommonPostData.c_str());
        }

	return CURLE_OK;
}

通过设置CURLOPT_HTTPPOST,我们将MultiPart型数据——包括文件上传数据设置好。通过设置CURLOPT_COPYPOSTFIELDS,我们将普通Post型数据设置好。

Get型请求没什么好说的。详细见之后给的工程源码。

工程源码链接:http://pan.baidu.com/s/1i3eUnMt 密码:hfro

时间: 2024-10-13 22:30:37

实现HTTP协议Get、Post和文件上传功能——使用libcurl接口实现的相关文章

实现HTTP协议Get、Post和文件上传功能——使用WinHttp接口实现

在<使用WinHttp接口实现HTTP协议Get.Post和文件上传功能>一文中,我已经比较详细地讲解了如何使用WinHttp接口实现各种协议.在最近的代码梳理中,我觉得Post和文件上传模块可以得到简化,于是几乎重写了这两个功能的代码.因为Get.Post和文件上传功能的基础(父)类基本没有改动,函数调用的流程也基本没有变化,所以本文我将重点讲解修改点.(转载请指明出于breaksoftware的csdn博客) 首先我修改了接口的字符集.之前我都是使用UNICODE作为接口参数类型,其中一个

达到HTTP合约Get、Post和文件上传功能——采用WinHttp介面

于<采用WinHttp实现HTTP协议Get.Post和文件上传功能>一文中,我已经比較具体地解说了怎样使用WinHttp接口实现各种协议. 在近期的代码梳理中,我认为Post和文件上传模块能够得到简化,于是差点儿重写了这两个功能的代码.由于Get.Post和文件上传功能的基础(父)类基本没有修改,函数调用的流程也基本没有变化,所以本文我将重点解说修改点. (转载请指明出于breaksoftware的csdn博客) 首先我改动了接口的字符集.之前我都是使用UNICODE作为接口參数类型,当中一

接口规范 13. 文件上传及管理相关接口

13 文件上传及管理相关接口 这组接口实现视频资源的上传及管理,为视频转码和发布提供支持.上传的服务器上的视频文件,经过转码发布后形成可以对外播出的在线视频资源.本小节描述文件上传和管理的接口,转码接口在下一章节说明. 13.1.文件上传接口用途通过HTTP POST协议上传视频文件.文件上传接口是一个特殊的接口,接口位置与其他接口有差异.上传协议采用HTTP POST协议,提交form的数据采用multipart/form-data编码(enctype="multipart/form-data

Spring MVC 文件上传功能详解

前言 在Spring MVC中实现文件上传功能并不复杂,前端使用HTML语法,后端使用特定抽象.参考Spring Boot相关文档即可.本文主要讲解常见资料忽略的两个问题: 文件上传错误 链接重置问题 版本:Spring Boot 1.5x 文件上传错误 一个关注点是如何处理文件上传期间发生的错误,错误主要分为两类: IOException 文件读取或写入错误. MultipartException 上传文件时,超过文件大小上限所触发的异常. IOException 只需要在Controller

文件上传功能的实现

一:文件上传功能 先要在在index.jsp的界面上初始化一个表单. 代码如下: <body> <form enctype="multipart/form-data" action="<%=path%>/1.jsp" method="post"> 姓名:<input type="text" name="username"/> 选择文件:<input ty

AEAI Miscdp文件上传功能使用心得

1.概述 在实际项目中,我们常常可以遇到这样的情况:上传头像.上传图片.上传视频.上传音乐.上传作业,等等-那么如何使用MiscdpStudio对这类功能进行开发,就成了MiscdpStudio使用者必须掌握的技能之一. 接下来,我就对单表操作模型,简单介绍一下如何对文件进行上传.这里,我们主要使用的是fileupload组件,当然,这也是开发平台中自带的组件. 2.创建数据表 在这里,我们需要在数据库创建两个表,一个是业务表.另一个是关联表.关联表里有三个字段:逻辑主键.业务标识字段.附件标识

iOS 的 Safari 文件上传功能详解

iOS 6 给 Safari 浏览器带来的另外一个功能是文件上传,终于 Safari 终于支持 input 输入框的文件类型了,并且还支持 HTML媒体捕获(HTML Media Capture). 上传单张图片或者视频 <input type="file"> 可以选择直接拍照或者摄影,也可以从相册中选取.选好之后,iOS 上的 Safari 和其他浏览器不同是它显示图片的截图,而不是图片的临时名称. 上传多张图片或者视频 如果你想一次上传多张图片,可以使用 HTML5 一

文件/大文件上传功能实现(JS+PHP)全过程

文件/大文件上传功能实现(JS+PHP) 参考博文:掘金-橙红年代 前端大文件上传 路漫漫 其修远 PHP + JS 实现大文件分割上传 本文是学习文件上传后的学习总结文章,从无到有实现文件上传功能,前端小白写的代码不是最优,如果有错误的地方请多多指教,如果本文对你有所帮助,深感荣幸. 近期公司的项目中,涉及到上传大文件的问题,大文件上传用普通表单上传时出现的问题是,无法断点续存,一但中途中断上传,就要重头开始,这很明显不是我们想要的,所以经过一番查询,学习了一下大文件分割上传的方法.并且使用简

实现HTTP协议Get、Post和文件上传功能——设计和模块

之前写过一遍<使用WinHttp接口实现HTTP协议Get.Post和文件上传功能>,其中谈到了如何使用WinHttp接口实现Http的Get.Post和文件上传功能.后来发现蛮多人关注该技术的实现,于是我决定重新设计框架结构,梳理这块的知识并提供可供测试的方案.同时,该系列也将作为<VC开发Windows客户端软件之旅>中"网络"模块的一部分. 本系列不再将技术限定于WinHttp接口,还引入curllib库.同时为了公正且方便测试代码的正确性,我们将引入成熟