XRootD
XrdXrootdRedirHelper Class Reference

#include <XrdXrootdRedirHelper.hh>

+ Collaboration diagram for XrdXrootdRedirHelper:

Public Types

enum class  Outcome {
  Unchanged ,
  Replaced ,
  Error
}
 

Static Public Member Functions

static void Init (XrdXrootdRedirPI *pi, XrdSysError *eDest, int ipHold)
 
static bool IsActive ()
 
static bool ParseURL (const char *url, std::string &urlHead, std::string &host, std::string &port, std::string &urlTail)
 
static Outcome Redirect (const char *trg, int &port, XrdNetAddrInfo &clientAddr, std::string &outTarget, std::string &errMsg)
 
static void SetClockForTesting (std::function< time_t()> nowFn)
 

Detailed Description

Process-wide adapter around the redirect plugin (XrdXrootdRedirPI) so the plugin can be invoked uniformly from the XRootD protocol and from any other subsystem that produces redirect targets (e.g. the HTTP TPC handler addressed by issue #2767).

Responsibilities centralised here so they do not need to be reproduced at every call site:

  • the static plugin pointer plus the logger used for diagnostics;
  • a cache of XrdNetAddrInfo objects for previously-resolved redirect targets (each plugin call needs the target's resolved netaddr, which is comparatively expensive to obtain);
  • the empty / "<new target>" / "!<error>" return-string contract of the plugin, normalised into a tri-state Outcome.

The class is non-instantiable; it is initialised once by Init() (called from XrdXrootdConfig.cc after the redirect plugin has been loaded) and its Redirect() static method is then invoked concurrently from any number of worker threads. When no plugin is configured IsActive() returns false and Redirect() is an inexpensive no-op.

Definition at line 59 of file XrdXrootdRedirHelper.hh.

Member Enumeration Documentation

◆ Outcome

Tri-state outcome returned by Redirect().

Mirrors the three cases of the plugin's return-string contract:

  • empty string -> Unchanged: caller keeps the original target
  • "<new target>" -> Replaced: caller emits the new target
  • "!<message>" -> Error: caller fails the request, surfaces the message
Enumerator
Unchanged 
Replaced 
Error 

Definition at line 73 of file XrdXrootdRedirHelper.hh.

73 { Unchanged, Replaced, Error };

Member Function Documentation

◆ Init()

void XrdXrootdRedirHelper::Init ( XrdXrootdRedirPI pi,
XrdSysError eDest,
int  ipHold 
)
static

Initialise the helper. Must be called exactly once, after the redirect plugin has been loaded and before any worker thread invokes Redirect(). Calling Init(nullptr, ...) is a valid no-op that simply disarms the helper.

Parameters
pipointer to the loaded redirect plugin, or nullptr if no plugin was loaded. When nullptr, IsActive() returns false and Redirect() always returns Unchanged.
eDestlogger used to report cache-population failures. Must not be nullptr when pi is non-null.
ipHoldnumber of seconds for which a successfully-resolved target XrdNetAddrInfo is reused before being refreshed. Mirrors XrdXrootdProtocol::redirIPHold.

Definition at line 164 of file XrdXrootdRedirHelper.cc.

166 {
167  gPlugin = pi;
168  gLog = eDest;
169  gIPHold = ipHold;
170 }
XrdSysError eDest(0, "HttpMon")

References eDest.

Referenced by XrdXrootdProtocol::Configure().

+ Here is the caller graph for this function:

◆ IsActive()

bool XrdXrootdRedirHelper::IsActive ( )
static
Returns
true iff a redirect plugin has been registered via Init().

Definition at line 176 of file XrdXrootdRedirHelper.cc.

176 { return gPlugin != nullptr; }

◆ ParseURL()

bool XrdXrootdRedirHelper::ParseURL ( const char *  url,
std::string &  urlHead,
std::string &  host,
std::string &  port,
std::string &  urlTail 
)
static

Parse a scheme://host[:port][/tail] redirect URL into its components.

Exposed as a static method, rather than kept private to the .cc, so the URL grammar Redirect() relies on for URL-form targets can be unit-tested directly without a plugin, the netaddr cache or DNS. Redirect() is its only production caller.

Parameters
urlURL to parse. Must not be nullptr.
urlHeadoutput: scheme prefix up to and including "://".
hostoutput: host name, with any ":port" suffix removed.
portoutput: port string, or "" when no port is present.
urlTailoutput: path/query tail including the leading '/', or "" when the URL has no path.
Returns
true on success. false when url is not a parseable scheme://host[:port][/tail] URL; the output parameters are then left in an unspecified state.

Definition at line 275 of file XrdXrootdRedirHelper.cc.

278 {
279  const char *hBeg = strstr(url, "://");
280  if (!hBeg) return false;
281  hBeg += 3;
282  urlHead.assign(url, hBeg - url);
283 
284  // Split off the path/query tail; require the host[:port] authority that
285  // precedes it to be at least two characters long.
286  if (const char *tail = strstr(hBeg, "/"); !tail)
287  {urlTail.clear(); host = hBeg;}
288  else {if (tail - hBeg < 3) return false;
289  host.assign(hBeg, tail - hBeg);
290  urlTail = tail;
291  }
292 
293  // Separate an optional ":port" suffix from the host.
294  port.clear();
295  if (size_t colon = host.find(':'); colon != std::string::npos)
296  {port.assign(host, colon + 1, std::string::npos);
297  host.erase(colon);
298  }
299  return true;
300 }

Referenced by Redirect().

+ Here is the caller graph for this function:

◆ Redirect()

XrdXrootdRedirHelper::Outcome XrdXrootdRedirHelper::Redirect ( const char *  trg,
int &  port,
XrdNetAddrInfo clientAddr,
std::string &  outTarget,
std::string &  errMsg 
)
static

Run a redirect target through the redirect plugin.

Single entry point for both redirect forms; port selects which one, mirroring the calling convention of XrdXrootdProtocol::fsRedirPI():

  • port >= 0 : trg is a host[?cgi] target and port is its numeric port. The target is split internally (via splitHostCgi) and the plugin's Redirect() entry point is invoked; port is rewritten in place when the plugin asks for a different port.
  • port < 0 : trg is a full scheme://host[:port][/tail] URL. It is parsed internally (via ParseURL) and the plugin's RedirectURL() entry point is invoked. Here port doubles as the plugin's rdrOpts argument; because it is also the protocol's URL marker it is passed to the plugin but left unmodified on return. A URL that cannot be parsed is logged and yields Outcome::Unchanged without invoking the plugin.

Used by the XRootD protocol (fsRedirPI) for both forms and by the HTTP TPC handler (issue #2767), which only ever passes the host[?cgi] form.

Parameters
trgredirect target: host[?cgi] when port >= 0, or a scheme://host[:port][/tail] URL when port < 0. A nullptr is tolerated defensively and yields Outcome::Unchanged without invoking the plugin.
portin/out, doubling as the host-vs-URL selector; see the per-form description above.
clientAddrnetwork info of the client being redirected.
outTargetoutput: when Replaced, holds the new redirect target. Untouched otherwise.
errMsgoutput: when Error, holds the plugin's error message with the contract's leading '!' already stripped. Untouched otherwise.
Returns
Unchanged if no plugin is configured, the target's network address could not be resolved, a URL target could not be parsed, or the plugin returned an empty string. Replaced if the plugin returned a new target. Error if the plugin returned a fatal error.
Note
Thread-safe. Internal state is protected by a mutex; the caller may invoke this from any thread once Init() has returned.

Definition at line 199 of file XrdXrootdRedirHelper.cc.

202 {
203  if (!gPlugin || !trg) return Outcome::Unchanged;
204 
205  std::string pluginReply; // raw plugin reply, filled by the branch below
206  uint16_t hostPort = 0; // host form: the plugin's port (in/out)
207 
208  if (port >= 0)
209  {// Host[?cgi] form: split the target and invoke the plugin's host+port
210  // Redirect() entry point, which may rewrite the port in place.
211  if (port > UINT16_MAX)
212  {if (gLog) gLog->Emsg("RedirPI", "Redirect port out of range -",
213  std::to_string(port).c_str());
214  return Outcome::Unchanged;
215  }
216  std::string host;
217  std::string cgi;
218  splitHostCgi(trg, host, cgi);
219 
220  // The plugin call needs the target's resolved netaddr. If we cannot
221  // produce one the safest fallback is to leave the redirect alone
222  // (Unchanged) so the caller emits the original target unmodified.
223  THandle T;
224  T.Info = LookupTarget(host.c_str(), port);
225  if (!T.Info) return Outcome::Unchanged;
226 
227  hostPort = static_cast<uint16_t>(port);
228  pluginReply = gPlugin->Redirect(host.c_str(), hostPort, cgi.c_str(),
229  T.Info->netAddr, clientAddr);
230  } else {
231  // URL form: parse scheme://host[:port][/tail] and invoke the plugin's
232  // RedirectURL() entry point. port doubles as the rdrOpts argument and
233  // is the protocol's URL marker, so it is passed in but left unmodified.
234  // A URL we cannot parse is not salvageable: skip the plugin and report
235  // Unchanged so the caller emits the original target unmodified.
236  std::string urlHead;
237  std::string host;
238  std::string urlPort;
239  std::string urlTail;
240  if (!ParseURL(trg, urlHead, host, urlPort, urlTail))
241  {if (gLog) gLog->Emsg("RedirPI", "Invalid redirect URL -", trg);
242  return Outcome::Unchanged;
243  }
244 
245  // The cache is keyed by host alone (no scheme, no port suffix); pass
246  // -1 so the netaddr resolution does not bind to any specific port. A
247  // resolution failure falls back to Unchanged, as in the host form.
248  THandle T;
249  T.Info = LookupTarget(host.c_str(), -1);
250  if (!T.Info) return Outcome::Unchanged;
251 
252  int rdrOpts = port;
253  pluginReply = gPlugin->RedirectURL(urlHead.c_str(), host.c_str(),
254  urlPort.c_str(), urlTail.c_str(),
255  rdrOpts, T.Info->netAddr,
256  clientAddr);
257  }
258 
259  // Translate the plugin's "" / "<target>" / "!<msg>" return-string contract.
260  if (pluginReply.empty()) return Outcome::Unchanged;
261  if (pluginReply.front() == '!') { errMsg.assign(pluginReply, 1);
262  return Outcome::Error; }
263 
264  // Replaced: commit the plugin's (possibly rewritten) port. Host form only;
265  // the URL form leaves port untouched as the protocol's URL marker.
266  if (port >= 0) port = hostPort;
267  outTarget = std::move(pluginReply);
268  return Outcome::Replaced;
269 }
void splitHostCgi(std::string_view target, std::string &host, std::string &cgi)
static bool ParseURL(const char *url, std::string &urlHead, std::string &host, std::string &port, std::string &urlTail)

References XrdSysError::Emsg(), Error, ParseURL(), XrdXrootdRedirPI::Redirect(), XrdXrootdRedirPI::RedirectURL(), Replaced, splitHostCgi(), and Unchanged.

+ Here is the call graph for this function:

◆ SetClockForTesting()

void XrdXrootdRedirHelper::SetClockForTesting ( std::function< time_t()>  nowFn)
static

====================== ONLY FOR TESTING, DO NOT USE ======================

Override the clock function used by the internal target-netaddr cache so unit tests can fast-forward past the ipHold expiry and exercise the refresh path of LookupTarget. Pass nullptr to restore the default clock (time(nullptr)).

Warning
This function exists solely to make XrdXrootdRedirHelper unit- testable. It must never be called from production code. Calling it from anywhere other than tests would replace the wall-clock with a fake clock for every redirect resolution in the whole process and silently break the cache TTL semantics.
Parameters
nowFnfunction returning a time_t to be used in place of time(nullptr). Pass an empty function to restore the default.

Definition at line 189 of file XrdXrootdRedirHelper.cc.

190 {
191  gNow = std::move(nowFn);
192 }

The documentation for this class was generated from the following files: