fake_mqtt_server.rb 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  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. loop do
  49. # Wait for a client to connect
  50. client = @socket.accept
  51. @pings_received = 0
  52. handle_client(client)
  53. break if just_one
  54. end
  55. end
  56. end
  57. # Stop the thread and close the socket
  58. def stop
  59. logger.info "Stopping fake MQTT server"
  60. @socket.close unless @socket.nil?
  61. @socket = nil
  62. @thread.kill if @thread and @thread.alive?
  63. @thread = nil
  64. end
  65. # Start the server thread and wait for it to finish (possibly never)
  66. def run
  67. start
  68. begin
  69. @thread.join
  70. rescue Interrupt
  71. stop
  72. end
  73. end
  74. protected
  75. # Given a client socket, process MQTT packets from the client
  76. def handle_client(client)
  77. loop do
  78. packet = MQTT::Packet.read(client)
  79. logger.debug packet.inspect
  80. case packet
  81. when MQTT::Packet::Connect
  82. client.write MQTT::Packet::Connack.new(:return_code => 0)
  83. when MQTT::Packet::Publish
  84. client.write packet
  85. @last_publish = packet
  86. when MQTT::Packet::Subscribe
  87. client.write MQTT::Packet::Suback.new(
  88. :message_id => packet.message_id,
  89. :granted_qos => 0
  90. )
  91. topic = packet.topics[0][0]
  92. client.write MQTT::Packet::Publish.new(
  93. :topic => topic,
  94. :payload => "hello #{topic}",
  95. :retain => true
  96. )
  97. client.write MQTT::Packet::Publish.new(
  98. :topic => topic,
  99. :payload => "did you know about #{topic}",
  100. :retain => true
  101. )
  102. when MQTT::Packet::Pingreq
  103. client.write MQTT::Packet::Pingresp.new
  104. @pings_received += 1
  105. when MQTT::Packet::Disconnect
  106. client.close
  107. break
  108. end
  109. end
  110. rescue MQTT::ProtocolException => e
  111. logger.warn "Protocol error, closing connection: #{e}"
  112. client.close
  113. end
  114. end
  115. if __FILE__ == $0
  116. server = MQTT::FakeServer.new(MQTT::DEFAULT_PORT)
  117. server.logger.level = Logger::DEBUG
  118. server.run
  119. end