This post will show how to extend an existing handler by using BeanShell.
Since PAW 0.96 BeanShell can be used to write handlers (and filters). In earlier versions of PAW handlers and filters had to be provided as Java class file, so a Java development environment was needed. By the use of BeanShell handlers and filters can now be developed directly on an Android device. This is especially handy when developing handler and filters for testing.
|
PAW Standard Handler Chain |
I’ll briefly recap what handlers are good for.
Handlers form the processing chain on HTTP requests. Handler are organized in a list that is processed until a handler processing the request is found. If a handler signals that it can handle the request, the handler will process the request and the list of handlers is no longer processed.
In the example we will extend the
BasicAuthHandler which handles the basic authentication to protect a web resource (most often a directory).
The handler will be extended in the way, that it will only request credentials if pages are called from external, localhost request should not need to authenticate.
The standard
BasicAuthHandler does not provide this functionality. It always asks for credentials, regardless where the request came from.
Since PAW 0.96 there is a new handler called
CallBshHandler which accepts a BeanShell file to act as a handler.
The BeanShell file has the following base structure:
/*
Variables: server, prefix, handler
*/
init() {
}
/*
Variables: server, prefix, handler, request
*/
respond() {
}
There are the two methods
init() and
respond(). Both methods return
true on success and
false on failure. The
init() method receives the
server,
prefix and
handler variables. The server and prefix variables are needed to get configuration parameters specified inside the
conf/handler.xml file. The handler variable holds a reference to the calling
CallBshHandler instance. This instance is needed to persist settings (between
init() and
response()) methods. This is necessary, because
init() and
response() are not called within the same BeanShell interpreter instance (to make the handler thread safe). To persists objects within the handler, the methods
save() and
load() are provided. The example will show how these methods are used.
Extending the current
BasicAuthHandler will be done in three steps:
- Disable the BasicAuthHandler
- Create the new handler
- Configure the new handler
Disable the BasicAuthHandler
To disabling the current
BasicAuthHandler, find the following handler in the
conf/handler.xml file and change the status property to inactive
<handler status="inactive">
<name>Basic Auth Handler</name>
…
</hadler>
Create the New Handler
We will now build a handler that calls the standard
BasicAuthHandler only on external requests. Local requests, which have the IP number
127.0.0.1 or
::1 will not be processed and so no credentials will be requested.
To build the new handler, create a directory
handler inside the PAW installation folder (
paw/handler). Inside that folder create a file called authHandler.bsh with the following content:
import org.paw.handler.BasicAuthHandler;
LOCALHOST = "127.0.0.1";
LOCALHOST_V6 = "::1";
/*
Variables: server, prefix, handler
*/
init() {
authHandler = new BasicAuthHandler();
handler.save("authHandler", authHandler);
return authHandler.init(server, prefix);
}
/*
Variables: server, prefix, handler, request
*/
respond() {
ip = request.sock.getInetAddress().getHostAddress();
if(ip.equals(LOCALHOST) || ip.equals(LOCALHOST_V6)) {
return false;
}
authHandler = handler.load("authHandler");
return authHandler.respond(request);
}
The code is straightforward. Inside the
init() method the
BasicAuthHandler is instantiated. That instance is saved within the calling handler by calling
handler.save().
The last line of the
init() method returns the result of the call to i
nit() of the instantiated
BasicAuthHandler class.
If the
init() method returns true, the handler will be added to the list of handlers and the
respond()method will be called on each request.
When the
respond() method is called it first gets the requestor’s IP number and assigns it to the variable
ip. If the variable contains a localhost IP (
127.0.0.1 or
::1) the method returns
false, which indicates that the handler will not handle the request.
In that case no credentials are requested and the handler chain is further processed.
If the request is not initiated by a localhost address, the
respond() method of the
BasicAuthHandler instance is called and the return value of that method is returned.
Configure the New Handler
To configure the new handler, just place the following handler definition below or above the original
BasicAuthHandler definition of the c
onf/hander.xml file:
<handler status="active">
<name>BeanShell auth handler</name>
<description>Auth handler that allows local access</description>
<removable>true</removable>
<id>bshAuth</id>
<files/>
<params>
<param name="bshAuth.class" value="org.paw.handler.CallBshHandler" />
<param name="bshAuth.script" value="[PAW_HOME]/handler/authHandler.bsh" />
<param name="bshAuth.confdir" value="[PAW_HOME]/webconf/auth" />
</params>
</handler>
The parameters describe the
class (
org.paw.handler.CallBshHandler) and
script (
[PAW_HOME]/handler/authHandler.bsh) to use. The third parameter called
confdir is the standard configuration parameter used by the
BasicAuthHandler. The BeanShell handler passes this parameter when calling the
BasicAuthHander.init() method from its i
nit() method.
Because the new handler calls the standard handler, the Directory Protection settings from within PAW’s web interface also apply to the new handler.
On next startup of the server the new handler should be active. If the startup fails, increase the log level, restart the server and have a look at the log file.
Testing the Handler
To test the handler, I’ve created a directory
test within the
html folder (
html/test). For the at folder, directory protection has been setup using the Directory Protection page of the web interface.
The
localhost connection was tested by using ADB’s TCP forward parameter (
adb forward tcp:8080 tcp:8080).
When testing with an IP number, authentication is required:
$ telnet 192.168.178.79 8080
Trying 192.168.178.79...
Connected to 192.168.178.79.
Escape character is '^]'.
GET /test/ HTTP/1.0
HTTP/1.0 401 Unauthorized
WWW-Authenticate: basic realm="Bsh Auth Test"
Date: Sun, 06 Dec 2015 09:05:38 GMT
Server: PAW Server 0.97-android (Brazil/2.0)
Connection: close
Content-Length: 34
Content-Type: text/html
Missing http header: AuthorizationConnection closed by foreign host.
On a localhost request, no authentication is needed and the directory listing is displayed:
$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /test/ HTTP/1.0
HTTP/1.0 200 OK
Set-Cookie: cookie=7th5kto9f4nrf1a5r8pdse; PATH=/
Date: Sun, 06 Dec 2015 09:08:32 GMT
Server: PAW Server 0.97-android (Brazil/2.0)
Connection: close
Content-Length: 99
Content-Type: text/html
<title>Directory Listing</title>
<h1>Directory Listing</h1>
<a href=..><b>parent directory</b></a>
Connection closed by foreign host.