2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 93 eCAP Interface */
12 #include "adaptation/ecap/Config.h"
13 #include "adaptation/ecap/Host.h"
14 #include "adaptation/ecap/ServiceRep.h"
15 #include "adaptation/ecap/XactionRep.h"
16 #include "AsyncEngine.h"
17 #include "base/TextException.h"
18 #include "debug/Stream.h"
19 #include "EventLoop.h"
21 #if HAVE_LIBECAP_ADAPTER_SERVICE_H
22 #include <libecap/adapter/service.h>
24 #if HAVE_LIBECAP_COMMON_OPTIONS_H
25 #include <libecap/common/options.h>
27 #if HAVE_LIBECAP_COMMON_NAME_H
28 #include <libecap/common/name.h>
30 #if HAVE_LIBECAP_COMMON_NAMED_VALUES_H
31 #include <libecap/common/named_values.h>
37 /// libecap::adapter::services indexed by their URI
38 typedef std::map
<std::string
, Adaptation::Ecap::ServiceRep::AdapterService
> AdapterServices
;
39 /// all loaded services
40 static AdapterServices TheServices
;
41 /// configured services producing async transactions
42 static AdapterServices AsyncServices
;
49 /// wraps Adaptation::Ecap::ServiceConfig to allow eCAP visitors
50 class ConfigRep
: public libecap::Options
53 typedef Adaptation::Ecap::ServiceConfig Master
;
54 typedef libecap::Name Name
;
55 typedef libecap::Area Area
;
57 ConfigRep(const Master
&aMaster
);
59 // libecap::Options API
60 const libecap::Area
option(const libecap::Name
&name
) const override
;
61 void visitEachOption(libecap::NamedValueVisitor
&visitor
) const override
;
63 const Master
&master
; ///< the configuration being wrapped
66 /// manages async eCAP transactions
67 class Engine
: public AsyncEngine
71 int checkEvents(int timeout
) override
;
74 void kickAsyncServices(timeval
&timeout
);
78 } // namespace Adaptation
80 Adaptation::Ecap::ConfigRep::ConfigRep(const Master
&aMaster
): master(aMaster
)
85 Adaptation::Ecap::ConfigRep::option(const libecap::Name
&name
) const
87 // we may supply the params we know about, but only when names have host ID
88 if (name
== metaBypassable
)
89 return Area(master
.bypass
? "1" : "0", 1);
91 // TODO: We could build a by-name index, but is it worth it? Good adapters
92 // should use visitEachOption() instead, to check for name typos/errors.
93 typedef Master::Extensions::const_iterator MECI
;
94 for (MECI i
= master
.extensions
.begin(); i
!= master
.extensions
.end(); ++i
) {
96 return Area(i
->second
.data(), i
->second
.size());
103 Adaptation::Ecap::ConfigRep::visitEachOption(libecap::NamedValueVisitor
&visitor
) const
105 // we may supply the params we know about too, but only if we set host ID
106 visitor
.visit(metaBypassable
, Area(master
.bypass
? "1" : "0", 1));
108 // visit adapter-specific options (i.e., those not recognized by Squid)
109 typedef Master::Extensions::const_iterator MECI
;
110 for (MECI i
= master
.extensions
.begin(); i
!= master
.extensions
.end(); ++i
)
111 visitor
.visit(Name(i
->first
), Area::FromTempString(i
->second
));
114 /* Adaptation::Ecap::Engine */
117 Adaptation::Ecap::Engine::checkEvents(int)
119 // Start with the default I/O loop timeout, convert from milliseconds.
120 static const struct timeval maxTimeout
= {
121 EVENT_LOOP_TIMEOUT
/1000, // seconds
122 (EVENT_LOOP_TIMEOUT
% 1000)*1000
124 struct timeval timeout
= maxTimeout
;
126 kickAsyncServices(timeout
);
127 if (timeout
.tv_sec
== maxTimeout
.tv_sec
&& timeout
.tv_usec
== maxTimeout
.tv_usec
)
130 debugs(93, 7, "timeout: " << timeout
.tv_sec
<< "s+" << timeout
.tv_usec
<< "us");
132 // convert back to milliseconds, avoiding int overflows
133 if (timeout
.tv_sec
>= std::numeric_limits
<int>::max()/1000 - 1000)
134 return std::numeric_limits
<int>::max();
136 return timeout
.tv_sec
*1000 + timeout
.tv_usec
/1000;
139 /// resumes async transactions (if any) and returns true if they set a timeout
141 Adaptation::Ecap::Engine::kickAsyncServices(timeval
&timeout
)
143 if (AsyncServices
.empty())
146 debugs(93, 3, "async services: " << AsyncServices
.size());
148 // Activate waiting async transactions, if any.
149 typedef AdapterServices::iterator ASI
;
150 for (ASI s
= AsyncServices
.begin(); s
!= AsyncServices
.end(); ++s
) {
152 s
->second
->resume(); // may call Ecap::Xaction::resume()
155 // Give services a chance to decrease the default timeout.
156 for (ASI s
= AsyncServices
.begin(); s
!= AsyncServices
.end(); ++s
) {
157 s
->second
->suspend(timeout
);
161 /* Adaptation::Ecap::ServiceRep */
163 Adaptation::Ecap::ServiceRep::ServiceRep(const ServiceConfigPointer
&cfg
):
164 /*AsyncJob("Adaptation::Ecap::ServiceRep"),*/ Adaptation::Service(cfg
),
169 Adaptation::Ecap::ServiceRep::~ServiceRep()
173 void Adaptation::Ecap::ServiceRep::noteFailure()
175 assert(false); // XXX: should this be ICAP-specific?
179 Adaptation::Ecap::ServiceRep::finalize()
181 Adaptation::Service::finalize();
182 if (!cfg().connectionEncryption
.configured())
183 writeableCfg().connectionEncryption
.defaultTo(true);
184 theService
= FindAdapterService(cfg().uri
);
187 tryConfigureAndStart();
189 } catch (const std::exception
&e
) { // standardized exceptions
190 if (!handleFinalizeFailure(e
.what()))
191 throw; // rethrow for upper layers to handle
192 } catch (...) { // all other exceptions
193 if (!handleFinalizeFailure("unrecognized exception"))
194 throw; // rethrow for upper layers to handle
196 return; // success or handled exception
198 debugs(93,DBG_IMPORTANT
, "WARNING: configured ecap_service was not loaded: " << cfg().uri
);
202 /// attempts to configure and start eCAP service; the caller handles exceptions
204 Adaptation::Ecap::ServiceRep::tryConfigureAndStart()
206 debugs(93,2, "configuring eCAP service: " << theService
->uri());
207 const ConfigRep
cfgRep(dynamic_cast<const ServiceConfig
&>(cfg()));
208 theService
->configure(cfgRep
);
210 debugs(93,DBG_IMPORTANT
, "Starting eCAP service: " << theService
->uri());
213 if (theService
->makesAsyncXactions()) {
214 AsyncServices
[theService
->uri()] = theService
;
215 debugs(93, 5, "asyncs: " << AsyncServices
.size());
219 /// handles failures while configuring or starting an eCAP service;
220 /// returns false if the error must be propagated to higher levels
222 Adaptation::Ecap::ServiceRep::handleFinalizeFailure(const char *error
)
224 const bool salvage
= cfg().bypass
;
225 const int level
= salvage
? DBG_IMPORTANT
:DBG_CRITICAL
;
226 const char *kind
= salvage
? "optional" : "essential";
227 debugs(93, level
, "ERROR: failed to start " << kind
<< " eCAP service: " <<
228 cfg().uri
<< ":\n" << error
);
231 return false; // we cannot handle the problem; the caller may escalate
233 // make up() false, preventing new adaptation requests and enabling bypass
235 debugs(93, level
, "WARNING: " << kind
<< " eCAP service is " <<
236 "down after initialization failure: " << cfg().uri
);
238 return true; // tell the caller to ignore the problem because we handled it
241 bool Adaptation::Ecap::ServiceRep::probed() const
243 return true; // we "probe" the adapter in finalize().
246 bool Adaptation::Ecap::ServiceRep::up() const
248 return bool(theService
);
251 bool Adaptation::Ecap::ServiceRep::wantsUrl(const SBuf
&urlPath
) const
254 SBuf nonConstUrlPath
= urlPath
;
255 // c_str() reallocates and terminates for libecap API
256 return theService
->wantsUrl(nonConstUrlPath
.c_str());
259 Adaptation::Initiate
*
260 Adaptation::Ecap::ServiceRep::makeXactLauncher(Http::Message
*virgin
,
261 HttpRequest
*cause
, AccessLogEntry::Pointer
&alp
)
265 // register now because (a) we need EventLoop::Running and (b) we do not
266 // want to add more main loop overheads unless an async service is used.
267 static AsyncEngine
*TheEngine
= nullptr;
268 if (AsyncServices
.size() && !TheEngine
&& EventLoop::Running
) {
269 TheEngine
= new Engine
;
270 EventLoop::Running
->registerEngine(TheEngine
);
271 debugs(93, 3, "asyncs: " << AsyncServices
.size() << ' ' << TheEngine
);
274 XactionRep
*rep
= new XactionRep(virgin
, cause
, alp
, Pointer(this));
275 XactionRep::AdapterXaction
x(theService
->makeXaction(rep
));
280 // returns a temporary string depicting service status, for debugging
281 const char *Adaptation::Ecap::ServiceRep::status() const
283 // TODO: move generic stuff from eCAP and ICAP to Adaptation
292 buf
.append("down", 4);
295 buf
.append(",detached", 9);
300 return buf
.content();
303 void Adaptation::Ecap::ServiceRep::detach()
308 bool Adaptation::Ecap::ServiceRep::detached() const
313 Adaptation::Ecap::ServiceRep::AdapterService
314 Adaptation::Ecap::FindAdapterService(const String
& serviceUri
)
316 AdapterServices::const_iterator pos
= TheServices
.find(serviceUri
.termedBuf());
317 if (pos
!= TheServices
.end()) {
321 return ServiceRep::AdapterService();
325 Adaptation::Ecap::RegisterAdapterService(const Adaptation::Ecap::ServiceRep::AdapterService
& adapterService
)
327 TheServices
[adapterService
->uri()] = adapterService
; // may update old one
328 debugs(93, 3, "stored eCAP module service: " << adapterService
->uri());
329 // We do not update AsyncServices here in case they are not configured.
333 Adaptation::Ecap::UnregisterAdapterService(const String
& serviceUri
)
335 if (TheServices
.erase(serviceUri
.termedBuf())) {
336 debugs(93, 3, "unregistered eCAP module service: " << serviceUri
);
337 AsyncServices
.erase(serviceUri
.termedBuf()); // no-op for non-async
340 debugs(93, 3, "failed to unregister eCAP module service: " << serviceUri
);
344 Adaptation::Ecap::CheckUnusedAdapterServices(const Adaptation::Services
& cfgs
)
346 typedef AdapterServices::const_iterator ASCI
;
347 for (ASCI loaded
= TheServices
.begin(); loaded
!= TheServices
.end();
350 for (Services::const_iterator cfged
= cfgs
.begin();
351 cfged
!= cfgs
.end() && !found
; ++cfged
) {
352 found
= (*cfged
)->cfg().uri
== loaded
->second
->uri().c_str();
355 debugs(93, DBG_IMPORTANT
, "WARNING: loaded eCAP service has no matching " <<
356 "ecap_service config option: " << loaded
->second
->uri());