In today’s web-driven world, servers process an overwhelming number of requests. Efficiently analyzing, counting, interpreting, and rerouting these requests is essential, especially when applying MVC principles.
In 4D 20 R8, with the introduction of HTTP Request Handlers on the 4D HTTP Server, you can define accurately which business logic you want to trigger regarding specific URLs patterns.
Main advantages of this feature are:
- Offering more granularity in implementing the code handling received requests. This business logic can be split in several classes. Forget extensive Case of statements in the On Web Connection database method.
- Allowing a redirection on a Qodly page which opens up new perspectives to extend 4D application on the web
This powerful feature unlocks many possibilities, as detailed below. Keep reading to learn more and work with concrete examples.
Before diving into details, let’s have a comparative analysis showing all the benefit you can take from this feature.
comparative analysis
Can I: | Using on Web Connection database method as a handler | Using HTTP Handlers |
Split my code handling requests among several classes | No | Yes |
Redirect the user on a Qodly page | No | Yes |
Force a user to authenticate themselves even when requesting an existing web resource (e.g. an existing html page) | No | Yes |
To illustrate that, let’s have a look at those simple requirements:
- As long as the user is not authenticated, they must be redirected on an authentication page.
- They can only view the html pages in the /pages folder. If they request an unexisting page in this folder, they are redirected on a Not found page.
- If they request other resources, they are redirected on a Not authorized page.
- The /pages request displays the /pages/welcome.html page.
- The authorized html pages offer some upload and download files features
how to do that with the On Web Authentication / Connection database methods
The On Web Authentication method
Case of
// The user is not authenticated
: (Session.isGuest())
WEB SEND HTTP REDIRECT("/authentication/authentication.html")
return False
Else
return True
End case
The On Web connection method
#DECLARE($url : Text)
Case of
: ($url="/pages")
WEB SEND HTTP REDIRECT("/pages/welcome.html")
: (Position("/fileUpload/"; $url)=1)
//Handle the file upload business logic
: (Position("/fileDownload"; $url)=1)
//Handle the file download business logic
// The requested resource is not an html page of the pages folder
: Not(Match regex("/pages/[a-z]*[0-9]*[.]html"; $url))
WEB SEND HTTP REDIRECT("/error/notAuthorized.html")
Else
WEB SEND HTTP REDIRECT("/error/notFound.html")
End case
It is just a sample of code showing the On Web Connection database method might be complex, thus difficult to maintain.
with The http request handlers
The series of examples below will show you how to easily associate some URL patterns to specific business logic and how to organize properly this code among classes.
You’ll also learn how to handle a redirection on Qodly pages and how to redirect the user on an authentication page (even when requesting an existing resource).
request handlers why?
Handling request handlers leads to various scenarios such as:
- use a given URL as a resource provider (e.g., download various files by calling a specific URL)
- use a given URL as a file-uploading box (e.g., upload various files by calling a specific URL)
- handle redirection on specific pages according to a business context (e.g., user authenticated or not, not found page, privileges granted or not … et.c)
how to set up an HTTP request handler
The HTTP request handlers must be set up in the HTTPHandlers.json file in the Project/Sources folder.
This file contains a JSON format collection of objects. Each object refers a request handler with the following information:
- the URL pattern to handle
- the verb used by the URL
- the [singleton class + function] where the request handler code is implemented
example
HTTPHandlers.json file:
[
{
"class": "GeneralHandling",
"method": "gettingStarted",
"pattern": "start",
"verbs": "get, post"
}
]
Question: What does it mean?
Answer: This means that when any request starting by /start/ with a GET or POST verb is received on the server, the function gettingStarted of the singleton GeneralHandling is executed.
how to implement the request handler code
The singleton class mentioned above must be shared.
Input: an instance of the 4D.IncomingMessage class
The request is received on the server as an object instance of the 4D.IncomingMessage class.
The following information is available:
- the complete url of the request
- the parts of the url
- the verb of the request
- the headers of the request
- the parameters put in the URL (if any)
- the body of the request (if any)
Then, the request handler can use all this information to trigger appropriate business logic.
Output: an instance of the 4D.OutgoingMessage class
If needed, the request handler can return an object instance of the 4D.OutgoingMessage class, i.e. some full web content ready for a browser to handle (e.g. such as file content).
a simple example to start
With the same HTTPHandlers.json file as above:
[
{
"class": "GeneralHandling",
"method": "gettingStarted",
"pattern": "start",
"verbs": "get, post"
}
]
The request http://127.0.0.1/start/example?param=demo&name=4D is run with a GET verb in a browser. It is handled by the gettingStarted() function of GeneralHandling singleton class below:
shared singleton Class constructor()
Function gettingStarted($request : 4D.IncomingMessage) : 4D.OutgoingMessage
var $result:=4D.OutgoingMessage.new()
var $body : Text
$body:="Called URL: "+$request.url+"\n"
$body+="The parameters are received as an object:"+"\n"+JSON Stringify($request.urlQuery; *)+"\n"
$body+="The verb is: "+$request.verb+"\n"
$body+="There are "+String($request.urlPath.length)+" url parts - Url parts are: "+$request.urlPath.join(" - ")+"\n"+"\n"
$result.setBody($body)
$result.setHeader("Content-Type"; "text/plain")
return $result
The request is received on the server as $request (an object instance of the 4D.IncomingMessage class).
This $request object contains the properties:
- url (String) – URL of the request
- urlQuery (Object) – Parameters of the request.
- verb (String) – Verb
- urlPath (collection of Strings) – URL parts of the request
Here is the response:
how to handle a body in the request
The 4D.IncomingMessage class provides some functions to get the headers and the body of the request.
example
Let’s run a simple example to upload a file on the server.
The HTTPHandlers.json file:
[
{
"class": "UploadFile",
"method": "uploadFile",
"regexPattern": "/putFile",
"verbs": "POST"
}
]
Note that the URL pattern is given as a regular expression with the regexPattern property.
The request http://127.0.0.1:8044/putFile?fileName=testFile is run with a POST verb and a file content in its body.
The UploadFile singleton class:
Function uploadFile($request : 4D.IncomingMessage) : 4D.OutgoingMessage
var $response:=4D.OutgoingMessage.new()
var $body:="Not supported file"
var $fileName; $fileType : Text
var $file : 4D.File
var $picture : Picture
var $created : Boolean
//The file name is given as parameter in the URL
$fileName:=$request.urlQuery.fileName
// The header "Content-Type" provides the format of the body
$fileType:=$request.getHeader("Content-Type")
Case of
// The body contains a pdf file
: ($fileType="application/pdf")
$file:=File("/PACKAGE/Files/"+$fileName+".pdf")
$created:=$file.create()
// The getBlob() function returns the body of the request as a Blob
$file.setContent($request.getBlob())
$body:="Upload OK - File size: "+String($file.size)
// The body contains a jpg image
: ($fileType="image/jpeg")
$file:=File("/PACKAGE/Files/"+$fileName+".jpg")
// The getPicture() function returns the body of the request as a Picture
$picture:=$request.getPicture()
WRITE PICTURE FILE($file.platformPath; $picture)
$body:="Upload OK - Image size: "+String($file.size)
End case
$response.setBody($body)
$response.setHeader("Content-Type"; "TEXT/plain")
return $response
The file name is given as parameter (fileName) in the URL. It is received in the urlQuery object in the request.
Functions to get the headers and the body of the request
In the example above, note the getHeader() function which returns a given header of the request. The 4D.IncomingMessage class also offers a headers property (Object) containing all the request headers.
The header Content-Type provides the format of the received body. In this example, only the pdf and jpeg formats are handled.
Note the getBlob() and getPicture() functions on the $request object. They provide the body content in the appropriate format that matches the Content-Type header.
See the documentation for more details about those functions.
A getText() function is also available and provides the body of the request as a String (if it has been sent as a String)
If the body has been given as a valid JSON representation, the getJSON() function provides its resolution with the correct type (e.g. , Object, Collection, String, …).
handle redirection
The request handler is also able to redirect on a specific page by returning an instance of the the 4D.OutgoingMessage class. The Location header must be used to indicate the page to redirect on.
The HTTP status 3xx value indicates the client must take additional action to complete the request.
Let’s run this scenario:
– The user can view all the html pages in the WebFolder/pages folder once they are authenticated. As long as the user is not authenticated, they are redirected on an authentication page.
– If the asked page is not in this folder, a Not found page is served.
– If the user asks for a page in a subfolder of the /pages folder, a Not authorized page is served.
The HTTPHandlers.json file:
[
{
"class": "PagesHandling",
"method": "handle",
"regexPattern": "/pages/"
}
]
The PagesHandling singleton class:
shared singleton Class constructor()
Function handle($request : 4D.IncomingMessage) : 4D.OutgoingMessage
var $result:=4D.OutgoingMessage.new()
var $file : 4D.File
//The user is not authenticated
If (Session.isGuest())
$result.setHeader("Location"; "/authentication/Authentication.html")
$result.setStatus(307)
Else
//The URL is an html page in the /pages folder
If (($request.urlPath.length=2) && (Position(".html"; $request.url)#0))
$file:=File("/PACKAGE/WebFolder"+$request.url)
//The pages exists
If ($file.exists)
$result.setBody($file.getContent())
$result.setHeader("Content-Type"; "text/html")
$result.setStatus(200)
//The pages does not exist
Else
$result.setHeader("Location"; "/error/NotFound.html")
$result.setStatus(307)
End if
//The URL is NOT a an html page in the /pages folder
Else
$result.setHeader("Location"; "/error/NotAuthorized.html")
$result.setStatus(307)
End if
End if
return $result
Note that the request handler has access to the Session object which allows more complex business logic (checking the presence of some privileges, use the Session.storage shared object).
Compare this example with the example given at the top of this blogpost (redirection handled with the On Web Connection database method). You’ll see that thanks to the URL patterns given in the HTTPHandlers.json file and the urlPath property of the 4D.IncomingMessage class, no need to test complex regex for the URL.
how to redirect on a qodly page?
Just indicate the rendering URL of your Qodly page in the Location header.
$result.setHeader("Location"; "https://myQodlyApp/$lib/renderer/?w=Authentication")
Download the HDI to practise handling the HTTP requests and don’t wait more to enter the project mode world proposing classes and other powerful features.