{"id":1128,"date":"2017-03-24T19:33:18","date_gmt":"2017-03-25T02:33:18","guid":{"rendered":"http:\/\/www.wiredatom.com\/blog\/?p=1128"},"modified":"2017-03-24T19:41:49","modified_gmt":"2017-03-25T02:41:49","slug":"websockets-from-ember-rails-nginx-with-aws-elasticache-redis","status":"publish","type":"post","link":"https:\/\/www.wiredatom.com\/blog\/2017\/03\/24\/websockets-from-ember-rails-nginx-with-aws-elasticache-redis\/","title":{"rendered":"WebSockets from Ember, Rails, Nginx with AWS ElastiCache Redis"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Screen-Shot-2017-03-24-at-7.33.50-PM.png\" alt=\"\" width=\"269\" height=\"180\" class=\"alignleft size-full wp-image-1132\" srcset=\"https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Screen-Shot-2017-03-24-at-7.33.50-PM.png 269w, https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Screen-Shot-2017-03-24-at-7.33.50-PM-150x100.png 150w\" sizes=\"auto, (max-width: 269px) 85vw, 269px\" \/><\/p>\n<p>When we finally upgraded our Rails application to v5, plans were put in place to take advantage of its WebSockets capabilities.<\/p>\n<p>Locally on development machines, things seem to chug along smoothly until it all broke down on our staging stack.<\/p>\n<p>Here&#8217;s the basic setup:<\/p>\n<p>* Rails 5.0.x<br \/>\n* Ember 2.x (using <a href=\"https:\/\/github.com\/algonauti\/ember-cable\" target=\"_blank\">Ember Cable<\/a> for WebSockets)<br \/>\n* Puma<br \/>\n* Nginx (for reverse proxying Ember calls to Rails + serving Ember app)<br \/>\n* AWS ElastiCache Redis<br \/>\n* Postgres hosted on AWS RDS<br \/>\n* EC2 instance + ELB + custom security group&#8230; etc.<br \/>\n* Entire stack is orchestrated with Chef running on AWS OpsWorks<\/p>\n<p>I didn&#8217;t think the set up was anything out of the ordinary per se. The only exception is probably my choice of using ElastiCache instead of having an EC2 server running our own Redis instance (or having it hosted at Redis Labs or something). But other than that, it&#8217;s pretty vanilla.<\/p>\n<p>Immediately we rant into trouble having Ember making a proper connection. We kept seeing this error:<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><div class=\"bash codecolorer\">WebSocket connection to <span class=\"st_h\">'ws:\/\/staging.domain.name\/cable'<\/span> failed: Connection closed before receiving a handshake response<\/div><\/div>\n<p>First thing first, we had to make sure our <code class=\"codecolorer text default\"><span class=\"text\">\/cable<\/span><\/code> endpoint was proxied properly so that Rails can answer the calls. So in our main Nginx config file, we added:<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/>7<br \/>8<br \/>9<br \/>10<br \/><\/div><\/td><td><div class=\"bash codecolorer\">location <span class=\"sy0\">\/<\/span>cable <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; proxy_set_header &nbsp; X-Real-IP <span class=\"re1\">$remote_addr<\/span>;<br \/>\n&nbsp; &nbsp; proxy_set_header &nbsp; X-Forwarded-For <span class=\"re1\">$proxy_add_x_forwarded_for<\/span>;<br \/>\n&nbsp; &nbsp; proxy_set_header &nbsp; Host <span class=\"re1\">$http_host<\/span>;<br \/>\n&nbsp; &nbsp; proxy_set_header &nbsp; Upgrade <span class=\"re1\">$http_upgrade<\/span>;<br \/>\n&nbsp; &nbsp; proxy_set_header &nbsp; Connection <span class=\"st0\">&quot;Upgrade&quot;<\/span>;<br \/>\n&nbsp; &nbsp; proxy_redirect &nbsp; &nbsp; off;<br \/>\n&nbsp; &nbsp; proxy_http_version <span class=\"nu0\">1.1<\/span>;<br \/>\n&nbsp; &nbsp; proxy_pass &nbsp; &nbsp; &nbsp; &nbsp; http:<span class=\"sy0\">\/\/<\/span>app_server;<br \/>\n<span class=\"br0\">&#125;<\/span><\/div><\/td><\/tr><\/tbody><\/table><\/div>\n<p>It still didn&#8217;t quite work.<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/><\/div><\/td><td><div class=\"bash codecolorer\">I, <span class=\"br0\">&#91;<\/span><span class=\"nu0\">2017<\/span>-03-22T18:<span class=\"nu0\">46<\/span>:<span class=\"nu0\">39.166623<\/span> <span class=\"co0\">#4175] &nbsp;INFO -- : Started GET &quot;\/cable&quot; for 127.0.0.1 at 2017-03-22 18:46:39 +0000<\/span><br \/>\nI, <span class=\"br0\">&#91;<\/span><span class=\"nu0\">2017<\/span>-03-22T18:<span class=\"nu0\">46<\/span>:<span class=\"nu0\">39.167699<\/span> <span class=\"co0\">#4175] &nbsp;INFO -- : Started GET &quot;\/cable\/&quot; [WebSocket] for 127.0.0.1 at 2017-03-22 18:46:39 +0000<\/span><br \/>\nE, <span class=\"br0\">&#91;<\/span><span class=\"nu0\">2017<\/span>-03-22T18:<span class=\"nu0\">46<\/span>:<span class=\"nu0\">39.167837<\/span> <span class=\"co0\">#4175] ERROR -- : Request origin not allowed: https:\/\/staging.domain.name<\/span><br \/>\nE, <span class=\"br0\">&#91;<\/span><span class=\"nu0\">2017<\/span>-03-22T18:<span class=\"nu0\">46<\/span>:<span class=\"nu0\">39.167950<\/span> <span class=\"co0\">#4175] ERROR -- : Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE:)<\/span><br \/>\nI, <span class=\"br0\">&#91;<\/span><span class=\"nu0\">2017<\/span>-03-22T18:<span class=\"nu0\">46<\/span>:<span class=\"nu0\">39.168064<\/span> <span class=\"co0\">#4175] &nbsp;INFO -- : Finished &quot;\/cable\/&quot; [WebSocket] for 127.0.0.1 at 2017-03-22 18:46:39 +0000<\/span><\/div><\/td><\/tr><\/tbody><\/table><\/div>\n<p>Obviously we need to specify in Rails that our Staging subdomain, along with its <code class=\"codecolorer text default\"><span class=\"text\">https<\/span><\/code> protocol should be allowed. So we added that to our Rails <code class=\"codecolorer text default\"><span class=\"text\">\/config\/environments\/staging.rb<\/span><\/code> file.<\/p>\n<div class=\"codecolorer-container ruby railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><div class=\"ruby codecolorer\">config.<span class=\"me1\">action_cable<\/span>.<span class=\"me1\">allowed_request_origins<\/span> = <span class=\"sy0\">%<\/span>w<span class=\"br0\">&#40;<\/span>https:<span class=\"sy0\">\/\/<\/span>staging.<span class=\"me1\">domain<\/span>.<span class=\"me1\">name<\/span> staging.<span class=\"me1\">domain<\/span>.<span class=\"me1\">name<\/span><span class=\"br0\">&#41;<\/span><\/div><\/div>\n<p>Once we squared that away, we were still left with that dreadful error on line #4 above.<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><div class=\"bash codecolorer\">Failed to upgrade to WebSocket <span class=\"br0\">&#40;<\/span>REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE:<span class=\"br0\">&#41;<\/span><\/div><\/div>\n<p>Somehow, the <code class=\"codecolorer text default\"><span class=\"text\">$http_upgrade<\/span><\/code> variable in Nginx is simply not being set despite browser&#8217;s request. As it turns out, WebSocket traffic isn&#8217;t quite HTTP\/HTTPS. The devil is in the details of the AWS ELB (load balancer).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Screen-Shot-2017-03-24-at-7.02.18-PM.png\" alt=\"\" width=\"698\" height=\"116\" class=\"aligncenter size-full wp-image-1130\" srcset=\"https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Screen-Shot-2017-03-24-at-7.02.18-PM.png 698w, https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Screen-Shot-2017-03-24-at-7.02.18-PM-150x25.png 150w, https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Screen-Shot-2017-03-24-at-7.02.18-PM-300x50.png 300w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px\" \/><\/p>\n<p>I simply swapped out <code class=\"codecolorer text default\"><span class=\"text\">HTTPS<\/span><\/code> with <code class=\"codecolorer text default\"><span class=\"text\">SSL<\/span><\/code>. And <strong>BOOM!<\/strong> Like that, it worked:<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><div class=\"bash codecolorer\">Successfully upgraded to WebSocket <span class=\"br0\">&#40;<\/span>REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket<span class=\"br0\">&#41;<\/span><\/div><\/div>\n<p>Now connection was being made properly. Except for this one little thing: The browser had to keep reinitialize the connection every few seconds. And we noticed the UI simply wasn&#8217;t being updated despite changes to data. Sigh&#8230;<\/p>\n<p>Next stop was to test if the EC2 instance itself was actually talking to AWS ElastiCache. There are a couple of ways to try this:<\/p>\n<p>In Rails:<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/><\/div><\/td><td><div class=\"bash codecolorer\">irb<span class=\"br0\">&#40;<\/span>main<span class=\"br0\">&#41;<\/span>:001:<span class=\"nu0\">0<\/span><span class=\"sy0\">&gt;<\/span> redis = Redis.new<span class=\"br0\">&#40;<\/span>:host =<span class=\"sy0\">&gt;<\/span> <span class=\"st_h\">'my-elasticache-instance-identifier.0001.usw2.cache.amazonaws.com'<\/span>, :port =<span class=\"sy0\">&gt;<\/span> <span class=\"nu0\">6379<\/span><span class=\"br0\">&#41;<\/span><br \/>\n=<span class=\"sy0\">&gt;<\/span> <span class=\"co0\">#&lt; redis client v3.3.3 for redis:\/\/my-elasticache-instance-identifier.0001.usw2.cache.amazonaws.com:6379\/0&gt;<\/span><br \/>\nirb<span class=\"br0\">&#40;<\/span>main<span class=\"br0\">&#41;<\/span>:002:<span class=\"nu0\">0<\/span><span class=\"sy0\">&gt;<\/span> redis.ping<br \/>\nRedis::CannoConnectError: Error connecting to Redis on my-elasticache-instance-identifier.001.usw2.cache.amazonaws.com:<span class=\"nu0\">6379<\/span> <span class=\"br0\">&#40;<\/span>Redis::TimeoutError<span class=\"br0\">&#41;<\/span><br \/>\n...<\/div><\/td><\/tr><\/tbody><\/table><\/div>\n<p>Using Telnet:<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/><\/div><\/td><td><div class=\"bash codecolorer\">$ telnet my-elasticache-instance-identifier.0001.usw2.cache.amazonaws.com <span class=\"nu0\">6379<\/span><br \/>\nTrying <span class=\"sy0\">\/<\/span><span class=\"kw2\">ip<\/span> ADDRESS<span class=\"sy0\">\/<\/span>...<br \/>\n<span class=\"co0\"># eventually times out<\/span><\/div><\/td><\/tr><\/tbody><\/table><\/div>\n<p>OMFG. WHAT ELSE IS WRONG NOW??!!<\/p>\n<p>As it turns out, the devil, again, is in the details. This time, it&#8217;s in AWS Security Groups.<\/p>\n<p>AWS resources are only allowed to talk to each other if said resources are within the same security groups (among other things). In this case, AWS ElastiCache has a <code class=\"codecolorer text default\"><span class=\"text\">default<\/span><\/code> security group, which we&#8217;re not allowed to change (not to my knowledge anyway). So the trick was to make sure that our OpsWorks <code class=\"codecolorer text default\"><span class=\"text\">Layers<\/span><\/code> also have been assigned the <code class=\"codecolorer text default\"><span class=\"text\">default<\/span><\/code> security group. Now, OpsWorks documentation says that you must reboot each instance within the layer for the new security group settings to kick in. But the truth is I had to spawn brand new instances for the new security group to stick. But once that was done, everything finally started to gel:<\/p>\n<div class=\"codecolorer-container bash railscasts\" style=\"overflow:auto;white-space:nowrap;width:680px;\"><table cellspacing=\"0\" cellpadding=\"0\"><tbody><tr><td class=\"line-numbers\"><div>1<br \/>2<br \/>3<br \/>4<br \/>5<br \/>6<br \/><\/div><\/td><td><div class=\"bash codecolorer\">irb<span class=\"br0\">&#40;<\/span>main<span class=\"br0\">&#41;<\/span>:001:<span class=\"nu0\">0<\/span><span class=\"sy0\">&gt;<\/span> redis = Redis.new<span class=\"br0\">&#40;<\/span>:host =<span class=\"sy0\">&gt;<\/span> <span class=\"st_h\">'my-elasticache-instance-identifier.0001.usw2.cache.amazonaws.com'<\/span>, :port =<span class=\"sy0\">&gt;<\/span> <span class=\"nu0\">6379<\/span><span class=\"br0\">&#41;<\/span><br \/>\n=<span class=\"sy0\">&gt;<\/span> <span class=\"co0\">#&lt;redis client v3.3.3 for redis:\/\/my-elasticache-instance-identifier.0001.usw2.cache.amazonaws.com:6379\/0&gt;<\/span><br \/>\nirb<span class=\"br0\">&#40;<\/span>main<span class=\"br0\">&#41;<\/span>:002:<span class=\"nu0\">0<\/span><span class=\"sy0\">&gt;<\/span> redis.ping<br \/>\n=<span class=\"sy0\">&gt;<\/span> <span class=\"st0\">&quot;PONG&quot;<\/span><br \/>\n...<br \/>\n<span class=\"sy0\">&lt;\/<\/span>redis<span class=\"sy0\">&gt;<\/span><\/div><\/td><\/tr><\/tbody><\/table><\/div>\n<p>I hate technology sometimes.<\/p>\n<p>References:<br \/>\n* https:\/\/blog.jverkamp.com\/2015\/07\/20\/configuring-websockets-behind-an-aws-elb\/ (<a href=\"https:\/\/www.wiredatom.com\/blog\/wp-content\/uploads\/2017\/03\/Configuring-Websockets-behind-an-AWS-ELB-_-jverkamp.pdf\" target=\"_blank\">cached in PDF<\/a>)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When we finally upgraded our Rails application to v5, plans were put in place to take advantage of its WebSockets capabilities. Locally on development machines, things seem to chug along smoothly until it all broke down on our staging stack. Here&#8217;s the basic setup: * Rails 5.0.x * Ember 2.x (using Ember Cable for WebSockets) &hellip; <a href=\"https:\/\/www.wiredatom.com\/blog\/2017\/03\/24\/websockets-from-ember-rails-nginx-with-aws-elasticache-redis\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;WebSockets from Ember, Rails, Nginx with AWS ElastiCache Redis&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[21,10,14],"tags":[],"class_list":["post-1128","post","type-post","status-publish","format-standard","hentry","category-coding","category-geek-stuff","category-linuxunix"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p54IqZ-ic","_links":{"self":[{"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/posts\/1128","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/comments?post=1128"}],"version-history":[{"count":0,"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/posts\/1128\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/media?parent=1128"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/categories?post=1128"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wiredatom.com\/blog\/wp-json\/wp\/v2\/tags?post=1128"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}