Requests: تم تعيين العنوان "ترميز النقل: مقسم" حتى إذا تم توفير طول المحتوى مما يؤدي إلى عدم تقطيع الجسم فعليًا

تم إنشاؤها على ٤ أكتوبر ٢٠١٣  ·  52تعليقات  ·  مصدر: psf/requests

اختبار كتابي

import requests
import time

def f():
    yield b"lol"
    time.sleep(2)
    yield b"man"

requests.post('http://127.0.0.1:8801/', data=f(), headers={"Content-Length": 6})

نتيجة فعلية

تم الاستلام على الخادم:

$ nc -p 8801 -l
POST / HTTP/1.1
Host: 127.0.0.1:8801
User-Agent: python-requests/2.0.0 CPython/3.3.1 Linux/3.11.0-031100rc4-generic
Accept: */*
Transfer-Encoding: chunked
Content-Length: 6
Accept-Encoding: gzip, deflate, compress

lolman

نتيجة متوقعة

لم أتوقع "Transfer-Encoding: chunked" منذ أن قدمت Content-Length. إذا أصرت الطلبات على إجراء ترميز نقل مقسم ، فيجب أن تتجاهل طول المحتوى وتقسيم المحتوى فعليًا (كما يحدث إذا لم يتم توفير رأس طول المحتوى).

Breaking API Change Bug

التعليق الأكثر فائدة

timuralp ما زلت أعارض إضافة علم لهذا. من غير المقبول حقًا أن يكون لديك تطبيق HTTP / 1.1 لا يمكنه التعامل مع ترميز النقل المقسم في عام 2016. لقد كان أحد متطلبات المواصفات لفترة طويلة لدرجة أن المواصفات الأولى التي تطلبها قديمة بما يكفي للتصويت في الولايات المتحدة الأمريكية: لا أعتقد أنه يمكننا الاستمرار في قطع الكيانات عن الركود لعدم القيام بذلك.

من وجهة نظري ، يظل الخطأ هنا هو أننا قد نصدر بشكل غير صحيح كلاً من Content-Length و Transfer-Encoding. بالطبع ، وجهة نظري غير ملزمة. ؛)

ال 52 كومينتر

أوافق ، يجب أن نختار أحد هذين الخيارين. =)

أعلق هنا كما كان يجب أن أفعل منذ أكثر من 6 ساعات:

إذا أصرت الطلبات على إجراء ترميز نقل مقسم ، فيجب أن تتجاهل طول المحتوى وتقسيم المحتوى فعليًا (كما يحدث إذا لم يتم توفير رأس طول المحتوى).

أنت تشير إلى أنه في هذه الحالة لم نقم بتقسيم البيانات فعليًا. هل لديك دليل حقيقي على ذلك؟ الناتج وحده لا يكاد يكفي لإثبات ذلك.

علاوة على ذلك ، فإن تشغيل المثال أعلاه في 2.0.0 لا يعمل: أحصل على هذا التتبع:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/api.py", line 88, in post
    return request('post', url, data=data, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/sessions.py", line 357, in request
    resp = self.send(prep, **send_kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/sessions.py", line 460, in send
    r = adapter.send(request, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/adapters.py", line 319, in send
    timeout=timeout
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 541, in urlopen
    body=body, headers=headers)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 366, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 955, in request
    self._send_request(method, url, body, headers)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 989, in _send_request
    self.endheaders(body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 951, in endheaders
    self._send_output(message_body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 815, in _send_output
    self.send(message_body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 787, in send
    self.sock.sendall(data)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
TypeError: must be string or buffer, not generator

هذه ليست مشكلة فعلية بقدر ما أستطيع أن أقول. ليس على الأقل على 2.0.0 وهو الإصدار الوحيد المدعوم حاليًا من الطلبات الموجودة.

لم أتوقع "Transfer-Encoding: chunked" منذ أن قدمت Content-Length

ما الذي أعطاك الانطباع بأن هذا سيكون هو الحال؟

ما أعطاني الانطباع هو حقيقة أن الطلبات لم يتم تقسيمها (حتى لو تم تعيين العنوان) بمجرد تعيين طول المحتوى.

المثال يعمل مع Python 3.3.

تجربته على Python 3.3 لا تنفجر ، من ناحية أخرى ، فقد أدت إلى تقسيم المحتوى. لا يعتمد التقسيم على النظام الأساسي ، لذا فأنا مرتبك تمامًا بذلك.

POST / HTTP/1.1
Host: 127.0.0.1:8801
Content-Length: 6
User-Agent: python-requests/1.2.3 CPython/3.3.2 Darwin/12.5.0
Transfer-Encoding: chunked
Accept: */*
Accept-Encoding: gzip, deflate, compress

3
lol
3
man
0

ثانيًا ، ما زلت أرغب في معرفة ما الذي منحك الانطباع بأن تعيين رأس Content-Length سيمنع الترميز المقسم للبيانات.

@ sigmavirus24 أنت لا تستخدم الطلبات 2.0.0 في تثبيت Python 3.3.

ysangkok نقطة جيدة. :-) مع 2.0 انه كسر بالتأكيد. بغض النظر عن رأيي ، فمن المؤكد في المحكمة لنا إزالة رأس Content-Length .

من المحتمل أنك محق في @ sigmavirus24 ، ربما ينبغي علينا تجريد رأس طول المحتوى.

حسنًا ، لم أعد مقتنعًا تمامًا بحجتي الخاصة بعد الآن. ذهبت للبحث عن المناقشات القديمة المحيطة بالمستخدمين الذين قاموا بتعيين رأس Content-Length بأنفسهم ويجب أن أتذكر محادثات IRC القديمة.

ما زلت على رأي مفاده أنه لا ينبغي للمستخدمين حقًا تعيين هذه الرؤوس بأنفسهم ، وأنه يجب علينا إزالتها ، ومع ذلك ، أعتقد أن هذه المشكلة تشير إلى مشكلة أكثر أهمية بكثير ، وهي السلوك المختلف تمامًا للطلبات في نسختين مختلفتين بايثون.

في 2.7 (كما أوضحت) ، لا يؤدي تعيين رأس Content-Length إلى تغيير كيفية تحميل الطلبات للبيانات. في 3.3 ، ومع ذلك ، فإن ysangkok هو الصحيح في أن الضبط يرسل كل شيء بأسرع ما يمكن (لا يزال يستخدم المولد ولكنه لا يرسله في عزبة مقسمة بالفعل).

تتمثل إحدى الطرق السهلة لإصلاح ذلك في إزالة الرأس عند استخدام مولد (أو دائمًا لتوفير سلوك ثابت) ، والعيب في هذا هو أن هذا السلوك غير متوافق مع الإصدارات السابقة.

الطريقة السهلة الأخرى هي كسر اتساق واجهة برمجة التطبيقات (API) من خلال عدم استخدام ترميز النقل المقسم مع المولد دائمًا. Lukasa يحتاج هذا بالتأكيد إلى بعض التفكير العميق حيث لا يمكنني العثور على المحادثات القديمة حيث تم تحذير المستخدمين من تعيين هذا العنوان بأنفسهم.

لكي أكون صادقًا ، لا أتوقع أبدًا أن يؤدي تعيين رأس إلى تغيير سلوك استخدام المولد.

هذا بالتأكيد وضع صعب للغاية

بليه. سوف أفكر.

كما أن هذا ليس تغيير واجهة برمجة تطبيقات معطل سأتردد في إجرائه لأنني يجب أن أتساءل عن عدد الأشخاص الذين يعتمدون بالفعل على هذا السلوك.

لقد واجهت موقفًا يُعرف فيه طول المولد (على سبيل المثال ، ملف متدفق من خادم آخر أو قرص كبير بالفعل في الملف). على سبيل المثال:

response = requests.get('http://example.com/mybig.iso', stream=True)
length = response.headers.get('Content-Length')

def exhaust(response):
    while True:
        out = response.raw.read(1024*1024)
        if not out:
            break
        yield out

response = requests.post('http://example.com/upload', data=exhaust(response), headers={'Content-Length': length})

قد يكون هذا سببًا وجيهًا للاحتفاظ بطول المحتوى. الحل الوحيد الآن (الذي أعرفه) هو استنفاد المولد في الذاكرة وتمرير البايتات مباشرة.

bryanhelmig هل الخادم الذي تحمّله

bryanhelmig هل رأيت التعليقات في طلب السحب المرتبط؟

على أي حال ، لا أفهم لماذا لا يعتبر ترميز نقل المحتوى مجرد علامة. لا داعي لحذف أي رؤوس (أو القيام بأي نوع آخر من الإمساك باليد) ، لم يكن الأمر يتعلق مطلقًا بأن يكون طول المحتوى صحيحًا أو خاطئًا ، والمشكلة الفعلية هي أن وجود محتوى شبه طول يعطل نقل المحتوى -الترميز الذي لا معنى له على الإطلاق. لكن مجرد جعل الطلبات تتجاهل طول المحتوى لا يحل المشكلة الحقيقية ، وهي أن الطلبات تستخدم ترميز نقل المحتوى عندما تشعر بذلك (يبدو أن هذا من المفترض أن يكون عند القراءة من منشئ) ، على الرغم من أن العديد من مواقع الويب الخوادم لا تدعمها حتى.

سيؤدي تجاهل Content-Length إلى إرباك الأشخاص الذين يقومون بتوفيره. إذا كنت (@ sigmavirus24) تصر على عدم نقلها ، فلماذا لا تطرح استثناء فقط؟ كما قلت ، ربما لا يتم استخدام هذه الوظيفة على نطاق واسع.

في طلب السحب ، قلت "حالة الاستخدام في المشكلة هي مجرد مثال على السلوك. لا يوجد مبرر هناك لسبب قيامك بما تفعله". لا أوافق ، أعتقد أن الكود الأصلي في هذه المشكلة هو سلوك طبيعي تمامًا ، وفي الحقيقة أعتقد أن تدفق بيانات POST يمثل حالة استخدام ضخمة ، وأنه من السخف أن يضطر المرء إلى استخدام ترميز نقل المحتوى أو اللجوء إلى مكتبات منخفضة المستوى عند دفق / استخدام المولدات.

للتلخيص: يجب أن يكون ترميز نقل المحتوى علامة ، ويجب أن تثير مجموعات المعلمات غير القانونية استثناءات ، ويجب إرسال العلامات التي يوفرها المستخدم عندما يكون ذلك ممكنًا. وبالطبع ، لا ينبغي أن يكون من الممكن شبه تعطيل ترميز نقل المحتوى.

توقف توقف توقف.

الجميع يأخذ أنفاسهم.

ysangkok يمكنك إجراء تحميلات متدفقة بدون مولدات على ما يرام. قم بتوفير كائن يشبه الملف في معلمة البيانات وسيعمل ذلك. نعم ، الأمر ليس بهذه البساطة مثل استخدام المولد ، لكن هذا جيد لأنه لا يزال غير صعب للغاية.

