Design your own proxy server using Delphi
When the author was writing an Internet billing software, it involved the issue of how to bill Internet access for each workstation in the local area network. Generally speaking, these workstations access the Internet through a proxy server. When using ready-made proxy server software, since the proxy server software is a closed system, it is difficult to write a program to obtain real-time Internet access timing information. Therefore, consider whether you can write your own proxy server to solve the problem of group Internet access on the one hand and the billing problem on the other hand?
After experimental programming, the problem was finally solved satisfactorily. Write it down now and share it with your colleagues.
1. Ideas
There is a parameter in the system options of currently popular browsers, namely "Connect through a proxy server". After programming testing,
Try, when a workstation in the local network specifies this attribute and then issues an Internet request, the request data will be sent to the specified proxy server. The following is an example of a request packet:
GET http://home.microsoft.com/intl/cn/ HTTP/1.0
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)
Host: home.microsoft.com
PRoxy-Connection: Keep-Alive
The first line is the target URL and related methods and protocols, and the "Host" line specifies the address of the target host.
From this we know the process of proxy service: receiving the request from the proxy, connecting to the real host, receiving the data returned by the host, and sending the received data to the proxy.
For this purpose, a simple program can be written to complete the above network communication redirection problem.
When designing with Delphi, choose ServerSocket as the socket control to communicate with the proxy workstation, and choose ClientSocket dynamic array as the socket control to communicate with the remote host.
An important issue that should be solved during programming is the problem of multiple connection processing. In order to speed up the proxy service and the response speed of the agent, the properties of the socket control should be set to non-blocking; each communication session is dynamically bound to the socket. , use the SocketHandle attribute value of the socket to determine which session it belongs to.
The communication connection process is shown in the figure below:
proxy server
Serversocket
(1) receive
Sent by the agent to the remote host
(6) (2) (5)
Browser ClientSocket (4) Web Server
take over
Send(3)
(1) The proxy browser sends a Web request, and the proxy server's Serversocket receives the request.
(2) The proxy server program automatically creates a ClientSocket, sets the host address, port and other attributes, and then connects to the remote host.
(3) After remote connection, the send event is triggered and the Web request packet received by Serversocket is sent to the remote host.
(4) When the remote host returns page data, the read event of ClientSocket is triggered to read the page data.
(5) The proxy server program determines which Socket in the ServerSocket control should send the page information received from the host to the proxied end based on the binding information.
(6) The corresponding Socket in ServerSocket sends the page data to the agent.
2. Programming
It is very simple to design the above communication process using Delphi, mainly related to ServerSocket and ClientSocket.
Software driver programming. The following is a list of the experimental proxy server interface and source program written by the author, including a brief function description:
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, ScktComp, TrayIcon, Menus, StdCtrls;
type
session_record=record
Used: boolean; {whether session recording is available}
SS_Handle: integer; {proxy server socket handle}
CSocket: TClientSocket; {socket used to connect to the remote}
Lookingup: boolean; {whether looking for the server}
LookupTime: integer; {Lookup server time}
Request: boolean; {whether there is a request}
request_str: string; {request data block}
client_connected: boolean; {client online flag}
remote_connected: boolean; {remote server connection flag}
end;
type
TForm1 = class(TForm)
ServerSocket1: TServerSocket;
ClientSocket1: TClientSocket;
Timer2: TTimer;
TrayIcon1: TTrayIcon;
PopupMenu1: TPopupMenu;
N11: TMenuItem;
N21: TMenuItem;
N1: TMenuItem;
N01: TMenuItem;
Memo1: TMemo;
Edit1: TEdit;
Label1: TLabel;
Timer1: TTimer;
procedure Timer2Timer(Sender: TObject);
procedure N11Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure N21Click(Sender: TObject);
procedure N01Click(Sender: TObject);
procedure ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocket1ClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocket1ClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Integer);
procedure ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure ClientSocket1Write(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedure ServerSocket1Listen(Sender: TObject;
Socket: TCustomWinSocket);
procedure AppException(Sender: TObject; E: Exception);
procedure Timer1Timer(Sender: TObject);
private
{Private declarations}
public
Service_Enabled: boolean; {whether the proxy service is enabled}
session: array of session_record; {session array}
sessions: integer; {number of sessions}
LookUpTimeOut: integer; {connection timeout value}
InvalidRequests: integer; {number of invalid requests}
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
file://System startup timer, after the startup window is displayed, shrink to System Tray...
procedure TForm1.Timer2Timer(Sender: TObject);
begin
timer2.Enabled:=false; {turn off timer}
sessions:=0; {number of sessions=0}
application.OnException := AppException; {To shield exceptions that occur on the proxy server}
invalidRequests:=0; {0 error}
LookUpTimeOut:=60000; {timeout value=1 minute}
timer1.Enabled:=true; {turn on timer}
n11.Enabled:=false; {Enable service menu item invalid}
n21.Enabled:=true; {Close service menu item is valid}
serversocket1.Port:=988; {proxy server port=988}
serversocket1.Active:=true; {Start service}
form1.hide; {hide interface, shrink to System Tray}
end;
file://Open service menu item…
procedure TForm1.N11Click(Sender: TObject);
begin
serversocket1.Active:=true; {Start service}
end;
file://Stop service menu item…
procedure TForm1.N21Click(Sender: TObject);
begin
serversocket1.Active:=false; {stop service}
N11.Enabled:=True;
N21.Enabled:=False;
Service_Enabled:=false; {flag cleared}
end;
file://Main window creation…
procedure TForm1.FormCreate(Sender: TObject);
begin
Service_Enabled:=false;
timer2.Enabled:=true; {When the window is created, open the timer}
end;
file://When the window is closed...
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
timer1.Enabled:=false; {turn off timer}
if Service_Enabled then
serversocket1.Active:=false; {Close the service when exiting the program}
end;
file://Exit program button…
procedure TForm1.N01Click(Sender: TObject);
begin
form1.Close; {Exit program}
end;
file://After turning on the proxy service...
procedure TForm1.ServerSocket1Listen(Sender: TObject;
Socket: TCustomWinSocket);
begin
Service_Enabled:=true; {set service flag}
N11.Enabled:=false;
N21.Enabled:=true;
end;
After file:// is connected to the proxy server by the proxy, a session is established and bound to the socket...
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j: integer;
begin
j:=-1;
for i:=1 to sessions do {find if there are blank items}
if not session[i-1].Used and not session[i-1].CSocket.active then
begin
j:=i-1; {Yes, assign it}
session[j].Used:=true; {set as in use}
break;
end
else
if not session[i-1].Used and session[i-1].CSocket.active then
session[i-1].CSocket.active:=false;
if j=-1 then
begin {none, add one}
j:=sessions;
inc(sessions);
setlength(session,sessions);
session[j].Used:=true; {set as in use}
session[j].CSocket:=TClientSocket.Create(nil);
session[j].CSocket.OnConnect:=ClientSocket1Connect;
session[j].CSocket.OnDisconnect:=ClientSocket1Disconnect;
session[j].CSocket.OnError:=ClientSocket1Error;
session[j].CSocket.OnRead:=ClientSocket1Read;
session[j].CSocket.OnWrite:=ClientSocket1Write;
session[j].Lookingup:=false;
end;
session[j].SS_Handle:=socket.socketHandle; {Save the handle and implement binding}
session[j].Request:=false; {No request}
session[j].client_connected:=true; {client is connected}
session[j].remote_connected:=false; {remote not connected}
edit1.text:=inttostr(sessions);
end;
When file:// is disconnected by the agent...
procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j,k: integer;
begin
for i:=1 to sessions do
if (session[i-1].SS_Handle=socket.SocketHandle) and session[i-1].Used then
begin
session[i-1].client_connected:=false; {client is not connected}
if session[i-1].remote_connected then
session[i-1].CSocket.active:=false {If the remote connection is still connected, disconnect it}
else
session[i-1].Used:=false; {If both are disconnected, set the release resource flag}
break;
end;
j:=sessions;
k:=0;
for i:=1 to j do {There are several unused items at the end of the statistics session array}
begin
if session[ji].Used then
break;
inc(k);
end;
if k>0 then {Modify the session array and release unused items at the end}
begin
sessions:=sessions-k;
setlength(session,sessions);
end;
edit1.text:=inttostr(sessions);
end;
When a file://communication error occurs...
procedure TForm1.ServerSocket1ClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Integer);
var
i,j,k: integer;
begin
for i:=1 to sessions do
if (session[i-1].SS_Handle=socket.SocketHandle) and session[i-1].Used then
begin
session[i-1].client_connected:=false; {client is not connected}
if session[i-1].remote_connected then
session[i-1].CSocket.active:=false {If the remote connection is still connected, disconnect it}
else
session[i-1].Used:=false; {If both are disconnected, set the release resource flag}
break;
end;
j:=sessions;
k:=0;
for i:=1 to j do
begin
if session[ji].Used then
break;
inc(k);
end;
if k>0 then
begin
sessions:=sessions-k;
setlength(session,sessions);
end;
edit1.text:=inttostr(sessions);
errorcode:=0;
end;
When file:// is sent by the proxy to request the page...
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
tmp,line,host: string;
i,j,port: integer;
begin
for i:=1 to sessions do {determine which session it is}
if session[i-1].Used and (session[i-1].SS_Handle=socket.sockethandle) then
begin
session[i-1].request_str:=socket.ReceiveText; {save request data}
tmp:=session[i-1].request_str; {stored in temporary variable}
memo1.lines.add(tmp);
j:=pos(char(13)+char(10),tmp); {one line mark}
while j>0 do {scan the request text line by line, looking for the host address}
begin
line:=copy(tmp,1,j-1); {take a line}
delete(tmp,1,j+1); {delete a row}
j:=pos('Host',line); {host address flag}
if j>0 then
begin
delete(line,1,j+5); {delete the previous invalid character}
j:=pos(':',line);
if j>0 then
begin
host:=copy(line,1,j-1);
delete(line,1,j);
try
port:=strtoint(line);
except
port:=80;
end;
end
else
begin
host:=trim(line); {get host address}
port:=80;
end;
if not session[i-1].remote_connected then {If the expedition is not connected yet}
begin
session[i-1].Request:=true; {set request data ready flag}
session[i-1].CSocket.host:=host; {Set the remote host address}
session[i-1].CSocket.port:=port; {set port}
session[i-1].CSocket.active:=true; {Connect to remote host}
session[i-1].Lookingup:=true; {set flag}
session[i-1].LookupTime:=0; {starts counting from 0}
end
else
{If the remote is connected, send the request directly}
session[i-1].CSocket.socket.sendtext(session[i-1].request_str);
break; {stop scanning request text}
end;
j:=pos(char(13)+char(10),tmp); {points to the next line}
end;
break; {stop loop}
end;
end;
file://When the connection to the remote host is successful...
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
var
i: integer;
begin
for i:=1 to sessions do
if (session[i-1].CSocket.socket.sockethandle=socket.SocketHandle) and session[i-1].Used then
begin
session[i-1].CSocket.tag:=socket.SocketHandle;
session[i-1].remote_connected:=true; {Set the remote host connected flag}
session[i-1].Lookingup:=false; {clear flag}
break;
end;
end;
file://When the remote host disconnects...
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j,k: integer;
begin
for i:=1 to sessions do
if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
begin
session[i-1].remote_connected:=false; {set to not connected}
if not session[i-1].client_connected then
session[i-1].Used:=false {If the client is disconnected, set the release resource flag}
else
for k:=1 to serversocket1.Socket.ActiveConnections do
if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) and session[i-1].used then
begin
serversocket1.Socket.Connections[k-1].Close;
break;
end;
break;
end;
j:=sessions;
k:=0;
for i:=1 to j do
begin
if session[ji].Used then
break;
inc(k);
end;
if k>0 then {fix session array}
begin
sessions:=sessions-k;
setlength(session,sessions);
end;
edit1.text:=inttostr(sessions);
end;
file://When an error occurs communicating with the remote host...
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Integer);
var
i,j,k: integer;
begin
for i:=1 to sessions do
if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
begin
socket.close;
session[i-1].remote_connected:=false; {set to not connected}
if not session[i-1].client_connected then
session[i-1].Used:=false {If the client is disconnected, set the release resource flag}
else
for k:=1 to serversocket1.Socket.ActiveConnections do
if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) and session[i-1].used then
begin
serversocket1.Socket.Connections[k-1].Close;
break;
end;
break;
end;
j:=sessions;
k:=0;
for i:=1 to j do
begin
if session[ji].Used then
break;
inc(k);
end;
errorcode:=0;
if k>0 then {fix session array}
begin
sessions:=sessions-k;
setlength(session,sessions);
end;
edit1.text:=inttostr(sessions);
end;
file://Sends a page request to the remote host…
procedure TForm1.ClientSocket1Write(Sender: TObject;
Socket: TCustomWinSocket);
var
i: integer;
begin
for i:=1 to sessions do
if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
begin
if session[i-1].Request then
begin
socket.SendText(session[i-1].request_str); {If there is a request, send}
session[i-1].Request:=false; {clear flag}
end;
break;
end;
end;
file://When the remote host sends page data...
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j: integer;
rec_bytes: integer; {returned data block length}
rec_Buffer: array[0..2047] of char; {returned data block buffer}
begin
for i:=1 to sessions do
if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
begin
rec_bytes:=socket.ReceiveBuf(rec_buffer,2048); {receive data}
for j:=1 to serversocket1.Socket.ActiveConnections do
if serversocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle then
begin
serversocket1.Socket.Connections[j-1].SendBuf(rec_buffer,rec_bytes); {Send data}
break;
end;
break;
end;
end;
File:// "Page not found" and other error messages appear...
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
inc(invalidrequests);
end;
file://Find remote host timing...
procedure TForm1.Timer1Timer(Sender: TObject);
var
i,j: integer;
begin
for i:=1 to sessions do
if session[i-1].Used and session[i-1].Lookingup then {if connecting}
begin
inc(session[i-1].LookupTime);
if session[i-1].LookupTime>lookuptimeout then {if timeout}
begin
session[i-1].Lookingup:=false;
session[i-1].CSocket.active:=false; {stop searching}
for j:=1 to serversocket1.Socket.ActiveConnections do
if serversocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle then
begin
serversocket1.Socket.Connections[j-1].Close; {disconnect client}
break;
end;
end;
end;
end;
end.
3. Postscript
Since this design idea only adds a redirection function between the proxy end and the remote host, the original proxy end
Some features such as caching technology are retained, so the efficiency is high. After testing, when using a 33.6K Modem to access the Internet, three to ten proxy workstations can access the Internet at the same time, and there is still a good response speed. Since the connection between the proxy workstation and the proxy server workstation generally passes through a high-speed link, the bottleneck mainly occurs in the proxy server's Internet access method.
Through the above method, the author successfully developed a complete set of proxy server software and fully integrated it with the computer room billing system.
Successfully, it is possible to use one workstation to complete functions such as Internet proxy, Internet billing, and machine usage billing. Friends with programming experience can add additional proxy server functions, such as setting prohibited access sites, counting customer traffic, Web access lists, etc.
Author's Blog: http://blog.csdn.net/BExpress/