스트리밍
wss wss://ws.xsignalfeed.com/v1/stream
연결하기
스트림은 REST와 같은 세 가지 방법으로 키를 받습니다. 서버에서는 헤더를 쓰고, URL 파라미터는 헤더를 설정할 수 없을 때만 쓰세요. 키가 도착하지 않거나 거부되면 연결이 열린 뒤 곧바로 4001로 닫히며, 닫힘 사유가 이유를 알려 줍니다.
| 키 전달 방법 | 사용하는 경우 |
|---|---|
Authorization: Bearer xsf_… | 서버에 권장. REST와 같은 헤더입니다 |
X-API-Key: xsf_… | 위와 동일 |
?api_key=xsf_… | 브라우저, 그리고 핸드셰이크 헤더를 설정할 수 없는 클라이언트. 키가 URL의 일부가 되므로 그런 URL이 로그에 남지 않게 하세요 |
python — 헤더로 키 전달
import websockets async with websockets.connect("wss://ws.xsignalfeed.com/v1/stream", additional_headers={"Authorization": "Bearer xsf_your_key"}) as ws: print(await ws.recv()) # {"t": "system.hello", ...}
| 쿼리 파라미터 | 의미 |
|---|---|
| api_key | 헤더를 설정할 수 없을 때 사용하는 키 |
| last_id | 이 sid 이후부터 재개합니다. 키에 replay 스코프가 없으면 조용히 무시됩니다 |
import asyncio, json, websockets async def main(): url = "wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key" async with websockets.connect(url) as ws: async for raw in ws: ev = json.loads(raw) if ev["t"].startswith("system."): continue print(ev["t"], ev["target"]["screen_name"], ev["d"].get("text", "")[:60]) asyncio.run(main())
import WebSocket from "ws"; const ws = new WebSocket("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key"); ws.on("message", (raw) => { const ev = JSON.parse(raw); if (ev.t.startsWith("system.")) return; console.log(ev.t, ev.target.screen_name, (ev.d.text ?? "").slice(0, 60)); });
c, _, err := websocket.DefaultDialer.Dial("wss://ws.xsignalfeed.com/v1/stream?api_key=xsf_your_key", nil) for { _, raw, err := c.ReadMessage() if err != nil { break } var ev Event json.Unmarshal(raw, &ev) if strings.HasPrefix(ev.T, "system.") { continue } log.Println(ev.T, ev.Target.ScreenName) }
프레임
세 가지 프레임은 X가 아닌 저희 것입니다:
| 프레임 | 시점 |
|---|---|
system.hello | 첫 프레임입니다. targets는 이 시점에 이 키가 모니터링하는 계정 수입니다(나중에 추가한 계정은 다시 연결하지 않아도 합류합니다). replay_from은 다시 연결할 때 보낸 last_id를 그대로 돌려주며, 보내지 않았다면 null이고 이 경우 아무것도 재전송되지 않습니다 |
system.ping | 침묵 중에 킵얼라이브로 전송되어, 유휴 프록시가 소켓을 닫지 않게 합니다 |
system.error | 연결에 문제가 있습니다. 곧 종료가 뒤따릅니다 |
종료 코드
핸드셰이크는 항상 성공하고 종료가 즉시 뒤따르므로, 클라이언트 라이브러리는 단순한 HTTP 오류 대신 코드와 사유를 볼 수 있습니다.
| 종료 코드 | 의미 |
|---|---|
1012 | 점검 중 서비스 재시작입니다. 백오프로 다시 연결하면서 마지막으로 처리한 sid를 ?last_id=로 전달하세요. 끊긴 동안의 이벤트가 재전송되어 아무것도 잃지 않습니다 |
4001 | 키가 받아들여지지 않았습니다. 닫힘 사유가 이유를 알려 줍니다: missing_api_key(키가 도착하지 않음: 헤더 이름, 또는 프록시나 클라이언트 라이브러리가 전달하는지 확인), invalid_api_key(그런 키가 없음: 오타 또는 로테이션으로 바뀐 시크릿), key_revoked(폐기된 키) |
4002 | 이 키의 동시 연결 수가 너무 많습니다. 종료 전에 system.error 프레임이 전송됩니다 |
4003 | 키에 stream 스코프가 없습니다 |
제대로 재연결하기
단순한 버전 말고 이것을 한 번만 제대로 작성해 두십시오. 중요한 것은 두 가지, 지수 백오프와 프로세스 재시작 후에도 남는 곳에 sid를 보관하는 것입니다.
python — 프로덕션 형태
import asyncio, json, pathlib, random, websockets STATE = pathlib.Path("xsf.cursor") # survives restarts; a DB row is better still async def run(key, handle): backoff = 1 while True: last = STATE.read_text().strip() if STATE.exists() else None url = f"wss://ws.xsignalfeed.com/v1/stream?api_key={key}" + (f"&last_id={last}" if last else "") try: async with websockets.connect(url, ping_interval=20, max_size=2**22) as ws: backoff = 1 async for raw in ws: ev = json.loads(raw) if ev["t"].startswith("system."): continue handle(ev) # must be idempotent: dedupe on ev["id"] if ev.get("sid"): STATE.write_text(ev["sid"]) # only after handling except Exception as e: print("disconnected:", e) await asyncio.sleep(backoff + random.random()) # jitter, or every client returns at once backoff = min(60, backoff * 2)