Selasa, 16 Desember 2025

Creating Simple Web Server with Lazarus

 

Web server is a program or sub program that provide data to client program not limited to web browser using HTTP or HTTPS (HTTP over SSL) protocol. HTTP is a simple, fast and robust protocol for client-server application. The HTTP protocol works by having the client send a request to the server, then the server processes the request and sends back a response to the client.

Example of HTTP Client's Request:

GET / HTTP/1.1 Host: www.example.com

The first line of a HTTP request consist of a command, a url and the protocol version. Followed by header lines and terminated by empty line. Each header line consist of header name and value separated by colon and one space. After the header data is the content data that contain n bytes where n is the value of Content-Length header.

Example of HTTP Server's Response:

HTTP/1.1 200 OK Date: Mon, 23 May 2005 22:38:34 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 155 Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux) ETag: "3f80f-1b6-3e1cb03b" Accept-Ranges: bytes Connection: close <html> <head> <title>An Example Page</title> </head> <body> <p>Hello World, this is a very simple HTML document.</p> </body> </html>

The first line of a HTTP response consist of protocol version and result code followed by error message. Followed by header lines and terminated by empty line. Each header line consist of header name and value separated by colon and one space. After the header data is the content data that contain n bytes where n is the value of Content-Length header.

Free Pascal, Delphi and Lazarus share the same programming language the Object Pascal with little differentiation. So many Delphi component can be used in Lazarus and vice versa because it only requires minor changes in the source code to support both programming. If you don't want to use visual component you can use operating system API like winsock (Windows Socket) for Windows or unisock (Unix Socket) for Linux. I will use synapse for Free Pascal library in this example.

To help parsing input data I create TStringSplitter :

  1.      type
  2.       { TStringSplitter }
  3.       TStringSplitter = class(TObject)
  4.       private
  5.         FLine: String;
  6.         FCol: Integer;
  7.         procedure SetCol(AValue: Integer);
  8.         procedure SetLine(AValue: String);
  9.       public
  10.         constructor Create;
  11.         function Fetch(const ATerminator: String=''): String;
  12.         property Line: String read FLine write SetLine;
  13.         property Col: Integer read FCol write SetCol;
  14.       end;

  15.     function PosAfter(const SubText, Text: String; StartAt: Integer): Integer;
  16.     var
  17.       I, L1, L2: Integer;
  18.     begin
  19.       Result := -1;
  20.       L1 := Length(SubText);
  21.       L2 := Length(Text);
  22.       if ((L1 > L2) or (StartAt < 1)) then exit;
  23.       for I := StartAt to L2-L1+1 do
  24.         if (CompareMem(@SubText[1], @Text[I], L1)) then
  25.         begin
  26.           Result := I;
  27.           exit;
  28.         end;
  29.     end;
  30.  
  31.     procedure TStringSplitter.SetCol(AValue: Integer);
  32.     begin
  33.       FCol := AValue;
  34.     end;
  35.  
  36.     procedure TStringSplitter.SetLine(AValue: String);
  37.     begin
  38.       FLine := AValue;
  39.       FCol := 1;
  40.     end;
  41.  
  42.     constructor TStringSplitter.Create;
  43.     begin
  44.       FLine := '';
  45.       FCol := 1;
  46.     end;
  47.  
  48.     function TStringSplitter.Fetch(const ATerminator: String): String;
  49.     var
  50.       I, J: Integer;
  51.     begin
  52.       Result := '';
  53.       I := FCol;
  54.       if (I > Length(FLine)) then exit;
  55.       if (ATerminator <> '') then
  56.       begin
  57.         J := PosAfter(ATerminator, FLine, I);
  58.         if (J > 0) then
  59.         begin
  60.           Result := Copy(FLine, I, J-I);
  61.           FCol := J + Length(ATerminator);
  62.           exit;
  63.         end;
  64.       end;
  65.       Result := Copy(FLine, FCol, MaxInt);
  66.       FCol := Length(FLine) + 1;
  67.     end;

When the server is started it initialize the socket to listen to the specified port. Then it repeatly check for incoming connection and start HTTP handler worker thread until the server is terminated. 

  1. procedure TDaemon1.WorkServerListener(Thread: TWorkerThread);
  2. var
  3.   sock: TTCPBlockSocket;
  4.   h: TSocket;
  5. begin
  6.   try
  7.     sock := TTCPBlockSocket.Create;
  8.     try
  9.       sock.Bind('0.0.0.0',IntToStr(Port));
  10.       if (sock.LastError <> 0) then
  11.         raise ENetworkError.Create('Cannot bind to port '+IntToStr(Port));
  12.       sock.Listen;
  13.       if (sock.LastError <> 0) then
  14.         raise ENetworkError.Create('Cannot listen to port '+IntToStr(Port));
  15.       while (not Thread.Terminated) do
  16.       begin
  17.         if (sock.CanRead(0)) then
  18.         begin
  19.           h := sock.Accept;
  20.           {$HINTS OFF}
  21.           if (h <> INVALID_SOCKET) then
  22.             WorkerThreads.AddNew(@WorkRequestHandler,Pointer(h));
  23.           {$HINTS ON}
  24.         end
  25.         else
  26.           Sleep(1);
  27.       end;
  28.     finally
  29.       sock.Free; // close and free socket object
  30.     end;
  31.   except
  32.     on E: Exception do
  33.       // DebugLog('HTTP Server terminated by error '+E.ClassName+': '+
  34.       //   E.Message);
  35.   end;
  36. end;