في غضون ذلك ، يجب ألا تشير الطلبات إلى أنها تقوم بتقطيع البيانات عندما لا تقوم بذلك. نحن جميعا متفقون على ذلك. السؤال هو ما يجب علينا فعله _ في حالتك المحددة_: تحديدًا ، توفير مولد و Content-Length . أنت و @ sigmavirus24 لا

ysangkok لقد قلت إن "تجاهل طول المحتوى سيؤدي إلى إرباك الأشخاص الذين @ sigmavirus24 أن تجاهل الوثائق الواضحة جدًا عند تزويده بمولد سوف يربك الأشخاص الذين يفعلون ذلك. كلاكما على حق.

(كملاحظة جانبية ، حقيقة أن العديد من الخوادم لا تفهم Transfer-Encoding هي مجرد تأكيد جامح يعتمد على عدم وجود دليل رأيته. حتى يتم تقديم أي دليل ، أختار تجاهله. )

بطريقة أو بأخرى ، علينا اختيار ما نفعله هنا. من المحتمل أن يكون القرار الصحيح هو طرح استثناء عند توفير كل من المولد و Content-Length . هذا قابل للتطبيق. حتى أنه لا يجعل قضيةbryanhelmig أسوأ ، لأنه يجب عليه فقط تمرير response.raw مباشرة بدلاً من تغليفها في ديكور (لصالحك ، Bryan ).

أنا أميل بطبيعة الحال إلى الجلوس على السياج هنا وإلقاء استثناء YoureACrazyPerson ، لكن يمكنني أن أرى لماذا يؤمن كل منكما بما تؤمن به. على وجه الخصوص ، فإن اتخاذ القرارات بناءً على الترويسات التي يوفرها المستخدم أمر ضعيف ومربك ، ويجب أن نحاول عدم القيام بذلك. ومع ذلك ، فإن الخطوط المتشددة هي التالية:

  1. لن يكون التحكم في Transfer-Encoding علامة. ليس الآن ولا أبدا. الطلبات لا تفعل أعلام حالة خاصة صغيرة مثل هذا.
  2. لا يمكننا فعل أي شيء.
  3. الطلبات ليست ملزمة بدعم جميع حالات الاستخدام. سأقوم بسعادة بإلقاء أي من حالتي الاستخدام تحت الحافلة إذا كان ذلك يجعل واجهة برمجة التطبيقات أفضل.

هل الخادم الذي تحمّله يطالبك بإرسال عنوان طول المحتوى؟

@ sigmavirus24 يفعل ذلك. :-( 411 لمحاولات بدون طول محتوى. IMO مزعج للغاية.

هل رأيت التعليقات في طلب السحب المرتبط؟

ysangkok فعلت.

يجب أن يكون مجرد استجابة عابرة ، ارسمها مباشرة بدلاً من لفها في الديكور

Lukasa كان هذا هو

شكرا لجميع الردود الشاملة. على الرغم من ذلك ، من الجيد حقًا أن يضطر المستخدمون في المواقف الغريبة إلى التبديل إلى lib مختلف بواحد من أصل 100 طلب. الحياة أسهل بكثير بالنسبة لـ 99 حالة أخرى.

على الرغم من ذلك ، من الجيد حقًا أن يضطر المستخدمون في المواقف الغريبة إلى التبديل إلى lib مختلف بواحد من أصل 100 طلب.

أفضّل الطلبات _جعل الأمور سهلة والأشياء الصعبة ممكنة_ :)

@ piotr-dobrogost متفق عليه ، ولكن إذا كان جعل الشيء الصعب ممكنًا يتطلب جعل الأمر السهل أكثر صعوبة ، فإننا نفضل ترك الأمر السهل سهلاً. =)

أشياء قليلة:

بقدر ما أشعر بالقلق ، كان هذا (وسيظل كذلك ، حتى نتخذ قرارًا) سلوكًا غير محدد

كملاحظة جانبية ، حقيقة أن العديد من الخوادم لا تفهم ترميز النقل هي مجرد تأكيد جامح يعتمد على عدم وجود دليل رأيته. حتى يتم تقديم أي دليل ، أختار تجاهله.

هناك فرق بين الخوادم التي لا تفهم ترميز النقل المقسم والخوادم التي لا تريد احترامها. أظن أن هذا هو الحال في قضية bryanhelmig . بدلاً من ذلك ، ربما تمت كتابة التطبيق من قبل شخص لا يفهم أو يعرف عن ترميز النقل وبالتالي يتطلب طول المحتوى.

من المحتمل أن يكون القرار الصحيح هو طرح استثناء عند توفير كلٍّ من المولد وطول المحتوى. هذا قابل للتطبيق.

قد تكون الاستثناءات شديدة للغاية في هذه الحالة. لا نرفع استثناءات بشكل عام لأي شيء بخلاف عناوين URL غير الصالحة. يمكن للطريقة التي نعالج بها معلمات البيانات والملفات أن تثير الاستثناءات ولكننا لا نقدم أي حالة خاصة هناك. ومع ذلك ، كنا نتحدث عن مدى ضعف الفكرة عندما يحدد المستخدمون طول المحتوى ورؤوس المضيف الخاصة بهم (من بين أمور أخرى ربما أنساها). نظرًا لأن هذه ليست ممارسات غير صالحة من الناحية الفنية ، ولكنها ممارسات ننصح بعدم اتباعها ، أقترح بدلاً من ذلك إطلاق تحذير ثم القيام بالشيء الصحيح في مواقف معينة موثقة جيدًا.

  • إذا تلقينا رأس مضيف ، فإننا نوجه تحذيرًا ولكن لا نحذفه.
  • إذا تلقينا كائنًا يمكننا تحديد حجمه وتم توفير رأس طول المحتوى ، فيجب أن نرفع تحذيرًا بأنه لا ينبغي تقديمه في مثل هذه الحالة. لست متأكدًا مما إذا كان يجب علينا تجاوز الإعداد الخاص بهم.
  • إذا تلقينا منشئًا ورأسًا بطول المحتوى ، فيجب أن نرفع تحذيرًا ونزيل الرأس.

