Gunicorn: Bug lama direproduksi: Objek 'Response' tidak memiliki atribut 'status_code' di wsgi.py dengan soket web

Dibuat pada 9 Agu 2018  ·  33Komentar  ·  Sumber: benoitc/gunicorn

Seperti yang dikatakan masalah lama 1210 ini, gunicorn mencatat kesalahan saat klien terputus, dan lingkungan saya:

  • Debian GNU/Linux 7.8

  • nginx

  • Python3.4

  • gunicorn(19.8.1) (dengan satu atau beberapa pekerja)

  • Flask-SocketIO, klien menentukan transportasi websocket

Semuanya berfungsi dengan baik termasuk klien, kecuali untuk log kesalahan ini, dua instance produksi independen cloud, keduanya terus-menerus mencatat, tetapi saya tidak dapat mereproduksinya di mesin pengembangan saya, yang merupakan mac.

Terima kasih banyak atas bantuan Anda.

Kesalahan saat menangani permintaan /socket.io/?EIO=3&transport=websocket
Traceback (panggilan terakhir terakhir):
File "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/workers/async.py", baris 56, di handle
self.handle_request(listener_name, req, client, addr)
File "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/workers/async.py", baris 116, di handle_request
resp.close()
File "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", baris 409, di dekat
self.send_headers()
File "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", baris 325, di send_headers
tosend = self.default_headers()
File "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", baris 306, di default_headers
elif self.should_close():
File "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", baris 229, di should_close
jika self.status_code < 200 atau self.status_code di (204, 304):
AttributeError: objek 'Response' tidak memiliki atribut 'status_code'

Feedback Requested unconfirmed ThirdPartFlask

Komentar yang paling membantu

Bump @benoitc

Semua 33 komentar

apakah Anda punya contoh sederhana untuk mereproduksinya? Coba juga dengan master terbaru jika memungkinkan.

Sebelumnya saya telah mencoba beberapa kali di lingkungan pengembangan lokal saya, yang merupakan kode aplikasi yang sama dengan lingkungan produksi, tetapi saya tidak dapat mereproduksinya.

Dan saya telah memeriksa log rilis versi 19.9.0,tidak menemukan sesuatu yang terkait,Saya akan menyimpannya
menonton log kesalahan ini, jika menemukan sesuatu yang baru, saya akan memposting di sini.

Saya juga memiliki masalah ini, khususnya ketika saya memaksa semua koneksi saya dari klien ke protokol websocket. Pengaturan saya sama dengan BoWuGit. Jika izinkan protokol polling sebelum memutakhirkan, ini tidak muncul, tetapi kesalahan lain:
`
[ERROR] Kesalahan menangani permintaan /socket.io/?EIO=3&transport=polling&t=MPRHUoV&sid=cd64be7c940e474d8728b114c3fb9bbe

Traceback (panggilan terakhir terakhir):
File "/usr/local/lib/python3.6/site-packages/gunicorn/workers/async.py", baris 56, di handle
self.handle_request(listener_name, req, client, addr)

File "/usr/local/lib/python3.6/site-packages/gunicorn/workers/async.py", baris 107, di handle_request
respiter = self.wsgi(environ, resp.start_response)

File "/usr/local/lib/python3.6/site-packages/flask/app.py", baris 1994, di __call__
kembalikan self.wsgi_app(environ, start_response)

File "/usr/local/lib/python3.6/site-packages/flask_socketio/__init__.py", baris 43, di __call__
start_response)

File "/usr/local/lib/python3.6/site-packages/engineio/middleware.
py", baris 47, di __call__
kembalikan self.engineio_app.handle_request(environ, start_response)

File "/usr/local/lib/python3.6/site-packages/socketio/server.py", baris 360, di handle_request
kembalikan self.eio.handle_request(lingkungan, start_response)

File "/usr/local/lib/python3.6/site-packages/engineio/server.py", baris 279, di handle_request
socket = self._get_socket(sid)

File "/usr/local/lib/python3.6/site-packages/engineio/server.py", baris 439, di _get_socket
menaikkan KeyError('Sesi terputus')
`
Tapi saya curiga mungkin ada hubungannya satu sama lain, karena saya memaksa koneksi menjadi websocket, kesalahan ini tidak terlihat lagi.

