打开上游流式连接并预读首行(把「200 但立刻空体」这种抖动也纳入重试)。 返回 (resp, first_chunk, content_type);首字节到手后不再重试。
(url, data, headers, attempts=4, timeout=300)
| 253 | |
| 254 | |
| 255 | def open_stream(url, data, headers, attempts=4, timeout=300): |
| 256 | """打开上游流式连接并预读首行(把「200 但立刻空体」这种抖动也纳入重试)。 |
| 257 | 返回 (resp, first_chunk, content_type);首字节到手后不再重试。""" |
| 258 | headers = {"User-Agent": UPSTREAM_UA, **headers} |
| 259 | for i in range(attempts): |
| 260 | req = urllib.request.Request(url, data=data, headers=headers) |
| 261 | try: |
| 262 | r = urllib.request.urlopen(req, timeout=timeout) |
| 263 | first = r.readline(65536) |
| 264 | if not first: |
| 265 | r.close() |
| 266 | raise ConnectionError("上游 200 但立刻空体") |
| 267 | return r, first, r.headers.get("Content-Type", "application/json") |
| 268 | except urllib.error.HTTPError: |
| 269 | raise |
| 270 | except Exception as e: |
| 271 | if i < attempts - 1: |
| 272 | log(f" ~ 上游连接抖动,重试 {i + 1}/{attempts - 1}: {e}") |
| 273 | time.sleep(0.8 * (i + 1)) |
| 274 | continue |
| 275 | raise |
| 276 | |
| 277 | |
| 278 | def _open_stream_with_keepalive(write_chunk, url, data, headers): |