يعد استخدام التحذيرات طريقة أكثر لطفًا لإعلام المستخدم بما يفعله وهو غير مستحسن. كما أنه يغطي حقيقة أن الكثير من مستخدمينا لا يزعجهم قراءة الوثائق وبالتالي تصبح المكتبة نوعًا ما تقوم بالتوثيق الذاتي. يمنحنا هذا أيضًا القدرة على تغيير السلوك في المستقبل. والأفضل من ذلك ، إذا أراد المستخدمون تعطيل التحذيرات ، يمكنهم ذلك لأن بيثون يوفر طريقة لإسكات التحذيرات.

هناك فرق بين الخوادم التي لا تفهم ترميز النقل المقسم والخوادم التي لا تريد احترامها. أظن أن هذا هو الحال في قضية bryanhelmig . بدلاً من ذلك ، ربما تمت كتابة التطبيق من قبل شخص لا يفهم أو يعرف عن ترميز النقل وبالتالي يتطلب طول المحتوى.

أعتقد في الواقع أن هذا هو الحال على الأرجح في معظم الأمثلة. نحن (zapier) نحاول تحميل الملفات إلى أكثر من عشرة واجهات برمجة تطبيقات مختلفة والقليل منها يتطلب مهلة طول المحتوى (يبدو أنه) مع ترميز النقل المقسم.

كملاحظة جانبية ، حقيقة أن العديد من الخوادم لا تفهم ترميز النقل هي مجرد تأكيد جامح يعتمد على عدم وجود دليل رأيته. حتى يتم تقديم أي دليل ، أختار تجاهله.

يمكنني تجميع مجموعة اختبار حول كيفية استجابة الخدمات المختلفة لطول المحتوى / ترميز النقل ، لكني أشعر أنه حتى لو تم تنفيذ واجهات برمجة التطبيقات بشكل غير صحيح ، فلا ينبغي أن ينصح بتصميم طلبات بيثون الأسهل من ذلك ، يمكنني فقط تسمية الأسماء بناءً على تجربتي في محاربة هذا الأسبوع الماضي ، ولكن مرة أخرى ، إذا كانت أخطاء API / الخادم ، فما فائدة هذه المعلومات لطلبات Python؟

أقترح أن نطلق تحذيرًا بدلاً من ذلك ثم نفعل الشيء الصحيح في مواقف معينة موثقة جيدًا.

اتفق على السلوك الافتراضي ، ولكن في بعض الأحيان يتفوق الواقع على "الشيء الصحيح" (خاصةً عندما لا يكون لديك سيطرة على خادم من المحتمل أن يكون معطلاً). قد يكون من الجيد توثيق تقنية لتجاوزها (حتى لو كانت تدعو المستخدم للقيام بالكثير من العمل ، مثل كتابة محول مخصص).

فقط للتوضيح: "بالشيء الصحيح" لا أعني بالضرورة الشيء RFC. (في حال كان هذا ما تعتقد أنني قصدته)

يمكنني تجميع مجموعة اختبار حول كيفية استجابة الخدمات المختلفة لطول المحتوى / ترميز النقل ، لكني أشعر أنه حتى لو تم تنفيذ واجهات برمجة التطبيقات بشكل غير صحيح ، فلا ينبغي أن ينصح بتصميم طلبات بيثون

أوافق على أن الخوادم التي تعمل بشكل غير صحيح يجب ألا تُعلم تصميمنا.

الأسهل من ذلك ، يمكنني فقط تسمية الأسماء بناءً على تجربتي في محاربة هذا الأسبوع الماضي ، ولكن مرة أخرى ، إذا كانت أخطاء API / الخادم ، فما فائدة هذه المعلومات لطلبات Python؟

نحن نعلم بالفعل مدى تعطل الويب. بغض النظر ، فإن معرفة ما إذا كان يجب علينا حذف الرأس بعد تحذير المستخدم أو تركه هو المكان الذي يمكن أن تكون فيه هذه البيانات مفيدة.

بليه. هذا يحزنني.

حسنًا ، أعتقد أن خطة @ sigmavirus24 هي الأفضل هنا ، على الأقل في الوقت الحالي.

هناك بعض الأشياء التي ظهرت تشغلني عن تزويدكم ببعض البيانات التفصيلية ، ولكن إليك تفريغ عقلي لما رأيته:

أكبر مخالف رأيته هو نقاط نهاية واجهة برمجة التطبيقات التي تتطلب تحميلًا متعدد الأجزاء: عادةً ما يحتاجون إلى طول المحتوى أو يفزعون من ترميز النقل المقسم (لست متأكدًا من ذلك). هذا ليس هو الحال في كل مكان ، ولكن إليك الأجزاء التي يمكنني سحبها من مصدرنا باستخدام grep سريع:

  1. يقوم Twitter بعمل متعدد الأجزاء ويفشل مع الترميز المقسم / بدون طول.
  2. يقوم Podio بعمل متعدد الأجزاء ويفشل مع الترميز المقسم / بدون طول.
  3. يعمل Facebook متعدد الأجزاء ويعمل مع الترميز المقسم / بدون طول.
  4. Salesforce متعدد الأجزاء ويعمل مع الترميز المقسم / بدون طول.

