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:
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:
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:
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.