currently I am able to run a command but i cant figure out how to get the result into a string.
I do a get like so
idhttp1.get('http://codeelf.com/games/the-grid-2/grid/',TStream(nil));
and everything seems to run ok, in wireshark i can see the results from that command. Now if i do
HTML := idhttp1.get('http://codeelf.com/games/the-grid-2/grid/');
it will freeze up the app, in wireshark i can see it sent the GET and got a response, but dont know why it freezes up. HTML is just a string var.
EDIT FULL CODE
BUTTON CLICK
login(EUserName.Text,EPassWord.Text);
procedure TForm5.Login(name: string; Pass: string);
var
Params: TStringList;
html : string;
begin
Params := TStringList.Create;
try
Params.Add('user='+name);
Params.Add('pass='+pass);
Params.Add('sublogin=Login');
//post password/username
IdHTTP1.Post('http://codeelf.com/games/the-grid-2/grid/', Params);
//get the grid source
HTML := idhttp1.Get('http://codeelf.com/games/the-grid-2/grid/');
finally
Params.Free;
end;
llogin.Caption := 'Logged In';
end;
RESPONCE
The responce i get says Transfer-Encoding: chunked\r\n and Content-Type: text/html\r\n dont know if that matters.
Thanks
Indy has support for some types of streamed HTTP responses (see New TIdHTTP hoNoReadMultipartMIME flag), but this will only help if the server uses multipart/* responses. The linked blog article explains the details further and also shows how the Indy HTTP component can feed a MIME decoder with a continuous response stream.
If this is not applicable to your case, a workaround is to go down to the "raw" TCP layer, which means send the HTTP request using a TIdTCPClient component, and then read the response line by line (or byte by byte) from the IOHandler. This gives total control over response handling. Request and Response should be processed in a thread to decouple it from the main thread.
TIdHTTP.Post() returns the response data, you should not be calling TIdHTTP.Get() to retrieve it separately:
procedure TForm5.Login(name: string; Pass: string);
var
Params: TStringList;
html : string;
begin
Params := TStringList.Create;
try
Params.Add('user='+name);
Params.Add('pass='+pass);
Params.Add('sublogin=Login');
//post password/username
HTML := IdHTTP1.Post('http://codeelf.com/games/the-grid-2/grid/', Params);
finally
Params.Free;
end;
llogin.Caption := 'Logged In';
end;
Related
I would like to load png images from the web, but with the code below not all images do get correctly into the stream (while posting the url in a webbrowser, e.g. edge, does give the image correctly). For example, the first url in the code gives a loading bitmap failed error (in the stream FSize is only 14?), while the second url does not give a problem. Does anyone know how to download the first url correctly?
For this piece of code to work, a TButton and a TImage was put on a form.
System.Net.HttpClientComponent was added in the uses. I am using Delphi 10.3.3. fmx.
Thanks,
Gerard
procedure TForm1.Button1Click(Sender: TObject);
var ms: TmemoryStream;
httpCli: TNetHTTPClient;
url: string;
begin
httpCli := TNetHTTPClient.Create(nil);
ms := TMemoryStream.Create();
url := 'https://a.tile.openstreetmap.org/11/1050/674.png';
// url := 'https://upload.wikimedia.org/wikipedia/commons/d/d5/Japan_small_icon.png';
httpCli.Get(url,ms);
ms.Position := 0;
Image1.Bitmap.LoadFromStream(ms);
ms.free;
httpCli.free;
end;
The problem with the OpenStreetMap tile server is the UserAgent. You must change the default value to something acceptable by the server. I checked a number of possibilities and it looks like almost anything but default value of TNetHTTPClient works. See this Wikipedia article for details.
To do that, you need to add the line
httpCli.UserAgent := 'Delphi/4.0 (compatible; Delphi; HttpClient)';
My version of your code which includes HTTP status code checking is the following:
procedure TForm1.Button1Click(Sender: TObject);
var
ms : TMemoryStream;
httpCli : TNetHTTPClient;
resp : IHTTPResponse;
url : String;
begin
httpCli := TNetHTTPClient.Create(nil);
try
httpCli.UserAgent := 'Delphi/4.0 (compatible; Delphi; HttpClient)';
ms := TMemoryStream.Create();
try
url := 'https://a.tile.openstreetmap.org/11/1050/674.png';
resp := httpCli.Get(url, ms);
if resp.StatusCode <> 200 then
Memo1.Lines.Add(Format('HTTP Error=%d %s',
[resp.StatusCode, resp.StatusText]))
else begin
ms.Position := 0;
Image1.Bitmap.LoadFromStream(ms);
end;
finally
ms.Free;
end;
finally
httpCli.Free;
end;
end;
When operating on HTTP you have to check if the HTTP server can satisfy your request (status code 200, as per RFC7231, § 6.3.1) or any error occured. In your case requesting that URI and making sure to see what comes from the server can be done with i.e. wget:
wget -S --content-on-error https://a.tile.openstreetmap.org/11/1050/674.png
This will print the server's response headers and in any case create a file that will hold the payload. The response headers are (excerpt):
HTTP/1.1 403 Forbidden
Content-Length: 14
Which means: you are not allowed to query that resource (HTTP status code 403). The payload is saved in a file sizing 14 bytes, containing this text in 14 ASCII characters:
Access denied.
Trivia: the smallest valid PNG file sizes at least 67 bytes (read "Smallest possible transparent PNG" by Gareth Rees.
This is a variation of fpiete's answer. I found this way simpler, with no need to create the TMemoryStream object, for example.
You can use the RESTRequest4D library for the request. The usage is very simple and I find it more intuitive than the regular HTTP client.
procedure TQRCodeForm.LoadImageFromURL;
var
LResponse: IResponse;
begin
LResponse := TRequest
.New
.BaseURL(ImageURL)
.Get;
if LResponse.StatusCode = 200 then
begin
MyTImage.Bitmap.LoadFromStream(LResponse.ContentStream);
end;
end;
I have seen a lot of examples online, but I cannot understand why my code doesn't work.
I have an url that looks like this:
http://www.domain.com/confirm.php?user=USERNAME&id=THEID
confirm.php is a page that does some checks on a MySQL database and then the only output of the page is a 0 or a -1 (true or false):
<?php
//long code...
if ( ... ) {
echo "0"; // success!
die();
} else {
echo "-1"; // fail!
die();
}
?>
My Delphi FireMonkey app has to open the URL above, passing the username and the id, and then read the result of the page. The result is only a -1 or a 0. This is the code.
//I have created a subclass of TThread
procedure TRegister.Execute;
var
conn: TIdHTTP;
res: string;
begin
inherited;
Queue(nil,
procedure
begin
ProgressLabel.Text := 'Connecting...';
end
);
//get the result -1 or 0
try
conn := TIdHTTP.Create(nil);
try
res := conn.Get('http://www.domain.com/confirm.php?user='+FUsername+'&id='+FPId);
finally
conn.Free;
end;
except
res := 'error!!';
end;
Queue(nil,
procedure
begin
ProgressLabel.Text := res;
end
);
end;
The value of res is always error!! and never -1 or 0. Where is my code wrong? The error caught from on E: Exception do is:
HTTP/1.1 406 not acceptable
I have found a solution using System.Net.HttpClient. I can simply use this function
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;
This works and gives me -1 and 0 as I expected. To get an example of a working code I have tested this:
procedure TForm1.Button1Click(Sender: TObject);
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;
function GetURLAsString(const aURL: string): string;
var
lHTTP: TIdHTTP;
begin
lHTTP := TIdHTTP.Create;
try
Result := lHTTP.Get(aURL);
finally
lHTTP.Free;
end;
end;
begin
Memo1.Lines.Add(GetURL('http://www.domain.com/confirm.php?user=user&id=theid'));
Memo1.Lines.Add(GetURLAsString('http://www.domain.com/confirm.php?user=user&id=theid'))
end;
end.
The first function works perfectly but Indy raises the exception HTTP/1.1 406 not acceptable. It seems that Indy cannot automatically handle the content type of the page. Here you can see the REST Debugger log:
HTTP Error 406 Not acceptable typically means that the server is not able to respond with the content type the client wanted. Both the Server and Client need to appropriately use the MIME type as you need. In this case, your client's Accept headers should provide the desired type of response, and your server should also be responding with the same. In your case, the Content-Type will most likely be text/plain.
So long story short, your client is expecting a MIME type which the server does not explicitly return in its response. The problem could be on either side, or perhaps both.
Your Client's Accept headers must provide the MIME type(s) you expect and need. Specifically Accept, Accept-Charset, Accept-Language, Accept-Encoding. By default in Indy TIdHTTP, these headers should accept essentially anything, assuming these headers haven't been overwritten. The Accept header is by default set to text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 where the */* opens the door for any MIME type.
Your Server's Response's Content-Type must be one of the provided MIME types, as well as the format of the response as also desired by the client. It is likely that your HTTP server is not providing the appropriate Content-Type in its response. If the server responds with anything in the */* filter (which should mean everything), then the client will accept it (assuming the server responds with text/plain). If the server responds with an invalid content type (such as just text or plain), then it could be rejected.
I am sending an HTTP Get request to Google's Map API, and I fill my StringStream with the response. However, when I try to read from the stream, I am just presented with an empty string ''.
{ Attempts to get JSON back from Google's Directions API }
function GetJSONString_OrDie(url : string) : string;
var
lHTTP: TIdHTTP;
SSL: TIdSSLIOHandlerSocketOpenSSL;
Buffer: TStringStream;
begin
{Sets up SSL}
SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
{Creates an HTTP request}
lHTTP := TIdHTTP.Create(nil);
{Sets the HTTP request to use SSL}
lHTTP.IOHandler := SSL;
{Set up the buffer}
Buffer := TStringStream.Create(Result);
{Attempts to get JSON back from Google's Directions API}
lHTTP.Get(url, Buffer);
Result:= Buffer.ReadString(Buffer.Size); //An empty string is put into Result
finally
{Frees up the HTTP object}
lHTTP.Free;
{Frees up the SSL object}
SSL.Free;
end;
Why am I getting an empty string back, when I can see that the StringStream Buffer has plenty of data (size of 32495 after the Get is called).
I've tested my call, and I am returned with valid JSON.
First, you are using TStringStream to receive the response data. If you are using Delphi 2009+, DO NOT do that! TStringStream is tied to a specific encoding that has to be declared in the constructor before the stream is populated with data, and it cannot be changed dynamically. The default encoding is TEncoding.Default, which represents the OS default encoding. If the HTTP response uses a different encoding, the data will not decode to a String correctly.
Second, you are not seeking the stream's Position back to 0 before calling ReadString(). An easier way to retrieve a TStringStream's content as a decoded String is to use the DataString property instead, which ignores the Position property and returns the entire stream content as a whole:
Result := Buffer.DataString;
Third, you are doing too much manual work. TIdHTTP.Get() has an overloaded version that returns a decoded String. The benefit of using this method is that it uses the actual charset of the response, rather than the charset of a TStringStream:
function GetJSONString_OrDie(const URL: string): string;
var
lHTTP: TIdHTTP;
SSL: TIdSSLIOHandlerSocketOpenSSL;
begin
{Creates an HTTP request}
lHTTP := TIdHTTP.Create(nil);
try
{Sets the HTTP request to use SSL}
lHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lHTTP);
{Attempts to get JSON back from Google's Directions API}
Result := lHTTP.Get(URL);
finally
{Frees up the HTTP object}
lHTTP.Free;
end;
end;
Which can be simplified further if you are using an up-to-date version of Indy (see this blog post for details):
function GetJSONString_OrDie(const URL: string): string;
var
lHTTP: TIdHTTP;
begin
{Creates an HTTP request}
lHTTP := TIdHTTP.Create(nil);
try
{Attempts to get JSON back from Google's Directions API}
Result := lHTTP.Get(URL);
finally
{Frees up the HTTP object}
lHTTP.Free;
end;
end;
Maybe first set Buffer.Position := 0?
In Delphi XE2, I am trying to upload the lines of a memo to a file on my webspace with IdHTTP.Put:
procedure TForm1.btnUploadClick(Sender: TObject);
var
StringToUpload: TStringStream;
begin
StringToUpload := TStringStream.Create('');
try
StringToUpload.WriteString(memo.Lines.Text);
// Error: HTTP/1.1 405 Method Not Allowed.
IdHTTP1.Put(edtOnlineFile.Text, StringToUpload);
finally
StringToUpload.Free;
end;
end;
But I always get this error message:
So what must I do to avoid the error and make the upload?
It means the HTTP server does not support the PUT method on that URL (if at all). There is nothing you can do about that. You will likely have to upload your data another way, usually involving POST instead, or a completely different protocol, like FTP.
BTW, when using TStringStream like this, don't forget to reset the Position if you use the WriteString() method:
StringToUpload.WriteString(memo.Lines.Text);
StringToUpload.Position := 0;
Otherwise, use the constructor instead:
StringToUpload := TStringStream.Create(memo.Lines.Text);
Thanks for the above code, here is perhaps a little more information with a little helper function to assist with that Stream constructor which I found works for any string you pass through, even it contains binary stuff.
//Helper function to make JSON string correct for processing with POST / GET
function StringToStream(const AString: string): TStream;
begin
Result := TStringStream.Create(AString);
end;
//somewhere in your code, I am posting to Spring REST, encoding must be utf-8
IdHTTP1.Request.ContentType := 'application/json'; //very important
IdHTTP1.Request.ContentEncoding := 'utf-8'; //which encoding?
response := IdHTTP1.Put(URL, StringToStream(body)); //response,URL,body are declared as String
I'm trying to access the URL Shortener ( http://goo.gl/ ) via its API from within Delphi.
However, the only result I get is: HTTP/1.0 400 Bad Request (reason: parseError)
Here is my code (on a form with a Button1, Memo1 and IdHTTP1 that has IdSSLIOHandlerSocketOpenSSL1 as its IOHandler. I got the necessary 32-bit OpenSSL DLLs from http://indy.fulgan.com/SSL/ and put them in the .exe's directory):
procedure TFrmMain.Button1Click(Sender: TObject);
var html, actionurl: String;
makeshort: TStringList;
begin
try
makeshort := TStringList.Create;
actionurl := 'https://www.googleapis.com/urlshortener/v1/url';
makeshort.Add('{"longUrl": "http://slashdot.org/stories"}');
IdHttp1.Request.ContentType := 'application/json';
//IdHTTP1.Request.ContentEncoding := 'UTF-8'; //Using this gives error 415
html := IdHTTP1.Post(actionurl, makeshort);
memo1.lines.add(idHTTP1.response.ResponseText);
except on e: EIdHTTPProtocolException do
begin
memo1.lines.add(idHTTP1.response.ResponseText);
memo1.lines.add(e.ErrorMessage);
end;
end;
memo1.Lines.add(html);
makeshort.Free;
end;
Update: I have left off my API key in this example (should usually work well without one for a few tries), but if you want to try it with your own, you can substitute the actionurl string with
'https://www.googleapis.com/urlshortener/v1/url?key=<yourapikey>';
The ParseError message leads me to believe that there might be something wrong with the encoding of the longurl when it gets posted but I wouldn't know what to change.
I've been fuzzing over this for quite a while now and I'm sure the mistake is right before my eyes - I'm just not seeing it right now.
Any help is therefore greatly appreciated!
Thanks!
As you discovered, the TStrings overloaded version of the TIdHTTP.Post() method is the wrong method to use. It sends an application/x-www-form-urlencoded formatted request, which is not appropriate for a JSON formatted request. You have to use the TStream overloaded version of the TIdHTTP.Post() method instead`, eg:
procedure TFrmMain.Button1Click(Sender: TObject);
var
html, actionurl: String;
makeshort: TMemoryStream;
begin
try
makeshort := TMemoryStream.Create;
try
actionurl := 'https://www.googleapis.com/urlshortener/v1/url';
WriteStringToStream(makeshort, '{"longUrl": "http://slashdot.org/stories"}', IndyUTF8Encoding);
makeshort.Position := 0;
IdHTTP1.Request.ContentType := 'application/json';
IdHTTP1.Request.Charset := 'utf-8';
html := IdHTTP1.Post(actionurl, makeshort);
finally
makeshort.Free;
end;
Memo1.Lines.Add(IdHTTP1.Response.ResponseText);
Memo1.Lines.Add(html);
except
on e: Exception do
begin
Memo1.Lines.Add(e.Message);
if e is EIdHTTPProtocolException then
Memo1.lines.Add(EIdHTTPProtocolException(e).ErrorMessage);
end;
end;
end;
From the URL shortener API docs:
Every request your application sends to the Google URL Shortener API
needs to identify your application to Google. There are two ways to
identify your application: using an OAuth 2.0 token (which also
authorizes the request) and/or using the application's API key.
Your example does not contain code for OAuth or API key authentication.
To authenticate with an API key, the docs are clear:
After you have an API key, your application can append the query
parameter key=yourAPIKey to all request URLs.