Frage

Ich habe eine Rewrite-Regel, die Umleitungen / wenn keine accept-Sprache vorhanden ist und jemand versucht zu Besuch ?lang=en. Es funktioniert gut, außer für den zurückgegebenen Header. Vary: accept-language wird aus der Reaktion fehlt.

RewriteCond %{HTTP:Accept-Language} ^$  
RewriteCond %{QUERY_STRING}         ^lang=en  
RewriteRule ^$                      http://www.example.com/?     [R=301,L]

Der Apache-Dokumentation gibt an:

Wenn ein HTTP-Header in einem Zustand verwendet wird dieser Header auf die Header der Antwort bei Vary hinzugefügt wird, die Bedingung das Ergebnis true für den Antrag. Es ist nicht, ob die Bedingung das Ergebnis falsch für die Anforderung.

hinzugefügt

Die Bedingungen werden auf jeden Fall passend und Umleitung, so dass ich nicht verstehen, warum Apache ist nicht die Sprache Hinzufügen variieren. Man kann sehen, warum dies ein echtes Problem wäre, wenn ein Proxy-Cache sind das? Lang = en immer Umleitungen an / unabhängig von den accept-language-Header gesendet.

War es hilfreich?

Lösung

Nachdem in den zwielichtigen spähen Unterbauch von Apache Wunsch Handhabungssystem, es stellt sich heraus, dass die Dokumentation etwas irreführend ... Aber bevor ich in der Erklärung bekommen, von dem, was ich Ihnen sagen kann, auf Gedeih und Verderb von Apache sind auf diese ein.

Der Kunde Problem

Als erstes wird der Header-Name nicht auf die hinzugefügt werden Vary Antwort-Header, wenn es nicht durch den Client gesendet wird. Dies ist darauf zurückzuführen, wie mod_rewrite konstruiert den Wert für diesen Header intern .

Es sieht den Header von Namen alle mit apr_table_get() , der Kopftabelle Wunsch, und der Name, den Sie zur Verfügung gestellt:

const char *val = apr_table_get(ctx->r->headers_in, name);

Wenn name kein Schlüssel in der Tabelle ist, wird diese Funktion NULL zurück. Dies ist ein Problem, denn unmittelbar danach eine Prüfung gegen val ist:

if (val) {
   // Set the structure member ctx->vary_this
}

ctx->vary_this wird auf einer Pro-RewriteCond Basis zu akkumulieren Headernamen verwendet, die in die endgültige zusammengesetzt werden sollte Vary Header *. Da keine Zuordnung oder Anhänge werden auftreten, wenn kein Wert ist, eine referenzierte (aber nicht gesendet) Header wird nie in Vary erscheinen. Die Dokumentation nicht ausdrücklich diesen Zustand, so kann es oder nicht gewesen sein kann, was Sie erwarten.

* Als Nebenwirkung der NV (kein variieren) Flag und ignoriert-on-failure Funktionalität durch Einstellen ctx->vary_this zu NULL implementiert ist, seine Zugabe zu den Antwort-Header zu verhindern.

Allerdings ist es möglich, dass Sie gesendet Accept-Language , aber es war leer. In diesem Fall wird die leere Zeichenkette, die die obige Prüfung übergeben, und der Header-Name wird hinzugefügt werden Vary durch mod_rewrite von dem, was oben beschrieben ist. Vor diesem Hintergrund, habe ich folgende Anfrage zu diagnostizieren, was los war:

User-Agent: Fiddler
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: 
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Host: 129.168.0.123

Das funktioniert auch nicht, aber warum? mod_rewrite setzt definitiv die Header, wenn die Regel und Bedingung Spiel (ctx->vary ein Aggregat von ctx->vary_this über alle geprüften Bedingungen ist):

if (ctx->vary) {
    apr_table_merge(r->headers_out, "Vary", ctx->vary);
}

Dies kann mit einer Log-Anweisung überprüft werden, und r->headers_out ist die verwendete Variable, wenn die Antwort-Header zu erzeugen. Angesichts etwas definitiv falsch obwohl gehen, muss es Schwierigkeiten geben, nachdem die Regeln ausgeführt werden.

Das .htaccess Problem

Zur Zeit scheinen Sie Ihre Regeln in .htaccess zu definieren, oder ein <Directory> Abschnitt. Das bedeutet, dass mod_rewrite in Apache Fixup Phase betrieben wird, und der Mechanismus verwendet es, um tatsächlich umschreibt hier zuführen, ist sehr chaotisch. Nehmen wir an, eine Sekunde lang, es gibt keine externe Umleitung, da Sie Problem ohne eine selbst hatte (und ich werde später mit der Umleitung auf die Frage bekommen).

Nachdem Sie eine Rewrite ausführen, es ist viel zu spät in der Anforderungsverarbeitung für das Modul tatsächlich in eine Datei zuordnen. Was sie tut, ist stattdessen zuweisen sich als der „Inhalt“ Handler Wunsch und wenn die Anforderung erreicht, dass Punkt, es einen Aufruf an ap_internal_redirect() führt. Dies führt zur Schaffung eines neuen Request-Objekt, das nicht die headers_out Tabelle aus dem Original enthält.

, dass mod_rewrite Unter der Annahme, verursacht keine weiteren Umleitungen wird die Reaktion erzeugt aus dem neuen Request-Objekt, das die entsprechenden (original) wird nie ihr zugeordneten Header hat. Es ist möglich, dies zu umgehen, indem sie in einem pro-Server-Kontext arbeitet (in der Hauptkonfiguration oder in einem <VirtualHost>), aber ...

Das Redirect Problem

Leider stellt sich heraus, dass es weitgehend irrelevant ohnehin ist, Denn selbst wenn wir den Einsatz mod_rewrite in einem Serverkontext zu tun, den Weg der Reaktion im Falle einer Umleitung nimmt immer noch die Header bewirkt, dass der Modulsatz geworfen werden.

Wenn die Anforderung von Apache, durch eine Kette von Funktionsaufrufen empfangen wird, macht es seinen Weg zu ap_process_request(). Dies wiederum ruft ap_process_request_internal(), wo der Großteil der wichtigen Schritte Anforderung auftritt (einschließlich dem Aufruf von mod_rewrite) Parsen. Es gibt einen Integer-Statuscode, der im Fall Ihrer Umleitung erfolgt auf 301 festgelegt werden.

Die meisten Anfragen zurückgeben OK (die einen Wert von 0 hat), was sofort zu ap_finalize_request_protocol(). Allerdings, das ist nicht der Fall hier :

if (access_status == OK) {
    ap_finalize_request_protocol(r);
}
else {
    r->status = HTTP_OK;
    ap_die(access_status, r);
}

ap_die() hat einige zusätzliche Manipulation (wie den Antwortcode zurück auf 301 zurückkehrt), und in diesem speziellen Fall endet mit einem Aufruf an ap_send_error_response().

Zum Glück, das ist schließlich Wurzel des Problems. Obwohl es wie es scheinen mag, sind die Dinge nicht „assbackwards“, und dies bewirkt, dass die Zerstörung der ursprünglichen Header. Es gibt sogar einen Kommentar darüber in der Quelle :

if (!r->assbackwards) {
    apr_table_t *tmp = r->headers_out;

    /* For all HTTP/1.x responses for which we generate the message,
     * we need to avoid inheriting the "normal status" header fields
     * that may have been set by the request handler before the
     * error or redirect, except for Location on external redirects.
     */
    r->headers_out = r->err_headers_out;
    r->err_headers_out = tmp;
    apr_table_clear(r->err_headers_out);

    if (ap_is_HTTP_REDIRECT(status) || (status == HTTP_CREATED)) {
        if ((location != NULL) && *location) {
            apr_table_setn(r->headers_out, "Location", location);
        }
        //...
    }
//...
}

Beachten Sie, dass r->headers_out ersetzt wird, und die ursprüngliche Tabelle gelöscht wird. Diese Tabelle hatte alle Informationen, die so nun in der Antwort zu zeigen, erwartet wurde es verloren ist.

Fazit

Wenn Sie nicht umleiten und Sie definieren die Regeln in einem pro-Server-Kontext, alles scheint richtig zu arbeiten. Dies ist jedoch nicht das, was Sie wollen. Ich kann eine mögliche Abhilfe sehen, aber ich bin nicht sicher, ob es akzeptabel wäre, nicht die Notwendigkeit zu erwähnen, den Server neu zu kompilieren.

Wie für die Vary: Accept-Encoding, kann ich nur annehmen, es kommt aus einem anderen Modul, dass verhält sich in einer Weise, dass der Kopf zu schleichen durch ermöglicht. Ich bin auch nicht sicher, warum Gumbo nicht ein Problem hat, wenn es zu versuchen.

Als Referenz Ich war auf der Suche am 2.2.14 und

Andere Tipps

Sie möchten vielleicht so etwas wie die folgenden als Behelfslösung, um zu versuchen:

<LocationMatch "^.*lang\=">
    Header onsuccess merge Vary "Accept-Language"
</LocationMatch>
scroll top