├── httpclient ├── base64.h ├── base64.cpp ├── http_response.cpp ├── http.h ├── http_thread.h ├── file_reader.h ├── file_writer.h ├── global_defs.h ├── lock.h ├── http_global.h ├── standard_header.h ├── url_parser.h ├── thread.h ├── http_response_receiver.h ├── file_reader.cpp ├── util.h ├── http_header_parser.h ├── socket_helper.h ├── http_delegate.h ├── dll_init.cpp ├── file_writer.cpp ├── http_response.h ├── streaming_socket.h ├── proxy_socket.h ├── proxy_config.h ├── http_response_receiver.cpp ├── http_pool.h ├── thread.cpp ├── url_parser.cpp ├── http_post_file.h ├── http_thread.cpp ├── http_header_parser.cpp ├── streaming_socket.cpp ├── http_client.h ├── util.cpp ├── http_request.h ├── http_pool.cpp ├── http_post_file.cpp ├── socket_helper.cpp ├── http_request.cpp ├── httpclient.vcproj ├── proxy_socket .cpp └── http_client.cpp ├── .gitattributes ├── .gitignore ├── README.md ├── LICENSE ├── httpclient.sln └── test ├── main.cpp └── test.vcproj /httpclient/base64.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangwangfeng/httpclient/HEAD/httpclient/base64.h -------------------------------------------------------------------------------- /httpclient/base64.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangwangfeng/httpclient/HEAD/httpclient/base64.cpp -------------------------------------------------------------------------------- /httpclient/http_response.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_response.cpp 3 | * @brief Http反馈 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #include "standard_header.h" 10 | #include "http_response.h" 11 | 12 | NAMESPACE_BEGIN(Http) 13 | 14 | //空... 15 | 16 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/http.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_header.h 3 | * @brief httpclient的引用头文件集合 4 | * @author xiangwangfeng 5 | * @data 2011-7-8 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include "proxy_config.h" 11 | #include "http_global.h" 12 | #include "http_post_file.h" 13 | #include "http_request.h" 14 | #include "http_response.h" 15 | #include "http_client.h" 16 | #include "http_delegate.h" 17 | #include "http_pool.h" -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /httpclient/http_thread.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_thread.h 3 | * @brief Http线程 4 | * @author xiangwangfeng 5 | * @data 2011-7-8 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #pragma once 9 | 10 | #include "thread.h" 11 | 12 | NAMESPACE_BEGIN(Http) 13 | 14 | class HttpPool; 15 | class HttpClient; 16 | 17 | class HttpThread : public Util::Thread 18 | { 19 | public: 20 | HttpThread(const std::string& name,HttpPool* http_pool); 21 | ~HttpThread(); 22 | void killSelf(); 23 | void run(); 24 | private: 25 | void executeHttp(); 26 | private: 27 | HttpPool* _http_pool; 28 | HttpClient* _http_client; 29 | }; 30 | 31 | 32 | NAMESPACE_END(Http) 33 | -------------------------------------------------------------------------------- /httpclient/file_reader.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file_reader.h 3 | * @brief 读文件Wrapper类 4 | * @author xiangwangfeng 5 | * @data 2011-4-27 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "global_defs.h" 13 | 14 | NAMESPACE_BEGIN(Util) 15 | 16 | //读文件Wrapper类 17 | class FileReader 18 | { 19 | public: 20 | FileReader(const std::wstring& filepath); 21 | ~FileReader(); 22 | public: 23 | bool open(); 24 | int read(char* buffer,size_t length); 25 | void close(); 26 | private: 27 | std::wstring _filepath; //文件路径 28 | bool _ready; //是否准备完毕 29 | FILE* _file; //文件指针 30 | }; 31 | 32 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | httpclient 2 | ========== 3 | 4 | Http client for windows 5 | 6 | * [构造HttpClient三部曲之一:支持代理的Socket封装][1] 7 | * [构造HttpClient三部曲之二:GET方法实现][2] 8 | * [构造HttpClient三部曲之三:POST方法实现][3] 9 | 10 | 11 | [1]:https://xiangwangfeng.github.io/2011/05/08/%E6%9E%84%E9%80%A0HttpClient%E4%B8%89%E9%83%A8%E6%9B%B2%E4%B9%8B%E4%B8%80-%E6%94%AF%E6%8C%81%E4%BB%A3%E7%90%86%E7%9A%84Socket%E5%B0%81%E8%A3%85/ 12 | [2]:https://xiangwangfeng.github.io/2011/05/09/%E6%9E%84%E9%80%A0HttpClient%E4%B8%89%E9%83%A8%E6%9B%B2%E4%B9%8B%E4%BA%8C-GET%E6%96%B9%E6%B3%95%E5%AE%9E%E7%8E%B0/ 13 | [3]:https://xiangwangfeng.github.io/2011/05/15/%E6%9E%84%E9%80%A0HttpClient%E4%B8%89%E9%83%A8%E6%9B%B2%E4%B9%8B%E4%B8%89-POST%E6%96%B9%E6%B3%95%E5%AE%9E%E7%8E%B0/ 14 | -------------------------------------------------------------------------------- /httpclient/file_writer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file_writer.h 3 | * @brief 写文件Wrapper类 4 | * @author xiangwangfeng 5 | * @data 2011-4-25 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "global_defs.h" 13 | 14 | NAMESPACE_BEGIN(Util) 15 | 16 | //写文件Wrapper类 17 | class FileWriter 18 | { 19 | public: 20 | FileWriter(const std::wstring& filepath); 21 | ~FileWriter(); 22 | public: 23 | bool create(); 24 | bool write(const char* buffer,size_t length); 25 | void flush(); 26 | void close(); 27 | private: 28 | std::wstring _filepath; //文件路径 29 | bool _ready; //是否准备完毕 30 | FILE* _file; //文件指针 31 | }; 32 | 33 | 34 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/global_defs.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file global_defs.h 3 | * @brief 类和名词空间相关的宏定义 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | 11 | #define NAMESPACE_BEGIN(x) namespace x{ 12 | #define NAMESPACE_END(x) } 13 | 14 | #ifdef HTTP_CLIENT_EXPORT 15 | #define HTTP_API __declspec(dllexport) 16 | #define HTTP_DATA __declspec(dllexport) 17 | #define HTTP_CLASS __declspec(dllexport) 18 | #else 19 | #define HTTP_API __declspec(dllimport) 20 | #define HTTP_DATA __declspec(dllimport) 21 | #define HTTP_CLASS __declspec(dllimport) 22 | #endif 23 | 24 | #define DECLARE_NON_COPYABLE(className) \ 25 | className (const className&);\ 26 | className& operator= (const className&); 27 | -------------------------------------------------------------------------------- /httpclient/lock.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file lock.h 3 | * @brief 同步锁的封装 4 | * @author xiangwangfeng 5 | * @data 2011-7-5 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #pragma once 9 | 10 | #include "global_defs.h" 11 | #include "standard_header.h" 12 | 13 | NAMESPACE_BEGIN(Util) 14 | 15 | class Lock 16 | { 17 | public: 18 | Lock() { InitializeCriticalSection(&_cs); } 19 | ~Lock() { DeleteCriticalSection(&_cs); } 20 | public: 21 | void enter() { EnterCriticalSection(&_cs); } 22 | void exit() { LeaveCriticalSection(&_cs); } 23 | private: 24 | CRITICAL_SECTION _cs; 25 | }; 26 | 27 | 28 | class ScopedLock 29 | { 30 | public: 31 | ScopedLock(Lock& lock): _lock(lock) {lock.enter();} 32 | ~ScopedLock() {_lock.exit();} 33 | private: 34 | Lock& _lock; 35 | }; 36 | 37 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/http_global.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_global.h 3 | * @brief Http全局数据定义 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #pragma once 9 | #include "global_defs.h" 10 | 11 | const char kget[] = "GET"; 12 | const char kpost[] = "POST"; 13 | const char kaccept[] = "Accept"; 14 | const char kconnection[] = "Connection"; 15 | const char kcontent_type[] = "Content-Type"; 16 | const char kcontent_length[] = "Content-Length"; 17 | 18 | NAMESPACE_BEGIN(Http) 19 | 20 | //Http传输错误码 21 | enum HTTPERROR 22 | { 23 | HTTPERROR_SUCCESS, //正确 24 | HTTPERROR_INVALID, //HTTP已经被弃用 25 | HTTPERROR_CONNECT, //连接出错 26 | HTTPERROR_TRANSPORT, //传输失败 27 | HTTPERROR_IO, //IO错误 28 | HTTPERROR_PARAMETER //无效参数 29 | }; 30 | 31 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/standard_header.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file standard_header.h 3 | * @brief 标准头文件 4 | * @author xiangwangfeng 5 | * @data 2011-7-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #pragma once 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define HTTP_CLIENT_EXPORT //导出标识 20 | 21 | #define PTR_BOOL(p) {assert(p); if(!p){return false;}} 22 | #define PTR_VOID(p) {assert(p); if(!p){return;}} 23 | #define SAFE_DELETE(p) {if(p) {delete p; p = 0;}} 24 | 25 | const int kmax_file_buffer_size = 10240; //上传下载文件的buffer的大小 26 | const int kmax_buffer_size = 1024; //普通传输的buffer大小 27 | const int kmax_http_pool_threads_num = 5; 28 | -------------------------------------------------------------------------------- /httpclient/url_parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file url_parser.h 3 | * @brief 简单的URL解析类(缺少各种判断,待完善) 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | 13 | NAMESPACE_BEGIN(Http) 14 | 15 | //URL解析类 16 | class URLParser 17 | { 18 | public: 19 | URLParser(const std::string& url); 20 | public: 21 | const std::string& getDomain() const { return _domain;} 22 | const std::string& getObject() const { return _object;} 23 | int getPort() const { return _port;} 24 | private: 25 | void parseURL(const std::string& url); 26 | void splitURL(const std::string& format_url); 27 | private: 28 | int _port; //端口 29 | std::string _domain; //域名 30 | std::string _object; //请求数据 31 | }; 32 | 33 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/thread.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file thread.h 3 | * @brief 线程封装 4 | * @author xiangwangfeng 5 | * @data 2011-7-6 6 | * @website www.xiangwangfeng.com 7 | * @note 这个类只在内部使用 所以不提供对线程强杀的方法,也不推荐使用 8 | */ 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | 13 | NAMESPACE_BEGIN(Util) 14 | 15 | class Thread 16 | { 17 | public: 18 | explicit Thread(const std::string& thread_name); 19 | virtual ~Thread(); 20 | public: 21 | void waitThreadExit(); 22 | void startThread(); 23 | void enterThread(); 24 | virtual void run() = 0; 25 | static unsigned int __stdcall ThreadProc(void* data); 26 | private: 27 | void setThreadName(); 28 | void launchThread(); 29 | protected: 30 | std::string _thread_name; 31 | void * _thread_handle; 32 | unsigned int _thread_id; 33 | 34 | }; 35 | 36 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/http_response_receiver.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_response_receiver.h 3 | * @brief Http反馈接受类 4 | * @author xiangwangfeng 5 | * @data 2011-4-26 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include 12 | #include "global_defs.h" 13 | 14 | namespace Util 15 | { 16 | class FileWriter; 17 | } 18 | 19 | NAMESPACE_BEGIN(Http) 20 | 21 | //Http反馈接受的Wrapper类 22 | class HttpResponseReceiver 23 | { 24 | public: 25 | HttpResponseReceiver(); 26 | HttpResponseReceiver(const std::wstring& filepath); 27 | ~HttpResponseReceiver(); 28 | public: 29 | bool write(const char* buffer,int length); 30 | void close(); 31 | const std::string& getBody() const { return _body;} 32 | private: 33 | std::string _body; 34 | bool _save_as_file; 35 | Util::FileWriter* _file_writer; 36 | const int _min_flush_length; 37 | }; 38 | 39 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/file_reader.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file_reader.cpp 3 | * @brief 读文件Wrapper类 4 | * @author xiangwangfeng 5 | * @data 2011-4-27 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #include "standard_header.h" 10 | #include "file_reader.h" 11 | 12 | NAMESPACE_BEGIN(Util) 13 | 14 | FileReader::FileReader(const std::wstring& filepath) 15 | :_filepath(filepath), 16 | _ready(false), 17 | _file(0) 18 | { 19 | 20 | } 21 | 22 | FileReader::~FileReader() 23 | { 24 | close(); 25 | } 26 | 27 | bool FileReader::open() 28 | { 29 | _ready = (_wfopen_s(&_file,_filepath.c_str(),L"rb") == 0); 30 | return _ready; 31 | } 32 | 33 | int FileReader::read(char *buffer, size_t length) 34 | { 35 | return _ready ? fread(buffer,1,length,_file) 36 | : -1 ; 37 | } 38 | 39 | void FileReader::close() 40 | { 41 | if (_ready) 42 | { 43 | fclose(_file); 44 | _ready = false; 45 | } 46 | } 47 | 48 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file util.h 3 | * @brief 通用的辅助函数集合 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | 13 | 14 | 15 | NAMESPACE_BEGIN(Util) 16 | 17 | //转换字符串为小写 18 | void makeLower(char* source,int len); 19 | 20 | //转换字符串为小写 21 | void makeLower(const std::string& input,std::string& output); 22 | 23 | //数字转字符串 24 | std::string num_to_string(int num); 25 | 26 | //除去字符串两边空格 27 | void trimString(const std::string& input,std::string& output); 28 | 29 | //Unicode转UTF8 30 | std::string toUTF8(const std::wstring& source); 31 | 32 | //Unicode转MBCS 33 | std::string toMBCS(const std::wstring& source); 34 | 35 | //返回文件大小 36 | int getFileSize(const std::wstring& filepath); 37 | 38 | //rfc1738编码 39 | std::string torfc1738(const std::string& source); 40 | 41 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/http_header_parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_header_parser.h 3 | * @brief Http头解析类 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | 13 | NAMESPACE_BEGIN(Http) 14 | 15 | //Http文件头解析类 16 | //这个解析类并不是一个通用的解析类,只关注如下字段: 17 | //是否Chunked传输,文件长度,Http返回码 18 | class HttpHeaderParser 19 | { 20 | public: 21 | HttpHeaderParser(const std::string& header); 22 | public: 23 | bool isChunked() const { return _chunked;} 24 | int getContentLength() const { return _content_length;} 25 | int getHttpCode() const { return _http_code;} 26 | private: 27 | void parseHeader(); 28 | void parseChunkedType(); 29 | void parseConetentLength(); 30 | void parseHttpCode(); 31 | private: 32 | std::string _header; 33 | bool _chunked; 34 | int _http_code; 35 | int _content_length; 36 | }; 37 | 38 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/socket_helper.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file socket_helper.h 3 | * @brief Socket相关的辅助函数 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | 13 | NAMESPACE_BEGIN(SocketHelper) 14 | 15 | void initializeWinSocket(); 16 | 17 | bool connectSocket(int volatile& handle,const std::string& remote_host_name, 18 | int remote_port_number,int timeout_millisecs); 19 | 20 | int readSocket(int handle,bool connected,char* dest_buffer,int max_read_length,bool block_util_all_arrived); 21 | 22 | int writeSocket(int handle,const char* source_buffer,int max_write_length); 23 | 24 | bool resetSocketOptions(int handle); 25 | 26 | bool setSocketBlockingState(int handle,bool block); 27 | 28 | int waitForReadiness (int handle, bool for_ready, int timeout_msecs); 29 | 30 | unsigned long getIntAddress(const char* ip_address); 31 | 32 | NAMESPACE_END(SocketHelper) -------------------------------------------------------------------------------- /httpclient/http_delegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_delegate.h 3 | * @brief Http传输的回调接口 4 | * @author xiangwangfeng 5 | * @data 2011-7-4 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "http_global.h" 12 | 13 | NAMESPACE_BEGIN(Http) 14 | 15 | class HttpRequest; 16 | class HttpResponse; 17 | 18 | //传输进度回调 19 | class IProgressDelegate 20 | { 21 | public: 22 | virtual void dataWriteProgress(int write_length,int total_length) = 0; 23 | virtual void dataReadProgress(int read_length,int total_length) = 0; 24 | virtual ~IProgressDelegate() {} 25 | }; 26 | 27 | //异步传输回调 28 | class IAsyncHttpDelegate : public IProgressDelegate 29 | { 30 | public: 31 | virtual void onError(HTTPERROR error_code,const HttpRequest* request,const HttpResponse* resposne) = 0; 32 | virtual void onSuccess(const HttpRequest* request,const HttpResponse* resposne) = 0; 33 | virtual ~IAsyncHttpDelegate() {} 34 | }; 35 | 36 | 37 | 38 | NAMESPACE_END(Http) 39 | -------------------------------------------------------------------------------- /httpclient/dll_init.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dll_init.cpp 3 | * @brief DLL加载卸载过程 4 | * @author xiangwangfeng 5 | * @data 2011-7-5 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "http_pool.h" 10 | 11 | void onDLLProcessAttach() 12 | { 13 | 14 | } 15 | 16 | void onDLLThreadAttach() 17 | { 18 | 19 | } 20 | 21 | void onDLLThreadDetach() 22 | { 23 | 24 | } 25 | 26 | 27 | void onDLLProcessDetach() 28 | { 29 | Http::HttpPool::freeInstance(); 30 | } 31 | 32 | 33 | BOOL APIENTRY DllMain(HANDLE hModule, 34 | DWORD ul_reason_for_call, 35 | LPVOID lpReserved) 36 | { 37 | switch( ul_reason_for_call ) 38 | { 39 | case DLL_PROCESS_ATTACH: 40 | 41 | onDLLProcessAttach(); 42 | break; 43 | case DLL_THREAD_ATTACH: 44 | onDLLThreadAttach(); 45 | break; 46 | case DLL_THREAD_DETACH: 47 | onDLLThreadDetach(); 48 | break; 49 | case DLL_PROCESS_DETACH: 50 | onDLLProcessDetach(); 51 | break; 52 | } 53 | return TRUE; 54 | } -------------------------------------------------------------------------------- /httpclient/file_writer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file file_writer.h 3 | * @brief 写文件Wrapper类 4 | * @author xiangwangfeng 5 | * @data 2011-4-25 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #include "standard_header.h" 10 | #include "file_writer.h" 11 | 12 | NAMESPACE_BEGIN(Util) 13 | 14 | FileWriter::FileWriter(const std::wstring &filepath) 15 | :_filepath(filepath), 16 | _ready(false), 17 | _file(0) 18 | { 19 | 20 | } 21 | 22 | FileWriter::~FileWriter() 23 | { 24 | close(); 25 | } 26 | 27 | bool FileWriter::create() 28 | { 29 | _ready = (_wfopen_s(&_file,_filepath.c_str(),L"wb") == 0); 30 | return _ready; 31 | } 32 | 33 | bool FileWriter::write(const char *buffer, size_t length) 34 | { 35 | return _ready ? length == fwrite(buffer,1,length,_file) 36 | : false; 37 | } 38 | 39 | void FileWriter::flush() 40 | { 41 | fflush(_file); 42 | } 43 | 44 | void FileWriter::close() 45 | { 46 | if (_ready) 47 | { 48 | fclose(_file); 49 | _ready = false; 50 | } 51 | } 52 | 53 | 54 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/http_response.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_response.h 3 | * @brief Http反馈 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include 12 | #include "global_defs.h" 13 | 14 | NAMESPACE_BEGIN(Http) 15 | 16 | //Http反馈 17 | class HTTP_CLASS HttpResponse 18 | { 19 | public: 20 | HttpResponse():_http_code(-1) {} 21 | public: 22 | int getHttpCode() const { return _http_code;}//反馈的HTTP状态嘛 23 | const std::string& getHeader() const { return _header;} //HTTP消息头 24 | const std::string& getBody() const { return _body;} //HTTP消息体,如果请求是以文件的形式被下载的,这里放的文件路径(UTF编码) 25 | 26 | private: 27 | void setHttpCode(int http_code) { _http_code = http_code;} 28 | void setBody(const std::string& body) { _body = body;} 29 | void setHeader(const std::string& header) { _header = header;} 30 | 31 | private: 32 | std::string _header; //Http头 33 | std::string _body; //Http体 34 | int _http_code; //Http返回码 35 | friend class HttpClient; 36 | }; 37 | 38 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/streaming_socket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file streaming_socket.h 3 | * @brief 对原生WinSocket的封装类 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | 13 | NAMESPACE_BEGIN(Http) 14 | 15 | //对原生SOCKET的封装 16 | class HTTP_CLASS StreamingSocket 17 | { 18 | public: 19 | StreamingSocket(); 20 | ~StreamingSocket(); 21 | public: 22 | bool connect(const std::string& remote_host_name, 23 | int remote_port_number, 24 | int timeout = 30000); 25 | 26 | bool isConnected() const {return _connected;} 27 | 28 | void close(); 29 | 30 | int read(char* dest_buffer,int max_read_length); 31 | 32 | int write(const char* source_buffer,int max_write_length); 33 | 34 | bool writeAll(const char* source_buffer,int max_write_length); 35 | private: 36 | bool resetSocketOptions(); 37 | private: 38 | std::string _host_name; //目标地址 39 | int volatile _port_number,_handle; //端口号,SOCKET 40 | bool _connected; //是否已连接 41 | 42 | }; 43 | 44 | 45 | NAMESPACE_END(Http) 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2014 Xiang Wangfeng (http://xiangwangfeng.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /httpclient/proxy_socket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy_socket.h 3 | * @brief 支持代理的Socket类 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "proxy_config.h" 12 | #include "streaming_socket.h" 13 | 14 | NAMESPACE_BEGIN(Http) 15 | 16 | //支持代理的Socket类封装 17 | class HTTP_CLASS ProxySocket 18 | { 19 | public: 20 | ProxySocket(); 21 | ProxySocket(const std::string& remote_host_name,int port_number); 22 | ~ProxySocket(); 23 | 24 | public: 25 | void setHost(const std::string& remote_host_name,int port_number); 26 | static void setProxy(const ProxyConfig& proxy_config); 27 | 28 | bool connect(); 29 | bool isConnected() const { return _socket.isConnected();} 30 | int read(char* dst_buffer,int max_read_length); 31 | int write(const char* source_buffer,int max_write_length); 32 | bool writeAll(const char* source_buffer,int max_write_length); //确保所有数据都被发送 33 | void close(); 34 | private: 35 | bool handShakeWithHttpProxy(); 36 | bool handShakeWithSock4Proxy(); 37 | bool handShakeWithSock5Proxy(); 38 | private: 39 | std::string _host_name; 40 | int _port_number; 41 | StreamingSocket _socket; 42 | static ProxyConfig _proxy_config; 43 | }; 44 | 45 | 46 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/proxy_config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy_config.h 3 | * @brief 代理配置项 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | 13 | NAMESPACE_BEGIN(Http) 14 | 15 | //代理类型 16 | enum HTTP_DATA ProxyType 17 | { 18 | PROXY_NULL, 19 | PROXY_HTTP, 20 | PROXY_SOCK4, 21 | PROXY_SOCK5, 22 | }; 23 | 24 | //代理配置项 25 | struct HTTP_DATA ProxyConfig 26 | { 27 | ProxyConfig() 28 | :_proxy_type(PROXY_NULL) 29 | ,_port_number(0) 30 | { 31 | 32 | } 33 | 34 | ProxyConfig(ProxyType proxy_type,const std::string& host_name,int port_number, 35 | const std::string& username ="",const std::string& password = "") 36 | :_proxy_type(proxy_type), 37 | _host_name(host_name), 38 | _port_number(port_number), 39 | _username(username), 40 | _password(password) 41 | { 42 | 43 | } 44 | 45 | void copy(const ProxyConfig& proxy_config) 46 | { 47 | _proxy_type = proxy_config._proxy_type; 48 | _host_name = proxy_config._host_name; 49 | _port_number = proxy_config._port_number; 50 | _username = proxy_config._username; 51 | _password = proxy_config._password; 52 | } 53 | 54 | ProxyType _proxy_type; 55 | std::string _host_name; 56 | int _port_number; 57 | std::string _username; 58 | std::string _password; 59 | }; 60 | 61 | 62 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/http_response_receiver.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_response_receiver.cpp 3 | * @brief Http反馈接受类 4 | * @author xiangwangfeng 5 | * @data 2011-4-26 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "http_response_receiver.h" 10 | #include "file_writer.h" 11 | 12 | NAMESPACE_BEGIN(Http) 13 | 14 | HttpResponseReceiver::HttpResponseReceiver() 15 | :_save_as_file(false), 16 | _min_flush_length(1024 * 100), 17 | _file_writer(0) 18 | { 19 | 20 | } 21 | 22 | 23 | HttpResponseReceiver::HttpResponseReceiver(const std::wstring& filepath) 24 | :_min_flush_length(1024 * 100), 25 | _save_as_file(true) 26 | { 27 | _file_writer = new Util::FileWriter(filepath); 28 | _file_writer->create(); 29 | } 30 | 31 | HttpResponseReceiver::~HttpResponseReceiver() 32 | { 33 | SAFE_DELETE(_file_writer); 34 | } 35 | 36 | bool HttpResponseReceiver::write(const char *buffer, int length) 37 | { 38 | if (!_save_as_file) 39 | { 40 | _body.append(buffer,length); 41 | return true; 42 | } 43 | else 44 | { 45 | static int write_bytes = 0; 46 | bool write = _file_writer->write(buffer,length); 47 | if (write) 48 | { 49 | write_bytes += write; 50 | if (write_bytes >= _min_flush_length) 51 | { 52 | write_bytes -= _min_flush_length; 53 | _file_writer->flush(); 54 | } 55 | } 56 | return write; 57 | } 58 | } 59 | 60 | void HttpResponseReceiver::close() 61 | { 62 | if (_save_as_file) 63 | { 64 | _file_writer->close(); 65 | } 66 | } 67 | 68 | 69 | 70 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/http_pool.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_pool.h 3 | * @brief Http传输池 4 | * @author xiangwangfeng 5 | * @data 2011-7-6 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #pragma once 9 | #pragma warning(disable:4251) 10 | 11 | #include 12 | #include 13 | #include "global_defs.h" 14 | 15 | NAMESPACE_BEGIN(Util) 16 | class Lock; 17 | NAMESPACE_END(Util) 18 | 19 | NAMESPACE_BEGIN(Http) 20 | 21 | class HttpPool; 22 | class HttpClient; 23 | class HttpRequest; 24 | class IAsyncHttpDelegate; 25 | class HttpThread; 26 | 27 | struct HttpWorkItem 28 | { 29 | HttpRequest* _http_request; 30 | IAsyncHttpDelegate* _async_delegate; 31 | HttpWorkItem(HttpRequest* http_request,IAsyncHttpDelegate* delegate); 32 | ~HttpWorkItem(); 33 | }; 34 | 35 | 36 | class HTTP_CLASS HttpPool 37 | { 38 | public: 39 | ~HttpPool(); 40 | static HttpPool* getInstance(); 41 | static void freeInstance(); 42 | 43 | //异步调用HTTP传输,外部必须自己控制http_request和delegate的生存期 44 | void postRequest(HttpRequest* http_request,IAsyncHttpDelegate* delegate); 45 | private: 46 | HttpPool(); 47 | void init(); 48 | void releaseAll(); 49 | HttpWorkItem* getWork(); 50 | std::string genThreadName(int index); 51 | private: 52 | std::vector _http_threads; //线程列表 53 | std::list _http_work_list; //工作对象 54 | static HttpPool* _pool; //静态池对象 55 | void* _exit_event; //退出事件 56 | void* _work_semaphore; //工作信号量 57 | Util::Lock* _work_lock; //线程池队列的锁 58 | friend class HttpThread; 59 | }; 60 | 61 | NAMESPACE_END(Http) 62 | -------------------------------------------------------------------------------- /httpclient.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual Studio 2008 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "httpclient", "httpclient\httpclient.vcproj", "{0E873164-0137-4027-BF44-7FE5E1FAAA3D}" 5 | EndProject 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcproj", "{AD0F2A92-6EAA-4C03-8AE8-592828A12ACF}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {0E873164-0137-4027-BF44-7FE5E1FAAA3D} = {0E873164-0137-4027-BF44-7FE5E1FAAA3D} 9 | EndProjectSection 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Win32 = Debug|Win32 14 | Release|Win32 = Release|Win32 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {0E873164-0137-4027-BF44-7FE5E1FAAA3D}.Debug|Win32.ActiveCfg = Debug|Win32 18 | {0E873164-0137-4027-BF44-7FE5E1FAAA3D}.Debug|Win32.Build.0 = Debug|Win32 19 | {0E873164-0137-4027-BF44-7FE5E1FAAA3D}.Release|Win32.ActiveCfg = Release|Win32 20 | {0E873164-0137-4027-BF44-7FE5E1FAAA3D}.Release|Win32.Build.0 = Release|Win32 21 | {AD0F2A92-6EAA-4C03-8AE8-592828A12ACF}.Debug|Win32.ActiveCfg = Debug|Win32 22 | {AD0F2A92-6EAA-4C03-8AE8-592828A12ACF}.Debug|Win32.Build.0 = Debug|Win32 23 | {AD0F2A92-6EAA-4C03-8AE8-592828A12ACF}.Release|Win32.ActiveCfg = Release|Win32 24 | {AD0F2A92-6EAA-4C03-8AE8-592828A12ACF}.Release|Win32.Build.0 = Release|Win32 25 | EndGlobalSection 26 | GlobalSection(SolutionProperties) = preSolution 27 | HideSolutionNode = FALSE 28 | EndGlobalSection 29 | EndGlobal 30 | -------------------------------------------------------------------------------- /httpclient/thread.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file thread.cpp 3 | * @brief 线程封装 4 | * @author xiangwangfeng 5 | * @data 2011-7-6 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #include "standard_header.h" 10 | #include "thread.h" 11 | #include "process.h" 12 | 13 | 14 | 15 | NAMESPACE_BEGIN(Util) 16 | 17 | Thread::Thread(const std::string& thread_name) 18 | :_thread_name(thread_name), 19 | _thread_handle(0), 20 | _thread_id(0) 21 | { 22 | } 23 | 24 | Thread::~Thread() 25 | { 26 | CloseHandle(_thread_handle); 27 | } 28 | 29 | void Thread::startThread() 30 | { 31 | launchThread(); 32 | } 33 | 34 | void Thread::launchThread() 35 | { 36 | _thread_handle = (HANDLE)_beginthreadex(0,0,&Thread::ThreadProc,this,0,&_thread_id); 37 | } 38 | 39 | unsigned int __stdcall Thread::ThreadProc(void *data) 40 | { 41 | Thread* thread = (Thread*)data; 42 | thread->enterThread(); 43 | return 0; 44 | } 45 | 46 | void Thread::enterThread() 47 | { 48 | setThreadName(); //设置当前Thread名字,方便调试 49 | run(); 50 | } 51 | 52 | void Thread::waitThreadExit() 53 | { 54 | if (_thread_handle) 55 | { 56 | WaitForSingleObject(_thread_handle,INFINITE); 57 | } 58 | } 59 | 60 | void Thread::setThreadName() 61 | { 62 | struct 63 | { 64 | DWORD dwType; 65 | LPCSTR szName; 66 | DWORD dwThreadID; 67 | DWORD dwFlags; 68 | } info; 69 | 70 | info.dwType = 0x1000; 71 | info.szName = _thread_name.c_str(); 72 | info.dwThreadID = GetCurrentThreadId(); 73 | info.dwFlags = 0; 74 | 75 | __try 76 | { 77 | RaiseException (0x406d1388 /*MS_VC_EXCEPTION*/, 0, sizeof (info) / sizeof (ULONG_PTR), (ULONG_PTR*) &info); 78 | } 79 | __except (EXCEPTION_CONTINUE_EXECUTION) 80 | {} 81 | } 82 | 83 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/url_parser.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file url_parser.cpp 3 | * @brief 简单的URL解析类(缺少各种判断,待完善) 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "url_parser.h" 10 | #include "util.h" 11 | 12 | NAMESPACE_BEGIN(Http) 13 | 14 | 15 | URLParser::URLParser(const std::string &url) 16 | :_port(80), 17 | _object(url) 18 | { 19 | parseURL(url); 20 | } 21 | 22 | void URLParser::parseURL(const std::string &url) 23 | { 24 | size_t length = url.length(); 25 | if (length < 7) //过短的URL可以认为是无效的 26 | { 27 | assert(false); 28 | return; 29 | } 30 | else 31 | { 32 | std::string protocal = url.substr(0,7); 33 | std::string format_url = url; 34 | if (_strcmpi(protocal.c_str(),"http://") == 0) 35 | { 36 | format_url = url.substr(7,length-7); 37 | } 38 | splitURL(format_url); 39 | } 40 | } 41 | 42 | void URLParser::splitURL(const std::string& format_url) 43 | { 44 | size_t index = format_url.find('/'); 45 | size_t length = format_url.length(); 46 | if (index != std::string::npos 47 | && index != length - 1) 48 | { 49 | _object = format_url.substr(index,length - index); 50 | std::string domain = format_url.substr(0,index); 51 | size_t colon_index = domain.find(":"); 52 | size_t domain_length = domain.length(); 53 | if (colon_index != std::string::npos && 54 | colon_index < domain_length - 1) 55 | { 56 | _domain = domain.substr(0,colon_index); 57 | std::string port = domain.substr(colon_index + 1,domain_length - colon_index -1); 58 | try 59 | { 60 | _port = atoi(port.c_str()); 61 | } 62 | catch (...) 63 | { 64 | _port = 80; 65 | } 66 | } 67 | else 68 | { 69 | _domain = domain; 70 | } 71 | } 72 | else 73 | { 74 | _domain = format_url; 75 | } 76 | } 77 | 78 | 79 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/http_post_file.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_post_file.h 3 | * @brief 用于HTTP POST的文件接口定义 4 | * @author xiangwangfeng 5 | * @data 2011-4-26 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include 12 | #include "global_defs.h" 13 | #include "http_global.h" 14 | 15 | NAMESPACE_BEGIN(Http) 16 | 17 | class ProxySocket; 18 | class IProgressDelegate; 19 | 20 | 21 | //文件POST传输结构 22 | struct HTTP_CLASS FilePoster 23 | { 24 | ProxySocket* _proxy_socket; 25 | HTTPERROR& _http_error; 26 | IProgressDelegate* _delegate; 27 | 28 | FilePoster(ProxySocket* proxy_socket,HTTPERROR& http_error,IProgressDelegate* delegate) 29 | : _proxy_socket(proxy_socket) ,_http_error(http_error),_delegate(delegate){} 30 | }; 31 | 32 | //HTTP POST文件接口 33 | class HTTP_CLASS IHttpPostFile 34 | { 35 | public: 36 | const std::string& getContentType()const { return _content_type;} 37 | const std::string& getFilename() const { return _filename;} 38 | int getFileSize() const { return _file_size;} 39 | 40 | //传输接口,需要派生类实现 41 | virtual bool postFile(FilePoster& file_poster) = 0; 42 | virtual ~IHttpPostFile() {} 43 | protected: 44 | int _file_size; //文件大小 45 | std::string _filename; //文件名 46 | std::string _content_type; //Content-Type 47 | }; 48 | 49 | 50 | //本地文件流 51 | class HTTP_CLASS HttpFileStream : public IHttpPostFile 52 | { 53 | public: 54 | HttpFileStream(const std::wstring& filepath,const std::string& content_type = "Application/oct-stream"); 55 | public: 56 | virtual bool postFile(FilePoster& file_poster); 57 | private: 58 | std::wstring _filepath; //文件名 59 | }; 60 | 61 | 62 | //内存文件流 63 | class HTTP_CLASS HttpMemoryStream : public IHttpPostFile 64 | { 65 | public: 66 | HttpMemoryStream(const char* data,size_t length,const std::string& filename, 67 | const std::string& content_type = "Application/oct-stream"); 68 | public: 69 | virtual bool postFile(FilePoster& file_poster); 70 | private: 71 | std::string _data; //数据块 72 | }; 73 | 74 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/http_thread.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_thread.cpp 3 | * @brief Http线程 4 | * @author xiangwangfeng 5 | * @data 2011-7-8 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #include "standard_header.h" 10 | #include "http_thread.h" 11 | #include "http_delegate.h" 12 | #include "http_request.h" 13 | #include "http_response.h" 14 | #include "http_client.h" 15 | #include "http_pool.h" 16 | 17 | NAMESPACE_BEGIN(Http) 18 | 19 | 20 | HttpThread::HttpThread(const std::string& name,HttpPool* http_pool) 21 | :Util::Thread(name), 22 | _http_pool(http_pool) 23 | { 24 | _http_client = new HttpClient(); 25 | } 26 | 27 | HttpThread::~HttpThread() 28 | { 29 | delete _http_client; 30 | } 31 | 32 | void HttpThread::run() 33 | { 34 | HANDLE notify_handle[2]; 35 | notify_handle[0] = _http_pool->_exit_event; 36 | notify_handle[1] = _http_pool->_work_semaphore; 37 | 38 | while (true) 39 | { 40 | DWORD object = WaitForMultipleObjects(2,notify_handle,FALSE,INFINITE); 41 | if (object == WAIT_OBJECT_0) 42 | { 43 | break; //退出 44 | } 45 | else if (object == WAIT_OBJECT_0 + 1) 46 | { 47 | executeHttp();//执行任务 48 | } 49 | else 50 | { 51 | assert(false); 52 | break; //理论上不会到这一步 53 | } 54 | } 55 | } 56 | 57 | void HttpThread::killSelf() 58 | { 59 | _http_client->killSelf(); 60 | } 61 | 62 | void HttpThread::executeHttp() 63 | { 64 | HttpWorkItem* item = _http_pool->getWork(); 65 | if (item) 66 | { 67 | IAsyncHttpDelegate* delegate = item->_async_delegate; 68 | 69 | //进行HTTP交互 70 | _http_client->reset(); 71 | _http_client->setProgressDelegate(delegate); 72 | HttpResponse response; 73 | bool execute = _http_client->execute(item->_http_request,&response); 74 | //执行回调 75 | if (delegate) 76 | { 77 | if (execute) 78 | { 79 | delegate->onSuccess(item->_http_request,&response); 80 | } 81 | else 82 | { 83 | delegate->onError(_http_client->getErrorCode(),item->_http_request,&response); 84 | } 85 | } 86 | 87 | //回收资源 88 | delete item; 89 | } 90 | } 91 | 92 | 93 | NAMESPACE_END(Http) 94 | -------------------------------------------------------------------------------- /httpclient/http_header_parser.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_header_parser.cpp 3 | * @brief Http头解析类 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "http_header_parser.h" 10 | #include "util.h" 11 | 12 | NAMESPACE_BEGIN(Http) 13 | 14 | HttpHeaderParser::HttpHeaderParser(const std::string& header) 15 | :_chunked(false), 16 | _http_code(-1), 17 | _content_length(0) 18 | { 19 | Util::makeLower(header,_header); 20 | parseHeader(); 21 | } 22 | 23 | 24 | void HttpHeaderParser::parseHeader() 25 | { 26 | parseHttpCode(); 27 | parseChunkedType(); 28 | parseConetentLength(); 29 | } 30 | 31 | void HttpHeaderParser::parseHttpCode() 32 | { 33 | //正常HTTP头中 第9-11位为HTTP状态码 34 | size_t lenght = _header.length(); 35 | if (lenght >= 12) 36 | { 37 | std::string http_code = _header.substr(9,3); 38 | try 39 | { 40 | _http_code = atoi(http_code.c_str()); 41 | } 42 | catch (...) 43 | { 44 | _http_code = -1; 45 | } 46 | } 47 | } 48 | 49 | void HttpHeaderParser::parseChunkedType() 50 | { 51 | size_t index = _header.find("transfer-encoding:"); 52 | if (index != std::string::npos) 53 | { 54 | size_t start_index = index + 19; 55 | size_t end_index = _header.find("\r\n",start_index); 56 | std::string header_value= _header.substr(start_index,end_index-start_index); 57 | std::string transfer_encoding; 58 | Util::trimString(header_value,transfer_encoding); 59 | _chunked = (transfer_encoding.compare("chunked") == 0); 60 | } 61 | } 62 | 63 | void HttpHeaderParser::parseConetentLength() 64 | { 65 | 66 | size_t start_index = _header.find("content-length:"); 67 | if (start_index == std::string::npos) 68 | { 69 | return; 70 | } 71 | size_t end_index = _header.find("\r\n",start_index); 72 | if (end_index != std::string::npos) 73 | { 74 | std::string content_length = _header.substr(start_index+16,end_index-start_index-16); 75 | try 76 | { 77 | _content_length = atoi(content_length.c_str()); 78 | } 79 | catch(...) 80 | { 81 | _content_length = 0; 82 | } 83 | } 84 | } 85 | 86 | 87 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/streaming_socket.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file streaming_socket.cpp 3 | * @brief 对原生WinSocket的封装类 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "streaming_socket.h" 10 | #include "socket_helper.h" 11 | 12 | NAMESPACE_BEGIN(Http) 13 | 14 | StreamingSocket::StreamingSocket() 15 | :_port_number(0),_handle(-1),_connected(false) 16 | { 17 | SocketHelper::initializeWinSocket(); 18 | } 19 | 20 | StreamingSocket::~StreamingSocket() 21 | { 22 | close(); 23 | } 24 | 25 | bool StreamingSocket::connect(const std::string &remote_host_name, int remote_port_number, 26 | int timeout) 27 | { 28 | close(); 29 | 30 | _host_name = remote_host_name; 31 | _port_number = remote_port_number; 32 | 33 | _connected = SocketHelper::connectSocket(_handle,_host_name,_port_number,timeout); 34 | 35 | 36 | if (!(_connected && resetSocketOptions())) 37 | { 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | void StreamingSocket::close() 44 | { 45 | if (_connected || _handle != SOCKET_ERROR) 46 | { 47 | closesocket(_handle); 48 | } 49 | _connected = false; 50 | _host_name = ""; 51 | _port_number = 0; 52 | _handle = -1; 53 | } 54 | 55 | 56 | int StreamingSocket::read(char *dest_buffer, int max_read_length) 57 | { 58 | return _connected ? SocketHelper::readSocket(_handle,_connected,dest_buffer,max_read_length,false) : 59 | -1; 60 | } 61 | 62 | int StreamingSocket::write(const char *source_buffer, int max_write_length) 63 | { 64 | return _connected ? SocketHelper::writeSocket(_handle,source_buffer,max_write_length) : 65 | -1; 66 | } 67 | 68 | bool StreamingSocket::writeAll(const char* source_buffer,int max_write_length) 69 | { 70 | int unsend_data_length = max_write_length; 71 | const char* data = source_buffer; 72 | 73 | while (unsend_data_length) 74 | { 75 | int ret = write(data,unsend_data_length); 76 | if (ret <= 0) 77 | { 78 | break; 79 | } 80 | else 81 | { 82 | unsend_data_length -= ret; 83 | data += ret; 84 | } 85 | 86 | } 87 | return unsend_data_length == 0; 88 | } 89 | 90 | bool StreamingSocket::resetSocketOptions() 91 | { 92 | return SocketHelper::resetSocketOptions(_handle); 93 | } 94 | 95 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/http_client.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_client.h 3 | * @brief Http传输类 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include "global_defs.h" 12 | #include "http_global.h" 13 | #include "proxy_config.h" 14 | 15 | namespace Util 16 | { 17 | class Lock; 18 | } 19 | 20 | NAMESPACE_BEGIN(Http) 21 | 22 | class ProxySocket; 23 | class HttpRequest; 24 | class HttpResponse; 25 | class IHttpPostFile; 26 | class IProgressDelegate; 27 | 28 | //Http传输类 29 | class HTTP_CLASS HttpClient 30 | { 31 | public: 32 | HttpClient(bool keep_connection = false); 33 | ~HttpClient(); 34 | public: 35 | //执行Http参数方法 36 | bool execute(HttpRequest* request,HttpResponse* respone); //执行Http传输 37 | static void setProxy(const ProxyConfig* proxy_config); //设置代理 38 | HTTPERROR getErrorCode() const { return _http_error;} //返回错误码 39 | void setProgressDelegate(IProgressDelegate* delegate) { _delegate = delegate;} //设置进度委托 40 | private: 41 | void killSelf(); //弃用当前HttpClient 42 | void reset(); //重设当前HttpClient 43 | void setErrorCode(HTTPERROR http_error); 44 | bool httpGet(); 45 | bool httpPost(); 46 | bool getResponse(); 47 | bool downloadHeader(std::string& body_header); 48 | bool downloadBody(const std::string& body_header); 49 | bool downloadFixedSizeBody(const std::string& body_header,int content_length); 50 | bool downloadChunkedBody(const std::string& body_header); 51 | bool continueToReceiveBody(std::string& body); 52 | bool sendHeader(); 53 | bool sendBody(); 54 | bool doMultipartPost(); 55 | bool uploadFile(IHttpPostFile* post_file); 56 | void onDataReadProgress(int read_length,int total_length); 57 | DECLARE_NON_COPYABLE(HttpClient) 58 | private: 59 | ProxySocket* _proxy_socket; //支持代理的Socket类 60 | HTTPERROR _http_error; //Http传输错误码 61 | bool _keep_connection; //是否保持连接 62 | HttpRequest* _request; //Http请求方法 63 | HttpResponse* _response; //Http反馈数据 64 | IProgressDelegate* _delegate; //Http传输的委托 65 | bool _is_valid; //是否可用(如果调用了killSelf接口这个Client就弃用) 66 | Util::Lock* _is_valid_lock; //是否可用的同步锁 67 | 68 | friend class HttpThread; 69 | }; 70 | 71 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/util.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file util.cpp 3 | * @brief 通用的辅助函数集合 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #include "standard_header.h" 10 | #include "util.h" 11 | 12 | 13 | #pragma warning(disable :4996) 14 | 15 | NAMESPACE_BEGIN(Util) 16 | 17 | void makeLower(char* source,int len) 18 | { 19 | std::transform(source, source + len, source, tolower); 20 | } 21 | 22 | void makeLower(const std::string& input,std::string& output) 23 | { 24 | size_t length = input.size(); 25 | if (length > 0) 26 | { 27 | output.resize(length); 28 | std::transform(input.begin(),input.end(),output.begin(),tolower) 29 | ; } 30 | } 31 | 32 | void trimString(const std::string& input,std::string& output) 33 | { 34 | output = input; 35 | size_t start_index = input.find_first_not_of(" "); 36 | size_t end_index = input.find_last_not_of(" "); 37 | if (start_index != std::string::npos && 38 | end_index >= start_index) 39 | { 40 | output = input.substr(start_index,end_index + 1 - start_index); 41 | } 42 | } 43 | 44 | 45 | 46 | std::string num_to_string(int num) 47 | { 48 | char buff[33] = {0}; 49 | sprintf_s(buff,33,"%d",num); 50 | return std::string(buff); 51 | } 52 | 53 | std::string toUTF8(const std::wstring& source) 54 | { 55 | int bytes = WideCharToMultiByte(CP_UTF8,0,source.c_str(),-1,NULL,0,NULL,NULL); 56 | char* buffer = new char[bytes]; 57 | bytes = WideCharToMultiByte(CP_UTF8,0,source.c_str(),-1,buffer,bytes,NULL,NULL); 58 | 59 | std::string dest(buffer); 60 | delete [] buffer; 61 | return dest ; 62 | } 63 | 64 | 65 | std::string toMBCS(const std::wstring& source) 66 | { 67 | int bytes = WideCharToMultiByte(CP_ACP,0,source.c_str(),-1,NULL,0,NULL,NULL); 68 | char* buffer = new char[bytes]; 69 | bytes = WideCharToMultiByte(CP_ACP,0,source.c_str(),-1,buffer,bytes,NULL,NULL); 70 | 71 | std::string dest(buffer); 72 | delete [] buffer; 73 | return dest ; 74 | } 75 | 76 | 77 | int getFileSize(const std::wstring& filepath) 78 | { 79 | std::string mbcs_filepath = toMBCS(filepath); 80 | struct stat file_stat; 81 | stat(mbcs_filepath.c_str(),&file_stat); 82 | return (int)file_stat.st_size; //不支持大文件 83 | } 84 | 85 | 86 | std::string torfc1738(const std::string& source) 87 | { 88 | static char hex[] = "0123456789ABCDEF"; 89 | std::string dst; 90 | for (size_t i = 0; i < source.size(); ++i) 91 | { 92 | if (isalnum(source[i])) 93 | { 94 | dst += source[i]; 95 | } 96 | else 97 | if (source[i] == ' ') 98 | { 99 | dst += '+'; 100 | } 101 | else 102 | { 103 | unsigned char c = static_cast(source[i]); 104 | dst += '%'; 105 | dst += hex[c / 16]; 106 | dst += hex[c % 16]; 107 | } 108 | } 109 | return dst; 110 | } 111 | 112 | 113 | NAMESPACE_END(Util) -------------------------------------------------------------------------------- /httpclient/http_request.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_request.h 3 | * @brief Http请求 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #pragma once 10 | #include 11 | #include 12 | #include "global_defs.h" 13 | 14 | #pragma warning(disable:4251) 15 | 16 | NAMESPACE_BEGIN(Http) 17 | 18 | class IHttpPostFile; 19 | 20 | //Http字段 21 | struct HttpField 22 | { 23 | std::string _field_name; 24 | std::string _field_value; 25 | HttpField(const std::string& name,const std::string& value) 26 | :_field_name(name),_field_value(value){} 27 | }; 28 | 29 | //Http上传文件定义 30 | struct HttpFile 31 | { 32 | std::string _name; 33 | IHttpPostFile* _post_file; 34 | HttpFile(const std::string& name,IHttpPostFile* post_file) 35 | :_name(name),_post_file(post_file){} 36 | ~HttpFile(); 37 | }; 38 | 39 | 40 | class HTTP_CLASS HttpRequest 41 | { 42 | public: 43 | HttpRequest(); 44 | HttpRequest(const std::string& method,const std::string& url); 45 | ~HttpRequest(); 46 | public: 47 | void setRequest(const std::string& method,const std::string& url); 48 | void addHeaderField(const std::string& name,const std::string& value); 49 | void addFile(const std::string& name,IHttpPostFile* post_file); 50 | void addField(const std::string& name,const std::string& value) { _body_fields.push_back(HttpField(name,value));} 51 | void setBody(const std::string& body) { _body = body;} 52 | void setOnlyDownloadHeader() { _only_download_header = true;} 53 | void saveToFile(const std::wstring& filepath) { _save_as_file = true; _filepath = filepath;} 54 | private: 55 | const std::string& getMethod() const { return _http_method;} 56 | const std::string& getHost() const { return _http_host;} 57 | int getPortNumber() const { return _port_number;} 58 | const std::string& getBody() const { return _body;} 59 | const std::string& getBoundary() const { return _boundary;} 60 | bool isMultipart() const { return _is_multipart;} 61 | const std::vector& getFiles() const { return _post_files;} 62 | bool onlyDownloadHeader()const { return _only_download_header;} 63 | bool saveAsFile() const { return _save_as_file;} 64 | const std::wstring& getFilePath() const { return _filepath;} 65 | int generateHeader(std::string& header); 66 | void addExtraHeaderField(const std::string& name,const std::string& value); 67 | void initDefaultValue(); 68 | void calcBody(); 69 | void genBoundary(); 70 | DECLARE_NON_COPYABLE(HttpRequest) 71 | private: 72 | std::string _http_method; //HTTP方法 73 | std::string _http_host; //主机 74 | std::string _object; //请求对象 75 | int _port_number; //端口 76 | std::vector _header_fields; //HTTP头字段 77 | std::vector _body_fields; //HTTP体参数 78 | std::vector _post_files; //上传文件列表 79 | std::string _boundary; //Boundary; 80 | std::string _body; //HTTP体 81 | bool _is_multipart; //是否是Multipart 82 | bool _only_download_header;//是否只下载HTTP头 83 | std::wstring _filepath; //用于下载的文件路径 84 | bool _save_as_file; //是否以文件的形式接受反馈 85 | 86 | friend class HttpClient; 87 | }; 88 | 89 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../httpclient/http.h" 4 | 5 | class DelegateA : public Http::IProgressDelegate 6 | { 7 | public: 8 | virtual void dataWriteProgress(int write_length,int total_length) {std::cout<getBody()<saveToFile(buff); 53 | DelegateB* delegate = new DelegateB(); 54 | Http::HttpPool::getInstance()->postRequest(request,delegate); 55 | } 56 | 57 | } 58 | 59 | int main() 60 | { 61 | //未使用代理 62 | Http::ProxyConfig empty_proxy; 63 | std::wstring noproxy_filepath = L"D:\\httpclient_download\\noproxy.jpg"; 64 | SyncDownloadFile(noproxy_filepath); 65 | 66 | //使用HTTP代理 67 | Http::ProxyConfig http_proxy(Http::PROXY_HTTP,"192.168.132.163",808,"proxy","test"); 68 | Http::HttpClient::setProxy(&http_proxy); 69 | std::wstring http_proxy_filepath = L"D:\\httpclient_download\\httpproxy.jpg"; 70 | SyncDownloadFile(http_proxy_filepath); 71 | 72 | 73 | //使用Socks4代理 74 | //Http::ProxyConfig socks4_proxy(Http::PROXY_SOCK4,"192.168.132.163",1080); 75 | //Http::HttpClient::setProxy(&socks4_proxy); 76 | //std::wstring sock4_proxy_filepath = L"D:\\httpclient_download\\socks4.jpg"; 77 | //SyncDownloadFile(sock4_proxy_filepath); 78 | 79 | 80 | //使用Socks5代理 81 | Http::ProxyConfig socks5_proxy(Http::PROXY_SOCK5,"192.168.132.163",1080,"proxy","test"); 82 | Http::HttpClient::setProxy(&socks5_proxy); 83 | std::wstring socks5_proxy_filepath = L"D:\\httpclient_download\\socks5.jpg"; 84 | SyncDownloadFile(socks5_proxy_filepath); 85 | 86 | 87 | 88 | Http::HttpClient::setProxy(&empty_proxy); 89 | //AsyncDownloadFile(); 90 | 91 | Sleep(5000000); 92 | return 0; 93 | } -------------------------------------------------------------------------------- /httpclient/http_pool.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_pool.cpp 3 | * @brief Http传输池 4 | * @author xiangwangfeng 5 | * @data 2011-7-6 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "http_pool.h" 10 | #include "lock.h" 11 | #include "http_delegate.h" 12 | #include "http_request.h" 13 | #include "http_response.h" 14 | #include "http_client.h" 15 | #include "http_thread.h" 16 | 17 | NAMESPACE_BEGIN(Http) 18 | 19 | HttpPool* HttpPool::_pool = 0; 20 | 21 | 22 | HttpWorkItem::HttpWorkItem(HttpRequest* http_request, 23 | IAsyncHttpDelegate* delegate) 24 | :_http_request(http_request),_async_delegate(delegate) 25 | { 26 | assert(_http_request); 27 | } 28 | 29 | HttpWorkItem::~HttpWorkItem() 30 | { 31 | //HttpWorkItem不负责清理内部数据 32 | } 33 | 34 | 35 | 36 | HttpPool::HttpPool() 37 | { 38 | init(); 39 | } 40 | 41 | HttpPool::~HttpPool() 42 | { 43 | releaseAll(); 44 | } 45 | 46 | 47 | HttpPool* HttpPool::getInstance() 48 | { 49 | if (_pool == 0) 50 | { 51 | _pool = new HttpPool(); 52 | } 53 | return _pool; 54 | } 55 | 56 | void HttpPool::freeInstance() 57 | { 58 | SAFE_DELETE(_pool); 59 | } 60 | 61 | void HttpPool::init() 62 | { 63 | //初始化信号量,退出事件,锁 64 | _exit_event = CreateEvent(NULL,TRUE,FALSE,NULL); 65 | _work_semaphore = CreateSemaphore(NULL,0,INT_MAX,NULL); 66 | _work_lock = new Util::Lock(); 67 | 68 | //初始化线程 69 | for (int i = 0; i < kmax_http_pool_threads_num; i++) 70 | { 71 | std::string thread_name = genThreadName(i); 72 | HttpThread* http_thread = new HttpThread(thread_name,this); 73 | _http_threads.push_back(http_thread); 74 | http_thread->startThread(); 75 | } 76 | } 77 | 78 | void HttpPool::releaseAll() 79 | { 80 | //释放退出事件 81 | ::SetEvent(_exit_event); 82 | 83 | //调用HttpThread自杀的方法 84 | for (size_t i = 0; i < _http_threads.size(); i++) 85 | { 86 | _http_threads[i]->killSelf(); 87 | } 88 | 89 | //等待HttpThread退出 90 | for (size_t i = 0; i < _http_threads.size(); i++) 91 | { 92 | _http_threads[i]->waitThreadExit(); 93 | delete _http_threads[i]; 94 | } 95 | _http_threads.clear(); 96 | 97 | //清理任务队列 98 | for (std::list::iterator it = _http_work_list.begin(); 99 | it != _http_work_list.end(); it++) 100 | { 101 | delete *it; 102 | } 103 | _http_work_list.clear(); 104 | 105 | //清理句柄 106 | CloseHandle(_exit_event); 107 | CloseHandle(_work_semaphore); 108 | delete _work_lock; 109 | } 110 | 111 | void HttpPool::postRequest(HttpRequest* http_request,IAsyncHttpDelegate* delegate) 112 | { 113 | const Util::ScopedLock scoped_lock(*_work_lock); 114 | PTR_VOID(http_request); 115 | PTR_VOID(delegate); 116 | HttpWorkItem* item = new HttpWorkItem(http_request,delegate); 117 | _http_work_list.push_back(item); 118 | ReleaseSemaphore(_work_semaphore,1,0); 119 | } 120 | 121 | 122 | HttpWorkItem* HttpPool::getWork() 123 | { 124 | const Util::ScopedLock scoped_lock(*_work_lock); 125 | if (_http_work_list.size() == 0) 126 | { 127 | return 0; 128 | } 129 | else 130 | { 131 | HttpWorkItem* item = *(_http_work_list.begin()); 132 | _http_work_list.pop_front(); 133 | return item; 134 | } 135 | } 136 | 137 | std::string HttpPool::genThreadName(int index) 138 | { 139 | char buffer[256] = {0}; 140 | sprintf_s(buffer,256,"HttpThread%d",index); 141 | return std::string(buffer); 142 | } 143 | 144 | 145 | NAMESPACE_END(HttpPool) 146 | -------------------------------------------------------------------------------- /httpclient/http_post_file.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_post_file.cpp 3 | * @brief 用于HTTP POST的文件接口定义 4 | * @author xiangwangfeng 5 | * @data 2011-4-26 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "http_post_file.h" 10 | #include "util.h" 11 | #include "file_reader.h" 12 | #include "proxy_socket.h" 13 | #include "http_delegate.h" 14 | 15 | NAMESPACE_BEGIN(Http) 16 | 17 | 18 | 19 | HttpFileStream::HttpFileStream(const std::wstring &filepath,const std::string& content_type) 20 | :_filepath(filepath) 21 | { 22 | _filename = Util::toUTF8(filepath); 23 | _content_type = content_type; 24 | _file_size = Util::getFileSize(filepath); 25 | } 26 | 27 | 28 | 29 | bool HttpFileStream::postFile(FilePoster& file_poster) 30 | { 31 | ProxySocket* proxy_socket = file_poster._proxy_socket; 32 | HTTPERROR& http_error = file_poster._http_error; 33 | IProgressDelegate* delegate = file_poster._delegate; 34 | if (proxy_socket) 35 | { 36 | bool complete = false; 37 | Util::FileReader file_reader(_filepath); 38 | if (file_reader.open()) 39 | { 40 | int file_size = getFileSize(); //不支持大文件 41 | if (file_size > 0) 42 | { 43 | int unsend_data = file_size; 44 | char buff[kmax_file_buffer_size]; 45 | while(unsend_data > 0) 46 | { 47 | int read_count = file_reader.read(buff,kmax_file_buffer_size); 48 | if (read_count > 0) 49 | { 50 | bool send = proxy_socket->writeAll(buff,read_count); 51 | if (!send) 52 | { 53 | http_error = HTTPERROR_TRANSPORT; 54 | break; 55 | } 56 | else 57 | { 58 | unsend_data -= read_count; 59 | if (delegate) 60 | { 61 | delegate->dataWriteProgress(file_size - unsend_data,file_size); 62 | } 63 | } 64 | } 65 | else 66 | { 67 | http_error = HTTPERROR_IO; 68 | break; 69 | } 70 | } 71 | complete = unsend_data == 0; 72 | } 73 | } 74 | return complete; 75 | } 76 | else 77 | { 78 | assert(false); 79 | return false; 80 | } 81 | } 82 | 83 | 84 | 85 | HttpMemoryStream::HttpMemoryStream(const char* data,size_t length,const std::string& filename, 86 | const std::string& content_type) 87 | :_data(data,length) 88 | { 89 | _filename = filename; 90 | _content_type = content_type; 91 | _file_size = (int)_data.length(); 92 | } 93 | 94 | bool HttpMemoryStream::postFile(FilePoster &file_poster) 95 | { 96 | ProxySocket* proxy_socket = file_poster._proxy_socket; 97 | HTTPERROR& http_error = file_poster._http_error; 98 | IProgressDelegate* delegate = file_poster._delegate; 99 | 100 | if (proxy_socket) 101 | { 102 | int total_data_length = (int)_data.size(); 103 | int unsend_data_length = total_data_length; 104 | const char* data = _data.c_str(); 105 | while (unsend_data_length) 106 | { 107 | int send_data_length = (unsend_data_length >= kmax_file_buffer_size ? 108 | kmax_file_buffer_size :unsend_data_length); 109 | int ret = proxy_socket->write(data,send_data_length); 110 | if (ret <= 0) 111 | { 112 | http_error = HTTPERROR_TRANSPORT; 113 | break; 114 | } 115 | else 116 | { 117 | unsend_data_length -= ret; 118 | data += ret; 119 | 120 | if (delegate) 121 | { 122 | delegate->dataWriteProgress(total_data_length - unsend_data_length,total_data_length); 123 | } 124 | } 125 | } 126 | return unsend_data_length == 0; 127 | } 128 | else 129 | { 130 | assert(false); 131 | return false; 132 | } 133 | } 134 | 135 | 136 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /test/test.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 26 | 29 | 32 | 35 | 38 | 41 | 52 | 55 | 58 | 61 | 69 | 72 | 75 | 78 | 81 | 84 | 87 | 90 | 91 | 99 | 102 | 105 | 108 | 111 | 114 | 125 | 128 | 131 | 134 | 143 | 146 | 149 | 152 | 155 | 158 | 161 | 164 | 165 | 166 | 167 | 168 | 169 | 174 | 177 | 178 | 179 | 184 | 185 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /httpclient/socket_helper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file socket_helper.cpp 3 | * @brief Socket相关的辅助函数 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "socket_helper.h" 10 | 11 | 12 | #pragma comment(lib,"Wsock32.lib") 13 | 14 | NAMESPACE_BEGIN(SocketHelper) 15 | 16 | void initializeWinSocket() 17 | { 18 | static bool initialization = false; 19 | if (!initialization) 20 | { 21 | WSADATA wsd; 22 | initialization = (WSAStartup(MAKEWORD(1, 1), &wsd) != 0); 23 | } 24 | } 25 | 26 | bool connectSocket(int volatile& handle,const std::string& remote_host_name, 27 | int remote_port_number,int timeout_millisecs) 28 | { 29 | struct hostent* const host_ent = gethostbyname (remote_host_name.c_str()); 30 | 31 | if (host_ent == 0) 32 | return false; 33 | 34 | struct in_addr target_address; 35 | memcpy (&target_address.s_addr, 36 | *(host_ent->h_addr_list), 37 | sizeof (target_address.s_addr)); 38 | 39 | struct sockaddr_in server_temp_address; 40 | memset(&server_temp_address,0,sizeof(sockaddr_in)); 41 | server_temp_address.sin_family = PF_INET; 42 | server_temp_address.sin_addr = target_address; 43 | server_temp_address.sin_port = htons ((u_short) remote_port_number); 44 | 45 | if (handle < 0) 46 | handle = (int) socket (AF_INET, SOCK_STREAM, 0); 47 | 48 | if (handle < 0) 49 | return false; 50 | 51 | setSocketBlockingState (handle, false); 52 | 53 | const int result = ::connect (handle, (struct sockaddr*) &server_temp_address, sizeof (struct sockaddr_in)); 54 | 55 | if (result < 0) 56 | { 57 | 58 | if (result == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) 59 | { 60 | if (waitForReadiness (handle, false, timeout_millisecs) != 1) 61 | { 62 | setSocketBlockingState (handle, true); 63 | return false; 64 | } 65 | } 66 | } 67 | 68 | setSocketBlockingState (handle, true); 69 | resetSocketOptions (handle); 70 | 71 | return true; 72 | } 73 | 74 | int readSocket(int handle,bool connected,char* dest_buffer,int max_read_length,bool block_util_all_arrived) 75 | { 76 | int bytes_read = 0; 77 | 78 | while (bytes_read < max_read_length) 79 | { 80 | int bytes_this_time; 81 | 82 | bytes_this_time = recv (handle, dest_buffer + bytes_read, max_read_length - bytes_read, 0); 83 | 84 | if (bytes_this_time <= 0 || ! connected) 85 | { 86 | if (bytes_read == 0) 87 | bytes_read = -1; 88 | 89 | break; 90 | } 91 | 92 | bytes_read += bytes_this_time; 93 | 94 | if (!block_util_all_arrived) 95 | break; 96 | 97 | } 98 | 99 | return bytes_read; 100 | } 101 | 102 | int writeSocket(int handle,const char* source_buffer,int max_write_length) 103 | { 104 | return send(handle,source_buffer,max_write_length,0); 105 | } 106 | 107 | bool resetSocketOptions(int handle) 108 | { 109 | const int sndBufSize = 65536; 110 | const int rcvBufSize = 65536; 111 | const int one = 1; 112 | 113 | return handle > 0 114 | && setsockopt (handle, SOL_SOCKET, SO_RCVBUF, (const char*) &rcvBufSize, sizeof (rcvBufSize)) == 0 115 | && setsockopt (handle, SOL_SOCKET, SO_SNDBUF, (const char*) &sndBufSize, sizeof (sndBufSize)) == 0; 116 | } 117 | 118 | bool setSocketBlockingState(int handle,bool block) 119 | { 120 | u_long nonBlocking = block ? 0 : 1; 121 | 122 | return (ioctlsocket (handle, FIONBIO, &nonBlocking) == 0); 123 | } 124 | 125 | int waitForReadiness ( int handle, bool for_ready,int timeout_msecs) 126 | { 127 | struct timeval timeout; 128 | struct timeval* timeoutp; 129 | 130 | if (timeout_msecs >= 0) 131 | { 132 | timeout.tv_sec = timeout_msecs / 1000; 133 | timeout.tv_usec = (timeout_msecs % 1000) * 1000; 134 | timeoutp = &timeout; 135 | } 136 | else 137 | { 138 | timeoutp = 0; 139 | } 140 | 141 | fd_set rset, wset; 142 | FD_ZERO (&rset); 143 | FD_SET (handle, &rset); 144 | FD_ZERO (&wset); 145 | FD_SET (handle, &wset); 146 | 147 | fd_set* const prset = for_ready ? &rset : 0; 148 | fd_set* const pwset = for_ready ? 0 : &wset; 149 | 150 | if (select (handle + 1, prset, pwset, 0, timeoutp) < 0) 151 | return -1; 152 | 153 | int opt; 154 | int len = sizeof (opt); 155 | 156 | if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0 157 | || opt != 0) 158 | return -1; 159 | 160 | 161 | if ((for_ready && FD_ISSET (handle, &rset)) 162 | || ((! for_ready) && FD_ISSET (handle, &wset))) 163 | return 1; 164 | 165 | return 0; 166 | } 167 | 168 | unsigned long getIntAddress(const char* ip_address) 169 | { 170 | unsigned long ret = INADDR_NONE; 171 | if (ip_address) 172 | { 173 | ret = ::inet_addr(ip_address); 174 | if (ret == INADDR_NONE) //是域名 175 | { 176 | struct hostent* host = gethostbyname(ip_address); 177 | if (host == 0) 178 | { 179 | return INADDR_NONE; 180 | } 181 | CopyMemory(&ret, host->h_addr_list[0], host->h_length); 182 | } 183 | } 184 | return ret; 185 | } 186 | NAMESPACE_END(SocketHelper) -------------------------------------------------------------------------------- /httpclient/http_request.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_request.cpp 3 | * @brief Http请求 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "http_request.h" 10 | #include 11 | #include "http_global.h" 12 | #include "util.h" 13 | #include "url_parser.h" 14 | #include "http_post_file.h" 15 | 16 | 17 | NAMESPACE_BEGIN(Http) 18 | 19 | 20 | HttpFile::~HttpFile() 21 | { 22 | delete _post_file; 23 | } 24 | 25 | 26 | HttpRequest::HttpRequest() 27 | :_is_multipart(false), 28 | _port_number(80), 29 | _only_download_header(false), 30 | _save_as_file(false) 31 | { 32 | initDefaultValue(); 33 | } 34 | 35 | HttpRequest::HttpRequest(const std::string& method,const std::string& url) 36 | :_is_multipart(false), 37 | _port_number(80), 38 | _only_download_header(false), 39 | _save_as_file(false) 40 | { 41 | initDefaultValue(); 42 | setRequest(method,url); 43 | } 44 | 45 | HttpRequest::~HttpRequest() 46 | { 47 | 48 | } 49 | 50 | void HttpRequest::setRequest(const std::string& method,const std::string& url) 51 | { 52 | if (_strcmpi(method.c_str(),kget) == 0) 53 | { 54 | _http_method = kget; 55 | } 56 | else if (_strcmpi(method.c_str(),kpost) == 0) 57 | { 58 | _http_method = kpost; 59 | } 60 | else 61 | { 62 | assert(false); 63 | return; 64 | } 65 | URLParser url_parser(url); 66 | _http_host = url_parser.getDomain(); 67 | _object = url_parser.getObject(); 68 | _port_number= url_parser.getPort(); 69 | } 70 | 71 | void HttpRequest::addHeaderField(const std::string &name, const std::string &value) 72 | { 73 | for (size_t i = 0; i < _header_fields.size(); i++) 74 | { 75 | std::string& header_name = _header_fields[i]._field_name; 76 | if (_strcmpi(header_name.c_str(),name.c_str()) == 0) 77 | { 78 | //重名字段直接用新值代替旧值 79 | _header_fields[i]._field_value = value; 80 | return; 81 | } 82 | } 83 | _header_fields.push_back(HttpField(name,value)); 84 | } 85 | 86 | 87 | 88 | void HttpRequest::addFile(const std::string& name,IHttpPostFile* post_file) 89 | { 90 | PTR_VOID(post_file); 91 | for (size_t i = 0; i < _post_files.size(); i ++) 92 | { 93 | const std::string& add_name = _post_files[i]->_name; 94 | if (_strcmpi(name.c_str(),add_name.c_str()) == 0) 95 | { 96 | //不允许重名 97 | assert(false); 98 | return; 99 | } 100 | } 101 | HttpFile* http_file = new HttpFile(name,post_file); 102 | _post_files.push_back(http_file); 103 | _is_multipart = true; 104 | } 105 | 106 | 107 | void HttpRequest::initDefaultValue() 108 | { 109 | _http_method = kget; 110 | 111 | //添加一些默认Http头字段 112 | _header_fields.push_back(HttpField(kaccept,"*/*")); 113 | _header_fields.push_back(HttpField(kconnection,"Keep-Alive")); 114 | } 115 | 116 | 117 | int HttpRequest::generateHeader(std::string& header) 118 | { 119 | header.clear(); 120 | 121 | 122 | //请求方法和实体 123 | header.append(_http_method+" "); 124 | header.append(_object); 125 | header.append(" HTTP/1.1\r\n"); 126 | 127 | //主机 128 | header.append("Host:"); 129 | header.append(_http_host); 130 | header.append("\r\n"); 131 | 132 | 133 | //计算Body的长度,赋值给ContentLength 134 | if (_stricmp(_http_method.c_str(),kpost) == 0) 135 | { 136 | calcBody(); 137 | } 138 | 139 | //添加Header Field 140 | size_t field_size = _header_fields.size(); 141 | for (size_t i = 0; i < field_size; i ++) 142 | { 143 | std::string& name = _header_fields[i]._field_name; 144 | std::string& value = _header_fields[i]._field_value; 145 | header.append(name + ": " + value + "\r\n"); 146 | } 147 | 148 | //添加最后一个\r\n 149 | header.append("\r\n"); 150 | 151 | ///返回结果 152 | return (int)header.size(); 153 | } 154 | 155 | void HttpRequest::calcBody() 156 | { 157 | if (_is_multipart) 158 | { 159 | genBoundary(); 160 | 161 | int length = 0; //计算Content-Length 162 | _body.clear(); 163 | 164 | 165 | //添加Fields 166 | for (size_t i = 0; i < _body_fields.size(); i++) 167 | { 168 | const std::string& name = _body_fields[i]._field_name; 169 | const std::string& value = _body_fields[i]._field_value; 170 | std::string field = "--" + _boundary + "\r\n" + 171 | "content-disposition: form-data; name=\"" + name + "\"\r\n\r\n" + 172 | value + "\r\n"; 173 | _body += field; 174 | } 175 | length = (int)_body.size(); 176 | 177 | //添加Files 178 | for (size_t i = 0; i < _post_files.size(); i++) 179 | { 180 | std::string name = _post_files[i]->_name; 181 | IHttpPostFile* post_file = _post_files[i]->_post_file; 182 | std::string post_part = "--" + _boundary + "\r\n" + 183 | "content-disposition: form-data; name=\"" + name + 184 | "\"; filename=\"" + post_file->getFilename() + "\"\r\n" + 185 | "content-type: " + post_file->getContentType() + "\r\n" + 186 | "\r\n"; 187 | length += (int)post_part.size(); 188 | length += post_file->getFileSize(); 189 | length += 2; //在文件后需要添加\r\n 190 | } 191 | 192 | std::string post_end = "--" + _boundary + "--\r\n"; 193 | length += (int)post_end.size(); 194 | 195 | //添加Content-Type和Content-Length字段 196 | std::string content_type = "multipart/form-data; boundary=" + _boundary; 197 | addHeaderField(kcontent_type,content_type); 198 | std::string content_length = Util::num_to_string(length); 199 | addHeaderField(kcontent_length,content_length); 200 | } 201 | else 202 | { 203 | //如果没有直接设置Http体,则以Field的形式构造HTTP体 204 | if (_body.empty()) 205 | { 206 | for (size_t i = 0 ; i < _body_fields.size(); i++) 207 | { 208 | if (!_body.empty()) 209 | { 210 | _body += "&"; 211 | } 212 | const std::string& name = _body_fields[i]._field_name; 213 | const std::string& value = _body_fields[i]._field_value; 214 | _body += (name + "=" + Util::torfc1738(value)); 215 | } 216 | addExtraHeaderField(kcontent_type,"application/x-www-form-urlencoded"); 217 | } 218 | else 219 | { 220 | addExtraHeaderField(kcontent_type,"text/plain"); 221 | } 222 | std::string content_length = Util::num_to_string((int)_body.length()); 223 | addHeaderField(kcontent_length,content_length); 224 | } 225 | 226 | } 227 | 228 | void HttpRequest::genBoundary() 229 | { 230 | _boundary = "----"; 231 | 232 | std::string random_number; 233 | unsigned char c; 234 | srand((unsigned int)time(0)); 235 | for (int i = 1; i < 15; i++) 236 | { 237 | c = int(rand()*127); 238 | if (isalpha(c) || isdigit(c)) 239 | { 240 | random_number += std::string(1,(char)c); 241 | } 242 | else 243 | { 244 | i--; 245 | } 246 | } 247 | _boundary += "__" + random_number; 248 | } 249 | 250 | void HttpRequest::addExtraHeaderField(const std::string& name,const std::string& value) 251 | { 252 | 253 | for (size_t i = 0; i < _header_fields.size(); i++) 254 | { 255 | std::string& header_name = _header_fields[i]._field_name; 256 | if (_strcmpi(header_name.c_str(),name.c_str()) == 0) 257 | { 258 | //如果已经有同名的header 直接返回 不添加header field 259 | return; 260 | } 261 | } 262 | _header_fields.push_back(HttpField(name,value)); 263 | } 264 | 265 | 266 | NAMESPACE_END(Http) -------------------------------------------------------------------------------- /httpclient/httpclient.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 26 | 29 | 32 | 35 | 38 | 41 | 52 | 55 | 58 | 61 | 69 | 72 | 75 | 78 | 81 | 84 | 87 | 90 | 91 | 99 | 102 | 105 | 108 | 111 | 114 | 125 | 128 | 131 | 134 | 143 | 146 | 149 | 152 | 155 | 158 | 161 | 164 | 165 | 166 | 167 | 168 | 169 | 174 | 177 | 178 | 181 | 184 | 185 | 188 | 189 | 192 | 193 | 194 | 197 | 200 | 201 | 204 | 205 | 208 | 209 | 212 | 213 | 216 | 217 | 220 | 221 | 224 | 225 | 226 | 229 | 232 | 233 | 236 | 237 | 240 | 241 | 244 | 245 | 248 | 249 | 250 | 253 | 256 | 257 | 260 | 261 | 262 | 263 | 268 | 271 | 272 | 275 | 276 | 279 | 280 | 283 | 286 | 287 | 290 | 291 | 294 | 295 | 298 | 299 | 300 | 303 | 306 | 307 | 310 | 311 | 314 | 315 | 318 | 319 | 322 | 323 | 326 | 327 | 330 | 331 | 334 | 335 | 338 | 339 | 340 | 343 | 346 | 347 | 350 | 351 | 354 | 355 | 358 | 359 | 362 | 363 | 366 | 367 | 368 | 371 | 374 | 375 | 378 | 379 | 380 | 381 | 386 | 387 | 388 | 389 | 390 | 391 | -------------------------------------------------------------------------------- /httpclient/proxy_socket .cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file proxy_socket.cpp 3 | * @brief 支持代理的Socket类 4 | * @author xiangwangfeng 5 | * @data 2011-4-23 6 | * @website www.xiangwangfeng.com 7 | */ 8 | 9 | #include "standard_header.h" 10 | #include "proxy_socket.h" 11 | #include "base64.h" 12 | #include "util.h" 13 | #include "socket_helper.h" 14 | 15 | #pragma pack(1) //1字节内存对齐模式(代理传输都是以结构体直接转换成字节流,所以必须1字节对齐) 16 | 17 | NAMESPACE_BEGIN(Http) 18 | 19 | ProxyConfig ProxySocket::_proxy_config; 20 | 21 | ProxySocket::ProxySocket() 22 | :_port_number(0) 23 | { 24 | 25 | } 26 | 27 | ProxySocket::ProxySocket(const std::string &remote_host_name, 28 | int port_number) 29 | :_host_name(remote_host_name),_port_number(port_number) 30 | { 31 | 32 | } 33 | 34 | ProxySocket::~ProxySocket() 35 | { 36 | close(); 37 | } 38 | 39 | 40 | void ProxySocket::setHost(const std::string &remote_host_name, int port_number) 41 | { 42 | _host_name = remote_host_name; 43 | _port_number = port_number; 44 | } 45 | 46 | void ProxySocket::setProxy(const ProxyConfig &proxy_config) 47 | { 48 | _proxy_config.copy(proxy_config); 49 | } 50 | 51 | 52 | bool ProxySocket::connect() 53 | { 54 | ProxyType proxy_type = _proxy_config._proxy_type; 55 | 56 | if (proxy_type == PROXY_NULL) 57 | { 58 | return _socket.connect(_host_name,_port_number); 59 | } 60 | else 61 | { 62 | std::string host_name = _proxy_config._host_name; 63 | int port_number = _proxy_config._port_number; 64 | bool connect_proxy = _socket.connect(host_name,port_number); 65 | 66 | if (!connect_proxy) 67 | { 68 | return false; 69 | } 70 | else 71 | { 72 | bool connect_host = false; 73 | switch(proxy_type) 74 | { 75 | case PROXY_HTTP: 76 | connect_host = handShakeWithHttpProxy(); 77 | break; 78 | case PROXY_SOCK4: 79 | connect_host = handShakeWithSock4Proxy(); 80 | break; 81 | case PROXY_SOCK5: 82 | connect_host = handShakeWithSock5Proxy(); 83 | break; 84 | default: 85 | assert(false); 86 | break; 87 | } 88 | return connect_host; 89 | } 90 | } 91 | } 92 | 93 | 94 | void ProxySocket::close() 95 | { 96 | _socket.close(); 97 | } 98 | 99 | int ProxySocket::read(char *dst_buffer, int max_read_length) 100 | { 101 | return _socket.read(dst_buffer,max_read_length); 102 | } 103 | 104 | int ProxySocket::write(const char *source_buffer, int max_write_length) 105 | { 106 | return _socket.write(source_buffer,max_write_length); 107 | } 108 | 109 | bool ProxySocket::writeAll(const char *source_buffer, int max_write_length) 110 | { 111 | return _socket.writeAll(source_buffer,max_write_length); 112 | } 113 | 114 | 115 | bool ProxySocket::handShakeWithHttpProxy() 116 | { 117 | char buff[kmax_file_buffer_size] = {0}; 118 | 119 | if (!_proxy_config._username.empty()) 120 | { 121 | std::string auth = _proxy_config._username + ":" + _proxy_config._password; 122 | std::string base64_encode_auth; 123 | Util::base64Encode(auth,base64_encode_auth); 124 | sprintf_s(buff,kmax_file_buffer_size,"CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\nAuthorization: Basic %s\r\nProxy-Authorization: Basic %s\r\n\r\n", 125 | _host_name.c_str(),_port_number,_host_name.c_str(),_port_number,base64_encode_auth.c_str(),base64_encode_auth.c_str()); 126 | } 127 | else 128 | { 129 | sprintf_s(buff,kmax_file_buffer_size,"CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n\r\n", 130 | _host_name.c_str(),_port_number,_host_name.c_str(),_port_number); 131 | } 132 | 133 | //发送HTTP代理连接请求 134 | bool send_connect_request = _socket.writeAll(buff,strlen(buff)); 135 | if (!send_connect_request) 136 | { 137 | return false; 138 | } 139 | //获得HTTP代理回复 140 | int ret = _socket.read(buff,sizeof(buff)); 141 | if (ret <= 0) 142 | { 143 | return false; 144 | } 145 | buff[ret] = '\0'; 146 | 147 | Util::makeLower(buff,strlen(buff)); 148 | return strstr(buff, "200 connection established") != 0; 149 | } 150 | 151 | bool ProxySocket::handShakeWithSock4Proxy() 152 | { 153 | //Socks4没有用户密码验证 154 | struct Sock4Reqeust 155 | { 156 | char VN; 157 | char CD; 158 | unsigned short port; 159 | unsigned long ip_address; 160 | char other[256]; // 变长 161 | } sock4_request; 162 | 163 | struct Sock4Reply 164 | { 165 | char VN; 166 | char CD; 167 | unsigned short port; 168 | unsigned long ip_address; 169 | } sock4_reply; 170 | 171 | sock4_request.VN = 0x04; // VN是SOCK版本,应该是4; 172 | sock4_request.CD = 0x01; // CD是SOCK的命令码,1表示CONNECT请求,2表示BIND请求; 173 | sock4_request.port= ntohs(_port_number); 174 | sock4_request.ip_address = SocketHelper::getIntAddress(_host_name.c_str()); 175 | sock4_request.other[0] = '\0'; 176 | 177 | if (sock4_request.ip_address == INADDR_NONE) 178 | return false; 179 | 180 | //发送SOCKS4连接请求 181 | bool send_sock4_requst = _socket.writeAll((char*)&sock4_request,9); 182 | if (!send_sock4_requst) 183 | { 184 | return false; 185 | } 186 | //获得Socks4代理的回复 187 | int ret = _socket.read((char *)&sock4_reply, sizeof(sock4_reply)); 188 | if (ret <= 0) 189 | { 190 | return false; 191 | } 192 | /* 193 | CD是代理服务器答复,有几种可能: 194 | 90,请求得到允许; 195 | 91,请求被拒绝或失败; 196 | 92,由于SOCKS服务器无法连接到客户端的identd(一个验证身份的进程),请求被拒绝; 197 | 93,由于客户端程序与identd报告的用户身份不同,连接被拒绝。 198 | */ 199 | return sock4_reply.CD == 90; 200 | } 201 | 202 | bool ProxySocket::handShakeWithSock5Proxy() 203 | { 204 | char buff[kmax_file_buffer_size] = {0}; 205 | 206 | //第一次请求:验证用户 207 | struct FirstSock5Request 208 | { 209 | char version; 210 | char method; 211 | char methods[255]; 212 | } first_sock5_request; 213 | first_sock5_request.version = 0x05; // socks5 214 | first_sock5_request.method = 0x02; // 验证方式的总数 215 | first_sock5_request.methods[0] = 0x00; // NO AUTHENTICATION REQUIRED 216 | first_sock5_request.methods[1] = 0x02; // USERNAME/PASSWORD 217 | 218 | bool send_first_sock4_requst = _socket.writeAll((char*)&first_sock5_request,4); 219 | if (!send_first_sock4_requst) 220 | { 221 | return false; 222 | } 223 | 224 | // 第一次应答 225 | struct FirstSock5Answer 226 | { 227 | char version; 228 | char method; 229 | } first_sock5_answer; 230 | int ret = _socket.read((char*)&first_sock5_answer, sizeof(first_sock5_answer)); 231 | if (ret <= 0 || 232 | first_sock5_answer.version != 5) 233 | { 234 | return false; 235 | } 236 | 237 | // 需要用户名和密码验证 238 | if (first_sock5_answer.method == 0x02) 239 | { 240 | struct AuthRequest 241 | { 242 | char version; 243 | char name_length; 244 | char name[255]; // 变长 245 | char password_length; 246 | char password[255]; // 变长 247 | }; 248 | AuthRequest * auth_request = (AuthRequest *)buff; 249 | int auth_length = 0; 250 | auth_request->version = 0x01; 251 | auth_length ++; 252 | auth_request->name_length = _proxy_config._username.length(); 253 | auth_length ++; 254 | strcpy_s(auth_request->name,255,_proxy_config._username.c_str()); 255 | auth_length += _proxy_config._username.length(); 256 | char password_length = (char)_proxy_config._password.length(); 257 | memcpy((char*)auth_request+auth_length, &password_length, 1); 258 | auth_length ++; 259 | strcpy_s((char*)auth_request+auth_length,255, _proxy_config._password.c_str()); 260 | auth_length += _proxy_config._password.length(); 261 | 262 | bool send_auth_data = _socket.writeAll(buff, auth_length); 263 | if (!send_auth_data) 264 | { 265 | return false; 266 | } 267 | 268 | struct AuthAnswer 269 | { 270 | char Ver; 271 | char Status; 272 | } auth_answer; 273 | 274 | ret = _socket.read((char*)&auth_answer, sizeof(auth_answer)); 275 | if (ret <= 0 || auth_answer.Status != 0x00) 276 | { 277 | return false; 278 | } 279 | } 280 | 281 | // 第二次请求:连接目标地址 282 | struct SecondSock5Request 283 | { 284 | char version; 285 | char command; 286 | char reserved; 287 | char address_type; 288 | unsigned long dest_address; 289 | unsigned short dest_port; 290 | } second_sock5_request; 291 | second_sock5_request.version = 0x05; // socks版本 292 | second_sock5_request.command = 0x01; // CONNECT X'01', BIND X'02', UDP ASSOCIATE X'03' 293 | second_sock5_request.reserved = 0x00; // RESERVED 294 | second_sock5_request.address_type = 0x01; // IP V4 address: X'01', DOMAINNAME: X'03', IP V6 address: X'04' 295 | second_sock5_request.dest_address = SocketHelper::getIntAddress(_host_name.c_str()); 296 | second_sock5_request.dest_port = ntohs(_port_number); 297 | 298 | if (second_sock5_request.dest_address == INADDR_NONE) 299 | return false; 300 | 301 | bool send_second_sock5_request = _socket.writeAll((char*)&second_sock5_request, 10); 302 | if (!send_second_sock5_request) 303 | { 304 | return false; 305 | } 306 | 307 | // 第二次应答 308 | struct SecondSock5Answer 309 | { 310 | char version; 311 | char reply; 312 | char reserved; 313 | char address_type; 314 | char other[256]; 315 | } second_sock5_answer; 316 | memset(&second_sock5_answer, 0, sizeof(second_sock5_answer)); 317 | ret = _socket.read((char*)&second_sock5_answer, sizeof(second_sock5_answer)); 318 | if (ret <= 0) 319 | { 320 | return false; 321 | } 322 | 323 | return second_sock5_answer.reply == 0x00; // X'00' succeeded 324 | } 325 | 326 | 327 | NAMESPACE_END(Http) 328 | -------------------------------------------------------------------------------- /httpclient/http_client.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_client.cpp 3 | * @brief Http传输类 4 | * @author xiangwangfeng 5 | * @data 2011-4-24 6 | * @website www.xiangwangfeng.com 7 | */ 8 | #include "standard_header.h" 9 | #include "http_client.h" 10 | #include "util.h" 11 | #include "lock.h" 12 | #include "proxy_socket.h" 13 | #include "http_request.h" 14 | #include "http_response.h" 15 | #include "http_delegate.h" 16 | #include "file_writer.h" 17 | #include "http_header_parser.h" 18 | #include "http_response_receiver.h" 19 | #include "http_post_file.h" 20 | 21 | NAMESPACE_BEGIN(Http) 22 | 23 | HttpClient::HttpClient(bool keep_connection) 24 | :_http_error(HTTPERROR_PARAMETER), 25 | _keep_connection(keep_connection), 26 | _request(0), 27 | _response(0), 28 | _delegate(0), 29 | _is_valid(true) 30 | { 31 | _proxy_socket = new ProxySocket(); 32 | _is_valid_lock = new Util::Lock(); 33 | } 34 | 35 | HttpClient::~HttpClient() 36 | { 37 | delete _proxy_socket; 38 | delete _is_valid_lock; 39 | } 40 | 41 | bool HttpClient::execute(HttpRequest* request,HttpResponse* respone) 42 | { 43 | //参数检查 44 | PTR_BOOL(request); 45 | PTR_BOOL(respone); 46 | _request = request; 47 | _response = respone; 48 | setErrorCode(HTTPERROR_SUCCESS); 49 | 50 | //连接服务器 51 | const std::string& host = _request->getHost(); 52 | int port_number = _request->getPortNumber(); 53 | _proxy_socket->setHost(host,port_number); 54 | 55 | //尝试连接 56 | { 57 | const Util::ScopedLock scoped_lock(*_is_valid_lock); 58 | if (_is_valid) //判断HttpClient是否还有效 59 | { 60 | if (!(_keep_connection && _proxy_socket->isConnected())) 61 | { 62 | if (!_proxy_socket->connect()) 63 | { 64 | setErrorCode(HTTPERROR_CONNECT); 65 | return false; 66 | } 67 | } 68 | } 69 | else 70 | { 71 | setErrorCode(HTTPERROR_INVALID); 72 | return false; 73 | } 74 | } 75 | 76 | //进行数据传输 77 | bool execute = false; 78 | const std::string& method = _request->getMethod(); 79 | 80 | //Get方法 81 | if (_strcmpi(method.c_str(),kget) == 0) 82 | { 83 | execute = httpGet(); 84 | } 85 | //Post方法 86 | else if (_strcmpi(method.c_str(),kpost) == 0) 87 | { 88 | execute = httpPost(); 89 | } 90 | //其他,抛出错误 91 | else 92 | { 93 | assert(false); 94 | } 95 | 96 | //如果不是长连接 就关闭网络 97 | if (!_keep_connection) 98 | { 99 | _proxy_socket->close(); 100 | } 101 | 102 | return execute; 103 | } 104 | 105 | void HttpClient::setProxy(const Http::ProxyConfig *proxy_config) 106 | { 107 | PTR_VOID(proxy_config); 108 | ProxySocket::setProxy(*proxy_config); 109 | } 110 | 111 | void HttpClient::setErrorCode(HTTPERROR http_error) 112 | { 113 | _http_error = http_error; 114 | //如果传输失败后,关闭当前Socket 115 | //(为了支持keep-alive模式,在服务器出错后能够正常进行重连) 116 | if (_http_error != HTTPERROR_SUCCESS) 117 | { 118 | _proxy_socket->close(); 119 | } 120 | } 121 | 122 | 123 | void HttpClient::killSelf() 124 | { 125 | const Util::ScopedLock scoped_lock(*_is_valid_lock); 126 | _is_valid = false; 127 | _proxy_socket->close(); 128 | } 129 | 130 | void HttpClient::reset() 131 | { 132 | const Util::ScopedLock scoped_lock(*_is_valid_lock); 133 | _proxy_socket->close(); 134 | } 135 | 136 | 137 | bool HttpClient::httpGet() 138 | { 139 | //发送HTTP头请求 140 | if (sendHeader()) 141 | { 142 | //接受反馈 143 | return getResponse(); 144 | } 145 | else 146 | { 147 | setErrorCode(HTTPERROR_TRANSPORT); 148 | return false; 149 | } 150 | } 151 | 152 | bool HttpClient::httpPost() 153 | { 154 | //发送HTTP头请求 155 | bool complete = false; 156 | if (sendHeader()) 157 | { 158 | if (_request->isMultipart()) 159 | { 160 | complete = doMultipartPost(); 161 | } 162 | else 163 | { 164 | if (sendBody()) 165 | { 166 | complete = getResponse(); 167 | } 168 | } 169 | } 170 | return complete; 171 | } 172 | 173 | bool HttpClient::sendHeader() 174 | { 175 | std::string header; 176 | int header_length = _request->generateHeader(header); 177 | bool send = _proxy_socket->writeAll(header.c_str(),header_length); 178 | if (!send) 179 | { 180 | setErrorCode(HTTPERROR_TRANSPORT); 181 | } 182 | return send; 183 | } 184 | 185 | bool HttpClient::sendBody() 186 | { 187 | const std::string& body = _request->getBody(); 188 | size_t length = body.length(); 189 | if (length == 0) 190 | { 191 | return true; //如果body内没有内容 直接返回true 192 | //(上传文件的时候body内容是动态生成,body内容可能为空) 193 | } 194 | bool send = _proxy_socket->writeAll(body.c_str(),body.length()); 195 | if (!send) 196 | { 197 | setErrorCode(HTTPERROR_TRANSPORT);; 198 | } 199 | return send; 200 | } 201 | 202 | bool HttpClient::doMultipartPost() 203 | { 204 | //如果有Fields段 已经写入了body内 直接发送 205 | if (!sendBody()) 206 | { 207 | setErrorCode(HTTPERROR_TRANSPORT); 208 | return false; 209 | } 210 | 211 | //发送文件 212 | const std::vector& post_files = _request->getFiles(); 213 | const std::string& boundary = _request->getBoundary(); 214 | for (size_t i = 0; i < post_files.size(); i++) 215 | { 216 | const std::string name = post_files[i]->_name; 217 | IHttpPostFile* post_file = post_files[i]->_post_file; 218 | std::string file_header = "--" + boundary + "\r\n" 219 | "content-disposition: form-data; name=\"" + name + "\"; filename=\"" + 220 | post_file->getFilename() + "\"\r\n" + 221 | "content-type: " + post_file->getContentType() + "\r\n" + 222 | "\r\n"; 223 | 224 | bool send_file_header = _proxy_socket->writeAll(file_header.c_str(),file_header.size()); 225 | if (!send_file_header) 226 | { 227 | setErrorCode(HTTPERROR_TRANSPORT); 228 | return false; 229 | } 230 | 231 | bool post_file_success = uploadFile(post_file); 232 | if (!post_file_success) 233 | { 234 | setErrorCode(HTTPERROR_TRANSPORT); 235 | return false; 236 | } 237 | 238 | 239 | std::string file_tailer = "\r\n"; 240 | bool send_file_tailer = _proxy_socket->writeAll(file_tailer.c_str(),file_tailer.size()); 241 | if (!send_file_tailer) 242 | { 243 | setErrorCode(HTTPERROR_TRANSPORT); 244 | return false; 245 | } 246 | } 247 | 248 | //发送boundary结束标记 249 | std::string post_tailer = "--" + boundary + "--\r\n"; 250 | bool send_post_tailer = _proxy_socket->writeAll(post_tailer.c_str(),post_tailer.size()); 251 | 252 | return send_post_tailer ? getResponse() : setErrorCode(HTTPERROR_TRANSPORT) , false; 253 | 254 | } 255 | 256 | bool HttpClient::uploadFile(IHttpPostFile* post_file) 257 | { 258 | FilePoster file_poster(_proxy_socket,_http_error,_delegate); 259 | return post_file ? post_file->postFile(file_poster) : false; 260 | } 261 | 262 | 263 | bool HttpClient::getResponse() 264 | { 265 | std::string body_header; //收取的Body和Header的混合体 266 | if (downloadHeader(body_header)) 267 | { 268 | //如果请求指定只需要获取Http头直接返回 (主要是为分段下载减少下载量) 269 | if (_request->onlyDownloadHeader()) 270 | { 271 | return true; 272 | } 273 | else 274 | { 275 | return downloadBody(body_header); 276 | } 277 | } 278 | else 279 | { 280 | return false; 281 | } 282 | } 283 | 284 | bool HttpClient::downloadHeader(std::string& body_header) 285 | { 286 | body_header.clear(); 287 | char buff[kmax_buffer_size] = {0}; 288 | std::string header; 289 | bool complete = false; 290 | while(!complete) 291 | { 292 | int ret = _proxy_socket->read(buff,kmax_buffer_size); 293 | if (ret <= 0) 294 | { 295 | setErrorCode(HTTPERROR_TRANSPORT); 296 | break; 297 | } 298 | header.append(buff,ret); //因为Header往往很短,基本一次可以收完 299 | size_t end_index = header.find("\r\n\r\n"); //所以也不需要计算偏移来提高搜索速度 300 | if (end_index != std::string::npos) 301 | { 302 | complete = true; 303 | size_t length = header.length() ; 304 | body_header = header.substr(end_index + 4,length - end_index - 4); 305 | _response->setHeader(header.substr(0,end_index + 4)); 306 | } 307 | } 308 | return complete; 309 | } 310 | 311 | bool HttpClient::downloadBody(const std::string& body_header) 312 | { 313 | const std::string& header = _response->getHeader(); 314 | HttpHeaderParser header_parser(header); 315 | bool is_chunked = header_parser.isChunked(); 316 | int http_code = header_parser.getHttpCode(); 317 | int content_lenght = header_parser.getContentLength(); 318 | bool complete = false; 319 | _response->setHttpCode(http_code); 320 | if (is_chunked) //Chunk类型的Http体 321 | { 322 | complete = downloadChunkedBody(body_header); 323 | } 324 | else //带Content-Length的Http体 325 | { 326 | complete = downloadFixedSizeBody(body_header,content_lenght); 327 | } 328 | 329 | return complete; //下载完毕 330 | } 331 | 332 | 333 | bool HttpClient::downloadFixedSizeBody(const std::string& body_header,int content_length) 334 | { 335 | int received_length = (int)body_header.length(); 336 | if (received_length > content_length || 337 | content_length < 0) 338 | { 339 | setErrorCode(HTTPERROR_TRANSPORT); 340 | assert(false); 341 | return false; 342 | } 343 | 344 | //构造Http反馈接收类 345 | HttpResponseReceiver* response_receiver = 0; 346 | if (_request->saveAsFile()) 347 | { 348 | const std::wstring& filepath = _request->getFilePath(); 349 | _response->setBody(Util::toUTF8(filepath)); //如果是下载到本地IO,则response的Body里面保存的是文件路径 350 | response_receiver = new HttpResponseReceiver(filepath); 351 | } 352 | else 353 | { 354 | response_receiver = new HttpResponseReceiver(); 355 | } 356 | 357 | //接受Http体 358 | bool complete= false; 359 | bool write = response_receiver->write(body_header.c_str(),body_header.length()); 360 | if (write) 361 | { 362 | int unreceived_length = content_length - received_length; 363 | onDataReadProgress(received_length,content_length); 364 | char buff[kmax_file_buffer_size] = {0}; 365 | while (unreceived_length > 0) 366 | { 367 | int ret = _proxy_socket->read(buff,kmax_file_buffer_size); 368 | if (ret <= 0) 369 | { 370 | setErrorCode(HTTPERROR_TRANSPORT); 371 | break; 372 | } 373 | else if (!response_receiver->write(buff,ret)) 374 | { 375 | setErrorCode(HTTPERROR_IO); 376 | break; 377 | } 378 | unreceived_length -= ret; 379 | onDataReadProgress(content_length - unreceived_length,content_length); 380 | } 381 | complete = (unreceived_length == 0); 382 | } 383 | else 384 | { 385 | setErrorCode(HTTPERROR_IO); 386 | } 387 | 388 | //如果是写入内存数据 需要赋回给HttpResponse 389 | if (complete && !_request->saveAsFile()) 390 | { 391 | const std::string& body = response_receiver->getBody(); 392 | _response->setBody(body); 393 | } 394 | 395 | delete response_receiver; 396 | return complete; 397 | } 398 | 399 | bool HttpClient::downloadChunkedBody(const std::string& body_header) 400 | { 401 | std::string body = body_header; //接受HTTP头时拿到的部分HTTP体信息 402 | bool complete = false; //是否已经接受完整了 403 | bool find_first_chunk = false; //是否找到第一个ChunkSize 404 | int chunk_size = 0; //Chunk的数据大小 405 | int chunk_size_length = 0; //ChunkSize的大小 比如 12\r\n 说明;chunk_size为18 chunk_size_length为2 406 | 407 | //构造Http反馈接收类 408 | HttpResponseReceiver* response_receiver = 0; 409 | bool save_as_file = _request->saveAsFile(); 410 | if (save_as_file) 411 | { 412 | const std::wstring& filepath = _request->getFilePath(); 413 | _response->setBody(Util::toUTF8(filepath)); 414 | response_receiver = new HttpResponseReceiver(filepath); 415 | } 416 | else 417 | { 418 | response_receiver = new HttpResponseReceiver(); 419 | } 420 | 421 | //接受并解析chunk内容 422 | while(true) 423 | { 424 | //如果在上次已经查询到第一块chunk的大小 425 | if (find_first_chunk) 426 | { 427 | if (chunk_size == 0)//如果是最后一块了 428 | { 429 | complete = true; 430 | break; 431 | } 432 | else //否则分析chunk内容并进行切割 433 | { 434 | size_t length = body.length(); 435 | size_t first_chunk = chunk_size_length + 2 + chunk_size + 2; 436 | if (length >= first_chunk) //如果已经接受到一整块chunkdata了则进行切割,否则重新接受 437 | { 438 | find_first_chunk = false; 439 | std::string chunk_data = body.substr(chunk_size_length + 2, chunk_size); 440 | body.erase(0,first_chunk); 441 | if (!response_receiver->write(chunk_data.c_str(),chunk_data.length())) 442 | { 443 | setErrorCode(HTTPERROR_IO); 444 | break; 445 | } 446 | } 447 | else 448 | { 449 | if (!continueToReceiveBody(body)) 450 | { 451 | setErrorCode(HTTPERROR_TRANSPORT); 452 | break; 453 | } 454 | } 455 | } 456 | } 457 | else//查找chunk_size 458 | { 459 | size_t index = body.find("\r\n"); 460 | if (index != std::string::npos) //找到,做标记 461 | { 462 | find_first_chunk = true; 463 | chunk_size_length = (int)index; 464 | std::string raw_chunk_size = body.substr(0,chunk_size_length); 465 | chunk_size = (int)strtoul(raw_chunk_size.c_str(),0,16); 466 | 467 | } 468 | else //没有找到,继续接受信息 469 | { 470 | if (!continueToReceiveBody(body)) 471 | { 472 | setErrorCode(HTTPERROR_TRANSPORT); 473 | break; 474 | } 475 | } 476 | } 477 | } 478 | 479 | if (!save_as_file && complete) 480 | { 481 | const std::string& body = response_receiver->getBody(); 482 | _response->setBody(body); 483 | } 484 | delete response_receiver; 485 | 486 | return complete; 487 | } 488 | 489 | bool HttpClient::continueToReceiveBody(std::string& body) 490 | { 491 | char buff[kmax_file_buffer_size] = {0}; 492 | int ret = _proxy_socket->read(buff,kmax_file_buffer_size); 493 | if (ret <= 0) 494 | { 495 | setErrorCode(HTTPERROR_TRANSPORT); 496 | return false; 497 | } 498 | else 499 | { 500 | body.append(buff,ret); 501 | return true; 502 | } 503 | } 504 | 505 | void HttpClient::onDataReadProgress(int read_length,int total_length) 506 | { 507 | if (_delegate) 508 | { 509 | _delegate->dataReadProgress(read_length,total_length); 510 | } 511 | } 512 | 513 | NAMESPACE_END(Http) --------------------------------------------------------------------------------