Tutorial - Server-side Scripting
Introduction
Candle is designed to be a new scripting language that replaces
existing server-side scripting languages. The advantages of Candle
comparing to conventional server-side scripting languages include:
- Structured and convenient output
construction: in Candle, the output is constructed as a
structured node-tree, instead of text stream. The output from a Candle
script thus is guaranteed to be well-formed at least. The output
can also be easily validated against a schema. These built-in measures
help to avoid issues like cross-site script that plague
traditional server-side scripting languages. And Candle has
many
built-in language features that makes node composition intuitive and
easy as breathing.
- Candle has built-in schema
validation support;
- Candle has template
transformation support: the template transformation mechanism
defined in XSLT is supported in Candle;
- Candle is a high-level query
language: XQuery is just a subset of Candle;
- Candle is more functional:
Candle uses the separation-of-side effect mechanism to cleanly separate
functional code from procedural code. This mechanism helps separating
server-side scripts into functional scripts and procedural scripts,
which makes the site more structured.
To runs the sample scripts in this tutorial, you should have got
Candle's built-in web server running already. You can refer to Candle Runtime
Reference on how to do so.
This tutorial assumes that that Candle web server is configured similar
to the following:
<?cmk1.0?>
<server port=7077
mode="production" max-content-length=33554432> !! 32M
<!-- mime mappings are omitted
-->
<domain name="127.0.0.1:7077"
root='file:///some-dir' !! the
local file URI to the dir of the server-side scripts
default-page="index.csp">
</domain>
</server>
Hello World Script
Here's a simple hello-world server-side Candle script:
<?csp1.0?>
function main(request) {
<html>
<body>"Hello world from
server-side Candle!"</body>
</html>
}
You can copy the code into a script named index.csp
and store it under root directory of the server domain you configured.
You can then open your browser and go to http://127.0.0.1:7077/, and you
should see the hello-world message displayed.
The differences between a command-line Candle script and a server-side
Candle script are:
- server-side
Candle script takes one extra parameter, the client
request;
- server-side
Candle script has more strict requirement on the script extension. If
the script's
main
routine is a function, the file
extension must be '.csp
'. If the script's main routine is
a method, the file extension must be '.run
';
If you are curious about this request parameter, you can easily view
its
content using this view-request.csp
:
<?csp1.0?>
function main(request) {
<html>
<body>
<code>{request?source}</code> </body>
</html>
}
You can see that it is actually the HTTP request MIME message converted
into an element. In the following, you'll see how it can be used to
perform some of the common tasks in server-side scripting.
Handling the Query String
For server-side scripting, one of the basic need is to access the query
string and posted form data from the client request. This can be easily
done through the request
parameter passed to the main
routine. To fully understand the request
parameter, you
need to have some knowledge of HTTP protocol. The request parameter is
just a conversion of the HTTP request message from MIME format into
Candle markup format.
Copy the following code into a script, say parse-query-string.csp. In
the browser, go to http://127.0.0.1:7077/parse-query-string.csp?a=123&b=value,
<?csp1.0?>
function main(request) {
<html>
<body>
"Request: "
<code>{request?source}</code>
let qstr =
substring-after(request/@candle:io:url, "?");
"Raw query
string: " {qstr} <br/>
let
qstr-params = parse(qstr, value::candle:core:url-encoded);
"Parsed query
object: " {qstr-params?source}
</body>
</html>
}
As you can see from the example, you can access the query string from
the url
attribute of the request
object.
You can then use substring-after()
function to extract
the query string. The query string at this point of time is still in
URL-encoded format, as can be seen from the printed output.
To convert it into a format that can be more easily processed, you can
use the parse()
function. The second parameter tells the
function about the format of the source string. Through the
function, the query string is parsed into an anonymous object in
Candle, with each name-value pair converted into an attribute of this
object.
If several name-value pairs have the same name, the values are grouped
as a list under the same attribute. You can test with http://127.0.0.1:7077/parse-query-string.csp?a=123&b=value&b=456
Handling the Posted Form Data
To test posted form data, you need to have a simple HTML form. Here's
one example:
<html>
<body>
<form action="form-post.csp"
method='post'>
a: <input
type='text' name='a'> <br>
b: <input
type='checkbox' name='b' value="1"> option 1
<input type='checkbox' name='b' value="2">
option 2 <br>
<input
type='submit'>
</form>
</body>
</html>
And here's the server-side script, form-post.csp
, that
handles the form post:
<?csp1.0?>
function main(request) {
<html>
<body>
"Request: " <code>{request?source}</code>
let
post-data = request/node();
"Raw
posted data: " {post-data} <br/>
let
post-params = parse(post-data, value::candle:core:url-encoded);
"Parsed post object: " {post-params?source}
</body>
</html>
}
As you can see from the example, the form data is converted into a text
node under the request element. And again, you can use parse()
function to convert it an object.
Handling the File Upload
Here's an HTML form with file upload:
<html>
<body>
<form
action="view-request.csp" method='post'
enctype="multipart/form-data">
a: <input
type='text' name='a'> <br>
b: <input
type='checkbox' name='b' value="1">
option 1
<input type='checkbox' name='b' value="2">
option 2
<br>
c: <input type='file' name='c'> <br>
<input
type='submit'>
</form>
</body>
</html>
We are reusing the previous view-request.csp
script to
dump the request object. And you'll get something like the following:
<ns:org:candlescript:io:message
ns:org:candlescript:io:method="POST"
ns:org:candlescript:io:url="/view-request.csp"
ns:org:candlescript:io:host="127.0.0.1:7077"
ns:org:candlescript:io:user-agent="Mozilla/5.0 (Windows NT 6.1; WOW64;
rv:16.0) Gecko/20100101 Firefox/16.0"
ns:org:candlescript:io:accept="text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
ns:org:candlescript:io:accept-language="en-US,en;q=0.5"
ns:org:candlescript:io:accept-encoding="gzip, deflate"
ns:org:candlescript:io:connection="keep-alive"
ns:org:candlescript:io:referer="http://127.0.0.1:7077/form-upload.htm"
ns:org:candlescript:io:cache-control="max-age=0"
ns:org:candlescript:io:content-type="multipart/form-data;
boundary=---------------------------2447431410930"
ns:org:candlescript:io:content-length="473"><ns:org:candlescript:io:message
ns:org:candlescript:io:content-disposition="form-data;
name=&dq;a&dq;">"abc"</ns:org:candlescript:io:message><ns:org:candlescript:io:message
ns:org:candlescript:io:content-disposition="form-data;
name=&dq;b&dq;">"1"</ns:org:candlescript:io:message><ns:org:candlescript:io:message
ns:org:candlescript:io:content-disposition="form-data;
name=&dq;b&dq;">"2"</ns:org:candlescript:io:message><ns:org:candlescript:io:message
ns:org:candlescript:io:content-disposition="form-data;
name=&dq;c&dq;; filename=&dq;test&dq;"
ns:org:candlescript:io:content-type="application/octet-stream"><ns:org:candlescript:io:content
ns:org:candlescript:io:cache="d:\Candle\cache/77DuLQ3Hxk"/></ns:org:candlescript:io:message></ns:org:candlescript:io:message>
From the dump you can see that, each name-value pair is converted into
a candle:io:message
, reflecting the underlying multipart
MIME message format. And the file uploaded is represented by an candle:io:content
element. The candle:io:cache
attribute tells where the
file is stored temporarily. By default it is the cache
directory under the Candle installed directory. You can write custom
code to move the file from the cache directory to your target directory.
Producing Custom MIME Response
Sometimes you may want to produce non-html response.
One scenario is to produce server-side redirect. Here's sample script
to achieve that:
<?csp1.0?>
namespace io=candle:io;
function main(request) {
<io:message io:status-code=303 !!
io:reason-phrase="See Other"
io:location='index.csp'>
</io:message>
}
Your script just need to generate a candle:io:message
.
Its attribute candle:io:status-code
is used to set the
HTTP response status code, and the candle:io:location
attribute is used to generate HTTP response header field LOCATION.
You can specify any of the predefined HTTP response header fields
(in RFC 2616) as the
message attribute. And they'll be copied to the HTTP response MIME
message. You just need to take note that:
- The name of these header attributes are in lower-case;
- The value of these header attributes can only be of 3 types:
integer, string and uri; if the value is not one of these 3 types, the
header field is skipped when writing the HTTP response message;
Another scenario is to set the server-side cookie, which is needed
for session handling:
<?csp1.0?>
namespace io=candle:io;
function main(request) {
<io:message io:content-type="text/html"
io:set-cookie="name=value">
<html>
!! normal HTML
content
</html>
</io:message>
}
You just need to wrap the HTML content inside a candle:io:message
,
and use candle:io:set-cookie
attribute to set the cookie.
You can take a look at the script server.run
under /lib
directory under Candle installed directory, to understand how it
servers various types of static files and dynamic pages. You can use it
as the basis to create your own customer web application framework in
Candle.