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