السبب في أن هذا جديد في ذهني هو أننا أنشأنا مولدًا مخصصًا متعدد الأجزاء للجسم يعمل مع ملفات "كسولة" يمكنها أيضًا حساب الطول ، وقد حدث هذا في ظهور الكثير من المشكلات التي نتحدث عنها هنا. في الوقت الحالي ، نحن نخدع فقط ونقوم بعمل getvalue() لنقاط نهاية التحميل الفاشلة. من المحتمل أن نعيد النظر فيه يومًا ما.

يصعب التعرف على الأمثلة الأخرى ، لكن شركات الملفات (Box ، Dropbox ، SugarSync ، إلخ ...) تحصل عليها جميعًا وتعمل بشكل جيد مع الترميز المقسم ، لذلك لا تقلق هناك.

نأمل أن يسلط هذا مزيدًا من الضوء على حالة الاستخدام في العالم الحقيقي. أتمنى أن أحصل على مزيد من المعلومات لتأكيد العناوين التي تتسبب عادةً في الأخطاء (عادةً ما تكون مهلة).

يجب أن يكون هذا أسهل للاختبار الآن لأن لدينا عددًا قليلاً من واجهات برمجة التطبيقات التي يمكننا إعادة إنتاجها ، وبالتحديد Twitter.

لدي نفس حالة الاستخدام بالضبط: تحميل البيانات إلى خادم لا يدعم التشفير المقسم. طول البيانات معروف مسبقًا ، لكنه لا يأتي من ملف (من مولد).
كنت أتوقع أن يؤدي تعيين رأس طول المحتوى إلى تعطيل حساب الطول في الطلبات ، وكذلك تعطيل تشفير النقل المقسم.
تمكنت الآن من التغلب على هذه المشكلة عن طريق إضافة سمة 'len' إضافية إلى كائن المولد الخاص بي ، بحيث تُعيد الطلبات uses.super_len () شيئًا ما بحيث لا يتم اختيار الترميز المقسم عن طريق الطلبات: هذا قبيح ورائع.
باعتباري مستخدم يونكس (ومثل piotr-dobrogost @) ، أتوقع أن يعرف المبرمج ما يفعله ، وينبغي للمكتبة أن تلتزم به: إن توفير عنوان بطول المحتوى هو مؤشر واضح على عدم استخدام الترميز المقسم. لكن هذا يتعارض مع الجملة أعلاه: "لا أتوقع أبدًا أن يؤدي تعيين رأس الصفحة إلى تغيير سلوك استخدام المولد بالرغم من ذلك". حسنًا ، إذا تم توثيقه بوضوح ، فلا أرى النقطة. لن يكسر API ، أليس كذلك؟

netheosgithub أود أن أزعم أن تغيير هذا السلوك يشكل تغييرًا في واجهة برمجة التطبيقات. ضع في اعتبارك الحالة الحالية لواجهة برمجة التطبيقات ، من الوثائق :

تدعم الطلبات أيضًا ترميز النقل المقسم للطلبات الصادرة والواردة. لإرسال طلب مشفر مقطعًا ، ما عليك سوى توفير مولد (أو أي مكرر بدون طول) لجسمك.

لاحظ أن الوثائق _ لا تقول _ "لإرسال طلب مشفر مقطعًا ، ما عليك سوى توفير مولد ولا تقم بتعيين رأس طول المحتوى." هذا يجعل هذا تغيير API في كتابي. ليس فقط أي تغيير في واجهة برمجة التطبيقات: إنه تغيير سيء. فكرة أن تعيين عنوان يغير جسم الطلب يجعلني أشعر بعدم الارتياح الشديد. ضع في اعتبارك ما إذا طلب منا شخص ما طلبًا مشابهًا حيث إذا قام بتعيين العنوان Content-Type: application/json ، يجب علينا تشفير المعلمة data بدلاً من ترميز النموذج! كنت أرمي هذا الطلب بسرعة.

أعتقد أننا يجب أن نحاول معالجة جذر المشكلة: لماذا يستخدم الناس المولدات في هذه الحالة؟

في الوقت الحالي ، يعد توفير منشئ وتحديد طول المحتوى خطأ (يؤدي إلى إنشاء طلب http غير صالح) ، لذلك لا ينبغي لأي شخص استخدام حالة الاستخدام هذه. لهذا السبب كنت أفكر أنه لن يكسر برامج المستخدم الخاص بك.
لماذا المولدات؟ لا يتم توفير البيانات دائمًا بواسطة ملف مثل الكائن. على سبيل المثال ، أرغب في مشاهدة تقدم التحميل من خلال الحصول على بيانات مقسمة إلى أجزاء ، وما إلى ذلك (وإلا فسيتعين عليّ تجاوز طرق read ())

إذا كانت وسيطة "طلب HTTP غير الصالحة" فقط صالحة. أتمنى لو عشت في هذا العالم. ومع ذلك ، فإن العديد من الخوادم ستقبل بالتأكيد بسعادة مثل هذه المجموعة.

يوفر RFC 2616 هذا القسم المضيء حول تحديد طول الرسالة:

