45

There's a web services I want to call in my application, I can use it with importing the WSDL or by just use "HTTP GET" with the URL and parameters, so I prefer the later because it's simple thing.

I know I can use indy idhttp.get, to do the job, but this is very simple thing and I don't want to add complex indy code to my application.

UPDATE: sorry if I was not clear, I meant by "not to add complex indy code", that I don't want add indy components for just this simple task, and prefer more lighter way for that.

Sahil Mahajan Mj
  • 11,033
  • 8
  • 53
  • 100
Mohammed Nasman
  • 10,992
  • 7
  • 43
  • 68

8 Answers8

34

Calling a RESTful web service using Indy is pretty straight forward.

Add IdHTTP to your uses clause. Remember that IdHTTP needs the "HTTP://" prefix on your URLs.

function GetURLAsString(const aURL: string): string;
var
  lHTTP: TIdHTTP;
begin
  lHTTP := TIdHTTP.Create;
  try
    Result := lHTTP.Get(aURL);
  finally
    lHTTP.Free;
  end;
end;
Bruce McGee
  • 15,076
  • 6
  • 55
  • 70
  • 5
    The question was to not add complex Indy code. The code isn't complex. – Bruce McGee Nov 19 '08 at 13:37
  • Bruce, Lars is right, I said in my questions I don't want to use indyt "idhttp.get", it's the simplest way but I wanted the lighted way :) – Mohammed Nasman Nov 20 '08 at 10:56
  • 1
    Your "lightest" option is to use external code, whether it's Indy, Synapse or WinINet. If you can't or won't use Delphi components or classes, use Delphi's wrapper functions around WinINet. Also external, but at least the DLL is installed with Windows. – Bruce McGee Nov 20 '08 at 11:35
  • 4
    In its defence, Indy ships with Delphi, but WinInet version and behaviour changes with installed IE version, which is like DLL hell. – Warren P Feb 03 '11 at 15:08
  • In my situation adding idHTTP made my exe jump from 300K to 2MB which doesnt always matter, but in my case did matter. – Toby Allen Oct 09 '13 at 14:24
  • @Toby: I'd be very curious to know why that is. What version of Delphi, and what kind of application (VCL/Console)? – Bruce McGee Nov 15 '13 at 13:21
  • @BruceMcGee I think it was Delphi XE . In Delphi 7 it was a bit of a jump maybe to about 800K but in XE much bigger. Console app. No idea why it made it so much bigger, a lot of code to compile in I suppose. – Toby Allen Nov 16 '13 at 20:35
  • 1
    @Toby: An empty console app is 40K in D7 and 83K in DXE. Adding IdHttp to the uses clause takes them to 139K and 739K respectively. I'm not sure where that 2MB comes from. It sounds like it could be a later version of Delphi compiled in Debug mode. – Bruce McGee Nov 17 '13 at 00:49
  • 1
    `TIdHTTP.Create(nil);` can be shortened to `TIdHTTP.Create;` with Indy 10.6 – mjn Aug 30 '14 at 11:57
29

You could use the WinINet API like this:

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;

source: http://www.scalabium.com/faq/dct0080.htm

The WinINet API uses the same stuff InternetExplorer is using so you also get any connection and proxy settings set by InternetExplorer for free.

Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
  • 1
    As bruce says, if you had an IE misconfigured to use a proxy setting that is down, for example, you would be waylaid, too. Obscure, and frustrating, if it happened. – Warren P Feb 03 '11 at 15:07
  • 1
    I have learned from hard experience now that you also get all the bugs in IE's wininet.dll that is installed on your client's system, for free, including the dread TIMEOUT bug. BE AWARE. – Warren P Jun 16 '11 at 14:16
  • @Warren: those get fixed in security updates and service packs, while your own code or anyone elses does not get the same critical review by so many eyeballs. – Dave Van den Eynde Aug 16 '11 at 11:31
  • And they get broken. Why are broken WinInet versions never fixed until you upgrade to a new version of IE? Terrible. Not fixed in a service pack unless IE itself is force-fed into a service pack. – Warren P Aug 16 '11 at 21:25
  • 3
    Note that WinInet should not be used in services. – Marc Durdin Dec 16 '15 at 05:21
  • Note that this code does not take character encodings into account. It assumes `char`/`string` are 8-bit and stores the raw response bytes *as-is* into the returned `string`. The response body has an encoding, which is specified/inferred from the HTTP response headers. And `string` has an encoding too, whether that be ANSI in D2007 and earlier, or UTF-16 in D2009 and later. The code needs to convert the response body data from the response's encoding to Unicode, and then from Unicode to `string`'s native encoding. – Remy Lebeau Jun 22 '18 at 19:28
  • 1
    Indy's `TIdHTTP` component handles those conversions for you when it returns a response body as a `string`. – Remy Lebeau Jun 22 '18 at 19:29
