sâmbătă, 12 iulie 2014

Lazarus FPC / Indy 10 / HTTP / HTTPS (OpenSuSE 12.3)

Can FPC/Lazarus be used to create a web server? Yes, it can! Can it be done in a secure mode? Of course!

1) A simple HTTP server

On a form drop a TIdHTTPServer component from "Indy Server Protocols (am)". Assign a free port to that (let's say 1800) and a "OnCommandGet" event (it will not work without it). A simple example for OnCommandGet:

AResponseInfo.ContentText:= '<!DOCTYPE html>'+
    '<html>'+
    '<body>'+
    'This is a web page! <br>'+
    '</body>'+
    '</html>';

To start the server add the following lines in form's OnShow event:

      hserver1.Bindings.Add.IPVersion:=Id_IPv4;
      hserver1.Active:=true;

(where hserver1 is the name of the TIdHTTPServer component)

Run the program, open a web browser and type the network address of your computer, like this:
   http://192.168.1.100:1800
It should display a white page with the message "This is a web page!"
Make sure the firewall is not interfering (it will be covered in another post)

Some tips for OnCommandGet:

- To read the parameters passed by the client:
      ARequestInfo.queryparams;

- To read the host's address:
     ARequestInfo.Host;

- To read the client's ip:
     Acontext.Binding.PeerIP;

 - To pass a document to the client (let's say it is required like this: http://192.168.1.100:1800/document.doc):
     var t:widestring;
     [...]
     t:=copy(ARequestInfo.document,2,99);
     if t='document.doc' then
         aresponseinfo.servefile(acontext,'document.doc');

The file documet.doc must be in the same place as the executable; if not, you can use the full path like this:
aresponseinfo.servefile(acontext,'c:\Documents\document.doc');

- To display a picture on your web page (let's say it is required like this: http://192.168.1.100:1800/img1.gif) use the following piece of code:
          var t:widestring; s:tmemorystream;
     [...]
     t:=copy(ARequestInfo.document,2,99);
     if t='img.gif' then
       begin
        s:=tmemorystream.create;
        s.loadfromfile('img1.gif');
        AResponseInfo.ContentType:='image/gif';
        aresponseinfo.ContentStream:=s; 
       end;


2) HTTPS - a secure connection

(1) In order to create a secure connection OpenSSL is required.
- OpenSUSE - open YaST and install the following packages: openssl, libopenssl-devel. A restart might be required.
- Windows - the required program can be found freely on the Internet, just google for "windows openssl" (I found this: https://www.openssl.org/related/binaries.html); download, install and restart.

(2) Now a private key and a certificate must be created. It will not work without that. The full documentation can be found here:
     https://www.openssl.org/docs/HOWTO/keys.txt
     https://www.openssl.org/docs/HOWTO/certificates.txt
In short, these are the steps to do just that (the OpenSuSE way):
    - Open Dolphin and go the directory where your program is
    - Open a terminal window
    - Type openssl genrsa -des3 -out privkey.pem 2048
       You will be asked for a password, then the file privkey.pem will be created
    - Type openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095
        You will be asked for the password of the private key created in the previous step and then for some informations that will be included in the certificate (name, country, etc.), then the file cacert.pem will be created

(3) Back to our program, drop a TIdIOHendlerSSLOpenSSL component from "Indy I/O handler Protocols" on the form with the TIdHTTPServer component (see the first part of this post). I also droped a TIdAntyFreeze component and set it active, I don't know if it helped or not.
Let's configure the components:
 - Check the "Method" property of the OpenSSL component, it should be set to "sslvTLSv1"; if you have problems with the connection and nothing else is working, you may try to select a different value for that property.
 - Assign the TIdHTTPServer component's property "IOhandler" to your OpenSSL component (select the OpenSSL component for that property)
 - Assign  the password of your key to the SSL component at Events> OnGetPassword like this (xxxxx is your key password):
          password:='xxxxx';
 - Assign the key and the certificate to the SSL component before you start the server. So, the OnShow event of the form should contain the following:

     ssl1.SSLOptions.KeyFile:='privkey.pem';
     ssl1.SSLOptions.CertFile:='cacert.pem';
     ssl1.SSLOptions.RootCertFile:='cacert.pem';
     hserver1.Bindings.Add.IPVersion:=Id_IPv4;
     hserver1.Active:=true;

      ssl1 is the name of the TIdIOHendlerSSLOpenSSL component.

Everything goes just like in the first part of this post with one exception: now you will access the page from a web browser by typing:
     https://192.168.1.100:1800
Be aware that the OpenSSL component might generate various exceptions in debugging mode, so if it doesn't seems to work try to build your program and run it without the debugger