Web Server Basic Techniques
NetTalk WebServer - The Basics
Introduction
The goal of this document is to show you how to use the NetTalk
NetWebServer class to create an interface in your program, that can be
accessed by a user using a web browser. We have tried to write this from
the point of view of a programmer who has never done web programming
before. So some of these concepts may seem trivial to you.
Before you can begin writing a Web Server, there
are some general ideas and concepts that are worth understanding. This
document will cover the basics, as well as discuss some NetTalk specific
conventions and features. Other documents will cover each of these items
in more depth, the goal here is not to cover every possibility, but
rather to get a good grounding in what is available.
The NetTalk Web Server is provided as two layers.
The first layer is the NetTalk Web Server classes. These classes are the
foundation of any NetTalk web server program.
The second layer is the template framework. I need
to stress that this framework is not prescriptive in the sense that this
is not the only way to build your web server. It is offered rather as a
particular way you may want to build your server. We have found it very
useful when coding our own web applications, however you will need to
evaluate it carefully to see if it meets your needs.
The best way to learn the class layer in more
detail, is probably to start by using the template framework layer. Once
you are comfortable with that, and how that works, then you can
experiment a bit with different approaches. One positive aspect of the
template framework is that you can use all of it, some of it, or none of
it, depending on what you prefer. You can also mix “hand-coded” pages
with template pages without any problems.
There isn’t a lot of structure in this document,
many different and varied topics are discussed. We recommend reading
through this document as you would a book. If you’re not 100% sure about
something then skip over it for now and keep reading. Later on,
especially once you’ve built a couple applications of your own, much of
it will make sense.
We recommend returning to this document from time
to time as the understanding of it will grow as your experience grows.
Acknowledgements
There is no point in re-inventing all the really
cool features that are currently available to web developers. What we’ve
done is integrate the ones we really like into NetTalk. A full list of
the scripts, their owners and licenses are
included here. Our grateful
thanks go out to all the authors involved in these projects.
If you find scripts that you think should be
integrated into NetTalk Web Server, and if they are suitably licensed,
then please let us know. No promises, but we’re always open to
suggestions.
There have been numerous dedicated web developers who have fed me more
bug reports, and feature suggestions than I could possibly handle. You
know who you are. My thanks to you all.
The Web is
a different country, and things are done differently there…
Traditionally in striving to make web development for Clarion
programmers as simple as possible every attempt has been made to hide
the differences (between Win32 programming and web programming) from the
programmer. However for some of the other Clarion web options available
in the past this has lead to inefficient models, which sooner or later
result in programmer frustration. We hope to avoid this problem, and so
at some times the differences between web and win32 become visible.
Thus understanding some of the crucial differences is important to
understanding how the server works, which in turn leads to easier
programming.
Before we start discussing how to build the web
server using NetTalk, we’d like to take a moment to highlight some of
the differences between a Win32 application, and a web application.
We’ll also cover some basic web server/web browser concepts. You’re
probably aware of some of these, but they’re worth mentioning again.
Caveat: Some of the statements below are untrue. Like the way that
saying "the world is round" is untrue. In some cases details that are
unimportant to the point being discussed are either ignored or
conveniently forgotten.
Web Browser / Server Interaction
A web browser is an incredibly simple beast. A Server
is even simpler.
Despite all the really fancy things you’ve seen web
pages do, underneath the skin the Browser and Server follow very simple
ideas. So simple in fact that most often people are left asking “is that
all?”
A web browser can only do 3
things:
1.
GET a page from a server.
2.
POST information to a server, accepting whatever page the web browser sends
in reply.
3.
Display the page.
On the other side all a
server does is:
1. Respond to a GET by loading that file off the disk and sending it to the
browser.
2. Respond to a POST by doing “something” and loading a file off the disk
and sending it to the browser.
This is remarkably
simple, yet at the same time remarkably powerful. But not, as it turns
out, powerful enough. In order to make it more powerful a small,
subtle, change was added on the server side.
1.
Respond to a GET by loading that file off the disk, parsing it,
and sending it to the browser.
2.
Respond to a POST by doing “something”, load a file off the disk,
parse it, and send it to the browser. OR
3.
Respond to a GET or a POST by generating a page dynamically, and
returning it to the browser.
The server-side power, sometimes called
server-side-scripting is in the small Parse step above. Note that the
client does not see the unparsed page. As far as the client is aware the
page came off the disk just like that.
Of course for the parsing to be useful, the page
needs to contain instructions that the server understands. Thus if your
server is IIS you can use ASP instructions. If your server is Apache
then PHP is ideal. The NetTalk web server uses its own special tags –
and we’ll cover those in more depth in a moment.
The Browser Interface is Limited...
This is the most obvious statement of all. The set
of native controls available in a browser are quite poor. This is both a
positive thing (you are forced to redesign your interface to reduce
complexity) and a negative thing (you can get fancy, but only by using
client-side tools, like JavaScript).
The immediate temptation is to spec your Browser
interface to have all the bells and whistles of the Windows interface.
Before you go too far down that road, I recommend making your first
iteration of the site as “clean” as possible. Start by making the site
work. Then work on making it slick. One big plus in using templates is
that changing basic behavior on a global level is not too difficult.
...But not as Limited as you Think
Most customers, and most developers dismiss web programming as providing exceptionally poor performance and usability to the user. A decade of
bad web apps have convinced developers that the fundamentals of the web
itself do not allow for good quality applications. This belief is just
wrong. The web is capable of highly interactive, highly responsive
applications. It's not the same as the desktop, that's true, but
the abilities of the web go far beyond what most web apps aspire to.
Ajax / Web 2.0
You may have heard much recently about the
so-called Web 2.0. Or you may have heard of the term AJAX. You may
also know that both of these items are very loosely defined, and seem to
apply to a broad spectrum of functionality rather than any one specific
thing.
The NetTalk Web Server uses AJAX techniques in many of the templates
that are provided. However the mechanics of this are completely
invisible to you the programmer. If asked, yes, your application uses
AJAX.
Fancy though this may seem, it’s ideal if your client does not notice that your
site employs the latest, greatest technology. Frankly he probably doesn’t care
about the technical details. (And to some extent, neither should you.) AJAX techniques are not impressive to end users because they accomplish things that
have been happening in Windows programs for over 10 years. Getting those effects
in a browser may impress the techie in you, but don’t expect your customers to
leap with wild unabandoned joy.
The Web Browser Interface has its own Navigation
Although the web browser “Back” and “Forward” buttons
can be hidden, the user can still do these options by right-clicking on
the page. When a user does this no request is sent to the server. The
browser simply changes page without the server knowing. Thus for the
server to attempt to know “where” the client is, is useless. The client
browser may be literally anywhere in the program at any time.
To make matters worse, clients can “bookmark” a
page and return there at any time in the future. They can type any URL
into the address bar at any time. They can abandon a form, or indeed
your whole site, without letting you know.
In short, the navigation around your web application is a lot more fluid
than the fixed navigation around a Windows application. This has an
effect on the way some things are constructed, and some items (which may
seem strange at first) are designed to cope with this issue.
The Web Browser Interface is Stateless
The most basic concept of a program that you have
deep in your subconscious is that a program is stateful. By that I mean
it not only always knows “where” you are, but if you depart (for example
to a lookup) you can always return, and local variables will be as they
were when you left them. Consider the following situation (in regular
Windows programming):
-
You are on a menu, and you click on the Employee Browse button.
-
Up pops the employee browse.
-
You
enter a locator and jump to the people starting with J.
-
Clicking a lookup button, you select the date range for when the
employee was hired.
-
Returning from the date lookup, you notice the J employees are still
located.
-
Highlighting an employee, you click on Update, change the phone
number and click Ok,
-
and
go back to the Browse.
-
The
J’s are still located, and the date range is still as you left it.
-
This is all so normal you don’t even notice it.
This scenario can be drawn something like this:
The sequence shown above is so basic it’s almost the first thing you
learned when you were programming. It’s so basic that you’ve forgotten
it even exists. That’s simply how things are.
If you like, take a moment now to consider what
you’d need to do if the following restriction was added to your
programming language:
“Only 1 window can be open at a time – If you
make a call to 1 procedure, you must at the same time, close the
existing procedure.”
In web programming, specifically stateless web
programming, that’s exactly the restriction that is added. Actually it’s
slightly worse than that, but we’ll get to that in a minute.
The browser, as a client-side tool, has absolutely
no idea where it is in the program. And the server sees each specific
request (from the browser) in isolation. In the windows program above,
the lookup, and form, knew to return to the browse when done. The range
and filter set on the browse did not change simply because we went to
the form.
Let’s consider the same set of actions as seen by a
web server.
-
The
user goes to the menu.
-
Clicks a button which goes to the Browse. (The menu closes.)
-
Enters a letter, and goes to the Browse (The original browse
closes.)
-
Clicks the lookup-date-button and goes to the calendar screen. (The
browse closes.)
-
Clicks the OK button and goes to the Browse. (The lookup closes.)
-
Clicks the update button and goes to the Form. (The browse closes.)
-
Clicks the OK button and goes to the Browse. (The form closes.)
Drawn, it looks like this.
Notice that in a web server program, you never
go back. In the description and picture above you never go back to
the browse. You always go forward to the next browse.
In a purely stateless world, the date lookup would
clear the locator. The Form edit would clear the locator and the date
filter. Not surprisingly programs written in a purely-stateless world
are extremely limited (at least for what we’re trying to do.)
The solution to this issue always falls into one of
the following 3 approaches;
1.
Store variables in the browser, as cookies. This works as long as the
data being stored is very limited, and as long as the user has cookies
turned on.
2.
Pass all the necessary variables from one page to the next on the
“command line” ie as part of the page address. While workable, and
infinitely scalable (from a number-of-servers point of view) there are
limits to the length of the command line.
3.
Create a “session” on the server. Store all the variables inside this
session. All you need to know is what Session belongs to the client
(which is passed in a cookie), from this you can
set appropriate default values onto pages being delivered. This is the
primary approach taken by NetTalk.
Probably the closest equivalent is writing a program where there are
only global variables, and procedure parameters, but no local variables.
Unfortunately with web programs there is one more wrinkle. There may
be more than 1 user accessing the same program at the same time. Each
user needs his own variable space. For this reason we use a Queue, and
hence we talk about the SessionQueue.
[Aside: Later on we’ll see that this may not be a
Queue at all, but that’s not important right now. Think of this big
variable storage area as a Queue.]
Sessions
The single biggest difference between a Windows
program, and a Web-Program is the issue of persistence. In a Windows
program one person is running a copy of the program, and that copy
belongs to them. When they’re done they close the program. With the web
there’s only one program, being shared by many users at the same time.
This would be fine if each page was completely
independent of each other. But in even simple programs this is not the
case.
In order to maintain the state of all the
server-side variables for each user, the idea of a Session is used. As
long as the user keeps passing their session number with each request then the server is able to keep track of the user. In NetTalk the session ID is passed as a cookie with each request, so the
mechanism for this is completely invisible to you.
Internally the settings (variables) are stored in a
Queue, called the Session Queue. You can also use the Session Queue
yourself to store your own variables for each user. There are methods to
read and write values in the Queue.
Parameters (Values)
When the Browser asks for a page, it is also possible for it to pass parameters
to the server. If the request takes the form of a GET then the parameters appear
in the URL. In the log you can see them here:
When the request takes the form of a POST can appear in
the URL, or they can appear below the header. In the log they look like
this:
Regardless of whether the request is a GET or a POST,
all these values are automatically parsed for you and placed in the
Value Queue. If a value appears in both the POST section, and the URL
then the URL value takes precedence. (If a cookie exists with the same name,
these overwrite the cookie value in the Value Queue.)
You can use the method
value = p_web.GetValue('name')
to get the current value, and
p_web.SetValue('name','value')
to set the value to a specific value. To determine whether a value exists
use
p_web.IfExistsValue('name')
These settings are only available during the course of
this thread. They are not (by default) stored for use in other threads. If
you do want to save them, then be sure to write them into the SessionQueue.
As we'll see later this is an important point. Indeed in most cases, should
you need to work with a value directly, all you should be doing is storing
it in the session queue. Then in all other places in your code you use only
Session Queue data. The method to store a value is
p_web.StoreValue('name')
Cookies
Warning: Some people turn the cookies
functionality off for various reasons. NetTalk depends on the cookie
mechanism to pass the Session ID with each request. The use of cookies
though should be used sparingly. There are very few cases, outside the
Session ID, and possibly login information, where cookies are actually
required. Data should be stored on the server, not the client.
A Cookie is a value, sent by the browser. This value is
attached to all GET's and POST's which you receive from the browser.
Incoming cookies are automatically parsed for you, and added to the
Value Queue.
In order to set a cookie in the Browser you use the
p_web.SetCookie('name','value')
method.
If used like this then the cookie will be valid in the
browser for as long as the browser is open, but will then be discarded. To
make the cookie more permanent you need to set the date, and optionally the
time, when it will expire.
p_web.SetCookie('name','value',ExpiryDate,ExpiryTime)
The ExpiryDate and ExpiryTime are standard Clarion Date
and Time fields (ie LONG's). Note that the time and date are relative to GMT
time, not the time of the browser, or the time of the server.
To delete a cookie currently being stored in the
Browser use the DeleteCookie method
p_web.DeleteCookie('name')
Note that cookies can only be accessed by the site that
"wrote" them. It is not possible to access the cookies placed in the site by
another browser.
Example 7 contains an example of using cookies. In this
example the LoginForm procedure saves the value of the login and password
fields, for 30 days, so if the user returns to the site the login is
"remembered".
There is no need for special functionality to "Read"
the cookie. All the cookies that belong to a site are sent automatically by
the browser with every single request. These are automatically placed in the
Value queue for you so you can read them using the normal
p_web.GetValue('CookieName') syntax.
Remember though, there are almost no cases where a
cookie is the right solution to the problem. In almost every case where you
think you might need a cookie, the Session Queue, or a User Data Table is a
better place to store the data.
Tip: Cookie names, like all HTML field names cannot
contain a colon. However if the name contains a double-underscore, then that
will be translated into a colon by
GetValue.
For example;
p_web.SetCookie('Loc__Login','NetTalk')
would be accessible using
p_web.GetValue('Loc:Login')
Session ID Cookie Name
The
SessionID cookie is a built in cookie
that allows each request the user makes to be linked to their session.
This is done automatically and you do not need to do anything. With very
few exceptions this is ideally the only cookie your app should make use
of. Any other user settings can be set at the server side in Session
Values.
Up to NetTalk build 9.23 the default name for the session ID cookie is SessionID. This name
was used for both secure, and insecure sites.
From build 9.24 this default name is SessionID for HTTPS connections,
but SessionIDx for non secure connections. This allows both secure, and
insecure sites to exist on the same domain. This is
necessary because starting from Chrome 52 and Firefox 52 insecure
sites can no longer overwrite existing cookies for secure sites.
Should you wish to you can override the name of the cookie.
This is done in the web handler procedure, in the
SetSessionIDCookieName method. At this point in the code you can
test
self.SSL to see if the connection is
secure or not. It is recommended that you use different names for
secure, and insecure sites.
Having a unique cookie name for
secure and insecure parts of the domain means that sites that are
intentionally mixed may have limited functionality.
Browsers understand 3 languages, and none of them are
Clarion.
ul>
HTML is the "Markup language" - this contains the
Structure of the document. HTML is made up mostly of <tags> which
describe the structure of the text being displayed.
CSS is the "Visual language" - this determines what
the page looks like, and (some of) the page layout.
JavaScript (which is Not Java) is a programming
language with variables, and loops, and things like that, which is able
to manipulate both the HTML and the CSS on the page.
As a NetTalk developer you do not need to know any of
these languages in order to get started. However you will find yourself
picking up HTML as you go, and likely learning CSS as well. More advanced
developers will also start using a line or two of JavaScript in strategic
places.
Your NetTalk app will make use of all three languages,
by generating HTML, and by including the default NetTalk CSS and JavaScript
files.
NetTalk includes, the jQuery
JavaScript library. jQuery contains some fantastic JavaScript code, which we
use as a building-block, and also some UI components that improve the user
interface of your NetTalk site. It also contains a "standard" theme, based
on a system called ThemeRoller, which allows for easier changes to the CSS
files. jQuery is also a "pluggable" system with a large number of plugins
already available on the web.
With the NetTalk WebServer we’ve tried to place as
few limits as possible on the programmer. We like flexibility ourselves,
and we appreciate it in the tools we use. But every now and then we need
to impose a condition simply because without it the system will not
work.
As far as your tables goes, the NetTalk Web Server
requires tables to have at least 1 unique, unchanging key. This is so
that records that are used in one place, and have to be reloaded in
another, can be properly identified.
Consider for example a Form. When a form is opened,
so that a record can be changed, the record is read into memory, and
displayed in the web page. Then that thread finishes. Later on the user
clicks Save (after editing some data). At that point the original record
is reloaded, the changes applied, and the record is saved. But the
system needs to know which record was being edited in order to do this.
If the user were allowed to edit the very field used to identify the
record it could not then be reloaded.
If you had the luxury of going to University you’ll
probably already have this key. Good database design courses teach this
approach anyway as a pre-requisite to having a good database design.
If you don’t have a unique, unchanging key then you
can add one to your tables. Set the key as AutoNumbered, and forget
about it.
If you are using the CapeSoft Replicate product in
your dictionary then you’ll already have a non-changing unique key in
the GuidKey that Replicate requires. You can use this key as your unique
key in NetTalk WebServer.
The Server interacts with the browser in a very
specific way. The Browser makes a GET or a POST (as we saw above). The
server responds by providing a web page. Once that’s done (typically
taking a small fraction of a second) the server then forgets all about
the request. During that time there may be instructions to copy items
in, or out, the Session Queue, but once the page is delivered it is
immediately forgotten.
Of course the Browser doesn’t forget. It displays the page to the user for as
long as the user likes. The user then clicks a button, or a link, and another
POST or GET is sent to the server. For another brief instant the server awakes,
does some work, and forgets.
This is a very efficient approach because there are
no resources (no RAM or CPU) on the server being used for the time the
user spends staring at the screen. Thus supporting many users “at the
same time” is not a big problem, because each request uses the server
resources for a very brief period of time. Compare this to the
WebBuilder, or Terminal Server approach of having a complete copy of the
program, in memory, for every single connected user, for the duration of
their entire session from start to finish.
I cannot stress this enough - There is no "connection" between the
Browser and the Server. This is why it is impossible for the server to
"push" information into the browser. The browser makes a request, gets a
reply, and the connection is severed.
Every time the user does something (clicks a link,
clicks a button, etc) a request is sent to the server. Each request
processed by the server is done on its own thread. In other words;
a.
A
request arrives at the server'
b.
A
new thread is started to handle the request'
c. The thread immediately performs any actions, and sends a page back to
the browser'
d.
The thread ends.
Thus unlike a windows program, the lifespan of each
thread is very short.
The Web Server is capable of managing multiple requests
simultaneously.
Thread Pools
By default each incoming thread is processed on its own thread. The request comes in and the WebServer starts a thread for the WebHandler.
The WebHandler then processes the request, and returns the reply. The thread then ends.
In a normal application the "cost" of starting and ending the thread
is very small. So this approach is both simple, fast and effective. However
the time it takes to start the thread is proportional to the amount of
global threaded data and objects that you have. The biggest part of the data
is of course the number of THREADed tables in the dictionary.
Therefore in some apps the cost of starting and ending threads becomes
significant. To overcome this problem a feature called "Thread Pooling"
exists.
Please
Note:
Thread Pooling cannot be used in the Multi-Site host application. If an
application is used as a DLL to the Host, then the thread-pooling features
there will be ignored.
The idea is that instead of closing when the request
completes, the thread hangs around for a bit. If another request comes in
then the WebServer can pass the request to the existing thread, instead of
having to create a new thread. If the thread is not used for some period of
time then it quietly closes in the background.
To make use of thread
Pooling virtual no changes need to be made to the app. All that has to be
done is;
a) The creation of a WebHandlerPoolThread procedure and
b)
The activation of the feature in the WebServer procedure
Create the WebHandlerPoolThread procedure
This is a simple window procedure,
with the window MDI attribute OFF. No controls are required on the window.
The prototype for the procedure should be;
(string p_PoolNumber,string p_waiting, string p_server)
Add the NetTalk Extension, WebServer Pool Thread Window to the procedure.
Set the name of the Handler Procedure (typically WebHandler) in the
extension properties.
Activate Pooling in the WebServer procedure
Two settings for the thread Pooling can be found in the WebServer
extesion, Performance tab.
The name of the WebhandlerPoolThread
procedure, and the maximum number of pool threads can be entered here.
If all the pool threads are busy then an additional pool thread is
started until the maximum number of pool threads is reached. After that
individual threads (up to the maximum allowed number of threads) will be
created in the usual way.
IIn Windows a program usually runs only after a user
has logged in. However if you are creating a Server program then
typically you want it to run before a user logs in. (For many servers,
no user logs in at all unless there are other tasks to perform.)
Services can be made in Clarion 5.5 and earlier,
but they are not reliable and cannot be multi-threaded. Which means
they’re pretty useless in the real world. In Clarion 6 you can turn any
program into a service, either by using the Windows 32 API, or (if
you’re lazy like me) by using a template. Not surprisingly we recommend
SelfService (https://www.capesoft.com/accessories/selfservicesp.htm)
for making services./p>
One of the first decisions you’ll need to make is
if the site is going to use Frames or not. This is a key starting point,
because this will determine the way you build the site.
It is of course possible to make a site that works
using both Frames or No-Frames, but that is extra work, so start with
one approach, and add the other approach necessary if you want to.
Frames allow you to divide the visible window (in
the browser) into different “panes”. Each pane can then be updated
individually, and one pane can change another.
The primary disadvantage with frames is two-fold.
a.
Search Engines struggle with them, and
b.
The address bar in the browser stops working when you have frames.
If your site is designed to be used as a program,
with a login and then the ability to do stuff then Frames are good. If
your site is going to be a public site, and you want Google to search it
then don’t use frames.
Sites without frames however are also a bit more flexible when it
comes to layout, and give the programmer somewhat greater freedom when
it comes to doing stuff. Personally I've moved away from using frames to
building apps that do not use frames.
TLS
In the web world there are 2 kinds of servers.
Secure servers, and non-secure servers. If you are transferring
information between the server and the client that you want to protect
(and almost all web apps fall into this category) then you want your server to be
secured using TLS.
This is a topic that is very simple on one hand,
but also can get bogged down in details very quickly. NetTalk allows you
to build Secure servers, non-secure servers, and servers which do a
combination of both. This is discussed in detail in the document
Building Secure Web Sites.
A very quick overview of a typical NetTalk app is useful here. Each of these
parts is discussed in more detail in a moment.
-
Each web server app has a WebServer procedure and a
WebHandler procedure. These handle the incoming responses.
-
The app may contain static htm pages stored on the disk.
-
The app may contain one or more NetWebPage procedures,
which generate a complete HTM page on demand. You cannot embed one Page
inside another Page.
-
The app may contain one or more "Control" procedures.
These are things like browses and forms. They can be embedded on a page, or
they can be called directly as a page.
-
The app may contain one or more "Source" procedures.
These can be embedded on a page, but not called directly as a page.
Each WebServer you create will listen on a specific
port. The default port for web servers is 80, but only one server can be
listening on a single port at a time. If you are making a general
purpose site, one aimed at the whole internet community then of course
you will need to run on port 80. If you are making a non-internet
server (for example, you’re just making a way to access your program
across the network) then you can use your own port number. Port numbers
above 1000 are generally considered to be “open”.
The default port for Secure servers is 443. However
any port can be secure.
An app may contain multiple web servers. Each Web
server listens on a single port, but as a whole the app is listening on
multiple ports.
The WebServer procedure is the procedure that
listens to the open port. In the examples it’s always called WebServer,
but the name is not fixed – you can call it anything you like. Obviously
if you have more than one then they need unique names.
The WebServer procedure consists of a Window
procedure, to which is added one or more NetTalk Objects. These objects
are set to the NetWebServer base class. Some settings for the server
will appear on the settings tab. The details of each setting is
discussed in the
reference document.
The WebServer procedure listens for incoming
requests. For each request the WebHandler procedure is called, on a new
thread. The name of the WebHandler procedure for this web server is set
on the Settings tab.
In NetTalk the multi-site Host application was introduced. This
allows a single server to share port 80 between multiple apps. For more
information see the document here.
The WebHandler procedure is based on the
NetWebHandleRequest procedure template. When adding this procedure it is
recommended to use the NetWebHandleRequest
Default .
The WebHandler procedure will become a good place
to embed some code later on, but for now there’s no hand-code necessary here. There are also no template settings
of any note in this procedure.
Each web app will contain at least 1 WebServer
procedure, and at least 1 WebHandler procedure.
Many WebServer procedures can share the same
WebHandler procedure.
Many NetWebServer objects can share the same
WebHandler procedure, even if some of the NetWebServer objects are
secure, and others are not.
Legacy Templates
In order for all the Web Server features to work correctly, you
need to make sure that the Smart Generate feature in the Web
Handler procedure is turned on.
Web Hander procedure, Extensions, NetTalk or
NetSimple Object Extension, Class Tab, Smart Generate option.
|
A NetWebPage is the basic structure that starts each web page. On
this page you can place your own HTML code, menus, Browses, Forms and so
on.
To Create a new page use the NetWebPage Procedure
Default.
Each page has a number of settings which are
described in the
Reference document.
To a Web Page procedure
you can attach one or more extensions. These extensions include:
NetWebFrame - Frame Extension
NetWebBoder - Border Extension
NetWebMenu - Menu Extension
Each of these extensions will be discussed in more
detail in a moment.
Using HTML you can describe the content of the
page, including other NetWeb controls (such as Browses and Forms.) You
can also embed custom HTML source, from other procedures in your app
(procedures based on the NetWebSource procedure template.)
It should be noted that you cannot embed another
page on this page.
An example of a simple NetWebPage can be found in
example 1.
In this example the IndexPage procedure contains
the following HTML;
<!-- Net:PageHeaderTag -->
<!-- Net:MailboxesBrowseControl -->
<!-- Net:PageFooterTag -->
Essentially this page is made up of 3 parts. Each
part is constructed by a separate procedure in the app. Using the
NetTalk tag system procedures can be embedding on the page.
Tags
A NetTalk Tag is basically a wrapper around some
variable, or procedure, that you want to include at this point in the
page. It takes the form
<!--Net:Something -->
In this case that Something are procedures, so the
HTML generated by those procedures are included on the page.
If the Something is not a procedure, then it might
be a session variable. For example you might have a session variable and
you can include that on the page. You’ll see a lot of this happening as
you progress through the examples.
Static pages (usually .htm pages on disk) that contain tags have to be parsed by the
server. If you use a static
page on the disk these tags can still be used. However in that case the
first line of the page must be
<!-- NetWebServer -->
It's important to note that you can put any tag
you like into your web page, as long as you handle it in the
ProcessTag method in the WebHandler procedure. However there are
some built-in Tags which you can use without having to add code to
the WebHandler procedure.
-
<!-- Net:FunctionName
-->
This tag will call the specified function in that point in the code.
This can be a function based on any of the NetWeb procedures. (Note:
Not all the procedures make sense at every point though.
-
<!--
Net:s:SessionVariable -->
This will embed the current contents of the session variable on the
web page. If the variable should be formatted (for example a DATE
field) then make sure the Picture parameter of the session variable
is set using either
p_web.SetSessionValue('name','value','pic') or
p_web.SetSessionPicture('name','pic')
-
<!-- Net:v:Variable
-->
This will display the current contents of a Parameter at this point
in the web page. In other words the same as (2) above, but uses the
Value queue, not the Session queue.
-
<!-- Net:f:FileName
-->
Includes a (html) file at this point of the web page. Similar to the
Clarion "INCLUDE" command. Use this to include static bits of HTML,
stored in static files on the disk, inside dynamic web pages.
Tip: If the file contains raw text, not formatted HTML, then you can
wrap the tag with the <pre> Html tags. For example
<pre>
<!-- Net:f:disclaimer.txt -->
</pre>
The file itself should be placed in the
Web folder. It can be in a sub folder, but then the filename should
contain a web-relative path. for example;
<!-- Net:f:/loggedin/header.htm
-->
The encoding of the files can be complicated. Assuming your site
is serving pages as utf-8 (which it should be doing) then ideally
your files are also encoded as utf-8. Unfortunately there is no way
to reliably detect if a file is encoded as ANSI, UTF-8 or UTF-16.
Therefore NetTalk has to guess the format (to determine if
conversion is necessary.) There are however techniques which you can
use to make the guessing more accurate. The order of the assessment
is as follows;
A. If the file has a byte-order-mark then that
will be used to know is is already in utf-8 or utf-16 format.
B.
If the file starts with the text; <!--
utf-8 --> , then it is assumed to be in utf-8 format.
C.
If the file starts with the text; <!--
utf-16 --> , then it is assumed to be in utf-16 format.
D.
If the file contains the text <meta
charset="UTF-8"> then it is assumed to be in utf-8 format.
E. If the file contains the text <meta
charset="UTF-16"> then it is assumed to be in utf-16 format.
F. If the second, and fourth characters in the file are CHR(0) then
it is assumed the file is in utf-16 format.
G. If all the above
are not true then the file is assumed to be in ANSI format.
- <!-- Net:x:SessionVariable -->
This is exactly the same as a
session variable, except that the contents of the variable will not
be xml encoded. Use this when you expect the session value to
contain actual HTML code (which you want the browser to see as
actual HTML code.) In this case however you are required to make
sure the contents are actually xHTML compliant.
- <!-- Net:h:HostVariable -->
Host variables
are like session variables except that they are "global" to all
sessions. For more information on host variables see Host Variables.
- <!-- Net:d:something --> Tags
The
d: tag allows you to access various current date and time values and
include them in html. The following identifiers are recognised;
tag | meaning |
<!-- Net:d:today--> | Display
today's date, format using
p_web.site.datepicture. |
<!-- Net:d:clock--> |
Display the current time, using @T4 as the format picture. |
<!-- Net:d:day--> |
Display the day number in the month |
<!-- Net:d:month--> |
Display the number of the current month in the year |
<!-- Net:d:monthname--> |
Display the name of the current month (January, Feburary and so on) |
<!-- Net:d:year--> |
Display the current year value. |
<!-- Net:d:dow--> |
Display the day number in the week, (Sunday=0, Monday=1 etc.) |
<!-- Net:d:weekday--> |
Display the week day name, Monday, Tuesday and so on. |
<!-- Net:d:dayname--> |
Display the name of the day of the month, eg 1st, 2nd, 3rd, 4th and so
on. |
<!-- Net:d:hour--> |
Display the current "hour" part of the time. |
<!-- Net:d:minute--> |
Display the current "Minute" part of the time. |
<!-- Net:d:second--> |
Display the current "second" part of the time. |
A good example of embedding browses and forms in static
pages is the appropriately named Pages (47) example.
See also
Host Variables
The Frameset extension allows you to create the frames that your app
will run in.
Before making use of the FrameSet extension you
need to decide if your site will be using Frames or not. See the section
above entitled Frames for more information on making this decision.
A good example of the use of Frames is Example
number 4 – “Frame with Menu”.
Frames, as an HTML concept, are both simple, and
complex. There is no easy way to give you the power in Clarion, without
also giving you some complexity, so this is by far the least intuitive
of the extension templates. Taking a moment to understand frames is a
good idea if you want to use them.
A FrameSet is an HTML term that denotes the
breaking up of the window into multiple pieces. Each of these pieces in
turn can contain either another Frameset, or a Frame. Thus it is a
somewhat recursive process which can make it confusing.
To make matters worse, the manner of breaking up
the window is also flexible. You can break the window into multiple
rows, or multiple columns, or multiple rows and multiple columns.
Let’s use the Example 4 Frameset procedure as an
example. Here’s the result as seen in the browser:
In this case the window is split initially into 2
rows. The top row becomes the Header of the eventual site. The bottom
row is split into 2 columns. The left column is where we’ll put the Menu
for the site. The right side of the bottom row is where the rest of the
site will appear.
It’s important to note that each part of the above
window is ultimately a Frame. And each Frame has a name (which you set.)
By using this name (also known as the “target”) you can determine what
pages appear in which frame.
The Framset page itself does not contain any HTML
of its own. It just provides the “container” into which other pages,
based on NetWebPage, will appear.
Web Border Extension
One way of making pages look better is by placing a
border around them. NetTalk has an extension template that simplifies
this for you.
All the examples use this extension in the
PageHeaderTag and PageFoorterTag procedures.
The border extension includes different style
possibilities, as well as allowing you to specify the HTML contents to
appear inside the border.
You can use the Web Border extension on NetWebPage
procedures, as well as NetWebSource procedures.
Borders are cosmetic in nature – not functional.
Menus are an important part of any app, and the Web
is no different. However unlike Windows there are no standards as to how
menus are implemented, or how they behave. NetTalk Web Server includes
in-the-box support for 5 menu styles. You can of course implement any menu system you like if
you have a sufficient knowledge of JavaScript and HTML.
The first supported menu is the Chrome menu. This is a horizontal menu bar, with drop-down menu items.
The second menu style is the Accordion style. This is a vertical menu and works well in both Framed,
and unframed pages. The key thing to note about the accordion style is that only one section can be open
at a time. As a section is opened, the other sections are closed automatically.
The third menu style is called the Double-Drop menu. Similar to the Chrome style this menu is
horizontal in nature. However the Double-Drop menu supports menus within menus, and also uses the
ThemeRoller styles.
The fourth menu is the XP-Panel menu, which in NetTalk 4 was called the XP-TaskPanel menu. It
is similar to an Accordion menu, except that multiple sections may be open at the same time. It
is styled to be similar to a Taskpanel in Windows XP.
the TaskPanel menu is similar to the XP-PAnel. However like the Double-Drop menu, it supports
both menus-inside-menus and Themeroller styles.
A procedure template called NetWebSource exists.
This allows you to create HTML “snippets” that can be included in other
pages. These could contain anything you like, however “Browses” and “Forms are better handled using those specific templates.
A good example of using the Source procedure in the
Examples are the PageHeader, and Page Footer tags. Since the header of
each page is common to all pages, this can be defined in one place (in
the NetWebSource procedure) and then “embedded” onto multiple pages.
Sometimes it’s possible that you want to use a Browse,
Form procedure as a page in it’s own right. NetTalk
makes this possible through the concept of a generic page. If you use a
control procedure (i.e. a Form, Browse) in a place where you
would usually use a Page, then NetTalk will wrap the control up as a
page and serve it.
It’s likely that you may have either a common
header, or common footer, (or both) for these pages. In the first 3
examples you can see this in action via the PageHeaderTag and
PageFooterTag procedures. These 2 source code procedures include HTML
that you want included at the top, and bottom, of every generic page.
The name of the two tags are set in the WebServer
procedure, under the Settings. The generic header tag is
'<!-- Net:PageHeaderTag -->'
and the generic footer tag is
'<!-- Net:PageFooterTag -->'
Notice the standard wrapping for a NetTalk tag
<!-- Net:Something -->
The use of a procedure name in a tag means that
that procedure will get called and the HTML from that procedure will get
generated into the page at that point.
The generic header and footer do not apply to
NetWebPage pages. In example 3 the procedure IndexPage contains the
following HTML – explicitly including the header and footer if we want
them.
<!-- Net:PageHeaderTag -->
<!-- Net:PageFooterTag -->
In the beginning we all had desktop computers, with large screens (circa 12 inches to 17 inches) and life was easy.
As time progressed though the size of screens changed dramatically - from 4 inches on the low end to 30 inches on the high end, and
everything in between.
Web apps had to evolve to handle this
range of sizes. They became more responsive, able to respond to
different screen sizes by adopting different layouts, hiding or unhiding
extra information, and so on.
A
Responsive Web App
is thus an app which Responds to screen size.
As the web has
evolved, so NetTalk has evolved, but with the added proviso that
backward compatibility is ever important.
The primary way the web
evolved was to move from layout based HTML <tables> to layout based on
HTML <div>'s. This change, coupled with extra power added to the CSS
language allows for a much for flexible layout - one which is able to
best respond to the user.
NetTalk 10 added support for DIV mode.
In DIV mode all <tables> (on browses, forms and child-layouts) can be
switched to <divs>. In NetTalk 10 the default is still Table layout,
however in future NetTalk releases this will change to default to <div>
layout. If you have an existing app you will need to switch the app
manually from TABLE mode to DIV mode. This is done in the WebServer
procedure, Settings / Defaults tab - on the Browse and Form tabs.
It is likely you will see some differences in your application after
making this switch. In some cases the shipping CSS can be extended to
better support the switch, but there are some differences you will need
to understand.
Browse Width
The biggest fundamental difference has to do with width.
Specifically <table> width and <column> width.
Tables are
very flexible when it comes to width. They automatically adjust
their own width, and the width of every column, to take up as much
space as they need, and at the same time as little space as
possible.
One way in which this is evident is that columns
will automatically size themselves to fit the visible content.
Equally a table will limit it's own width to just-fit the data -
often resulting in a browse which appears to be "cramped" against
the left-hand edge of the page. It is possible (but not required) to
set the size of a table though, and the size of each specific column
in the table.
<div> based layouts are different. In a <div>
based layout it is necessary to set the width of the browse or form
(defaults to 100%) and each column is sized relative to the other
columns. So, for example, in a browse you do not set an absolute
value for the width of a column, but rather the relative value for
the width of a column.
(Aside: Taking about this is
complicated because on a small screen the browse adapts, so using
words like "row" and "column" don't really map to what you see on
the screen. But for purposes of this discussion we're considering a
browse as it appears on a large monitor.)
Another side effect
of the <div> columns is that because they have this relative width,
the column does not get wider to accommodate wider content. So as
the screen gets smaller more and more content can become truncated.
Browse Cells
In
the past a browse contained a single item in each column. A cell ( a
<td> in HTML terms) in the table contained a single piece of data,
or a single button, and so on.
NetTalk now however allows you
to put multiple items together into a single cell. This means that
columns can be combined together - for example the typical column
for a change button, and the column for a delete button, can now be
merged into a single cell, which can making sizing a lot more
effecient.
This has a dramatic effect as the screen gets
smaller, because as the screen shrinks so the browse switches from
being a horizontal layout, to being a vertical layout. In this case
each cell now takes up a whole horizontal row. If you have multiple
buttons in the cell then they can still appear next to each other.
If you have the buttons in different cells then they appear
underneath each other.
A form is a good example of the difference between
a Web app, and a Windows app.
In a windows App the form procedure starts, and
remains running until the user closes the window. The act of loading the
record, displaying the form, validating the input, and saving to disk
all takes place in one place. Importantly one single procedure drives
all this functionality.
In a Web app the form behavior is split over several separate threads. When the user clicks on a link that opens the form, a
thread starts which loads the record, and constructs the Html. This Html
(this “page”) is passed to the browser and the thread closes.
When the Save button on a form is pressed then a
second thread starts. This thread will validate the record, and save it
to disk.
In a NetTalk app it is convenient though to keep
all the code related to a single form in one procedure. This makes
things more organized and easier to keep track of. However this one
procedure performs a few different functions. Each function should be considered as
distinct from the others. Local variables in the form procedure are
NOT preserved between calls. The library ends up calling the Form
procedure many times. A parameter to the procedure determines which part of
the process the Form procedure must now perform.
So far I’ve described the form as a Page. And that
is not strictly accurate. The form is more like a Control. It can exist
on a page all by itself, but it’s more likely to be just one part of a
more complex page. A web form procedure in your app is thus Not a page,
but just a control to be included on other pages.
In the App a form is created using the NetWebForm
procedure template default.
The specific options for the form are covered in
the Reference document.
Advanced
Forms can be dynamic. In other words when the user
Selects, or Accepts any of the fields, then one or more items on the form
can be updated. This is done asynchronously (in the background) and allows
you to make your forms highly interactive.
Some of the interactivity is already built in for you.
For example if you have a lookup field, and you've entered the code and
description fields of the remote table on the Lookup Settings tab, then your
lookup field is already dynamic. Try typing in the code rather than looking
it up. Notice the way the description changes? That's the sort of thing I'm
talking about here.
Example 21, UpdateInvoices procedure, has a good example of this when
selecting a customer.
Aside: Try typing in the description into the entry
field and see what happens.
Adding your own interactivity to a form is a 2 step
process.
First identify what needs to be changed, and when it
needs to be changed. Each row of the form consists of 3 parts, the Prompt,
the Value, and the Comment. Each part can be updated, and any number of
parts on the screen can be updated on any event. You can also update
Prompts, Values and Comments for other fields when this field changes.
The second part is embedding the correct code (in the
right place) to calculate the item you want to display.
For example, let's say you want to update a Comment
Field when a Value is entered. To keep it simple let's assume you're going
to validate an Email field to make sure it contains both a @ and a .
So, step 1, identify (and set) the fields that need to
be updated. This is done on the Client-Side tab. For the email field set
Refresh: Comment on.
Then step 2. In some cases you won't need to do a step
2 because the templates are already doing it for you.
In other cases the code you've entered into the comment
field may be sufficient. Like here
(In this case you've made your own function, called
EmailCheck, which is doing the validation for you and returning an
appropriate comment. Notice the SessionValue is always used - the whole
comment field looks like this
EmailCheck(p_web.GetSessionValue('Cus:Email'))
But in some cases you'll need to embed code in the
routine that generates this comment (or prompt, or value).
Remember each prompt, value, or comment field can be
updated, and for each of these cells a routine has been generated called
either prompt::field,
value::field or
comment::field where
field is the name of the field. For example, in this case the entry
field is cus:email, and we're looking to set the comment, so use the
embeditor to find the comment::cus:email
routine. In this routine is an embed point, and here you can set
loc:comment to be what it needs to be. For example;
If Instring ('@',p_web.GetSessionValue('cus:email'),1,1)
> 0 and |
Instring('.',p_web.GetSessionValue('cus:email'),1,1) > 0
loc:Comment = 'Email ok'
Else
loc:Comment = 'Email not ok'
End
In the prompt embed point you would set loc:prompt and
in the value embed point you would set the Sessionvalue of the field, using
p_web.SetSessionValue('fieldname',value).
Debugging Tips
It can be quite hard to debug this asynchronous
updating when it's not working. Since there are different parts involved
it's hard to know where to look, and which part of the process isn't
working. Here's one process you can use to help identify the source of the
problem.
1) Do a Regenerate All. If the code you are adding
hasn't been correctly generated into the WebHandler (by the template) then
that's the easiest possible problem to fix.
2) First thing to determine is if the event on the
browser is triggering an event to the server as expected. You can do this by
watching the Web Server window. Click on the Clear button to clear the log,
then change a value. You should see and item appear in the GET part of the
log. Like this:
Note that the request included the Event (Event:Accepted
= 1) and also the new value for the field that changed. You can also see the
request was asynchronous because of the XMLHttpRequest item in the header.
3) So the request is being generated, but the response
is not making it to the browser. Or perhaps it is, it's just not the
response you're expecting. The most likely place for an error is in the
embed code you may have added. (Don't worry, it's normal to make errors when
writing code. This isn't about blame, it's about correctly identifying the
source of the problem so it can be fixed.) What I usually do in this case is
comment out my embed code entirely, and replace it with a simple bit of
code, that doesn't have a bug, and which makes a visible impact in the
browser. For me that's
loc:comment = Random(1,1000)
If this works then I examine the embed code a bit at a time to see which
part isn't working as expected.
Tip: Remember that this little routine is being called asynchronously
and none of the rest of the Form has necessarily run. So if you need to
access other data tables, then you will need to open and close them
appropriately. Also you should always use Session variables rather than file
variables for the actual form fields. That's why in the example code above I
used p_web.GetSessionValue('cus:email')
and not just cus:email.
4) But what if the simple use of Random suggested above
doesn't work? Well then the most likely reason is you missed out step 1. But
what if it still doesn't work? Well then the next step is to examine the
packet that is being sent to the browser. You can do this by adding
NETSHOWSEND=>1
to your project, in your app, as a Project Define. Then
use the free DebugView program (available from
SysInternals) to monitor the outgoing packets.
Browses
The browse is the root of many applications. It
allows you to display data on the screen in a tabular format. In many
way the browses provided by NetTalk WebServer are similar to the browses
you are already familiar with in Clarion.
A browse can have many different settings. These
are covered in more depth in the
Reference
document.
Browses can be page-loaded, or file-loaded. They
can be sorted by clicking on the header. They can have locators.
In most cases a browse is simply a tabular list of data, something
that will be instantly recognizable to any Clarion developer.
However tabular data can be displayed in many creative ways, some of
which don't look like a browse at all. The following are two
browses, one on the left, and a child on the right.
One big difference between a Web app and a Windows
app is the concept of global variables. In a Windows app one user is using
the program. Multiple copies of the program may be running, but each
copy belongs to only one user. So you can set global variables which
implicitly belong to just that user.
A web app can be serving multiple users (at the
same time) from one single exe. So you can't assign a global variable to a
single user. All users can see all the globals all the time. (At least
that's how your program would behave if you used globals.)
In NetTalk you use Session Variables to assign
values to a user. we discussed Sessions earlier. Each user has a single,
unique, session number which they keep for the duration that they are in
the browser. If you need to store a variable you do it by writing the
value into the Session Queue. And you can fetch it from the Queue when
you need it. For example;
After the user logs in we want to store their name.
So we go to the place where their login is validated (ValidateUpdate
Routine) and we add some code;
p_web.SetSessionValue('LoginName',clip(loc:login))
Later on we can get this value, and put it in a
local variable by doing
loc:login =
p_web.GetSessionValue('LoginName')
if you wanted to embed this session variable inside
an HTML page then you can put, inside your HTML,
<!-- Net:s:LoginName -->
The default deployment folder structure looks
something like this;
HTML files are placed in the web folder. The WebServer is limited
(by default) to supplying files in, and below, this folder. Default
is appPath\web where appPath is the location of the
Exe.
You can set the web folder on the NetTalk settings
on the WebServer Window.
Below this folder are a
number of folders. You can override these as well, but by default they
are:
Scripts (contain .js files)
Styles (contain .css files)
Log
(contain Log files if logging-to-disk is turned on)
If these folders do not exist they will be created
for you.
In addition it is recommended that images are
placed in an “images” directory. However this is not a requirement, just
a suggestion.
You need to deploy your program (exe, dlls, tps
files whatever) as normal.
NetTalk web server does not require any additional
programs on the machine. (i.e. no IIS or Apache etc.)
You need to deploy your web folder onto the server machine as well.
For more information on program deployment see the
Deployment doc.
Multiple Data Sets
It is occasionally desirable to identify the user, and then direct them to a specific data set. The same program will be
running, but the user will be using one set of data (and potentially a different web folder) to other users of the server.
The multiple-data-set approach works well for both TPS and SQL data
stores.
To implement multiple data sets you need to
- Set the dictionary correctly
- Identify the user on each request
- Set the filenames or owner strings for the tables appropriately.
Setup Dictionary
Tables in the dictionary need to use either a unique Full Path Name
variable (for TPS and other ISAM file type) or a Owner Name variable
(for SQL file types.) Multiple SQL files can share the same Owner,
however multiple Owners (hence different databases for different tables)
are also allowed.
The variables used MUST be set as THREADed.
Example
A SQL Table Customers has the Owner Name
set to
!Glo:Owner
A global, Threaded, String
Glo:Owner is also created (in the dictionary, or the app.)
Example
A TPS Table Customers has the Full Path
Name set to !Glo:CustomersName
A global, Threaded,
String Glo:CustomersName is also created (in
the dictionary or the app).
Identify the User
There are a few basic ways to identify the dataset the user is needing.
- Use the HostName (ie the URL the user typed) to determine which
set to use. OR
- Have a separate USERS database, in which you store the location.
The user then uses a generic login procedure, which then sets a
Session Value, and the SessionValue is then used to set the data
location.
Using the Host Name
The Host Name (ie the URL the user typed) could be used to differentiate the data set desired.
For example www.cidc2015.com and www.cidc2017.com are two host names that point at the same server.
The server decides which site to serve based on the URL the user used.
In the same way, sub domains can be used. For example capesoft.somesystem.com might route to a different
database than softvelocity.somesystem.com.
Setting
information based on the hostname can be done in the WebHandler
procedure, in the RequestHostSet method, before the parent call.
Example
Access:Sites.Open()
Access:Sites.UseFile()
Sit:HostName = self.RequestHost
If Access:Sites.Fetch(Sit:HostKey)
= 0
self.site.WebFolderPath =
Sit:WebFolder
self.site.DataPath =
Sit:DataFolder
self.ChangeTheme(Sit:Theme)
end
Access:Sites.Close()
In
the above example the host is checked against a table to see if
it exists. If it does then the
WebFolderPath, DataPath and
Theme are all set based on the Host
value.
Note that this bit of code will be called a LOT -
once for every incoming request. Thus storing this data in a
Memory table is strongly recommended. A Global Unthreaded Queue
could also be used, but that is not thread safe, so you would
need to wrap queue access in a Critical Section. If you are not
sure how to do that then stick to using the Memory table.
Note also that this code is not setting the Filenames or
Owners - that must still be done as described below.
Generic Login
The most useful place to validate a login is in the WebHandler
procedure, Authenticate method.
In this procedure you can
check the incoming user credentials against your USERS database,
and then set session values appropriate to the user there.
Example
p_web.Authenticate
PROCEDURE(String pUser,String pPassword)
CODE
Access:Users.Open()
Access:Users.UseFile()
Use:User = pUser
If Access:Users.Fetch(Use:UserKey) =
Level:Benign
If Use:Password = pPassword
self.SetSessionLoggedIn(true)
self.SetSessionValue('WebFolderPath',use:WebFolderPath)
self.SetSessionValue('DataPath',use:DataPath)
self.SetSessionValue('theme',use:Theme)
Return True
End
End
This sets the session values, but you then need to set the properties based on those session values for each request.
p_web.ParseRequestHeader PROCEDURE()
CODE
self.site.WebFolderPath =
self.GetSessionValue('WebFolderPath')
self.site.DataPath =
self.GetSessionValue('DataPath')
self.ChangeTheme(self.GetSessionValue('theme')
parent.ParseRequstHeader()
Set File Names / Owners
The global, threaded, filename and/or owner variables need to be set in
the WebHandler procedure, ProcessLink
method, before the parent call.
Example
Glo:BreaksName = clip(self.site.DataPath) & 'Breaks.Tps'
Glo:CountryName = clip(self.site.DataPath) & 'Country.Tps'
Example
Glo:Owner =
'some database connection string'
Printing Reports
There are various way to approaching printing in a
WebServer application.
Firstly it should be remembered that the printing is a
function of the browser. Which means that any page your application delivers
is intrinsically a report. The user can click on the browser print option at
any time. By careful design, and attention to the information you are
presenting, you can make many "reports" obsolete.
There are times though where a specifically formatted
document is necessary. In these cases HTML doesn't necessarily offer the
degree of control that you need. The best option in this situation is to
create a PDF file, and have the browser display this.
Since Clarion 6 Enterprise Edition contains a
Report-To-PDF extension, this seems like a good place to start. You can take
existing report procedures in your application, and with minor additions
make it generate a PDF file, and also allow the NetTalk WebServer to send
the PDF file to the browser. PDF makes an excellent format for this sort of
thing because most browsers already know how to display a PDF (via the Adobe
plugin) and you have 100% control over how the page will be displayed, and
importantly 100% control over how it will look when printed.
If you don't have Clarion 6 EE, then there are other
3rd party tools that can be purchased that do a similar thing. NetTalk 4
includes examples of using the Clarion 6 EE functionality, as well as
examples from other vendors.
There are 2 examples included with NetTalk which
demonstrate the technique used to use existing report procedures in your web
site. In each example there are 2 reports, one simple one, and one more
complex one that makes use of an "Options" window, and the ABC Pause button.
Example 13 (PDF Report - requires C6EE) requires, as
the name suggests, Clarion 6 Enterprise Edition (or alternatively the
Clarion 6 Professional Edition, with the optional PDF Report Generator).
Example 14 (PDF Report - requires PDF-Tools) is the same example, but uses
the
PDF-Tools product from Tracker Software Products to convert the reports
to PDF. PDF-Tools is available for Clarion 5.5, 6,7 and 8 so this example
works in all of those.
Steps to use an existing report procedure in a Web application
-
If you've not already done so, add either the SoftVelocity PDF Global Extension, or the PDF-Tools Report Global
extension to your application.
- If you've not already done so add the two Global
extensions required by NetTalk (Activate CapeSoft's NetTalk, and
Activate NetTalk Web Server.)
Tip: If this is a multi-dll system, you can still add these two
extensions, even if this is not the actual web Server app. See the section
on Multi-DLL for more information.
-
Set the Prototype, and Parameters, of the report procedure to
(<NetWebServerWorker p_web>)
-
Add the NetTalk Extension to ABC Report to the Extensions list for this procedure.
-
This step is optional, and is only required if you have a "Report Options" window, or if you have additional parameters passed to
the Report.
When called in "Web mode" the report will not be able to stop and ask the
user for options. If options are necessary you will need to gather these
using a NetWebForm procedure, or alternatively have the options stored in
the Session Queue. An embed point is included at the correct place where you
should prime these optiions. The embed point is called
Prime Report Options
In the supplied examples a NetWebForm procedure is created, which contains 2
variables, FromSize and ToSize. The Report procedure is dependant on 2
options, also called FromSize and ToSize. In the embed point the following
embed code is included:
fromsize = p_web.GetValue('FromSize')
tosize = p_web.GetValue('ToSize')
Advanced
If your report procedure already takes parameters, then
add the new parameter in Step 3 above as the first parameter.
Make all the other parameters optional.
When called normally, leave the first parameter out. For example
myReport(,otherparms)
When called by the report engine the other parameters will not be passed, so
the report will need to populate those values from either the SessionQueue,
or the Value queue using the techniques described in Step 5 above.
If your Report is set to start in Paused mode, then the
Start button will automatically be pressed by the NetTalk template, when the
report is running in Web mode.
Date formats are a little tricky to handle. In Clarion
you're probably used to selecting date formats all over the place, using one
of the built-in date pictures (like @D1 or @D2 etc.). However at the
browser these values are meaningless, so a suitable method for selecting
dates must be available.
With NetTalk, the date-selector popup is the one offered by jQuery.
This supports all Clarion date pictures up to (and including) @D16. Note
that D17 and D18 are not supported.
In order to allow you to select the format globally,
rather than just on a form-by-form basis, there is a setting on the
WebServer procedure, Extensions, NetTalkWebServer, Settings called Date
Picture. This should be entered without quotes.
On Browses you can set the picture of a specific column to anything you
like. If you would like to set it to say D6 then you would enter
'@D6' . If you would like to set it to
the global default then use
p_web.RequestData.Webserver.DatePicture .
If you have, or are making, a Multi-DLL system (ie one
comprised of many APP files) then you may want to use procedures in one
other other DLL's in your NetWeb application. For example, if you have
a report, or graph, in an external DLL, it would be ideal if this procedure
could be used in the Web interface as well.
First a quick note about normal Clarion Source
procedures. These can be used already in the normal way. Remember your web
app is a Clarion program, and Clarion already has templates in place to
allow you to use External procedures in an application. So using normal
source procedures is no problem.
The problem occurs when you are exporting procedures
with NetWeb extensions. For example a NetWebReport, or NetwebBrowse and so
on. If you wish to use these procedures in the main NetWeb app then you need
to tell the WebHandler to include these procedures. To do this:
- Open the application that contains the WebHandler
procedure.
- Go to the Global Extensions, to the Activate NetTalk Web Server
extension.
- Go to the Multi-DLL tab.
- Enter the name of the remote APP file here.
Now all the NetWeb procedure in that remote APP file will be included
correctly in the Web Handler procedure.
In the remote app you will need to add the Activate
NetTalk Global extension, Activate Net Web Server Global extension as normal
(although you don't need to fill in anything on the Multi-DLL tab).
Your procedures in the remote APP will need to be EXPORTED in the normal
Clarion way.
In the WEBHANDLER app you will need to link in the remote procedures in the
normal Clarion way.
So to summarize:
- Make the apps just as you would in any normal Multi-DLL Clarion system.
- In the WebHandler App, add the "External Apps" to the External Apps list
on the NetTalk Global Extension.
Possible Errors:
- Compiling the WebServer application you get an error;
Link Error: Unresolved External BrowseSomething@
This means you probably haven't EXPORTED the procedure in the app where
it is declared. Go to that app, go to that procedure, and make sure
Export Procedure is ticked on.
- Compiling the WebServer application you get an error;
(someapp_ni.clw) Syntax error: Unknown Function label
and when you go to the line in question you see a call to one of the web
procedures - possibly something like this;
UpdateCustomers(self,Net:Web:Div)
This means you have not included that procedure (in this example,
UpdateCustomers) as an external procedure in the WebServer app. Add it
as an external procedure (with the correct Prototype) in the normal
Clarion way.
- Compiling the WebServer application you get an error;
(someapp_ni.clw) Syntax Error : No matching Prototype available
This means you've added the procedure, as an external, to the WebServer
app, but you've not set the Prototype correctly for that procedure. The
best approach is to go to the app where it is declared, and copy the
prototype from there, so they match.
- When you run your application, and go from one procedure to another,
say from a browse to a form, and it does not seem to work, then make
sure all the external apps are added to the Global Extension in the
WebServer app. Adding the LIB's is not sufficient, you need to add any
apps with web procedures to the global extension.
Using Browses and Forms on Static Pages
By embedding a couple of tags on your web page, it
is possible to generate fully functional browses and forms which appear
inside your static pages.
Basic support needed on
the page
-
The first line of the page must be
<!-- NetWebServer -->
-
Inside the <head> of your page you must put
<!-- Net:c:Head -->
-
Just before the </body> tag of your static
page add
<!-- Net:c:BodyEnd -->
-
The NetTalk styles, and scripts, should be in
folders called Styles and Scripts respectively, and these folders should
be inside the same folder as your HTML pages. In other words, the pages
will reference styles/netweb.css.
Embedding a Browse on the
Page
You can embed a browse on the page simply by using
a NetTalk tag. For example
<!-- Net:BrowseCustomers -->
will embed the BrowseCustomers procedure at that point on the page.
Embedding a Form on a page
If you have embedded a browse on a page, then
chances are you will also want to embed the form on a page.
In this case simply
a) Insert the tag into the page as normal. For example
<!-- Net:UpdateCustomers -->
and
b) Tell the browse procedure, in your app, to call your static page on
an update, rather than go directly to the form. You can do this by going
to the NetWebBrowse settings, to the Form tab, clear the Form Control
Procedure field, click on the Advanced button, and enter the name of
your static form page.
Embedding the Popup, and Message on a page
The validation on the form can generate an automatic Message (which
appears on the window) and/or an automatic Popup (which appears as the
form is refreshed). If you create a static page as a container for
the form, then you'll want to embed these onto that static page as well.
Use <!--Net:Message--> to embed the
message. I recommend putting this just before the tag which includes the
form.
Use <!-- Net:Popup--> To
include the popup. I recommend placing this at the bottom of the page,
after all the visible components of the page have been done
Most browsers allow the data passed from the server to
be compressed using the GZIP format. Using this technique it is possible to
improve performance, by reducing the bandwidth requirements for a site.
Graphics
If you are using PNG, GIF or JPG files for your
graphics, then the graphics are already compressed. No additional
compression is necessary, or recommended.
Static Page Pre-Compression
The most efficient way to compress am unchanging text
file, such as a static HTM file, a JS file, or a CSS, file is to store a
compressed copy of the file on the disk, in the same folder as the original
file. Because the file is already compressed there is no additional CPU load
on the server, and the minimum bandwidth is required to get the file to the
browser.
NetTalk supports this pre-compression by looking first
for filename.gz before looking for filename. So if you have a file, called
say whatever.css, then you can use the Gzip utility to compress this to
whatever.css.gz. and store this on the server along with whatever.css.
However you want to leave the whatever.Css file there as well for the
benefit of those browsers that don't support compressed files.
You can disable the automatic serving of pre-compressed
files on the Settings/Advanced tab of the NetTalk Extension in the WebServer
procedure.
Dynamic Compression
NetTalk 7 introduced the concept of dynamic-page compression as well.
This is on by default, but can also be turned off on Settings/Advanced tab
of the NetTalk Extension in the WebServer
procedure.
Anchors
Traditional Web Pages make use of Anchors to hyperlink not just to a page, but into
the page as well, taking the user to a specific part of the page. While this feature would
be nice to do in a dynamic web app, there are some complications.
A normal anchor comes after the URL, and is separated from the URL by a # character. For example in
https://www.capesoft.com/downloads#nettalk10 the nettalk10
part would be considered to be the anchor part.
Note that the anchor part, after the # sign is not passed to the server. Thus when the user uses an
anchor in the URL, the server does not know that they have done so, nor does the anchor get passed to the
server.
The most likely place where you would want to use an anchor, on a dynamic page, is on
a browse. And the browse template has an option for putting one, or more, anchors on
the browse itself. For example the dynamically-generated Downloads page on the CapeSoft site
contains the product name for each row of the browse.
So
https://www.capesoft.com/downloads#nettalk10
takes you to the downloads page, and then straight to the NetTalk 9 product on that page.
This approach works very well for file-loaded browses. Since all the data rows are always on the page, if
the anchor exists then the browser will navigate to it. For page loaded browses however the approach fails because
the server does not see the anchor, and hence does not know to generate the specific page that contains that
specific anchor.
NetTalk has two approaches to solving this problem. By necessity these are not web standards, however they
may be useful. The first approach is to use a normal URL parameter (
a) in the name, and the second is
to use a slightly modified URL. You can use either, or both approaches in your application.
The parameter approach is somewhat more standard in that passing parameters is a well established web technique.
The name of the parameter is use is simply
a. For example;
https://www.capesoft.com/downloads?a=nettalk10
As you can see the # has simply been replaced with ?a=
More completely
the URL should be
https://www.capesoft.com/downloads?a=nettalk10#nettalk10
But since the browse is page-loaded, all the records will (hopefully) be
visible so the last part is unnecessary.
The second approach is slightly neater, but also somewhat more outside the web norms.
https://www.capesoft.com/downloads!nettalk10
In this case the # symbol is replaced with a ! symbol. This is passed to the server, which then removes the
name after the ! and attempts to find the record identified by that anchor in the browse.
Whichever approach you use it should be pointed out that anchors are not suitable for all browses, at least not
without a little bit of hand-coding. In order to position to the browse to contain the record, the table is searched
to find a record with a matching anchor. This is not a key or index based operation, so can result in the reading
of a lot of records on large tables. For tables with more than a few thousand records it is
suggested that the anchor
be chosen to match a key value, and that some embed code is added to
optimize the search based on the knowledge that
the value can be located via a key.
Translations
Server-Side translations
It is often desirable to allow your web app to be translated into the language of the user's choice. A NetTalk app has several features
which makes translations possible.
Translate Method
The root of all translation activity in the WebServer is the
WebHandler procedure,
.Translate method. This method is called
regularly from inside the web app with all the various "text" that
the app uses. You can use any translation engine inside this method
to convert the text to something else. The exact code that
goes in here will depend on the engine you are using.
Internally the Translate method also checks the text to make sure it
is "safe" and if necessary encodes any special characters so they
are compatible with HTML and XML. For this reason it is advised to
call the Parent method, with the translated text before returning
that value. For example;
p_web.Translate
PROCEDURE(<String p_String>,Long p_AllowHtml=0)
ReturnValue
ANY
CODE
If Omitted(p_String) or
p_String = '' then return ''.
ReturnValue =
SomeTranslationFunction(p_String)
ReturnValue =
PARENT.Translate(ReturnValue,p_AllowHtml)
Return
ReturnValue
The contents of the SomeTranslationFunction call will depend on
the translation engine you are using.
AnyText
There are several 3rd party translation tools available for Clarion. One of them is another CapeSoft accessory called
AnyText.
AnyText includes built-in support for NetTalk which is covered in the
documentation there. Basically AnyText includes a template which
adds the necessary code to the Translate method discussed above.
Date Picture
The recommended date picture to use throughout the application is
p_web.site.datepicture. This picture can then be set in the
ProcessLink method, in the
WebHandler procedure, based on the user preference.
Client-Side Translations
The server-side translation engine takes care of text generated by the app. However NetTalk also makes use of
a number of client-side JavaScript widgets which (in some cases) have their own text. Most of these widgets also include
a mechanism for translating that text as desired.
Date Picker
Date Options (for all the date lookups) can be set by setting a property (p_web.site.DateOptions) in the WebHandler Procedure,
in the .ProcessLink method, before the parent call.
The various
options for the date picker are
documented on the JQuery UI web site. Here's an example from
Jeffrey Kuijt which translates the various fields into Dutch.
Self.site.DateOptions = |
'monthNames: [''januari'', ''februari'', ''maart'', ''april'', ''mei'',
' & |
'''juni'', ''juli'', ''augustus'', ''september'', ''oktober'', ' & |
'''november'', ''december''],' & |
'monthNamesShort: [''jan'', ''feb'', ''maa'', ''apr'', ''mei'', ''jun'',
' & |
'''jul'', ''aug'', ''sep'', ''okt'', ''nov'', ''dec''],'&|
'dayNames: [''zondag'', ''maandag'', ''dinsdag'', ''woensdag'',
' & |
'''donderdag'', ''vrijdag'', ''zaterdag''],'&|
'dayNamesMin: [''zo'', ''ma'', ''di'', ''wo'', ''do'', ''vr'',
''za''],'&|
'dayNamesShort: [''zon'', ''maa'', ''din'', ''woe'', ''don'', ''vri'',
''zat''],'&|
'closeText: ''Annuleren'','&|
'currentText: ''Vandaag'','&|
'dateFormat: ''dd-mm-yyyy'','&|
'nextText: ''Volgend'','&|
'prevText: ''Vorig'','&|
'showWeek: true,'&|
'weekHeader: ''wk'','&| 'firstDay: 1,'&|
',buttonText: ''Hulpkalender'''
Authentication
When a client program (like a browser) access the server then a session is created. In many cases it is desirable for the
client to "log in" to the server in order to perform some actions. This process of identifying the user is known as Authentication.
There are a number of approaches that the client can take.
- Enter the login and password on a Form
- Using HTTP Basic or Digest Authentication
- Pass the login and password values as Cookies as part of the header
Because there is this variety of methods, and because you don't want to
duplicate code more than is necessary there are best-practice places for you
to write the necessary code.
WebHandler Procedure; Authenticate method
This method can be called with a Username and Password. You can do all
the work in here to determine if the password is correct or not. If it is
correct then set the ReturnValue to true. Note that if you access any tables
(including your User table) you will need to open and close it here.
You should definitely populate this method if you want to support
Basic Authentication. You can also populate this method, and call it
manually, from a Login Form.
WebHandler Procedure ; SetSessionLoggedIn method
This method will be called when the user is logged in (or logged out).
Any additional work required at that point (like setting the SessionLevel or
storing other User variables in the Session Queue) should be done here. Note
that if you access any tables (including your User table) you will need to
open and close it here.
WebHandler Procedure ; GetPassword method
This method is only used if you support Digest Authentication and you
have access to the plain-text password on the server side(*). Given a
specific user this method should return the password for that user.
(*) Storing user's passwords is considered a bad practice and as such
Digest Authentication is not a recommended approach to authentication. It is
better to store a Salted Hash of the user's password instead.
Basic Authentication allows you to store a salted has of the
password, so is considered a better approach over Digest
Authentication for sites secured with TLS.
Multi-Tab Support
Users can, and sometimes do, open your web app in multiple tabs in their browser at the same time. Because all tabs
share a Session cookie, the server is unable to tell when a request comes from one tab, or from some other tab, and hence
activities on one tab can influence the "state" of the app in another tab. This influence is usually unwelcome and can
lead to strange behaviors, or in extreme cases data corruption.
NetTalk
8.22 introduced Multi-Tab support which can be enabled in the WebServer
procedure, Performance Tab settings.
If this support is on then each
tab is given a unique identifier, and requests from that tab include the
identifier. In this way the app can identify which tab is making a request,
and hence keep the state in one tab different to the state in another tab.
If this feature is on, then setting a session value in one tab will not
set that value in other tabs (See below for how to create cross-tab values).
However the session itself is shared.
The following states are stored in the session itself, and NOT in the
session data queue. If any of these states change in one tab they will
therefore change in all tabs.
- LoggedIn (yes or no).
The details of who is logged in are
usually stored in the SessionData, and that SesisonData will not change.
However the overall state (logged in / not logged in) will be consistent
across all the tabs. If the user wishes to Login as 2 different users at
the same time, in different tabs, then they would need to login on one
tab, open a new tab and login again with different credentials. As the
programmer, you should record _who_ is logged in in SessionData and use
the SessionData where applicable. Because each tab has distinct session
data the user will effectively be logged in twice. If they logout _in
any tab_ they will be logged out in all tabs.
If you are logging
login's and logouts by embedding code in the SetSessionLoggedIn,
ValidateLogin and/or NotifyDeleteSession then you need to be aware that
you'll only get one call to these methods, but in effect multiple
logout's may be occurring at this point.
- Security Level
This is also at the SessionLevel, not the
SessionDataLevel, so changing the level in one tab will change the level
in all tabs.
- Last Activity
Every time the user performs some activity, which
results in a call to the server, the session timeout period is reset.
This remains at the session level, not the Tab level, so if the user
performs an activity in any one tab, they extend the session, and hence
all tabs are extended.
Cross-Tab Session Values
Session values are stored in a queue using the SessionID / Name as the identifier. You don't actually pass the
SessionID as that's handled for you. Multi-Tab support changes this so that the queue became Session ID / TabId / Name.
Again TabId was handled (invisibly) for you.
From build 8.45 there is the ability for you to specify the "tabId" to use.
The generated one only uses numbers and letters, so adding something else (like say a *) will guarantee there is no clash.
By specifying the TabId you can basically create your own (invisible) "tab" - and you can get, and set values in this "tab".
So when you want to create or use a "cross tab" value, you can put it in this tab. For example;
p_web.SetSessionValue('name','value','picture','tabid')
Typically the picture is omitted so this might be
p_web.SetSessionValue('name','value', '' ,'tabid')
or
p_web.SSV('name','value', '' ,'tabid')
however do NOT use
p_web.SetSessionValue('name','value','tabid')
Omitting the extra , '' , would be bad.
On the Get side it's a little simpler;
p_web.GetSessionValue('name','tabid')
or
p_web.GSV('name','tabid')
Other methods that have been extended are;
p_web.IfExistsSessionValue('name','tabid')
p_web.DeleteSessionValue('name','tabid')
p_web.GetSessionValueLength('name','tabid')
p_web.GetSessionValueFormat('name','tabid')
p_web.GetSessionPicture('name','tabid')
Of course you will need to change all your existing code where you want to store or retrieve these "cross tab" values,
but all other places remain unchanged - unless specified the tab ID default remains as it was.
Web Sockets
Web sockets
allow a connection to be made between the browser and the server, and to
remain open. Fundamentally this means that information can be pushed to the
browser, something not possible with the request/response nature of HTTP.
Requirements
- IE 10 or later. Android browser 4.4 or later. Or any other
browser.
Sessions
If a web socket connection is open between the page and the server, then the session on the server will
not time out. In other words, the web socket connection keeps the session alive. However if the web socket
disconnects (and there has been no other activity on the session for some time) then the session may end soon
afterwards.
Activate WebSockets Support
- Go to the WebServer procedure, Extensions, Settings, Scripts tab and
tick on the WebSockets option.
NetWebSource (including Header and Footer)
To add a watched value to a NetWebSource procedure is a two step process;
- Add the value to the HTML with an appropriate ID
- Add the item to the Watch List (on the watch Tab)
HTML
The watched value needs to be in a block (ie a
<span> or a <div>) with an ID parameter. For example;
Users Online: <span id="NumberOfUsers"><!--
Net:h:UsersOnline --></span>
In the above example the id
is NumberOfUsers and the value being
displayed is a Host Value (it could just as easily be a Session Value)
called UsersOnline. The idea is that as the
value inside the Host value changes, so the contents of the
<span> will be refreshed.
NetWebForm
Display
Add the Display field to the form in the usual way. When setting the
settings for the tab, on the Display tab, set the "Display" to
either Session Value or HostValue, and then enter the Session Value
Name (or Host Value Name) in the field. For example;
Display:
SessionValue
'userAge'
NetRefresh
NetTalk Desktop includes a global extension template called NetRefresh. This adds functionality to desktop programs such that if a row in
a table is changed on one workstation, it is automatically updated on another workstation. In other words if 3 users are looking at the browse
customers screen, and one of them changes a record, then that change is automatically (and immediately) visible on the other user screens (
without them doing a refresh.)
From NetTalk 11 this functionality has been extended to include WebServers. This means that changes in a web server app will immediately reflect
in the browser for other users who happen to be looking at the same data.
To be clear, data updated in the web server, or updated in a desktop app will be updated for other users, regardless of whether they are in a web
or desktop view.
In the Web Server program this is accomplished by the browse creating a web-socket connection to the server. Therefore this feature is off by default
and must be turned on on a browse-by-browse basis. Noting the changes made in a form (or EIP) do not incur a performance penalty, and so that is on
for all forms (if NetRefresh is active.)
Activating NetRefresh in a Web Server App
- Add the Global NetTalk Activate NetRefresh extension template to the app
- Go to the WebServer procedure, Scripts tab, and turn on support
for web sockets.
- In the app, Open the WebServer procedure, go to Extensions, to the NetRefresh extension and tick on this is a WebServer procedure
- Unlike for desktop programs, Netrefresh is not enabled for browses
and calendars by default. It needs to be turned on, on a case-by-case
basis.
For browses that need to be automatically refreshed, go to the Advanced tab and tick on Refresh via WebSocket. Also set the Refresh parameter to determine where the browse will refresh to (default is "disabled")
- For calendars that should be automatically refreshed go to the
Advanced tab and tick on the option to refresh via WebSocket.
Triggering a table change from the WebServer
You now have browses watching the Host-Table value, and refreshing themselves when that changes. If the table changes via the web interface,
or via another desktop app with NetRefresh (on the same LAN as the server) then you are done.
But what if the table changes via some other mechanism - like perhaps code in the
WebServer procedure? In this case, in the web server procedure you
can use
s_web.SetTableValue('tablename',today()
& '-' & clock())
If you are in a WebHandler procedure
(like WebHandler, or any NetWebBrowse, NetWebForm etc) then you can
use
p_web.SetTableValue('tablename',today()
& '-' & clock())
This will trigger a refresh event to
the browses that are monitoring the table value.
PHP
NetTalk has the ability to serve PHP pages. It does this by
having the ability to call the Pgp-cgi.Exe program, feeding it the necessary
information it needs, and trapping the output. The primary purpose is to
allow easier integration to existing web sites.
Note: In order to support PHP from your web server, you will require
OddJob and
StringTheory.
Example: A good example of using PHP in an app is
example 58 (cunningly called PHP).
In order for your application to support PHP pages, you need to do the
following three steps;
a) Add the OddJob, and StringTheory global extensions to your application. If you are
building a multi-dll application then add this to the Data Dll.
b) Copy the PHP folder from the Example 58 folders. This contains the Exe
and DLL's and other support files that PHP requires. This folder should be a
sub folder of your application folder.
c) Make sure when you deploy, that the path name to the application does
not include any bracket characters. The example is in a folder called "php
(58)" and this WILL NOT WORK. You must rename the folder to say "PhpExample"
in order for it to work.
There is a property, called
phpPath which contains the path to the
Php folder. If you wish to relocate
the Php folder for some reason, then set the property in the WebHandler
procedure, ProcessLink method, to the actual location. For example;
self.site.phpPath = 'c:\php'
Note that one of the files in the Php folder is the
Php.Ini file which contains details that the Php exe uses. Since this file
can change from time to time (and can be changed internally by the web
server) it is not recommended to have the Php folder in the
c:\program Files
path. Under Windows writing to this
folder is forbidden.
IP Banning
Occasionally a client behaves badly when connecting to a server. You'll occasionally see this in a web server
when a server is subject to an unsolicited penetration test.
All NetSimple servers (and hence WebServers) in NetTalk 9 have the ability
to Ban an IP address. When an address is banned then all existing
connections to that address are closed and no further connections from that
IP address will be accepted.
IP Banning is implemented at the
NetSimple level, so any NetSimple server can ban clients. The most common
usage is for the WebServer, but Banning is not limited to the web server.
A maximum of 10 IP addresses can be
banned at any one time. If additional addresses are banned then the oldest
banning is dropped and the latest one added. If this limit becomes a problem
it can be expanded, but typically banning is unusual and not many addresses
need to banned at once.
Banning is not the same as Rate Limiting,
which simply applies a standard of "fair use", Rate Limiting will be
discussed elsewhere.
Methods
Method | Description |
Ban (IPAddress) | Adds a specific IP address to the Banned
list. If the IP is already on the list then there is no change to
the list. |
Unban(IPAddress) | Removes a specific address from the
banned list. If the IP is not on the list then nothing happens. |
IsBanned (IPAddress) | Returns true is the
IP address is banned, false if not. |
GetBanned | Returns a comma separated string. the string
contains the IP addresses of all the currently banned clients. In
addition the final entry in the list contains the number of
addresses that are currently banned. |
Apply to WebServer
To apply banning to a WebServer procedure
- Go to the WebServer Procedure to the Window Designer.
- Delete both the Logging and Performance control templates.
- Return to the AppTree (this step is important.)
- Go back to the WebServer procedure, to the screen designer.
- Populate the Logging and Performance control templates onto the
two tabs.
The Ban button is added to the first tab, with the logging. To ban an IP address highlight an entry in the log list,
and click the Ban button.
The list of banned IP addresses and the Unban button is on the
performance tab.
Request Filtering
It's possible to add request filtering to the server. This is a check for specific patterns of "bad"
behavior which can help minimize the
time spent on requests which are obviously not valid for this site.
That said, checks on incoming requests apply to all requests, so effectively
slow down all legitimate requests. So there's a balance here between
aggressive filtering and improved overall performance.
If a request
comes in, it is received by the
WebServer procedure. This does very little
to inspect the incoming request, doing just enough to understand when the
request has been completed. Once the request has completely arrived it is
handed onto a separate thread for processing.
This processing thread
first checks the request to see if it matches any of the procedures
(browses, forms, pages etc). If it does not then it checks the filename for
obvious problems (in the
ValidateFileName method) and then looks for the
file on disk. If not found then it returns a 404 error.
Errant
requests cannot cause harm to the system. If someone asks for a PHP page
that does not exist they simply get a 404. If you run a server on the
internet you'll quickly see items appear in the log asking for pages or
URL's which are known flaws in say PHP or IIS or whatever. These don't do
any harm to the NetTalk system other than using up a few CPU cycles.
That said, it is possible to add extra filtering to the
WebServer
procedure,
and the
WebHandler procedure if you wish to do so. For example, if none of
your pages use a
.php suffix, and you don't support
PHP, then you can filter
out PHP pages earlier in the process.
WebServer
In build 10.20 a new method was introduced in the
WebServer procedure. This method, called
FilterRequest, is called once per request when the end of the
HTTP header in the request has been detected.
At this point you
can inspect the request (header) and return Net:Ok
if the request passes, or Net:NotOk if it
fails. Remember this code runs for every single incoming request so make
sure to keep it as fast as possible.
At this point in the code
the incoming request is in the self._ConnectionDataQueue.Data
property. This string may be bigger than the current request, and may
not be padded with spaces. The current length of the request is in
self._ConnectionDataQueue.DataLen. The
data property may contain more than just the
header, it may also contain some (or all) of the body. A passed
parameter pCRLF indicates the location of
the end of the header part.
No properties for the request are
parsed by this point, but you can use the self._GetHeaderField
method to parse out headers.
For example;
expect = self._GetHeaderField ('Expect:', self._ConnectionDataQueue.Data,
1, |
self._ConnectionDataQueue.DataLen,
1, 1)
Here's another example, looking for .asp
ReturnValue =
PARENT.FilterRequest(pCRLF)
If
instring('.asp',lower(sub(self._ConnectionDataQueue.Data,1,self._ConnectionDataQueue.DataLen)),1,1)
ReturnValue = NET:NOTOK
End
RETURN ReturnValue
Bear
in mind that the above test is simplistic. It is searching for .asp in
the whole header, not just the URL part. You may want to limit the
search to the first line of the request, and so on.
Since this
filter takes place before the item is added to the log, and before any
performance measure, this request will not be added to the log and will
not be added to the site statistics (total requests and so on.)
WebHandler
There are two methods in the
WebHandler procedure where additional
filtering code (based on anything you like) can easily be added.
ParseRequestHeader
The
ParseRequestHeader method parses out the HTTP header, and allows you
access to all the common header values. If you add code after the parent
call you can inspect any of these values and reject the request by
returning
Net:NotOk. When this happens the
connection to the client is terminated without an error code being
returned.
Since this happens in the
WebHandler the item is added to the log and it is also included
in the site performance metrics.
Some useful properties you can inspect here, after the
parent call, are
Property | Populated From |
self.PageName | The name of the page/file in the URL. No
parameters, and no path, just the page name. |
self.RequestAuthorization | Authorization: header |
self.RequestContentType | Content-Type: header |
self.RequestFileName | The local name, including local
path to the file on the disk. |
self.RequestHost | Host: header |
self.RequestOrigin | Origin: header |
self.RequestReferer | Referer: header |
self.SOAPAction | SOAPAction: header |
self.Spider | Set to true if
the request is coming from a known web spider, like Google or Baidu
etc. |
self._UserAgent | User-Agent:
header |
self.UserAgent | The browser being used, like curl, edge,
safari or chrome etc. |
self.WholeURL | The whole incoming URL of the request. |
self.XForwardedProto | X-Forwarded-Proto: header |
Example
ReturnValue =
PARENT.ParseRequestHeader() If
instring('.php',lower(self.pageName),1,1)
ReturnValue = Net:NotOk
End
RETURN ReturnValue
ValidateFileNameAs mentioned earlier a later test is done using the
ValidateFileName method. This method takes a
filename as a parameter
and returns
Net:Ok if the file is ok. It returns either
Net:Blank (-1) if the file is blank, or
Net:Abort (-2)
if the file is known to be bad. If the method returns anything other than
Net:Ok then it sends the browser a 400 Error (Bad Request)'
and closes the connection.
Host Variables
Host variables are like Session variables, but they are common to all users. So a host variable written by one user can be read by another user.
Host variables can be set from a processing thread by using
p_web, but they can also be used from the web
server procedure using
s_web. So
p_web.SetHostValue(name,value) can be used in
WebHandler, Browses, Forms and so on. In the WebServer procedure though you
can also set them using
s_web.SetHostValue(name,value).
Host
variables exist outside of sessions and so are not cleared by the deletion
of a session. Internally they are stored in the same queue as the
SessionData, so they will reflect as SessionData on the Performance tab of
the server. This means that it's possible to have SessionData exist, even if
the number of sessions is set to 0.
Because the variables exist
across sessions they are useful for sharing information between users. They
are useful for broadcasting information to either all users, or a subset of
users using
WebSockets.
Tags
A new tag form Net:h:name, has been added. This allows you to use host variables in tags, just as you would session values.
Methods
Method | Description |
SetHostValue(Name,Value) | Set a specific hostname to a
specific value. |
SHV (Name,Value) | Shorthand for call to SetHostValue. |
GetHostValue(Name) | Get the value of a specific hostname. |
GHValue(Name) | Shorthand for call to GetHostValue. |
DeleteHostValue(Name) | Delete a hostname variable. |
WatchHostValue(Name,Watch) | Watch a hostname variable. If
it changes then the new value is sent to either debugview, or the
web sockets. Valid values for Watch are
net:WatchDebug and net:WatchSocket. |
PushHostValue(Name) | Sends the current hostname variable
to all the websockets which are watching the variable. |
See Also
Tags
Popup Animations
Support for animations for the opening and closing of popup windows has been added. These animations can be set globally
(On the various Default tabs of the WebServer procedure) or locally for each procedure type.
Animations are provided by the jQuery UI framework. animations
consist of an effect name, and a duration time. The duration time is in
thousandths of a second. A list of possible effects can be found at
https://jqueryui.com/effect/.
Examples;
'effect: "slide",duration: 500'
'effect: "transfer",duration: 300'
Rubberbands
This is a feature which can be used to multi-select multiple rows, or columns (or both) on a browse.
To turn it on Select the Rubberband option
on the Browse template, Options tab.
If the option is on then the
user can click hold the mouse left button down on a cell, move the mouse to
span multiple cells, or rows, and then release the button. While the mouse
left button is down a "rubber band" will be visible to the user so they can
see what they are selecting.
When the user releases the button the
rubber band can be either left visible or made invisible. By default it
becomes invisible visible. If you wish it to remain on mouse up then set the
option Hide rubberband on mouse up (also on the
Options tab) to off. If this option is off then you will need to hide the
rubberband manually yourself (when it is appropriate to do so.) You can do
this by calling;
p_web.ntBrowse(loc:divname,'hideRubberband')
Remember that JavaScript is case sensitive, so the hideRubberband
must be written exactly as is.
For a web app, when the user selects
an area a request is sent to the server. This request sets the event to
selectRange, and it is handled in the browse
procedure in the SelectRange routine. This
routine contains a loop, which parses out the row ID's for you and loads the
records the user has selected. What you then do with those records is up to
you.
Once your code is complete you may wish to refresh the browse.
This would be necessary if the code you did altered the rows, and you wanted
that alteration to be visible to the user. You can turn this on using the
Refresh browse on mouse up option (also on the
options tab.)
On the options tab you can also set the minimum and
maximum column allowed for the rubberband. This allows you to minimize the
columns the user can select. If blank (or 0) then the setting is not
applied.
Scanning Barcodes and QR Codes
NetTalk 11.16 added support for the Form Field Type Barcode Scanner.
This
allows your camera (webcam on PC's, Environment Camera on phones and
tablets) to be used to scan barcodes and QR Codes.
To enable support
for this make sure you tick on the Barcode Scanner script, on the Scripts
tab, in the WebServer procedure.
Tthe barcode scanner is added to a
form in the same way as any other form-field type. The size of the camera
image can be controlled using CSS. This CSS sizing does not affect the
resolution of the scanner (which is dependent on the resolution of the
camera.)
A variety of Barcode formats are supported (including CODE
128, CODE 39, CODE 39 VIN, CODE 93, EAN, EAN-8, CODABAR, UPC Code, UPC E
Code, INTERLEAVED 2of5 and 2of5. QR Codes are also supported.
On most
devices (everything except iOS) a sound can be played when the barcode is
scanned.
The value read can be sent to a field on the form - in the
case of a QR code, if the QR Code represents a URL then that URL can be
automatically followed.
Notifications
Notifications allow you to display a notification to the user when something happens.
To make use of Notification turn on support for the Notification script in
the WebServer procedure, Scripts tab.
Web Notifications
This implements the browser Notifications API.
Web Notifications happen when the user is in a Web Browser and they are open on a page in your site.
The site must be secure (HTTPS) for notifications to work.
Web
Notifications are supported in Chrome, Firefox, Safari and Edge. They
are not supported in IE. They are sort-of supported in Chrome on Android, but
not Safari on iOS. How the notification is displayed to the user depends
on the OS and Browser combination. Some browsers (for example Chrome on
Windows 10) make use of the OS Notification system others (like say
Firefox on windows 10) display the notifications inside the browser.
(For notifications on Android and iOS see
Push Notifications below.
In Chrome on Android, the notification makes a small sound on
arrival, and will appear in the notifications bar (when the user pulls
down the notifications area) but it does not interrupt the browser. So
the notification is likely to be unnoticed.
On the first notification sent to the user, they will be asked to
either allow, or block, notifications. Whatever they choose will be
remembered, and they won't be asked again. So it is advisable to make
turning notifications on a menu item (or configuration setting) so that
you only send a message (and therefore ask for permission) after they
have indicated they want this feature turned on.
You can send a
notification to the browser at any time by calling the
p_web.DisplayNotification method.
p_web.DisplayNotification Procedure(String pId,
String pTitle, String pBody, String pIcon)The method
takes four parameters.
pId: Each message should have an
ID string. Messages with the same ID will replace each other in the
user's message list. In other words, if you are sending the same
message, or an updated version of the message, use the same ID.
pTitle, pBody : The title and body of the notification.
pIcon: a
graphic file to display on the notification.
Web Sockets
In normal circumstances the call to
DisplayNotification above will add a small script to the
current server-response-to-the-browser. This script will be executed
once the whole response has been received by the browser.
However if
Web Socket support in the application is on [1], and the WebSocket
Listener has been activated on your web page[2], then calls to
DisplayNotification will be sent to the
browser immediately via the web socket connection. This approach
does not rely on a completed response to the browser.
[1]
WebServer procedure, NetTalk Extension, Settings / Scripts tab, Web
Socket script on.
[2] On a specific page, browse, form or
whatever call
p_web.NotificationSocketsOn()
If you want this to apply to every page in the application you can
add this to the Header or Footer procedure.
Service Worker and
Actions
If you have a service worker for your application turned on
[1] then you can add Actions to your notifications.
Note that this feature is only available in NetTalk Apps level. Also
note that this feature is currently only supported by the Chrome
browser.
Actions allow you to add one or more "buttons" to
the notification. Each button has an action, a targ
[1] Go to the Global Extensions for the application, to the
Activate NetTalk Web Server extension. Go to the
Apps tab, to the
PWA tab. Tick on
Generate ServiceWorker.js. Note that you
do not need to create a complete PWA - this feature can work
independently of the app being a PWA.
User Experience
Some common pitfalls to avoid:
-
Don't put your website in the title or the body. Browsers include your domain in the notification so don't duplicate it.
- Use all the information that you have available. If you send a push message because someone sent a message to a user,
rather than using a title of 'New Message' and body of 'Click here to read it.' use a title of 'John just sent a new message'
and set the body of the notification to part of the message.
Device (Push) Notifications
This implements the HTML Push API.
Device Notifications happen when the user is using a mobile device
(running Android or iOS) and they are not on your web site, and your
application may not even be running.
Support for Device Notifications to
follow in a later build.
Paths
BBy default your web server is installed in a directory
(the AppPath) and below this hang the Web folder, Log folder, Web\Uploads
folder and so on. However in some situations you may prefer to rename some
of these folders.
Log Folder/strong>
Default is AppPath\Log
This can be problematic under Windows if you have installed your program into
the \Program Files folder. If you've done this then the LogPath breaks
Windows' rules. Setting the LogPath to the AppData folder is probably
preferable in these circumstances.
To set the logpath, in the WebServer procedure, in the
NetTalk object before Init Section embed point, add some
code such as the following;
ThisWebServer.LogPath = 'c:\logs'
Web Folder, and Web Uploads folder
Defaults are AppPath\Web and
AppPath\web\uploads
respectively. However the same Windows problem that affects the Log path
applies here as well. If your program accepts uploads, then writing them
into the \Program Files folder can be a problem.
You have 2 choices when moving the Uploads folder. Either
move the Web folder completely (by adjusting the WebFolderPath
AND
UploadsPath properties) or just move the Uploads folder.
Remember that static files can only be served if they are
under the web folder, so the real question is whether you want your uploaded
files to be available for download. If yes, then you need to move the whole
web folder, if no, then you can afford to just move the Uploads folder.
Because both the WebFolder, and Uploads folder are site
specific, you need to alter their properties in the _SitesQueue
property of
the WebServer. For example;
Get(ThisWebServer._SitesQueue,1)
ThisWebServer._SitesQueue.Defaults.WebFolderPath = 'c:\web'
ThisWebServer._SitesQueue.Defaults.UploadsPath = 'c:\web\uploads'
Put(ThisWebServer._SitesQueue)
The best place to do this is in the Init method of the web server procedure.
Guide to WebServer Examples
Not surprisingly there are many different ways to
accomplish any given task. The NetWebServer examples are designed to
cover as many of these possibilities as we can. This section describes
each example in a bullet form, covering the features you can see in
operation.
This is the simplest example. It provides a server
that displays the contents of a single table, and allows you to Add,
Change and Delete entries.
Table is File Loaded.
Table has a fixed sort order.
Table uses “Radio” style row selection.
Form has a forced-lookup date-entry field.
Form has “plain” style interface.
Adds a Windows style menu to the top of the page,
to navigate through the site. The example consists of 2 related files.
Tables are File Loaded.
Tables demonstrate client-side sorting. (click on headers).
Tables have “Highlight” style row selection.
Form allows date lookup or date entering.
Alias Browse has a relational lookup on the Mailbox file.
Forms have ‘rounded’ style interface.
Basic form validation added.
Alias Form has example of a “drop down” entry field.
Shows the addition of Login and Password
information, which the user must use to access parts of the site. (Use
login of Demo, password Demo when running the application).
Tables are Page-Loaded with Server side Sorting.
Forms have ‘tab’ style interface.
Greenbar effect on browse.
Locator above browse.
Shows browse set to “include blank rows”.
Alias Form has example of a “lookup”.
Adds a simple Frame to the index page. Includes an
Outlook style menu in the left frame.
Forms have ‘Outlook’ style interface.
Browse is page loaded.
Greenbar effect on browse.
Locator below browse.
Shows hand-coded procedure which displays login message. Linked into
LoginForm procedure as “layout tab / Source Before”. The message is
set, in LoginForm, if a login fails.
Shows a single page that has both a browse, and a Form on the same
page.
If you click on an item in the browse, then that record is loaded in
the form.
Clicking on Save in the Form refreshes the Browse.
[
this example is not working perfectly yet ]
Demonstrates a server running exclusively on a
secure SSL port.
Similar
to Example 4, but shows the login screen appearing before the frame
appears.
Useful
for programs where absolutely the first thing the user must do is log
in.
Frame borders are suppressed.
Range Locator. (try pressing ‘j’ in locator field.)
Includes a picture in the browse and on the form.
Example of using a cookie to preserve the login & password
information in the browser so it is remembered for the next time the
user logs in.
Demonstrates a server running on 2 ports, one
serving normal pages, and another serving SSL pages.
Demonstrates a server running on 2 ports, a secure
port and a non-secure port. All incoming requests on the non-secure port
are redirected to requests on the secure port.
Requires
Insight
Graphing
Shows putting a graph on a window.
11:
Send
Email from a Web Browser
Shows the use of a Memory form, linked to a SendEmail function.
The user can fill in the details for an email, but the Server
program sends the email (rather than relying on the client's email
setup.)
12: User Access Control (** work
in progress **)
Requires
Secwin
Integrates Secwin functionality into a NetTalk WebServer
application, including the ability to limit users from certain
controls, depending on the individual, at runtime.
13: PDF Report using C6 EE
Report-To-PDF functionality
Requires
Clarion 6.x Enterprise Edition
Demonstrates the use of the template that takes an existing Report
procedure and makes it available as a PDF file to the browser
14: PDF Report using PDF-Tools
Requires
PDF-Tools SDK
Demonstrates the use of the template that takes an existing Report
procedure and makes it available as a PDF file to the browser
19:
Select Item from one browse, then filter another
Select a Mailbox on one browse, then the Alias browse is always
filtered based on that selection.
This example also shows setting a dynamic header on the Alias Browse.
20:
Multi-DLL ABC Example
Shows using the web server in a multi-DLL example.
Normal Multi-DLL rules apply, but NetTalk specific settings are listed
below.
1. AllFiles.App. This is the Data DLL. (Generate all
file declarations is on.)
a) This app has the Activate NetTalk global extension added.
2. Customers.App. This app contains one or more NetWeb
procedures.
a) Global NetTalk Extension, and Global NetTalk Web Server
extension are added as normal.
b) ALL NetWeb procedures MUST be marked as EXPORT.
3. WebServer.App. This app contains the WebServer and
WebHandler procedure, as well as one or more other NetWeb procedures.
a) Global NetTalk Extension, and Global NetTalk Web Server
extension are added as normal.
b) On Activate NetTalk Web Server, Global Extension,
Multi-DLL tab, all apps (excluding this one) with netweb procedures are
listed.
c) ALL NetWeb procedures in ALL other DLL's must be added
here as EXTERNAL procedures.
Make sure to get the prototypes
right;
Browse, Page, Source :
(NetWebServerWorker p_web)
Form: (NetWebServerWorker
p_web,long p_action=0),long,proc
d) All files used by NetWeb procedures must be generated in
this app. (Typically just turn on Generate All File Declarations).
4. MainExe.App. This is the Exe program
a) Activate NetTalk Global Extension is added.
b) WebServer procedure is called as desired.
21:
Browse In Form Example
This example demonstrates the use of a Browse as a form entry field.
It's the classic Invoice-LineItems relationship, where LineItems can be
added, or edited when the Invoice Form is open.
22:
Relational Update Example
Similar to Example 21, this example uses a Invoice / LineItem
dictionary. In this case however there is a new field, InvoiceNumber added
to the Invoice table, and the LineItems are related to the Invoice Number
and not the Invoice ID.
Because the Inv:Id field is used in the unique key it cannot be changed
on the UpdateInvoice web form. However this allows the Inv:Number to be
changed on the form. As the LineItems are related to the Invoice number
(and not the ID) they need to change whenever the invoice number changes.
Outwardly this example is no different to example 21 - there's nothing
the programmer needs to do to make the relational updating work. The
relational stuff is done for you by the template, and classes.
23:
Browse To Another Form
In this example the Browse is on one table (Customers) but the Form is
on a different table (MoreCustomers). [ note
- as at build 4.21 this example is not working yet ]
24:
Form To Form
This example shows how to override the destination of the "Save" button
to chain to a second form. Notice the URL on Save
setting for the FirstForm procedure.
25:
Parent-Child Browse
An often requested ability is to have 2 browses, on the same page,
where the "child" browse updates as a row in the "parent" browse is
selected. This example demonstrates this behavior.
Notice the Children tab on the Browse Invoices
procedure. This is where you set the set the BrowseLineItems procedure
to be a child of the BrowseInvoices procedure. Children can be to the
right, or below, the parent. Children Browses can have children of their
own (demonstrated in example 31).
Notice also the ability to have conditional filters on the LineItems
browse. In this case the filter is set to a specific range if the parent
is the BrowseInvoices procedure. This feature allows browses to be
re-used in different places, with different filters.
This example also shows the ability to override the browse colors on
the extension to the Web Server procedure.
26:
File Upload / Hot Fields / Logging
This example shows how the user can upload a file to the server. In
this case the expected file is a picture which will be associated with
the mailbox.
The first thing to note here is on the form. The current picture name
is displayed as a Display field. The "Upload" field is NOT pre-primed
with the existing name. (If it was the file would be re-uploaded on
every Change.) If the upload-file field is blank when the form is Saved
then the existing value is NOT overwritten.
In addition, this example, shows how the graphic can be displayed
both in the browse, and as a "Hot Field" to the right of the browse.
This is done by using the NetWebSource procedure "HotImage". Notice the
way HotImage is set as a Child procedure of the MailBoxes browse.
Lastly this example has Logging enabled. Activate the logging by
clicking on the "Screen & Disk" option on the main Web Server window
when the program is running. Right-click on the Screen-&-Disk radio
option in the Window formatter to see the various logging options that
need to be set.
27:
Frame with Task Panel
Similar to example 7, this one uses the XP Task Panel style menu on the left
hand side instead of the Outlook style menu. The menu in question is in the
MenuOnLeft procedure. Note that toggling between the menu styles here is as
simple as changing the option on the drop-down.
28:
Buttons
This example shows the ability to place buttons on Forms, and Browses.
Currently Buttons to send Emails, and link to other screens are
included. Special notice should be taken of the "Link with ID" button on
the browse.
29:
Basic XML
An example to show serving an XML page rather than a HTML page. Notice
the MailBoxesXML procedure, where the page type is set to XML. This
would be used primarily in cases where other programs (not browsers)
need to access your data (and XML is the preferred markup).
30:
Hyperlinks
Shows the ability to add hyperlinks to browses and forms.
31:
Accounts
A largish demo (currently running at
http://oak.capesoft.com) this example incorporates many features
into a single app. Notable highlights include the use of the XP
Task-Panel menu, Child browses, Dynamic Forms and so on.
32:
Error Page
Shows how a custom error page can be embedded into the Web Server
procedure.
Tip: Resist the urge to place
the name of the missing file in the error message. This can lead to a
security problem known as
cross-site-scripting.
33:
Drop Filter
This example shows the use of a Drop box (on a form) to dynamically
filter a browse (also on the form). See the FilterAlias NetWebForm
procedure.
34:
Calculator
This example shows dynamic fields being captured on the form, which
then change the values in other fields on the form.
The Calculator tab includes a "Calculate" button, although this
doesn't actually do anything. It does give the user something to "press"
though.
The Area tab shows fields being hidden, and unhidden in real-time as
the "shape" of the area is chosen.
The Button tab shows the use of individual buttons to build up the
equation. The result is calculated on the fly. The embed code here shows
calling a generic routine to do the work. Notice that the Equation and
Result fields are set as "Dynamic" because they are not set as Reset
Fields for any of the buttons. An alternative to the last 2 lines of the
hand-coded routine would be to set the Equation and Result to be Reset
Fields of each button. While there's nothing exciting about a
server-side calculator, this technique can be used to prevent
key-logging when entering passwords etc.
Notice the use of the Layout tab (on the template settings) to
prevent the form from "dancing" as fields are hidden and unhidden.
35:
Time Fields
An example of capturing time as a form field. The field is set as a
String with a time picture (typically @T1 or @T4). However the code
interpreting the field is very forgiving. The user can enter almost any
number into a time field and it will be intelligently captured. All the
following are legal:
1 ( resolves to 1:00)
200 (resolves to 2:00)
3am (resolves to 3:00)
4pm (resolves to 16:00)
5:16pm (resolves to 17:16)
and so on.
36:
Html
Editor
Demonstrates the HTML editing control.
37:
Legacy
Basic
A basic web server built using Legacy templates, not ABC
templates.
In order for all the Web Server features to work correctly
using the Legacy templates., you need to make sure that the Smart Generate
feature in the Web Handler procedure is turned on.
Web Hander procedure, Extensions, NetTalk or NetSimple
Object Extension, Class Tab, Smart Generate option.
38:
Legacy
Report Requires C6EE
Similar to example 37, but with the addition of a report
procedure.
39:
Example of using CapeSoft Message Box in a Web server application
Similar to example 37, but with the addition of a report
procedure.
40:
FileDownload
This example is designed to demonstrate 2 different
alternate techniques for serving "static" content from the web server.
Serving static files from blobs
Firstly it contains the ability to serve files from a BLOB
inside a TPS file. In this example the files in web\images, web\scripts, and
web\styles have been added to a simple TPS file (called BlobFile). Each
record in the table contains the name of the file, and the contents of the
file (in the Blob.)
In the WebHandler procedure, _Sendfile method, some
embed code has been added that checks the Blob file before checking the
disk. If the file is found in Blobfile then it is sent from there, rather
than from the disk.
This approach allows you to dispense with shipping the web
folder, and sub-folders. The server will still create these folders, but
they will be empty.
Serving static files from outside the "web"
directory
By design, static files can only be served if they are
below the web folder on the disk. This is a security mechanism, and should
not lightly be over-ridden. However if you need to serve files from
elsewhere on the disk you can. In this example 3 static PDF files can be
served from the application folder, above the web folder.
To do this a generic procedure ServeDocument has been
added to the App. This procedure checks for the parameter called Name, and
uses this parameter to find the file to serve. From the browse this
procedure is called with an URL similar to this;
ServeDocument?name=file1.pdf
If you choose to override the built-in security and offer
this approach to accessing static files, then you will need to add your own
handcode to suitable limit what files will be served by this procedure.
Failure to add code will result in any file on the server being servable.
AddBlob utility app
In order to make maintenance of the TPS file easier a
small, windows, utility called AddBlob is also in this example folder. You
will need WinEvent
in order to compile it. If you don't have WinEvent then you can just run the
EXE in the folder.
This example can be called from the command line, using a
parameter -r. If you do this then the files inside the blob will
automatically be refreshed with the current version of the original file.
In other words, let's say you added
c:\temp\whatever.txt to your blob
file. And you've set the FileName (the "ServeAs" name) to be whatever.txt.
If you call AddBlob -r then the
c:\temp\whatever.txt file, inside your blobfile, will be refreshed.
41:
LegacyGraph
A simple example, which uses
Insight Graphing with the Clarion ("Legacy") templates. Similar to
Example 10, but for Legacy not ABC.
42:
SOAPServer (Requres
XFiles version 2.86 or later)
A very simple example of a SOAP server. An example SOAP
client (as a Windows program), called client.app, is also here.
Scenario
In this example the client app (a windows program) passes
a request-for-information to the server as an XML packet. The request asks
for either Teacher or Student information. If asking for the teacher
information then a password is also supplied.
On the client side the server receives the request, and
creates one of three results. Either the Teacher, or Student information
which was requested, or an Error.
SOAP Server
The app has, apart from the Web Server procedures, only
one function. This function is called GetInfo. GetInfo is based on the
NetWebPage procedure template, and the Page Type is set as XML. the handling
of the incoming XML packet, and the formation of the outgoing XML packet are
done in embed code, using xFiles.
The possible incoming, and outgoing structures are defined
as groups. Using an xFiles object the incoming request is placed into the
Request group. Then depending on the request one of the response groups is
populated. the xFiles object turns the group into an XML string (Xml.XmlData),
and this string is the "Page" sent to the client by the NetTalk web server.
SOAP Client
In this example the client uses xFiles to construct the
XML packet with the SOAP wrapping, and then does a simple POST to the
server. Note the URL of the post contains the name of the function (getInfo)
- this is how the server knows which function to run.
Data
In the example data files you'll find teacher with an ID
of 1 (password jake) and another with an ID of 2 (password brian). Threre
are also 3 students, with numbers 1,2 and 3 respectivly.
Tip: For more SOAP Client examples, see the examples that
ship with xFiles.
43:
AccessLevels
An example of Access Levels.
In this example log in as
Login: Super / Password: Super
to have full access to the browses and forms, login as
Login: Operator / Password: Operator
to have limited access to some of the forms, and login as
Login: Guest / Password: Guest
to have read-only access to the system
45:
WScriptActiveX
This example will only work in IE, and only if the IE
security levels are set to allow ActiveX scripts for the "zone" that the
server is in.
It demonstrates using an ActiveX control, specifically the
built-in WScript.NetWork control.
In the login form, a call to the JavaScript function
SendUser() is made. This function is in a file called wscript.js, which is
in the scripts folder. This function uses the WScript.Network control to get
the UserName and ComputerName of the client computer, and send it to the
server as a session value. If you examine the log after requesting the login
page in your browser, you should see the additional calls to set the Session
values.
Note: The use of ActiveX controls is clearly limited to
very specific situations. Typically this approach is only useful in an
environment where the browser itself is specified (IE) and the security
settings on the browser can be set for your site.
Zones: If you want this control to work, the user must
place your site into a "Zone" in their browser that has a Low priority set.
the attached picture for IE 7 may help;
At this point the user will probably get a popup warning
whenever the script is invoked. To turn off this warning, click on the
Custom Level button (as seen in the picture above), and set the ActiveX
settings from "prompt" to "enable".
My thanks to Bram Klootwijk who assisted with the creation
of this example and did the research to make it work.
46:
Timers
This example shows the use of Timers as they are applied
to web pages.
From the index page, click on the link "Page with
Progress". This will show a very simple text progress percentage. This case
uses a NetWebSource with a timer in the ProgressSoFar procedure.
The MailBoxForControl procedure includes a Display Form
Field that is updated on a timer. This uses the TimeClock NetWebSource
procedure.
47:
Pages
This example shows how to embed a browse in a static htm
page, and also how to embed a form in a static htm page.
The default page for the site is set as 'index.htm' which
is a static htm page in the web folder, rather than the normal case where
the page is a procedure in the application. This index.htm page contains an
embedded session variable, as well as an embedded browse procedure. It also
has an embedded NetWebSource procedure, the footer.
The browse calls a static page, form.htm, instead of the
form procedure. Form.htm is also in the web folder. This page has both the
form, and the header embedded on it.
48:
Tagging
This example shows the EIP on a browse, where the EIP is
working on a secondary table. As the user tags the records in the browse,
the state of the tags is saved into a table. (In the example a TPS table has
been used, but if you have the In-Memory driver, then this would make a very
good driver for this table.) While nothing is actually done with the tagged
records, obviously you can use the Tagged table later on in your code to "do
something" with the tagged records.
In normal (primary file) EIP the changes are saved
automatically as they are made. When the changes are made on a secondary
table (in this case a table containing the tags themselves) hand-code must
be added to fetch, modify and save the appropriate secondary record.
In addition this example contains some embed code in the
WebServer procedure that shows custom behavior when a session is
automatically deleted.
49:
Locators
The goal of this example is show the variety of ways that
locators can be configured for a browse.
Typically these settings are set at design time, but the
programmer, however as this example demonstrates it's also possible to set
the settings at runtime - thus allowing the user to select the locator they
prefer.
50:
MultiRow
Most browses are single line affairs. However this example
uses multi-line techniques, and challenges the very essence of what we
consider a browse to be.
53:
Validation
This example demonstrates the use of dictionary
Validation, and local Form Field validation.
In this example all the fields on the MailBoxes browse
are set to Validate Immediately creating a highly responsive form which
insists on the correct data being entered. Visual clues including
highlighted entry fields, popup messages, and comments are all included
automatically.
The NickName field shows how hand-coded validation can
also be applied to the field. In this case the field has Validate
Immediately setting on. In addition some source code has been added
to the Server Code button on the Client-Side
tab. The code contains both some validation (an arbitrary nickname length of
4 characters is enforced) and some "clean up" code which "corrects" the
nickname to a suitable case.
Note that in the case where the validation is hand-coded
the Loc:Invalid, and Loc:Alert variables are set appropriately.
54:
Excel
This example demonstrates exporting to Excel from inside
a web server.
NOTE: This example requires Office Inside in order to
compile, as it uses
OfficeInside to create the Excel file. Also Excel must be
installed on the same machine as the server.
55:
Services
This example demonstrates adding SelfService to a Web
server application.
NOTE: This example requires
SelfService in order to compile.
56:
Menus
This example demonstrates various menus that are
available. Use this app to experiment with different menu options and
settings.
57:
Tabs
This example demonstrates the various form types (also
known as Tab Types) that are available.
58:
Php
This example demonstrates the use of Php pages combined
with NetTalk pages. Not that this example (and PHP support in general
requires
OddJob and
StringTheory). For more about PHP see the notes above.
59:
MultiSite
This app is more than an example, it is the source for
the Host exe, the program that allows multiple web sites to share the same
IP address and Port number. Compiling, and using this app is described in a
different document here.
60:
CPCSReport
This example demonstrates the use of the web server with
existing (or new) CPCS
Report procedures.
CPCS
[End of this document]