I must be overlooking something....
Why doesn't the NetHTTPClientRequestError get triggered here?
type
TUpdateChecker = class
private
var FHTTP : TNetHTTPClient;
var FAbort : Boolean;
procedure NetHTTPClientRequestError(const Sender: TObject; const AError: string);
public
constructor Create;
destructor Destroy; override;
function CheckNewVersion(var ANewVer: String): Boolean;
end;
{ TUpdateChecker }
constructor TUpdateChecker.Create;
begin
inherited;
FHTTP := TNetHTTPClient.Create(nil);
FHTTP.OnRequestError := NetHTTPClientRequestError; // Error handler assigned here
FHTTP.ConnectionTimeOut := 10000;
end;
destructor TUpdateChecker.Destroy;
begin
FHTTP.Free;
inherited;
end;
function TUpdateChecker.CheckNewVersion(var ANewVer: String): Boolean;
var
lJSONStr: String;
begin
Result := false;
try
lJSONStr := FHTTP.Get(cUpdateJSON + 'sdhgfy').ContentAsString; // Add some junk to cause error
except
on E:Exception do
begin
// We don't get at the breakpoint here
WriteErrorLog(TSettings.DataLocation + cErrLogFile, 'CheckNewVersion: ' + E.Message);
Exit;
end;
end;
if FAbort then Exit; // FABort = false here
// Rest of code removed
end;
procedure TUpdateChecker.NetHTTPClientRequestError(const Sender: TObject; const AError: string);
begin
// We don't get at the breakpoint here, FAbort never becomes true
ShowMessage('ERROR Sender.ClassName=' + Sender.ClassName + ' AError=' + AError);
FAbort := true;
end;
Calling code from a FormShow:
lCheck := TUpdateChecker.Create;
try
if lCheck.CheckNewVersion(lNewVer) then
begin
LblNewVersion.Caption := Format(LblNewVersion.Caption,[lNewVer]);
LblNewVersion.Visible := true;
LblUpgrade.Visible := true;
end;
finally
lCheck.Free;
end;
This is a Win32 app running on Win10.
cUpdateJSON is a valid URL to a JSON file on my website. I added the 'sdhgfy' junk to cause the error. My attention is to catch both 'common' HTTP status code like 500, 404, as well as exceptions.
Because :
lJSONStr := FHTTP.Get(cUpdateJSON + 'sdhgfy').ContentAsString;
is executing successfully.
Whatever cUpdateJSON is, it points to a valid server that is returning data to you, even though you have appended some garbage. It won't be the data you expect, but it will be data nevertheless, so the error is not raised.
You will need to validate the returned data to be sure that the server returned what you expected. NetHTTPClientRequestError will only handle cases where the URL specified fails to connect, etc (transport, socket, and protocol level exceptions). It knows nothing about whether or not the service on the other end was able to handle your specific request or not. It delivered your request successfully and the server returned a response. That's all it cares about.
If you want to check the server response you can inspect the StatusCode from the returned IHTTPResponse before saving its content into a string:
function TUpdateChecker.CheckNewVersion(var ANewVer: String): Boolean;
const
HTTP_OK = 200;
var
lResp : IHTTPResponse;
lJSONStr: String;
begin
Result := false;
try
lResp := FHTTP.Get('http://www.google.com/thiswontwork');
// Note by OP: *if* the NetHTTPClientRequestError gets triggered
// we have serious errors like e.g. invalid certificates
// and lResp.StatusCode will give an AV. Therefore:
if FAbort then Exit;
if lResp.StatusCode <> HTTP_OK then begin
// handle me!
end else
lJSONStr := lResp.ContentAsString();
except
on E:Exception do
begin
WriteErrorLog(TSettings.DataLocation + cErrLogFile,
'CheckNewVersion: ' + E.Message);
Exit;
end;
end;
if FAbort then Exit; // FABort = false here
// Rest of code removed
end;
Related
If sqlite3_open_v2 is commentted, it gives an error: unknown function C.sqlite3
which is strange, why should the prototype affect db :=C.sqlite3(0)?
But if it is commented then I get a bad parameter or other API misuse.
What am I doing wrong?
import sqlite
//fn C.sqlite3_open_v2(charptr, &&C.sqlite3, int, charptr) int
struct C.sqlite3_stmt { }
fn main(){
db := &C.sqlite3(0)
stm := &C.sqlite3_stmt(0)
db_path := ':memory:'
query := 'select 1'
C.sqlite3_open(db_path.str, &db)
err := C.sqlite3_prepare_v2(&db, query.str, -1, &stm, C.NULL)
if err != C.SQLITE_OK {
C.puts(C.sqlite3_errstr(err))
}
C.sqlite3_close(db)
}
You're importing sqlite without really using it. In the sqlite module, there are wrapper functions to do what you want. This is how you would do it:
import sqlite
fn main() {
db_path := ':memory:'
db := sqlite.connect(db_path) or {
panic(err)
}
query := 'select 1'
val, code := db.exec(query) // declare code as 'mut' so it can be reused later
assert code == 101 // 101 is the same as C.SQLITE_DONE
println('$val') // prints the following:
// [sqlite.Row{
// vals: ['1']
// }]
// No closing right now, not sure if it's in the works or needed
}
We have to include SNI (Server_Name_Indication) in our rest requests because the server will hosts several virtual domains in the near future. We are using a standard THTTPClient component created at runtime.
As soon as we add a new domain with new certificate we receive an exception
"Server certificate invalid or does not exist"
(translated from the german error text)
Is there a special header value to archive SNI this like for the basic authentication?
Below is a simplified version of our code (no logging, no proxy, fake URL). I apologize that I can not provide the real URL we are using.
We would expect that we must augment the GetRequest method, but did not find any example or hint.
unit Unit1;
interface
uses
Winapi.Windows, WinApi.WinHTTP, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
System.Net.HTTPClient;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
private
function PostRequest(const cRequest, cBody: string): string;
public
{ Public declarations }
end;
type
TMyRestClient = class(TObject)
private
fHTTPClient: THTTPClient;
fSysUrl: string;
fUserID: string;
fPassWD: string;
//...
function GetRequest(const cMethod, cUrl: string): IHTTPRequest;
public
constructor Create(const cUrl: string);
destructor Destroy; override;
function Post(const cRequest, cBody: string): string;
//...
property SystemUrl: string read fSysUrl write fSysUrl;
property Username: string read fUserID write fUserID;
property Password: string read fPassWD write fPassWD ;
end;
var
Form1: TForm1;
implementation
uses
System.SysUtils, System.Variants, System.NetEncoding;
{$R *.dfm}
constructor TMyRestClient.Create(const cUrl: string);
begin
fHTTPClient := THTTPClient.Create;
SystemUrl := cUrl;
end;
destructor TMyRestClient.Destroy;
begin
FreeAndNil(fHTTPClient);
inherited Destroy;
end;
function TMyRestClient.GetRequest(const cMethod, cUrl: string): IHTTPRequest;
begin
Result := fHTTPClient.GetRequest(cMethod, cUrl);
Result.AddHeader('content-type', 'appplication/json');
if (Password <> '') then
Result.AddHeader('Authorization', 'Basic ' + TNetEncoding.Base64.Encode(Username + ':' + Password));
end;
function TMyRestClient.Post(const cRequest, cBody: string): string;
var
iRequest: IHTTPRequest;
iResponse: IHTTPResponse;
aBody: TStringStream;
begin
iRequest := GetRequest('POST', Format('%s/%s', [SystemUrl, cRequest]));
aBody := TStringStream.Create(UTF8String(cBody));
try
iRequest.SourceStream := aBody;
iResponse := fHTTPClient.Execute(iRequest);
finally
aBody.DisposeOf;
end;
if (iResponse.StatusCode <> HTTP_STATUS_OK) then
raise Exception.CreateFmt('Post: %s failed (Code %d) %', [cRequest, iResponse.StatusCode, iResponse.StatusText]);
Result := iResponse.ContentAsString;
end;
function TForm1.PostRequest(const cRequest, cBody: string): string;
var
xClient: TMyRestClient;
begin
xClient := TMyRestClient.Create('www.myserver.com/demo');
try
// Basic authentification (not used)
xClient.Username := '';
xClient.Password := '';
Result := xClient.Post(cRequest, cBody);
finally
xClient.DisposeOf
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Edit1.Text := PostRequest('retrieve/paramvalue', '{''PARAM'':''VERSION''}' );
end;
I have a protected type in VHDL, which implements a initializer function or procedure.
Here is my code with an initializer procedure:
type T_SIM_STATUS is protected
procedure init;
procedure fail(Message : in STRING := "") ;
procedure simAssert(condition : BOOLEAN; Message : STRING := "") ;
procedure simReport;
end protected;
type T_SIM_STATUS is protected body
variable NotImplemented : BOOLEAN := TRUE;
variable Passed : BOOLEAN := TRUE;
procedure init is
begin
NotImplemented := FALSE;
end procedure;
procedure fail(Message : in STRING := "") is
begin
if (Message'length > 0) then
report Message severity error;
end if;
Passed := FALSE;
end procedure;
procedure simAssert(condition : BOOLEAN; Message : STRING := "") is
begin
if (condition = FALSE) then
fail(Message);
end if;
end procedure;
procedure simReport is
variable l : LINE;
begin
write(l, STRING'("SIMULATION RESULT = "));
if (NotImplemented = TRUE) then
write(l, STRING'("NOT IMPLEMENTED"));
elsif (Passed = TRUE) then
write(l, STRING'("PASSED"));
else
write(l, STRING'("FAILED"));
end if;
end procedure;
end protected body;
shared variable simStatus : T_SIM_STATUS;
Where should I call the init procedure?
My current solution calls init in a seperate process in the testbench's architecture body:
architecture rtl of test is
-- ...
begin
procInit : process
begin
simStatus.init;
wait;
end process;
procGenerator : process
begin
-- generate stimuli
wait;
end process;
procTester : process
begin
-- check results by using simStatus.simAssert
simStatus.simReport;
wait;
end process;
end architecture;
Are there better solutions?
Based on your code, and assuming effect of init should be made before any use of other procedures (methods) in the protected type, it looks like init procedure could be removed if NotImplemented if given an initial value of FALSE instead of TRUE.
Otherwise, if init is to be called first, just make sure that other uses of the shared variable are not called at time 0, in which case the call to init can be made as concurrent, thus without the process wrapper, but simply like:
simStatus.init;
If more complex setup must be done through a init call, then it could be called automatically when instantiating the shared variable if init is made as a function, that is then called from within the body of the shared variable, like:
type T_SIM_STATUS is protected
-- No init function is made public
procedure fail(Message : in STRING := "") ;
procedure simAssert(condition : BOOLEAN; Message : STRING := "") ;
procedure simReport;
end protected;
type T_SIM_STATUS is protected body
variable NotImplemented : BOOLEAN := TRUE;
variable Passed : BOOLEAN := TRUE;
... # Other code
impure function init return boolean is
begin
NotImplemented := FALSE;
... -- Or other more complex code
return TRUE;
end function;
variable dummy : boolean := init; -- Calling init with side effects
end protected body;
I am trying to return a limited type via a constructor function. I understand that because it is a limited type, I cannot copy the type but I am not sure of the best way to do it. I have it working using an extended return statement but I have been told that I should be able to return a limited type without it.
thing_protected.ads:
package Thing_Protected is
type Thing is protected interface;
procedure Verb_It (Object : in out Thing; Key : String) is abstract;
function Create return Thing'Class;
private
protected type Thing_Impl is new Thing with
overriding procedure Verb_It (Key : String);
private
Data : Integer;
end Thing_Impl;
end Thing_Protected;
thing_protected.adb:
package body Thing_Protected is
function Create return Thing'Class is
begin
-- Not sure how to make this work:
-- return Thing_Impl'(Data=><>, others=><>);
-- thing_protected.adb:6:35: expected type "Thing_Impl" defined at thing_protected.ads:10
-- thing_protected.adb:6:35: found a composite type
-- extended return:
-- return X : Thing_Impl do
-- null;
-- end return;
-- shortened version:
return X : Thing_Impl;
end;
protected body Thing_Impl is
overriding procedure Verb_It (Key : String) is
begin
null;
end;
end Thing_Impl;
end Thing_Protected;
main.adb:
with Thing_Protected;
procedure Main is
Thing_Instance : Thing_Protected.Thing'Class := Thing_Protected.Create;
begin
null;
end;
Hm, so you want to initialize Data? You could use generics/packages to do that... it's a little long and perhaps a little convoluted.
package Thing_Protected is
type Thing is protected interface;
procedure Verb_It (Object : in out Thing; Key : String) is abstract;
function Create return Thing'Class;
private
generic
Default : in Integer;
package Implementation is
protected type Thing_Impl is new Thing with
procedure Verb_It (Key : String);
private
Data : Integer:= Default;
end Thing_Impl;
Function Create return Thing'Class;
end Implementation;
end Thing_Protected;
package body Thing_Protected is
package body Implementation is
protected body Thing_Impl is
overriding procedure Verb_It (Key : String) is
begin
null;
end;
end Thing_Impl;
function Create return Thing'class is
begin
return Result : Thing_Impl do
null;
end return;
end Create;
end Implementation;
function K( Data_Val : Integer := 10 ) return Thing'Class is
Package I is new Implementation( Default => Data_Val );
begin
return X : Thing'Class := I.Create do
null;
end return;
end K;
function Create return Thing'Class is ( K );
end Thing_Protected;
I have been using Synapse for some time now, to send e-mails mainly. Today I am creating a simple installer, and trying to download the application exe file through HTTP. The file is about 9 MB in size, so I´d like to add a progress status to user, but I do not understand the examples I found. Here is what I got so far:
type
THookSocketStatus = Procedure(Sender: TObject; Reason: THookSocketReason; const Value: String) of Object;
CallBack = class
Class Procedure Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
end;
Class Procedure CallBack.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
var
V: String;
Begin
V := GetEnumName(TypeInfo(THookSocketReason), Integer(Reason)) + ' ' + Value;
Form1.mem1.Lines.Add(V);
application.ProcessMessages;
end;
procedure TForm1.btn1Click(Sender: TObject);
var
HTTP: THTTPSend;
MSTM: TMemoryStream;
begin
Screen.Cursor := crHourGlass;
HTTP := THTTPSend.Create;
MSTM := TMemoryStream.Create;
Try
Try
HTTP.Sock.OnStatus := CallBack.Status;
If HTTP.HTTPMethod('GET', edt1.Text) Then
Begin
MSTM.Seek(0, soFromBeginning);
MSTM.CopyFrom(HTTP.Document, 0);
MSTM.SaveToFile(ExtractFilePath(Application.ExeName) + 'test.exe');
end;
Except
end;
Finally
MSTM.Free;
HTTP.Free;
Screen.Cursor := crDefault;
end;
end;
In this simple test I got this result:
HR_SocketClose
HR_ResolvingBegin www.website.com:80
HR_ResolvingEnd 176.102.295.18:80
HR_SocketCreate IPv4
HR_Connect www.website.com:80
HR_WriteCount 158
HR_CanRead
HR_ReadCount 288
HR_CanRead
HR_ReadCount 8192
HR_ReadCount 8192
HR_ReadCount 8192
HR_ReadCount 6720
HR_CanRead
HR_ReadCount 3299
.
.
.
HR_ReadCount 8192
HR_ReadCount 8192
HR_ReadCount 7828
HR_SocketClose
HR_SocketClose
Please, what means WriteCount and ReadCount? How can I get total file size to set the progress bar before start the download?
Thank you guys!
I had the same problem and found a solution by extending the code above. The file length was available as suggested above by using the Header information.
Here is my code:
unit uhttpdownloader;
{$mode Delphi}{$H+}
interface
uses
Classes, SysUtils, httpsend, blcksock, typinfo;
//Interface for notifications about the progress
type
IProgress = interface
procedure ProgressNotification(Text: String; CurrentProgress : integer; MaxProgress : integer);
end;
type
{ THttpDownloader }
THttpDownloader = class
public
function DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
private
Bytes : Integer;
MaxBytes : Integer;
HTTPSender: THTTPSend;
ProgressMonitor : IProgress;
procedure Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
function GetSizeFromHeader(Header: String):integer;
end;
implementation
function THttpDownloader.DownloadHTTP(URL, TargetFile: string; ProgressMonitor : IProgress): Boolean;
var
HTTPGetResult: Boolean;
begin
Result := False;
Bytes:= 0;
MaxBytes:= -1;
Self.ProgressMonitor:= ProgressMonitor;
HTTPSender := THTTPSend.Create;
try
//add callback function for status updates
HTTPSender.Sock.OnStatus:= Status;
HTTPGetResult := HTTPSender.HTTPMethod('GET', URL);
if (HTTPSender.ResultCode >= 100) and (HTTPSender.ResultCode<=299) then begin
HTTPSender.Document.SaveToFile(TargetFile);
Result := True;
end;
finally
HTTPSender.Free;
end;
end;
//Callback function for status events
procedure THttpDownloader.Status(Sender: TObject; Reason: THookSocketReason; const Value: String);
var
V, currentHeader: String;
i: integer;
begin
//try to get filesize from headers
if (MaxBytes = -1) then
begin
for i:= 0 to HTTPSender.Headers.Count - 1 do
begin
currentHeader:= HTTPSender.Headers[i];
MaxBytes:= GetSizeFromHeader(currentHeader);
if MaxBytes <> -1 then break;
end;
end;
V := GetEnumName(TypeInfo(THookSocketReason), Integer(Reason)) + ' ' + Value;
//HR_ReadCount contains the number of bytes since the last event
if Reason = THookSocketReason.HR_ReadCount then
begin
Bytes:= Bytes + StrToInt(Value);
ProgressMonitor.ProgressNotification(V, Bytes, MaxBytes);
end;
end;
function THttpDownloader.GetSizeFromHeader(Header: String): integer;
var
item : TStringList;
begin
//the download size is contained in the header (e.g.: Content-Length: 3737722)
Result:= -1;
if Pos('Content-Length:', Header) <> 0 then
begin
item:= TStringList.Create();
item.Delimiter:= ':';
item.StrictDelimiter:=true;
item.DelimitedText:=Header;
if item.Count = 2 then
begin
Result:= StrToInt(Trim(item[1]));
end;
end;
end;
end.
The complete source code and example can be downloaded here as well:
http://andydunkel.net/lazarus/delphi/2015/09/09/lazarus_synapse_progress.html
Andy