طول نقل الرسالة هو طول نص الرسالة كما يظهر في الرسالة ؛ أي بعد تطبيق أي ترميز نقل. عندما يتم تضمين نص رسالة مع رسالة ، يتم تحديد طول النقل لهذا النص من خلال أحد الإجراءات التالية (بترتيب الأسبقية):

  1. يتم دائمًا إنهاء أي رسالة رد "يجب ألا" تتضمن نص الرسالة (مثل الردود 1xx و 204 و 304 وأي رد على طلب HEAD) بواسطة أول سطر فارغ بعد حقول الرأس ، بغض النظر عن الكيان- حقول رأس موجودة في الرسالة.
  2. إذا كان حقل رأس ترميز النقل (ترميز النقل) موجودًا وله أي قيمة بخلاف "الهوية" ، فسيتم تحديد طول النقل باستخدام ترميز النقل "المقسم" (ترميز النقل) ، ما لم تكن الرسالة تم إنهاؤه بإغلاق الاتصال.
  3. في حالة وجود حقل عنوان طول المحتوى (طول المحتوى) ، فإن قيمته العشرية في OCTETs تمثل كلاً من طول الكيان وطول النقل. يجب عدم إرسال حقل رأسية طول المحتوى إذا كان هذان الطولان مختلفين (على سبيل المثال ، في حالة وجود حقل رأسية ترميز النقل). إذا تم استلام رسالة مع كل من حقل رأس ترميز النقل وحقل رأس طول المحتوى ، فيجب تجاهل الأخير.
    ...
    يجب ألا تتضمن الرسائل كلاً من حقل عنوان طول المحتوى وتشفير نقل بدون هوية. إذا اشتملت الرسالة على ترميز نقل بدون هوية ، فيجب تجاهل طول المحتوى.

للتوضيح: نحن متفقون على أن الطلبات تفعل الشيء الخطأ. ومع ذلك ، نحن _ لا _ متفقون على أن تعيين رأس Content-Length يجب أن يوقف التقسيم. هذا هو الشيء الذي يسبب الخلاف.

يجب أن أشير أيضًا إلى أن أي خادم يفشل في الترميز المقسم يعد أيضًا انتهاكًا لـ RFC 2616:

يجب أن تكون جميع تطبيقات HTTP / 1.1 قادرة على تلقي وفك تشفير ترميز النقل "المقسم" ، ويجب أن تتجاهل امتدادات الامتدادات المقطوعة التي لا يفهمونها.

هذه ليست محاولة للقول إن الطلبات لا ينبغي أن تحل هذه المشكلة ، إنها ببساطة للإشارة إلى أن جميع المعنيين ينتهكون RFC 2616.

أعتقد أننا يجب أن نحاول معالجة جذر المشكلة: لماذا يستخدم الناس المولدات في هذه الحالة؟

كانت إحدى حالات الاستخدام الخاصة بنا هي أخذ ملف كبير جدًا بحجم معرفة من خدمة واحدة وتحميله إلى خدمة ثانية ، باستثناء أن البيانات الثنائية كانت مجرد جزء من أجزاء POST متعددة الأجزاء (مع بيانات تعريف JSON مثل الأخرى) الخدمة الثانية المطلوبة.

لذلك قمنا بلف المُنشئ متعدد الأجزاء في مولد ؛ واحد يمكننا معرفة حجمه. بدا الأمر وكأنه حل واضح ومفيد للغاية ، على الأقل حتى أوقفتنا ميزة الطلبات / الخطأ هذه (أعتقد أننا نحتفظ بشوكة الآن ، لكن خدعة superlen قد تساعد في التراجع عن ذلك!).

لدينا الآن حالات استخدام أخرى للمولدات ذات العدسات ، لكن عليّ اكتشافها.

بريان

في 18 فبراير 2014 ، الساعة 12:45 صباحًا ، كتب Cory Benfield [email protected] :

أعتقد أننا يجب أن نحاول معالجة جذر المشكلة: لماذا يستخدم الناس المولدات في هذه الحالة؟

@ bryanhelmig مم ، نعم. أعتقد أن الحل الصحيح موجود من الآن فصاعدًا لاستخدام المشفر متعدد الأجزاء المتدفق في حزام أدوات الطلبات .

في الوقت الحالي ، ليس من الواضح بالنسبة لي سبب وجود مثل هذه المقاومة القوية لاستخدام كائنات تشبه الملفات هنا.

في الإصدار رقم 1895 ، واجهت هذه المشكلة حتى مع كائن يشبه الملف. كان الطلب عبارة عن PUT ، وكان الكائن الذي يشبه الملف هو sys.stdin ، وهو في الواقع شبيه بالملف لدرجة أنه أطلق تشفيرًا مقسمًا كما هو الحال مع ملف عادي. أدى توفير طول المحتوى (الذي تعلمته عبر الوسائل الخارجية) في هذه الحالة إلى عدم قيام HTTPAdapter.send على القيام بالتقطيع كما قد يتوقع المرء ، ولكن لا يزال PreparedRequest.prepare_body يلاحظ أن الكائن الذي يشبه الملف قابل للتكرار وأضف رأس ترميز النقل على أي حال. اختنق الخادم (Amazon S3) بهذا العنوان على الرغم من أن الطلب كان سيعمل بخلاف ذلك.

تضمين التغريدة sys.stdin هو _not_ ملف مثل أنه أدى إلى تشغيل الترميز المقسم. إذا قمت بتمرير كائن ملف إلينا ، فسنقوم بدفقه ، وليس قطعه. مشكلة sys.stdin هي أنه ليس له طول ، والذي يندرج تحت الوصف المقدم في المستندات كما هو مذكور أعلاه:

أي مكرر بدون طول

التوقع العام هو أن المستخدمين لن يقوموا أبدًا بتعيين رأس طول المحتوى بشكل صريح. هذا لأن الطلبات قد ينتهي بها الأمر إلى العبث بكيفية إعداد نص الطلب.