The HTTP handler worker thread read HTTP request from incoming connection and send back HTTP Respond.

  1. procedure TDaemon1.WorkRequestHandler(Thread: TWorkerThread);
  2. var
  3.   sock: TTCPBlockSocket;
  4.   S1: TStringSplitter;
  5.   s, cmd, url, http_ver, nm, vl, error_msg, respond_text, respond_data: AnsiString;
  6.   I, result_code: Integer;
  7.   close_socket, can_read: Boolean;
  8.   headers, gets, respond_headers: TStrings;
  9. begin
  10.   result_code := 500;
  11.   close_socket := True;
  12.   sock := TTCPBlockSocket.Create;
  13.   S1 := TStringSplitter.Create;
  14.   headers := TStringList.Create;
  15.   gets := TStringList.Create; 
  16.   respond_headers := TStringList.Create;
  17.   try
  18.     {$HINTS OFF}
  19.     sock.Socket := TSocket(Thread.Param);
  20.     {$HINTS ON}
  21.     repeat
  22.       can_read := sock.CanRead(0);
  23.       if (sock.LastError <> 0) then exit;
  24.       if (can_read) then
  25.       begin
  26.         I := Pos('?',url);
  27.         if (I >= 1) then
  28.         begin
  29.          S1.Line := Copy(url, I+1, MaxInt);
  30.          repeat
  31.             nm := S1.Fetch('=');
  32.             if (nm <> '') then
  33.             begin
  34.               vl := S1.Fetch('&');
  35.               gets.Values[nm] := vl;  
  36.             end;
  37.           until (nm = '');
  38.           Delete(url,I,MaxInt); 
  39.         end;  
  40.         S1.Line := sock.RecvString(ReadTimeout);
  41.         if (sock.LastError <> 0) then exit;
  42.         cmd := S1.Fetch(' ');
  43.         url := S1.Fetch(' ');
  44.         http_ver := S1.Fetch;
  45.         if ((cmd = 'GET') or (cmd = 'POST')) then
  46.         begin
  47.           close_socket := (http_ver = 'HTTP/0.9') or (http_ver = 'HTTP/1.0');
  48.           S1.Line := sock.RecvString(ReadTimeout);
  49.           if (sock.LastError <> 0) then exit;
  50.           while (S1.Line <> '') do
  51.           begin
  52.             nm := S1.Fetch(': ');
  53.             vl := S1.Fetch;
  54.             headers.Values[nm] := vl;
  55.             S1.Line := sock.RecvString(ReadTimeout); 
  56.             if (sock.LastError <> 0) then exit; 
  57.           end;
  58.           //
  59.           // .. do some processing here
  60.           //
  61.           respond_data :=
  62.             '<head><title>Welcome</title></head><body><h1>Welcome to my Web Server</h1><p>You are in here: '+HttpEncode(url)+'</body>';
  63.           respond_headers.Values['Content-Type'] := 'html/text';
  64.           respond_headers.Values['Content-Length'] := IntToStr(Length(respond_data));
  65.           result_code := 200;
  66.         end
  67.         else begin
  68.           result_code := 501;
  69.           close_socket := True; 
  70.         end;
  71.        if((result_code < 200) or (result_code > 499)) then close_socket := True;
  72.         case result_code of
  73.         200: error_msg := 'OK';
  74.         400: error_msg := 'Bad Reqeust';
  75.         500: error_msg := 'Internal Server Error'; 
  76.         501: error_msg := 'Not Implemented';   
  77.         end;  
  78.         respond_text :=
  79.           http_ver + ' ' + IntToStr(result_code) + ' ' + error_msg + #13#10;
  80.         for I := 0 to respond_headers.Count do
  81.           respond_text := respond_text + respond_headers.Names[I] + ': ' +
  82.             respond_headers.ValueFromIndex[I] + #13#10;
  83.         respond_text := respond_text + #13#10 + respond_data;
  84.         sock.SendBuffer(@respond_text[1], Length(respond_text));
  85.         headers.Clear;
  86.         respond_headers.Clear;
  87.         respond_text := '';
  88.       end
  89.       else
  90.         Sleep(1);
  91.     until (Thread.Terminated or close_socket);
  92.   finally
  93.     respond_headers.Free;
  94.     gets.Free;
  95.     headers.Free;
  96.     S1.Free;
  97.     sock.Free;
  98.   end;
  99. end;

You can send and receive data using easy to parse data like JSON, XML, BSON or binary data for data service, it is very flexible.

That's all for this article and I hope it's useful.  

Tidak ada komentar:

Creating Expert System for Database Application Development

  In the year 2016, my friend show me he create a desktop application using Delphi that he create very fast. I think I cannot compete with h...