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.0
    
If 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.0
    
As 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!