-
Notifications
You must be signed in to change notification settings - Fork 0
TINN Web
TINN Web is a super simple web framework for TINN which provides the following functionalities:
- simple functions to ease the processing of HTTP requests
- customizable error handler to generate valid HTTP responses even when things go wrong
- generation of HTTP response
- support for dynamic webpages through
<?js ?>
tag (same as<?php ?>
tag) - include functions for dynamic pages equivalent to
require
,require_once
,include
,include_once
ofphp
- web controller to map URLs to pages or request handlers
Install using the tinn install
command as a local package:
tinn install tinn_web
Or as a global package by adding the -g
flag:
tinn install tinn_web -g
var web = require('tinn_web');
var sockAddr = ':8200';
Http.openSocket(sockAddr);
print("Server listening on: " + sockAddr);
while(true) {
Http.accept();
web.handleRequest();
Http.finish();
}
Try creating a script with the above code and run it.
If you set up the NGINX frontend on 127.0.0.1:80 to direct requests to FCGI on port 8200
(if you are on windows this is automatically set up) you can hit http://127.0.0.1
and should
get a PageNotFound
error.
Now let's create a default page. Create a directory called pages\
in the same directory as the running
script, and then create a file called index.html
inside it and write some content in it.
Hitting again http://127.0.0.1
in the browser should return the content of the page (read more about
dynamic pages below).
Let's now try adding a request handler. Add the following lines to the script right after the first line where tinn_web
is imported and run it again:
web.addRequestHandler('Test', function() {
this.response('this is a test');
});
Hitting the URL http://127.0.0.1/test
in the browser should give you the text returned by the request handler.
Requests are processed by pages
and request handlers
. When a request comes, the controller first checks whether one of the register request handlers can handle that request, if not then it checks whether the request maps to one of the existing pages
. Pages are dynamic html files that can embed javascript code by using the <?js ?>
tag.
Request handlers are functions that are in charge of processing requests for specific URLs.
Request controllers implement the logic to connect URLs to request handlers under the following schema:
- a request controller resolves the incoming HTTP request into one or more keys
- the request handlers that are registered under those keys are in charge of processing the request
The default request constroller
in TINN Web connects requested URLs to request handlers with a simple logic that is described in the following example. Consider the URL:
http://127.0.0.1/api/admin
When the above URL is requested the controller first looks for a request handler registered under the key Api
and then it looks for a request handler registered under the key ApiAdmin
. If the request handler for the first key is found then its function is invoked and if the function returns true
then the request handler for the second key is called.
When a request handler for a key is not found then the controller always looks for the request handler for the next key.
To register a request handler for a specific key, use the 'addRequestHandler` function as shown below:
var web = require('tinn_web');
web.addRequestHandler('Api', function() {
//do authentication here...
....
return true;
});
web.addRequestHandler('ApiAdmin', function() {
....
web.response(JSON.stringify({result: 'this is a test'}));
});
The above code registers two request handlers that will be called in sequence when the URL /api/admin
is requested. The first request handler acts as a filter and it can be used to check for authentication (this handler will be invoked for every request sent to /api/
), the second request handler is in charge of writing the response.
Request controllers can be manipulated and extended by using the getRequestControllers
function. This function allows adding custom request controllers or replacing the default one.
To implement a custom request controller
the following:
- a
request controller
is a function that runs in the scope of the main controller of TINN Web. This means that in the body of therequest controller
thethis
object allows accessing properties and calling functions ofRequestController
, which is the main controller class. - after analyzing the request, the
request controller
function can invoke request handlers by calling the internal_processRequest
function passing the request key as a parameter (the request key is the key used to registerrequest handlers
)
The following example shows how to implement a request controller that calls every request handler whose key matches the path in the request URL as a regular expression:
var web = require('tinn_web');
var regExpRequestController = function() {
for (var key in this._requests) {
if (!this._requests[key].regExp) this._requests[key].regExp = new RegExp(key);
if (this._requestPath.match(this._requests[key].regExp) && !this._processRequest(key)) {
break;
}
}
}
//replace the default request controller
web.getRequestControllers().splice(0, 1, regExpRequestController);
Pages are dynamic web pages located in a directory structure that is directly mapped to the requested URLs. By default pages must reside in a directory called pages
that is located in the server root
(see getServerRoot
).
When a URL with path /content/1/index.html
is requested then the file under /pages/content/1/index.html
is used to process the request.
For the processing of pages, the following defaults are applied:
- if the request URL path maps to an existing directory in the page structure rather than a file, then the page controller looks for a file called
index.html
(default page) in that directory and if found it processes it as a page. The name of the default page can be changed by setting theDEFAULT_PAGE
constant - the
Content-Length
header in the response is automatically set totext/html
(this can be changed by usingsetHeader
)
Pages are dynamic through the <?js ?>
tag that allows embedding javascript code that is processed server side before serving the resulting page.
When a page is processed for the first time, the <js ?>
tags are resolved and the resulting page is cached. By default the page cache is in a directory called cache
inside the server root
but a different directory can be used by setting the CACHE_DIR
constant.
The following example shows how to create a table dynamically from an array, with one row per element:
<table>
<?js items.each(function(item)) { ?>
<tr>
<td><?=item.id?></td><td><?=item.name?></td>
</tr>
<?js } ?>
</table>
In the above example note the short tag <?=...?>
used to quickly output values into the page. The short tag converts the value returned by the expression into a string and outputs it in the page. Thus doing:
<?='Hello World'?>
Is the same as doing:
<?js echo('Hello World') ?>
When processing a page the Web controller exports to the scope of the page all its API functions so that they are directly accessible by the code of the page without having to require tinn_web
.
For instance, to call include
in a page, instead of doing:
<?js
var web = require('tinn_web');
web.include('page_to_include.inc');
?>
One can simply do:
<?js include('page_to_include.inc') ?>
When an error occurs during the processing of a request (either in a request handler or in a page) the default behaviour is to output the error message and stack trace in the response.
It is possible to handle errors in different ways by using setErrorHandler
. When a custom error handler is set, it will be called every time there is an error, and the error will be passed to it.
Consider the following script which sets a custom error handler to display the error message in bold in the page.
Try running the script and hit the URL http://127.0.0.1/test
in the browser. The request handler will be called and will throw an error. The error is passed to the custom error handler that in turn outputs its message in the page in bold.
If you remove the call to addRequestHandler
and run the script again when hitting the same URL you will get the full error, including the stack trace, output in the page.
var web = require('tinn_web');
var sockAddr = ':8200';
Http.openSocket(sockAddr);
print("Server listening on: " + sockAddr);
web.addRequestHandler('Test', function() {
throw new Error('something went wrong');
});
web.setErrorHandler(function(e) {
this.response('<b>Error: ' +e.message + '</b>');
});
while(true) {
Http.accept();
web.handleRequest();
Http.finish();
}
Web controller is the main class in TINN Web that takes care of processing HTTP requests.
This section describes the functions that are exported by the web controller.
echo(string text)
Echoes text
inside the page.
url(string path)
Returns the URL for the requested path relative to the URL of the current page.
exit()
Interrupts the executing of the page. This results in a HTTP response whose content corresponds to page generated up to that moment.
include(string what, bool required)
Includes another page. When required
is true
if the execution of the included page produces an exception then its content won't be included in the page. Basically this means that in order to be included the whole page needs to evaluate with no error.
includeOnce(string what, bool required)
This is like include
but forces the page to be included only once in the response.
getParam(string name)
Gets the value of the requested querystring parameter.
getParams()
Returns all querystring parameters in the HTTP request as an object with key/value for each parameter.
setHeader(string name, string value)
Sets the header in the response.
getHeader(string name)
Gets the value of the requested header.
getHeaders()
Returns all headers in the HTTP request as an object with key/value for each header.
getServerRoot()
Returns the root directory. By default this is the directory that contains cache/
and pages/
directories.
The default location of server root
is set to the directory containing the current running script (process.mainModule.filename
).
To use a different location for server root
set the SERVER_ROOT
constant as shown below:
var web = require('tinn_web');
web.SERVER_ROOT = '/var/www/';
setErrorHandler(function func)
Sets func
as the current error handler. This function will be called when the processing of a request produces an error and the error will be passed to it.
response(string text)
Writes text
to the response buffer.
addRequestHandler(string key, function func)
Registers the request handler given by func
to handle requests that are mapped to key
.
getRequestHandlers()
Returns all registered request handlers as an object whose keys are the keys used to register the handlers and values are the actual functions.
getRequestControllers()
Returns an array containing the current request controllers. If no other request controller was added, the returned array only contains the default request controller.
getPagePath()
Returns the path of the current page
getPageStack()
Returns an array representing the stack of pages that have been included
responseStatus(integer code, string text)
Sets the status code and text of the HTTP response. By default the status code is set to 200
and the text is set to OK
.
List of configuration variables:
- SERVER_ROOT: server root. This is the directory that is expected to contain the
pages\
andcache
directories. - CACHE_DIR: directory used for caching pages. By default this is set to
cache\
insideSERVER_ROOT
. - DEFAULT_PAGE: default page that is processed when the requested URL points to an existing directory. By default this is set to
index.html
The following code shows how to set a differentDEFAULT_PAGE
:
var web = require('tinn_web');
web.DEFAULT_PAGE = 'mypage.html';