fake_mqtt_server.rb 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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(port=nil, bind_address='127.0.0.1')
  34. @port = port
  35. @address = bind_address
  36. end
  37. # Get the logger used by the server
  38. def logger
  39. @logger ||= Logger.new(STDOUT)
  40. end
  41. # Start the thread and open the socket that will process client connections
  42. def start
  43. @socket ||= TCPServer.new(@address, @port)
  44. @address = @socket.addr[3]
  45. @port = @socket.addr[1]
  46. @thread ||= Thread.new do
  47. logger.info "Started a fake MQTT server on #{@address}:#{@port}"
  48. @times = 0
  49. loop do
  50. @times += 1
  51. # Wait for a client to connect
  52. client = @socket.accept
  53. @pings_received = 0
  54. handle_client(client)
  55. break if just_one
  56. end
  57. end
  58. end
  59. # Stop the thread and close the socket
  60. def stop
  61. logger.info "Stopping fake MQTT server"
  62. @socket.close unless @socket.nil?
  63. @socket = nil
  64. @thread.kill if @thread and @thread.alive?
  65. @thread = nil
  66. end
  67. # Start the server thread and wait for it to finish (possibly never)
  68. def run
  69. start
  70. begin
  71. @thread.join
  72. rescue Interrupt
  73. stop
  74. end
  75. end
  76. protected
  77. # Given a client socket, process MQTT packets from the client
  78. def handle_client(client)
  79. loop do
  80. packet = MQTT::Packet.read(client)
  81. logger.debug packet.inspect
  82. case packet
  83. when MQTT::Packet::Connect
  84. client.write MQTT::Packet::Connack.new(:return_code => 0)
  85. when MQTT::Packet::Publish
  86. client.write packet
  87. @last_publish = packet
  88. when MQTT::Packet::Subscribe
  89. client.write MQTT::Packet::Suback.new(
  90. :message_id => packet.message_id,
  91. :granted_qos => 0
  92. )
  93. topic = packet.topics[0][0]
  94. case @times
  95. when 1, ->x { x >= 3 }
  96. # Deliver retained messages
  97. client.write MQTT::Packet::Publish.new(
  98. :topic => topic,
  99. :payload => "did you know about #{topic}",
  100. :retain => true
  101. )
  102. client.write MQTT::Packet::Publish.new(
  103. :topic => topic,
  104. :payload => "hello #{topic}",
  105. :retain => true
  106. )
  107. when 2
  108. # Deliver a still retained message
  109. client.write MQTT::Packet::Publish.new(
  110. :topic => topic,
  111. :payload => "hello #{topic}",
  112. :retain => true
  113. )
  114. # Deliver a fresh message
  115. client.write MQTT::Packet::Publish.new(
  116. :topic => topic,
  117. :payload => "did you know about #{topic}",
  118. :retain => false
  119. )
  120. end
  121. when MQTT::Packet::Pingreq
  122. client.write MQTT::Packet::Pingresp.new
  123. @pings_received += 1
  124. when MQTT::Packet::Disconnect
  125. client.close
  126. break
  127. end
  128. end
  129. rescue MQTT::ProtocolException => e
  130. logger.warn "Protocol error, closing connection: #{e}"
  131. client.close
  132. end
  133. end
  134. if __FILE__ == $0
  135. server = MQTT::FakeServer.new(MQTT::DEFAULT_PORT)
  136. server.logger.level = Logger::DEBUG
  137. server.run
  138. end