mod_rewrite ne pas envoyer Variez: accepter de langue lorsque les matchs RewriteCond

StackOverflow https://stackoverflow.com/questions/3698363

  •  02-10-2019
  •  | 
  •  

Question

J'ai une règle de réécriture qui redirige vers / si aucune langue ACCEPT tentatives actuelles et de visiter quelqu'un ?lang=en. Il fonctionne très bien, sauf pour les en-têtes retournés. Vary: accept-language est absent de la réponse.

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

La documentation Apache précise:

Si un en-tête HTTP est utilisé dans un état cet en-tête est ajouté à la Variez tête de la réponse au cas où la condition est évaluée à true pour la demande. Il n'est pas ajouté si les evalue condition à false pour la demande.

Les conditions assortissent certainement et la réorientation, donc je ne comprends pas pourquoi Apache n'ajoute pas la langue varie. On peut voir pourquoi ce serait un vrai problème si un proxy devait cache? Lang = fr réoriente toujours / quel que soit l'en-tête accept-language envoyé.

Était-ce utile?

La solution

Après jeter un oeil dans le sordide bas-ventre du système de gestion de la demande d'Apache, il se trouve que la documentation est un peu trompeur ... Mais avant d'entrer dans l'explication, de ce que je peux vous dire que vous êtes à la merci d'Apache sur ce une.

Le problème client

D'abord, le nom d'en-tête ne sera pas ajouté à la Variez en-tête de réponse si elle n'est pas envoyé par le client. Cela est dû à la façon dont mod_rewrite construit la valeur de cet en-tête interne .

Il semble l'en-tête par nom en utilisant apr_table_get() , la table d'en-tête de la demande, et le nom que vous avez fourni:

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

Si name est pas une clé dans le tableau, cette fonction retourne NULL. Ceci est un problème, parce que, immédiatement après un chèque contre val:

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

ctx->vary_this est utilisé sur une base per-RewriteCond aux noms d'en-tête Accumulez qui doivent être assemblés dans la finale Variez tête *. Puisque aucune cession ou annexant se produira s'il n'y a pas de valeur, une référence (mais pas envoyé) en-tête ne sera jamais apparaître dans Vary. La documentation ne précise pas explicitement, il peut ou peut ne pas avoir été ce que vous attendiez.

* En aparté, le NV (pas varier) flag et ignorent-on-échec fonctionnalité est mise en oeuvre par la mise en ctx->vary_this à NULL, ce qui empêche son addition à l'en-tête de réponse.

Cependant, il est possible que vous avez envoyé Accept-Language , mais il était vide. Dans ce cas, la chaîne vide passera le contrôle ci-dessus, et le nom d'en-tête sera ajouté à Variez par mod_rewrite de ce qui est décrit ci-dessus. En gardant cela à l'esprit, je la demande suivante pour diagnostiquer ce qui se passait:

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

Cela ne fonctionne pas non plus, mais pourquoi? mod_rewrite fixe définitivement les en-têtes lorsque la règle et match de condition (ctx->vary est un agrégat de ctx->vary_this dans toutes les conditions vérifiées):

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

Cela peut être vérifié avec une instruction journal et r->headers_out la variable utilisée lors de la génération des en-têtes de réponse. Étant donné que quelque chose va certainement mal cependant, il doit y avoir des problèmes après les règles sont exécutées.

Le problème .htaccess

À l'heure actuelle, vous semblez être définir vos règles .htaccess, ou une section de <Directory>. Cela signifie que mod_rewrite fonctionne dans la phase fixup d'Apache, et le mécanisme qu'il utilise pour réaliser effectivement ici réécritures est très salissant. Supposons que pour une seconde, il n'y a pas de redirection externe, puisque vous avez eu un problème, même sans (et je vais arriver à la question avec la redirection plus tard).

Après avoir effectué une ré-écriture, il est beaucoup trop tard dans le traitement de la demande pour le module à la carte en fait un fichier. Ce qu'il fait est au lieu de se saisir comme gestionnaire « contenu » de la demande et quand le cours de ce point demande, il effectue un appel à ap_internal_redirect(). Cela conduit à la création d'un nouvel objet de la demande, qui ne contient pas la table de headers_out de l'original.

En supposant que mod_rewrite ne provoque pas réoriente plus, la réponse est générée à partir du nouveau objet de requête, qui jamais les en-têtes appropriés (d'origine) attribuée. Il est possible de contourner ce problème en travaillant dans un contexte par serveur (dans la configuration principale ou dans un <VirtualHost>), mais ...

Le problème Redirect

Malheureusement, il se trouve qu'il est largement hors de propos de toute façon, Car même si nous utilisons mod_rewrite dans un contexte de serveur, le chemin de la réponse prend en cas d'une redirection provoque toujours les en-têtes que l'ensemble du module à jeté dehors.

Lorsque la demande est reçue par Apache, par une chaîne d'appels de fonction, il fait son chemin à ap_process_request(). Cela appelle ap_process_request_internal(), où la majeure partie de la demande importante des étapes de l'analyse se produit (y compris l'invocation de mod_rewrite). Il renvoie un code d'état entier, qui, dans le cas de votre redirection arrive à être réglé sur 301.

La plupart des demandes de retour OK (qui a une valeur de 0), ce qui conduit immédiatement à ap_finalize_request_protocol(). Cependant, c'est pas le cas :

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

ap_die() fait quelques manipulations supplémentaires (comme retourner le dos de code de réponse à 301), et dans ce cas particulier se termine par un appel à ap_send_error_response().

Par chance, il est enfin racine du problème. Bien que cela puisse sembler, les choses ne sont pas « assbackwards », et cela provoque la destruction des en-têtes d'origine. Il y a même un commentaire à ce sujet dans la source :

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);
        }
        //...
    }
//...
}

Prenez note que r->headers_out est remplacé, et la table d'origine est effacée. Cette table avait toutes les informations qui devait apparaître dans la réponse, alors maintenant il est perdu.

Conclusion

Si vous le faites, tout ne semble pas redirigez et vous définissez les règles dans un contexte par serveur pour fonctionner correctement. Cependant, ce n'est pas ce que vous voulez. Je peux voir une solution de contournement potentiel, mais je ne sais pas si ce serait acceptable, sans parler de la nécessité de recompiler le serveur.

En ce qui concerne le Vary: Accept-Encoding, je ne peux que supposer qu'il vient d'un autre module qui se comporte d'une manière qui permet à l'en-tête de se faufiler à travers. Je suis également pas sûr pourquoi Gumbo n'a pas eu un problème lors de l'essayer.

Pour référence, je regardais la 2.2.14 et

Autres conseils

Vous pouvez essayer quelque chose comme ce qui suit comme une solution de contournement:

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