Delphi中调用Http GET url的最简单方法是什么?

45

我想在应用程序中调用一个Web服务,可以通过导入WSDL或者使用URL和参数的"HTTP GET"来使用它。因此,我更喜欢后者,因为它比较简单。

我知道我可以使用Indy IdHTTP.get完成这项工作,但这很简单,我不想在我的应用程序中添加复杂的Indy代码。

更新: 抱歉如果我表达不清楚,我所说的"不想添加复杂的Indy代码"是指我不想为了这个简单的任务而添加Indy组件,并希望有更轻量级的方法。


你认为仅使用HTTP GET就足以调用Web服务函数吗? - Serguzest
如果它是一个符合RESTful标准的Web服务,那就是这样。 - Bruce McGee
我不知道 REST 也可以有 WSDL。 - Serguzest
AhmetCiftci,正如我之前所说,该服务有“HTTP GET” URL可供调用,因此可以使用WSDL或仅请求“HTTP GET”。 - Mohammed Nasman
8个回答

34

使用Indy调用RESTful Web服务非常简单。

在您的uses子句中添加IdHTTP。请记住,IdHTTP需要在您的URL上添加“HTTP://”前缀。

function GetURLAsString(const aURL: string): string;
var
  lHTTP: TIdHTTP;
begin
  lHTTP := TIdHTTP.Create;
  try
    Result := lHTTP.Get(aURL);
  finally
    lHTTP.Free;
  end;
end;

5
问题是不要添加复杂的Indy代码。该代码并不复杂。 - Bruce McGee
1
你最“轻量级”的选择是使用外部代码,无论是Indy、Synapse还是WinINet。如果您不能或不想使用Delphi组件或类,请使用Delphi对WinINet的包装函数。这也是外部的,但至少DLL已经随Windows安装了。 - Bruce McGee
4
为其辩护,Indy与Delphi捆绑在一起,但WinInet版本和行为会随着安装的IE版本而改变,这就像DLL地狱。 - Warren P
1
@Toby:在D7中,一个空的控制台应用程序大小为40K,在DXE中则为83K。将IdHttp添加到uses子句中会使它们分别达到139K和739K。我不确定那2MB是从哪里来的。听起来像是以Debug模式编译的Delphi的较新版本。 - Bruce McGee
1
在 Indy 10.6 中,TIdHTTP.Create(nil); 可以简写为 TIdHTTP.Create; - mjn
显示剩余4条评论

29

您可以像这样使用WinINet API

uses WinInet;

function GetUrlContent(const Url: string): string;
var
  NetHandle: HINTERNET;
  UrlHandle: HINTERNET;
  Buffer: array[0..1024] of Char;
  BytesRead: dWord;
begin
  Result := '';
  NetHandle := InternetOpen('Delphi 5.x', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if Assigned(NetHandle) then 
  begin
    UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);

    if Assigned(UrlHandle) then
      { UrlHandle valid? Proceed with download }
    begin
      FillChar(Buffer, SizeOf(Buffer), 0);
      repeat
        Result := Result + Buffer;
        FillChar(Buffer, SizeOf(Buffer), 0);
        InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
      until BytesRead = 0;
      InternetCloseHandle(UrlHandle);
    end
    else
      { UrlHandle is not valid. Raise an exception. }
      raise Exception.CreateFmt('Cannot open URL %s', [Url]);

    InternetCloseHandle(NetHandle);
  end
  else
    { NetHandle is not valid. Raise an exception }
    raise Exception.Create('Unable to initialize Wininet');
end;

源代码:http://www.scalabium.com/faq/dct0080.htm

WinINet API使用与InternetExplorer相同的工具,因此您也可以免费获取由InternetExplorer设置的任何连接和代理设置。


1
正如Bruce所说,如果您的IE浏览器配置了使用一个不可用的代理设置,那么您也会受到阻碍。如果这种情况发生,可能会让人感到困惑和沮丧。 - Warren P
1
我从痛苦的经历中学到,您在客户端系统上安装的IE的wininet.dll中也会免费获得所有错误,包括可怕的TIMEOUT错误。请注意。 - Warren P
@Warren:这些问题会在安全更新和服务包中得到修复,而您自己的代码或其他人的代码则不会得到那么多眼球的重要审查。 - Dave Van den Eynde
3
注意,WinInet 不应在服务中使用。 - Marc Durdin
1
Indy的TIdHTTP组件在返回响应体作为string时会自动处理这些转换。 - Remy Lebeau
显示剩余2条评论

17

实际上,被接受的答案中的代码对我没有起作用。所以我稍微修改了一下,使它能够返回字符串并在执行后优雅地关闭所有内容。示例将检索到的数据作为UTF8String返回,因此对于ASCII和UTF8页面都能正常工作。

uses WinInet;

function GetUrlContent(const Url: string): UTF8String;
var
  NetHandle: HINTERNET;
  UrlHandle: HINTERNET;
  Buffer: array[0..1023] of byte;
  BytesRead: dWord;
  StrBuffer: UTF8String;
begin
  Result := '';
  NetHandle := InternetOpen('Delphi 2009', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  if Assigned(NetHandle) then
    try
      UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);
      if Assigned(UrlHandle) then
        try
          repeat
            InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
            SetString(StrBuffer, PAnsiChar(@Buffer[0]), BytesRead);
            Result := Result + StrBuffer;
          until BytesRead = 0;
        finally
          InternetCloseHandle(UrlHandle);
        end
      else
        raise Exception.CreateFmt('Cannot open URL %s', [Url]);
    finally
      InternetCloseHandle(NetHandle);
    end
  else
    raise Exception.Create('Unable to initialize Wininet');
end;

希望这对像我一样在寻找如何在Delphi中检索页面内容的简单代码的人有所帮助。 祝愿, Aldis :)


