Connecting to ActionCable from iOS app

回眸只為那壹抹淺笑 提交于 2019-11-30 12:01:49

问题


I have been stuck on this all day. I have the very simple ActionCable example app (the chat app) by David Heinemeier Hansson working correctly (https://www.youtube.com/watch?v=n0WUjGkDFS0).

I am trying to hit the websocket connection with an iPhone app. I am able to receive pings when I connect to ws://localhost:3000/cable, but I'm not quite sure how to subscribe to channels from outside of a javascript context.


回答1:


Oh man, I went through this problem too after reading this question.

After a while, I finally found this magical Github issue page:

https://github.com/rails/rails/issues/22675

I do understand that this patch would break some tests. That is not surprising to me. But the original issue I believe is still relevant and shouldn't be closed.

The following JSON sent to the server should succeed:

{"command": "subscribe","identifier":{"channel":"ChangesChannel"}}

It does not! Instead you must send this:

{"command": "subscribe","identifier":"{\"channel\":\"ChangesChannel\"}"}

I finally got the iOS app to subscribe to room channel following the Github user suggestion about Rails problem.

My setup is as follow:

  • Objective C
  • Using PocketSocket framework for making web socket connection
  • Rails 5 RC1
  • Ruby 2.2.4p230

I assume you know how to use Cocoapods to install PocketSocket.

The relevant codes are as follow:

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");
}

Important Note:

I also digged a bit into the subscription class source code:

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

Notice the line:

connection.server.channel_classes[id_options[:channel]]

We need to use the name of the class for the channel.

The DHH youtube video uses "room_channel" for the room name but the class file for that channel is named "RoomChannel".

We need to use the class name not the instance name of the channel.

Sending Messages

Just in case others want to know how to send messages also, here is my iOS code to send a message to the server:

-(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];
}

This assumes you've hooked up your UITextField to handle pressing the return key or some "send" button somewhere on your UI.

This whole demo app was a quick hack, obviously, if I was to do it in a real app, I would make my code more cleaner, more reusable and abstract it into a class altogether.

Connecting to Rails server from real iPhone device:

In order for iPhone app to talk to Rails server on real device, not iPhone simulator.

Do the following:

  1. Check your computer's TCP/IP address. On my iMac for example, it might be 10.1.1.10 on some days (can change automatically in the future if using DHCP).
  2. Edit your Rail's config > environment > development.rb file and put in the following line somewhere like before the end keyword:

    Rails.application.config.action_cable.allowed_request_origins = ['http://10.1.1.10:3000']

  3. Start your Rails server using following command:

    rails server -b 0.0.0.0

  4. Build and run your iPhone app onto the iPhone device. You should be able to connect and send messages now :D

I got these solutions from following links:

Request origin not allowed: http://localhost:3001 when using Rails5 and ActionCable

Rails 4.2 server; private and public ip not working

Hope that helps others in the future.




回答2:


// open socket connection first

var ws = new WebSocket("ws://localhost:3000/cable");  

// subscribe to channel
// 'i' should be in json

var i = { 'command': 'subscribe', 'identifier': {'channel':'ProfileChannel', 'Param_1': 'Value_1',...}};

 ws.send(i);

// After that you'll receive data inside the 'onmessage' function.

Cheers!




回答3:


Actually, Here is the code snippet that i'm using to connect to action cable.

  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": "abc@xyz.com",  "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;
         };
      }


来源:https://stackoverflow.com/questions/35145429/connecting-to-actioncable-from-ios-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!