Gunicorn: ์žฌํ˜„๋œ ์˜ค๋ž˜๋œ ๋ฒ„๊ทธ: 'Response' ๊ฐœ์ฒด์—๋Š” websockets๊ฐ€ ์žˆ๋Š” wsgi.py์— 'status_code' ์†์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค.

์— ๋งŒ๋“  2018๋…„ 08์›” 09์ผ  ยท  33์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: benoitc/gunicorn

์ด ์˜ค๋ž˜๋œ ๋ฌธ์ œ 1210 ์—์„œ ๋งํ–ˆ๋“ฏ์ด gunicorn์€ ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์ด ๋Š๊ธธ ๋•Œ ์˜ค๋ฅ˜๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋‚ด ํ™˜๊ฒฝ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๋ฐ๋น„์•ˆ GNU/๋ฆฌ๋ˆ…์Šค 7.8

  • nginx

  • ํŒŒ์ด์ฌ3.4

  • gunicorn(19.8.1)(ํ•˜๋‚˜ ์ด์ƒ์˜ ์ž‘์—…์ž ํฌํ•จ)

  • Flask-SocketIO, ํด๋ผ์ด์–ธํŠธ๋Š” ์›น ์†Œ์ผ“ ์ „์†ก์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

์ด ์˜ค๋ฅ˜ ๋กœ๊ทธ, ๋‘ ๊ฐœ์˜ ํด๋ผ์šฐ๋“œ ๋…๋ฆฝ ํ”„๋กœ๋•์…˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ชจ๋‘ ์ง€์†์ ์œผ๋กœ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ๋ฅผ ํฌํ•จํ•˜์—ฌ ๋ชจ๋“  ๊ฒƒ์ด ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ Mac์ธ ๊ฐœ๋ฐœ ์‹œ์Šคํ…œ์—์„œ๋Š” ์ด๋ฅผ ์žฌํ˜„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋งŽ์€ ๋„์›€์„ ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์š”์ฒญ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜ /socket.io/?EIO=3&transport=websocket
์—ญ์ถ”์ (๊ฐ€์žฅ ์ตœ๊ทผ ํ˜ธ์ถœ ๋งˆ์ง€๋ง‰):
ํŒŒ์ผ "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/workers/async.py", 56ํ–‰, ํ•ธ๋“ค
self.handle_request(listener_name, req, ํด๋ผ์ด์–ธํŠธ, addr)
handle_request์˜ ํŒŒ์ผ "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/workers/async.py", 116ํ–‰
resp.close()
ํŒŒ์ผ "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", 409ํ–‰, ๋‹ซ๊ธฐ
self.send_headers()
ํŒŒ์ผ "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", 325ํ–‰, send_headers
tosend = self.default_headers()
default_headers์˜ ํŒŒ์ผ "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", 306ํ–‰
elif self.should_close():
ํŒŒ์ผ "/opt/apps/lms/virtualenv/lib/python3.4/site-packages/gunicorn/http/wsgi.py", 229ํ–‰, should_close์— ์žˆ์Šต๋‹ˆ๋‹ค.
self.status_code < 200 ๋˜๋Š” self.status_code๊ฐ€ (204, 304)์ธ ๊ฒฝ์šฐ:
AttributeError: 'Response' ๊ฐœ์ฒด์— 'status_code' ์†์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค.

Feedback Requested unconfirmed ThirdPartFlask

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

๋ฒ”ํ”„ @benoitc

๋ชจ๋“  33 ๋Œ“๊ธ€

๊ทธ๊ฒƒ์„ ์žฌํ˜„ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ๋˜ํ•œ ๊ฐ€๋Šฅํ•˜๋ฉด ์ตœ์‹  ๋งˆ์Šคํ„ฐ๋กœ ์‹œ๋„ํ•˜์‹ญ์‹œ์˜ค.

์ด์ „์— ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ๊ณผ ๋™์ผํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ์ธ ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์žฌํ˜„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฒ„์ „ 19.9.0 ๋ฆด๋ฆฌ์Šค ๋กœ๊ทธ๋ฅผ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.๊ด€๋ จ๋œ ํ•ญ๋ชฉ์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.
์ด ์˜ค๋ฅ˜ ๋กœ๊ทธ๋ฅผ ๋ณด๊ณ  ์ƒˆ๋กœ์šด ๊ฒƒ์„ ์ฐพ์œผ๋ฉด ์—ฌ๊ธฐ์— ๊ฒŒ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ ํด๋ผ์ด์–ธํŠธ์—์„œ ์›น ์†Œ์ผ“ ํ”„๋กœํ† ์ฝœ๋กœ์˜ ๋ชจ๋“  ์—ฐ๊ฒฐ์„ ๊ฐ•์ œํ•  ๋•Œ ์ด ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด ์„ค์ •์€ BoWuGit๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. ์—…๊ทธ๋ ˆ์ด๋“œ ์ „์— ํด๋ง ํ”„๋กœํ† ์ฝœ์„ ํ—ˆ์šฉํ•˜๋ฉด ํ‘œ์‹œ๋˜์ง€ ์•Š์ง€๋งŒ ๋‹ค๋ฅธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
`
[์˜ค๋ฅ˜] ์š”์ฒญ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜ /socket.io/?EIO=3&transport=polling&t=MPHUoV&sid=cd64be7c940e474d8728b114c3fb9bbe

์—ญ์ถ”์ (๊ฐ€์žฅ ์ตœ๊ทผ ํ˜ธ์ถœ ๋งˆ์ง€๋ง‰):
ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/gunicorn/workers/async.py", 56ํ–‰, ํ•ธ๋“ค
self.handle_request(listener_name, req, ํด๋ผ์ด์–ธํŠธ, addr)

handle_request์˜ ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/gunicorn/workers/async.py", 107ํ–‰
respiter = self.wsgi(ํ™˜๊ฒฝ, resp.start_response)

ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/flask/app.py", 1994ํ–‰, __call__
self.wsgi_app(ํ™˜๊ฒฝ, start_response) ๋ฐ˜ํ™˜

ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/flask_socketio/__init__.py", 43ํ–‰, __call__
์‹œ์ž‘_์‘๋‹ต)

ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/engineio/middleware.sys"
py", 47ํ–‰, __call__
๋ฐ˜ํ™˜ self.engineio_app.handle_request(ํ™˜๊ฒฝ, ์‹œ์ž‘_์‘๋‹ต)

handle_request์˜ ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/socketio/server.py", 360ํ–‰
๋ฐ˜ํ™˜ self.eio.handle_request(ํ™˜๊ฒฝ, ์‹œ์ž‘_์‘๋‹ต)

handle_request์˜ ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/engineio/server.py", 279ํ–‰
์†Œ์ผ“ = self._get_socket(sid)

ํŒŒ์ผ "/usr/local/lib/python3.6/site-packages/engineio/server.py", 439ํ–‰, _get_socket
raise KeyError('์„ธ์…˜ ์—ฐ๊ฒฐ์ด ๋Š๊ฒผ์Šต๋‹ˆ๋‹ค')
`
๊ทธ๋Ÿฌ๋‚˜ ์›น ์†Œ์ผ“์— ์—ฐ๊ฒฐ์„ ๊ฐ•์ œ ์‹คํ–‰ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—์ด ์˜ค๋ฅ˜๊ฐ€ ๋‹ค์‹œ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋กœ ๊ด€๋ จ์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

eventlet 0.24.1์„ ์‚ฌ์šฉํ•  ๋•Œ gunicorn 19.9.0 ๋ฐ Flask-socketIO 3.0.2์—์„œ๋„ ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

AttributeError: 'Response' ๊ฐœ์ฒด์— 'status_code' ์†์„ฑ์ด ์—†์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋‹ค์Œ ์š”๊ตฌ ์‚ฌํ•ญ๊ณผ ํ•จ๊ป˜ ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

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

์—ด๋ฆฐ ์†Œ์ผ“ ์—ฐ๊ฒฐ์ด ์žˆ๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ์„ ๋•Œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€:

 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'

์ด ๋ฌธ์ œ๋Š” python-engineio ์ตœ์‹  ๋ฒ„์ „์—์„œ ์ˆ˜์ •๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

python-engineio ์ตœ์‹  ๋ฒ„์ „(2.3.2)์œผ๋กœ ํ…Œ์ŠคํŠธํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์†Œ์‹์ด ์žˆ์Šต๋‹ˆ๊นŒ? Sentry-python์„ ์‚ฌ์šฉํ•  ๋•Œ ๋™์ผํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€์žˆ๋‹ค

์ด๋ฒคํŠธ๋ ›: 0.25.1
ํ”Œ๋ผ์Šคํฌ ์†Œ์ผ“: 4.2.1
์ด๊ธฐ์ฝ˜: 19.9.0

image

image

๊ทธ๊ฒƒ์„ ์žฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•? cna ๋‹น์‹ ์€ ๊ฐ„๋‹จํ•œ ์˜ˆ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๊นŒ?

๋‚˜๋Š” ๋˜ํ•œ ๊ทธ๊ฒƒ์„ ์žฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž˜ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ๋‚ด gunicorn ์•ฑ์—์„œ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ ๊ณ ์น  ๋•Œ ์ž์ฃผ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ๋‚ด ํ™˜๊ฒฝ์€ @eazow ์™€ ๋™์ผํ•˜์ง€๋งŒ gunicorn == 20.0.4์ž…๋‹ˆ๋‹ค.
๋ฒ„๊ทธ ์ถ”์ ์„ ์œ„ํ•ด ์„ผํŠธ๋ฆฌ๋ฅผ ์„ค์น˜ํ•œ ํ›„ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๋ฌธ์ œ๋Š” ๋‹ค์Œ์„ ํ†ตํ•ด ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ(์ƒˆ ํŽ˜์ด์ง€๋ฅผ ์—ด์ง€ ์•Š์Œ)
  2. ํŽ˜์ด์ง€ ๋‹ซ๊ธฐ

ํฅ๋ฏธ๋กญ๊ฒŒ๋„ ์ƒˆ ํŽ˜์ด์ง€๋ฅผ ์—ด๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด์œ ๋Š” ํ™•์‹คํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌ ํ•ด์š”!

@cowbonlin ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ™์€ gunicorn ๋ฒ„์ „๋„.

Sentry๋ฅผ ์„ค์น˜ํ•œ ํ›„ ์—„์ฒญ๋‚œ ์–‘์˜ ์ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์„ผํŠธ๋ฆฌ ์ด์ „์— ์˜ค๋ฅ˜๋ฅผ ์ถ”์ ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์ด ํ•ญ์ƒ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋งํ•˜๊ธฐ๋Š” ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ ์„œ๋ฒ„์˜ ์‹ค์ œ ๊ธฐ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์ง€๋งŒ ์ด๊ฒƒ์€ ์—„์ฒญ๋‚œ ์ŠคํŒธ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋„ ๊ฐ™์€ ์ผ์„ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Sentry๊ฐ€ ์„ค์น˜๋˜์—ˆ์ง€๋งŒ ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ์•„์ด๋””์–ด?

์„ผํŠธ๋ฆฌ๊ฐ€ ์„ค์น˜๋œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

์„ผํŠธ๋ฆฌ(๋น„ํ™œ์„ฑํ™” ์—ฌ๋ถ€) ์—†์ด ์žฌ์ƒ์‚ฐํ•˜๋Š” ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

๋˜ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋Œ€์‹  ์ˆ˜๋™์œผ๋กœ /api๋ฅผ ๋ˆŒ๋ €์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋Œ€์‹  ์ˆ˜๋™์œผ๋กœ /api๋ฅผ ๋ˆŒ๋ €์Šต๋‹ˆ๋‹ค.

