|
This article represents the fully functional multithreaded multi connection HTTP server that works asynchronously in a thread pool, accepts the GET, POST, PUT and any other HTTP requests, and sends the corresponding user-defined responses.
The TclHttpServer component is built based on the Clever Internet Suite library and uses the fast and stable classes: TclTcpServer, TclUserConnection, and TclThreadPool.
|
The Clever Internet Suite library provides a special TclTcpServer basic component, which can be used as an ancestor class for implementing a new server component. We need to override the following two virtual methods in order to create a new user connection and handle the request data:
// [Delphi]
TclHttpServer = class(TclTcpServer)
protected
function CreateDefaultConnection: TclUserConnection; override;
procedure DoReadConnection(AConnection: TclUserConnection; AData: TStream); override;
end; |
// [Delphi]
function TclHttpServer.CreateDefaultConnection: TclUserConnection;
begin
Result := TclHttpUserConnection.Create(HttpVersion, CharSet);
end; |
// [Delphi]
procedure TclHttpServer.DoReadConnection(AConnection: TclUserConnection; AData: TStream);
begin
inherited DoReadConnection(AConnection, AData);
//handle the request data here
end; |
The TclHttpUserConnection object holds the request parameters, such as the request URI, request method, header, cookies, and the request body. If you need to specify the response headers, response cookies, or the response version, you can do that by assiging the corresponding TclHttpUserConnection property.
// [Delphi]
TclHttpUserConnection = class(TclUserConnection)
public
function AcceptRequestData(AData: TStream): Boolean;
property RequestVersion: TclHttpVersion read FRequestVersion;
property RequestMethod: string read FRequestMethod;
property RequestUri: string read FRequestUri;
property RequestHeader: TclHttpRequestHeader read FRequestHeader;
property RequestCookies: TStrings read FRequestCookies;
property ResponseVersion: TclHttpVersion read FResponseVersion write FResponseVersion;
property ResponseHeader: TclHttpResponseHeader read FResponseHeader;
property ResponseCookies: TStrings read FResponseCookies;
property RequestBody: TStream read FRequestBody write SetRequestBody;
end; |
When a request is received, the DoReadConnection method is called. We need to read the request header, parse it, and accept the rest of the request data. The DoReadConnection method handles the server errors as well. If there is HTTP protocol error, the server returns the error status response. Otherwise, the OnServerError event fires. This event belongs to the TclTcpServer component. If the connection is still available, the server attempts to send the “Internal error” status to the client with the code 500.
The Content-Length header field should be extracted and analyzed. The TclHttpUserConnection class implements this functionality.
// [Delphi]
procedure TclHttpServer.DoReadConnection(AConnection: TclUserConnection; AData: TStream);
var
conn: TclHttpUserConnection;
begin
inherited DoReadConnection(AConnection, AData);
conn := TclHttpUserConnection(AConnection);
try
if conn.AcceptRequestData(ARequest) then
begin
// raises the OnReceiveRequest event
DoReceiveRequest(conn, conn.RequestMethod, conn.RequestUri,
conn.RequestHeader, conn.RequestBody);
end;
except
on E: EclHttpServerError do
begin
SendResponseAndClose(conn, E.ErrorCode, E.Message, E.Message);
end;
end;
end; |
Each request handling should be ended by sending appropriate response.
We need to send the HTTP response header and the response body, if latter exists. A set of overloaded SendResponse methods does all the work. There is also a set of SendResponseAndClose methods. The only difference is that the SendResponseAndClose methods close the connection when the response sending completed. The cause of this behavior is an asynchronous nature of the server. Data is transferred asynchronously after exiting the SendResponse and SendResponseAndClose methods.
// [Delphi]
procedure TclHttpServer.SendResponse(AConnection: TclHttpUserConnection;
AStatusCode: Integer; const AStatusText: string; ABody: TStream);
begin
try
SendResponseHeader(AConnection, AStatusCode, AStatusText, ABody);
AConnection.WriteData(ABody);
finally
AConnection.Clear();
end;
end; |
The usage of the TclHttpServer component is simple. After downloading and installing the Clever Internet Suite library, simply download the HttpServer project, compile and install it.
There is only one important component event should be handled: OnReceiveRequest. This event is used for obtaining the request data and sending the server response. The other component events can be used for, e.g. logging purposes.
Note: the component events are not synchronized with the main application thread. For accessing the VCL objects, such as TEdit and TMemo, a thread synchronization code should be used.
// [Delphi]
procedure TForm1.clHttpServer1ReceiveRequest(Sender: TObject;
AConnection: TclHttpUserConnection; const AMethod, AUri: string;
AHeader: TclHttpRequestHeader; ABody: TStream);
begin
PutLogMessage('Request: ' + AMethod + ' ' + AUri + ' Length: ' + IntToStr(ABody.Size));
AConnection.ResponseHeader.ContentType := 'text/html';
clHttpServer1.SendResponse(AConnection, 200, 'OK',
'<html><body>Your requested the ' + AUri + ' resource.</body></html>');
end; |
// [Delphi]
procedure TForm1.clHttpServer1AcceptConnection(Sender: TObject;
AConnection: TclUserConnection; var Handled: Boolean);
begin
PutLogMessage('Accept Connection. Host: ' + AConnection.PeerIP);
end; |
// [Delphi]
procedure TForm1.PutLogMessage(const ALogMessage: string);
begin
FSynchronizer.Enter();//an instance of TCriticalSection
try
memLog.Lines.Add(ALogMessage);
finally
FSynchronizer.Leave();
end;
end; |
Download HttpServer Component and Demo Program on GitHub
Clever Internet Suite Download
Learn more about Clever Internet Suite Library
Ask a Question
Sergey Shirokov
Clever Components team
www.clevercomponents.com
|