How To: Creating a HTTP gateway
Posted: (EET/GMT+2)
How To: Creating a HTTP gateway
Level: WinSock, HTTP, Delphi Advanced
If you have a network with multiple computers, but only a single connection
to the Internet, you will quickly run into problems if multiple users want
to use the connection at the same time. For example, I have a small network
at home, but only a single modem. Of course, I have no problems surfing the
Net because my computer has the connection. But how could my family surf
using the other computers?
The solution
The solution is to write a simple piece of software, known as a gateway. Some of you might want to call the result a proxy, but I refuse to call my creation a proxy, because the software doesn't do any caching, and it is too simple. Thus, I call it a gateway, since it's mainly a pass-thru for HTTP requests.
The operation of the software created here is simple. All you need is some knowledge of how the HTTP protocol used to transfer WWW pages works. For example, when you type the address "www.intel.com" to your browser, the browser makes a connection to Intel's site, and sends a query like the following:
GET / HTTP/1.0If you are using a proxy, for example hosted by your ISP, the browser only connects to and communicates with the proxy software. In this case, the query sent to the proxy looks like this:
GET http://www.intel.com/ HTTP/1.0As you can see, the name of the WWW site is included with the query. The proxy now connects to www.intel.com, and sends a query like in the first example, and the returns the results to the browser.
Writing the gateway
Having got this far, we only need a simple WinSock app, which waits for connections made by the browser, and strips the host name from the query, connects to the given host, and waits for the page. Then, the gateway must return the page to the browser.
In code, we need some additional twists, because browsers like to issue multiple requests at the same time. Thus, the gateway must be multi-threaded. Actually, this is simple, if you follow the rules. In our implementation, the gateway creates a thread which simply waits for connections made by the browser(s). Of course, this could be done in the main thread, but using blocking calls this way would freeze the UI.
When a connection arrives, the gateway spawns another thread to handle the request. Handling the request requires four operations, as described previously. The code looks like this:
procedure THTTPRequest.Execute;
Var
Buf : Array[0..512000] of Char; { HTTP data must be less than 500k }
RS : TSocket; { request socket }
I,J : Integer;
Name : TSockAddr;
Secs : Double;
S,T : String;
P : PChar;
begin
Try
Secs := Now;
I := Recv(Socket,Buf,SizeOf(Buf),0);
If (I = SOCKET_ERROR) Then Begin
Msg := 'Request: Can''t receive header: '+IntToStr(Socket)+' '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
S := Buf;
Delete(S,1,Length('GET HTTP://'));
T := S;
I := Pos('/',T)-1;
SetLength(T,I);
Delete(S,1,I);
S := 'GET '+S;
RS := WinSock.Socket(af_Inet,sock_Stream,IPProto_TCP);
If (RS = Invalid_Socket) Then Begin
Msg := 'Request: Can''t create socket: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
P := StrPos(Buf,#13#10);
Buf[P-Buf] := #0;
Msg := 'Request: ['+IntToStr(RS)+'] '+Buf;
Synchronize(DisplayMessage);
With Name do Begin
sin_Family := pf_Inet;
sin_Port := htons(80);
sin_Addr := TInAddr(SolveHostName(T));
End;
I := Connect(RS,Name,SizeOf(Name));
If (I = SOCKET_ERROR) Then Begin
Msg := 'Request: Can''t connect: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
Try
I := Send(RS,S[1],Length(S),0);
If (I = SOCKET_ERROR) Then Begin
Msg := 'Request: Can''t send thru: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
J := 0;
Repeat
I := Recv(RS,Buf[J],SizeOf(Buf)-J-1,0);
If (I = SOCKET_ERROR) Then Begin
Msg := 'Request: Can''t receive from server: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End
Else J := J+I;
Until (I <= 0);
Buf[J] := #0;
I := Send(Socket,Buf,J,0);
If (I = SOCKET_ERROR) Then Begin
Msg := 'Request: Can''t send to client: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
Secs := ((Now-Secs)*86400000)/1000;
Msg := 'Request: ['+IntToStr(RS)+'] '+IntToStr(J)+' bytes OK in '+
FloatToStrF(Secs,ffFixed,15,3)+' second(s) - '+
FloatToStrF(J/Secs/1024,ffFixed,15,1)+'kB/sec';
Synchronize(DisplayMessage);
Finally
CloseSocket(RS);
End;
Finally
CloseSocket(Socket);
End;
end;
Here, most of the code does simple error handling and miscellaneous stuff,
easy enough not to require explanations. Things to note are that the
gateway currently supports only the GET method, and fails if you try to
post forms etc. Also, the error handling is limited. For example, should the
actual host (Intel in our example) fail, the gateway will fail too.
Also, because the gateway desperately wants to display stats to the user, the message displaying must be synchronized with the main VCL thread. This is done using the Synchronize method of TThread.
Creating a listening socket
To me, the most difficult part in creating the gateway was creating a listening socket. The code is:
Procedure TAcceptor.CreateListenSocket;
Var
Name : TSockAddr;
I : Integer;
Begin
Socket := WinSock.Socket(af_Inet,sock_Stream,IPProto_TCP);
If (Socket = Invalid_Socket) Then Begin
Msg := 'Acceptor: Can''t create socket: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
With Name do Begin
sin_Family := pf_Inet;
sin_Port := htons(TCPGatewayPort);
I := htonl(InAddr_Any);
sin_Addr := TInAddr(I);
End;
I := Bind(Socket,Name,SizeOf(Name));
If (I = SOCKET_ERROR) Then Begin
Msg := 'Acceptor: Can''t bind: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
I := Listen(Socket,3);
If (I = SOCKET_ERROR) Then Begin
Msg := 'Acceptor: Can''t listen: '+IntToStr(WSAGetLastError);
Synchronize(DisplayMessage);
Exit;
End;
Msg := 'Acceptor: Waiting for connections...';
Synchronize(DisplayMessage);
End;
After we have created a socket, we bind it to the "proxy" port, which is
in our case hard-coded as 8000, although this is trivial to change. Next, we
just start to listen, with a buffer of three connections. Actually,
spawning a new thread to the handle the request is so fast, that you will
hardly need all the three sockets.
Configuring the browser
Configuring your browser to use the gateway just created is simple. Simply open the Proxies configuration page, for example Options|Network Preferences, Proxies, View... in Navigator 3. Then, just enter the name of the computer running the gateway (and having the connection to the Internet) in to the HTTP proxy field, and you're ready to go.

Netscape Navigator 3.01's Manual Proxy Configuration.
If you did everything correctly, the results look, well, not very sexy, but
interesting none the less.

The HTTP Gateway in action.
Conclusion
After downloading and studying the code given, you should have some basic
idea how to create a HTTP gateway or a proxy. For example, you could easily
add better error control, access control, caching, FTP support... Again,
there are no limits. Have phun surfing!