我们还是顺着代理类中的一个方法来跟踪,如下所示:
function TFirstSampleService_Proxy.Nicknames(const FullName: Widestring): Widestring;
begin
try
__Message.InitializeRequestMessage(__TransportChannel, 'FirstSample', __InterfaceName, 'Nicknames');
__Message.Write('FullName', TypeInfo(Widestring), FullName, []);
__Message.Finalize;
__TransportChannel.Dispatch(__Message);
__Message.Read('Result', TypeInfo(Widestring), result, []);
finally
__Message.UnsetAttributes(__TransportChannel);
__Message.FreeStream;
end
end;
从上一节我们知道__Message就是我们指定的Message控件,InitializeRequestMessage方法将接口名称,消息名称,消息类型等信息传递给Message控件;Write方法实现:Serializer.Write(aName, aTypeInfo, Ptr);开始流化数据。这里的Serializer对象将会根据不同的Message类型创建不同的序列化类型,关键的流化方法如WriteInt64,WriteInteger,WriteWideString等方法在Serializer中实现。并由__Message.Write方法的第二个参数指定参数类型,据此调用具体的流化方法.好了,消息控件的任务完成了,他已经收集了所有需要传输的数据,接着就由通道调用Dispatch方法来使用消息控件了.
在uROClient单元中,找到procedure TROTransportChannel.Dispatch(aMessage: IROMessage);方法.这个方法中调用了Dispatch(lRequest, lResponse);方法,看看参数名称,是不是豁然开朗了?请求流和应答流.接着就是调用ReadFromStream方法来获取服务端传回来的结果了.我们继续看Dispatch(lRequest, lResponse);方法.这个方法中判断通道是否繁忙,如果需要,会进行信息流压缩,然后调用IntDispatch方法向下传输,并对返回的信息解压缩.IntDispatch是一个虚方法,由派生类实现.我们现在看看实现:
procedure TROWinInetHTTPChannel.IntDispatch(aRequest, aResponse: TStream);
var lRequest:hInternet;
begin
CheckProperties;
Connected := true;
try
lRequest := SendData(aRequest);
try
ReceiveData(lRequest,aResponse);
finally
InternetCloseHandle(lRequest);
end;
finally
if not KeepConnection then Connected := false;
end;
end;
看到两个关键的函数,SendData和ReceiveData.数据在这里穿墙并返回.下面重点研究这两个函数.
function TROWinInetHTTPChannel.SendData(iData: TStream):hInternet;
var
RetVal: DWORD;
bRes: Boolean;
procedure _Check;
begin
if not bRes then begin
RetVal := GetLastError;
if (RetVal > const_WinInetErrorCode_MIN) and (RetVal < const_WinInetErrorCode_MAX) then begin
if (RetVal = ERROR_INTERNET_SEC_CERT_ERRORS) or
(RetVal = ERROR_INTERNET_INVALID_CA) or
(RetVal = ERROR_INTERNET_SEC_CERT_CN_INVALID) or
(RetVal = ERROR_INTERNET_SEC_CERT_DATE_INVALID) or
(RetVal = ERROR_INTERNET_SEC_CERT_REV_FAILED) then begin
RetVal := AllowInvalidCA(Result, RetVal);
// MODIFIED BY TRINET
RetVal := ERROR_RETRY;
// MODIFIED BY TRINET END
bRes := True;
end else if (RetVal = ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED) then begin
{ you are accessing Secure Socket Layer (SSL)-protected resource
on a Web server that requires a valid client certificate. }
RetVal := CheckInetError(Result, RetVal);
bRes := True;
// MODIFIED BY TRINET
RetVal := ERROR_RETRY;
// MODIFIED BY TRINET END
end else if (RetVal = HTTP_STATUS_DENIED)
or (RetVal = HTTP_STATUS_PAYMENT_REQ) then begin
RetVal := CheckInetError(Result, RetVal);
// MODIFIED BY TRINET
RetVal := ERROR_RETRY;
// MODIFIED BY TRINET END
bRes := True;
end else if (RetVal = ERROR_INTERNET_CONNECTION_ABORTED) then begin
// slavad
// RetVal := ERROR_RETRY;
RetVal := ERROR_CANCELLED;
// slavad
bRes := True;
end else if (RetVal = ERROR_INTERNET_FORCE_RETRY) then
begin
RetVal := ERROR_RETRY;
bRes := True;
end
end;
end else begin
RetVal := CheckInetError(Result, GetLastError);
end;
Check(not bRes);
end;
const
bufsize = 64*1024; //32kb
var
lHeaders: string;
Flags: DWord;
AcceptTypes: array of PChar;
Mask: DWORD;
buf: Pointer;
lTotalsize: cardinal;
lCurSize: cardinal;
lSendSize,lwrittensize: cardinal;
intbuf: INTERNET_BUFFERS;
lStreamSize: integer;
begin
SetLength(AcceptTypes, 2);
TriggerProgress(ptStart, pdSending, 0, iData.Size); //更新进度条
AcceptTypes[0] := PChar('application/octet-stream');
AcceptTypes[1] := nil;
//设置标志位:
Flags := INTERNET_FLAG_NO_CACHE_WRITE;
if (KeepConnection) or (ProxyLogin.Username<>'') then Flags := Flags or INTERNET_FLAG_KEEP_CONNECTION; //保持连接
if fURLScheme = INTERNET_SCHEME_HTTPS then begin
Flags := Flags or INTERNET_FLAG_SECURE; //安全特性
if fTrustInvalidCA then begin //使用证书
Flags := Flags or INTERNET_FLAG_IGNORE_CERT_CN_INVALID
or INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
end;
end;
//调用事件
if assigned(fOnBeforeOpenRequest) then
fOnBeforeOpenRequest(Self);
//打开Http通道
result := HttpOpenRequest(FInetConnect, 'POST', PChar(fURLSite), nil, nil, Pointer(AcceptTypes), Flags, Integer(Self));
Check(not Assigned(result));
//调用事件
if assigned(fOnAfterOpenRequest) then
fOnAfterOpenRequest(Self, result);
//设置Http通道的超时属性
{ Time Out }
if fTimeOut > 0 then begin
InternetSetOption(Result, INTERNET_OPTION_RECEIVE_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));
InternetSetOption(FInetConnect, INTERNET_OPTION_RECEIVE_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));
InternetSetOption(Result, INTERNET_OPTION_SEND_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));
InternetSetOption(FInetConnect, INTERNET_OPTION_SEND_TIMEOUT, @fTimeOut, SizeOf(fTimeOut));
end;
//
{ Certificates Errors }
Mask := INTERNET_ERROR_MASK_COMBINED_SEC_CERT;
InternetSetOption(FInetConnect, INTERNET_OPTION_ERROR_MASK, @Mask, SizeOf(Mask));
// MODIFIED BY TRINET
lStreamSize:=iData.Size;
GetMem(buf, bufsize);
try
if ProxyLogin.Username<>'' then
begin
bRes := HttpSendRequest(Result,nil,0,nil,0);
repeat
bRes := InternetReadFile (Result, buf, bufsize, lSendSize);
until (lSendSize = 0);
end;
//开始组织包头 分包
RetVal := ERROR_RETRY;
while RetVal=ERROR_RETRY do
begin
iData.Position := 0;
//包头数据
FillChar(intbuf, sizeof(INTERNET_BUFFERS),0);
intbuf.dwStructSize := sizeof(INTERNET_BUFFERS);
SetHeaders('Content-Length',IntToStr(lStreamSize));
intbuf.lpcszHeader:= PChar(fHeaders);
intbuf.dwHeadersLength:= Length(fHeaders);
intbuf.dwBufferTotal:=lStreamSize;
lSendSize := 0;
lTotalsize:= iData.Size;
repeat //发送HTTP包头
bRes:= HttpSendRequestEx(Result, @intbuf,nil, 0, 0);
_Check;
until RetVal <> ERROR_INTERNET_FORCE_RETRY;
if RetVal=ERROR_RETRY then continue;
while lTotalsize > 0 do begin //分包传输
if lTotalsize > bufsize then
lCurSize := bufsize
else
lCurSize := lTotalsize;
iData.Read(buf^,lCurSize);
inc(lSendSize, lCurSize);
dec(lTotalsize,lCurSize);
while true do begin
lHeaders := fHeaders;
fHeaders := '';
while lCurSize > 0 do begin
bRes := InternetWriteFile(Result,buf,lcursize, lwrittensize); //写包
if not bRes and (lwrittensize <> lcursize) and (lwrittensize<>0) then begin
//如果一个包没有发生成功或发送出去一部分,将没发送的部分下次循环发送
Dec(lCurSize,lwrittensize);
Move( (PAnsichar(buf)+lwrittensize)^ ,buf^, lCurSize);
end
else begin
Break;
end;
end;
_Check;
case RetVal of
ERROR_SUCCESS: begin
SetLastError(ERROR_SUCCESS);
Break;
end;
ERROR_CANCELLED: SysUtils.Abort;
ERROR_INTERNET_FORCE_RETRY: {Retry the operation};
end;
end;
TriggerProgress(ptInProgress, pdSending, lSendSize, lStreamSize);
end;
bres:= HttpEndRequest(Result,nil,0,0);
_Check;
if RetVal = ERROR_INTERNET_FORCE_RETRY then RetVal := ERROR_RETRY;
end;
finally
Freemem(buf);
end;
// MODIFIED BY TRINET - END
TriggerProgress(ptDone, pdSending, 0, lStreamSize);
end;
procedure TROWinInetHTTPChannel.ReceiveData(iRequest:hInternet; ioData:TStream);
const MaxStatusText : Integer = 4096;
var
Size, Status, Len, Index: DWord;
S:string;
lpszData: PAnsiChar; // buffer for the data
dwSize: DWORD; // size of the data available
dwDownloaded: DWORD; // size of the downloaded data
lTotalSize,lReceivedSize:dword;
begin
Len := SizeOf(Status);
Index := 0;
{ Get Status Code }
if not HttpQueryInfo(iRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER,
@Status, Len, Index) then RaiseLastOSError();
{ Throw Exception is StatusCode >= 300 BUT not 500. SOAP faulty envelopes comes with that set }
if (Status >= 300) and (Status <> 500) then begin
Index := 0;
Size := MaxStatusText;
SetLength(S, Size);
if HttpQueryInfo(iRequest, HTTP_QUERY_STATUS_TEXT, @S[1], Size, Index) then begin
SetLength(S, Size);
raise EROException.CreateFmt('%s (%d)', [S, Status]);
end;
end;
Index := 0;
{ get total size }
if not HttpQueryInfo(iRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, @lTotalSize, Len, Index) then begin
{ do not raise an exception when there is no "Content-Length" value in result header }
if GetLastError <> ERROR_HTTP_HEADER_NOT_FOUND then begin
RaiseLastOSError(); //TotalSize = 0;//
end else begin
SetLastError(0);
lTotalSize := 0;
end;
end;
TriggerProgress(ptStart, pdReceiving, 0, lTotalSize);
lReceivedSize := 0;
dwSize := 0;
dwDownloaded := 0;
while (true) do begin //循环检测
// The call to InternetQueryDataAvailable determines the amount of data available to download.
while (not InternetQueryDataAvailable(iRequest, dwSize, 0, 0)) //检测数据不可读 则暂停一下
and (GetLastError = ERROR_IO_PENDING) do begin //可读则在dwSize返回数据大小
Sleep(1);
end;
// Allocates a buffer of the size returned by InternetQueryDataAvailable
GetMem(lpszData, dwSize + 1);
try
// Reads the data from the HINTERNET handle.
while (not InternetReadFile(iRequest, lpszData, dwSize, dwDownloaded))
and (GetLastError = ERROR_IO_PENDING) do begin
Sleep(1);
end;
if (GetLastError >= const_WinInetErrorCode_MIN) and (GetLastError <= const_WinInetErrorCode_MAX) then begin
Check(True);
end;
if GetLastError <> 0 then begin
// Adds a null terminator to the end of the data buffer
lpszData[dwDownloaded] := #0;
end;
ioData.Write(lpszData[0], dwDownloaded);
Inc(lReceivedSize, dwDownloaded);
finally
FreeMem(lpszData);
end;
TriggerProgress(ptInProgress, pdReceiving, lReceivedSize, lTotalSize); //更新进度
// Check the size of the remaining data. If it is zero, break.
if (dwDownloaded = 0) then begin
break;
end;
end;
ioData.seek(0, soFromBeginning);
TriggerProgress(ptDone, pdReceiving, 0,0);
end;
由于WinInet没有提供源码,不过从帮助上看,好像就是将API函数做了对应的参数转换,网络底层通信暂时不跟进了.等以后再研究.其实上面的代码已经很清楚了,使用HTTP进行客户端编程的主要几个函数都有体现.等有空再看看Indy通道的实现.客户端的通信实现起来毕竟比较简单,关键还是要看看服务端.