只有当响应实际上是使用ASCII或UTF-8编码时,此方法才有效,但这并不是一个保证。 - Remy Lebeau

13
在较新的Delphi版本中,更好地使用System.Net.HttpClient单元中的THTTPClient,因为它是标准并且跨平台的。简单示例如下:
function GetURL(const AURL: string): string;
var
  HttpClient: THttpClient;
  HttpResponse: IHttpResponse;
begin
  HttpClient := THTTPClient.Create;
  try
    HttpResponse := HttpClient.Get(AURL);
    Result := HttpResponse.ContentAsString();
  finally
    HttpClient.Free;
  end;
end;

这里会抛出哪些异常?难道你不需要一个except来处理HTTP错误吗? - undefined

7
如果可以下载到文件中,您可以使用ExtActns单元中的TDownloadURL。比直接使用WinInet简单得多。
procedure TMainForm.DownloadFile(URL: string; Dest: string);
var
  dl: TDownloadURL;
begin
  dl := TDownloadURL.Create(self);
  try
    dl.URL := URL;
    dl.FileName := Dest;
    dl.ExecuteTarget(nil); //this downloads the file
  finally
    dl.Free;
  end;
end;

使用此功能时,还可以获取进度通知。只需将事件处理程序分配给TDownloadURL的OnDownloadProgress事件即可。


5
使用Windows HTTP API 也许也很容易。
procedure TForm1.Button1Click(Sender: TObject);
var http: variant;
begin
 http:=createoleobject('WinHttp.WinHttpRequest.5.1');
 http.open('GET', 'http://lazarus.freepascal.org', false);
 http.send;
 showmessage(http.responsetext);
end;

在上述代码中,我假设COM已经为主VCL线程初始化。据报道,这可能不是简单应用程序或LCL应用程序的情况。同时,它肯定不适用于异步(多线程)工作。

以下是正在运行的真实代码片段。请注意-这只是额外的功能。不需要工作。所以虽然我会发出请求,但我不关心它们的结果,结果被忽略并转储。

procedure TfmHaspList.YieldBlinkHTTP(const LED: boolean; const Key_Hardware_ID: cardinal);
var URL: WideString;
begin
  URL := 'http://127.0.0.1:1947/action.html?blink' +
    IfThen( LED, 'on', 'off') + '=' + IntToStr(Key_Hardware_ID);

  TThread.CreateAnonymousThread(
    procedure
    var Request: OleVariant;
    begin
      // COM library initialization for the current thread
      CoInitialize(nil);
      try
        // create the WinHttpRequest object instance
        Request := CreateOleObject('WinHttp.WinHttpRequest.5.1');
        // open HTTP connection with GET method in synchronous mode
        Request.Open('GET', URL, False);
        // set the User-Agent header value
//        Request.SetRequestHeader('User-Agent', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0');
        // sends the HTTP request to the server, the Send method does not return
        // until WinHTTP completely receives the response (synchronous mode)
        Request.Send;
//        // store the response into the field for synchronization
//        FResponseText := Request.ResponseText;
//        // execute the SynchronizeResult method within the main thread context
//        Synchronize(SynchronizeResult);
      finally
        // release the WinHttpRequest object instance
        Request := Unassigned;
        // uninitialize COM library with all resources
        CoUninitialize;
      end;
    end
  ).Start;
end;

1
是的,这是一个非常好而简单的解决方案,但不要忘记在开始和结束时调用 CoInitialize(nil);CoUninitialize - supersan
2
这在异步线程案例的链接中有提到。然而,在这个具体的例子中,对象在主VCL线程中运行,其中COM已经被初始化。我认为(尽管没有检查),在Windows上LCL也是如此。 - Arioch 'The
1
嗨,是的。我提到这个是因为当我使用它(带有一个按钮的标准TForm)时,我的应用程序给了我“Com未初始化或其他错误”。另外,由于使用了CoInitializeCoUninitialize方法后,关闭应用程序时出现了崩溃的情况。仅供参考。 - supersan
你的“http”变量是局部变量吗?可能是因为在CoUnInitialize之前没有销毁/清除该变量,所以它在COM子系统关闭后仍保留了对COM对象的指针。 - Arioch 'The
你好,@supersan。这里有点“死帖回复”。我在 Delphi XE2 中遇到了一个问题,标准的打开/保存对话框就是不起作用。主要是 PromptForFileName 函数,但实际上整个子系统都不行。原来,在 XE2 中,保存/打开功能是通过 COM 实现的,并且该函数在线程中被调用,因此 Windows 发出了 COM 未初始化错误信号,并被 VCL 吞噬。然而,由于 TOpenFileDialog 是标准的 Delphi 组件,并且在主 VCL 线程中简单可靠地工作,这意味着 COM 在主线程中的某个地方被自动初始化了。 - Arioch 'The
显示剩余2条评论

2
在HTTPSEND单位中使用Synapse TCP/IP功能(HTTPGetText,HTTPGetBinary)。它将为您执行HTTP拉取操作,不需要除Winsock之外的任何外部DLL。最新的SVN发布版在Delphi 2009中完美运行。这使用阻塞函数调用,因此无需编程事件。
更新:单元非常轻巧,不是基于组件的。来自SVN的最新版本也可以在Delphi XE4中完美运行。

1
Indy 中的 HTTP 调用也是阻塞的。 - Bruce McGee

0
如果您的应用程序仅限于Windows,我建议使用WinSock。它足够简单,允许执行任何HTTP请求,可以同步和异步工作(使用非阻塞WSASend/WSARecv回调或在专用线程中使用send/recv)。

虽然Winsock可以直接用于此,但我不建议这样做。手动从头开始实现HTTP是一项复杂的任务。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接