* fully supporting json concurrently with protocol buffers. specify ?pb=true to web URI

This commit is contained in:
Seth Call 2012-09-03 20:22:46 -05:00
parent 5b44da8ef2
commit e64f3a4d0c
4 changed files with 112 additions and 47 deletions

View File

@ -9,9 +9,17 @@ include JamWebsockets
jamenv = ENV['JAMENV']
jamenv ||= 'development'
config = YAML::load(File.open('config/application.yml'))[jamenv]
db_config = YAML::load(File.open('config/database.yml'))[jamenv]
bin_dir = File.expand_path(File.dirname(__FILE__))
app_config_file = File.join(bin_dir, '..', 'config', 'application.yml')
db_config_file = File.join(bin_dir, '..', 'config', 'database.yml')
# limit execution in base dir of project
#app_config_file = File.join('config', 'application.yml')
#db_config_file = File.join('config', 'database.yml')
config = YAML::load(File.open(app_config_file))[jamenv]
db_config = YAML::load(File.open(db_config_file))[jamenv]
if config["verbose"]
Logging.logger.root.level = :debug

View File

@ -5,7 +5,14 @@ require 'protocol_buffers/compiler'
class ProtocolBuffers::Message
def to_json(*args)
hash = {'json_class' => self.class.name}
json = to_json_ready_object()
json.to_json(*args)
end
def to_json_ready_object()
hash = {}
# simpler version, includes all fields in the output, using the default
# values if unset. also includes empty repeated fields as empty arrays.
@ -19,15 +26,26 @@ class ProtocolBuffers::Message
value = value_for_tag(field.tag)
hash[field.name] = value unless value.empty?
else
hash[field.name] = value_for_tag(field.tag) if value_for_tag?(field.tag)
if value_for_tag?(field.tag)
value = value_for_tag(field.tag)
if field.instance_of? ProtocolBuffers::Field::EnumField # if value is enum, resolve string value as ruby const
hash[field.name] = field.value_to_name[value]
else
proxy_class = field.instance_variable_get(:@proxy_class)
if proxy_class.nil?
hash[field.name] = value
else
hash[field.name] = value.to_json_ready_object
end
end
end
end
end
hash.to_json(*args)
return hash
end
def self.json_create(hash)
hash.delete('json_class')
# initialize takes a hash of { attribute_name => value } so you can just
# pass the hash into the constructor. but we're supposed to be showing off
# reflection, here. plus, that raises an exception if there is an unknown
@ -37,7 +55,15 @@ class ProtocolBuffers::Message
message = new
fields.each do |tag, field|
if value = hash[field.name.to_s]
message.set_value_for_tag(field.tag, value)
if value.instance_of? Hash # if the value is a Hash, descend down into PB hierachy
inner_class = field.instance_variable_get(:@proxy_class)
value = inner_class.json_create(value)
message.set_value_for_tag(field.tag, value)
elsif field.instance_of? ProtocolBuffers::Field::EnumField # if value is enum, resolve string value as ruby const
message.set_value_for_tag(field.tag, field.instance_variable_get(:@proxy_enum).const_get(value))
else
message.set_value_for_tag(field.tag, value)
end
end
end
message

View File