๋ฌด์Šจ ๋œป์ธ๊ฐ€์š” ? ์ด ์„ผํŠธ๋ฆฌ์™€ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๋˜ํ•œ ๋„ค์ž„์ŠคํŽ˜์ด์Šค ๋Œ€์‹  ์ˆ˜๋™์œผ๋กœ /api๋ฅผ ๋ˆŒ๋ €์Šต๋‹ˆ๋‹ค.

๋ฌด์Šจ ๋œป์ธ๊ฐ€์š” ? ์ด ์„ผํŠธ๋ฆฌ์™€ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

์•„๋‹ˆ์š”, ์ด๊ฒƒ์€ socket.io ๋„ค์ž„์ŠคํŽ˜์ด์Šค์™€ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ๊ฑฐ๋ฅผ ์‹œ๋„ํ–ˆ๋Š”๋ฐ ์ œ๊ฑฐํ•œ ํ›„์—๋„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ gunicorn ๋˜๋Š” nginx๊ฐ€ ์—†๋Š” ๋กœ์ปฌ ์ปดํ“จํ„ฐ์—์„œ ์ด ๋‹ค๋ฅธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ด€๋ จ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚ด ์š”๊ตฌ ์‚ฌํ•ญ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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

์ด๊ฒƒ์€ ์„œ๋ฒ„ ์ธก์˜ flask-socketio ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

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

๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์€ ํด๋ผ์ด์–ธํŠธ ์ธก์˜ React ์†Œ์ผ“ io ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

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

๋„์›€์ด ๋˜๋Š”์ง€ ์•Œ๋ ค์ฃผ์„ธ์š”. Ubuntu์—์„œ ์˜ค๋ฅ˜๋Š” ์œ„์˜ ๊ฒƒ๊ณผ ๊ฐ™๊ณ  Windows์—์„œ ๋กœ์ปฌ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
```์ถ”์ (๊ฐ€์žฅ ์ตœ๊ทผ ํ˜ธ์ถœ ๋งˆ์ง€๋ง‰):
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", 599ํ–‰, handle_one_response
์“ฐ๊ธฐ(b'')
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", ์ค„ 491, ์“ฐ๊ธฐ
๋ฐœ์ƒ AssertionError("start_response() ์ „์— write()")
AssertionError: start_response() ์ „์— write()

์œ„์˜ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์—ญ์ถ”์ (๊ฐ€์žฅ ์ตœ๊ทผ ํ˜ธ์ถœ ๋งˆ์ง€๋ง‰):
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", 357ํ–‰, __init__
self.handle()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", 390ํ–‰, ํ•ธ๋“ค
self.handle_one_request()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", 466ํ–‰, handle_one_request
self.handle_one_response()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", 609ํ–‰, handle_one_response
์“ฐ๊ธฐ(err_body)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", ์ค„ 538, ์“ฐ๊ธฐ
wfile.flush()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\socket.py", 607ํ–‰, ์“ฐ๊ธฐ ์ค‘
๋ฐ˜ํ™˜ self._sock.send(b)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", 397ํ–‰, ๋ณด๋‚ด๊ธฐ
๋ฐ˜ํ™˜ self._send_loop(self.fd.send, ๋ฐ์ดํ„ฐ, ํ”Œ๋ž˜๊ทธ)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", 384ํ–‰, _send_loop
send_method(๋ฐ์ดํ„ฐ, *args) ๋ฐ˜ํ™˜
ConnectionAbortedError: [WinError 10053] ํ˜ธ์ŠคํŠธ ์ปดํ“จํ„ฐ์˜ ์†Œํ”„ํŠธ์›จ์–ด์— ์˜ํ•ด ์„ค์ •๋œ ์—ฐ๊ฒฐ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์œ„์˜ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์—ญ์ถ”์ (๊ฐ€์žฅ ์ตœ๊ทผ ํ˜ธ์ถœ ๋งˆ์ง€๋ง‰):
fire_timers์˜ ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\hubs\hub.py", ๋ผ์ธ 461
์‹œ๊ฐ„์ œ ๋…ธ๋™์ž()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\hubs\timer.py", 59ํ–‰, __call__
cb( ์ธ์ˆ˜, * kw)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\semaphore.py", 147ํ–‰, _do_acquire
waiter.switch()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenthread.py", ๋ผ์ธ 221, ๋ฉ”์ธ
๊ฒฐ๊ณผ = ํ•จ์ˆ˜( ์ธ์ˆ˜, * kwargs)
"C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py" ํŒŒ์ผ, 818ํ–‰, process_request
proto.__init__(conn_state, self)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", 359ํ–‰, __init__
self.finish()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\wsgi.py", 732ํ–‰, ์™„๋ฃŒ
BaseHTTPServer.BaseHTTPRequestHandler.finish(์ž์ฒด)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\socketserver.py", 784ํ–‰, ์™„๋ฃŒ
self.wfile.close()
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\socket.py", 607ํ–‰, ์“ฐ๊ธฐ ์ค‘
๋ฐ˜ํ™˜ self._sock.send(b)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", 397ํ–‰, ๋ณด๋‚ด๊ธฐ
return self._send_loop(self.fd.send, ๋ฐ์ดํ„ฐ, ํ”Œ๋ž˜๊ทธ)
ํŒŒ์ผ "C:\ProgramData\Anaconda3\lib\site-packages\eventlet\greenio\base.py", 384ํ–‰, _send_loop
send_method(๋ฐ์ดํ„ฐ, *์ธ์ˆ˜) ๋ฐ˜ํ™˜
ConnectionAbortedError: [WinError 10053] ํ˜ธ์ŠคํŠธ ์ปดํ“จํ„ฐ์˜ ์†Œํ”„ํŠธ์›จ์–ด์— ์˜ํ•ด ์„ค์ •๋œ ์—ฐ๊ฒฐ์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค```

์„ผํŠธ๋ฆฌ๊ฐ€ ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”๋˜๋ฉด ์ด ์˜ค๋ฅ˜๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. gunicorn์ด ์ด๊ฒƒ์„ ์ฒ˜๋ฆฌํ•  ๋งŒํผ ์ถฉ๋ถ„ํžˆ ๊ฒฌ๊ณ ํ•˜๋‹ค๋ฉด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ฒ”ํ”„ @benoitc

์„ผํŠธ๋ฆฌ๊ฐ€ ์™„์ „ํžˆ ๋น„ํ™œ์„ฑํ™”๋˜๋ฉด ์ด ์˜ค๋ฅ˜๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. gunicorn์ด ์ด๊ฒƒ์„ ์ฒ˜๋ฆฌํ•  ๋งŒํผ ์ถฉ๋ถ„ํžˆ ๊ฒฌ๊ณ ํ•˜๋‹ค๋ฉด ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Sentry์˜ FlaskIntegration ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ด๋„ ์˜ค๋ฅ˜๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

๋น„์Šทํ•œ ํ–‰๋™์„ ๋ณด์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜์—์„œ New Relic์„ ์‚ฌ์šฉํ•˜๋ฉด flask-socketio์—์„œ ์ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์‹œ์—๋Š” flask-socketio๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๊ธฐ ์ „์— werkzeug ๋””๋ฒ„๊ฑฐ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋กœ๋“œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋”ฐ๋ผ์„œ engineio์˜ wsgi ์•ฑ์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š์Œ). ๋ฌธ์ œ๋Š” ํ”„๋กœ๋•์…˜์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ์ •๋ง ์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

gunicorn ๊ตฌ์„ฑ์˜ post_request์—์„œ ์‘๋‹ต์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์—†์ง€๋งŒ resp.status_code์— ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ•์ œ๋กœ ์ ์šฉํ•˜๋ ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜๋„ ๊ฑธ๋ฆฌ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

์ด ์˜ค๋ฅ˜๋Š” Sentry์˜ FlaskIntegration์„ Gunicorn ๋ฐ Flask-SocketIO์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ์žฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋นจ๋ฆฌ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ• ๊นŒ์š”?

@Canicio ์šฐ๋ฆฌ๋Š” ์˜ค๋ฅ˜๋ฅผ ์ œ๊ฑฐํ•˜๋ ค๊ณ  ์‹œ๋„ํ–ˆ์œผ๋ฉฐ ํ†ตํ•ฉ์„ ๋น„ํ™œ์„ฑํ™”ํ•œ ํ›„์—๋„ ์˜ค๋ฅ˜๊ฐ€ ์ง€์†๋œ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ˆ„๊ตฌ๋“ ์ง€ @benoitc ์— ๋Œ€ํ•œ ๊ณต์œ  ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ/์ตœ์†Œํ•œ์˜ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

ํ™•์‹ ํ•˜๋Š”:

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>

์š”๊ตฌ ์‚ฌํ•ญ:

flask
sentry-sdk[flask]
flask-socketio
eventlet

์˜ˆ์ œ gunicorn ๊ตฌ์„ฑ:

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

/ ๋กœ๋“œ ์‹œ ์›น ์†Œ์ผ“์— ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ์›น ์†Œ์ผ“ ์—ฐ๊ฒฐ ํ•ด์ œ ์‹œ(์˜ˆ: ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ์ด๋™, ์ƒˆ๋กœ ๊ณ ์นจ) ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ˆ์™ธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

[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 "-" "-"

์ฐธ๊ณ : ์ €๋Š” ์‹ค์ œ๋กœ Sentry๋ฅผ ์‚ฌ์šฉํ•œ ์ ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์„ผํŠธ๋ฆฌ ์‹œ์ž‘ ํŽ˜์ด์ง€์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. dsn ์˜ˆ์ œ๋Š” ์šฐ๋ฆฌ ํ…Œ์ŠคํŠธ์—์„œ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

integrations=[FlaskIntegration()] ์— ์ฃผ์„์„ ๋‹ฌ๋ฉด ์˜ค๋ฅ˜๊ฐ€ ์ œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค(๋ฌผ๋ก  ํšจ๊ณผ์ ์œผ๋กœ ๋ณด์ดˆ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•จ).

๊ทธ๋งŒํ•œ ๊ฐ€์น˜๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜ ์—†์ด eventlet ๋Œ€์‹  gevent-websocket์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋“  ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ข‹์•„, ์ข€ ๋†€์•˜์–ด. Sentry/newrelic์ด ์‘๋‹ต์„ ๋ž˜ํ•‘ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. Sentry๊ฐ€ ์—†์œผ๋ฉด ์˜ˆ์ƒํ•œ ๋Œ€๋กœ <eventlet.wsgi._AlreadyHandled object at 0x7fd0f5b1c0d0> ๋ฅผ ์–ป๊ณ  gunicorn์˜ EventletWorker.is_already_handled()๊ฐ€ ๋ฐ˜๋ณต์„ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ Sentry๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋Œ€์‹  <sentry_sdk.integrations.wsgi._ScopedResponse object at 0x7f30155a5100> ์™€ ๊ฐ™์ด ๋˜์–ด ํ™•์ธ์— ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

๋Œ€์‹ , ์šฐ๋ฆฌ๋Š” ๊ทธ๊ฒƒ์ด ๋น„์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด respiter๋ฅผ ์—ฟ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด์ผ์€ ๋” ์ง€์ผœ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ข‹์Šต๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์ƒ๊ฐํ•ด๋‚ธ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

eventlet_fix.py:
์•„๋ž˜ ํŽธ์ง‘ ์ฐธ์กฐ

๊ทธ๋ฆฌ๊ณ  ๋‚ด gunicorn config.py: worker_class = 'eventlet_fix.EventletWorker .

๋ฌธ์ œ๋Š” sentry/newrelic์ด ์‘๋‹ต์„ ๋ž˜ํ•‘ํ•˜๋ฏ€๋กœ eventlet์˜ ALREADY_HANDLED ์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์š”์ฒญ์˜ ํŠน์„ฑ์€ gunicorn์˜ start_response ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ๋Œ€์‹  ์‘๋‹ต ์ƒํƒœ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ wsgi ํ˜ธ์ถœ์„ ๊ฐ€๋กœ์ฑ„์–ด ์‘๋‹ต ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ  ํ•„์š”์— ๋”ฐ๋ผ ์‘๋‹ต ๊ฐ’์„ ํ•ดํ‚นํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์š”์ฒญ์ด ์—ฌ์ „ํžˆ gunicorn์— ์˜ํ•ด ๊ธฐ๋ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์›๋ž˜ ๋™์ž‘์„ ์œ ์ง€ํ•˜๋ ค๋ฉด StopIteration ๋ฅผ ๋Œ€์‹  ์˜ฌ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ƒํƒœ๋ฅผ 101๋กœ ํ•ดํ‚นํ•˜๋Š” ๊ฒƒ์€ ์—ฌ๊ธฐ์—์„œ์˜ ์‚ฌ์šฉ ์‚ฌ๋ก€(flask-socketio websocket)์— ์ ํ•ฉํ•˜์ง€๋งŒ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด headers_sent ๋ฐ should_close ๊ฐ€ ๊ฐ•์ œ๋กœ True๋กœ ์ง€์ •๋˜๋ฏ€๋กœ None์œผ๋กœ ๋‘๋Š” ๊ฒƒ๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค.

๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ, status ๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ start_response ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ์š”์ฒญ์ด ์™ธ๋ถ€์ ์œผ๋กœ "์ด๋ฏธ ์ฒ˜๋ฆฌ"๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

ํŽธ์ง‘ : ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์žฌํ‰๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋ฉด resp.status start_response ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

edit2: ํ•ดํ‚น๋œ ์‘๋‹ต ๋ฐ˜๋ณต์ž๊ฐ€ ์žˆ๋Š” ์ˆ˜์ •๋œ ๋ฒ„์ „์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

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)

๋ถ„๋ช…ํžˆ ์ด๊ฒƒ์€ ๋‹จ์ง€ ์›์ˆญ์ด ํŒจ์น˜์ž…๋‹ˆ๋‹ค. ์‹ค์ œ ์ˆ˜์ •์€ ์ž ์žฌ์ ์œผ๋กœ base_async.py์˜ handle_request ์— ํฌํ•จ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ์€ resp.status ( start_response ํ˜ธ์ถœ๋จ) ๋˜๋Š” resp.headers_sent ์„ ํ™•์ธํ•˜์—ฌ respiter ๋ฅผ ๋ฐ˜๋ณตํ•œ ํ›„ start_response ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ (๊ฐ„์ ‘์ ์œผ๋กœ) ํ™•์ธํ•˜๋Š” ๊ฒƒ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. resp.headers_sent (์š”์ฒญ์— ์‹ค์ œ๋กœ ์‘๋‹ตํ–ˆ๋‹ค๋Š” ํ™•์ธ).

@benoitc
@ziddey ๊ฐ€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.

@ziddey ๊ท€ํ•˜์˜ ์˜ˆ์— ๋Œ€ํ•œ ๋น ๋ฅธ ์งˆ๋ฌธ(์ €๋Š” ์„ผํŠธ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—).

  • ์˜ค๋ฅ˜๊ฐ€ ์„ผํŠธ๋ฆฌ์—๋งŒ ์˜ํ–ฅ์„ ๋ฏธ์น˜๊ฑฐ๋‚˜ ์š”์ฒญ๋„ ์ค‘์ง€๋˜์—ˆ์Šต๋‹ˆ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด ์ž‘์—…์ž๊ฐ€ ์ข…๋ฃŒ ์ค‘์ž…๋‹ˆ๊นŒ(์‘๋‹ต์ด ๋ž˜ํ•‘๋œ ๊ฒฝ์šฐ ๊ทธ๋Ÿด ๊ฒƒ์œผ๋กœ ์˜์‹ฌ๋จ)?
  • ์‘๋‹ต์ด ํฌ์žฅ๋˜์–ด ์žˆ์–ด๋„ ์š”์ฒญ์„ ์ •๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋ฌด์–ธ๊ฐ€๋ฅผ ํฌ์žฅํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•˜์‹ญ๋‹ˆ๊นŒ?

