Wir sind Geonode und verkaufen Proxys. Daher handelt es sich hierbei um eine Anleitung zur Verwendung unserer Produkte mit einem bestimmten Client. Der Satz, den Sie unbedingt lesen sollten, auch wenn Sie den Rest überspringen: Überprüfen Sie nach der Konfiguration die Exit-Adresse, da eine Proxy-Einstellung, die nichts bewirkt, keinerlei Fehlermeldung ausgibt. SuperAgent leitet Ihre Anfrage gerne direkt weiter, gibt einen 200-Status zurück und liefert Ihnen keinerlei Hinweis darauf, dass der Proxy umgangen wurde. Der Abschnitt zur Überprüfung umfasst vier Zeilen und macht den Unterschied zwischen Wissen und Vermuten aus.
Beachte außerdem, dass SuperAgent auch in Browsern funktioniert, wo all dies nicht zutrifft – einem Browser kann man nicht per JavaScript befehlen, einen Proxy zu verwenden, daher gilt alles hier ausschließlich für Node.
Das Terminologieproblem
Klären wir das zunächst, denn es führt zu echten Fehlern.
In SuperAgent erstellt „.agent()“ ohne Argumente eine Kopie von SuperAgent, die Cookies speichert. Die Dokumentation sagt dies ausdrücklich: „In Node speichert SuperAgent standardmäßig keine Cookies, aber Sie können die Methode .agent() verwenden, um eine Kopie von SuperAgent zu erstellen, die Cookies speichert. Jede Kopie verfügt über einen separaten Cookie-Speicher.“
const agent = request.agent();
await agent.post("/login").send({ user, pass });
await agent.get("/cookied-page"); // session cookie carried over
Dieser Agent verfügt zudem über Standardwerte: „Reguläre Anfragemethoden, die für den Agenten aufgerufen werden, werden als Standardwerte für alle von diesem Agenten getätigten Anfragen verwendet.“
.agent(httpAgent) mit einem Argument legt hingegen den „http.Agent“ von Node für die Anfrage fest – dort ist die Proxy-Unterstützung untergebracht.
Gleicher Methodenname, zwei voneinander unabhängige Aufgaben, die sich nur dadurch unterscheiden, ob man ein Argument übergibt. Wenn Sie sich in derselben Sitzung sowohl über SuperAgent-Agenten als auch über Proxy-Agenten informiert haben, sollten Sie diesen Unterschied klären, bevor Sie mit dem Programmieren beginnen.
Variante 1: Ein Proxy-Agent
Dieser Ansatz ist zu bevorzugen, und der Grund dafür ist die Wartung.
import request from "superagent";
import { HttpsProxyAgent } from "https-proxy-agent";
const agent = new HttpsProxyAgent("http://myuser:mypass@proxy.example.com:9000");
const res = await request
.get("https://api.example.com/items")
.agent(agent);
Für SOCKS tauschen Sie das Paket aus:
import { SocksProxyAgent } from "socks-proxy-agent";
const agent = new SocksProxyAgent("socks5h://proxy.example.com:1080");
Beachten Sie, dass socks5h
anstelle von socks5
verwendet wird. Die Variante „h
“ löst Hostnamen auf dem Proxy statt lokal auf. Dadurch wird verhindert, dass DNS-Abfragen an Ihren eigenen Resolver gesendet werden, während Ihr Datenverkehr über einen anderen Weg geleitet wird – ein Leck, das den Sinn der Verwendung eines Proxys zur Geolokalisierung stillschweigend zunichte macht.
Und proxy-agent
verarbeitet jedes Protokoll, das die URL angibt, was nützlich ist, wenn der Proxy aus der Konfiguration stammt:
import { ProxyAgent } from "proxy-agent";
const agent = new ProxyAgent(); // reads http_proxy / https_proxy / no_proxy
Warum diese Pakete statt der SuperAgent-Erweiterung: Alle drei werden aktiv gepflegt. Bei einer Überprüfung anhand der npm-Registry im September 2026 lag proxy-agent
bei Version 8.0.2, https-proxy-agent
bei 9.1.0 und socks-proxy-agent
bei 10.1.0; alle wurden im Juni 2026 veröffentlicht.
Variante 2: superagent-proxy
Die speziell entwickelte Erweiterung und die damit verbundenen Einschränkungen.
import request from "superagent";
import superagentProxy from "superagent-proxy";
superagentProxy(request);
const res = await request
.get("https://api.example.com/items")
.proxy("http://myuser:mypass@proxy.example.com:9000");
In der README-Datei wird beschrieben, dass sie „die Klasse Request von superagent um eine Funktion .proxy(uri) erweitert“, und es wird darauf hingewiesen, dass „sie auf dem Modul proxy-agent basiert“.
Die API ist eleganter als die Übergabe eines Agenten. Der Aufruf .proxy(uri) lässt sich in einer Kette besser lesen und akzeptiert „HTTP-, HTTPS- oder SOCKS“-URIs, wobei die Protokollauswahl an proxy-agent delegiert wird.
Ein Vorbehalt betrifft das Veröffentlichungsdatum. superagent-proxy befindet sich in Version 3.0.0, veröffentlicht im September 2021 – zum Zeitpunkt des Verfassens dieses Artikels also etwa fünf Jahre alt, während die zugrunde liegende Abhängigkeit „proxy-agent“ weiterhin aktualisiert wurde. Sie ist nicht veraltet und funktioniert einwandfrei, aber es handelt sich um einen dünnen Wrapper, der stillgestanden hat, während sich das, was er umhüllt, weiterentwickelt hat.
Die praktische Konsequenz: Wenn Sie es bereits verwenden und es funktioniert, besteht keine Eile. Bei neuem Code bedeutet die direkte Verwendung eines Proxy-Agents lediglich eine zusätzliche Zeile und entfernt eine veraltete Ebene aus Ihrem Abhängigkeitsbaum – und da die Erweiterung genau diesen Agenten umhüllt, verlieren Sie nichts außer der Syntax.
Überprüfen Sie, ob es tatsächlich funktioniert hat
Die vier Zeilen, auf die es am meisten ankommt:
const res = await request
.get("https://api.ipify.org?format=json")
.agent(agent);
console.log(res.body);
Führen Sie den Befehl mit und ohne den Agenten aus. Wenn sich die Adresse nicht ändert, ist der Proxy nicht im Pfad enthalten. SuperAgent gibt in diesem Fall weder eine Fehlermeldung noch eine Warnung aus und es liegt auch kein fehlgeschlagener Aufruf vor – der Datenverkehr wird einfach direkt weitergeleitet.
Drei Gründe, warum eine Konfiguration häufig keine Wirkung zeigt:
Sie haben „.agent()“ ohne Argument aufgerufen, wodurch eine Cookie-persistente Kopie von SuperAgent erstellt wurde, anstatt einen HTTP-Agenten festzulegen. Dies ist eine terminologische Falle, die genau dieses Symptom hervorruft.
Sie haben den Agenten auf die falsche Anfrage angewendet. Die Verkettung bei SuperAgent erfolgt pro Anfrage, sodass ein bei einem Aufruf festgelegter Agent nicht für den nächsten gilt. Um ein konsistentes Verhalten zu gewährleisten, sollten Sie die Erstellung der Anfrage in eine Funktion einbinden.
Der Agententyp passt nicht zum Ziel. Ein „HttpsProxyAgent“ verarbeitet HTTPS-Ziele; für ein reines HTTP-Ziel ist möglicherweise die HTTP-Variante erforderlich. „proxy-agent“ umgeht dieses Problem, indem es die Auswahl für Sie trifft.
Bei geografisch ausgerichteten Proxys reicht eine Adressprüfung nicht aus. Überprüfen Sie anhand des Ergebnisses – fordern Sie etwas an, das sich je nach Region tatsächlich unterscheidet, und vergewissern Sie sich, dass sich die Antwort geändert hat. Wenn ein Lookup-Dienst das richtige Land angibt, während Ihre API Daten aus Ihrer Heimatregion zurückgibt, bedeutet dies, dass das Targeting nicht dort ansetzt, wo es darauf ankommt – ein Muster für „stille Fehler“, das wir in Warum das Testen von Proxys wichtig ist beschrieben haben.
Timeouts bei Nutzung eines Proxys
Das Timeout-Modell von SuperAgent ist außergewöhnlich gut und sollte unbedingt richtig genutzt werden, wenn ein Proxy für zusätzliche Latenz sorgt.
In der Dokumentation werden zwei Einstellungen beschrieben. „req.timeout({deadline: ms})
“ – oder „req.timeout(ms)
“ – „legt eine Frist fest, innerhalb derer die gesamte Anfrage (einschließlich aller Uploads, Weiterleitungen und der Server-Verarbeitungszeit) abgeschlossen sein muss. Wird die Antwort innerhalb dieser Zeit nicht vollständig heruntergeladen, wird die Anfrage abgebrochen.“ Und req.timeout({response: ms})
„legt die maximale Wartezeit für das Eintreffen des ersten Bytes vom Server fest, begrenzt jedoch nicht, wie lange der gesamte Download dauern darf.“
Der in der Dokumentation enthaltene Ratschlag zur Dimensionierung ist für Anfragen über einen Proxy direkt relevant: „Das Antwort-Timeout sollte mindestens einige Sekunden länger sein als die Zeit, die der Server für die Antwort benötigt, da es auch die Zeit für die DNS-Abfrage, den Aufbau von TCP/IP- und TLS-Verbindungen sowie die Zeit für das Hochladen der Anfragedaten umfasst.“
Über einen Proxy dauert jede dieser Phasen länger. Ein privater Exit-Server verursacht eine echte Latenz pro Anfrage, und das liegt an der Entfernung und nicht an einer Störung.
const res = await request
.get("https://api.example.com/items")
.agent(agent)
.timeout({ response: 15000, deadline: 60000 });
Die Dokumentation empfiehlt, beides zu verwenden, und der Grund dafür ist derselbe wie überall sonst: Ein Antwort-Timeout erfasst einen Server, der nie antwortet, während eine Deadline einen Server erfasst, der zwar antwortet, dann aber nur noch tröpfchenweise Daten liefert. Keines der beiden allein deckt beide Fälle ab.
Legen Sie die Werte anhand von Messungen über den Proxy fest und nicht aus Gewohnheit, sonst verursachen Sie Fehler, die wie ein defekter Proxy aussehen, aber lediglich auf Latenz zurückzuführen sind, die Sie nicht einkalkuliert haben.
Fehlerbehandlung
Das Standardverhalten von SuperAgent unterscheidet sich von dem der meisten Clients, und das ist in diesem Zusammenhang von Bedeutung.
In der Dokumentation heißt es ausdrücklich: „SuperAgent betrachtet 4xx- und 5xx-Antworten (sowie nicht verarbeitete 3xx-Antworten) standardmäßig als Fehler“. Es wird hinzugefügt, dass „diese Statusinformationen über err.status verfügbar sind“ und dass solche Fehler „auch ein Feld ‚err.response‘ enthalten“.
Ein Fehlschlag bei der Proxy-Authentifizierung wird also als Ablehnung und nicht als Antwort zurückgegeben:
try {
const res = await request.get(url).agent(agent).timeout({ deadline: 30000 });
return res.body;
} catch (err) {
if (err.status === 407) throw new Error("Proxy rejected credentials");
if (err.status === 401) throw new Error("Target requires authentication");
if (!err.status) throw new Error(`Network error: ${err.code} ${err.message}`);
throw err;
}
Die Unterscheidung zwischen 407 und 401 ist es wert, berücksichtigt zu werden. Ein 407 bedeutet, dass der Proxy den Zugriff blockiert hat und das Ziel nie erreicht wurde; ein 401 bedeutet, dass der Proxy funktioniert hat und das Ziel Anmeldedaten verlangt. Unterschiedliche Ursachen, unterschiedliche Lösungen – und sie sind leicht zu verwechseln, wenn beide als ausgelöste Fehler erscheinen.
Ein Fehler ohne „err.status“ bedeutet, dass überhaupt keine HTTP-Antwort eingegangen ist, was eher auf die Verbindung als auf die Authentifizierung eines Nutzers hindeutet. „ECONNREFUSED“ bedeutet, dass an der Proxy-Adresse niemand auf Anfragen wartet; „ETIMEDOUT“ bedeutet, dass Pakete verloren gehen.
Um bestimmte Fehlerstatus als Erfolge zu behandeln – also einen 404-Fehler als Daten statt als Fehler zu interpretieren –, verwenden Sie „.ok()“:
.ok(res => res.status < 500)
Wiederholungsversuche – mit Bedacht
SuperAgent verfügt über eine integrierte Wiederholungsfunktion, bei der eine dokumentierte Einschränkung zu beachten ist.
const res = await request.get(url).agent(agent).retry(2);
In der Dokumentation wird erläutert, dass „.retry()“ „Anfragen automatisch wiederholt, wenn sie vorübergehend fehlschlagen oder der Fehler auf eine instabile Internetverbindung zurückzuführen sein könnte“. Dabei werden eine optionale Anzahl von Wiederholungsversuchen (Standardwert 1) sowie ein Callback akzeptiert, der „vor jedem Wiederholungsversuch“ aufgerufen wird. Der Callback „kann true/false zurückgeben, um zu steuern, ob die Anfrage erneut versucht werden soll (die maximale Anzahl an Wiederholungsversuchen wird jedoch immer angewendet)“.
Und die Einschränkung, die in der Dokumentation klar formuliert ist: Verwenden Sie .retry() „nur bei Anfragen, die idempotent sind“.
Über einen Proxy ist dies aus einem bestimmten Grund wichtiger als sonst. Ein Timeout ist kein Beweis für einen Fehler – die Anfrage könnte das Ziel erreicht und erfolgreich abgeschlossen haben, während die Antwort auf dem Rückweg verloren gegangen ist. Es gibt einen zusätzlichen Hop, bei dem dies passieren kann. Ein erneuter POST-Versuch in dieser Situation kann zu einem doppelten Schreibvorgang führen, und keine noch so ausgefeilte Konfiguration für Wiederholungsversuche kann dies sicher verhindern. Wenn der Vorgang von Bedeutung ist, verwende einen Idempotenzschlüssel, sofern die API einen solchen anbietet.
Der Callback ist auch der richtige Ort, um eine Wiederholung bei einem Authentifizierungsfehler zu vermeiden, da ein 407-Fehler aufgrund falscher Anmeldedaten bei jedem Versuch erneut einen 407-Fehler auslöst:
.retry(3, (err, res) => {
if (res?.status === 407 || res?.status === 401) return false;
return true;
})
Ein Wrapper, der sich lohnt
Die Verkettung bei SuperAgent erfolgt pro Anfrage, was bedeutet, dass man die Proxy-Konfiguration bei genau dem einen Aufruf, auf den es ankommt, leicht vergessen kann. Das Einbinden der Anfrageerstellung löst dieses Problem und bietet Ihnen einen Ort, an dem Sie die übrigen Standardwerte hinterlegen können.
import request from "superagent";
import { ProxyAgent } from "proxy-agent";
const agent = process.env.PROXY_URL ? new ProxyAgent(process.env.PROXY_URL) : undefined;
const UA = "AcmeBot/1.0 (+https://acme.example.com/bot)";
function req(method, url) {
const r = request[method](url)
.set("User-Agent", UA)
.timeout({ response: 15000, deadline: 60000 })
.retry(2, (err, res) => {
if (res?.status === 407 || res?.status === 401) return false;
if (res?.status === 429) return false; // honour the rate limit instead
return true;
});
return agent ? r.agent(agent) : r;
}
export const get = url => req("get", url);
export const post = url => req("post", url);
export async function verifyExit() {
const res = await get("https://api.ipify.org?format=json");
console.log(`Exit address: ${res.body.ip}`);
return res.body.ip;
}
Fünf dieser Entscheidungen sind bewusst getroffen worden.
Der Proxy ist optional und wird aus der Umgebung bezogen. Ohne PROXY_URL ist der Agent undefined und Anfragen werden direkt weitergeleitet, wodurch sich die lokale Entwicklung und die Produktion vorhersehbar verhalten, ohne dass Verzweigungen im Anwendungscode erforderlich sind. Im Quellcode erscheinen keine Anmeldedaten.
„ProxyAgent“ ohne Konstruktorargument würde „http_proxy“ und Ähnliches auslesen, falls Sie eine umgebungsgesteuerte Konfiguration bevorzugen; die explizite Übergabe der URL macht die „Quelle der Wahrheit“ offensichtlich, was in der Regel mehr Wert hat.
Der User-Agent ist ehrlich und enthält eine Kontakt-URL. Das kostet nichts und verändert den Ablauf, wenn ein Website-Betreiber auf Sie aufmerksam wird.
Wiederholungsversuche schließen die Status aus, bei denen ein erneuter Versuch sinnlos oder unhöflich wäre. Ein 407-Fehler aufgrund falscher Anmeldedaten bleibt immer ein 407; ein 429-Fehler ist eine Anweisung, langsamer vorzugehen, und ein erneuter Versuch verwandelt eine vorübergehende Beschränkung in eine länger andauernde.
„verifyExit()“ wird exportiert und beim Start aufgerufen. Sechs Zeilen, die einen stillschweigend umgangenen Proxy in einen Eintrag in den Protokollen verwandeln – was das einzige wiederkehrende Thema der Proxy-Arbeit in jedem Client ist und das Einzige, was keine Bibliothek für Sie erledigen wird.
„.connect()“ ist kein Proxy
Das sollte erwähnt werden, da es so aussieht, als wäre es einer, es aber nicht ist.
SuperAgent bietet eine „.connect()“-Methode an, die laut Dokumentation ermöglicht, „die DNS-Auflösung zu ignorieren und alle Anfragen an eine bestimmte IP-Adresse weiterzuleiten“. Sie unterstützt eine Zuordnung, einschließlich eines Fallbacks für „*“:
const res = await request.get("http://redir.example.com:555")
.connect({
"redir.example.com": "127.0.0.1",
"www.example.com": false,
"mapped.example.com": { host: "127.0.0.1", port: 8080 },
"*": "proxy.example.com",
});
In der Dokumentation wird darauf hingewiesen, dass „die Anfragen ihren Host-Header mit dem ursprünglichen Wert beibehalten“ und dass .connect(undefined) die Funktion deaktiviert.
Hierbei handelt es sich um eine Host-Umleitung, nicht um Proxying. Es wird geändert, an welche Adresse die Verbindung hergestellt wird, während die Anfrage unverändert bleibt – es gibt keinen CONNECT-Tunnel, kein Proxy-Protokoll und keine Proxy-Authentifizierung. Diese Funktion dient zu Testzwecken und wird in der Dokumentation aus gutem Grund unter „Testen auf localhost“ aufgeführt.
Die Zeile „"*": "proxy.example.com"“ im offiziellen Beispiel ist die Ursache für die Verwirrung. Verwenden Sie „.connect()“, um Anfragen an einen lokalen Testserver weiterzuleiten; nutzen Sie einen Agenten für einen echten Proxy.
Häufig gestellte Fragen
Wie verwende ich einen Proxy mit SuperAgent?
Übergeben Sie einen Proxy-Agenten an „.agent()“: Erstellen Sie eine Instanz von „HttpsProxyAgent“ oder „SocksProxyAgent“ mit Ihrer Proxy-URL und übergeben Sie diese an die Anfrage. Alternativ können Sie die Erweiterung „superagent-proxy“ verwenden, die eine Methode „.proxy(uri)“ hinzufügt – allerdings wurde dieses Paket seit 2021 nicht mehr veröffentlicht.
Was ist der Unterschied zwischen .agent() mit und ohne Argumente?
Ohne Argument wird eine Cookie-persistente Kopie von SuperAgent mit eigener JAR-Datei und Standardoptionen erstellt. Mit einem Argument wird die „http.Agent“ von Node für diese Anfrage gesetzt – auf diese Weise wird die Proxy-Unterstützung angewendet. Der gleiche Name sorgt für echte Verwirrung.
Wird „superagent-proxy“ noch gepflegt?
Es ist nicht veraltet, aber Version 3.0.0 stammt aus dem September 2021, während die zugrunde liegende Abhängigkeit „proxy-agent“ weiterhin aktualisiert wurde – zuletzt im Juni 2026. Bei neuem Code vermeidet die direkte Verwendung eines Proxy-Agents einen veralteten Wrapper, was lediglich eine zusätzliche Zeile erfordert.
Warum funktioniert mein SuperAgent-Proxy nicht?
Meistens liegt es daran, dass „.agent()“ ohne Argument aufgerufen wurde, wodurch ein Cookie-Jar erstellt wird, anstatt einen Proxy einzurichten. Überprüfen Sie außerdem, ob der Agent auf die richtige Anfrage angewendet wurde, da die SuperAgent-Verkettung pro Aufruf erfolgt. Verifizieren Sie dies, indem Sie einen Dienst abfragen, der Ihre Adresse zurückgibt – ein umgangener Proxy erzeugt keinen Fehler.
Berücksichtigt SuperAgent die Umgebungsvariablen von HTTP_PROXY?
Von sich aus nicht. Das Paket „proxy-agent“ liest jedoch http_proxy, https_proxy und no_proxy. Wenn Sie also ein ProxyAgent() ohne Argumente erstellen und es an .agent() übergeben, erhalten Sie ein umgebungsgesteuertes Verhalten.
Wie lege ich Timeouts für Proxy-Anfragen fest?
Verwenden Sie beide Einstellungen: .timeout({ response: 15000, deadline: 60000 }). Das Antwort-Timeout begrenzt die Wartezeit auf das erste Byte, die Deadline begrenzt die gesamte Anfrage. Legen Sie deren Werte anhand von Messungen über den Proxy fest, da ein privater Ausgang sowohl in der DNS- als auch in der Verbindungs- und TLS-Phase zusätzliche Latenz verursacht.
Wie unterscheide ich einen Proxy-Fehler von einem Zielfehler?
Anhand des Statuscodes. SuperAgent behandelt 4xx- und 5xx-Codes als Fehler; fangen Sie daher „err.status“ ab und lesen Sie den Wert – 407 bedeutet, dass der Proxy die Anfrage abgelehnt hat und das Ziel nie erreicht wurde, während 401 bedeutet, dass der Proxy funktioniert hat und das Ziel Anmeldedaten verlangt. Wenn überhaupt kein „err.status“ vorliegt, bedeutet dies, dass keine HTTP-Antwort eingegangen ist.
Kann ich .connect() als Proxy verwenden?
Nein. Es leitet Anfragen an eine bestimmte IP-Adresse weiter und behält dabei den ursprünglichen „Host“-Header bei, was eher eine Host-Zuordnung zu Testzwecken als eine Proxy-Funktion darstellt. Es gibt keinen Tunnel, kein Proxy-Protokoll und keine Authentifizierung. Verwenden Sie für einen echten Proxy einen Agenten.
Fazit
SuperAgent verfügt über keine eigene Proxy-Option, daher hat man die Wahl zwischen einem Agenten und einer Erweiterung – wobei der Agent die bessere Standardwahl ist, da in beiden Fällen die gepflegten Pakete die eigentliche Arbeit erledigen.
Die Terminologie ist die größte Falle. „.agent()“ ohne Argument liefert Ihnen eine „Cookie-Jar“; „.agent(something)“ richtet einen HTTP-Agenten ein. Viele konfigurieren Ersteres, sehen, dass die Anfragen erfolgreich sind, und schließen daraus, dass der Proxy funktioniert. Niemand weist sie darauf hin, dass ein umgangener Proxy per Definition stillschweigend fehlschlägt.
Deshalb lohnt es sich, das Überprüfen zur Gewohnheit zu machen. Rufen Sie einen Dienst auf, der Ihre Adresse meldet – mit und ohne Agent – und vergewissern Sie sich, dass sich die Antwort ändert. Bei geografisch ausgerichteten Aufgaben sollten Sie noch einen Schritt weiter gehen und überprüfen, ob sich regional unterschiedliche Inhalte tatsächlich unterscheiden – die Adresse ist dabei der einfache Teil und liefert die wenigsten Informationen.
Legen Sie dann beide Timeouts fest, verzweigen Sie bei „err.status“, sodass ein 407- und ein 401-Fehler zu unterschiedlichen Meldungen führen, und halten Sie „.retry()“ von allem fern, was nicht idempotent ist. Über einen Proxy gibt es einen zusätzlichen Schritt, bei dem eine erfolgreiche Anfrage ihre Antwort verlieren kann, und ein erneuter Versuch in dieser Situation ist eher eine Doppelung als eine Wiederherstellung.