Product and documentation by Aparajita Fishman
All trade names referenced in this document are the trademark or registered trademark of their respective holder.
Active4D is copyright Aparajita Fishman and Victory-Heart Productions.
4th DIMENSION, ACI, ACI US, and 4D Compiler are registered trademarks and 4D, 4D Server, 4D Client, and 4D Insider are trademarks of ACI/ACI US, Inc. or 4D, Inc.
Windows is a trademark of Microsoft Corporation.
Macintosh and Mac OS are trademarks of Apple Computer, Inc.
JavaScript and Java are trademarks of Sun Microsystems, Inc.
First of all, thanks to the makers of PHP for giving me the vision of a better way.
Thanks to the client who asked me to generate every single character of HTML in 4D code, which inspired me to come up with a better way.
Thanks to Mike Erickson for his ongoing inspiration and support and for convincing me that Active4D was worth doing. I hope he is right.
Thanks to Steve Willis and Jim Crate of Deep Sky Technologies for their invaluable assistance with the web server.
Thanks to all of the users for their feedback and encouragement.
And thank you to David Adams for so kindly including a chapter about Active4D in "The 4D Web Companion" and for allowing me to include his HTTP chapter with these docs.
Database and Protocol Support 5
Documentation Archive Contents 9
Key Files 10
Timeouts 11
OEM License 12
Starting a Database from Scratch 12
Installing into a Non-Active4D Database 13
Updating an Existing Active4D 2.0 Database 14
Moving an Active4D Database from 4D 6.5 to 4D 6.7+ 14
Post-Installation Configuration 15
Configuring 4D Client as a Web Server 16
Using the Pre- and Post-Execute Hooks 17
Libraries and the Active4D Directory 19
Path Format 20
The Standard Search Path and Path Lists 22
Active4D.ini 22
Realms.ini 24
Deprecated Plugin Configuration Commands 24
Security 27
Web Server Security = Source Code Security 27
The Whole Fortune 500 Can't Be Wrong 28
Executing/Accessing Non-Web Files 29
The "safe script dirs" Config Option 29
The "safe doc dirs" Config Option 30
The "auto create vars" Config Option 31
HTTP Server 33
Active4D + TCP Layer = Web Server 34
Active4D HTTP Request Handling 35
Executable vs. Non-executable Files 35
POST Handling with the 4D 6.5 Web Server 37
POST Handling With the 4D 6.7+ Web Server 37
POST Handling With Stream-based TCP Layers 37
File Upload Handling With 4D's Web Server 38
Executable Request Handling 38
Non-Executable Request Handling 40
Active4D.ini 41
Realms.ini 45
A4D Execute <type> request Parameters 51
Interpreter 61
Comments 65
Identifiers 66
Data Types 66
Literals 68
Operators 69
break 71
return 71
continue 71
exit 72
Examples 72
Absolute vs. Relative Paths 73
Path Limits 74
Indirect Method Calls (aka Poor Man's method pointers) 76
Collections 77
Local vs. Global Collections 77
Referencing Collection Values 78
Iterating Over a Collection 79
Request Data 80
Working with Character Sets 83
Methods 87
Method Name 87
Method Parameter Declaration 88
Scope 89
Referencing "Global" Local Variables 90
Libraries 95
Load Errors 97
The "lib dirs" Config Option 97
The "lib extension" Config Option 97
The "auto refresh libs" Config Option 99
The "refresh interval" Config Option 100
Differences from Active4D 1.0 100
Event Handlers 103
Modifying the Active4D Library 103
On Request 104
On Authenticate 105
On Session Start 106
On Session End 106
On Execute Start 107
On Execute End 107
4D Commands 109
4D 6.5 Commands 109
4D Command List 110
Command Syntax 115
add element 117
append to array 117
clear array 118
is array 118
join array 119
multisort arrays 120
resize array 121
SELECTION/SELECTION RANGE TO ARRAY 121
set array 122
collection 125
new collection 125
copy collection 128
clear collection 129
get collection 129
set collection 132
is a collection 134
collection has 134
get error page 148
get log level 148
in error 148
log message 149
set error page 149
set log level 150
The Importance of Filename Extensions 152
Uploads and 4D's Web Server 152
copy upload 153
count uploads 153
get upload remote filename 155
get upload size 155
Locking and Unlocking the Globals 163
globals 165
globals has 165
get global 165
get global array 166
get global item 167
get global keys 168
set global 168
set global array 169
count globals 169
delete global 170
lock globals 170
unlock globals 170
Using for each 171
Using Iterators 171
more items 173
next item 174
get item key 174
get item value 174
get item type 175
get item array 175
is an iterator 175
call 4d method 177
call method 177
choose 178
define 180
EXECUTE 182
global 182
import 183
include 184
include into 184
longint to time 185
redirect 186
require 186
throw 187
time to longint 187
Duplicate Query Parameters 196
query params 197
query params has 197
get query param 197
get query params 199
request cookies 203
buffer size 211
clear buffer 211
save output 212
end save output 213
write 216
write blob 217
writebr 218
writeln 218
writep 219
write raw 219
= 220
Cookie Fields 221
Limitations with the 4D 6.7 Web Server 221
response cookies 222
set response cookie domain 224
get response cookie expires 224
set response cookie domain 224
response headers 229
get expires 232
set expires 233
get expires date 233
set expires date 233
get content type 234
set content type 234
current platform 237
get license info 237
get version 238
parameter mode 239
Configuring Related Record Auto-loading 242
Compatibility with Active4D 2.0.x 243
Examples 243
auto relate 245
FIRST/LAST/NEXT/PREVIOUS RECORD 245
GOTO RECORD 246
The Active4D Session Architecture 247
Session ID 247
Session Events 248
When Active4D Sends Session Cookies 248
Session Lifetime 249
Memory Caching of Sessions 250
Session Timeout and Memory Usage 250
session 253
session to blob 253
blob to session 253
get session 254
get session item 255
get session keys 256
set session 256
session has 257
abandon session 259
session id 259
session local 260
session query 260
String Commands and Multibyte Character Sets 264
capitalize 265
cell 265
compare strings 266
concat 266
enclose 267
first not of 268
first of 268
last not of 269
last of 269
left trim 270
mac to html 270
param text 271
Position 273
right trim 273
split string 274
String 275
trim 275
url decode 276
url decode path 276
url decode query 276
url encode 277
url encode path 277
url encode query 277
Document Paths 278
Document Command Enhancements 278
Append document 280
Create document 280
current file 280
current path 281
DELETE FOLDER 282
directory of 282
extension of 283
filename of 283
get root 283
Open document 284
RECEIVE PACKET 284
requested url 285
SEND PACKET 285
Timestamp Format 287
Timestamp Time 287
Using Timestamps 288
timestamp 290
add to timestamp 291
timestamp string 291
timestamp date 292
timestamp time 292
timestamp year 293
timestamp month 293
timestamp day 294
timestamp hour 294
timestamp minute 295
timestamp second 295
auth password 297
auth type 297
auth user 297
authenticate 298
current realm 298
defined 300
get local 301
local variables 301
set local 301
type descriptor 302
undefined 302
variable name 303
Enter the RowSet 317
Using RowSets 319
Debugging 323
a4d_web 323
dump array 324
dump collection 324
dump session 325
Displaying the Session Monitor 329
The Active4D Debugging Console 332
Using the Debugging Console 332
Filtering Console Messages 333
Error Handling 335
The "set error page" Command 337
Log Levels 338
Named Constants 351
Alphabetical 353
Welcome to Active4D! You have chosen the ultimate environment for building world-class web applications with 4D.
A dynamic web scripting environment has four layers: a TCP communications layer, an HTTP server layer, a scripting layer, and a database layer. The different layers can be pictured like this:
The HTTP server builds on top of the TCP layer, and the scripting layer builds on top of the HTTP server. The database is in a separate, independent layer.
Active4D is both an HTTP web server and a server-side HTML-embedded scripting language and development environment.
That's a mouthful. Let's break down the sentence and look at what it means.
An HTTP (HyperText Transfer Protocol) web server is a piece of software that receives HTTP requests, processes those requests and sends an appropriate response to the origin. In essence, this is what a web server does.
4D's built-in web server handles TCP communications and handles most of the HTTP protocol.
Internet Toolkit , better known as ITK, is just what it says -- not an HTTP server, but a toolkit that allows you to write an HTTP server. ITK comes with samples which implement a web server well for most purposes.
HTTP Server Deux is a 4D component that implements a full-fledged HTTP server on top of 4D Internet Commands or ITK.
Active4D implements a full-fledged HTTP server in a plugin. As an HTTP server, it offers:
Scripting languages like JavaScript and Java download their source code to the client browser and execute it there. Active4D, on the other hand, is executed on the server and the source code is removed before the page is sent to the client.
Before the advent of Active4D, to create a dynamic web site using 4D you had to generate all or part of the HTML within 4D methods.
Active4D turns that model inside out -- dynamic HTML generation is directly embedded inside the HTML page.
4D's semi-dynamic tag system, while providing some of the benefits of embedded scripting, still requires you to write many, many 4D methods to handle simple tasks like queries and ordering. In Active4D this is handled directly within the web page. In fact, with Active4D all of your application code can (and in most cases should) exist entirely outside of 4D itself.
To generate dynamic HTML you must have a programming language. Active4D is a full 4D interpreter in a plugin. To program in Active4D you don't need to learn a new syntax or specialized tag language. In many cases you can literally copy 4D code from a method and paste it into a web page to be executed by Active4D.
Active4D supports all 4D data types except for 2D arrays. It implements over 150 of the most important 4D commands, as well as over 200 new commands which add unmatched power to your web application.
Active4D also adds several often-requested extensions to the language, such as the break and return keywords, as well as pass-by-reference.
Active4D comes with a plethora of built-in debugging tools, both on the client side and on the 4D side. In conjunction with all of the other features Active4D provides, there simply is no more powerful or more productive web development environment for 4D, period. Nothing else comes close.
The fully dynamic, embedded scripting implementation in Active4D is the cutting edge of dynamic web technology. And by using this approach you are in good company. Here are just a few little web sites that use fully dynamic embedded scripting:
barnesandnoble.com
capitalone.com (Visa)
compaq.com
dell.com
deltaairlines.com
firstusa.com (Visa)
gateway.com
geico.com (insurance)
homedepot.com
macconnection.com / pcconnection.com
macmall.com
macwarehouse.com
microsoft.com
sears.com
staples.com
walmart.com
My research shows that over 75% of the Fortune 500 uses dynamic embedded scripting languages in their corporate, client and e-commerce web sites. And the numbers are growing.
Here's a simple example of what an Active4D page looks like:
Note that the code is fully embedded in the page, and that except for the writebr command, which writes an expression to the HTML page, the embedded code is 4D code.
During execution, Active4D passes straight HTML through, executes everything inside the <% %> tags, and spits out the following:
The source code has been replaced with the output of the Active4D code. You can find out more about programming with Active4D in See Interpreter
Active4D can do anything that 4D can do in the context of a web page, plus quite a few tricks that would be time-consuming if not impossible to implement in 4D.
If Active4D's commands don't fit the bill, you can write methods and libraries of methods completely within Active4D, using plain text files. And if you need to use a command or plugin that is not in Active4D's language, you can call any 4D method, passing parameters of any type and receiving a result of any type.
Primarily, however, Active4D is designed to act as a conduit between the database and the web page. As such, it excels at the following essential tasks:
Active4D comes with built-in support for 4D's built-in database engine. You could, without much work, write wrapper methods for the various connectivity plugins that would allow you to work with other databases such as Sybase and Oracle.
In addition, you could also easily write method wrappers around the 4D Internet Commands to allow you to access other internet protocols like SMTP.
Several years ago I had the occasion to use another dynamic embedded scripting language called PHP. I was amazed at the power it provided, and more importantly I realized the advantages of the embedded scripting model.
A little later I was hired to work on a vertical market, web-based application that used 4D. I was asked to add a new module to the existing application. To my amazement, this application (which used NetLink) generated every single character of HTML programmatically in a 4D method. This was before 4D offered its web tag system.
Having been exposed to embedded scripting, I quickly saw that this was a fundamentally flawed approach, and I said to myself, "There has to be a better way." So I set about creating one.
I never intended to create Active4D -- it just sort of happened. A few months after I began with a simple 4D-based parser that replaced variable and field names, I had a full working 4D interpreter in a plugin.
A few features and a few months later, Active4D 1.0 was released. That was November of 2000. Since then I had a chance to use Active4D heavily in web projects, and decided it was time to fill in all of the holes in its feature set.
I systematically went down the list of every important feature in the world's leading embedded scripting language, Microsoft's Active Server Pages, also known as ASP (from which Active4D takes its name), and implemented each and every one. And wherever possible I added a few features that ASP doesn't have.
The result is what you see here: Active4D 2.0, the ultimate 4D web environment.
First you must of course download the appropriate files from http://aparajitaworld.com/products/Active4D/downloads.html . Because Active4D supports a number of different platforms and environments, the downloads are organized so that you can easily mix and match just what you need.
There are four elements to Active4D:
Once you have downloaded one of the above archives, you will need to decompress it. Mac archives are in Stuffit 5 format, so you will need Stuffit Expander 5 or better to decompress them. Stuffit Expander is free and readily available from www.aladdinsys.com . Windows archives are in Zip format. There are millions of free Zip expanders available for Windows.
Once you decompress the plugin archive, you will find a single folder.
This folder contains the Active4D plugin itself, which must be placed either in a local or shared MAC4DX folder (on Macintosh) or WIN4DX folder (on Windows).
When you download the ITK or TCP Server Deux shells, there will be other files and plugins which support those shells. They should be placed together with Active4D in a 4DX folder.
Once you decompress the shell archive, you will find a number of files and folders.
The main folder contains a complete working shell which illustrates how to integrate Active4D with either 4D's web server, ITK, or TCP Server Deux. In addition, the shell contains the forms and methods which implement the server-side debugging features of Active4D.
This folder contains libraries and config files which are used by Active4D.
For more information on libraries, see See Libraries For more information on config files, see See Configuration
This folder contains plugins which are necessary to use Active4D (in addition to the Active4D plugin).
This purposely empty folder is a critical security device which is explained in See Circumventing Active4D.
The documentation archive contains three important documents:
Active4D runs in one of several modes, depending on the license currently in force. The license is determined by a key file (or lack thereof), which contains information about you and your license.
Active4D will look for a key file in two places, in the following order:
If you have a key file in both places, the one in the plugin directory will take precedence.
Key files contain the following information in encrypted format:
You can access this information both through 4D ( A4D GET LICENSE INFO ) and through Active4D ( get license info ).
Each license has certain limitations of which you should be aware. The license types Active4D supports are:
|
Tied to a single IP address, never times out on that address, times out after one week on another address |
|
The trial license and developer license both have a timeout period. When 4D has run continuously for the timeout period, Active4D becomes disabled. Thereafter any attempts to execute Active4D will return the text "Active4D has exceeded its time limit." At that point 4D must be restarted to reset the timeout and enable Active4D.
If Active4D cannot find a key file, or if the key file is incorrect in some way, Active4D operates in trial mode.
If Active4D finds a valid key file and the license type is Developer, Active4D operates in developer mode, unless the version check fails, in which case Active4D operates in trial mode.
If Active4D finds a key file and the license type Deployment, Active4D checks the IP address of the host machine against the IP address in the key file. If an exact match is found, Active4D operates in deployment mode with no timeout, unless the version check fails, in which case Active4D reverts to developer mode.
If the host machine is multihoming (supports more than one IP address on one physical machine), Active4D will check all of the host's IP addresses for a match.
If an IP address match cannot be made, Active4D reverts to expired mode , which times out after one week of continuous use. Until you get a new key file, you must restart 4D once a week.
Ideally you should obtain a new key file in advance if you know your application will be moved to a server with a different IP address. However, if you unable to secure a new key file before switching, this scheme allows you to operate Active4D for a full week after switching. During this time you should be able to obtain a new key file. Once you have a new key file, you must restart 4D to have Active4D run in Deployment mode once more.
If you are running Active4D on Server, checking the license status from Client will indicate the license is running in expired mode, since the IP address of the Client cannot match the Server IP address.
To check the license status of a Server-based key file, you must use the get license info command on a page served by Active4D running on Server. For more information on the get license info command, see See get license info.
There are several installation scenarios: starting a database from scratch, installing into a non-Active4D database, updating an existing Active4D 2.0 database, and moving an Active4D database from 4D 6.5 to 4D 6.7 or later.
The Active4D shell databases are perfect for use when you are starting a database from scratch, as they are ready to run. You just need to build your database on top of the shell appropriate to TCP/IP layer you plan to use (4D, ITK or TCP Server Deux).
The Active4D environment relies on a small set of 4D methods, lists, forms and styles, all of which have the prefix "A4D_" to prevent name conflicts with existing code (I hope).
There are three different builds of the Active4D shell to choose from, corresponding to three Insider groups in the distribution shell.
|
Depending on the TCP/IP layer you are using (4D, ITK or TCP Server Deux), there may be some extra configuration necessary if you are installing Active4D for the first time.
ITK's behavior as a web server can be configured using the 4D list "A4D_ITKConfig", which contains the following items:
By changing the numbers in the various items, you can change how Active4D's shell sets up ITK.
Ordinarily Active4D becomes inactive on 4D Client, since it assumes it is serving web pages from 4D Server. If you are using 4D Client as a web server, you must explicitly tell Active4D that you are doing so.
For some developers it is necessary to examine and perhaps modify a request before and after Active4D. The Active4D shells provide hooks to allow the developer to do so without changing the core shell methods.
The pre-execute hook allows you to inspect an incoming request. Within this hook you are given access to the raw request and are free to do whatever you want with it. If you decide to handle the requset yourself, you can prevent Active4D from handling the request by returning False from the hook. If you return True Active4D will handle the request normally.
|
Actually, there are two pre-execute hooks. The hook you will use depends on the TCP/IP layer you are using.
If you are using 4D's web server as the TCP/IP layer, you will use the hook method called A4D_PreOnWebConnectionHook . This method receives a copy of the requested URL, the first 32K of the request itself, and the remote address. Because the hook receives a copy of the request, any changes you make to your copy of the request will be ignored by Active4D.
If you are using ITK or TCP Server Deux as the TCP/IP layer, you will use the hook method called A4D_PreStreamExecuteHook . This method receives a stream reference which you can use to read and modify the request. Since Active4D uses the same stream reference, any changes you make to the request will be used by Active4D.
The A4D_PostExecuteHook method allows you to inspect and modify the response headers and response body returned by Active4D after it has executed, but before the response is sent. This method receives a pointer to the response header names and values (two parallel text arrays) and a pointer to the response body BLOB .
Within this hook you may make any changes you wish to the response headers or the response body, and those changes will be reflected in the response sent to the client.
Active4D ships preconfigured to meet most needs, but you can configure virtually every aspect of Active4D to fit your specific needs.
Before discussing the various configuration (or "config") files, you must know where to find them.
All config files must reside in a special directory called "Active4D". Active4D will look for the Active4D directory using the following search path:
This arrangement allows you to have multiple versions of config files, with those earlier in the search path overriding those later in the search path. This is analogous to how the 4DX plugin directory is handled by 4D.
So, for example, you could establish a baseline set of configuration options which you keep in the shared 4D directory, then override that configuration for a particular database by putting a separate config file in <database directory>/Active4D .
Active4D also follows the same search path when searching for libraries that are being imported. Thus the Active4D directory becomes a convenient place to put your custom libraries.
Because Active4D will follow the entire search path, you can structure your libraries in a hierarchy, from the most specific to the most general, by placing them at various points in the search path.
In addition, you can specify a list of library directories to search in the config file. For more information, see the documentation for the "lib dirs" setting in the sample Active4D.ini config file.
Active4D has four config files: Active4D.ini , ExtensionMap.ini , Realms.ini and VirtualHosts.ini . These are plain text files which may be created and edited on any platform, as Active4D will accept Mac, Windows and Unix line endings.
Sample files which should suit most needs are shipped with Active4D. Each file is heavily commented, so if this documentation is not handy you can always read the comments. It is recommended that you keep a copy of the original config files somewhere safe in case you need to go back to a baseline configuration.
Active4D periodically checks to see if the config files have been modified. The interval is set with the "refresh interval" option in Active4D.ini . If a config file has been modified, it is reloaded automatically, so there is no need to quit and restart 4D to have the changes take effect.
Some of the config files rely on the concept of the default directory . The default directory is the directory from which relative paths are resolved.
Under 4D Standalone or Server, the default directory is the current database directory (the one with the structure file). Under Client, the default directory is the Client application directory.
All paths specified in config files must be in URL format (also known as Unix format), which has the following attributes:
There are four tokens you may use at the beginning of a path to represent special directories. The tokens are:
|
The default directory, as defined in See The Default Directory |
|
The tokens include the `<` and `>', and are replaced with the directories they represent, without a trailing `/'. This allows you to create paths that are portable across machines and 4D directories.
For example, you may want to include files from a folder called "includes" outside of the web root, perhaps at the same level as the web root folder. To do so you would specify this path:
To help you understand how URL paths map to native Mac or Windows paths, here are a few samples of full paths in native format and URL format.
When Active4D searches for libraries and .ini files, it follows a standard search path :
<default directory>/Active4D
<shared 4D or ACI directory>/Active4D
<System:Preferences>/Active4D (Mac) or <Windows>/Active4D (Win)
Several config options allow you to specify a semicolon-delimited list of full directory paths to search in addition to the standard search path. If such a list is provided, it is searched after <default directory>/Active4D .
For example, to add two directories to the search path, you might use something like this:
/CKG/Development/Active4D/libs;/CKG/Development/Libs
Active4D.ini controls most of the behavior of Active4D and must be in an Active4D directory in the standard search path. There are over 30 options you can set in this file. Each option is in the form:
Case is not significant either for keys or values, and you can have any amount of white space surrounding the "=". You may also use comments just as you would in 4D, either on separate lines or at the end of a line.
The options in Active4D.ini are listed below. A detailed discussion of the options can be found in the chapter relating to the option.
This config file maps filename extensions to MacOS file creator and type codes, as well as to MIME types. It is used primarily by Active4D's HTTP server. For more information on this file, see See ExtensionMap.ini.
This config file specifies the mapping between realm names and portions of a URL that identify those realms. It is used primarily by Active4D's HTTP server. For more information on this file, see See User Authentication.
This config file provides a routing table for virtual hosting, which allows you to create multiple independent web sites on the same machine. For more information on this file, see See VirtualHosts.ini.
All of the plugin configuration commands present in Active4D 1.0, except for A4D Get root , have been deprecated (i.e. removed). They are:
A4D Get error page
A4D Get log level
A4D Get options
A4D Load library
A4D SET ERROR PAGE
A4D SET LOG LEVEL
A4D SET OPTIONS
You must now use the config file options in place of these commands.
Security is something you should worry about with any web site. When a web site has direct access to a mission-critical database, security is a mission-critical issue. Active4D provides several mechanisms to ensure your site is as secure as possible.
When you build a web site with Active4D, all or part of your source code is on the web server in plain text form.
This does not mean your source code is not secure.
In determining the potential security risk, you must keep in mind these two important facts:
If an attacker compromises the security of your web server, your source code is at risk -- but then, so is everything else on the web server, including the database.
If the attacker is bent on being destructive, there are far easier and more effective ways to wreak havoc than to change the embedded source code in a web page. And it is highly unlikely that the hackers of the world know the 4D language.
Active4D always strips out source code before sending a page to the client. But what if the page is served directly without passing through Active4D?
It is up to you to ensure that every request is passed to Active4D.
If you are using 4D's built-in web server as the TCP layer, there is a potential risk. Active4D is only invoked through the On Web Connection mechanism. On Web Connection is only invoked when the user requests a URL that does not exist. URLs that reference existing files are served directly by 4D without invoking On Web Connection . In addition, the URL must be prefixed with "/4DCGI" for form variables to be passed into On Web Connection .
For example, let's suppose the user is given this URL:
www.myserver.com/4DCGI/mypages/mypage.a4d
Let us further suppose that this page exists within a directory called "web" within the database directory, and the 4D web server's default HTML root is set in the Database Properties dialog to "web". If the user then enters the same URL without "/4DCGI", like this:
www.myserver.com/mypages/mypage.a4d
4D will directly serve that page, complete with embedded source code, without going through On Web Connection . On the other hand, if 4D's default HTML root is set to nothing, the user could still access the source with this URL:
www.myserver.com/web/mypages/mypage.a4d
It is critical that you create an empty "decoy" directory in your database directory and set 4D's default HTML root to that folder, then set Active4D's root directory to something else. By doing this, you ensure that all URLs without "/4DCGI" will be to non-existent files, thus forcing 4D to route the request through On Web Connection and ultimately to Active4D.
If you are using 4D's web server with Active4D and have not read through and followed the installation instructions in See Configuring for 4D, please do so now!
Take a look around the web at just about any major corporation that provides some sort of dynamic functionality -- banks, airlines, credit card companies, online retailers, etc. You will find the vast majority have page names that end in ".asp" or ".jsp". This means they are using either Microsoft's Active Server Pages or Sun's Java Server Pages, both of which embed the source code in the web page, just as Active4D does.
These companies have a lot to lose if their embedded source code is stolen or altered. Yet they continue to use embedded scripting, because they have realized that the issue is not source code security, but web server security.
Even if a miscreant does not succeed in hacking into your web server, there are still many opportunities for mischief. For an excellent essay on the security issues arising from a web-based scripting language, I recommend reading the following:
http://www.securereality.com.au/studyinscarlet.txt
If you do read this essay, please note that Active4D addresses almost every potential attack outlined there.
One potential source of attack is to allow access to files outside of the web site itself. Ordinarily, Active4D requires that any requested file be within the web root directory or a subdirectory thereof. This includes files that are executed with the include or require commands.
If an attempt is made to execute a file in an unauthorized directory, Active4D aborts execution and returns the status code 403 Forbidden along with a message indicating the error.
If you want to execute or include script files in a directory outside of the root directory, you can provide a list of allowable script directories with the "safe script dirs" option in Active4D.ini .
If an attempt is made to execute a file outside of the root directory, this path list is checked. If the file lies within one of the specified directories, the execution is allowed to proceed. Otherwise the behavior is as specified above for unauthorized directories.
Let us suppose that you have a script that deletes a file, and that the path to the file is kept in the session. An attacker could potentially force the session to contain a path to a critical system file outside of the web root directory.
To prevent this kind of attack, the path passed to all document commands ( Open document , DELETE DOCUMENT , etc.) is checked to ensure that the path is within the web root directory or a subdirectory thereof.
If an attempt is made to use a document command on an unauthorized directory, Active4D does the following:
As a result of this behavior, you should at least check the OK variable when using any document commands.
If you want to use document commands on files outside of the root directory, you must add the directory path to the "safe doc dirs" path list in Active4D.ini . For information on Active4D.ini , see See Configuration
If a document command is given a path outside the root directory, this path list is checked. If the path lies within one of the specified directories, the command is allowed to proceed. Otherwise it behaves as specified above for unauthorized directories.
Suppose you have a form which posts some critical piece of information in a hidden form variable called "f_id". In the form processing script, you ordinarily would access the local variable called $f_id to get the hidden value, then use this ID to perform some critical operation.
An attacker could execute the form processing script, passing in a query string with a parameter "f_id" and some bogus value. Because Active4D ordinarily creates local variables from both query parameters and form variables, your script would have no way of knowing that the local variable $f_id did not come from a hidden form variable. Depending on what you do with $f_id , this could present a potential security breach.
To prevent variable spoofing attacks, you can use the "auto create vars" option in Active4D.ini to turn off the creation of local variables from query parameters and form variables. In your form processing script, you would use the get form variable command to get the value of the "f_id" field.
There are two factors to take into account before going to this length.
If you have a web page which provides file upload, an attacker could attempt to upload a huge file. Most web servers buffer the request, thus it is possible to compromise the stability of the web server by forcing it to run out of memory.
Active4D provides an option called "max request size" in Active4D.ini , which limits the total size of the request Active4D will accept. By setting this option at something reasonable for your particular application (the default is 64KB), you can prevent the attack outlined above.
If a request is received which is larger than <max request size> bytes, Active4D aborts execution and returns the status code 413 Request Entity Too Large , along with an error message indicating that fact.
You may remember from the introduction the diagram of the various layers that make up a web scripting system. That diagram is reproduced here.
Active4D 1.0 was purely a text processor which occupied the scripting layer, leaving the implementation of the two lower layers to the host environment. This usually meant that the developer had to do a lot of work to implement the HTTP server.
Both the HTTP server layer and scripting layer are handled fully by Active4D.
You may be wondering how Active4D differs from 4D's built-in web server or from dedicated web servers like WebStar.
Traditionally a web server handles the following tasks:
Active4D relies on the host to provide TCP/IP communications. Other than that, Active4D handles all of the task listed above, right out of the box, with no programming required. And because Active4D is the web server, you have full access to every aspect of both the HTTP request and the HTTP response from within Active4D.
Believe me, implementing a web server with all of those features using other tools is not a trivial exercise!
The Active4D Shell comes preconfigured to use 4D's web server, which is the preferred environment in which to run. If you already are using ITK or TCP Server Deux (4D 6.7+ only) and wish to use them with Active4D, they can be used as the TCP layer as well.
In general, 4D's built-in web server is the best TCP layer to use, as it requires no extra installation or methods and it will give you the best performance. However, there are a few issues with 4D's built-in web server which may affect you. It is unlikely they will, but just in case it is important to be aware of them.
With 4D 6.5's built-in web server, the total size of the request is limited to 32K. This may cause problems if you allow posting of forms with multiline text fields, as the user may paste more than 32K of text in the field. This also precludes the uploading of files, as the request size could easily exceed 32K.
If the request is larger than 32K, neither 4D nor Active4D would crash, but the request will not be handled properly. The best solution to this issue is to upgrade 4D to at least 6.7.
With any version of 4D's built-in web server through version 6.8, file uploads are a potential problem, as 4D will try to load the entire uploaded file into memory with no limit on its size. Uploading a very large file may cause the server to crash if it runs out of memory. This problem does not exist in 4D 2003 or higher, as Active4D takes advantage of its ability to limit the size of a request.
If you cannot use 4D 2003, and you must allow file uploading, and you do not have enough memory to handle a potential large upload, then you may want to consider another TCP layer in place of 4D's built-in web server.
If you must use another TCP layer and you are using 4D 6.5, then you must use ITK. If you are using 4D 6.7 or greater, you may use either ITK or TCP Server Deux as the TCP layer, but TCP Server Deux is recommended as it provides a very robust threading model and better performance than ITK alone.
To understand the functioning of a web server, it really pays to understand how HTTP requests work. David Adams has been kind enough to allow me to include Chapter 47 of his most excellent book, The 4D Web Companion. Before going on, I recommend taking the time to read through this excellent introduction to HTTP, which is included in the Active4D documentation distribution as a separate PDF document, "HTTP Fundamentals.pdf."
Note that the descriptions below will refer to collections , which are special data structures used throughout Active4D. It is enough for now to know that a collection is an associative array of key and values. For more information on collections, see See Collections.
At any step of the request handling, if Active4D detects either corrupt data or data that does not conform to the HTTP specification, the status 400 ( Bad Request ) is returned immediately.
When Active4D is asked to serve a file, it first checks the file's extension against a list of executable extensions.
There are several reasons for determining if a file is considered executable or not:
The first step in handling a request is to retrieve and parse the request header.
If the file is considered executable, what occurs next depends on the TCP layer used in conjunction with Active4D.
When 4D is used as the TCP layer, any uploaded files go in BLOB variables that should have been declared in the Compiler_Web method. These variables must have the same name as the form variable used to upload the file.
By default, Active4D operates as a traditional web server serving static content. If you would like to serve static content yourself, you can do so by changing the "serve nonexecutables" option in Active4D.ini . Active4D makes it easier by providing you with the full native path and modification date/time of the requested file.
The Active4D web server is configured through the four config files, Active4D.ini , ExtensionMap.ini , Realms.ini and VirtualHosts.ini .
There are several config options in Active4D.ini that determine the basic behavior of the web server. For information on the format of config options, see See Configuration
The default cache control for executable files. Values should be valid directives to the "Cache-Control" HTTP header, as specified in RFC 2616, section 14.9. This value can be changed at runtime with the set cache control command.
The default content charset. Values should be standard ISO charset names, which can be found at http://www.iana.org/assignments/character-sets . This value can be changed at runtime with the set content charset command.
The default value for this option is "ISO-8859-1" on Roman language systems, "Shift_JIS" on Japanese language systems, and "GB2312" on Chinese language systems.
If a client requests a URL which specifies a directory, a default page within that directory is executed. This preference determines the name of the default page. The name must be a simple filename with no directory components.
The default value for this option is "default.a4d".
|
A list of file extensions (with leading dots) which Active4D will consider executable. The extensions may be separated by any amount of whitespace. Each extension must begin with `.' followed by a maximum of five alphanumeric characters.
There is no point in having Active4D parse and attempt to execute files which it cannot, such as graphics and static HTML pages. This list determines which files are executed by Active4D and which are not.
What Active4D does with non-executable files depends on the "serve nonexecutables" option.
The number of minutes before an executable page should expire on the client browser. Setting to zero forces pages to expire immediately. Setting a negative value will cause this value to be ignored by Active4D. Positive values are clipped to one year (in minutes). This value can be changed at runtime with the set expires command.
The maximum size in kilobytes (K) of a request, including the headers. Requests are buffered in memory, so if files are being uploaded they will at some point be completely in memory. To prevent the user from uploading extremely large files and thus potentially crashing the server, you should set this value to something reasonable for the type of file you expect to be uploaded. Values less than 32 (K) are ignored. If the post exceeds this size, the status 413 Request Entity Too Large is returned.
The default value for this option is 64 (K).
Determines which collection query string parameters and form variables go into. There are three possible modes: "separate", "query params", and "form variables".
In "separate" mode, query string parameter and form variables are maintained in separate collections. In "query params" mode, everything is put into the query params collection. In "form variables" mode, everything is put into the form variables collection.
Why do this? Because many scripts can be executed either through a POST or a GET . In either case the parameters they "receive" should be the same. If you manipulate the query params or form variables collection, it is a pain to constantly have to check the request method to figure out which collection to modify. By setting the parameter mode, you can force all values to go into one collection or the other. Usually you will want to do this.
When Active4D is invoked via A4D Execute stream request , Active4D calls a receive callback to get the request from the TCP layer. This option controls the maximum number of ticks Active4D will wait to receive. Values < 600 (10 seconds) are ignored.
This option determines the default root directory for file access. All URLs are relative to this directory. In addition, no scripts may be executed or non-executable files served outside of this directory, unless the directory is specified in the "safe script dirs" option.
The path specified must be in URL format and may be either absolute or relative. If the path is relative, it will be relative to the default directory. The path must be valid at the time the config file is read.
The default value for this option is <default directory>/web .
|
For information on this option, see See The "safe script dirs" Config Option.
When a non-executable file is requested, this setting determines what Active4D does, as outlined in See Non-Executable Request Handling above.
When a web server sends a file to the client browser, it must inform the browser of the file's type. Similarly, when a client uploads a file to the web server, the web server receives the file's type. This type is sent and received as a MIME type . MIME types are standardized and can identify a file's type independently of its filename extension.
On Windows (and to some extent MacOS X) the filename extension is sufficient to set the type and associated creator application of the file. On the MacOS, however, every file has a creator and type which is independent of its extension. For example, you would expect a file with the extension ".sit" to have its type and creator set such that you can double-click it and have it be processed by Stuffit.
ExtensionMap.ini is a config file that allows Active4D's web server to map filename extensions to Macintosh file types and creators and to MIME types. For example, let us say that Active4D receives an upload file with the MIME type "image/gif". It looks into its extension map and finds an entry which says that the corresponding filename extension is ".gif" and the creator and type are "ogle" and "GIFf" respectively, which is the creator type for the QuickTime PictureViewer application.
Each entry in ExtensionMap.ini is in the form:
<extension><WS><Mac type><WS><Mac creator><WS><MIME type>
where <extension> is the filename extension (including the dot), <WS> is any number of contiguous whitespace characters, <Mac type> and <Mac creator> are four-letter type codes, and <MIME type> is a MIME type such as "image/gif". Thus the GIF entry mentioned above would look like this:
Active4D comes with an ExtensionMap.ini file which contains 59 entries covering most of the file types you are likely to come across. However, should you need to receive or send a file not on the list, you should add it to this config file.
Active4D provides full support for protecting portions of your web site against intruders by using the basic HTTP authentication protocol. Currently authentication only works with executable files.
For example, you may have an admin section of your web site that only administrators should have access to. Another section of your web site is only for accounting users. By defining realms -- sections of the web site defined by a hostname or substring of a requested file's path -- you can automatically be notified when a user's credentials need to be authenticated.
The user authentication mechanism requires three elements that work together:
For information on the On Authenticate event handler, see See On Authenticate. For information on the authenticate command, see See User Authentication.
This config file specifies the mapping between realm names and substrings of a path that identify those realms.
Each entry in Realms.ini has two fields in this format:
The realm is a logical name for the section of the web site you wish to protect. It need not have any relation to an actual directory name. Realm names should not have any extended characters or spaces. If there are extended characters they are ignored. Spaces are converted to underscores.
You use the realm name internally to determine what section of the web site is being accessed. The browser caches user credentials for each realm, so subsequent authorized accesses in the same realm do not result in repeated requests for credentials.
The match string is checked against the host name and the fully resolved path of the requested URL. If any portion of the host name or path matches the match string, the corresponding realm is made the current realm. Matching is case-insensitive, but the entire match string must be found within the host name or path.
Because the match string could be anywhere in the path, you must ensure that your web directory names and match strings are selected such that the match strings do not inadvertently match the wrong directory.
In addition, because the realm entries are scanned in order, realms cannot be nested. For example, you may not put the directory admin in the "admin" realm and the subdirectory admin/accounting in the "accounting" realm. Only one of them will ever be selected.
If a realm is matched and the On Authenticate event handler has been defined, it is called before execution begins to allow you to authenticate the user.
If the match string is not found, there is no current realm and the authentication mechanism is not invoked.
Let's look at some example Realm.ini entries.
The first entry would place the entire host www.myserver.com in the realm "www.myserver.com". That means any request made on that server would have to be authenticated for that realm.
The second entry places the directory admin in the "admin" realm. Note the use of the leading and trailing slashes to ensure only a directory is matched in the path. Don't worry if the user enters www.myserver.com/admin , because Active4D redirects that to www.myserver.com/admin/ which will match correctly.
If you create a large database in 4D, most likely it has more than one module. Often these modules are restricted to a certain set of users.
When bringing a database to the web, you will want to carry the same structure to your web site. You could accomplish this by directing the different classes of users to separate subdirectories within the root directory. But wouldn't it be nice to have completely separate web sites for each one?
Ordinarily this is difficult, since the entire web site shares a single root folder and a single web address. However, if your host machine supports TCP/IP multihoming, you can easily set up completely different web sites on the same machine and configure Active4D to route requests to the correct folder.
This config file specifies where Active4D should route HTTP requests. The criteria is uses to determine the routing are:
Since this is a routing table, you must specify the target of the routing. The complete format for a virtual host entry consists of five tab-delimited fields. There may be any number of tabs between fields, which are as follows:
IP Address Hostname Language Root Default
You may use a value of * in the IP Address , Hostname and Language fields to indicate any value will be accepted. Using * in the Root or Default fields indicates that the global values should be used. All fields are limited to 255 characters in length.
The entries are searched in order, so they should progress from most specific to the least specific. All matching is case-insensitive and matches the entire value. An invalid or missing value will result in that entry being ignored.
The Root should be a URL-style path, either relative to the default directory or a full path. The Default should be a simple file name with no directories.
The final entry should be terminated with a carriage return.
To understand how virtual host routing works, let's look at some examples for a site that has an English and French version. We are using multihoming to have multiple IP addresses on the same machine. In addition, we will define three hosts, mac.home.com , mac.admin.com and mac.home.fr . The first two are on IP address 192.168.1.7, and the third is on 192.168.1.27.
|
`IP address Hostname Lang. Root Default * mac.home.fr * web_fr defaut.a4d |
The first entry routes all requests where the user's browser is configured with French as the preferred language. The directory "web_fr" is made the root directory for those requests, and a URL to a directory will redirect to the default file "defaut.a4d" in that directory.
The second entry will route all requests to mac.home.com . The root directory and default file will be set to whatever they are configured to be in Active4D.ini .
The third entry will route all requests to the virtual host mac.admin.com . The root directory will be set to "admin" and the default file will be set to whatever it is configured to be in Active4D.ini .
The fourth entry will route all requests to the host 192.168.1.7 (in case the user enters the IP address directly). This entry is an alias for the second one.
The fifth entry will route all other requests to the IP address 192.168.1.7 in the same way the second entry does. For completeness, you should always have an entry for both the hostname and IP address to catch all possible requests.
The sixth entry routes all request to the host mac.home.fr . The directory "web_fr" is made the root directory for those requests, and a URL to a directory will redirect to the default file "defaut.a4d" in that directory.
The final two entries are analogous to the fourth and fifth, only they route requests to a different IP address.
If an error occurs during the processing of a request, before any scripts are executed, Active4D returns the status code indicating the error that occurred and a default error message where indicated by the HTTP specification.
You can override the default error message by creating a file called <status>.html in the root web directory, where <status> is the numeric status code representing the error. These error pages should be purely static pages with no embedded scripts.
For example, to show a custom error page for a 404 (Not Found) error, simply create a static HTML page and name it "404.html", then place it in the root directory. When a 404 error occurs, Active4D will return your custom page.
If you are just starting to use Active4D, the Active4D shell provides everything you need to get up and running without the need to understand the details of the plugin API. If you are using the shell as is, you may safely skip this chapter.
If on the other hand you are integrating Active4D with your existing web server code, you will need to understand the plugin API.
There are two different ways of executing scripts in Active4D: request execution and direct execution .
Request execution requires that you pass a valid HTTP request to Active4D. The request body contains the embedded script.
Direct execution requires only that you pass the embedded script to Active4D, although you may optionally pass a query string as well.
Once the host web server receives HTTP request, you pass the request to Active4D through one of the A4D Execute <type> request commands. They are:
The request execution commands all take similar parameters.
A4D Execute <type> request |
version 2modified version 3.0 |
|||
|---|---|---|---|---|
|
A4D Execute <type> request(<inRequest>; inRequestInfo; outContentType; outHeaderNames; outHeaderValues; outResponse) ! Longint |
||||
The array parameters must be text arrays, otherwise an error is generated.
The inRequestInfo array provides Active4D with context information not available in the HTTP request itself. It has a very specific format as follows:
Examples of how to initialize this array are contained in the shell database methods On Web Connection and A4D ITK Listener .
The <inRequest> portion of the parameter list changes depending on the command used. The parameters before the common parameters listed above are summarized in the following pages.
After handling a request, Active4D returns everything necessary to create a proper HTTP response in the last four parameters. The actual contents of those parameters depends on whether or not the request executed successfully and what happened during execution.
If Active4D is given a request to an executable file, these parallel arrays receive the HTTP response headers. The headers returned for an executable file are as follows:
|
Always second element. See See Function result for possible values. |
|
|
Automatically added with the value "no-cache" if Cache-Control is "no-cache". |
|
|
Unless explicitly set within Active4D, is set to the current time to prevent response caching |
|
|
If a session is created and session cookies are enabled, or if a cookie is set within Active4D |
|
In addition to these headers, you can set response headers within Active4D with the set response header command.
If Active4D is given a request to an non-executable file and is configured to serve non-executables, the following headers are returned:
If Active4D is given a request to an non-executable file and is not configured to serve non-executables, outResponse is empty and the following headers are returned:
Given this information, the host web server can easily serve the file. For example, using 4D's web server you could call DOCUMENT TO BLOB followed by SEND HTML BLOB . Using ITK you could call ITK_TCPSendFile .
The modification date/time are returned to allow the host web server to implement a caching mechanism.
This parameter will always contain the response body. The actual contents depends on the context:
|
Variable, see See Interpreter for more info |
|
|
If the file can be found and it has not been modified since the time passed in, the response body will be empty. Otherwise the response body will contain the requested file. |
The status code returned by Active4D will be one of the following:
Active4D takes care of creating the proper response headers and response body for each status code.
A4D Execute request |
version 2 |
|||
|---|---|---|---|---|
|
A4D Execute request(inRequest; inRequestInfo; outHeaderNames; outHeaderValues; outResponse) ! Longint |
||||
This command should only be used with 4D 6.5's built-in web server. It expects the complete HTTP request to be contained in the inRequest parameter.
If a form is posted with a lot of data or a large file is uploaded such that the original request was larger than 32K, inRequest will not contain the complete request and Active4D will have no choice but to return HTTP Status Request Too Large (413).
The bottom line is, if you are going to use 4D's built-in web server, use 4D 6.7. If you must use 4D 6.5, I recommend you use ITK.
For an example of how to use this command, see On Web Connection in the shell.
A4D Execute BLOB request |
version 2 |
|||
|---|---|---|---|---|
|
A4D Execute BLOB request(inRequest; inRequestInfo; outHeaderNames; outHeaderValues; outResponse)
!
Longint |
||||
This command expects the entire request (including uploaded files) to be in the inRequest parameter. It could be used with ITK or TCP Server Deux if you retrieve the entire request into a BLOB first.
A4D Execute 4D request |
version 2 |
|||
|---|---|---|---|---|
|
A4D Execute 4D request(inRequest; inVarNames; inVarValues; inRequestInfo; outHeaderNames; outHeaderValues; outResponse)
!
Longint |
||||
This command is designed for use with 4D 6.7's built-in web server. It expects the header of the request to be in the inRequest parameter, and any form variables to be in the two arrays (which must be text arrays).
If you plan to allow file uploads, you must provide a Compiler_Web method which declares BLOB variables to hold the uploaded files. For an example of how to do this and how to use this command, see the shell.
A4D Execute stream request |
version 2 |
|||
|---|---|---|---|---|
|
A4D Execute stream request(inStreamRef; inRequestInfo; outHeaderNames; outHeaderValues; outResponse, outReqInfoNames; outReqInfoValues)
!
Longint |
||||
This command is designed for use with an ITK or TCP Server Deux-based web server. It does all the work of receiving the request from the TCIP/IP stream referenced by inStreamRef . It does so in two stages, first retrieving and parsing the request header, then retrieving the body if necessary.
A4D Execute stream request returns request info the two parallel arrays outReqInfoNames and outReqInfoValues . The content of those arrays is as follows:
The first three elements are a parsed version of the first line of the HTTP request. From the fourth element on are the HTTP headers of the request, URL-decoded and converted to the Macintosh Roman character set.
These arrays are provided to allow you to do custom post-processing of a request without having to parse it yourself. For example, Active4D's ITK shell looks for a "Connection: Keep-Alive" header.
If you have no need for this information, you can simply not pass outReqInfoNames and outReqInfoValues.
Before using this command you must call:
A4D Set stream callbacks(inReceiveMethod; inStatusMethod)
The two parameters are strings which contain the names of callback methods used by Active4D to retrieve the request from the TCP/IP stream.
The signatures of the callbacks are as follows:
ReceiveCallback |
version 2 |
|||
|---|---|---|---|---|
|
ReceiveCallback(inStreamRef; inStopString; inMaxLen; inTimeout)
!
BLOB |
||||
All of the parameters are passed in according to the rules of the ITK command ITK_TCPRecvBlob . Basically, you shouldn't have to worry about how this method works, since a receive callback method is provided for you in the shell.
If the receive fails for some reason, the callback must place a single longint with the value zero in the result BLOB.
StatusCallback |
version 2 |
|||
|---|---|---|---|---|
In addition to executing HTTP requests, there are also a set of commands that allow you to directly execute a file or to execute a block of text:
With these commands you do not have access to most of the HTTP context information like cookies, form variables, etc., since there is no HTTP request to parse.You can, however, pass in a query string.
The "direct" execute commands follow the same pattern as the request execute commands in terms of the parameters they take. For examples of how to use the "direct" execute commands, see the shell database.
Direct execution is useful in cases where you are not responding to an HTTP request. In fact, you could easily use Active4D as a general-purpose scripting engine by executing files or blocks of text that are nothing but Active4D code. You need only enclose the scripts in the <% %> tags. If you need user interface commands that Active4D does not provide, you could easily write wrapper methods for use within Active4D.
For example, to show an alert from within Active4D, you could define a method called A4D_ALERT, which would look like this:
Much of the power of Active4D lies in its language. Unlike 4D's semi-dynamic HTML tags, which are a transmogrified subset of the 4D language, Active4D's language is a superset of 4D's language. In other words, with Active4D you just write 4D code. In fact, in most cases you can write and test code in 4D and then paste it into a text editor to use with Active4D.
Active4D's interpreter is actually quite a bit stricter than 4D's, in that syntax errors and nonsensical constructions that 4D blithely ignores (but the compiler catches, of course) are considered errors and abort execution.
Active4D extends 4D's language in some important ways. In addition to defining new operators, you can also define methods and libraries of methods. These are covered in See Methods
The Active4D interpreter is essentially a text processor. It parses a text input (which might be a file or a block of text) and writes to a response buffer . The text input may have any mixture of HTML and embedded Active4D source code. When execution begins, the response buffer is empty.
Active4D source is separated from HTML by one of three tag pairs:
Although any of the tags are valid, the corresponding end tag must be used with its beginning tag.
In the case of the first two tag pairs, there is no need for white space between the tags and the Active4D source. In the case of the third pair, which is actually a specialized HTML comment, you must leave at least one whitespace character between the tags and the Active4D code it encloses.
Here is an example of Active4D source code embedded in the three different types of tag pairs.
Like 4D, Active4D is line-based; the end of a line or the end of a code block marks the end of a statement. However, you may have as much whitespace as you like between the Active4D tags and the actual source.
Let's look at an extremely simple example which will illustrate how Active4D parses and executes a script. Suppose we execute a file which has the following contents:
|
I was here at <% write(Current time) %> on <% write(Current date )%> |
Active4D begins execution by scanning for a source code begin tag. As it scans, the text is appended to the response buffer. When the first "<%" in the above example is reached, the response buffer contains:
Once a source code tag is found, Active4D skips past the tag and goes into execution mode. In execution mode, Active4D continues to parses the text, but instead of passing the text through to the response buffer, it resolves the text into executable tokens and then executes the tokens.
It would be pretty useless if you could not write dynamically generated text to the response buffer within your source code, so Active4D provides a write command to do just that. The write command takes a value of any type, converts it to text, and appends the result to the response buffer.
In the example above, the write command is the first token. The write command handler is given control, which proceeds to evaluate the parenthesized expression following the command. In this case the expression is the 4D command Current time , which is converted to text as if passed to the String command. The text is appended to the response buffer, and the interpreter then continues parsing.
The next token is "%>", which is a source code end tag. Active4D skips to the end of the tag and switches back to scan mode. At this point the response buffer contains:
The interpreter scans up to the next source code begin tag, appending to the response buffer. At the start of the next source code block, the response buffer contains:
The interpreter then executes the second source code block as it did the first, after which the response buffer contains:
The interpreter scans to the end of the file and returns control to the caller. At that point the response buffer contains:
If the interpreter was invoked by Active4D's web server, the web server generates the HTTP headers necessary to describe the response to the client. Finally, control returns to 4D, and the shell sends the generated headers and response body to the client browser, which displays the text:
I was here at 19:27:13 on 10/19/2001
The client cannot see the source code because it is stripped out before being sent back from the server.
For the most part Active4D's language (henceforward just "Active4D") has the same syntax as 4D's language. However, there are a few important differences and enhancements.
Because Active4D source code is plain text, you are free to use whatever indentation style you wish -- the 4D method editor is not there to do it for you. Typically you will want to use tabs in your text editor to perform indentation of control structures. For example:
|
write("hello") `This is exactly equivalent to the previous block if ($sort_ascending = "1") `Note the space around the = order by([customers];[customers]lastname;>) |
You can have any number of Active4D code blocks within an HTML page. In addition, it is not necessary to complete control structures within the block in which they are declared. This powerful feature allows constructs like this:
Using this feature with If/Else/End if, you can conditionally show entire tables, form items, etc.
|
Like 4D, Active4D is not case sensitive, so you can just as easily write "first record" as "FIRST RECORD". But whereas the 4D method editor would change "first record" to "FIRST RECORD" when it parsed the line, in Active4D it will remain "first record" because you will be using an external text editor.
Case-insensitivity extends to table and field names, method names, and variable names.
One difference between 4D's language and Active4D is that Active4D is expression-based, like C/C++/Java. In other words, everything is an expression, including an assignment. So the assignment:
actually evaluates to the value of $i after the assignment, namely 10. This allows you to do convenient tricks like this:
Active4D supports regular 4D comments (indicated by ` ), either on a separate line or at the end of the line, with no limit on their length. You may also use the characters "//" as a synonym for ` to indicate a 4D-style single-line comment.
In addition, Active4D supports block comments . Unlike 4D comments, block comments may be embedded within a line or may span multiple lines.
Block comments begin with the character sequence "/*" and end with the character sequence "*/". For those of you who know other languages, this is the standard syntax for block comments in C/C++ and Java.
Here are some commenting examples:
Identifiers in Active4D follow the same rules as in 4D, both in terms of valid characters and maximum length (31 characters excluding any prefix).
Active4D maintains its own local variables which are indicated by a leading dollar sign, as in 4D. You can also access existing 4D process and interprocess variables (using "<>", not the Macintosh diamond character), as well as any fields in the database. Last but not least, all 4D named constants (4DK#) are recognized, including ones which you may have installed in your database structure.
Active4D can operate on all 4D data types except for 2D arrays. Data types may be implicitly converted according to the same rules 4D uses, or explicitly converted using commands such as String and Num .
All compiler declarations are supported, but you may only use them on local variables. By predeclaring local variables with compiler declarations, you can achieve better type safety in your code.
|
`Without the compiler declaration, $long would become a real = 13.27. `Because it was declared a longint, its value is now 13. $long := "foobar" `this generates an incompatible argument error |
Active4D imitates 4D's interpreted behavior exactly in regards to variables that have been declared in compiler declarations. Once a variable has had its type fixed by a compiler declaration there are two ways it can be changed:
All array types except 2D arrays are now fully supported in Active4D. This includes picture and pointer arrays. You may declare and use local arrays in Active4D just as you would in 4D.
Active4D supports pointers to process and interprocess variables, tables, and fields. You may create and dereference pointers to any of these entities within Active4D.
As in 4D, you may not create a pointer to a local variable. However, Active4D allows you to pass local variables by reference to its own methods, which is effectively the same as using a pointer (but easier). For information on pass by reference, see See Methods
Active4D recognizes string, number date and time literals in all the standard formats except scientific notation. If the current platform charset is Shift_JIS or GB2312 , string literals are assumed to be in Shift_JIS or GB2312 and are parsed accordingly.
There is one major extension to string literals in Active4D. Strings literals support backslash-prefixed embedded control characters:
Here is an example of using backslashes in a literal, with the equivalent 4D code. You decide which is easier to write and read.
|
write("This is line 1.\r\"It's cool!\", you say.") "This is line 1."+Char(13)+Char(34)+"It's cool!"+Char(34)+", you say." |
In addition to supporting all installed 4DK# constants, Active4D allows you to define named constants at runtime using the define command. For more information on the define command, see See define.
Typing of local variables in Active4D follows the same rules as 4D. If a local variable has not been declared with a compiler directive, it is variant: its type changes on the fly according to the type of value assigned to it. Once it has been declared within a compiler declaration, it follows the rules outlined in See Compiler Declarations above.
On the other hand, 4D variables and fields are always considered invariant. You cannot assign a value to them that would change their type.
For all variables and fields, Active4D will generate an error and abort execution if a referenced variable or field is undefined.
Active4D implements all of 4D's operators (with the exception of some picture operators), including array indexing and character references. Character references must use the syntax [[N]] , where N is a number from 1 to the length of the text being referenced.
Active4D operators have the same restrictions on the data types they will accept as 4D does. Active4D will abort with an error if an attempt is made to divide or modulo by zero.
Active4D adds many operators from C/C++/Java which make writing code faster and easier to read. Here are the new operators:
Here are some examples of their usage:
|
$j:=++$i `increments the value of $i before the assignment, $j=4 $k:=$i++ `increments $i after the assignment, $k=4, $i=5 $i*=3 `multiplies $i by 3 and assigns, $i=15 |
In other words, any expression:
Any of these operators will work with array elements. So, for example, you can increment the fifth element of an array by using the ++ operator:
Active4D supports horizontal and vertical concatenation for pictures. Thus the operators that may be use with pictures are:
The + operator has been enhanced in Active4D to auto-convert arguments to the right to text if the argument to the left is text.
|
$s := "I was here at " + Current time + " on " + Current date |
Notice there is no need to explicitly convert Current date and Current time to strings.
Remember that the argument to the left of the + must be text for this trick to work, so the following statement would not work, because the + operator would try to add ", " to the date returned by Current date :
In this case, you would have to "prime the pot" by converting Current date to a string:
|
$s := string(Current date) + ", " + Current time + ": I was here" |
All of 4D's control structures and flow of control keywords are supported, with four notable additions: for each/end for each , break , return , exit and continue . If you are familiar with other languages that use these keywords, they work exactly as they do in those languages. If are aren't familiar with those languages, here's how they work.
This looping control structure is the easiest way to iterate over a collection. For more information on collections, see See Collections and See Iterators.
If you are within any kind of loop ( For, While, Repeat ), the break keyword (used on a line by itself) will transfer execution to the first line of code after the end of the loop. The loop variable used by a For loop will not be changed. If you use break outside of a loop it will generate an error.
This keyword (used on a line by itself) transfers execution to the first line of code after the current code block. If you are within an Active4D method, execution will continue at the first line of code after the line that called the method. It can also take a parenthesized expression to return as the result of the method.
If you are within any kind of loop ( For, While, Repeat ), the continue keyword (used on a line by itself) will transfer execution directly to the top of the loop. The loop variable in a For loop will be incremented according to the For clause. If you use continue outside of a loop it will generate an error.
This keyword, used on a line by itself, immediately aborts all execution without generating an error. This is primarily useful for debugging, when you want to dump the internal state before a certain point and then stop. This keyword is also useful if you have detected an error condition from which you cannot recover and you want to immediately stop execution.
Here are some examples of how to use the new keywords:
In the course of programming a web site with Active4D, you will often need to specify file path. If you have done any work with paths in 4D, you know what a pain it can be, especially when dealing with multiple platforms.
All commands that take a path in Active4D -- including Active4D's implementation of standard commands like Open document -- can take a URL-style path, which uses `/' as the directory separator. This allows you to program in a platform-neutral way, without having to worry about the native directory separator.
A path can be absolute or relative . What that means depends on the command using the path. Standard 4D document commands and Active4D's own commands treat paths differently.
Active4D provides a many utility commands for working with paths. For example, if you need to use a 4D document command on a file within the web root directory, the get root command returns the full path to the current root. In addition, there are commands to extract the filename from a path, the extension from a filename, and the directory from a path.
For more on these commands, see See System Documents.
One of the most powerful features of Active4D is its ability to include other files during execution. To include a file, you use the include command, passing a relative or absolute URL-style path. Since include is an Active4D command, absolute paths are relative to the root, and relative paths are relative to the currently executing file.
If found, the included file is interpreted as if it were part of the source file. The scope of the included file is whatever the scope was where the include command appeared.
This means that any local variables declared before the include are available in the scope of the include file's code, and any local variables declared in the include file are available to the source file when the include file is finished executing.
Included files may in turn include other files, ad infinitum (or until stack space or memory runs out). Before including a file, Active4D checks to see if the include is a circular reference and aborts execution if it is.
There are many uses of included files. Some common ones are:
You can also include files using the require command. The require command works like include , but it guarantees that any given file will only be executed once via require within a single execution of the interpreter.
The require command is primarily useful for creating files of global constants, variables and methods that only need to be loaded once per Active4D session. All files that reference these globals can require the globals file and you don't have to worry about the overhead of executing the file or the problem of defining named constants twice, which is an error.
Although you can call 4D methods within Active4D, this ties you to the database structure, which is exactly what Active4D is designed to avoid. If possible, you should define and use methods within Active4D. For more information on defining methods in Active4D, see See Methods
Nonetheless, should you need to, you can call any 4D method within Active4D using the exact same syntax you would within 4D.
When calling a 4D method from Active4D, you pass parameters just as you would within 4D. Strings are converted to Text and Longints are converted to Reals before being passed. You may return any type of value from the method.
Active4D has no way of matching parameters passed to a 4D method with the actual parameters declared in that method. It is up to you to make absolutely sure that the number and type of parameters declared in the 4D method are compatible with what you pass to it. If the 4D method expects some parameters to be optional, you may of course not pass those.
In an interpreted database, if the method parameters are not assignment-compatible, 4D will initialize the parameter to a null value. In a compiled database, however, if the parameter types do not match exactly, a runtime error will be generated, which will effectively bring your application to a halt in a very unfriendly way. You do not want this to happen.
In addition to calling 4D methods as you would within 4D, by directly referencing the method name, you can also indirectly call a method by name using the Call 4D method command.
The syntax of this command is as follows:
The method name may be any valid expression that returns text. This powerful feature allows you to dynamically determine which of several methods you call, as long as their parameter lists are compatible. This is (sort of) the equivalent of a method pointer in other programming languages.
As with ordinary method calls, the method called by Call 4D method may return a value.
If the 4D method returns a value, you may ignore it if you have no use for it. You need not assign it to a dummy variable as you would in 4D. On the other hand, if the 4D method returns no value, attempting to use the result of such a method will result in an error.
Active4D adds a new data type to the language: collections . A collection is a group of key/value pairs which are stored in memory. The keys are strings (with a maximum length of 255) and the values may be any 4D data type, including arrays. The key/value pair is also referred to as an item .
In classic programming terms, a collection is also known as an associative array, a dictionary, a symbol table or a map. In 4D terms, you can conceptually think of a collection as two parallel arrays, with keys being in one array and the values in the other. Of course, you can't do this in 4D because 4D arrays can only contain a single type, and a collection value may be of any type.
Active4D provides a full suite of commands for creating your own collections. In addition, Active4D uses collections to store HTTP headers, query parameters, form variables, cookies, global variables, and sessions.
For more information on the collection commands, see See Collections.
Collections are referred to by a Longint handle, much like an ObjectTools object handle or a 4D hierarchical list handle.
Active4D maintains an internal list of user-created collections. All collection commands that take a collection handle check the handle against this list and generate an error if the handle is not valid. Thus you are prevented from crashing the server by passing in a bogus handle.
Collections can be either local or global . A local collection is automatically deleted when a script finishes executing. A global collection remains in memory throughout the life of the server.
Local collections are like local variables -- you use them for temporary storage within a single script. You should always use a local variable to store a local collection handle.
Global collections are like interprocess variables -- you store application-wide data in them. Typically you will create global collections in the On Application Start method and clear them in the On Application End method.
Collections come in two basic varieties: read-only and read-write . You can perform the following operations with read-only collections:
Read-write collections add the following operations:
In addition to these basic operations, some of the specialized collections defined by Active4D provide other operations as well. These operations are covered in the relevant command reference sections.
Active4D extends the syntax of the {} indexing operators to allow indexing a collection by keys. The syntax of this way of indexing is as follows:
where collection is either a collection handle or collection iterator, and key is a text expression. If an item in the collection exists with the given key, the result of this expression is the item's value, and it may be treated in all respects as a variable of that type. If no item exists in the collection with the given key, the result of the expression is an empty string.
Because the result of the expression is treated as the value it resolves to, you can use collections as a natural part of the language. For example:
Note in the example above that you put a copy of an array into a collection by directly assigning the array to a collection item, like this:
You can even embed collections in collections (by storing their handles) to any depth and reference their items by adding more indexes, like this:
Very often you may need to iterate over every item in a collection. There are two ways to do so in Active4D.
The first (and easiest) way to iterate over a collection is to use the for each/end for each loop control structure. For more information on for each , see See Iterators.
The second way to iterate over a collection is through an iterator . Every collection provides a command which returns an iterator for the collection. You use this iterator to traverse the items in the collection.
In addition, some collections allow you to get an iterator for a specific item given the item's key. If no item with the given key exists, you are given an empty iterator. An empty iterator is a special iterator that has the following properties:
You can identify an empty iterator either by testing equal to zero, by calling is an iterator , by testing for an empty key, or by testing the item type.
For more information on iterators, see See Iterators.
When Active4D is used as the web server, the interpreter has full access to both the HTTP request and the response data. This data is critical when developing high-powered web sites. The commands necessary to access this information are covered in See Command Reference
As was discussed in See HTTP Server an HTTP request consists of several headers and an optional body, in addition to the requested URL itself.
The data encapsulated in an HTTP request includes:
In addition, information about the host environment is passed in to Active4D.
If you have never gone through this process, here's a little tip: the time it takes to do Step 1 alone will cost you more than the price of Active4D!
Fortunately Active4D does all of the above for you. You never have to deal with the particulars of the HTTP specification, which allows you to focus on the problem at hand -- building a great web site.
The primary way in which you "pass" parameters from one page to another in a web site is through form variables and query string parameters. Active4D places those parameters in easy-to-access collections.
For example, if the user posts a form which contains a field called "f_name" and you want to access the contents of that field, you can simply use the command:
Likewise, to access a query string parameter called "recnum", you could use:
This is simple enough, but Active4D gives you an even easier way: auto-variable creation.
By default, the interpreter automatically creates a local text variable for every form variable and query string parameter in the HTTP request. If there are multiple form variables or query parameters with the same name -- which is the normal occurrence if more than one item is selected from a multiple choice form list -- a text array is created which contains all of the values.
This feature allows you to conveniently access form variables and query string parameters as if they were "passed" to your script as named method parameters.
|
If a submit button is clicked on a form, the browser posts the button's form name and value. Buttons which appear on a form but are not clicked are not included in the posted form data.
Frequently you need to test a posted form to see which button was clicked. For example, your form may have "Search", "Previous" and "Next" buttons. There are two ways to test which button was clicked. The first way is to check the form variables collection. If a button was clicked, it will be in the collection and its value will be returned. If the button was not clicked, it will not be in the collection and an empty string will be returned. Using this technique, your code would look something like this:
|
:(get form variable("f_search") # "") :(get form variable("f_previous") # "") `Go to the previous group of records :(get form variable("f_next") # "") |
Because Active4D only creates local variables for fields that are posted, a variable will only be created for the button that was clicked. Thus you can also use the defined command to test which button was clicked, like this:
|
`Go to the previous group of records |
You control the response body indirectly with the write command and its peers. In addition, Active4D gives you dedicated commands for setting the response headers.
The data encapsulated in an HTTP response includes:
Some HTTP response headers are simple in their format. Others require a specific format which you must follow. As with the request headers, without Active4D you would have to know the HTTP specification for each format. For those headers that require special formatting, Active4D gives you simple commands that relieve you of having to know the HTTP specification.
Text comes into Active4D from several sources:
Clearly, it would be a real pain -- not to mention error-prone -- if you had to remember to do all of the character set conversions yourself. Fortunately, Active4D allows you to configure the various charsets and then takes care of most of these conversions for you.
Internally Active4D uses the Macintosh Roman charset, since that is what 4D expects. The character sets you configure determines which charset Active4D converts from on input and which charset it converts to on output.
The platform charset determines what charset Active4D converts from when parsing string literals. When converting from a Roman language charset, characters are converted to Macintosh Roman. When converting from a non-Roman charset, no conversion is performed.
For example, if you are using BBEdit on the Mac to write your embedded scripts, string literals would be in Macintosh Roman. On the other hand, if you are writing your scripts with Dreamweaver, string literals would be in the charset of the page you creating.
You can set the platform charset with the "platform charset" config option in Active4D.ini . The possible options are "mac", "win", "latin1", "ISO-8859-1", "shift_jis" or "gb2312". Note that "latin1" and "ISO-8859-1" are equivalent. You may also use the set platform charset command.
On a Roman language system, the default platform charset is the native charset for the platform on which Active4D is running ("mac" or "win"). On a Japanese language system, the default platform charset is "shift_jis". On a Chinese language system, the default platform charset is "gb2312".
The output charset determines what charset Active4D converts to when text is written to the response buffer. If the output charset is a Roman language charset, it is assumed text is always converted from Macintosh Roman. If the output charsest is a multibyte charset, no conversions are performed.
You can set the output charset with the "output charset" config option in Active4D.ini . The possible options are the same as for the platform character set. You may also use the set output charset command.
The default output charset on Roman language systems is ISO-8859-1 , which is the default charset for HTML. The default output charset on Japanese language systems is Shift_JIS . The default output charset on Chinese language systems is GB2312 .
The output encoding determines how Active4D converts special characters to HTML character entities when text is written to the response buffer. Output encoding is performed before the output character set conversion, since the encoding tables are based on the Mac Roman character set.
You can set the output encoding with the "output encoding" config option in Active4D.ini . You specify one or more bit flags to indicate which characters to encode. More than one flag can be specified by separating them by `+' and any number of spaces. The bit flags are "none", "quotes", "tags", "ampersand", "extended", "html" and "all" (without the quotes). Note that "extended" and "html" are synonymous.
You may also use the set output encoding command at runtime. For more information on output encoding, see See set output encoding.
The default output encoding on Roman language systems is "html". The default output encoding on Japanese language systems is "none".
When parsing an HTTP request, the headers are left as is. This is not a problem, since all headers except cookies will be in US ASCII and the character set is not an issue.
Query string parameters and form variables are automatically URL decoded, and the name portion of their name/value pairs is converted from ISO-8859-1 to Macintosh Roman.
If the platform charset is a Roman charset, query string parameter and form variable values are converted from ISO-8859-1 to Macintosh Roman. If the platform charset is a multibyte charset (like Shift_JIS), no conversion is performed.
Active4D cannot know the source platform of a file or its charset. It's up to you to know the charset used by a file, then convert to the appropriate character set when saving to the database or writing to the browser.
You can convert between character sets using Win to Mac , ISO to Mac and Mac to ISO .
Active4D goes to considerable lengths to catch errors and display meaningful error messages. If any errors occur during the execution of an Active4D program, execution is immediately aborted and the error handler takes over.
For complete information on error handling within Active4D, see See Error Handling
Despite our best intentions, sometimes our scripts may do bad things like going into infinite loops or waiting an inordinate amount of time for a shared resource.
Every script is given a set amount of time to execute. Before executing each line, Active4D checks to see if the timeout has been reached, and if so it generates an error and aborts execution.
You may set the script timeout with the "script timeout" config option in Active4D.ini . The value set with this option is the minimum script timeout in seconds and is the default value for all subsequent script executions.
The actual timeout can be set higher within Active4D with the set script timeout command. This command will affect the next execution of Active4D, not the one in which the command is used, and in no case can it be set lower than the minimum value set in Active4D.ini .
As you build your web site with Active4D, you will undoubtedly come across chunks of code that can be reused in many different contexts, just as you would reuse code in 4D. Fortunately, Active4D has a powerful system for defining its own methods.
The simplest way to define a method in Active4D is to declare it inline with the rest of your code, then reference it sometime later in the flow of execution.
Inline methods "live" only as long as the current invocation of Active4D. This means that you incur a small performance penalty each time the method is used, because it has to be parsed and stored in temporary memory before it can be used.
In general, you will only want to use inline methods during testing and debugging, because there is a much better way of defining and using methods: libraries . Libraries have many advantages over inline methods and allow you to group many methods together into one logical unit. They are covered completely in the next chapter, See Libraries.
Whether a method is defined inline or in a library, the syntax for declaring a method is the same. If you are familiar with AppleTalk, the syntax may look familiar:
|
method "<name>" {({&}$arg1{=expression} {...{&}$argN{=expressionN}})} |
I know this looks complex, but it is actually quite simple. Keep reading.
The method name must be a double-quoted literal string which follows the rules for 4D method names in terms of allowable characters and maximum length (31). The double-quotes are necessary to allow you to include spaces as part of the method name, and to differentiate the method name from the method keyword.
Here is a simple Active4D method definition:
If a method takes parameters, the parameter list must follow the method name enclosed in parentheses. Unlike 4D, where method parameters are numbered, Active4D method parameters are declared by name, with each parameter becoming a local variable with that name within the body of the method. Parameter names must begin with `$'and follow the rules for local variable names.
Note that there is currently no facility in Active4D for passing a variable number of parameters. You can simulate this technique either by passing a reference to an array and accessing the elements of the array, or by using default parameters . Both of these techniques are covered later in this chapter.
Parameters to Active4D methods differ from 4D method parameters in several important ways.
There is no typing of parameters, as they are simply local variables with a variant type. This means you can pass values of different types in the same parameter, as long as the use of the parameter in the method would not cause any type incompatibilities. If you want to ensure a parameter is of a particular type, you would have to test its type within the method using the Type command.
Here is an example of how you can take advantage of the variant parameter typing:
|
method "WriteMany"($inValue; $inHowMany) writebr($inValue) `writebr takes any type, thus $inValue can be anything |
Parameters passed to an Active4D method become a local variable within the scope of the method. Thus, like local variables in 4D, they have no existence outside of the method body.
Likewise, local variables defined outside of an Active4D method cannot ordinarily be referenced within the method.
Just as in 4D, it is best to pass whatever values a method needs as parameters. But what if you want to change the value of a local variable which was declared outside of a method? There are two ways to accomplish this: pass by reference and the global keyword.
Pass by reference allows you to effectively pass a pointer to a local variable. This is the preferred way of modifying locals external to the method, and is covered below. But at times it is preferable to reference an external local variable directly. For example, you may need to reference or modify many external variables within a method, and it would be too cumbersome to pass many parameters.
In these cases you may use the global keyword to declare a list of local variables that you want to make accessible to the method, as in this example:
|
for($i;1;size of array($names)) |
Normally, parameters passed into Active4D methods are passed by value . In other words, the expression passed into the given parameter is evaluated and the constant value resulting from the expression is assigned to the parameter.
In addition to pass by value, Active4D also allows you to pass by reference . This powerful feature is activated by prefixing a parameter in the method argument list with an ampersand (&).
Reference parameters are essentially pointers to the entity that was passed into them. You can pass 4D pointers into Active4D methods, but there are two important ways in which reference parameters are unlike 4D pointers:
Any entity that is assignable -- including variables, fields, array elements, and character references -- may be passed by reference. You can even pass a character reference to an array element by reference!
Here is some examples of passing by reference:
|
method "ParamTest"($inByValue; &$ioByReference) ParamTest($byValue;$byReference) `At this point $byValue still = 7, but $byReference = 8 for ($i;1;size of array($inArray)) |
As you can see, the combination of 4D pointer support (mainly for tables and fields) and pass by reference allows you to write highly generic, reusable code within Active4D.
In 4D, method parameters are numbered. Thus it is easy to pass a variable number of parameters. In Active4D, parameters are named, so there is another technique for passing variable number of named parameters. This technique is called default parameters and is another feature of Active4D borrowed from C++.
To create a default parameter, append the parameter name with `=' and any expression which is valid at the point of method declaration. The expression is evaluated once when the method is first parsed, and the expression's value is stored with the method parameter. Within a default parameter expression, you can reference variables, methods, named constants, etc., as long as they are valid at the point of declaration.
|
method "ShowArray"(&$inArray;$inSuffix="<br>") for ($i;1;size of array($inArray)) writeln($inArray{$i} + $inSuffix) |
In the first call to ShowArray , the $inSuffix parameter is not passed, so Active4D uses its default value of "<br>". In the second call to ShowArray , the $inSuffix parameter was passed, so the passed value is used.
This is how you would have accomplished the same thing in 4D:
To return a value from a method, use the return keyword, like this:
The return keyword can also be used without a value, but if you return a value the expression must be enclosed in parentheses. Whether or not you return a value, the return keyword will immediately exit the method, no matter where in the method it occurs. This allows you to quickly exit a method when a termination condition is reached, much as the break keyword allows you to immediately exit a loop. It turns out this is extremely useful, as it saves you from having to set some kind of "success" flag which you test after the loop.
It is common for programmers to continually build up their own set of utility methods which they can call upon when needed. In 4D, these methods can be grouped together either by a name prefix or by placing them in a component.
In Active4D, you can group methods together into a named logical unit called a library . The library in effect serves as both the component and the name prefix.
Libraries are special files that can only contain the following entities:
Any methods within a library file must be enclosed by a library/end library pair. The library keyword must be followed by a literal string which gives the name of the library. The library name must match the name of the library's definition file, minus the file extension. Libraries may not be nested.
Here is an example library file called "mylib.a4l":
To use the methods in a library, you must first import it with the import command.
If you read the above steps closely, you will notice the following advantages that libraries have over inline methods:
When parsing a library, if Active4D encounters a error in the syntax of the methods definitions, it displays an error message with the exact location and type of syntax error.
Active4D follows a standard search path when attempting to locate a library. If you wish to put your libraries in a directory other than one in the standard search path, you may add the directory to the "lib dirs" path list in Active4D.ini .
By default, when importing a library Active4D appends ".a4l" to the library name and searches for a file with that name. If you wish to use a different extension for libraries, you may change it by setting the "lib extension" option in Active4D.ini . The extension must be a dot followed by no more than five alphanumeric characters.
Methods and constants defined in a library are stored in memory under the library's name. The library name then becomes a namespace that encapsulates all of the methods defined within it. This allows you to define method names without worrying whether another developer has chosen to use the same method name. As long as they are in libraries with different names, you can access each method separately.
When Active4D encounters a non-4D method name, it first searches all of the imported libraries for a method with that name. If there is more than one match, an error is generated and execution is aborted. If there is one and only one match that method is executed.
If two or more libraries are imported that have a method with the same name, you must disambiguate the method name by prepending the library name and a dot before the method name. Otherwise Active4D will stop and tell you that there is an ambiguous reference to a local method.
For example, suppose you have these two libraries:
To call the two "DoSomething" methods, you must do the following:
Methods and constants defined within a library may be referenced anywhere within that library without using the library.entity syntax. In fact, all entity names within a library take precedence over names external to the library.
This rule leads to the following type of problem:
|
`Imagine the following is within a separate library file method "ShowArrayAndSomethingElse"(&$inArray, $inSomethingElse) |
In this example, the library method ShowArrayAndSomethingElse calls the method ShowArray . Because ShowArray is defined within the library, that version of the method takes precedence over the inline ShowArray method defined outside the library.
In the example above, what if you wanted to reference the inline ShowArray method within the library? Fortunately there is a way.
All methods that are defined inline, i.e. in global scope, are placed in a special library called "global" which is implicitly imported. The global library exists only as long as the current execution of the interpreter, and cannot be flushed programmatically. Any attempt to import a library with the name "global" will result in an error, as that name is reserved.
To reference a global method or constant within a library, you can (and probably should) use the form global.method or global.constant . This will ensure that the global version of the entity will be referenced.
Going back to the example above, to ensure the method ShowArrayAndSomethingElse calls the global method ShowArray , you would write it like this:
|
method "ShowArrayAndSomethingElse"(&$inArray, $inSomethingElse) global.ShowArray($inArray) `This calls the inline ShowArray() |
Because libraries are so useful, you will want to use -- and thus modify them -- all of the time.
Active4D checks every library at a regular interval to see if it has been modified. Any library that has been modified is flushed from memory and then imported again. This relieves you from having to manually flush and reload your libraries.
Once your web site is in production and the code is stable, you may wish to turn off auto-refresh. Possible reasons for doing this include:
You control library auto-refresh with the "auto refresh" option in Active4D.ini . If this option is set to "true" or "on", the auto refresh mechanism is active. If this option is set to "false" or "off", the only way to refresh a library is to manually use the A4D FLUSH LIBRARY command from 4D or to quit and restart the server.
If auto-refresh is on, you may configure the amount of time Active4D waits between checks for modified libraries. You control this interval with the "refresh interval" option in Active4D.ini . The interval is specified in seconds and must be in the range 5 to 60 inclusive.
In Active4D 1.0, there were two ways of loading a library: import and load library . Many developers used load library because:
In Active4D 2, all libraries must be loaded with the (vastly enhanced) import command. The load library command has been deprecated. The changes to the way libraries work can be summarized as follows:
In the course of executing a script, there are well-defined points at which a developer would like have some control.
Active4D recognizes special event handler methods which are executed before and after various "events." To be activated, these event handler methods must be defined in a special library called "Active4D.<lib>", which must reside in an Active4D directory. The "<lib>" extension must be whatever you have configured the library extension to be in the Active4D.ini file. By default the library extension is ".a4l".
As with the config files, you may have multiple copies of the Active4D library in different directories in the search path. An Active4D library at the beginning of the search path will override one later in the search path.
The event handler methods are outlined below. In addition to these, you may define and call other methods within the Active4D library just as you would with any library.
Each handler is executed within a certain context which determines what data is accessible within the handler. For example, handlers that execute within the context of an HTTP request have access to all HTTP request and response collections which Active4D creates.
This handler is executed when 4D first starts up or after the Active4D library has been modified. This handler is analogous to the On Startup database method in 4D.
The only Active4D collection you have access to in this handler is the globals collection.
This handler is executed just before the server shuts down, either when 4D is shutdown or when the Active4D library is modified. This handler is analogous to the On Exit database method in 4D.
The only Active4D collection you have access to in this handler is the globals collection.
This handler is executed just before the Active4D HTTP server handles a request. The handler is passed the path and query string portions of the URL.
If you wish to change the URL, you may do so by returning a non-empty string. Note that what you return must include a query string if the request should have one.
You may also refuse the request altogether by calling set response status with a status of something other than 200 (OK), such as 404 (Not Found). If you set the response status to something other than 200, you do not need to return a result.
Here is what an On Request handler might look like:
The request info and request cookies collections are accessible within this handler. This allows you to check things like the host, etc.
When Active4D determines that the current request is in a protected realm, if this event handler is defined it is invoked before the On Session Start and On Execute Start event handlers.
Here is what a sample On Authenticate handler might look like:
In this example we are using a table that defines all of the users and passwords for each realm. If we find a match, we check the passwords to make sure the capitalization is exactly the same. If there is no match, we authenticate again.
Be sure to pass along an authentication failure message with the authenticate command, either by writing directly to the response buffer, by including another file, or by creating a file called "401.html" in the root directory, which will automatically be served when the 401 (Unauthorized) status is returned.
Within this handler you may access all of the Active4D collections.
This handler is executed when a session is created within the context of an HTTP request, before the On Execute Start handler. Typically you would use this handler to initialize some items in the session.
For example, here is an On Session Start handler that initializes three items in a session:
|
set session("recsPerPage"; 10) |
Within the first page the user requests, these three session items will be set. For more information on sessions, see See Selecting Records.
You can use the requested url command to determine which part of your web site was accessed, thus determining how to initialize the session. You may also use the redirect command to force all new sessions to go to a login page, for example.
Within this handler you may access all of the Active4D collections.
This handler is executed when a session goes to heaven (if it has been good), either because it timed out, it was expired with the abandon session command, the Active4D library has been modified, or because the server is shutting down.
Typically you would use this handler to clean up data that was stored during the course of the user's session. For example, if the user uploaded a file and you stored the path to the file in the session, when the session times out you would use this handler to delete the file.
Because this handler is executed at idle time, the only Active4D collection you have access to is the globals collection, as there is no request context. However, the about-to-be-purged session is made current and you can access all of its data one last time before kissing it goodbye.
There are a couple of important points to note in regards to this handler:
This handler is executed before Active4D begins execution. Typical uses for this handler would include dumping some debugging information, or perhaps executing code that must be executed at the top of every page, such as a check for a timed out session.
Because this handler is executed before the requested file is parsed, any HTML you write to the response buffer will appear before the opening <html> tag. It is possible that some browsers may not like this, although both Internet Explorer and Netscape seem to handle it without problems.
Within this handler you may access all of the Active4D collections.
Active4D implements over 350 commands that provide unparalleled power and simplicity to web site programming.
Active4D implements over 150 4D commands which cover the majority of tasks one might perform within the context of a web site. Of these, about 70% are implemented by Active4D directly, 15% are implemented using the 4D plugin API, and the remaining 15% had to be implemented using the equivalent of EXECUTE . The result is that the vast majority of the commands you use will run at full compiled speed.
Following is a list of the 4D commands implemented by Active4D. Unless indicated by the formats noted below, they take the same parameters and work exactly as they do in 4D.
You may notice that some commands which are not implemented in 4D 6.5 are implemented in Active4D. In addition, any commands which were extended in 4D 6.7, such as GET FIELD PROPERTIES , are implemented in their extended form in Active4D.
Active4D implements over 200 new commands. Most are focused towards web programming. Some are additions to the 4D language that we have always wanted. The bottom line is this: if you learn these new commands, you will be far more productive. So please take the time to learn them!
Active4D Command List |
||
|---|---|---|
The commands in this chapter are listed with the same basic format that the 4D documentation uses, with a few small differences:
Active4D adds several commands which make working with arrays much easier. It will pay many times over for you to learn and use these commands.
In addition, there are some important issues to understand in the implementation of SELECTION TO ARRAY and SELECTION RANGE TO ARRAY .
add element |
version 1 |
|||
|---|---|---|---|---|
This command appends one or more elements to ioArray . If inHowMany is omitted, one element is appended to the end of ioArray . The element appended to the array is initialized to the default value for the array's type. The add element command is essentially shorthand for the following standard 4D statement:
|
INSERT ELEMENT ($ioArray; Size of array ($ioArray) + 1; $inHowMany) |
append to array |
version 1 (modified v2) |
|||
|---|---|---|---|---|
This command appends one or more values to the existing contents of ioArray . If a value is not assignment compatible with ioArray , an error is generated and execution is aborted.
This command is my solution to the insanity of appending elements to arrays in 4D:
|
INSERT ELEMENT($array;Size of array($array)+1) |
The real power of this command comes when you want to append multiple values at once, like this:
join array |
version 2 |
|||
|---|---|---|---|---|
|
join array(inArray; inSeparator {; inStart {; inPrefixNum {; inQuoteText}}}) ! Text |
||||
This command joins the elements of inArray together into a single string. Non-textual array elements are automatically converted to text.
If inStart is not specified, it defaults to 1.
If inPrefixNum is not specified, it defaults to false . If it is specified and true , each element is prefix by "{#} ", where # is the element number.
If inQuoteText is not specified, it defaults to false . If it is specified and true , elements of text or string arrays are surrounded by double quotes.
|
writebr(join array($longs; ", ")) set array($nums; "one"; "two"; "three") writebr(join array($nums; "<br>"; 1; true; true)) |
This command is especially useful for writing the contents of an array to the Active4D debugging console. Use this form:
multisort arrays |
version 3.0 |
|||
|---|---|---|---|---|
|
multisort arrays(inArray1; inDirection1 {; ...inArrayN; inDirectionN}) |
||||
This command performs a multilevel sort on the elements of inArray1 through inArrayN . The direction of the sort for each array is specified by the the inDirection argument following the array:
You may sort any array type except for picture arrays and pointer arrays. The arrays may be local, process, or interprocess variables.
The sort direction may be one of the operators `>', `<` and `=', or may be any text expression which resolves to one of those characters. This allows you to programmatically set the direction of the sort.
multisort named arrays |
version 3.0 |
|||
|---|---|---|---|---|
|
multisort named arrays(inArrayName1; inDirection1 |
||||
This command is identical to multisort arrays , except that instead of passing direct array references, you pass text expressions which resolve to the names of arrays. This allows you to programmatically determine both the order and direction of the sort.
The arrays names should begin with `$' to indicate a local array, `<>' to indicate an interprocess array, and no prefix to indicate a process array.
resize array |
version 3.0 |
|||
|---|---|---|---|---|
This command resizes ioArray to the given size. If inSize is less than zero, the array will be resized to zero.
SELECTION/SELECTION RANGE TO ARRAY |
(modified 4D) version 2 |
|||
|---|---|---|---|---|
These commands have the same parameters as their counterparts in 4D, but there are important limitations in the Active4D implementation.
Running under 4D Standalone or 4D Server, the performance of Active4D's implementation will in most cases be more or less the same as 4D's. But if Active4D is running on 4D Client, the performance of Active4D's implementation will always be worse than 4D's, and in some cases dramatically worse.
The reason for this difference is that there is no built-in support for these commands, so Active4D has to implement them itself. To do so requires loading each record in the selection and extracting the requested fields. If there are related fields being loaded, each record in those tables must be loaded as well. If Active4D is running on the same machine as the database -- i.e. under 4D Standalone or 4D Server -- this all happens very quickly and there is no performance hit.
When these commands are executed from 4D Client, the data is gathered into arrays on the server and then the arrays are sent to 4D Client. On the other hand, if Active4D is running on Client, each record in the selection and each related record must be sent to the Client over the network in its entirety. If the records contain a lot of data which is not going into the target arrays, there will be a large performance hit. This performance hit gets worse as the number and size of related records increases.
To lessen the effect of the performance hit, the Active4D 3.0 implementation of these commands does not load related one records by default. This is in contrast to the version 2.0.x implementation which did load related one records by default. But the new implementation does follow the current "auto relate one" setting as configured in Active4D.ini or as set by the auto relate command. For more information on auto relating, see See Selecting Records.
If your SELECTION/SELECTION RANGE TO ARRAY command does not use fields from related tables, the new behavior will improve performance under Client if auto relate one is off. Thus in these cases you will want to ensure auto relate one is off when executing SELECTION/SELECTION RANGE TO ARRAY.
You can do so with the auto relate command, like this:
|
SELECTION TO ARRAY([ingredients]id; $ids; [ingredients]name; $names) |
Of course if you are relying on auto relate one being on in other code, you would have to add this line after the SELECTION TO ARRAY :
If, on the other hand, you are loading related data in SELECTION/SELECTION RANGE TO ARRAY , you will need to ensure that auto relate one is on, like this:
|
SELECTION TO ARRAY([ingredients]id; $ids; [vendors]name; $vendors) |
Since this will automatically load all automatically related one records, you must be careful to use automatic relations only when absolutely necessary.
set array |
version 2 |
|||
|---|---|---|---|---|
The collection commands allow you to create, manipulate, examine and destroy your own local (temporary) or global (persistent) collections within your scripts.
For more information on collections, see See Collections.
|
collection |
version 2 |
|||
|---|---|---|---|---|
Given a collection, this command returns an iterator to the first item in the collection.
For more information on iterators, see See Iterators.
new collection |
version 2modified version 3.0 |
|||
|---|---|---|---|---|
|
new collection{(* {; inKey; inValue {; inKeyN; inValueN}})} ! Longint |
||||
This command creates a new local or global collection and returns a handle to the collection. You then use this handle with the other collection commands.
If no * is passed, this command is exactly equivalent to new local collection . If * is passed, this command is exactly equivalent to new global collection .
You may also initialize the collection with key/value pairs by passing pairs of parameters. If an array is passed as the value, it is stored in its entirety in the item.
For example, this code would create a local collection and initialize it with two items:
|
$person := new collection("name"; [People]Name; "age"; [People]Age) |