faye / websocket-driver-ruby

WebSocket protocol handler with pluggable I/O
Other
223 stars 43 forks source link

Plain HTTP request over HTTPS #37

Closed dmvt closed 8 years ago

dmvt commented 8 years ago

I'll start off by saying I'm not 100% that my issue is with this GEM. I have connected successfully to the websocket using EM & https://github.com/shokai/websocket-client-simple, but would prefer to use a Celluloid based solution.

Here is my implementation:

require "websocket/driver"
require "celluloid/io"
require "oj"
require "pry"

module WebSocket
  class Driver
    class Client
      def start
        puts "ready: #{@ready_state}"
        return false unless @ready_state == -1
        puts handshake_request
        puts Driver.encode(handshake_request, :binary)
        res = @socket.write(Driver.encode(handshake_request, :binary))
        puts res
        @ready_state = 0
        true
      end
    end
  end
end

class ExchangeFeed
  include Celluloid::IO
  include Celluloid::Internals::Logger

  extend Forwardable
  def_delegator :@socket, :write
  def_delegators :@driver, :text, :binary, :close

  attr_reader :driver, :socket, :url

  class << self
    def run
      new.async.go
    end
  end

  def initialize
    @url    = "wss://ws-feed.exchange.coinbase.com"
    @uri    = URI.parse(url)

    ssl_context = OpenSSL::SSL::SSLContext.new(:TLSv1_2_client)
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE

    @socket = TCPSocket.new(@uri.host, 443)
    @socket = SSLSocket.new(@socket, ssl_context)
    @driver = WebSocket::Driver.client self

    @driver.on :open do |e|
      $stdout.puts "open!"
      write Oj.dump({
        'type' => 'subscribe',
        'product_id' => 'BTC-USD'
      })
      debug "open!"
    end

    @driver.on :close do |e|
      $stdout.puts "close!"
      debug "close!"
    end

    @driver.on :message do |e|
      $stdout.puts "message: #{e.data}"
      debug "message: #{e.data}"
    end
  end

  # call this with async
  def go
    @driver.start

    begin
      while msg = @socket.readpartial(4096)
        $stdout.puts msg
        @driver.parse msg
      end
    rescue
      $stdout.puts "EOF"
    end
  end
end

When I call ExchangeFeed.run I get the following response:

I, [2016-01-22T16:51:19.665078 #79658]  INFO -- : Celluloid 0.17.3 is running in BACKPORTED mode. [ http://git.io/vJf3J ]
ready: -1
GET / HTTP/1.1
Host: ws-feed.exchange.coinbase.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: sEn9exHcrZ1+OXfLAHDylg==
Sec-WebSocket-Version: 13

GET / HTTP/1.1
Host: ws-feed.exchange.coinbase.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: sEn9exHcrZ1+OXfLAHDylg==
Sec-WebSocket-Version: 13

168
HTTP/1.1 400 Bad Request
Server: cloudflare-nginx
Date: Fri, 22 Jan 2016 21:51:19 GMT
Content-Type: text/html
Content-Length: 275
Connection: close

<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>cloudflare-nginx</center>
</body>
</html>
close!
EOF

It seems as though the encryption is being bypassed during the handshake. I've spent a while diving in the source and Google to no avail. Thoughts?

jcoglan commented 8 years ago

I don't think your problem is with the driver, it's with your I/O, but I'm not sure of anything beyond that.

I removed your monkey-patching of the Driver code, and added some patched to the socket objects to see what they were receiving:

    @socket = TCPSocket.new(@uri.host, 443)

    def @socket.write(chunk, *)
      p [:tcp_write, chunk]
      super
    end

    @socket = SSLSocket.new(@socket, ssl_context)

    def @socket.write(chunk, *)
      p [:tls_write, chunk]
      super
    end

Here's what I see:

I, [2016-02-06T13:08:14.453247 #10930]  INFO -- : Celluloid 0.17.3 is running in BACKPORTED mode. [ http://git.io/vJf3J ]
[:tls_write, "GET / HTTP/1.1\r\nHost: ws-feed.exchange.coinbase.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: x3W1g/TuwmndkbwEbUZY/g==\r\nSec-WebSocket-Version: 13\r\n\r\n"]
HTTP/1.1 400 Bad Request
Server: cloudflare-nginx
Date: Sat, 06 Feb 2016 13:08:14 GMT
Content-Type: text/html
Content-Length: 275
Connection: close

So the right data is being written to the TLS session but after that I'm not sure what happens.

dmvt commented 8 years ago

Thanks for the response. I'll keep diving!

On Saturday, February 6, 2016, James Coglan notifications@github.com wrote:

I don't think your problem is with the driver, it's with your I/O, but I'm not sure of anything beyond that.

I removed your monkey-patching of the Driver code, and added some patched to the socket objects to see what they were receiving:

@socket = TCPSocket.new(@uri.host, 443)

def @socket.write(chunk, *)
  p [:tcp_write, chunk]
  super
end

@socket = SSLSocket.new(@socket, ssl_context)

def @socket.write(chunk, *)
  p [:tls_write, chunk]
  super
end

Here's what I see:

I, [2016-02-06T13:08:14.453247 #10930] INFO -- : Celluloid 0.17.3 is running in BACKPORTED mode. [ http://git.io/vJf3J ] [:tls_write, "GET / HTTP/1.1\r\nHost: ws-feed.exchange.coinbase.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: x3W1g/TuwmndkbwEbUZY/g==\r\nSec-WebSocket-Version: 13\r\n\r\n"] HTTP/1.1 400 Bad Request Server: cloudflare-nginx Date: Sat, 06 Feb 2016 13:08:14 GMT Content-Type: text/html Content-Length: 275 Connection: close

So the right data is being written to the TLS session but after that I'm not sure what happens.

— Reply to this email directly or view it on GitHub https://github.com/faye/websocket-driver-ruby/issues/37#issuecomment-180763591 .

Dan Matthews

dan@bluefoc.us (802) 578-7987

jcoglan commented 8 years ago

@dmvt Did you get any further with this issue?

dmvt commented 8 years ago

Unfortunately life has been too busy for me to circle back on this yet. I'll close it out and come back to update if / when I get back to it.

onyxblade commented 7 years ago

I guess the problem is:

@socket = TCPSocket.new(@uri.host, 443)
@socket = SSLSocket.new(@socket, ssl_context)
@socket.connect # needed for SSLSocket