CHTTPSocket - Class to Explore Web 2002-4-25 http://www.vchelp.net/vchelp/view_article.asp?ft=1&article_id=283 封装了HTTP操作的类,支持代理服务HTTP下载类 2002-4-25 http://www.vchelp.net/vchelp/view_article.asp?ft=2&article_id=291 基于Winsock2的支持断点续传和SOCKS代理的HTTP下载类AmHttpUtilities 2002-4-25 http://www.vchelp.net/vchelp/view_article.asp?ft=1&article_id=308 封装了GET POST方法的HTTP类Multithread server class with example of HTTP server 2002-5-18 http://www.vchelp.net/vchelp/view_article.asp?ft=1&article_id=738 一个HTTP服务器的例子
使用wininet api可以下载各种形式的文件。这种未知长度的文件是采用所谓的chunked编码,编码规则就RFC2068的3.6节,下面是我的翻译,如果觉得翻译得不好或喜欢看英文就去看原文吧:参考:RFC2068 3.6 传输编码传输编码值是用来表示一个传输实体为编码传输以确保其在网络上安全传输。这与那些传输实体的内容编码是不同的。这种编码只是消息的一个属性,而不是针对原始实体。(注:即一个是对头部进行编码,而一个是对内容进行编码)所有的传输编码值都是视情况而定的。HTTP/1.1在传输编码头部使用传输编码值。(参看14.40)传输编码类似于MIME的内容传输编码。它的设计是为了使....块状传输编码修改消息体以使可以传输一系列的大数据块,这些数据块都有自己的长度标识,并且接下来就是一个可有可无的包含实体头部域的页脚。这可以使一些必要的信息可以随动态产生的数据一起传输给接收者,以使接收者可以接收到完整的消息。大数据块(头部)的组成: *chunk "0"CRLF ---CRLF即为\r\n,CR这\r,LF为\n -- 注意前面如果位数不够,后面用"0"(字符串0即0x20填充) footer CRLF其中,chunk的组成为: chunk-size[chunk-ext] CRLF -- 中括号中表示字段可有可无 chunk-data CRLFchunk-size = hex-no-zero * HEX hex-no-zero = <HEX excluding "0">chunk-ext = *(";" chunk-ext-name [ "=" chunk-ext-value ] ) chunk-ext-name = token chunk-ext-value = token | quoted-string chunk-data = chunk-size(0CTET)footer = *entity-header块编码以一个带footer的大小为零的块结尾。使用footer的目的是为了提供一条高效的途径来提供有关动态生成的实体信息。应用程序不能在footer里发送没有显示定义对footer来说合适的头部域。比如Content-MD5或其它对HTPP的未来的数字签名或其它应用。有关处理Chunked-body的解码的例子在19.4.6节中有陈述。所有的HTTP/1.1的应用程序必须能够接收和解码"chunked"传输编码,并且必须忽略那些自己不能理解的传输编码扩展。如果一个服务器收到了一个自身不能理解的传输编码实体,它必须返回501(即没有实现)(给客户端)并且关闭连接。服务方不能发送传输编码给那些HTTP/1.0的客户端。19.4.6 ——有关chunked encoding的解码伪语言表达方式length = 0; // 初始化length为0,这个长度应该为所下载的文件长度 // 读入chunk-size及chunk-ext(如果有的话)及\r\n read chunk-size, chunck-ext(if any) and CRLF while(chunk-size > 0) // 如果chunk-size大于0 { read chunk-data and CRLF // 读入chunk数据及\r\n append chunk-data to entity-body // 将读到的chunk数据添加到实体(所下载的文件) length = length + chunk-size; // 更新文件长度 read chunk-size and CRLF // 循环读入数据库大小 } // 下面是干什么?循环读入entity-header并添加到已存在的header域中 read entity-header while(entity-header not empty) { append entity-header to existing header fields read entity-header }据理解,chunk-size是以字符串形式的十六进制表示的,并且以\r\n结尾。如:61 62 63 0d 0a则表示chunk-size = (HEX)abc = (DEC)2748。参考PHP源码: $header = ""; $response = "";// connect if (!($request=fsockopen('whatever.com',80,$errno,$errstr))) exit($errstr); else { socket_set_timeout($request,10); // send request fwrite($request,$post); // get header -- 读出响应头 do $header.=fread($request,1); while (!preg_match('/\\r\\n\\r\\n$/',$header)); // check for chunked encoding if (preg_match('/Transfer\\-Encoding:\\s+chunked\\r\\n/',$header)) do { $byte = ""; $chunk_size=""; do { $chunk_size.=$byte; $byte=fread($request,1); } while ($byte!="\\r"); // till we match the CR fread($request, 1); // also drop off the LF $chunk_size=hexdec($chunk_size); // convert to real number $response.=fread($request,$chunk_size); fread($request,2); // ditch the CRLF that trails the chunk } while ($chunk_size); // till we reach the 0 length chunk (end er) else { // check for specified content length if (preg_match('/Content\\-Length:\\s+([0-9]*)\\r\\n/',$header,$matches)) { $response=fread($request,$matches[1]); } else { // not a nice way to do it (may also result in extra CRLF which trails the real content???) while (!feof($request)) $response .= fread($request, 4096); } } // close connection fclose($request); }// do something useful with the response print($header); print($response);
http://www.vchelp.net/vchelp/view_article.asp?ft=1&article_id=283
封装了HTTP操作的类,支持代理服务HTTP下载类 2002-4-25
http://www.vchelp.net/vchelp/view_article.asp?ft=2&article_id=291
基于Winsock2的支持断点续传和SOCKS代理的HTTP下载类AmHttpUtilities 2002-4-25
http://www.vchelp.net/vchelp/view_article.asp?ft=1&article_id=308
封装了GET POST方法的HTTP类Multithread server class with example of HTTP server 2002-5-18
http://www.vchelp.net/vchelp/view_article.asp?ft=1&article_id=738
一个HTTP服务器的例子
例如下面这个网页文件, 该如何下载
http://ent.sina.com.cn/x/2005-07-31/1213796953.html
用快车是可以正确下载的
{
CInternetSession session; //会话期对象)
CHttpConnection* pServer = NULL; // 指向服务器地址(URL)
CHttpFile * pHttpFile = NULL;//HTTP文件指针
CString strServerName; //服务器名
CString strObject; //查询对象名(http文件)
INTERNET_PORT nPort; //端口
DWORD dwServiceType; //服务类型
DWORD dwHttpRequestFlags = //请求标志
//INTERNET_FLAG_EXISTING_CONNECT;
INTERNET_FLAG_NO_AUTO_REDIRECT;
const TCHAR szHeaders[]=_T("Accept: text/* User-Agent:HttpClient ");
BOOL OK=AfxParseURL( //词法分析
pURL, //被分析URL串
dwServiceType, //服务类型,ftp,http等
strServerName, //服务器名
strObject, //URL中被查询对象
nPort ); //URL指定的端口,可能为空
OK=OK && //本例只考虑http协议
(dwServiceType ==
INTERNET_SERVICE_HTTP);
if (!OK)
{
AfxMessageBox("URL出错"); //报错
return false;
}
pServer = session.GetHttpConnection(strServerName, nPort); //获得服务器名
pHttpFile = pServer-> OpenRequest( CHttpConnection::HTTP_VERB_GET,strObject, NULL, 1, NULL, NULL,dwHttpRequestFlags);
//向服务器发送请求,建立http连接,
//建立本机上的http文件指针
pHttpFile->AddRequestHeaders(szHeaders);
pHttpFile->SendRequest(); //发送请求
CStdioFile f; //输出文件对象
//打开输出文件
if( !f.Open( SaveAsFilePath, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary ) )
{
MessageBox("Unable to open file");
return false;
}
//下面将检索结果保存到文件上
TCHAR szBuf[1024]; //缓存
int length=0;
UINT a=pHttpFile->GetLength();
//pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH,a);
while (length=pHttpFile->Read(szBuf, 1023))
f.Write(szBuf,length);
f.Close(); //善后工作
pHttpFile ->Close();
pServer ->Close();
if (pHttpFile != NULL) delete pHttpFile;
if (pServer != NULL) delete pServer;
session.Close();
return true;
}
发请求的时候别要求keep-alive就行
CInternetSession sess;
CHttpFile* pF = (CHttpFile*)sess.OpenURL(strUrl);
nLength = pF->Read(szHtm, 1000000);
pF->Close();
CFile f;
f.Open("c:\\1.html", CFile::modeCreate | CFile::modeWrite);
f.Write(szHtm, nLength);
f.Close();
GET /x/2005-07-31/1213796953.html HTTP/1.1
Accept: */*
User-Agent: Wix; Mozilla/4.0 (compatible; MSIE 6.0; Win32)
Host: ent.sina.com.cn
Cookie: UNIPROCT=1-4-2:1|71-0-0:1|60-2-2:1; UNIPROPATH=2:jin0607:0:13505166737:2:|*|1122992214918.3374|pid:60-2-2-0-4370|news.sr/cul/2005-03-01/4370.html|st:111.791|et:1122992214958|http://www.google.com/search?hl=zh-CN&q=%E7%AA%A6%E5%A9%B4&btnG=Google+%E6%90%9C%E7%B4%A2&lr=lang_zh-CN|hp:N|lb:1|*|; bn_stream_speed=301; UNIPROINFO=sz:1024x768||dp:32||ac:Mozilla||an:Microsoft Internet Explorer||av:4.0 (compatible, MSIE 6.0, Windows NT 5.0)||cpu:x86||pf:Win32||jv:1.3||ct:lan||lg:zh-cn||tz:-8
------------------------------------------------------------------
HTTP/1.0 200 OK
Date: Thu, 11 Aug 2005 07:42:16 GMT
Server: Apache/2.0.52 (Unix)
Last-Modified: Sun, 31 Jul 2005 04:52:49 GMT
ETag: "49e91f-eca0-e2ffca40"
Accept-Ranges: bytes
X-Powered-By: mod_xlayout_jh/0.0.1e
Vary: Accept-Encoding
Cache-Control: max-age=120
Expires: Thu, 11 Aug 2005 07:44:16 GMT
Content-Type: text/html
Age: 40
X-Cache: HIT from sqsh-228.sina.com.cn
Connection: close
------------------------------------------------------------------
LastURL : http://ent.sina.com.cn/x/2005-07-31/1213796953.html
ContentLength : 0
Date : 2005-08-11 07:42:16
LastModified : 2005-07-31 04:52:49
Expires : 1899-12-30 00:00:00
Server : Apache/2.0.52 (Unix)
AcceptRanges : bytes
ContentType : text/html
ContentEncodeing :
------------------------------------------------------------------
这是用WinInet.dll 写的程序下的,是可以正确下载的,下完后大小60576,
但不清楚WinInet.dll 是如何知道下载完毕的。
Date: Thu, 11 Aug 2005 07:42:16 GMT
Server: Apache/2.0.52 (Unix)
Last-Modified: Sun, 31 Jul 2005 04:52:49 GMT
ETag: "49e91f-eca0-e2ffca40"
Accept-Ranges: bytes
X-Powered-By: mod_xlayout_jh/0.0.1e
Vary: Accept-Encoding
Cache-Control: max-age=120
Expires: Thu, 11 Aug 2005 07:44:16 GMT
Content-Type: text/html
Age: 40
X-Cache: HIT from sqsh-228.sina.com.cn
Connection: close //这个地方不是说明了下载完毕关闭连接吗,连接关闭表示下载完毕,别告诉我,你不会判断对方关闭连接。
---------------------
就是这样啊,这种方式下载是非常规的哦,你明白了吗?
3.6 传输编码传输编码值是用来表示一个传输实体为编码传输以确保其在网络上安全传输。这与那些传输实体的内容编码是不同的。这种编码只是消息的一个属性,而不是针对原始实体。(注:即一个是对头部进行编码,而一个是对内容进行编码)所有的传输编码值都是视情况而定的。HTTP/1.1在传输编码头部使用传输编码值。(参看14.40)传输编码类似于MIME的内容传输编码。它的设计是为了使....块状传输编码修改消息体以使可以传输一系列的大数据块,这些数据块都有自己的长度标识,并且接下来就是一个可有可无的包含实体头部域的页脚。这可以使一些必要的信息可以随动态产生的数据一起传输给接收者,以使接收者可以接收到完整的消息。大数据块(头部)的组成:
*chunk
"0"CRLF ---CRLF即为\r\n,CR这\r,LF为\n -- 注意前面如果位数不够,后面用"0"(字符串0即0x20填充)
footer
CRLF其中,chunk的组成为:
chunk-size[chunk-ext] CRLF -- 中括号中表示字段可有可无
chunk-data CRLFchunk-size = hex-no-zero * HEX
hex-no-zero = <HEX excluding "0">chunk-ext = *(";" chunk-ext-name [ "=" chunk-ext-value ] )
chunk-ext-name = token
chunk-ext-value = token | quoted-string
chunk-data = chunk-size(0CTET)footer = *entity-header块编码以一个带footer的大小为零的块结尾。使用footer的目的是为了提供一条高效的途径来提供有关动态生成的实体信息。应用程序不能在footer里发送没有显示定义对footer来说合适的头部域。比如Content-MD5或其它对HTPP的未来的数字签名或其它应用。有关处理Chunked-body的解码的例子在19.4.6节中有陈述。所有的HTTP/1.1的应用程序必须能够接收和解码"chunked"传输编码,并且必须忽略那些自己不能理解的传输编码扩展。如果一个服务器收到了一个自身不能理解的传输编码实体,它必须返回501(即没有实现)(给客户端)并且关闭连接。服务方不能发送传输编码给那些HTTP/1.0的客户端。19.4.6 ——有关chunked encoding的解码伪语言表达方式length = 0; // 初始化length为0,这个长度应该为所下载的文件长度
// 读入chunk-size及chunk-ext(如果有的话)及\r\n
read chunk-size, chunck-ext(if any) and CRLF
while(chunk-size > 0) // 如果chunk-size大于0
{
read chunk-data and CRLF // 读入chunk数据及\r\n
append chunk-data to entity-body // 将读到的chunk数据添加到实体(所下载的文件)
length = length + chunk-size; // 更新文件长度
read chunk-size and CRLF // 循环读入数据库大小
}
// 下面是干什么?循环读入entity-header并添加到已存在的header域中
read entity-header
while(entity-header not empty)
{
append entity-header to existing header fields
read entity-header
}据理解,chunk-size是以字符串形式的十六进制表示的,并且以\r\n结尾。如:61 62 63 0d 0a则表示chunk-size = (HEX)abc = (DEC)2748。参考PHP源码:
$header = "";
$response = "";// connect
if (!($request=fsockopen('whatever.com',80,$errno,$errstr))) exit($errstr);
else {
socket_set_timeout($request,10);
// send request
fwrite($request,$post);
// get header -- 读出响应头
do $header.=fread($request,1); while (!preg_match('/\\r\\n\\r\\n$/',$header));
// check for chunked encoding
if (preg_match('/Transfer\\-Encoding:\\s+chunked\\r\\n/',$header))
do {
$byte = "";
$chunk_size="";
do {
$chunk_size.=$byte;
$byte=fread($request,1);
} while ($byte!="\\r"); // till we match the CR
fread($request, 1); // also drop off the LF
$chunk_size=hexdec($chunk_size); // convert to real number
$response.=fread($request,$chunk_size);
fread($request,2); // ditch the CRLF that trails the chunk
} while ($chunk_size); // till we reach the 0 length chunk (end er)
else {
// check for specified content length
if (preg_match('/Content\\-Length:\\s+([0-9]*)\\r\\n/',$header,$matches)) {
$response=fread($request,$matches[1]);
} else {
// not a nice way to do it (may also result in extra CRLF which trails the real content???)
while (!feof($request)) $response .= fread($request, 4096);
}
}
// close connection
fclose($request);
}// do something useful with the response
print($header);
print($response);