أخيرًا ، الفكرة القائلة بأن "قد يتوقع المرء" أن يؤدي توفير عنوان بطول المحتوى إلى تعطيل التقسيم ليس صحيحًا أيضًا: أتوقع منا إزالة الرأس. =)

على أي حال ، استمر هذا النقاش لفترة طويلة بما فيه الكفاية. عند تزويدنا بمكرر طويل ورأس طول المحتوى الذي يوفره المستخدم ، فلدينا هذه الخيارات:

  1. رفع استثناء.
  2. تفجير رأس طول المحتوى
  3. لا تقسم.

أي من هذه الخيارات الثلاثة يجعلنا نمتثل لـ RFC. من الواضح أن كل من يثير هذه القضية يفضل (3). @ sigmavirus24؟

لطالما كنت مع الرأي القائل بأننا يجب أن نفجر الرأس. من واقع خبرتي ، يعتقد المستخدم أنه يعرف ما يفعله ولكن نادرًا ما يعرف ما يكفي لمعرفة ما يفعله بالفعل. أحب عمومًا السماح للمستخدم بإطلاق النار على قدمه وقد فعلنا ذلك حتى الآن ولم يفلحنا كثيرًا. في الواقع ، لقد أتيحت لنا الفرصة لإبعاد هذا العنوان من قبل ولم نفعل ذلك على وجه التحديد. لقد أدى ذلك بالضبط إلى هذه المشكلة. لنكن منطقيين بشأن هذا:

حافظت واجهة برمجة التطبيقات (API) والوثائق الخاصة بنا دائمًا على أن تمرير متكرر بدون أي طريقة لقياس الطول يعني أننا سنستخدم تشفير النقل المقسم. وفقًا للمواصفات في هذه الحالة ، يجب أن نفجر رأس Content-Length لأن بعض الخوادم لا تلتزم به. يجب تجاهله. نحن لا نرتكب أي خطأ بإرساله بغض النظر عن الترميز ، الخادم يقوم بعمل خاطئ من خلال عدم تجاهله. للحماية من الخوادم السيئة مثل هذه _يجب أن نحذفها.

إذا قررنا في الإصدار 3.0.0 تغيير واجهة برمجة التطبيقات بحيث لا يؤدي توفير الرأس إلى تقسيم هذا الأمر إلى وحش مختلف. لكننا لا نفكر في الإصدار 3.0.0 وهذه هي المشكلة الحقيقية هنا. ماذا يمكننا أن نفعل لحل هذه المشكلة الآن. ما يمكننا القيام به هو اتباع المواصفات.

أنا أميل إلى الاتفاق معك @ sigmavirus24. سأرى ما إذا كان بإمكاني الحصول على بضع دقائق مع كينيث في وقت ما قريبًا للتحدث معه حول هذا الأمر.

أنا في نفس المركب مثل bryanhelmig و @ netheosgithub ، لدي منشئ حيث أعرف مسبقًا الحجم الذي ستكون عليه القطع المدمجة ، ولدي خادم لا يدعم التحميلات المقسمة (تطبيق WSGI ، WSGI وفقًا لي البحث لا يدعم الترميز المقسم على الإطلاق). البيانات من المولد أكبر من أن تتناسب مع ذاكرة الوصول العشوائي ، لذا فإن دمج الأجزاء السابقة وتمريرها إلى الطلبات أمر غير وارد.
هل حدث أي تطور جديد بخصوص هذه القضية؟

jbaiter إذن ما تحتاجه هو تمرير كائن يتصرف مثل ملف مع تعريف __len__ . خذ على سبيل المثال MultipartEncoder الخاص بحزام الأدوات . أنت تريد البث ، لذلك بشكل عام ، كل ما تحتاجه هو شيء من هذا القبيل له طريقة read وطريقة لتحديد طوله (يفضل أن يكون من خلال تنفيذ __len__ ). يمكن أن تبدو واجهة برمجة التطبيقات الخاصة بكائنك مشابهًا لما يلي:

class JBaiterStreamer(object):
    def __init__(self, size, iterator):
        self.size = size
        self.iterator = iterator

    def __len__(self):
        return self.size

    def read(self, *args):
        try:
            return next(self.iterator)
        except StopIteration:
            return b''

من المؤكد أنني لم أحاول معرفة ما إذا كان هذا الرمز سيعمل ، ولكن يجب أن يكون هناك شيء من هذا القبيل.

شكرًا @ sigmavirus24 : +1: ، لقد فعلت ذلك بالضبط ، كنت أتساءل فقط عما إذا كان هناك الآن طريقة أكثر أناقة للقيام بذلك

ربما هناك طريقة جيدة لتوفير ذلك عبر حزام الأدوات ، ehLukasa ؟

@ sigmavirus24 بالتأكيد. =)

@ sigmavirus24 "نحن لا نقوم بأي خطأ بإرساله بغض النظر عن الترميز ، الخادم يقوم بعمل خطأ بعدم تجاهله." هو في الواقع خطأ الآن:

RFC 7230: "يجب ألا يرسل المرسل حقل رأس طول المحتوى في أي رسالة تحتوي على حقل رأس ترميز النقل."

ztane ، هذه حالة مختلفة عما يشير إليه سؤال تجاوز سعة المكدس. في المستقبل ، يرجى قراءة مناقشة عن كثب قبل إنشاء إشعارات لجميع المشاركين فيها.

@ sigmavirus24 الذي نتج عن "ترميز النقل: مقطع ، مجموعة طول المحتوى => الجسم غير مقسم." ويمنع RFC 7230

