Cloudflare Durable Objects 웹소켓

junsangyu·2025년 6월 2일
post-thumbnail

Cloudflare에서 free-tier 서비스를 많이 제공해줘서 개인적인 사이드 프로젝트에 많이 사용하는 편이다.

기존에 Pages Workers 서비스들이 무료였는데 이제는 d1 Durable Objects 서비스도 무료로 제공해줘서 한번 사용해 보았다.

https://blog.cloudflare.com/building-ai-agents-with-mcp-authn-authz-and-durable-objects/

Durable Objects란?

기존에 Workers 에서는 stateless한 서비스만 개발할 수 있었다.
하지만 Durable Objects 에서는 sqlite 저장소에 접근해서 stateful한 서비스를 쉽게 개발할 수 있다.
또한 WebSocket 기능도 간단하게 구현할 수 있다.

예시로 채팅 서비스를 Durable Objects 에서 구현한다면?

  • 채팅 내역들을 sqlite에 저장
  • 실시간으로 연결된 웹소켓으로 채팅을 전송

하는 서비스를 쉽게 만들 수 있다!

sqlite 사용하기

export class MyDurableObject extends DurableObject {
  constructor(ctx, env) {
    super(ctx, env);
	this.sql = ctx.storage.sql;
	this.sql.exec(`
      CREATE TABLE IF NOT EXISTS chat (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        body TEXT NOT NULL,
        author TEXT NOT NULL,
        created_at DEFAULT CURRENT_TIMESTAMP
      );
	`); // 테이블 생성하기
  }
  async getChats() {
    const chats = this.sql.exec('SELECT * FROM chat').toArray();
    return chats;
  }
}

export default {
  async fetch(request, env, ctx) {
    const id = env.MY_DURABLE_OBJECT.idFromName('foo'); // 네임스페이스
    const stub = env.MY_DURABLE_OBJECT.get(id);
    return Response.json(await stub.getChats());
  }
}

WebSocket 사용하기

돈을 아끼기 위해 hibernate 가능한 웹소켓 API를 사용하자

hibernate시 인스턴스가 새로 생성되는거여서 클래스에 데이터를 저장하면 안되고 sqlite or attachment에 데이터를 저장해야한다.

DurableObject 클래스에서 웹소켓 시작 메소드는 항상 fetch() 이여야 한다!!!
https://github.com/cloudflare/workerd/issues/2319

export class MyDurableObject extends DurableObject {
  async fetch(request) {
    const webSocketPair = new WebSocketPair();
    const [client, server] = Object.values(webSocketPair);
    this.ctx.acceptWebSocket(server);
    
    const ip = request.headers.get('CF-Connecting-IP');
    server.serializeAttachment({ ip }); // 소켓마다 ip정보 attachment로 저장하기
    
    return new Response(null, {
      status: 101,
      webSocket: client,
    });
  }
  async webSocketMessage(ws, message) {
    const { ip } = ws.deserializeAttachment() // 소켓 ip attachment 가져오기
    ws.send(`[${ip}]: ${message}`);
  }
  async webSocketClose(ws, code, reason, wasClean) {
    ws.close(code, 'Durable Object is closing WebSocket');
  }
}

export default {
  async fetch(request, env, ctx) {
    const id = env.MY_DURABLE_OBJECT.idFromName('foo'); // 네임스페이스
    const stub = env.MY_DURABLE_OBJECT.get(id);
    return stub.fetch(request);
  }
}

기타

  • Durable Objectsnamespace는 각각의 storage를 가진다.
  • CF-Connecting-IP 헤더는 wrangler dev 환경에서 안보인다

Durable Objects로 만든 채팅 서비스 리포지토리
https://github.com/stupidJoon/supa-chat/tree/cf-do

profile
👨🏻‍💻

0개의 댓글