class HttpRequest : public TcpSocket { int phase; String data; int64 count; HttpHeader header; String error; String body; enum { DEFAULT_HTTP_PORT = 80, DEFAULT_HTTPS_PORT = 443 }; int max_header_size; int max_content_size; int max_redirects; int max_retries; int timeout; String host; int port; String proxy_host; int proxy_port; String proxy_username; String proxy_password; String ssl_proxy_host; int ssl_proxy_port; String ssl_proxy_username; String ssl_proxy_password; String path; bool ssl; int method; String custom_method; String accept; String agent; bool force_digest; bool is_post; bool std_headers; bool hasurlvar; bool keep_alive; String contenttype; String username; String password; String digest; String request_headers; String postdata; String multipart; VectorMap cookies; String protocol; int status_code; String reason_phrase; int start_time; int retry_count; int redirect_count; int chunk; IpAddrInfo addrinfo; bool gzip; Zlib z; int64 postdataPos; Upp::String postdataName; void Init(); void StartPhase(int s); void Start(); void Dns(); void StartConnect(); void ProcessSSLProxyResponse(); void AfterConnect(); void StartRequest(); bool SendingData(); bool SendingStream(); bool ReadingHeader(); void StartBody(); bool ReadingBody(); void ReadingChunkHeader(); void Finish(); bool IsRequestTimeout(); void CopyCookies(); void HttpError(const char *s); void ContentOut(const void *ptr, int size); void Out(const void *ptr, int size); String CalculateDigest(const String& authenticate) const; public: enum { METHOD_GET = 0, METHOD_POST = 1, METHOD_HEAD = 2, METHOD_PUT = 3, METHOD_DELETE = 4, METHOD_TRACE = 5, METHOD_OPTIONS = 6, METHOD_CONNECT = 7, METHOD_PATCH = 8, }; Callback2 WhenContent; Callback WhenStart; Callback WhenDo; HttpRequest& MaxHeaderSize(int m) { max_header_size = m; return *this; } HttpRequest& MaxContentSize(int m) { max_content_size = m; return *this; } HttpRequest& MaxRedirect(int n) { max_redirects = n; return *this; } HttpRequest& MaxRetries(int n) { max_retries = n; return *this; } HttpRequest& RequestTimeout(int ms) { timeout = ms; return *this; } HttpRequest& ChunkSize(int n) { chunk = n; return *this; } HttpRequest& Method(int m, const char *custom_name = NULL); HttpRequest& GET() { return Method(METHOD_GET); } HttpRequest& POST() { return Method(METHOD_POST); } HttpRequest& HEAD() { return Method(METHOD_HEAD); } HttpRequest& PUT() { return Method(METHOD_PUT); } HttpRequest& DEL() { return Method(METHOD_DELETE); } HttpRequest& TRACE() { return Method(METHOD_TRACE); } HttpRequest& OPTIONS() { return Method(METHOD_OPTIONS); } HttpRequest& CONNECT() { return Method(METHOD_CONNECT); } HttpRequest& PATCH() { return Method(METHOD_PATCH); } HttpRequest& Host(const String& h) { host = h; return *this; } HttpRequest& Port(int p) { port = p; return *this; } HttpRequest& SSL(bool b = true) { ssl = b; return *this; } HttpRequest& Path(const String& p) { path = p; return *this; } HttpRequest& User(const String& u, const String& p) { username = u; password = p; return *this; } HttpRequest& Digest() { force_digest = true; return *this; } HttpRequest& Digest(const String& d) { digest = d; return *this; } HttpRequest& Url(const char *url); HttpRequest& UrlVar(const char *id, const String& data); HttpRequest& operator()(const char *id, const String& data) { return UrlVar(id, data); } HttpRequest& PostData(const String& pd) { postdata = pd; return *this; } HttpRequest& PostDataStream(const String& pdn, const int64 pos = 0) { postdataName = pdn; postdataPos = pos; return *this; }// HttpRequest& PostUData(const String& pd) { return PostData(UrlEncode(pd)); } HttpRequest& Post(const String& data) { POST(); return PostData(data); } HttpRequest& Post(const char *id, const String& data); HttpRequest& Part(const char *id, const String& data, const char *content_type = NULL, const char *filename = NULL); // HttpRequest& ClearPost() { PostData(Null); multipart.Clear(); GET(); return *this; } HttpRequest& ClearPost() { PostData(Null); PostDataStream(Null); multipart.Clear(); GET(); return *this; }// HttpRequest& Headers(const String& h) { request_headers = h; return *this; } HttpRequest& ClearHeaders() { return Headers(Null); } HttpRequest& AddHeaders(const String& h) { request_headers.Cat(h); return *this; } HttpRequest& Header(const char *id, const String& data); HttpRequest& Cookie(const HttpCookie& c); HttpRequest& Cookie(const String& id, const String& value, const String& domain = Null, const String& path = Null); HttpRequest& CopyCookies(const HttpRequest& r); HttpRequest& StdHeaders(bool sh) { std_headers = sh; return *this; } HttpRequest& NoStdHeaders() { return StdHeaders(false); } HttpRequest& Accept(const String& a) { accept = a; return *this; } HttpRequest& UserAgent(const String& a) { agent = a; return *this; } HttpRequest& ContentType(const String& a) { contenttype = a; return *this; } HttpRequest& KeepAlive(bool ka = true) { keep_alive = ka; return *this;} HttpRequest& Proxy(const String& host, int port) { proxy_host = host; proxy_port = port; return *this; } HttpRequest& Proxy(const char *p); HttpRequest& ProxyAuth(const String& u, const String& p) { proxy_username = u; proxy_password = p; return *this; } HttpRequest& SSLProxy(const String& host, int port) { ssl_proxy_host = host; ssl_proxy_port = port; return *this; } HttpRequest& SSLProxy(const char *p); HttpRequest& SSLProxyAuth(const String& u, const String& p) { ssl_proxy_username = u; ssl_proxy_password = p; return *this; } HttpRequest& CommonProxy(const String& host, int port) { Proxy(host, port); return SSLProxy(host, port); } HttpRequest& CommonProxy(const char *p) { Proxy(p); return SSLProxy(p); } HttpRequest& CommonProxyAuth(const String& u, const String& p) { ProxyAuth(u, p); return SSLProxyAuth(u, p); } bool IsSocketError() const { return TcpSocket::IsError(); } bool IsHttpError() const { return !IsNull(error) ; } bool IsError() const { return IsSocketError() || IsHttpError(); } String GetErrorDesc() const { return IsSocketError() ? TcpSocket::GetErrorDesc() : error; } void ClearError() { TcpSocket::ClearError(); error.Clear(); } String GetHeader(const char *id) { return header[id]; } String operator[](const char *id) { return GetHeader(id); } String GetRedirectUrl(); int64 GetContentLength(); int GetStatusCode() const { return status_code; } String GetReasonPhrase() const { return reason_phrase; } const HttpHeader& GetHttpHeader() const { return header; } String GetCookie(const char *id) { return header.GetCookie(id); } String GetContent() const { return body; } String operator~() const { return GetContent(); } operator String() const { return GetContent(); } void ClearContent() { body.Clear(); } enum Phase { BEGIN, START, DNS, SSLPROXYREQUEST, SSLPROXYRESPONSE, SSLHANDSHAKE, REQUEST, HEADER, BODY, CHUNK_HEADER, CHUNK_BODY, TRAILER, FINISHED, FAILED, }; bool Do(); int GetPhase() const { return phase; } String GetPhaseName() const; bool InProgress() const { return phase != FAILED && phase != FINISHED; } bool IsFailure() const { return phase == FAILED; } bool IsSuccess() const { return phase == FINISHED && status_code >= 200 && status_code < 300; } String Execute(); void New(); HttpRequest(); HttpRequest(const char *url); static void Trace(bool b = true); static void TraceHeader(bool b); static void TraceBody(bool b); static void TraceShort(bool b); }; bool HttpRequest::Do() { int c1, c2; switch(phase) { case BEGIN: retry_count = 0; redirect_count = 0; start_time = msecs(); GlobalTimeout(timeout); case START: Start(); break; case DNS: Dns(); break; case SSLPROXYREQUEST: if(SendingData()) break; StartPhase(SSLPROXYRESPONSE); break; case SSLPROXYRESPONSE: if(ReadingHeader()) break; ProcessSSLProxyResponse(); break; case SSLHANDSHAKE: if(SSLHandshake()) break; StartRequest(); break; case REQUEST: if( IsNull(postdataName) ) { if(SendingData()) break; } else { if(SendingStream()) break; } StartPhase(HEADER); break; case HEADER: if(ReadingHeader()) break; StartBody(); break; case BODY: if(ReadingBody()) break; Finish(); break; case CHUNK_HEADER: ReadingChunkHeader(); break; case CHUNK_BODY: if(ReadingBody()) break; c1 = TcpSocket::Get(); c2 = TcpSocket::Get(); if(c1 != '\r' || c2 != '\n') HttpError("missing ending CRLF in chunked transfer"); StartPhase(CHUNK_HEADER); break; case TRAILER: if(ReadingHeader()) break; header.ParseAdd(data); Finish(); break; case FINISHED: case FAILED: WhenDo(); return false; default: NEVER(); } if(phase != FAILED) { if(IsSocketError() || IsError()) phase = FAILED; else if(msecs(start_time) >= timeout) { HttpError("connection timed out"); phase = FAILED; } else if(IsAbort()) { HttpError("connection was aborted"); phase = FAILED; } } if(phase == FAILED) { if(retry_count++ < max_retries) { LLOGS("HTTP retry on error " << GetErrorDesc()); start_time = msecs(); GlobalTimeout(timeout); StartPhase(START); } } WhenDo(); return phase != FINISHED && phase != FAILED; } void HttpRequest::StartRequest() { StartPhase(REQUEST); count = 0; String ctype = contenttype; if((method == METHOD_POST || method == METHOD_PUT) && IsNull(ctype)) ctype = "application/x-www-form-urlencoded"; static const char *smethod[] = { "GET", "POST", "HEAD", "PUT", "DELETE", "TRACE", "OPTIONS", "CONNECT", "PATCH", }; ASSERT(method >= 0 && method <= 8); data = Nvl(custom_method, smethod[method]); data << ' '; String host_port = host; if(port) host_port << ':' << port; String url; url << "http://" << host_port << Nvl(path, "/"); if(!IsNull(proxy_host) && !ssl) data << url; else { if(*path != '/') data << '/'; data << path; } data << " HTTP/1.1\r\n"; String pd = postdata; if(!IsNull(multipart)) pd << "--" << multipart << "--\r\n"; if(method == METHOD_GET || method == METHOD_HEAD){ pd.Clear(); postdataName.Clear(); } if(std_headers) { data << "URL: " << url << "\r\n" << "Host: " << host_port << "\r\n" << "Connection: " << (keep_alive ? "keep-alive\r\n" : "close\r\n") << "Accept: " << Nvl(accept, "*/*") << "\r\n" << "Accept-Encoding: gzip\r\n" << "User-Agent: " << Nvl(agent, "U++ HTTP request") << "\r\n"; if((IsNull(postdataName)? pd.GetCount() : Upp::GetFileLength(postdataName)-postdataPos) || method == METHOD_POST || method == METHOD_PUT) data << "Content-Length: " << (IsNull(postdataName)? pd.GetCount() : Upp::GetFileLength(postdataName)-postdataPos) << "\r\n"; if(ctype.GetCount()) data << "Content-Type: " << ctype << "\r\n"; } VectorMap > cms; for(int i = 0; i < cookies.GetCount(); i++) { const HttpCookie& c = cookies[i]; if(host.EndsWith(c.domain) && path.StartsWith(c.path)) { Tuple2& m = cms.GetAdd(c.id, MakeTuple(String(), -1)); if(c.path.GetLength() > m.b) { m.a = c.value; m.b = c.path.GetLength(); } } } String cs; for(int i = 0; i < cms.GetCount(); i++) { if(i) cs << "; "; cs << cms.GetKey(i) << '=' << cms[i].a; } if(cs.GetCount()) data << "Cookie: " << cs << "\r\n"; if(!IsNull(proxy_host) && !IsNull(proxy_username)) data << "Proxy-Authorization: Basic " << Base64Encode(proxy_username + ':' + proxy_password) << "\r\n"; if(!IsNull(digest)) data << "Authorization: Digest " << digest << "\r\n"; else if(!force_digest && (!IsNull(username) || !IsNull(password))) data << "Authorization: Basic " << Base64Encode(username + ":" + password) << "\r\n"; data << request_headers; LLOG("HTTP REQUEST " << host << ":" << port); if (pd.GetCount() || method == METHOD_POST || method == METHOD_PUT) LLOGSS("HTTP Request " << smethod[method] << " " << url << " data:" << ctype << "(" << pd.GetCount() << ")"); else LLOGSS("HTTP Request " << smethod[method] << " " << url); LLOG("HTTP request:\n" << data); data << "\r\n" << pd; LLOGB("HTTP request body:\n" << pd); } bool HttpRequest::SendingStream() { Upp::FileIn in( postdataName ); in.Seek( postdataPos ); int bufferSize = 2048 - (data.GetLength() & 2047); Upp::StringBuffer buffer( bufferSize ); int readingSize = in.Get( buffer, bufferSize ); int64 postdataSize = in.GetSize() - postdataPos - readingSize; data << Upp::String( ~buffer, readingSize ); for(;;) { int n = min(2048, data.GetLength() - (int)count); if( data.GetLength() - (int)count < 0 ) { int kk = 0; } n = TcpSocket::Put(~data + count, n); if(n == 0) break; count += n; } if( data.GetLength() == count ) { Upp::StringBuffer buffer( 2048 ); for(;;) { int n = min((int64)2048, (int64)data.GetLength() + postdataSize - (int64)count); int readingSize = in.Get( buffer, n ); n = TcpSocket::Put(~buffer, readingSize); if(n == 0) break; count += n; } } return count < data.GetLength() + postdataSize; }