Mengalami masalah ini juga dengan gunicorn 19.9.0 dan Flask-socketIO 3.0.2, saat menggunakan eventlet 0.24.1

AttributeError: objek 'Response' tidak memiliki atribut 'status_code'

Juga mengalami masalah ini dengan persyaratan berikut:

Flask==1.0.2
gunicorn==19.5.0
python-socketio==2.0.0
eventlet==0.24.1

Pesan kesalahan saat menutup browser web yang memiliki koneksi soket terbuka:

 Error handling request /socket.io/?EIO=3&transport=websocket&sid=d43ec0ae0bb946debc51f1ca2e5b8a94
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/gunicorn/workers/async.py", line 52, in handle
    self.handle_request(listener_name, req, client, addr)
  File "/usr/lib/python2.7/dist-packages/gunicorn/workers/async.py", line 114, in handle_request
    resp.close()
  File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 403, in close
    self.send_headers()
  File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 319, in send_headers
    tosend = self.default_headers()
  File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 300, in default_headers
    elif self.should_close():
  File "/usr/lib/python2.7/dist-packages/gunicorn/http/wsgi.py", line 233, in should_close
    if self.status_code < 200 or self.status_code in (204, 304):
AttributeError: 'Response' object has no attribute 'status_code'

Sepertinya masalah ini telah diperbaiki di versi terbaru python-engineio ..

Telah diuji dengan python-engineio versi terbaru (2.3.2), masih tidak berfungsi.

Ada berita tentang masalah ini? Saya mendapatkan kesalahan yang sama saat menggunakan penjaga-python

Saya memiliki masalah yang sama

acara: 0.25.1
termos-socketio: 4.2.1
gunicorn: 19.9.0

image

image

bagaimana cara memperbanyaknya? cna Anda memberikan contoh sederhana?

saya juga tidak yakin bagaimana mereproduksinya, tetapi itu sering terjadi ketika saya me-refresh halaman di aplikasi gunicorn saya

Mengalami masalah yang sama, dan lingkungan saya sama dengan @eazow sementara gunicorn == 20.0.4.
Tampaknya masalah terjadi setelah saya menginstal penjaga untuk pelacakan bug.
Masalah dapat direproduksi oleh

  1. menyegarkan halaman (tidak membuka halaman baru)
  2. menutup halaman

Menariknya, membuka halaman baru tidak akan menghasilkan masalah. Tidak yakin mengapa. Terima kasih!

Saya memiliki masalah yang sama dengan @cowbonlin . Versi gunicorn yang sama juga.

Setelah menginstal penjaga, kami mendapatkan banyak kesalahan ini. Meskipun saya merasa sulit untuk mengatakan apakah ini selalu terjadi atau tidak - karena kami tidak melacak kesalahan sebelum penjaga.

Meskipun tampaknya tidak memengaruhi fungsionalitas sebenarnya dari server kami, ini hanyalah satu ton spam.

Kami mengalami hal yang sama. Sentry diinstal tetapi dinonaktifkan. Ada ide?

Masalah yang sama dengan penjaga terpasang.

Apakah Anda memiliki contoh yang mereproduksinya tanpa penjaga (dinonaktifkan atau tidak)?

Selain itu, alih-alih namespace saya secara manual menekan /api.

Selain itu, alih-alih namespace saya secara manual menekan /api.

apa artinya ? Apakah penjaga ini terkait?

Selain itu, alih-alih namespace saya secara manual menekan /api.

apa artinya ? Apakah penjaga ini terkait?

Tidak, ini terkait dengan ruang nama socket.io. Saya mencoba menghapusnya, dan bahkan setelah menghapusnya, saya mendapatkan kesalahan. Saya mendapatkan kesalahan lain ini di mesin lokal tanpa gunicorn atau nginx, yang mungkin terkait.

Ini adalah persyaratan saya:

sentry_sdk == 0.14.3
Flask_SocketIO == 4.2.1
eventlet == 0.25.1

Ini adalah kode flask-socketio saya di sisi server:

socketio = SocketIO(engineio_logger=True, logger=True, debug=True, cors_allowed_origins="*", path='/socket.io')
...
socketio.init_app(app, async_mode="eventlet")

Dan ini adalah kode io soket React saya di sisi klien:

          this.socket = io.connect(`http://localhost:5000?info=${someInfo}`, {
            transports: ['websocket', 'polling'] // an attempt to keep polling as a fallback but start on websockets
          });

Beri tahu saya jika ini membantu. Di Ubuntu kesalahannya terlihat seperti di atas, dan secara lokal di Windows terlihat seperti ini:
```Traceback (panggilan terakhir terakhir):
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 599, di handle_one_response
tulis (b'')
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 491, di tulis
naikkan AssertionError("tulis() sebelum start_response()")
AssertionError: tulis() sebelum start_response()

Selama penanganan pengecualian di atas, pengecualian lain terjadi:

Traceback (panggilan terakhir terakhir):
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 357, di __init__
menangani sendiri()
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 390, di pegangan
self.handle_one_request()
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 466, di handle_one_request
self.handle_one_response()
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 609, di handle_one_response
tulis(err_body)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 538, di tulis
wfile.flush()
File "C:\ProgramData\Anaconda3\lib\socket.py", baris 607, di tulis
kembalikan diri._sock.send(b)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", baris 397, di kirim
kembalikan self._send_loop(self.fd.send, data, flags)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", baris 384, di _send_loop
kembalikan send_method(data, *args)
ConnectionAbortedError: [WinError 10053] Koneksi yang dibuat dibatalkan oleh perangkat lunak di mesin host Anda

Selama penanganan pengecualian di atas, pengecualian lain terjadi:

Traceback (panggilan terakhir terakhir):
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\hubs\hub.py", baris 461, di fire_timers
pengatur waktu()
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\hubs\timer.py", baris 59, di __call__
cb( args, * kw)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\semaphore.py", baris 147, di _do_acquire
pelayan.switch()
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenthread.py", baris 221, di main
hasil = fungsi ( args, * kwargs)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 818, di process_request
proto.__init__(conn_state, self)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 359, di __init__
mandiri.selesai()
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", baris 732, selesai
BaseHTTPServer.BaseHTTPRequestHandler.finish(mandiri)
File "C:\ProgramData\Anaconda3\lib\socketserver.py", baris 784, selesai
diri.wfile.close()
File "C:\ProgramData\Anaconda3\lib\socket.py", baris 607, di tulis
kembalikan diri._sock.send(b)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", baris 397, di kirim
kembalikan self._send_loop(self.fd.send, data, flags)
File "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", baris 384, di _send_loop
kembalikan send_method(data, *args)
ConnectionAbortedError: [WinError 10053] Koneksi yang dibuat dibatalkan oleh perangkat lunak di mesin host Anda```

Dapat mengkonfirmasi kesalahan ini hilang ketika penjaga sepenuhnya dinonaktifkan. Akan lebih bagus jika gunicorn cukup kuat untuk menghadapi ini.

Bump @benoitc

Dapat mengkonfirmasi kesalahan ini hilang ketika penjaga sepenuhnya dinonaktifkan. Akan lebih bagus jika gunicorn cukup kuat untuk menghadapi ini.

Saya menemukan bahwa menonaktifkan FlaskIntegration Sentry juga membuat kesalahan hilang.

Melihat perilaku serupa. Menggunakan New Relic dalam produksi menyebabkan kesalahan ini dengan flask-socketio. Dalam pengembangan, middleware debugger werkzeug perlu dimuat sebelum flask-socketio diinisialisasi (jadi ini tidak diterapkan ke aplikasi wsgi engineio). Masalahnya adalah produksi adalah tempat saya benar-benar tidak ingin kesalahan tersandung.

Tidak dapat mengganti respons di post_request konfigurasi gunicorn, tetapi saya mencoba memaksakan kode status ke resp.status_code. Itu tidak memakan waktu.

Kesalahan ini dapat direproduksi dengan menggunakan Sentry's FlaskIntegration bersama dengan Gunicorn dan Flask-SocketIO. Apakah mungkin untuk segera menyelesaikannya?

@Canicio kami berpikir untuk mencobanya untuk menghilangkan kesalahan dan bahkan setelah menonaktifkan integrasi, kesalahan tetap ada.

Adakah yang punya kode yang dapat dibagikan/contoh minimal untuk @benoitc untuk dimatikan?

Tentu:

import sentry_sdk
from flask import Flask
from flask_socketio import SocketIO
from sentry_sdk.integrations.flask import FlaskIntegration

sentry_sdk.init(
    dsn="https://[email protected]/0",
    integrations=[FlaskIntegration()]
)

app = Flask(__name__)
socketio = SocketIO(app)

@app.route('/')
def index():
    return '''
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
<script>
    var socket = io()
</script>

persyaratan:

flask
sentry-sdk[flask]
flask-socketio
eventlet

contoh konfigurasi gunicorn:

bind = '[::]:4444'
worker_class = 'eventlet'
accesslog = '-'

Saat memuat / itu akan terhubung ke soket web. Pada websocket disconnect (mis. navigasi pergi, segarkan), akan menghasilkan pengecualian seperti ini:

[2020-09-23 07:24:49 +0000] [16303] [ERROR] Error handling request /socket.io/?EIO=3&transport=websocket&sid=29f4c1adfac343d6bc6db56acf8fd0ee
Traceback (most recent call last):
  File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/workers/base_async.py", line 55, in handle
    self.handle_request(listener_name, req, client, addr)
  File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/workers/base_async.py", line 115, in handle_request
    resp.close()
  File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 402, in close
    self.send_headers()
  File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 318, in send_headers
    tosend = self.default_headers()
  File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 299, in default_headers
    elif self.should_close():
  File "/home/ziddey/projects/sentry/venv_sentry/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 219, in should_close
    if self.status_code < 200 or self.status_code in (204, 304):
AttributeError: 'Response' object has no attribute 'status_code'
2001:470:1f07:7eb:9dd4:254c:35d7:236c - - [23/Sep/2020:07:24:49 +0000] "GET /socket.io/?EIO=3&transport=websocket&sid=29f4c1adfac343d6bc6db56acf8fd0ee HTTP/1.1" 500 0 "-" "-"

Catatan: Saya sendiri tidak pernah benar-benar menggunakan penjaga. Ini hanya dari halaman memulai penjaga. Contoh dsn berfungsi dengan baik untuk pengujian kami.

Mengomentari integrations=[FlaskIntegration()] kemudian akan menghilangkan kesalahan (tentu saja secara efektif menonaktifkan penjaga).

Untuk apa nilainya, gevent-websocket dapat digunakan sebagai pengganti eventlet tanpa kesalahan. Namun, tampaknya menangani semua permintaan..

Ok, melakukan beberapa bermain-main. Sepertinya penjaga/newrelic membungkus tanggapan. Tanpa penjaga, kami mendapatkan <eventlet.wsgi._AlreadyHandled object at 0x7fd0f5b1c0d0> seperti yang diharapkan dan EventletWorker.is_already_handled() gunicorn akan menghentikan iterasi.

Namun, saat menggunakan penjaga, ini menjadi seperti <sentry_sdk.integrations.wsgi._ScopedResponse object at 0x7f30155a5100> sebagai gantinya, gagal dalam pemeriksaan

Sebaliknya, kita bisa mengintip respiter untuk melihat apakah itu kosong. Akan melihat lebih jauh besok.

Baiklah, inilah solusi yang saya buat:

eventlet_fix.py:
lihat edit di bawah

Dan di gunicorn config.py saya: worker_class = 'eventlet_fix.EventletWorker .

Masalahnya adalah bahwa penjaga/newrelic membungkus tanggapan, jadi kami tidak bisa begitu saja memeriksanya dengan ALREADY_HANDLED eventlet. Karena sifat dari permintaan yang sudah ditangani adalah bahwa start_response gunicorn tidak dipanggil, kita dapat memeriksa keberadaan status respons.

Jadi saya telah membajak panggilan wsgi untuk kemudian memeriksa status respons, dan meretas nilai respons seperlunya. Ini memungkinkan permintaan untuk tetap dicatat oleh gunicorn. Jika sebaliknya, diinginkan untuk mempertahankan perilaku asli, StopIteration dapat dinaikkan sebagai gantinya.

Meretas status ke 101 sesuai untuk kasus penggunaan kami di sini (websocket flask-socketio), tetapi sebaliknya, membiarkannya sebagai Tidak ada juga berfungsi karena headers_sent dan should_close dipaksa ke True.

Sekali lagi, ini membuat asumsi bahwa jika status tidak disetel, start_response tidak dipanggil, dan oleh karena itu permintaan harus "sudah ditangani" secara eksternal.

edit: Tidak bagus. Akan perlu untuk mengevaluasi kembali. Jika permintaan membutuhkan waktu untuk dijalankan, start_response tidak akan dipanggil sebelum resp.status diperiksa.

edit2: Ini adalah versi tetap dengan iterator respons yang diretas:

from functools import wraps

from gunicorn.workers.geventlet import EventletWorker as _EventletWorker


class HackedResponse:
    def __init__(self, respiter, resp):
        self.respiter = iter(respiter)
        self.resp = resp
        if hasattr(respiter, "close"):
            self.close = respiter.close

    def __iter__(self):
        return self

    def __next__(self):
        try:
            return next(self.respiter)
        except StopIteration:
            if not self.resp.status:
                self.resp.status = "101"  # logger derives status code from status instead of using status_code
                self.resp.status_code = 101  # not actually needed since headers_sent/force_close result in status_code not being checked anymore
                self.resp.headers_sent = True
                self.resp.force_close()
            raise


def wsgi_decorator(wsgi):
    @wraps(wsgi)
    def wrapper(environ, start_response):
        respiter = wsgi(environ, start_response)
        resp = start_response.__self__
        return HackedResponse(respiter, resp)

    return wrapper


class EventletWorker(_EventletWorker):
    def load_wsgi(self):
        super().load_wsgi()
        self.wsgi = wsgi_decorator(self.wsgi)

Jelas ini hanya tambalan monyet. Perbaikan sebenarnya berpotensi masuk handle_request di base_async.py. Kuncinya mungkin untuk (secara tidak langsung) memeriksa apakah start_response dipanggil setelah mengulangi respiter , baik dengan memeriksa resp.status (hanya start_response yang dipanggil) atau resp.headers_sent (konfirmasi bahwa kami benar-benar telah menanggapi permintaan tersebut).

@benoitc
@ziddey telah menemukan cara untuk memecahkan masalah.

@ziddey pertanyaan cepat untuk contoh Anda (karena saya tidak menggunakan penjaga).

  • Apakah kesalahan hanya berdampak pada penjaga atau permintaan juga dihentikan, yaitu pekerja berhenti (yang saya duga terjadi jika respons dibungkus)?
  • apakah Anda berharap ada sesuatu yang dibungkus di sana atau membersihkan permintaan bahkan jika responsnya dibungkus akan baik-baik saja?

@benoitc tidak dapat menguji saat ini, tetapi melihat traceback di atas https://github.com/benoitc/gunicorn/issues/1852#issuecomment -697189261 dan https://github.com/benoitc/gunicorn/blob/4ae2a05c37b332773997f90ba7542713b9bf8274/ gunicorn/workers/base_async.py#L107 -L140

Biasanya, is_already_handled akan mengembalikan True dan hanya akan berakhir di sini.

Namun, karena responsnya terbungkus, metode itu tidak berfungsi. Sebaliknya, eksekusi berlangsung, gagal pada baris 115: resp.close() mencoba mengirim header, tetapi start_response tidak pernah dipanggil, jadi tidak ada kode status. Bahkan jika itu terjadi, pada akhirnya tetap saja gagal.

Ini menghasilkan AttributeError yang dinaikkan kembali dan dianggap ditangani oleh handle_error . Karena permintaan sudah ditangani secara eksternal, tidak ada salahnya di sini selain mencatat spam.

Saya tidak bisa mengatakan terlalu banyak tentang Sentry-- Saya juga tidak menggunakannya.

Namun satu detail: mekanisme yang sudah ditangani saat ini tidak menghasilkan pencatatan akses. Saya kira ini secara teknis masuk akal karena tidak ada cara untuk mengetahui bagaimana itu ditangani secara eksternal. Dalam respons saya yang diretas, saya memaksa kode status ke 101, dengan headers_sent sebagai True sehingga pawang dapat melanjutkan dan permintaan masih mendapatkan akses yang dicatat.

Memeriksa resp.status adalah tes definitif untuk menentukan apakah start_response dipanggil.

@benoitc meninjau kembali ini. Untuk menyimpulkan secara lebih pasti bahwa permintaan sudah ditangani, environ['gunicorn.socket'] malah bisa menjadi semacam proxy untuk objek yang mendasarinya. Dengan begitu, dapat direkam ketika soket diakses secara langsung (misalnya membungkus get_socket() untuk eventlet), dan digunakan untuk sesuatu seperti is_already_handled

Itu masih membutuhkan peretasan status respons meskipun jika akses logging diinginkan.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat