File: client.rb

package info (click to toggle)
ruby-websocket 1.2.9-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 464 kB
  • sloc: ruby: 2,669; makefile: 4
file content (130 lines) | stat: -rw-r--r-- 4,790 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true

require 'uri'

module WebSocket
  module Handshake
    # Construct or parse a client WebSocket handshake.
    #
    # @example
    #   @handshake = WebSocket::Handshake::Client.new(url: 'ws://example.com')
    #
    #   # Create request
    #   @handshake.to_s # GET /demo HTTP/1.1
    #                   # Upgrade: websocket
    #                   # Connection: Upgrade
    #                   # Host: example.com
    #                   # Origin: http://example.com
    #                   # Sec-WebSocket-Version: 13
    #                   # Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    #
    #   # Parse server response
    #   @handshake << <<EOF
    #   HTTP/1.1 101 Switching Protocols\r
    #   Upgrade: websocket\r
    #   Connection: Upgrade\r
    #   Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r
    #   \r
    #   EOF
    #
    #   # All data received?
    #   @handshake.finished?
    #
    #   # No parsing errors?
    #   @handshake.valid?
    #
    class Client < Base
      attr_reader :origin, :headers

      # Initialize new WebSocket Client
      #
      # @param [Hash] args Arguments for client
      #
      # @option args [String]         :host Host of request. Required if no :url param was provided.
      # @option args [String]         :origin Origin of request. Optional, should be used mostly by browsers. Default: nil
      # @option args [String]         :path Path of request. Should start with '/'. Default: '/'
      # @option args [Integer]        :port Port of request. Default: nil
      # @option args [String]         :query. Query for request. Should be in format "aaa=bbb&ccc=ddd"
      # @option args [Boolean]        :secure Defines protocol to use. If true then wss://, otherwise ws://. This option will not change default port - it should be handled by programmer.
      # @option args [String]         :url URL of request. Must by in format like ws://example.com/path?query=true. Every part of this url will be overriden by more specific arguments.
      # @option args [String]         :uri Alias to :url
      # @option args [Array<String>]  :protocols An array of supported sub-protocols
      # @option args [Integer]        :version Version of WebSocket to use. Default: 13 (this is version from RFC)
      # @option args [Hash]           :headers HTTP headers to use in the handshake
      #
      # @example
      #   Websocket::Handshake::Client.new(url: "ws://example.com/path?query=true")
      def initialize(args = {})
        super

        if @url || @uri
          uri = URI.parse(@url || @uri)
          @secure ||= (uri.scheme == 'wss')
          @host ||= uri.host
          @port ||= uri.port
          @path ||= uri.path
          @query ||= uri.query
        end

        @path = '/' if @path.nil? || @path.empty?
        @version ||= DEFAULT_VERSION

        raise WebSocket::Error::Handshake::NoHostProvided unless @host

        include_version
      end
      rescue_method :initialize

      # Add text of response from Server. This method will parse content immediately and update state and error(if neccessary)
      #
      # @param [String] data Data to add
      #
      # @example
      #   @handshake << <<EOF
      #   HTTP/1.1 101 Switching Protocols
      #   Upgrade: websocket
      #   Connection: Upgrade
      #   Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
      #
      #   EOF
      def <<(data)
        super
        parse_data
      end
      rescue_method :<<

      # Should send content to server after finished parsing?
      # @return [Boolean] false
      def should_respond?
        false
      end

      private

      # Include set of methods for selected protocol version
      # @return [Boolean] false if protocol number is unknown, otherwise true
      def include_version
        @handler = case @version
                   when 75 then Handler::Client75.new(self)
                   when 76, 0 then Handler::Client76.new(self)
                   when 1..3  then Handler::Client01.new(self)
                   when 4..10 then Handler::Client04.new(self)
                   when 11..17 then Handler::Client11.new(self)
                   else raise WebSocket::Error::Handshake::UnknownVersion
                   end
      end

      FIRST_LINE = %r{^HTTP\/1\.1 (\d{3})[\w\s]*$}

      # Parse first line of Server response.
      # @param [String] line Line to parse
      # @return [Boolean] True if parsed correctly. False otherwise
      def parse_first_line(line)
        line_parts = line.match(FIRST_LINE)
        raise WebSocket::Error::Handshake::InvalidHeader unless line_parts
        status = line_parts[1]
        raise WebSocket::Error::Handshake::InvalidStatusCode unless status == '101'
      end
    end
  end
end