github.com/anycable/anycable-go@v1.5.1/features/sse.testfile (about)

     1  launch :anycable,
     2    "./dist/anycable-go --sse --public_streams --secret=qwerty --broadcast_adapter=http --presets=broker"
     3  
     4  wait_tcp 8080
     5  
     6  payload = {ext: {}.to_json, exp: (Time.now.to_i + 60)}
     7  
     8  token = ::JWT.encode(payload, "qwerty", "HS256")
     9  stream_name = "chat/2023"
    10  
    11  require "uri"
    12  require "net/http"
    13  require "fiber"
    14  
    15  identifier = URI.encode_www_form_component({channel: "$pubsub", stream_name: stream_name}.to_json)
    16  
    17  url = "http://localhost:8080/events?jid=#{token}&identifier=#{identifier}"
    18  
    19  Event = Struct.new(:type, :data, :id, :retry)
    20  
    21  # echo -n 'broadcast-cable' | openssl dgst -sha256 -hmac 'qwerty' | awk '{print $2}'
    22  BROADCAST_KEY = "42923a28b760e667fc92f7c6123bb07a282822b329dd2ef48e7aee7830d98485"
    23  
    24  def broadcast(stream, data)
    25    uri = URI.parse("http://localhost:8080/_broadcast")
    26    header = {
    27      "Content-Type": "application/json",
    28      "Authorization": "Bearer #{BROADCAST_KEY}"
    29    }
    30    data = {stream: stream, data: data.to_json}
    31    http = Net::HTTP.new(uri.host, uri.port)
    32    request = Net::HTTP::Post.new(uri.request_uri, header)
    33    request.body = data.to_json
    34    response = http.request(request)
    35  
    36    if response.code != "201"
    37      fail "Broadcast returned unexpected status: #{response.code}"
    38    end
    39  end
    40  
    41  def parse_sse_chunk(chunk)
    42    event = Event.new
    43    chunk.split("\n").each do |line|
    44      field, value = line.split(":", 2).map(&:strip)
    45  
    46      case field
    47      when "data"
    48        event.data = JSON.parse(value)
    49      when "event"
    50        event.type = value
    51      when "id"
    52        event.id = value
    53      when "retry"
    54        event.retry = value.to_i
    55      end
    56    end
    57    event
    58  end
    59  
    60  def streaming_request(uri, headers: {})
    61    begin
    62      fiber = Fiber.new do
    63        Net::HTTP.start(uri.host, uri.port, read_timeout: 2) do |http|
    64          request = Net::HTTP::Get.new(uri)
    65          headers.each do |key, value|
    66            request[key] = value
    67          end
    68          catch :stop do
    69            http.request(request) do |response|
    70              response.read_body do |chunk|
    71                chunk.split("\n\n").each do |raw_event|
    72                  event = parse_sse_chunk(raw_event)
    73                  # ignore pings
    74                  next if event.type == "ping"
    75  
    76                  cmd = Fiber.yield(event)
    77                  if cmd == :stop
    78                    throw :stop
    79                  end
    80                end
    81              end
    82            end
    83          end
    84        end
    85      end
    86      yield fiber
    87    rescue => e
    88      fiber.resume(:stop)
    89      raise
    90    end
    91  end
    92  
    93  start_time = Time.now.to_i
    94  last_id = nil
    95  
    96  streaming_request(URI(url)) do |stream|
    97    first_event = stream.resume
    98  
    99    if first_event.type != "welcome"
   100      fail "Expected welcome, got: #{first_event}"
   101    end
   102  
   103    second_event = stream.resume
   104  
   105    if second_event.type != "confirm_subscription"
   106      fail "Expected confirm_subscription, got: #{second_event}"
   107    end
   108  
   109    # Broadcast a message
   110    broadcast stream_name, {"text" => "Hello, stream!"}
   111  
   112    broadcast_event = stream.resume
   113  
   114    if broadcast_event.data != {"text" => "Hello, stream!"}
   115      fail "Expected broadcast data, got: #{broadcast_event.data}"
   116    end
   117  
   118    last_id = broadcast_event.id
   119  
   120    # Stop first session
   121    stream.resume(:stop)
   122  end
   123  
   124  # Broadcast another message
   125  broadcast stream_name, {"text" => "Where are you, stream?"}
   126  
   127  # Start new session with last ID
   128  streaming_request(URI(url), headers: {"Last-Event-ID" => last_id}) do |stream|
   129    fail "Expected welcome" unless stream.resume.type == "welcome"
   130    fail "Expected confirmation" unless stream.resume.type == "confirm_subscription"
   131  
   132    # And now we should receive the missed message
   133    missed_message = stream.resume
   134  
   135    if missed_message.data != {"text" => "Where are you, stream?"}
   136      fail "Expected missed message, got: #{missed_message.data}"
   137    end
   138  end
   139  
   140  # Start new session with history_since
   141  streaming_request(URI(url + "&history_since=#{start_time}")) do |stream|
   142    fail "Expected welcome" unless stream.resume.type == "welcome"
   143    fail "Expected confirmation" unless stream.resume.type == "confirm_subscription"
   144  
   145    # And now we should receive the missed messages
   146    %w[
   147      Hello,\ stream!
   148      Where\ are\ you,\ stream?
   149    ].each do |msg|
   150      missed_message = stream.resume
   151  
   152      if missed_message.data != {"text" => msg}
   153        fail "Expected missed message #{msg}, got: #{missed_message.data}"
   154      end
   155    end
   156  
   157    # And, finally, the history confirmation
   158    fail "Expected history confirmation" unless stream.resume.type == "confirm_history"
   159  end