mod_rewrite لا يرسل تباين: قبول اللغة عند إعادة كتابة مباريات
-
02-10-2019 - |
سؤال
لدي قاعدة إعادة كتابة تعيد التوجيه إلى / إذا لم يكن هناك أي لغة قبول ويحاول شخص ما الزيارة ?lang=en
. إنه يعمل بشكل جيد ، باستثناء الرؤوس التي تم إرجاعها. Vary: accept-language
مفقود من الاستجابة.
RewriteCond %{HTTP:Accept-Language} ^$
RewriteCond %{QUERY_STRING} ^lang=en
RewriteRule ^$ http://www.example.com/? [R=301,L]
تحدد وثائق Apache:
إذا تم استخدام رأس HTTP في حالة يضاف هذا الرأس إلى الرأس المتغير للاستجابة في حالة تقييم الشرط إلى صواب للطلب. لم تتم إضافته إذا كان الشرط يقيم إلى خطأ للطلب.
من المؤكد أن الظروف مطابقة وإعادة توجيه ، لذلك لا أفهم لماذا لا تتجاوز Apache أن اللغة تختلف. يمكن للمرء أن يرى لماذا سيكون هذا مشكلة حقيقية إذا كان الوكيل هو التخزين المؤقت؟ lang = en يعيد دائمًا إعادة التوجيه إلى / بغض النظر عن رأس القبول المرسلة.
المحلول
بعد النظر إلى نظام معالجة طلبات Apache غير الباطن ، اتضح أن الوثائق مضللة إلى حد ما ... ولكن قبل أن أتوصل إلى التفسير ، مما يمكنني إخبارك به تحت رحمة Apache على هذا.
مشكلة العميل
أولاً ، لن يتم إضافة اسم الرأس إلى يتغير رأس الاستجابة إذا لم يتم إرساله من قبل العميل. هذا بسبب كيف mod_rewrite
يبني القيمة لهذا الرأس داخليا.
يبحث عن الرأس بالاسم باستخدام apr_table_get()
, وجدول رأس الطلب والاسم الذي قدمته:
const char *val = apr_table_get(ctx->r->headers_in, name);
إذا name
ليس مفتاحًا في الجدول ، ستعود هذه الوظيفة NULL
. هذه مشكلة ، لأنه بعد ذلك فور شيك ضد val
:
if (val) {
// Set the structure member ctx->vary_this
}
ctx->vary_this
يستخدم علىRewriteCond
أساس لتراكم أسماء الرأس التي يجب تجميعها في النهائي يتغير رأس*. نظرًا لعدم حدوث أي مهمة أو إلحاق إذا لم تكن هناك قيمة ، فلن يظهر رأس مشار إليه (ولكن لم يتم إرساله) Vary
. الوثائق لا تنص بشكل صريح على هذا ، لذلك قد يكون أو لا يكون ما تتوقعه.
*جانبا ، NV
(لا تختلف) يتم تنفيذ العلم وتجاهل الوظائف على السعودية عن طريق الإعداد ctx->vary_this
إلى NULL
, ، منع إضافته إلى رأس الاستجابة.
ومع ذلك ، من الممكن أن أرسلت قبول اللغة, ، لكنها كانت فارغة. في هذه الحالة ، ستمر السلسلة الفارغة بالتحقق أعلاه ، وسيتم إضافة اسم الرأس إلى يتغير بواسطة mod_rewrite
من ما هو موضح أعلاه. مع وضع ذلك في الاعتبار ، استخدمت الطلب التالي لتشخيص ما يجري:
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
هذا لا يعمل أيضًا ، ولكن لماذا؟ mod_rewrite
بالتأكيد يضع الرؤوس عندما تتطابق القاعدة والحالة (ctx->vary
هو مجموع ctx->vary_this
عبر جميع الشروط المحددة):
if (ctx->vary) {
apr_table_merge(r->headers_out, "Vary", ctx->vary);
}
يمكن التحقق من ذلك ببيان السجل ، و r->headers_out
هو المتغير المستخدمة عند إنشاء رؤوس الاستجابة. بالنظر إلى أن هناك خطأ ما بالتأكيد ، يجب أن تكون هناك مشكلة بعد تنفيذ القواعد.
مشكلة .htaccess
حاليا ، يبدو أنك تحدد قواعدك في .htaccess
, أو أ <Directory>
الجزء. هذا يعني ذاك mod_rewrite
يعمل في مرحلة إصلاح Apache ، والآلية التي يستخدمها لأداء إعادة الكتابة هنا هي فوضوية للغاية. لنفترض لثانية واحدة لا يوجد إعادة توجيه خارجي ، نظرًا لأنك واجهت مشكلة حتى بدونها (وسأتوصل إلى مشكلة إعادة التوجيه لاحقًا).
بعد إجراء إعادة كتابة ، فات الأوان في معالجة الطلب للوحدة النمطية لخريطة ملف. ما يفعل ap_internal_redirect()
. هذا يؤدي إلى إنشاء كائن طلب جديد ، لا يحتوي على headers_out
الجدول من الأصل.
افترض أن mod_rewrite
لا تسبب أي إعادة توجيه ، يتم إنشاء الاستجابة من الجديد كائن طلب ، والذي لن يكون له الرؤوس (الأصلية) المناسبة المخصصة له. من الممكن الالتفاف على هذا من خلال العمل في سياق لكل خادم (في التكوين الرئيسي أو في أ <VirtualHost>
)، لكن...
مشكلة إعادة التوجيه
لسوء الحظ ، اتضح أنه غير ذي صلة إلى حد كبير على أي حال ، لأنه حتى لو استخدمنا mod_rewrite
في سياق الخادم ، لا يزال المسار الذي تسلكه الاستجابة في حالة إعادة التوجيه يسبب الرؤوس التي يتم تعيين الوحدة التي يتم إلقاؤها.
عندما يتم استلام الطلب من قبل Apache ، من خلال سلسلة من الوظائف ، فإنه يشق طريقه إلى ap_process_request()
. هذا بدوره المكالمات ap_process_request_internal()
, ، حيث تحدث الجزء الأكبر من خطوات تحليل الطلب المهمة (بما في ذلك الاحتجاج mod_rewrite
). إنه يعيد رمز حالة عدد صحيح ، والذي يحدث في حالة إعادة التوجيه الخاصة بك على 301.
معظم الطلبات عودة OK
(التي لها قيمة 0) ، تؤدي على الفور إلى ap_finalize_request_protocol()
. ومع ذلك ، هذا ليس هو الحال هنا:
if (access_status == OK) {
ap_finalize_request_protocol(r);
}
else {
r->status = HTTP_OK;
ap_die(access_status, r);
}
ap_die()
هل بعض التلاعب الإضافي (مثل إعادة رمز الاستجابة مرة أخرى إلى 301) ، وفي هذه الحالة بالذات ينتهي مكالمة إلى ap_send_error_response()
.
لحسن الحظ ، هذا هو أخيرًا جذر المشكلة. على الرغم من أن الأمر قد يبدو الأمر كذلك ، إلا أن الأمور ليست "Assbackwards" ، وهذا يسبب تدمير الرؤوس الأصلية. هناك حتى تعليق حول هذا الموضوع في المصدر:
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);
}
//...
}
//...
}
لاحظ ذلك r->headers_out
يتم استبداله ، ويتم مسح الجدول الأصلي. كان لهذا الجدول جميع المعلومات التي كان من المتوقع أن تظهر في الرد ، لذلك فقد فقدت الآن.
استنتاج
إذا لم تقم بإعادة التوجيه وحددت القواعد في سياق لكل خادم ، يبدو أن كل شيء يعمل بشكل صحيح. ومع ذلك ، هذا ليس ما تريد. أستطيع أن أرى حلًا محتملًا ، لكنني لست متأكدًا مما إذا كان من المقبول ، ناهيك عن الحاجة إلى إعادة ترجمة الخادم.
أما بالنسبة لل Vary: Accept-Encoding
, ، لا يمكنني إلا أن أفترض أنها تأتي من وحدة مختلفة تتصرف بطريقة تسمح للرأس بالتسلل. أنا أيضًا لست متأكدًا لماذا جامبو لم يكن لديك مشكلة عند تجربتها.
للإشارة ، كنت أنظر إلى 2.2.14 و 2.2 رمز مصدر الجذع, ، وكنت أعدل وتشغيل Apache 2.2.15. لا يبدو أن هناك أي فروق ذات دلالة إحصائية بين الإصدارات في أقسام الكود ذات الصلة.
نصائح أخرى
قد ترغب في تجربة شيء مثل ما يلي كحل بديل:
<LocationMatch "^.*lang\=">
Header onsuccess merge Vary "Accept-Language"
</LocationMatch>