ruby on rails - real - Conectando a ActionCable desde la aplicación iOS
ruby on rails real time updates (3)
// abrir la conexión de socket primero
var ws = new WebSocket("ws://localhost:3000/cable");
// suscribirse al canal
// ''i'' debería estar en json
var i = { ''command'': ''subscribe'', ''identifier'': {''channel'':''ProfileChannel'', ''Param_1'': ''Value_1'',...}};
ws.send(i);
// Después, recibirás datos dentro de la función ''onmessage''.
¡Aclamaciones!
He estado atrapado en esto todo el día. Tengo la aplicación de ejemplo ActionCable muy simple (la aplicación de chat) de David Heinemeier Hansson funcionando correctamente ( https://www.youtube.com/watch?v=n0WUjGkDFS0 ).
Estoy intentando conectar la conexión web con una aplicación de iPhone. Puedo recibir pings cuando me conecto a ws://localhost:3000/cable
, pero no estoy seguro de cómo suscribirme a canales fuera de un contexto javascript.
En realidad, aquí está el fragmento de código que estoy usando para conectarme al cable de acción.
function WebSocketTest()
{
var ws = new WebSocket("ws://localhost:3000/cable");
ws.onopen = function(data)
{
var i = JSON.stringify({"command":"subscribe" , "identifier": JSON.stringify({"channel":"CHANNEL_NAME"})});
// send data request
var j = JSON.stringify({"command":"message","identifier": JSON.stringify({"channel":"CHANNEL_NAME"}),"data": {"message":"Hello World","action": "METHOD_NAME_IN_CHANNEL","email": "[email protected]", "token" : "xxxxxxxxxxxxx", "id": {"id_message" : "something", "ddd" : "something"}}})
var response = ws.send(i);
setTimeout(function()
{
var response1 = ws.send(j);
}, 1000);
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
};
}
Oh hombre, también pasé por este problema después de leer esta pregunta.
Después de un tiempo, finalmente encontré esta página mágica de Github:
https://github.com/rails/rails/issues/22675
Entiendo que este parche rompería algunas pruebas. Eso no me sorprende. Pero creo que el problema original sigue siendo relevante y no debería cerrarse.
El siguiente JSON enviado al servidor debería tener éxito:
{"comando": "suscribirse", "identificador": {"canal": "ChangesChannel"}}
¡No es asi! En su lugar debes enviar esto:
{"comando": "suscribirse", "identificador": "{/" canal / ": /" ChangesChannel / "}"}
Finalmente obtuve la aplicación iOS para suscribirme al canal de la sala siguiendo la sugerencia del usuario de Github sobre el problema de Rails.
Mi configuración es la siguiente:
- C objetivo
- Usando el framework PocketSocket para hacer conexión de socket web
- Rieles 5 RC1
- Ruby 2.2.4p230
Supongo que sabes cómo usar Cocoapods para instalar PocketSocket.
Los códigos relevantes son los siguientes:
ViewController.h
#import <PocketSocket/PSWebSocket.h>
@interface ViewController : UIViewController <PSWebSocketDelegate, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>
@property (nonatomic, strong) PSWebSocket *socket;
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initViews];
[self initConstraints];
[self initSocket];
}
-(void)initSocket
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"ws://localhost:3000/cable"]];
self.socket = [PSWebSocket clientSocketWithRequest:request];
self.socket.delegate = self;
[self.socket open];
}
-(void)joinChannel:(NSString *)channelName
{
NSString *strChannel = @"{ /"channel/": /"RoomChannel/" }";
id data = @{
@"command": @"subscribe",
@"identifier": strChannel
};
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:nil];
NSString * myString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(@"myString= %@", myString);
[self.socket send:myString];
}
#pragma mark - PSWebSocketDelegate Methods -
-(void)webSocketDidOpen:(PSWebSocket *)webSocket
{
NSLog(@"The websocket handshake completed and is now open!");
[self joinChannel:@"RoomChannel"];
}
-(void)webSocket:(PSWebSocket *)webSocket didReceiveMessage:(id)message
{
NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSString *messageType = json[@"type"];
if(![messageType isEqualToString:@"ping"] && ![messageType isEqualToString:@"welcome"])
{
NSLog(@"The websocket received a message: %@", json[@"message"]);
[self.messages addObject:json[@"message"]];
[self.tableView reloadData];
}
}
-(void)webSocket:(PSWebSocket *)webSocket didFailWithError:(NSError *)error
{
NSLog(@"The websocket handshake/connection failed with an error: %@", error);
}
-(void)webSocket:(PSWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
NSLog(@"The websocket closed with code: %@, reason: %@, wasClean: %@", @(code), reason, (wasClean) ? @"YES": @"NO");
}
Nota IMPORTANTE:
También indagué un poco en el código fuente de la clase de suscripción:
def add(data)
id_key = data[''identifier'']
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
subscription_klass = connection.server.channel_classes[id_options[:channel]]
if subscription_klass
subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options)
else
logger.error "Subscription class not found (#{data.inspect})"
end
end
Observe la línea:
connection.server.channel_classes[id_options[:channel]]
Necesitamos usar el nombre de la clase para el canal.
El video de DHH en YouTube usa "room_channel" para el nombre de la sala, pero el archivo de clase para ese canal se llama "RoomChannel".
Necesitamos usar el nombre de la clase, no el nombre de la instancia del canal.
Enviando mensajes
En caso de que otros también quieran saber cómo enviar mensajes, aquí está mi código de iOS para enviar un mensaje al servidor:
-(void)sendMessage:(NSString *)message
{
NSString *strMessage = [[NSString alloc] initWithFormat:@"{ /"action/": /"speak/", /"message/": /"%@/" }", message];
NSString *strChannel = @"{ /"channel/": /"RoomChannel/" }";
id data = @{
@"command": @"message",
@"identifier": strChannel,
@"data": strMessage
};
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:nil];
NSString * myString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(@"myString= %@", myString);
[self.socket send:myString];
}
Esto supone que ha conectado su UITextField para manejar la pulsación de la tecla de retorno o algún botón "enviar" en algún lugar de su interfaz de usuario.
Toda esta aplicación de demostración fue un truco rápido, obviamente, si lo hiciera en una aplicación real, haría mi código más limpio, más reutilizable y lo abstraería en una clase.
Conexión al servidor Rails desde un dispositivo iPhone real:
Para que la aplicación del iPhone hable con el servidor Rails en un dispositivo real, no con el simulador de iPhone.
Haz lo siguiente:
- Verifique la dirección TCP / IP de su computadora. En mi iMac, por ejemplo, podría ser 10.1.1.10 en algunos días (puede cambiar automáticamente en el futuro si se usa DHCP).
Edite el archivo
config > environment > development.rb
su Rail y colóquelo en la siguiente línea en algún lugar como antes de la palabra claveend
:Rails.application.config.action_cable.allowed_request_origins = [''http://10.1.1.10:3000'']
Inicie su servidor Rails usando el siguiente comando:
rails server -b 0.0.0.0
Construye y ejecuta tu aplicación de iPhone en el dispositivo iPhone. Debes poder conectarte y enviar mensajes ahora: D
Obtuve estas soluciones de los siguientes enlaces:
Origen de solicitud no permitido: http: // localhost: 3001 cuando se usa Rails5 y ActionCable
Servidor Rails 4.2; IP privada y pública no funciona
Espero que ayude a otros en el futuro.