X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/533151d3ff0e22a91d0e2bcfc0fa225e8843c97f..e28f10c4b7db4283bb1d65e3a1a9b025f3becd5b:/methods/http.cc diff --git a/methods/http.cc b/methods/http.cc index 067d80564..0c520b33c 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -1,20 +1,18 @@ // -*- mode: cpp; mode: fold -*- // Description /*{{{*/ -// $Id: http.cc,v 1.32 1999/05/26 04:08:39 jgg Exp $ +// $Id: http.cc,v 1.50 2001/03/06 07:15:29 jgg Exp $ /* ###################################################################### HTTP Aquire Method - This is the HTTP aquire method for APT. It uses HTTP/1.1 and many of the fancy options there-in, such as - pipelining, range, if-range and so on. It accepts on the command line - a list of url destination pairs and writes to stdout the status of the - operation as defined in the APT method spec. - - It is based on a doubly buffered select loop. All the requests are + pipelining, range, if-range and so on. + + It is based on a doubly buffered select loop. A groupe of requests are fed into a single output buffer that is constantly fed out the socket. This provides ideal pipelining as in many cases all of the requests will fit into a single packet. The input socket is buffered - the same way and fed into the fd for the file. + the same way and fed into the fd for the file (may be a pipe in future). This double buffering provides fairly substantial transfer rates, compared to wget the http method is about 4% faster. Most importantly, @@ -30,7 +28,7 @@ #include #include #include -#include +#include #include #include @@ -41,11 +39,9 @@ #include // Internet stuff -/*#include -#include -#include -#include */ +#include +#include "connect.h" #include "rfc2553emu.h" #include "http.h" @@ -56,11 +52,12 @@ int HttpMethod::FailFd = -1; time_t HttpMethod::FailTime = 0; unsigned long PipelineDepth = 10; unsigned long TimeOut = 120; +bool Debug = false; // CircleBuf::CircleBuf - Circular input buffer /*{{{*/ // --------------------------------------------------------------------- /* */ -CircleBuf::CircleBuf(unsigned long Size) : Size(Size), MD5(0) +CircleBuf::CircleBuf(unsigned long Size) : Size(Size), Hash(0) { Buf = new unsigned char[Size]; Reset(); @@ -76,10 +73,10 @@ void CircleBuf::Reset() StrPos = 0; MaxGet = (unsigned int)-1; OutQueue = string(); - if (MD5 != 0) + if (Hash != 0) { - delete MD5; - MD5 = new MD5Summation; + delete Hash; + Hash = new Hashes; } }; /*}}}*/ @@ -185,8 +182,8 @@ bool CircleBuf::Write(int Fd) return false; } - if (MD5 != 0) - MD5->Add(Buf + (OutP%Size),Res); + if (Hash != 0) + Hash->Add(Buf + (OutP%Size),Res); OutP += Res; } @@ -259,9 +256,6 @@ ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner), // ServerState::Open - Open a connection to the server /*{{{*/ // --------------------------------------------------------------------- /* This opens a connection to the server. */ -string LastHost; -int LastPort = 0; -struct addrinfo *LastHostAddr = 0; bool ServerState::Open() { // Use the already open connection if possible. @@ -271,7 +265,8 @@ bool ServerState::Open() Close(); In.Reset(); Out.Reset(); - + Persistent = true; + // Determine the proxy setting if (getenv("http_proxy") == 0) { @@ -290,10 +285,17 @@ bool ServerState::Open() else Proxy = getenv("http_proxy"); + // Parse no_proxy, a , separated list of domains + if (getenv("no_proxy") != 0) + { + if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true) + Proxy = ""; + } + // Determine what host and port to use based on the proxy settings int Port = 0; string Host; - if (Proxy.empty() == true) + if (Proxy.empty() == true || Proxy.Host.empty() == true) { if (ServerName.Port != 0) Port = ServerName.Port; @@ -306,65 +308,9 @@ bool ServerState::Open() Host = Proxy.Host; } - /* We used a cached address record.. Yes this is against the spec but - the way we have setup our rotating dns suggests that this is more - sensible */ - if (LastHost != Host || LastPort != Port) - { - Owner->Status("Connecting to %s",Host.c_str()); - - // Lookup the host - char S[30] = "http"; - if (Port != 0) - snprintf(S,sizeof(S),"%u",Port); - - // Free the old address structure - if (LastHostAddr != 0) - { - freeaddrinfo(LastHostAddr); - LastHostAddr = 0; - } - - // We only understand SOCK_STREAM sockets. - struct addrinfo Hints; - memset(&Hints,0,sizeof(Hints)); - Hints.ai_socktype = SOCK_STREAM; - - // Resolve both the host and service simultaneously - if (getaddrinfo(Host.c_str(),S,&Hints,&LastHostAddr) != 0 || - LastHostAddr == 0) - return _error->Error("Could not resolve '%s'",Host.c_str()); - - LastHost = Host; - LastPort = Port; - } - - // Get the printable IP address - char Name[NI_MAXHOST]; - Name[0] = 0; - getnameinfo(LastHostAddr->ai_addr,LastHostAddr->ai_addrlen, - Name,sizeof(Name),0,0,NI_NUMERICHOST); - Owner->Status("Connecting to %s (%s)",Host.c_str(),Name); - - // Get a socket - if ((ServerFd = socket(LastHostAddr->ai_family,LastHostAddr->ai_socktype, - LastHostAddr->ai_protocol)) < 0) - return _error->Errno("socket","Could not create a socket"); - SetNonBlock(ServerFd,true); - if (connect(ServerFd,LastHostAddr->ai_addr,LastHostAddr->ai_addrlen) < 0 && - errno != EINPROGRESS) - return _error->Errno("connect","Connect initiate the connection"); - - /* This implements a timeout for connect by opening the connection - nonblocking */ - if (WaitFd(ServerFd,true,TimeOut) == false) - return _error->Error("Could not connect, connection timed out"); - unsigned int Err; - unsigned int Len = sizeof(Err); - if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0) - return _error->Errno("getsockopt","Failed"); - if (Err != 0) - return _error->Error("Could not connect."); + // Connect to the remote server + if (Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner) == false) + return false; return true; } @@ -403,6 +349,9 @@ int ServerState::RunHeaders() string Data; if (In.WriteTillEl(Data) == false) continue; + + if (Debug == true) + clog << Data; for (string::const_iterator I = Data.begin(); I < Data.end(); I++) { @@ -412,10 +361,19 @@ int ServerState::RunHeaders() return 2; I = J; } + + // 100 Continue is a Nop... + if (Result == 100) + continue; + + // Tidy up the connection persistance state. + if (Encoding == Closes && HaveContent == true) + Persistent = false; + return 0; } while (Owner->Go(false,this) == true); - + return 1; } /*}}}*/ @@ -557,7 +515,19 @@ bool ServerState::HeaderLine(string Line) if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2) return _error->Error("The http server sent an invalid reply header"); } - + + /* Check the HTTP response header to get the default persistance + state. */ + if (Major < 1) + Persistent = false; + else + { + if (Major == 1 && Minor <= 0) + Persistent = false; + else + Persistent = true; + } + return true; } @@ -597,11 +567,19 @@ bool ServerState::HeaderLine(string Line) { HaveContent = true; if (stringcasecmp(Val,"chunked") == 0) - Encoding = Chunked; - + Encoding = Chunked; return true; } + if (stringcasecmp(Tag,"Connection:") == 0) + { + if (stringcasecmp(Val,"close") == 0) + Persistent = false; + if (stringcasecmp(Val,"keep-alive") == 0) + Persistent = true; + return true; + } + if (stringcasecmp(Tag,"Last-Modified:") == 0) { if (StrToTime(Val,Date) == false) @@ -671,7 +649,7 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) { // In this case we send an if-range query with a range header - sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf.st_size - 1, + sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",(long)SBuf.st_size - 1, TimeRFC1123(SBuf.st_mtime).c_str()); Req += Buf; } @@ -688,8 +666,14 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) Req += string("Proxy-Authorization: Basic ") + Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n"; - Req += "User-Agent: Debian APT-HTTP/1.2\r\n\r\n"; -// cerr << Req << endl; + if (Uri.User.empty() == false || Uri.Password.empty() == false) + Req += string("Authorization: Basic ") + + Base64Encode(Uri.User + ":" + Uri.Password) + "\r\n"; + + Req += "User-Agent: Debian APT-HTTP/1.3\r\n\r\n"; + + if (Debug == true) + cerr << Req << endl; Out.Read(Req); } @@ -701,18 +685,20 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) bool HttpMethod::Go(bool ToFile,ServerState *Srv) { // Server has closed the connection - if (Srv->ServerFd == -1 && Srv->In.WriteSpace() == false) + if (Srv->ServerFd == -1 && (Srv->In.WriteSpace() == false || + ToFile == false)) return false; - fd_set rfds,wfds,efds; + fd_set rfds,wfds; FD_ZERO(&rfds); FD_ZERO(&wfds); - FD_ZERO(&efds); - // Add the server - if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1) + /* Add the server. We only send more requests if the connection will + be persisting */ + if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1 + && Srv->Persistent == true) FD_SET(Srv->ServerFd,&wfds); - if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1) + if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1) FD_SET(Srv->ServerFd,&rfds); // Add the file @@ -726,23 +712,17 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) // Add stdin FD_SET(STDIN_FILENO,&rfds); - // Error Set - if (FileFD != -1) - FD_SET(FileFD,&efds); - if (Srv->ServerFd != -1) - FD_SET(Srv->ServerFd,&efds); - // Figure out the max fd int MaxFd = FileFD; if (MaxFd < Srv->ServerFd) MaxFd = Srv->ServerFd; - + // Select struct timeval tv; tv.tv_sec = TimeOut; tv.tv_usec = 0; int Res = 0; - if ((Res = select(MaxFd+1,&rfds,&wfds,&efds,&tv)) < 0) + if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0) return _error->Errno("select","Select failed"); if (Res == 0) @@ -751,11 +731,6 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) return ServerDie(Srv); } - // Some kind of exception (error) on the sockets, die - if ((FileFD != -1 && FD_ISSET(FileFD,&efds)) || - (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&efds))) - return _error->Error("Socket Exception"); - // Handle server IO if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&rfds)) { @@ -781,7 +756,7 @@ bool HttpMethod::Go(bool ToFile,ServerState *Srv) // Handle commands from APT if (FD_ISSET(STDIN_FILENO,&rfds)) { - if (Run(true) != 0) + if (Run(true) != -1) exit(100); } @@ -917,14 +892,14 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) // Set the start point lseek(File->Fd(),0,SEEK_END); - delete Srv->In.MD5; - Srv->In.MD5 = new MD5Summation; + delete Srv->In.Hash; + Srv->In.Hash = new Hashes; - // Fill the MD5 Hash if the file is non-empty (resume) + // Fill the Hash if the file is non-empty (resume) if (Srv->StartPos > 0) { lseek(File->Fd(),0,SEEK_SET); - if (Srv->In.MD5->AddFD(File->Fd(),Srv->StartPos) == false) + if (Srv->In.Hash->AddFD(File->Fd(),Srv->StartPos) == false) { _error->Errno("read","Problem hashing file"); return 5; @@ -943,7 +918,7 @@ int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) void HttpMethod::SigTerm(int) { if (FailFd == -1) - exit(100); + _exit(100); close(FailFd); // Timestamp @@ -952,7 +927,7 @@ void HttpMethod::SigTerm(int) UBuf.modtime = FailTime; utime(FailFile.c_str(),&UBuf); - exit(100); + _exit(100); } /*}}}*/ // HttpMethod::Fetch - Fetch an item /*{{{*/ @@ -967,8 +942,13 @@ bool HttpMethod::Fetch(FetchItem *) // Queue the requests int Depth = -1; bool Tail = false; - for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; I = I->Next, Depth++) + for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; + I = I->Next, Depth++) { + // If pipelining is disabled, we only queue 1 request + if (Server->Pipeline == false && Depth >= 0) + break; + // Make sure we stick with the same server if (Server->Comp(I->Uri) == false) break; @@ -979,7 +959,7 @@ bool HttpMethod::Fetch(FetchItem *) QueueBack = I->Next; SendReq(I,Server->Out); continue; - } + } } return true; @@ -996,6 +976,7 @@ bool HttpMethod::Configuration(string Message) TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut); PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth", PipelineDepth); + Debug = _config->FindB("Debug::Acquire::http",false); return true; } @@ -1020,8 +1001,10 @@ int HttpMethod::Loop() return 0; } - // Run messages - if (Run(true) != 0) + /* Run messages, we can accept 0 (no message) if we didn't + do a WaitFd above.. Otherwise the FD is closed. */ + int Result = Run(true); + if (Result != -1 && (Result != 0 || Queue == 0)) return 100; if (Queue == 0) @@ -1033,7 +1016,15 @@ int HttpMethod::Loop() delete Server; Server = new ServerState(Queue->Uri,this); } - + + /* If the server has explicitly said this is the last connection + then we pre-emptively shut down the pipeline and tear down + the connection. This will speed up HTTP/1.0 servers a tad + since we don't have to wait for the close sequence to + complete */ + if (Server->Persistent == false) + Server->Close(); + // Reset the pipeline if (Server->ServerFd == -1) QueueBack = Queue; @@ -1061,6 +1052,7 @@ int HttpMethod::Loop() { _error->Error("Bad header Data"); Fail(true); + RotateDNS(); continue; } @@ -1071,13 +1063,15 @@ int HttpMethod::Loop() FailCounter++; _error->Discard(); Server->Close(); - + Server->Pipeline = false; + if (FailCounter >= 2) { - Fail("Connection timed out",true); + Fail("Connection failed",true); FailCounter = 0; } + RotateDNS(); continue; } }; @@ -1095,6 +1089,11 @@ int HttpMethod::Loop() // Run the data bool Result = Server->RunData(); + /* If the server is sending back sizeless responses then fill in + the size now */ + if (Res.Size == 0) + Res.Size = File->Size(); + // Close the file, destroy the FD object and timestamp it FailFd = -1; delete File; @@ -1110,12 +1109,12 @@ int HttpMethod::Loop() // Send status to APT if (Result == true) { - Res.MD5Sum = Server->In.MD5->Result(); + Res.MD5Sum = Server->In.Hash->MD5.Result(); URIDone(Res); } else Fail(true); - + break; } @@ -1137,6 +1136,7 @@ int HttpMethod::Loop() case 5: { Fail(); + RotateDNS(); Server->Close(); break; }