@ -3,12 +3,24 @@ require 'set'
require 'hot_bunnies'
require 'thread'
require 'json'
require 'eventmachine'
import java.util.concurrent.Executors
include Jampb
# add new field to client connection
module EventMachine
module WebSocket
class Connection < EventMachine::Connection
attr_accessor :encode_json
end
end
end
module JamWebsockets
class Router
attr_accessor :user_context_lookup, :session_context_lookup
@ -130,6 +142,7 @@ module JamWebsockets
@log.debug "received user-directed message for session: #{user_id}"
msg = Jampb::ClientMessage.parse(msg)
contexts.each do |context|
EM.schedule do
@log.debug "sending user message to #{context}"
@ -162,6 +175,7 @@ module JamWebsockets
@log.debug "received session-directed message for session: #{session_id}"
msg = Jampb::ClientMessage.parse(msg)
contexts.each do |context|
EM.schedule do
@log.debug "sending session message to #{context}"
@ -178,8 +192,12 @@ module JamWebsockets
end
def send_to_client(client, msg)
# this is so odd that this is necessary. but searching through the source code... it's all I could find in em-websocket
client.instance_variable_get(:@handler).send_frame(:binary, msg)
if client.encode_json
client.send(msg.to_json.to_s)
else
# this is so odd that this is necessary from an API perspective. but searching through the source code... it's all I could find in em-websocket for allowing a binary message to be sent
client.instance_variable_get(:@handler).send_frame(:binary, msg.to_s)
end
end
def cleanup()
@ -228,15 +246,21 @@ module JamWebsockets
@pending_clients.add(client)
end
is_json = false
# default to using json instead of pb
client.encode_json = true
client.onopen {
#binding.pry
@log.debug "client connected #{client}"
# check for '?pb' or '?pb=true' in url query parameters
query_pb = client.request["query"]["pb"]
p client.request["query"]
is_json = !!client.request["query"]["json"]
}
if !query_pb.nil? && (query_pb == "" || query_pb == "true")
client.encode_json = false
end
}
client.onclose {
@log.debug "Connection closed"
@ -263,12 +287,11 @@ module JamWebsockets
begin
if is_json
#{"type":100, "target":"server", "Login" : {"username":"hi"}}
if client.encode_json
#example: {"type":"LOGIN", "target":"server", "login" : {"username":"hi"}}
parse = JSON.parse(msg)
p parse
pb_msg = Jampb::ClientMessage.json_create(parse)
p pb_msg
self.route(pb_msg, client)
else
pb_msg = Jampb::ClientMessage.parse(msg.to_s)
self.route(pb_msg, client)
@ -277,7 +300,7 @@ module JamWebsockets
@log.info "ending client session deliberately due to malformed client behavior. reason=#{e}"
begin
# wrap the message up and send it down
error_msg = @message_factory.server_rejection_error(e.to_s).to_s
error_msg = @message_factory.server_rejection_error(e.to_s)
send_to_client(client, error_msg)
ensure
client.close_websocket
@ -289,7 +312,7 @@ module JamWebsockets
begin
# wrap the message up and send it down
error_msg = @message_factory.server_generic_error(e.to_s).to_s
error_msg = @message_factory.server_generic_error(e.to_s)
send_to_client(client, error_msg)
ensure
client.close_websocket
@ -385,9 +408,10 @@ module JamWebsockets
end
def handle_login(login, client)
username = login.username
token = login.token
password = login.password
username = login.username if login.value_for_tag(1)
password = login.password if login.value_for_tag(2)
token = login.token if login.value_for_tag(3)
user = valid_login(username, password, token)
@ -398,7 +422,7 @@ module JamWebsockets
# respond with LOGIN_ACK to let client know it was successful
#binding.pry
remote_port, remote_ip = Socket.unpack_sockaddr_in(client.get_peername)
login_ack = @message_factory.login_ack(remote_ip).to_s
login_ack = @message_factory.login_ack(remote_ip)
send_to_client(client, login_ack)
# remove from pending_queue
@ -440,13 +464,13 @@ module JamWebsockets
rescue => e
# send back a failure ack and bail
@log.debug "client requested non-existent session. client:#{client.request['origin']} user:#{context.user.email}"
login_jam_session = @message_factory.login_jam_session_ack(true, e.to_s).to_s
login_jam_session = @message_factory.login_jam_session_ack(true, e.to_s)
send_to_client(client, login_jam_session)
return
end
# respond with LOGIN_JAM_SESSION_ACK to let client know it was successful
login_jam_session = @message_factory.login_jam_session_ack(false, nil).to_s
login_jam_session = @message_factory.login_jam_session_ack(false, nil)
send_to_client(client, login_jam_session)
# send 'new client' message to other members in the session
@ -464,18 +488,8 @@ module JamWebsockets
def valid_login(username, password, token)
if !username.nil? and !password.nil?
# attempt login with username and password
user = User.find_by_email(username)
if !user.nil? && user.authenticate(password)
@log.debug "#{username} login via password"
return user
else
@log.debug "#{username} login failure"
return nil
end
elsif !token.nil?
if !token.nil? && token != ''
@log.debug "logging in via token"
# attempt login with token
user = User.find_by_remember_token(token)
@ -483,10 +497,24 @@ module JamWebsockets
@log.debug "no user found with token"
return false
else
@log.debug "#{username} login via token"
@log.debug "#{user} login via token"
return user
end
elsif !username.nil? and !password.nil?
@log.debug "logging in via user/pass '#{username}' '#{password}'"
# attempt login with username and password
user = User.find_by_email(username)
if !user.nil? && user.authenticate(password)
@log.debug "#{user} login via password"
return user
else
@log.debug "#{username} login failure"
return nil
end
else
else
raise SessionError, 'no login data was found in Login message'
end

View File

@ -2,7 +2,7 @@ require 'spec_helper'
require 'thread'
LoginClient = Class.new do
attr_accessor :onmsgblock, :onopenblock
attr_accessor :onmsgblock, :onopenblock, :encode_json
def initiaize()
@ -43,9 +43,10 @@ def login(router, user, password)
login_ack = message_factory.login_ack("127.0.0.1")
router.should_receive(:send_to_client).with(client, login_ack.to_s)
router.should_receive(:send_to_client).with(client, login_ack)
client.should_receive(:onclose)
client.should_receive(:onerror)
client.should_receive(:request).and_return({ "query" => { "pb" => "true" } })
client.should_receive(:get_peername).and_return("\x00\x02\x93\v\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00")
@router.new_client(client)
@ -65,7 +66,7 @@ def login_jam_session(router, client, jam_session)
message_factory = MessageFactory.new
login_jam_session = message_factory.login_jam_session(jam_session.id)
login_ack = message_factory.login_jam_session_ack(false, nil);
router.should_receive(:send_to_client).with(client, login_ack.to_s)
router.should_receive(:send_to_client).with(client, login_ack)
client.onmsgblock.call login_jam_session.to_s
end
@ -98,6 +99,7 @@ describe Router do
client.should_receive(:onclose)
client.should_receive(:onerror)
client.should_receive(:onmessage)
client.should_receive(:encode_json=)
@router.new_client(client)
@ -128,7 +130,7 @@ describe Router do
it "should not allow login of bogus user", :mq => true do
TestClient = Class.new do
attr_accessor :onmsgblock, :onopenblock
attr_accessor :onmsgblock, :onopenblock, :encode_json
def initiaize()
@ -153,10 +155,11 @@ describe Router do
error_msg = message_factory.server_rejection_error("invalid login")
@router.should_receive(:send_to_client).with(client, error_msg.to_s)
@router.should_receive(:send_to_client).with(client, error_msg)
client.should_receive(:close_websocket)
client.should_receive(:onclose)
client.should_receive(:onerror)
client.should_receive(:request).and_return({ "query" => { "pb" => "true" } })
@router.new_client(client)
@ -184,7 +187,7 @@ describe Router do
user2 = FactoryGirl.create(:user) # in the jam session
user3 = FactoryGirl.create(:user) # not in the jam session
jam_session = FactoryGirl.create(:jam_session, :user => user1)
jam_session = FactoryGirl.create(:jam_session, :creator => user1)
jam_session_member1 = FactoryGirl.create(:jam_session_member, :user => user1, :jam_session => jam_session)
jam_session_member2 = FactoryGirl.create(:jam_session_member, :user => user2, :jam_session => jam_session)
@ -202,7 +205,7 @@ describe Router do
user1 = FactoryGirl.create(:user) # in the jam session
user2 = FactoryGirl.create(:user) # in the jam session
jam_session = FactoryGirl.create(:jam_session, :user => user1)
jam_session = FactoryGirl.create(:jam_session, :creator => user1)
jam_session_member1 = FactoryGirl.create(:jam_session_member, :user => user1, :jam_session => jam_session)
jam_session_member2 = FactoryGirl.create(:jam_session_member, :user => user2, :jam_session => jam_session)