----------------------------------------------------------------------------- -- -- Onions Network Streams Library -- -- O N I O N S . C O N N E C T I O N S -- -- B o d y -- -- Copyright (C) 1997-1998 Regents of the University of California -- -- Onions is free software; you can redistribute it and/or modify it under -- the terms of the GNU General Public License as published by the Free -- Software Foundation, with or without the single exception listed below; -- either version 2, or (at your option) any later version. Onions is -- distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -- without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -- PARTICULAR PURPOSE. See the GNU General Public License for more details. -- You should have received a copy of the GNU General Public License -- distributed with Onions; see the file COPYING. If not, write to the -- Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA -- 02111-1307, USA. -- -- As a special exception, if other files instantiate generics from this -- library, or you link this library with other files to produce an -- executable, this library does not by itself cause the resulting -- executable to be covered by the GNU General Public License. This -- exception does not however invalidate any other reasons why the -- executable file might be covered by the GNU General Public License. -- -- Created in 1997 by Roy T. Fielding and Kari Nies ----------------------------------------------------------------------------- -- -- The Onions Connection Manager controls the number of active TCP socket -- connections used by a client application and keeps track of persistent -- connections (sockets that are currently connected to a host and waiting -- to be reused). -- -- Each connection slot is represented by a Connection object that keeps -- track of its state and the state of the two (input and output) pipes -- associated with the connection. Each pipe consists of a file stream -- object while the socket is open, topped by a channel object whenever -- the connection is in use. The channel objects provide a layer of -- encapsulation so that the Connection methods are called to control -- the stream instead of calling the file stream object directly. Note -- that the other stream methods (Read, Write, etc.) are not affected. -- -- A persistent connection is enabled when a persistent protocol like -- HTTP/1.1 is used (as set by the client) and both channels have closed -- normally. If the client leaves the connection state as not Persistent -- (the default), or if either channel is aborted instead of closed, then -- the connection is closed as soon as both channels have closed/aborted. -- -- We considered using C_shutdown to close each half of the connection as -- each non-persistent channel was closed, but then we realized that in -- HTTP/1.1 the client may not know whether or not the connection is -- persistent until after the response message is received, which is long -- after the first outgoing channel is closed. -- -- What we haven't implemented yet is how to enable pipelining on a single -- connection, i.e., using the outgoing channel to send many requests -- before the first request has been completely received. The problem is -- essentially one of keeping two queues of Incoming and Outgoing pipes, -- and just dequeueing them as they complete or get closed/aborted. -- However, it would also require more sophisticated stream control, -- such as placing a dam on each outgoing channel until it is connected -- to the file stream object, since we would then be dealing with the -- concurrency issues of multiple writers. Note that this is essentially -- the same problem as multiplexing data streams on a single connection, so -- the best solution may be to define separate packages for Pipelines and -- Muxes that are replacements for this package (and Channels); that would -- allow programmers to pick simplicity over performance when desired. -- First things first, though. -- with System; with Onions.Constants; with Onions.OS; with Onions.Instreams.Channel; with Onions.Outstreams.Channel; with Onions.Instreams.File; with Onions.Outstreams.File; package body Onions.Connections is use System; use Connection_Queues; use Onions.Instreams; use Onions.Outstreams; use Onions.Instreams.Channel; use Onions.Outstreams.Channel; use Onions.Instreams.File; use Onions.Outstreams.File; use Onions.Thin; use Onions.Constants; use Onions.Sockets; use type C.int; use type C.unsigned_short; -- Connect asks the Connection Manager for a new Connection and -- then, if it is not already connected, attempts to connect it. -- If successful, Incoming and Outgoing point to the input and -- output channels, respectively. Note that the caller will -- later "disconnect" a connection when they close both pipes. -- -- Raises Connection_Error if the attempt fails. -- procedure Connect (Server : in Host_Location; Incoming : out Input_Pipe; Outgoing : out Output_Pipe) is Conn : A_Connection; Sockfd : Descriptor; Dupfd : Descriptor; begin -- Note that the Connection Manager will queue a request for a new -- connection if it would exceed the maximum active connection limit. -- Connection_Manager.New_Connection (Server, Conn); if Conn.Persistent then Conn.Persistent := False; -- reset for next request else Sockfd := Establish_Connection (Server); -- We need to dup the socket descriptor so that -- incoming and outgoing can close separately. loop Dupfd := C_dup (Sockfd); exit when Dupfd /= Failure; if OS.C_errno /= EINTR then OS.Raise_Error (Connection_Error'Identity, OS.C_errno, "Connect: Unable to dup socket descriptor"); end if; end loop; Conn.Incoming := Bind (new File_Input_Stream, Sockfd); Conn.Outgoing := Bind (new File_Output_Stream, Dupfd); end if; Push (Conn.Incoming, Bind (new Channel_Input_Stream, Conn)); Push (Conn.Outgoing, Bind (new Channel_Output_Stream, Conn)); Conn.In_State := Open; Conn.Out_State := Open; Incoming := Conn.Incoming; Outgoing := Conn.Outgoing; end Connect; -- Close_Incoming is used by the input channel to signal a close. -- Note that the channel stream object will be freed by whatever process -- is closing the stream, so we pop it off our socket's file stream. -- procedure Close_Incoming (Conn : A_Connection) is Head : Input_Pipe; begin if Conn /= null and then Conn.In_State = Open then Conn.In_State := Closed; Pop (Conn.Incoming, Head); if Conn.Out_State /= Open then -- Outgoing has already been closed and had its channel popped -- if Conn.Persistent then Connection_Manager.Free_Connection (Conn); else Close (Conn.Outgoing); Close (Conn.Incoming); Conn.In_State := Disconnected; Conn.Out_State := Disconnected; Connection_Manager.Free_Connection (Conn); end if; end if; end if; end Close_Incoming; -- Close_Outgoing is used by the output channel to signal a close. -- Note that the channel stream object will be freed by whatever process -- is closing the stream, so we pop it off our socket's file stream. -- procedure Close_Outgoing (Conn : A_Connection) is Head : Output_Pipe; begin if Conn /= null and then Conn.Out_State = Open then Conn.Out_State := Closed; Pop (Conn.Outgoing, Head); if Conn.In_State /= Open then -- Incoming has already been closed and had its channel popped -- if Conn.Persistent then Connection_Manager.Free_Connection (Conn); else Close (Conn.Outgoing); Close (Conn.Incoming); Conn.In_State := Disconnected; Conn.Out_State := Disconnected; Connection_Manager.Free_Connection (Conn); end if; end if; end if; end Close_Outgoing; -- Abort_Incoming is used by the input channel to signal an abort. -- Note that the channel stream object will be freed by whatever process -- is closing the stream, so we pop it off our socket's file stream. -- procedure Abort_Incoming (Conn : A_Connection) is Head : Input_Pipe; begin if Conn /= null and then Conn.In_State /= Aborted then Conn.In_State := Aborted; Conn.Persistent := False; Pop (Conn.Incoming, Head); if Conn.Out_State /= Open then -- Outgoing has already been closed and had its channel popped -- Abort_Stream (Conn.Outgoing); Abort_Stream (Conn.Incoming); Conn.In_State := Disconnected; Conn.Out_State := Disconnected; Connection_Manager.Free_Connection (Conn); end if; end if; end Abort_Incoming; -- Abort_Outgoing is used by the output channel to signal an abort. -- Note that the channel stream object will be freed by whatever process -- is closing the stream, so we pop it off our socket's file stream. -- procedure Abort_Outgoing (Conn : A_Connection) is Head : Output_Pipe; begin if Conn /= null and then Conn.Out_State /= Aborted then Conn.Out_State := Aborted; Conn.Persistent := False; Pop (Conn.Outgoing, Head); if Conn.In_State /= Open then -- Incoming has already been closed and had its channel popped -- Abort_Stream (Conn.Outgoing); Abort_Stream (Conn.Incoming); Conn.In_State := Disconnected; Conn.Out_State := Disconnected; Connection_Manager.Free_Connection (Conn); end if; end if; end Abort_Outgoing; -- Name returns a string corresponding to the connection's server -- function Name (Conn : A_Connection) return String is begin if Conn /= null then return Image (Conn.Server); else return ""; end if; end Name; ------------------------------------------------------------------- -- The Connection Manager prevents the client from exceeding its -- own resource limits (too many open connections) while at the -- same time managing persistent connections. -- protected body Connection_Manager is -- Private routine prototypes -- procedure Deactivate_Connection (Conn : A_Connection); procedure Find_Connected_Server (Server : in Host_Location; Where : out A_Connection); -- Interface for client to see and set its own connection limits -- function Total_Connections return Natural is begin return Num_Active + Num_Persist; end Total_Connections; function Active_Connections return Natural is begin return Num_Active; end Active_Connections; function Get_Max_Connections return Natural is begin return Max_Connections; end Get_Max_Connections; procedure Set_Max_Connections (Max : Natural) is begin Max_Connections := Max; end Set_Max_Connections; -- Calls to New_Connection will queue if max number of active -- connections is exceeded. Look to see if a persistent connection -- is available first; if not, then check if we have too many and, -- if so, close one and take its slot. Otherwise, take the first -- free connection or create one from scratch. -- entry New_Connection (Server : Host_Location; Conn : out A_Connection) when Num_Active < Max_Connections is begin Find_Connected_Server (Server, Conn); if Conn = null then if (Num_Active + Num_Persist) >= Max_Connections then Extract_Last (Persist_List, Conn); Close (Conn.Outgoing); Close (Conn.Incoming); Conn.In_State := Disconnected; Conn.Out_State := Disconnected; elsif Free_List = null then Conn := new Connection; else Extract (Free_List, Conn); end if; Conn.Server := Server; end if; Active_List := Add_Before (Active_List, Conn); Num_Active := Num_Active + 1; end New_Connection; -- Free a Connection for later reuse. Raises Constraint_Error -- if we don't have any record of this connection object. -- procedure Free_Connection (Conn : A_Connection) is Where : A_Connection; begin Deactivate_Connection (Conn); if Conn.Persistent then Persist_List := Add_Before (Persist_List, Conn); Num_Persist := Num_Persist + 1; else Free_List := Add_Before (Free_List, Conn); end if; end Free_Connection; -- Find the connection within the Active_List and extract it. -- Raises Constraint_Error if we don't find it. -- procedure Deactivate_Connection (Conn : A_Connection) is Where : A_Connection; Pos : Connection_List; begin if Active_List.Data = Conn then Extract (Active_List, Where); Num_Active := Num_Active - 1; else Pos := Active_List.Next; while Pos /= null loop if Pos.Data = Conn then Extract (Pos, Where); Num_Active := Num_Active - 1; return; else Pos := Pos.Next; end if; end loop; raise Constraint_Error; end if; end Deactivate_Connection; -- Find a server within the Persist_List and extract it if found -- procedure Find_Connected_Server (Server : in Host_Location; Where : out A_Connection) is Pos : Connection_List; begin if Persist_List = null then Where := null; elsif Persist_List.Data.Server = Server then Extract (Persist_List, Where); Num_Persist := Num_Persist - 1; else Where := null; Pos := Persist_List.Next; while Pos /= null loop if Pos.Data.Server = Server then Extract (Pos, Where); Num_Persist := Num_Persist - 1; exit; else Pos := Pos.Next; end if; end loop; end if; end Find_Connected_Server; end Connection_Manager; end Onions.Connections;