ztane أشكرك على الاستمرار في عدم قراءة هذه المناقشة. يدور هذا الخطأ المحدد حول قيام شخص ما بتعيين رأس طول المحتوى يدويًا وتوقع الطلبات بعدم استخدام تحميل مقسم عندما يوفرون منشئًا (والذي يؤدي دائمًا إلى تحميل مقسم بالطلبات). رابط stackoverflow الذي قدمته وما تتحدث عنه هو خطأ مختلف يتم التعامل معه في مكان آخر. أنت تشوش هذه المناقشة بتفاصيل غير ذات صلة لأننا نسمح للمستخدمين بإطلاق النار على أقدامهم. على سبيل المثال ، نسمح للمستخدمين بتحديد عنوان غير صحيح بطول المحتوى أو رأس مضيف غير صالح ، أو ما يشاؤون بشكل أساسي. نحن لا نراقب كل شيء ولن نفعل ذلك. يرجى تركيز محادثتك على _المشكلة الصحيحة_ في المستقبل.

لقد تعثرت في هذا عند استخدام boto3 لإجراء PUT من دفق ضد AWS S3 (https://github.com/boto/botocore/issues/911). في حالتي ، حجم الدفق معروف - إنه كائن من مزود آخر. عند استخدام توقيعات الإصدار 2 من S3 ، لا يتم دعم "ترميز النقل: مقسم" وتقوم S3 بإرجاع الخطأ 501. يبدو أن وثائق boto3 تشير ضمنيًا إلى أن إعداد طول المحتوى سيتغلب على هذه المشكلة ، حيث تنص على ما يلي:

ContentLength (integer) -- Size of the body in bytes. This parameter is useful when
the size of the body cannot be determined automatically. [1]

بينما يجب أن يقوم boto3 بأمرين - إصلاح وثائقه والتأكد من عدم تعيين "Transfer-Encoding: chunked" - بالنسبة للمستهلك غير المباشر للطلبات ، يصعب تصحيح هذا السلوك. إذا تم تجاهل عنوان Content-Length ، فإن رفع استثناء سيجعل من الواضح على الأقل ما يجري. كما هو ، تم تشويشها من خلال حقيقة أن S3 لا تُرجع إلا خطأ 501 مع توضيح أن أحد الرؤوس غير مدعوم (ولكن لا يتم تحديد العنوان).

الحل المقترح لفه وتقديم الطول يعمل ، لكن يبدو قبيحًا. هل توسيع واجهة برمجة التطبيقات للسماح بتبديل الترميز المقسم (مع الحفاظ على السلوك الحالي كإعداد افتراضي) سيكون طريقة مستساغة للأمام (بدلاً من استخدام رأس طول المحتوى كعلامة)؟

[1] http://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.put_object

timuralp ما زلت أعارض إضافة علم لهذا. من غير المقبول حقًا أن يكون لديك تطبيق HTTP / 1.1 لا يمكنه التعامل مع ترميز النقل المقسم في عام 2016. لقد كان أحد متطلبات المواصفات لفترة طويلة لدرجة أن المواصفات الأولى التي تطلبها قديمة بما يكفي للتصويت في الولايات المتحدة الأمريكية: لا أعتقد أنه يمكننا الاستمرار في قطع الكيانات عن الركود لعدم القيام بذلك.

من وجهة نظري ، يظل الخطأ هنا هو أننا قد نصدر بشكل غير صحيح كلاً من Content-Length و Transfer-Encoding. بالطبع ، وجهة نظري غير ملزمة. ؛)

علاوة على ذلك ، إذا أراد boto3 تجاوز رأس Content-Length بالقوة ، فيجب عليهم استخدام تدفق الطلب المُعد حتى يتمكنوا من إزالة رأس Transfer-Encoding .

Lukasa @ sigmavirus24 عادل بما فيه الكفاية - شكرًا على الرد السريع. سأستمر في البحث لإصلاح مشكلة boto في هذا المشروع.

الطريقة التي استطعت بها التغلب على عدم القدرة على التحكم في ما إذا كان شيء ما مقطوعًا هي التحكم في ما إذا كان POST الخاص بي يستخدم "البيانات" أو جزء "الملفات" من استدعاء الطريقة. ويبدو أن تعمل بشكل جيد.

    req = Request('POST',url='http://apiendpointurl',
        headers=headers,
        data=fs)
    prepped = req.prepare()

    if 'Transfer-Encoding' in prepped.headers and prepped.headers['Transfer-Encoding'] == 'chunked':
        res=postAsFiles(headers, fs)
    else:
        res=postAsData(headers,fs)

حيث يكون الاختلاف الوحيد بين postAsFiles و postAsData هو:

def postAsData(headers, fs):
    return requests.post(
        url='http://apiendpointurl',
        headers=headers,
        data=fs)

def postAsFiles(headers, fs):
    return requests.post(
        url='http://apiendpointurl',
        headers=headers,
        files=fs)

مع # 3897 ، ستطلق الطلبات 3.0.0 استثناءً في هذه الحالة يمنع إرسال كلا الرأسين.

سيتمكن المستخدمون الذين يرغبون في إرسال عنوان Content-Length الخاص بهم من تعديل الرؤوس باستخدام تدفق PreparedRequests . لاحظ أن Content-Length لن يعمل مع البيانات التي تم تمريرها كمنشئ. إذا كان يجب على المستخدم تحديد طول المحتوى ، فستحتاج المولدات إلى استهلاكها وتمريرها في شيء مع تمثيل سلسلة / ملف. في معظم الحالات ، يجب ترك هذه الرؤوس بمفردها للمعالجة التلقائية للطلبات.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات