/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ #include "squid.h" #include "anyp/PortCfg.h" #include "client_side.h" #include "comm.h" #include "comm/Read.h" #include "Debug.h" #include "error/SysErrorDetail.h" #include "fd.h" #include "fde.h" #include "http/Stream.h" #include "LogTags.h" #include "MasterXaction.h" #include "servers/Server.h" #include "SquidConfig.h" #include "StatCounters.h" #include "tools.h" Server::Server(const MasterXaction::Pointer &xact) : AsyncJob("::Server"), // kids overwrite clientConnection(xact->tcpClient), transferProtocol(xact->squidPort->transport), port(xact->squidPort), receivedFirstByte_(false) {} bool Server::doneAll() const { // servers are not done while the connection is open return !Comm::IsConnOpen(clientConnection) && BodyProducer::doneAll(); } void Server::start() { // TODO: shuffle activity from ConnStateData } void Server::swanSong() { if (Comm::IsConnOpen(clientConnection)) clientConnection->close(); BodyProducer::swanSong(); } void Server::stopReading() { if (reading()) { Comm::ReadCancel(clientConnection->fd, reader); reader = NULL; } } /// Prepare inBuf for I/O. This method balances several conflicting desires: /// 1. Do not read too few bytes at a time. /// 2. Do not waste too much buffer space. /// 3. Do not [re]allocate or memmove the buffer too much. /// 4. Obey Config.maxRequestBufferSize limit. void Server::maybeMakeSpaceAvailable() { // The hard-coded parameters are arbitrary but seem reasonable. // A careful study of Squid I/O and parsing patterns is needed to tune them. SBufReservationRequirements requirements; requirements.minSpace = 1024; // smaller I/Os are not worth their overhead requirements.idealSpace = CLIENT_REQ_BUF_SZ; // we expect few larger I/Os requirements.maxCapacity = Config.maxRequestBufferSize; requirements.allowShared = true; // allow because inBuf is used immediately inBuf.reserve(requirements); if (!inBuf.spaceSize()) debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize); } void Server::readSomeData() { if (reading()) return; debugs(33, 4, clientConnection << ": reading request..."); // we can only read if there is more than 1 byte of space free if (Config.maxRequestBufferSize - inBuf.length() < 2) return; typedef CommCbMemFunT Dialer; reader = JobCallback(33, 5, Dialer, this, Server::doClientRead); Comm::Read(clientConnection, reader); } void Server::doClientRead(const CommIoCbParams &io) { debugs(33,5, io.conn); Must(reading()); reader = NULL; /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */ if (io.flag == Comm::ERR_CLOSING) { debugs(33,5, io.conn << " closing Bailout."); return; } assert(Comm::IsConnOpen(clientConnection)); assert(io.conn->fd == clientConnection->fd); /* * Don't reset the timeout value here. The value should be * counting Config.Timeout.request and applies to the request * as a whole, not individual read() calls. * Plus, it breaks our lame *HalfClosed() detection */ maybeMakeSpaceAvailable(); CommIoCbParams rd(this); // will be expanded with ReadNow results rd.conn = io.conn; switch (Comm::ReadNow(rd, inBuf)) { case Comm::INPROGRESS: if (inBuf.isEmpty()) debugs(33, 2, io.conn << ": no data to process, " << xstrerr(rd.xerrno)); readSomeData(); return; case Comm::OK: statCounter.client_http.kbytes_in += rd.size; if (!receivedFirstByte_) receivedFirstByte(); // may comm_close or setReplyToError if (!handleReadData()) return; /* Continue to process previously read data */ break; case Comm::ENDFILE: // close detected by 0-byte read debugs(33, 5, io.conn << " closed?"); if (shouldCloseOnEof()) { LogTagsErrors lte; lte.aborted = true; terminateAll(ERR_CLIENT_GONE, lte); return; } /* It might be half-closed, we can't tell */ fd_table[io.conn->fd].flags.socket_eof = true; commMarkHalfClosed(io.conn->fd); fd_note(io.conn->fd, "half-closed"); /* There is one more close check at the end, to detect aborted * (partial) requests. At this point we can't tell if the request * is partial. */ /* Continue to process previously read data */ break; // case Comm::COMM_ERROR: default: // no other flags should ever occur debugs(33, 2, io.conn << ": got flag " << rd.flag << "; " << xstrerr(rd.xerrno)); LogTagsErrors lte; lte.timedout = rd.xerrno == ETIMEDOUT; lte.aborted = !lte.timedout; // intentionally true for zero rd.xerrno terminateAll(Error(ERR_CLIENT_GONE, SysErrorDetail::NewIfAny(rd.xerrno)), lte); return; } afterClientRead(); } /** callback handling the Comm::Write completion * * Will call afterClientWrite(size_t) to sync the I/O state. * Then writeSomeData() to initiate any followup writes that * could be immediately done. */ void Server::clientWriteDone(const CommIoCbParams &io) { debugs(33,5, io.conn); Must(writer != nullptr); writer = nullptr; /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */ if (io.flag == Comm::ERR_CLOSING || !Comm::IsConnOpen(clientConnection)) { debugs(33,5, io.conn << " closing Bailout."); return; } Must(io.conn->fd == clientConnection->fd); if (io.flag && pipeline.front()) pipeline.front()->initiateClose("write failure"); afterClientWrite(io.size); // update state writeSomeData(); // maybe schedules another write }