由于贪方便,用idhttp控件实现POST部分。结果发现频繁提交的时候总产生Socket error :10054 等N多不可控错误。遂想换其它方法。百度找了下。基本都是靠webbrowser 、idhttp等控件提交的。于是,摸索着用 wininet.dll的api自己实现了一个webpost。效果不错。特地贴上方便自己以后检索。使用 WinInet时,建议use以下单元: Windows,SysUtils,Classes,WinInet。
Post提交数据
function WebPagePost(sURL,sPostData:string):Pointer;stdcall;
const
RequestMethod = 'POST';
HTTP_VERSION = 'HTTP/1.1'; //HTTP版本 我抓包看过 HTTP/1.0 HTTP/1.1。尚未仔细了解其区别。按MSDN来写的。留空默认是1.0
var
dwSize:DWORD;
dwFileSize: Int64;
dwBytesRead,dwReserved:DWORD;
hInte,hConnection,hRequest:HInternet;
ContentSize:array[1..1024] of Char;
HostPort:Integer;
HostName,FileName,sHeader:String;
procedure ParseURL(URL: string;var HostName,FileName:string;var HostPort:Integer);
var
i,p,k: DWORD;
function StrToIntDef(const S: string; Default: Integer): Integer;
var
E: Integer;
begin
Val(S, Result, E);
if E <> 0 then Result := Default;
end;
begin
if lstrcmpi('http://',PChar(Copy(URL,1,7))) = 0 then System.Delete(URL, 1, 7);
HostName := URL;
FileName := '/';
HostPort := INTERNET_DEFAULT_HTTP_PORT;
i := Pos('/', URL);
if i <> 0 then
begin
HostName := Copy(URL, 1, i-1);
FileName := Copy(URL, i, Length(URL) - i + 1);
end;
p:=pos(':',HostName);
if p <> 0 then
begin
k:=Length(HostName)-p;
HostPort:=StrToIntDef(Copy(HostName,p+1,k),INTERNET_DEFAULT_HTTP_PORT);
Delete(HostName,p,k+1);
end;
end;
begin
Result := Pointer(-1);
dwFileSize :=0;
ParseURL(sURL,HostName,FileName,HostPort);
// 函数原型见 http://technet.microsoft.com/zh-cn/subscriptions/aa385096(v=vs.85).aspx
hInte := InternetOpen('', //UserAgent
INTERNET_OPEN_TYPE_PRECONFIG,nil,nil,0);
if hInte<>nil then
begin
hConnection := InternetConnect(hInte, // 函数原型见 http://technet.microsoft.com/zh-cn/query/ms909418
PChar(HostName),
HostPort,
nil,
nil,
INTERNET_SERVICE_HTTP,
0,
0);
if hConnection<>nil then
begin
hRequest := HttpOpenRequest(hConnection, // 函数原型见 http://msdn.microsoft.com/zh-cn/library/aa917871
PChar(RequestMethod),
PChar(FileName),
HTTP_VERSION,
'', //Referrer 来路
nil,//AcceptTypes 接受的文件类型 TEXT/HTML */*
INTERNET_FLAG_NO_CACHE_WRITE or
INTERNET_FLAG_RELOAD,
0);
if hRequest<>nil then
begin
sHeader := 'Content-Type: application/x-www-form-urlencoded' + #13#10;
// +'CLIENT-IP: 216.13.23.33'+#13#10
// 'X-FORWARDED-FOR: 216.13.23.33' + #13#10+; 伪造代理IP
// 函数原型见 http://msdn.microsoft.com/zh-cn/library/aa384227(v=VS.85)
HttpAddRequestHeaders(hRequest,PChar(sHeader),
Length(sHeader),
HTTP_ADDREQ_FLAG_ADD or HTTP_ADDREQ_FLAG_REPLACE);
// 函数原型见 http://msdn.microsoft.com/zh-cn/library/windows/desktop/aa384247(v=vs.85).aspx
if HttpSendRequest(hRequest,nil,0,PChar(sPostData),Length(sPostData)) then
begin
dwReserved:=0;
dwSize:=SizeOf(ContentSize);
// 函数原型 http://msdn.microsoft.com/zh-cn/subscriptions/downloads/aa384238.aspx
if HttpQueryInfo(hRequest,HTTP_QUERY_CONTENT_LENGTH,@ContentSize,dwSize,dwReserved) then
begin
dwFileSize:=StrToInt(StrPas(@ContentSize));
GetMem(Result, dwFileSize);
InternetReadFile(hRequest,Result,dwFileSize,dwBytesRead);
end;
end;
end;
InternetCloseHandle(hRequest);
end;
InternetCloseHandle(hConnection);
end;
InternetCloseHandle(hInte);
end;
调用方法:
WebPagePost('http://www.xxx.com/register.php','user=xxx;pass=xxx');
Get下载数据
function TMyHttp.GetData(fileUrl:String):boolean; //请求下载文件
var
hSession : HINTERNET;
hOpenUrl : HINTERNET;
Temp : array [0..MAXBLOCKSIZE-1] of Byte;
dwRead : DWORD;
fs : TFileStream;
dwResult : DWORD;
begin
Result:=False;
if(length(trim(fileUrl))=0) then
raise Exception.Create('请求的URL为空、URL不合规则或URL不存在');
try
begin
hSession := InternetOpen('RookIE/1.0', //指定调用 WinINet 函数的应用程序或入口。该入口用作HTTP协议中用户代理项。
INTERNET_OPEN_TYPE_PRECONFIG,//访问要求类型,该参数可为下列值之一:
{ INTERNET_OPEN_TYPE_DIRECT 解析所有本地主机;
INTERNET_OPEN_TYPE_PRECONFIG 返回注册表中代理或直接的配置;
INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY 返回注册表中代理或直接的配置,并防止对Microsoft Jscript 或 INS文件的使用;
INTERNET_OPEN_TYPE_PROXY 为代理传递请求,除非代理提供了旁路列表且解析的名字可以绕过代理;此时,函数使用INTERNET_OPEN_TYPE_DIRECT。}
nil, //指定了当lAccessType类型为INTERNET_OPEN_TYPE_PROXY时,代理服务器的名字。
nil, //指向一个字符串,它指定一个可选的主机名列表或IP地址,列表可包括未知元素。
0);
{该参数可为下列值的任意组合:
INTERNET_FLAG_ASYNC 仅能用于作用在该函数返回的句柄的子句柄上的异步请求。
INTERNET_FLAG_FROM_CACHE 不做网络请求。所有的实体都由缓存返回。如果请求条目不在缓存中,一个适当的错误将返回。
INTERNET_FLAG_OFFLINE 与 INTERNET_FLAG_FROM_CACHE 一样。}
//成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinINet函数。
//失败:返回NULL。
if hSession = nil then Exit;
//通过一个完整的FTP,Gopher或HTTP网址打开一个资源。
hOpenUrl := InternetOpenUrl(hSession, //当前的 Internet 会话句柄。句柄必须由前期的 InternetOpen 调用返回。
PChar(fileUrl), //一个空字符结束的字符串变量的指针,指定读取的网址。只有以ftp:, gopher:, http:, 或者 https: 开头的网址被支持。
nil, //一个空字符结束的字符串变量的指针,指定发送到HTTP服务器的头信息。欲了解更多信息,请参阅HttpSendRequest函数里lpszHeaders参数的说明。
0, //额外的头的大小,以TCHAR为单位。如果这个参数是-1L并且lpszHeaders不是NULL,lpszHeaders被假设为零终止( ASCIIZ ),而长度被自动计算。
0,
//INTERNET_FLAG_DONT_CACHE, //不添加返回实体到缓存
{INTERNET_FLAG_EXISTING_CONNECT
如果使用相同的必须属性创建会话,会尝试利用现有的InternetConnect对象。这只对FTP操作非常有用,因为FTP是唯一在同一会话中执行多种操作的协议。WinINet API 为每个由InternetOpen产生的HINTERNET句柄缓冲一个单独链接句柄。InternetOpenUrl使用此标志的HTTP和FTP连接。
INTERNET_FLAG_HYPERLINK
当决定何时从网络重载时,如果服务器没有返回 Expires time 和 LastModified,那么强制重载。
INTERNET_FLAG_IGNORE_CERT_CN_INVALID
停用检查从服务器对必须的主机名称返回的SSL/PCT-based证书。 WinINet函数使用简单的比较匹配主机名称和通配符的规则检查证书。
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
停用检查的SSL/PCT-based的证书的适当的有效日期。
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
禁用检测这中特殊的重定向。当使用此标志, WinINet 透明允许从HTTPS到HTTP URL的重定向。
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
禁用检测这中特殊的重定向。当使用此标志, WinINet 透明的允许的HTTP到HTTPS URL的重定向。
INTERNET_FLAG_KEEP_CONNECTION
如果可能的话,为连接使用保活语义。这个标志要求微软网络( MSN ),NTLM和其他类型的身份验证。
INTERNET_FLAG_NEED_FILE
如果要创建的文件不能被缓存,创建临时文件。
INTERNET_FLAG_NO_AUTH
不试图自动验证。
INTERNET_FLAG_NO_AUTO_REDIRECT
不自动处理HttpSendRequest中的重定向。
INTERNET_FLAG_NO_CACHE_WRITE
不添加返回实体到缓存。
INTERNET_FLAG_NO_COOKIES
不会自动添加的Cookie头到请求,并且不自动添加返回的cookie到cookie数据库。
INTERNET_FLAG_NO_UI
禁用Cookie的对话框。
INTERNET_FLAG_PASSIVE
使用被动FTP语义。InternetOpenUrl为FTP的文件和目录使用此标志。
INTERNET_FLAG_PRAGMA_NOCACHE
即使代理中存在缓存副本,也强制要求由源服务器返回。
INTERNET_FLAG_RAW_DATA
检索的Gopher目录信息时,传回的数据作为GOPHER_FIND_DATA结构,如果检索的FTP目录信息时,作为一个WIN32_FIND_DATA结构。如果此标志没有指定,或者请求通过CERN代理创建, InternetOpenUrl返回的HTML版本的目录。
INTERNET_FLAG_RELOAD
从原服务器强制下载所要求的文件,对象,或目录列表,而不是从缓存下载。
INTERNET_FLAG_RESYNCHRONIZE
重新加载的HTTP资源,如果资源在最后一次下载后已被修改。所有FTP和Gopher资源将被重载。
INTERNET_FLAG_SECURE
使用安全传输语义。这次传输使用安全套字节层/专用通信技术(的SSL / PCT ),这只有在HTTP请求时有意义。
}
0);
// 返回值
// 如果已成功建立到FTP,Gopher,或HTTP URL的连接,返回一个有效的句柄,如果连接失败返回NULL。要检索特定的错误讯息,请GetLastError 。要确定为什么对服务器的访问被拒绝,请调用InternetGetLastResponseInfo。
if hOpenUrl = nil then Exit;
// fs := TFile.Open(FileName, TFileMode.fmOpenOrCreate, TFileAccess.faRead, TFileShare.fsRead);
fs := TFileStream.Create('c:\test.jpg',fmCreate); //创建文件
try
fs.Seek(0,soBeginning);
dwRead := 1;
while (dwRead > 0) do //循环读取数据
begin
InternetReadFile(hOpenUrl,
@Temp,
MAXBLOCKSIZE, //缓冲区大小
dwRead); //实际读取数据的大小
fs.Write(Temp,dwRead);
end;
finally
fs.Free;
InternetCloseHandle(hOpenUrl);
InternetCloseHandle(hSession);
end;
Result:=True;
end;
except on e:Exception do
begin
raise Exception.Create('请求的URL未以http://或https://开头,或者图片URL路径不存在!'); //向上抛异常
end;
end;
end;
调用方法:
GetData('http://www.xxx.com/register.php/123.jpg');
经验之谈:
当在Delphi中使用Http等网络控件进行数据通信时,如果Delphi本身的组件不稳定,可以使用C++、C#等生成程序来替换,Delphi程序与以上程序使用TCP、UDP通信来间接完成需求,或C++、C#等生成DLL供Delphi调用。