TServerRequest features
by Erick Engelke
August 31, 2024
TServerRequest is used to send HTTP or HTTPS requests to a server.
The requests are answered asynchronously because they may take some time, even several seconds, meaning there is a later upcall to your code when the transaction is complete, or there is a timeout or error.
The system will upcall one of:
-
OnComplete
-
OnError
-
OnTimeout
-
OnCancel
While the component is busy in the background, processing your request, you can do other activity, including GUI work or calling other TServerRequests, or call the request’s Cancel function to cancel the request at any time before completion.
Of note, you can queue a bunch of requests with TServerQueue which I talked about in another post. If so, TServerRequest’s OnStart upcall will be helpful to alert you that the particular request is starting.
Timeout
You can set the Timeout property to a given number of seconds for the connection to take place and the data to be returned. The default is a reasonable 30 seconds, setting it to zero means never time out, the minimum value is 1 second. EWB calles onTimeOut on a timeout.
Server EWB (also supports a ConnectTimeout value, which is the time for a TCP/IP connection to be made, independant of the total timeout which implies the data must be returned.
Sometimes, such as when you write Multi User games, you will want the timeout to be short enough, because the data doesn’t even make sense if delayed too long. But for normal processes, 30 seconds is long enough to handle most business cases.
Executing Request
After setting the URL, you call serverRequest.Execute;
I recommend calling ShowProgress('some message'); immediately following that if you want the user to know the network is busy. And call HideProgress in the OnComplete, OnTimeout and OnError handlers to clear the message.
Params
For most Get and other operations (execpt rmPOST), you can supply parameters on the URL line.
serverRequest.url := 'https://blah.com/proc.php?name=erick&id=32';
But you should use encodeURIcomponent() to convert many special characters likes space, colon, etc.
However, it’s a lot easier to use EWB’s built-in feature for Params.
serverRequest.url := 'https://blah.com/proc.php'
serverRequest.Params.Values['name'] := 'erick';
serverRequest.Params.Values['id'] := '32';
There is also a length issue, you cannot use large parameters - the exact length depending on the server, but they impose a restriction on how many characters.
Also note: parameters you pass in Params or on the URL line are stored in server logs, so you should never pass passwords this way.
Fortunately, POST methods solve these sorts of problems.
Post Content
Post requests can specify Request parameters in the URL as above (or using Params.Values which does the same thing more cleanly). But they also pass a Content which contains data the Post will read.
Most server languages let you access URL parameters in something like the Request variable, and the POST data in the Post variable. Read up on your preferred server environment for more details.
So, Post’s data are great:
-
they support more types of data, not just strings
-
they support very large data (often Megabytes or more depending on the server configuration)
-
they do not copy the parameters to log files
-
you can have multiple variables of the same name in an array
And Post methods return data, making them a decent replacement for Get or Put requests if you have any of the above concerns.
Storing data in the Post Content is slightly tricky. I like to store it in JSON format.
If you buy my Nice Library there are useful routines to create that Post JSON from a TDataSet. GetJSONof( TDataSet ) returns a string you use to set content. A second version, GetEWBJSONof() does the same thing, but handles time differently, it uses EWB comatible time Integer, whereas GetJSONof() uses ISO text time format suitable for PHP/SQL.
Here’s how you use them:
procedure TFormUWWebData.SaveData( req : TServerrequest; ds : TDataset ; url : string );
var
data : string;
begin
data := getjsonof( ds );
req.URL := url;
req.Method := rmPost;
req.Params.Values['userid'] := userid;
req.RequestContent.Text := data;
req.OnComplete := PostSaveData;
req.Execute;
ShowProgress('network');
end;
I regularly transfer PDF and JPEG or PNG file images which are megabytes long, storing them as text strings in a TDataSet field, both for upload and download.
Here is some sample code transferring a TDataSet with two records, each having two fields.
var
sr : TServerRequest;
begin
database.AutoTransactions := False; // avoid error
Dataset1.Open;
Dataset1.Insert;
Dataset1.Columns.Column['someinteger'].AsInteger := 52;
Dataset1.Columns.Column['somestring'].AsString := 'cool';
Dataset1.Save;
Dataset1.Insert;
Dataset1.Columns.Column['someinteger'].AsInteger := 58;
Dataset1.Columns.Column['somestring'].AsString := 'cooler';
Dataset1.Save;
cookies.set('test','1324', {maxage} -1, {path} '', {domain} '', {secure} False );
sr := TServerRequest.Create(self);
sr.Method := rmPost;
sr.URL := '1.php';
sr.Params.Values['name'] := 'Erick';
sr.Content.Text := GetJSONof( dataset1 );
sr.OnError := srError;
sr.OnComplete := srComplete;
sr.Execute;
end;
If I use my browser’s JavaScript console, Network tab, you can see that the Query String (ie. URL) parameters are listed as name: Erick
and if you click the Request Data link, it shows the JSON generated. Note that someinteger is a number, not a string. Much better than URL parameters which are always strings.
The server just needs to parse that JSON. The code needed depends on the language used for your server technology.
Here’s how you can do it in PHP
$passed = file_get_contents('php://input');
$data = json_decode( $passed );
$rows = $data->rows;
foreach ( $rows as $i=>$cur ) {
$someinteger = $cur->someinteger;
$somestring = $cur->somestring;
print "Got # $i: $someinteger, $somestring\n";
}
Setting Header Values
Sometimes you will need to pass Header values. This can achieve several goals:
-
identifying MIME content, discussed below, basically saying what you are sending, or what you can receive back
-
passing along some non-content data
-
meeting security restrictions - such as used for various authentication systems, such as OIDC (OpenID Connect), sometimes used with cookies
You set these by passing values like:
ServerRequest.Headers.Values['XXX-security']:='aj239fdlw;fz';
For most cases you don’t need to do this, but if the API you are calling expects some form of encrypted credentials to be passed in the headers, this is how it is achieved.
Setting the Mime Type
It’s good form to set the MIME type for your data so the server knows what you are sending.
ServerRequest.Headers.Values['Content-Type']:='application/json; charset=utf-8';
Cookies
Cookies are small lists of strings which are passed between client and server.
When you visit many sites, they will set a Session Cookie to give you an identity they can use to identify you.
For example, you first set up your Userid and credentials, the server may issue a cookie with a token that identifies your session so you don’t have to keep the password in memory, or keep sending it.
More advanced systems like ADFS, SAML and OIDC use cookies to identify your credentials in a way that can be shared among multiple servers, and individual servers never see your password. These are used in enterprise systems and federated systems.
An unfortunate use of cookies also tell Google and advertisers where you’ve been to give you focused advertising.
There are different types of cookies, ones visible to JavaScript and also protected ones hidden from JavaScript. Also, here are various conditions which can control when cookies are used, they can also be set by the browser or by the server. I encourage you to read up on Cookie technologies if you intend to use them.
If you issue a cookie, it’s a good idea to set the Domain and Path to limit cookies to only be sent to your intended server and path, and to set the MaxAge value to something which will timeout in a reasonable period of time.
The following example throws carefree to the wind, and sets a non-expiring, open-to-the-world cookie.
cookies.set('test','1324', {maxage} -1, {path} '', {domain} '', {secure} False );
You will need to include the WebComps unit to use them, setting the cookie before the first request which needs one.
Don’t declare your own cookie variable, use the system-wide one. Look at TCookie in the help for more details.
Also, don’t put a lot of data in cookies. They should usually be like a line or so of data. There is limited space shared among all sites in the browser cookie cache, and if you exceed it, NOTHING will work reliably until the cookie cache is flushed, or (the easy solution) the browser is restarted.
Summary
Hopefully you found new features you can use in your code. Some of these features will only be useful in certain circumstances, such as if you are communicating with an Enterprise type system or federated system. But note that EWB can be used in almost any situation where a JavaScript solution exists.