17

Actually code in accepted answer did't work for me. So I modified it a little bit so it actually returns String and gracefully closes everything after execution. Example returns retrieved data as UTF8String so it will work well for ASCII as well as for UTF8 pages.

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;

Hope it helps for somebody like me who was looking for easy code how to retrieve page content in Delphi. Cheers, Aldis :)

Aldis
  • 439
  • 4
  • 10
13

In newer Delphi versions it is better to use THTTPClient from System.Net.HttpClient unit, since it is standard and cross-platform. Simple example is

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;
EugeneK
  • 2,164
  • 1
  • 16
  • 21
7

If it's okay to download to a file, you can use TDownloadURL from the ExtActns unit. Much simpler than using WinInet directly.

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;

It's also possible to get progress notifications when using this. Simply assign an event handler to TDownloadURL's OnDownloadProgress event.

Toby Allen
  • 10,997
  • 11
  • 73
  • 124
Michael Madsen
  • 54,231
  • 8
  • 72
  • 83
5

Using Windows HTTP API might be easy too.

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;

In the code above I imply that COM was already initialized for the main VCL thread. Reportedly it might not be always the case for simplistic apps or for LCL apps. Also it would definitely not be the case for async (multithread) work.

Below is the snippet from a real code running. Note - the functionality is bonus. It is not required to work. So while I do issue requests, I do not care about their results, that result is ignored and dumped.

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;
Arioch 'The
  • 15,799
  • 35
  • 62
  • 1
    Yes, this is an excellent and simple solution but don't forget to call `CoInitialize(nil);` and `CoUninitialize` at start and end respectively. – supersan Mar 02 '16 at 08:26
  • 2
    That is mentioned in the links for the async thread case. In this specific example however the object operates within main VCL thread, where COM already was initialized. I suppose (though did not checked) same holds for LCL on Windows – Arioch 'The Mar 02 '16 at 08:48
  • 1
    Hi, yes. I mentioned it because my app gave me "Com not initialized or something error" when I used it (standard TForm with a button). Also for some reason when I shutdown the app it crashed after using the `CoInitialize` and `CoUninitialize` methods. Just FYI. – supersan Mar 02 '16 at 14:04
  • Was your "http" variable local? Could it be that you did not destroyed/cleared the variable before CoUnInitialize, so it retained pointer to the COM object after COM subsystem shut down? – Arioch 'The Mar 02 '16 at 14:20
  • In my specific case I needed to make calls to localhost service, so it would not block GUI and I did not needed any results at all, not even success/error status. So I just kept spawning TThread.Anonymous workers, that initialized and closed COM within themselves. Worked like a charm. – Arioch 'The Mar 02 '16 at 14:25
  • Hello, @supersan . A bit of necroposting here. Just stumbled upon a situation in Delphi XE2 when standard Open/Save dialogs just did not work. Primarily `PromptForFileName` function, but actually the whole subsystem. It turned out, in XE2 the save/open functionality is implemented via COM, and that function was called within a thread, so COM Not Initialized error was signaled by Windows and swallowed by VCL. However since `TOpenFileDialog` is a standard Delphi component and works simple and reliable in main VCL thread, that means COM gets auto-initialized in main thread somewhere – Arioch 'The Jan 29 '18 at 09:44
  • Just for reference to my prior comment: https://stackoverflow.com/questions/6719853/opendialog-does-not-show-up-in-delphi-multithreaded-application/48498788#48498788 – Arioch 'The Jan 31 '18 at 08:38
2

Use the Synapse TCP/IP function in the HTTPSEND unit (HTTPGetText, HTTPGetBinary). It will do the HTTP pull for you and doesn't require any external DLL's other than Winsock. The latest SVN release works perfectly well in Delphi 2009. This uses blocking function calls, so no events to program.

Update: The units are very light, and are not component based. The latest version from SVN runs perfectly well in Delphi XE4 also.

skamradt
  • 15,366
  • 2
  • 36
  • 53
0

If your application is Windows-only, I would suggest using WinSock. It's simple enough, allows to execute any HTTP request, can work both synchronously and asynchronously (using non-blocking WSASend/WSARecv with callbacks or good old send/recv in a dedicated thread).

user2024154
  • 199
  • 3
  • Although Winsock *can* be used directly for this, I would not recommend it. HTTP is a complex beast to implement from scratch manually. – Remy Lebeau Jun 22 '18 at 19:30