fake_mqtt_server.rb 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #!/usr/bin/env ruby
  2. #
  3. # This is a 'fake' MQTT server to help with testing client implementations
  4. #
  5. # See https://github.com/njh/ruby-mqtt/blob/master/spec/fake_server.rb
  6. #
  7. # It behaves in the following ways:
  8. # * Responses to CONNECT with a successful CONACK
  9. # * Responses to PUBLISH by echoing the packet back
  10. # * Responses to SUBSCRIBE with SUBACK and a PUBLISH to the topic
  11. # * Responses to PINGREQ with PINGRESP
  12. # * Responses to DISCONNECT by closing the socket
  13. #
  14. # It has the following restrictions
  15. # * Doesn't deal with timeouts
  16. # * Only handles a single connection at a time
  17. #
  18. $:.unshift File.dirname(__FILE__)+'/../lib'
  19. require 'logger'
  20. require 'socket'
  21. require 'mqtt'
  22. class MQTT::FakeServer
  23. attr_reader :address, :port
  24. attr_reader :last_publish
  25. attr_reader :thread
  26. attr_reader :pings_received
  27. attr_accessor :just_one
  28. attr_accessor :logger
  29. # Create a new fake MQTT server
  30. #
  31. # If no port is given, bind to a random port number
  32. # If no bind address is given, bind to localhost
  33. def initialize(bind_address='127.0.0.1')
  34. @address = bind_address
  35. end
  36. # Get the logger used by the server
  37. def logger
  38. @logger ||= Logger.new(STDOUT)
  39. end
  40. # Start the thread and open the socket that will process client connections
  41. def start
  42. @socket ||= TCPServer.new(@address, 0)
  43. @address = @socket.addr[3]
  44. @port = @socket.addr[1]
  45. @thread ||= Thread.new do
  46. logger.info "Started a fake MQTT server on #{@address}:#{@port}"
  47. @times = 0
  48. loop do
  49. @times += 1
  50. # Wait for a client to connect
  51. client = @socket.accept
  52. @pings_received = 0
  53. handle_client(client)
  54. break if just_one
  55. end
  56. end
  57. end
  58. # Stop the thread and close the socket
  59. def stop
  60. logger.info "Stopping fake MQTT server"
  61. @thread.kill if @thread and @thread.alive?
  62. @thread = nil
  63. @socket.close unless @socket.nil?
  64. @socket = nil
  65. end
  66. # Start the server thread and wait for it to finish (possibly never)
  67. def run
  68. start
  69. begin
  70. @thread.join
  71. rescue Interrupt
  72. stop
  73. end
  74. end
  75. protected
  76. # Given a client socket, process MQTT packets from the client
  77. def handle_client(client)
  78. loop do
  79. packet = MQTT::Packet.read(client)
  80. logger.debug packet.inspect
  81. case packet
  82. when MQTT::Packet::Connect
  83. client.write MQTT::Packet::Connack.new(:return_code => 0)
  84. when MQTT::Packet::Publish
  85. client.write packet
  86. @last_publish = packet
  87. when MQTT::Packet::Subscribe
  88. client.write MQTT::Packet::Suback.new(
  89. :message_id => packet.message_id,
  90. :granted_qos => 0
  91. )
  92. topic = packet.topics[0][0]
  93. case @times
  94. when 1, ->x { x >= 3 }
  95. # Deliver retained messages
  96. client.write MQTT::Packet::Publish.new(
  97. :topic => topic,
  98. :payload => "did you know about #{topic}",
  99. :retain => true
  100. )
  101. client.write MQTT::Packet::Publish.new(
  102. :topic => topic,
  103. :payload => "hello #{topic}",
  104. :retain => true
  105. )
  106. when 2
  107. # Deliver a still retained message
  108. client.write MQTT::Packet::Publish.new(
  109. :topic => topic,
  110. :payload => "hello #{topic}",
  111. :retain => true
  112. )
  113. # Deliver a fresh message
  114. client.write MQTT::Packet::Publish.new(
  115. :topic => topic,
  116. :payload => "did you know about #{topic}",
  117. :retain => false
  118. )
  119. end
  120. when MQTT::Packet::Pingreq
  121. client.write MQTT::Packet::Pingresp.new
  122. @pings_received += 1
  123. when MQTT::Packet::Disconnect
  124. client.close
  125. break
  126. end
  127. end
  128. rescue MQTT::ProtocolException => e
  129. logger.warn "Protocol error, closing connection: #{e}"
  130. client.close
  131. end
  132. end
  133. if __FILE__ == $0
  134. server = MQTT::FakeServer.new(MQTT::DEFAULT_PORT)
  135. server.logger.level = Logger::DEBUG
  136. server.run
  137. end