@benoitc ๋Š” ํ˜„์žฌ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์—†์ง€๋งŒ https://github.com/benoitc/gunicorn/issues/1852#issuecomment -697189261 ๋ฐ https://github.com/benoitc/gunicorn/blob/4ae2a05c37b332773957f90 ์œ„์˜ ์—ญ์ถ”์ ์„ ๋ณด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ is_already_handled ๋Š” True๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์—ฌ๊ธฐ์„œ ๋๋‚ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์‘๋‹ต์ด ๋ž˜ํ•‘๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋ฐฉ๋ฒ•์€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์‹คํ–‰์ด ์ง„ํ–‰๋˜๊ณ  115ํ–‰์—์„œ โ€‹โ€‹์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. resp.close() ํ—ค๋” ์ „์†ก์„ ์‹œ๋„ํ•˜์ง€๋งŒ start_response ๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋˜๋”๋ผ๋„ ๊ฒฐ๊ตญ ๋ถ„๋ช…ํžˆ ์‹คํŒจํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ ๊ฒฐ๊ณผ handle_error ์— ์˜ํ•ด ๋‹ค์‹œ ๋ฐœ์ƒํ•˜๊ณ  ์ฒ˜๋ฆฌ๋˜๋Š” AttributeError ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ์€ ์ด๋ฏธ ์™ธ๋ถ€์—์„œ ์ฒ˜๋ฆฌ๋˜์—ˆ์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์—๋Š” ๋กœ๊ทธ ์ŠคํŒธ ์™ธ์—๋Š” ์•„๋ฌด๋Ÿฐ ํ•ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๋‚˜๋Š” Sentry์— ๋Œ€ํ•ด ๋„ˆ๋ฌด ๋งŽ์ด ๋งํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ €๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ํ•œ ๊ฐ€์ง€ ์„ธ๋ถ€ ์‚ฌํ•ญ์€ ํ˜„์žฌ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์ธํ•ด ์•ก์„ธ์Šค ๋กœ๊น…์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์™ธ๋ถ€์—์„œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜์—ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์ด ๊ธฐ์ˆ ์ ์œผ๋กœ ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ํ•ดํ‚น๋œ ์‘๋‹ต์—์„œ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ 101๋กœ ์„ค์ •ํ•˜๊ณ  headers_sent ๋ฅผ True๋กœ ์„ค์ •ํ•˜์—ฌ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๊ณ„์† ์ง„ํ–‰๋˜๊ณ  ์š”์ฒญ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๊ฐ€ ๊ณ„์† ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค.

resp.status ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์€ start_response ๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ ์ตœ์ข… ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

@benoitc ๊ฐ€ ์ด๊ฒƒ์„ ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•ฉ๋‹ˆ๋‹ค. ์š”์ฒญ์ด ์ด๋ฏธ ์ฒ˜๋ฆฌ๋˜์—ˆ๋‹ค๋Š” ๊ฒฐ๋ก ์„ ๋‚ด๋ฆฌ๊ธฐ ์œ„ํ•ด environ['gunicorn.socket'] ๋Œ€์‹  ๊ธฐ๋ณธ ๊ฐœ์ฒด์— ๋Œ€ํ•œ ์ผ์ข…์˜ ํ”„๋ก์‹œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ฉด ์†Œ์ผ“์— ์ง์ ‘ ์•ก์„ธ์Šคํ•  ๋•Œ ๊ธฐ๋ก๋  ์ˆ˜ ์žˆ๊ณ (์˜ˆ: eventlet์— ๋Œ€ํ•ด get_socket() ๋ž˜ํ•‘) is_already_handled ์™€ ๊ฐ™์€ ์šฉ๋„๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ก์„ธ์Šค ๋กœ๊น…์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋„ ์—ฌ์ „ํžˆ ์‘๋‹ต ์ƒํƒœ๋ฅผ ํ•ดํ‚นํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