em-http-request 0.2.12 → 0.2.13

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of em-http-request might be problematic. Click here for more details.

@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.13 / 2010-09-25
4
+
5
+ - added SOCKS5 proxy support
6
+ - bugfix: follow redirects on HEAD requests
7
+
3
8
  ## 0.2.12 / 2010-09-12
4
9
 
5
10
  - added headers callback (http.headers {|h| p h})
data/README.md CHANGED
@@ -9,7 +9,7 @@ Asynchronous HTTP client for Ruby, based on EventMachine runtime.
9
9
  - Basic-Auth & OAuth support
10
10
  - Custom timeout support
11
11
  - Stream response processing
12
- - Proxy support (with SSL Tunneling)
12
+ - Proxy support (with SSL Tunneling): CONNECT, direct & SOCKS5
13
13
  - Auto-follow 3xx redirects with custom max depth
14
14
  - Bi-directional communication with web-socket services
15
15
  - [Native mocking support](http://wiki.github.com/igrigorik/em-http-request/mocking-httprequest) and through [Webmock](http://github.com/bblimke/webmock)
@@ -114,7 +114,6 @@ Allows you to efficiently stream a (large) file from disk via EventMachine's Fil
114
114
 
115
115
  Proxy example
116
116
  -------------
117
-
118
117
  Full transparent proxy support with support for SSL tunneling.
119
118
 
120
119
  EventMachine.run {
@@ -124,6 +123,17 @@ Full transparent proxy support with support for SSL tunneling.
124
123
  :authorization => ['username', 'password'] # authorization is optional
125
124
  }
126
125
 
126
+ SOCKS5 Proxy example
127
+ -------------
128
+ Tunnel your requests via connect via SOCKS5 proxies (ssh -D port somehost).
129
+
130
+ EventMachine.run {
131
+ http = EventMachine::HttpRequest.new('http://www.website.com/').get :proxy => {
132
+ :host => 'www.myproxy.com',
133
+ :port => 8080,
134
+ :type => :socks
135
+ }
136
+
127
137
  Auto-follow 3xx redirects
128
138
  -------------------------
129
139
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.12
1
+ 0.2.13
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{em-http-request}
8
- s.version = "0.2.12"
8
+ s.version = "0.2.13"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ilya Grigorik"]
12
- s.date = %q{2010-09-12}
12
+ s.date = %q{2010-09-25}
13
13
  s.description = %q{EventMachine based, async HTTP Request interface}
14
14
  s.email = %q{ilya@igvita.com}
15
15
  s.extensions = ["ext/buffer/extconf.rb", "ext/http11_client/extconf.rb"]
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  "examples/fetch.rb",
29
29
  "examples/fibered-http.rb",
30
30
  "examples/oauth-tweet.rb",
31
+ "examples/socks5.rb",
31
32
  "examples/websocket-handler.rb",
32
33
  "examples/websocket-server.rb",
33
34
  "ext/buffer/em_buffer.c",
@@ -76,6 +77,7 @@ Gem::Specification.new do |s|
76
77
  "examples/fetch.rb",
77
78
  "examples/fibered-http.rb",
78
79
  "examples/oauth-tweet.rb",
80
+ "examples/socks5.rb",
79
81
  "examples/websocket-handler.rb",
80
82
  "examples/websocket-server.rb"
81
83
  ]
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require '../lib/em-http'
4
+
5
+ EM.run do
6
+ # Establish a SOCKS5 tunnel via SSH
7
+ # ssh -D 8000 some_remote_machine
8
+
9
+ # http = EM::HttpRequest.new('http://whatismyip.org/').get({
10
+ http = EM::HttpRequest.new('http://igvita.com/').get({
11
+ :proxy => {:host => '127.0.0.1', :port => 8000, :type => :socks},
12
+ :redirects => 2
13
+ })
14
+
15
+ http.callback {
16
+ puts "#{http.response_header.status} - #{http.response.length} bytes\n"
17
+ puts http.response
18
+ EM.stop
19
+ }
20
+
21
+ http.errback {
22
+ puts "Error: " + http.error
23
+ puts http.inspect
24
+ EM.stop
25
+ }
26
+ end
@@ -1,20 +1,21 @@
1
- #--
2
- # Copyright (C)2008 Ilya Grigorik
3
- # You can redistribute this under the terms of the Ruby license
4
- # See file LICENSE for details
5
- #++
6
-
7
- require 'eventmachine'
8
-
9
- require File.dirname(__FILE__) + '/http11_client'
10
- require File.dirname(__FILE__) + '/em_buffer'
11
-
12
- require File.dirname(__FILE__) + '/em-http/core_ext/hash'
13
- require File.dirname(__FILE__) + '/em-http/core_ext/bytesize'
14
-
15
- require File.dirname(__FILE__) + '/em-http/client'
16
- require File.dirname(__FILE__) + '/em-http/multi'
17
- require File.dirname(__FILE__) + '/em-http/request'
18
- require File.dirname(__FILE__) + '/em-http/decoders'
19
- require File.dirname(__FILE__) + '/em-http/http_options'
1
+ #--
2
+ # Copyright (C)2008 Ilya Grigorik
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require 'eventmachine'
8
+ require 'socket'
9
+
10
+ require File.dirname(__FILE__) + '/http11_client'
11
+ require File.dirname(__FILE__) + '/em_buffer'
12
+
13
+ require File.dirname(__FILE__) + '/em-http/core_ext/hash'
14
+ require File.dirname(__FILE__) + '/em-http/core_ext/bytesize'
15
+
16
+ require File.dirname(__FILE__) + '/em-http/client'
17
+ require File.dirname(__FILE__) + '/em-http/multi'
18
+ require File.dirname(__FILE__) + '/em-http/request'
19
+ require File.dirname(__FILE__) + '/em-http/decoders'
20
+ require File.dirname(__FILE__) + '/em-http/http_options'
20
21
  require File.dirname(__FILE__) + '/em-http/mock'
@@ -125,7 +125,7 @@ module EventMachine
125
125
 
126
126
  # Non CONNECT proxies require that you provide the full request
127
127
  # uri in request header, as opposed to a relative path.
128
- query = uri.join(query) if proxy and not proxy[:use_connect]
128
+ query = uri.join(query) if proxy && proxy[:type] != :socks && !proxy[:use_connect]
129
129
 
130
130
  HTTP_REQUEST_HEADER % [method.to_s.upcase, query]
131
131
  end
@@ -224,19 +224,28 @@ module EventMachine
224
224
  @stream = nil
225
225
  @disconnect = nil
226
226
  @state = :response_header
227
+ @socks_state = nil
227
228
  end
228
229
 
229
230
  # start HTTP request once we establish connection to host
230
231
  def connection_completed
232
+ # if a socks proxy is specified, then a connection request
233
+ # has to be made to the socks server and we need to wait
234
+ # for a response code
235
+ if socks_proxy? and @state == :response_header
236
+ @state = :connect_socks_proxy
237
+ send_socks_handshake
238
+
231
239
  # if we need to negotiate the proxy connection first, then
232
240
  # issue a CONNECT query and wait for 200 response
233
- if connect_proxy? and @state == :response_header
234
- @state = :connect_proxy
241
+ elsif connect_proxy? and @state == :response_header
242
+ @state = :connect_http_proxy
235
243
  send_request_header
236
244
 
237
245
  # if connecting via proxy, then state will be :proxy_connected,
238
246
  # indicating successful tunnel. from here, initiate normal http
239
247
  # exchange
248
+
240
249
  else
241
250
  @state = :response_header
242
251
  ssl = @options[:tls] || @options[:ssl] || {}
@@ -306,9 +315,44 @@ module EventMachine
306
315
  end
307
316
  end
308
317
 
318
+ # determines if there is enough data in the buffer
319
+ def has_bytes?(num)
320
+ @data.size >= num
321
+ end
322
+
309
323
  def websocket?; @uri.scheme == 'ws'; end
310
324
  def proxy?; !@options[:proxy].nil?; end
311
- def connect_proxy?; proxy? && (@options[:proxy][:use_connect] == true); end
325
+
326
+ # determines if a proxy should be used that uses
327
+ # http-headers as proxy-mechanism
328
+ #
329
+ # this is the default proxy type if none is specified
330
+ def http_proxy?; proxy? && [nil, :http].include?(@options[:proxy][:type]); end
331
+
332
+ # determines if a http-proxy should be used with
333
+ # the CONNECT verb
334
+ def connect_proxy?; http_proxy? && (@options[:proxy][:use_connect] == true); end
335
+
336
+ # determines if a SOCKS5 proxy should be used
337
+ def socks_proxy?; proxy? && (@options[:proxy][:type] == :socks); end
338
+
339
+ def socks_methods
340
+ methods = []
341
+ methods << 2 if !options[:proxy][:authorization].nil? # 2 => Username/Password Authentication
342
+ methods << 0 # 0 => No Authentication Required
343
+
344
+ methods
345
+ end
346
+
347
+ def send_socks_handshake
348
+ # Method Negotiation as described on
349
+ # http://www.faqs.org/rfcs/rfc1928.html Section 3
350
+
351
+ @socks_state = :method_negotiation
352
+
353
+ methods = socks_methods
354
+ send_data [5, methods.size].pack('CC') + methods.pack('C*')
355
+ end
312
356
 
313
357
  def send_request_header
314
358
  query = @options[:query]
@@ -319,7 +363,7 @@ module EventMachine
319
363
 
320
364
  request_header = nil
321
365
 
322
- if proxy
366
+ if http_proxy?
323
367
  # initialize headers for the http proxy
324
368
  head = proxy[:head] ? munge_header_keys(proxy[:head]) : {}
325
369
  head['proxy-authorization'] = proxy[:authorization] if proxy[:authorization]
@@ -327,7 +371,7 @@ module EventMachine
327
371
  # if we need to negotiate the tunnel connection first, then
328
372
  # issue a CONNECT query to the proxy first. This is an optional
329
373
  # flag, by default we will provide full URIs to the proxy
330
- if @state == :connect_proxy
374
+ if @state == :connect_http_proxy
331
375
  request_header = HTTP_REQUEST_HEADER % ['CONNECT', "#{@uri.host}:#{@uri.port}"]
332
376
  end
333
377
  end
@@ -443,7 +487,9 @@ module EventMachine
443
487
 
444
488
  def dispatch
445
489
  while case @state
446
- when :connect_proxy
490
+ when :connect_socks_proxy
491
+ parse_socks_response
492
+ when :connect_http_proxy
447
493
  parse_response_header
448
494
  when :response_header
449
495
  parse_response_header
@@ -499,7 +545,7 @@ module EventMachine
499
545
  return false
500
546
  end
501
547
 
502
- if @state == :connect_proxy
548
+ if @state == :connect_http_proxy
503
549
  # when a successfull tunnel is established, the proxy responds with a
504
550
  # 200 response code. from here, the tunnel is transparent.
505
551
  if @response_header.http_status.to_i == 200
@@ -535,10 +581,13 @@ module EventMachine
535
581
  end
536
582
  end
537
583
 
538
- # shortcircuit on HEAD requests
584
+ # Fire callbacks immediately after recieving header requests
585
+ # if the request method is HEAD. In case of a redirect, terminate
586
+ # current connection and reinitialize the process.
539
587
  if @method == "HEAD"
540
588
  @state = :finished
541
- unbind
589
+ close_connection
590
+ return false
542
591
  end
543
592
 
544
593
  if websocket?
@@ -570,6 +619,114 @@ module EventMachine
570
619
  true
571
620
  end
572
621
 
622
+ def send_socks_connect_request
623
+ # TO-DO: Implement address types for IPv6 and Domain
624
+ begin
625
+ ip_address = Socket.gethostbyname(@uri.host).last
626
+ send_data [5, 1, 0, 1, ip_address, @uri.port].flatten.pack('CCCCA4n')
627
+
628
+ rescue
629
+ @state = :invalid
630
+ on_error "could not resolve host", true
631
+ return false
632
+ end
633
+
634
+ true
635
+ end
636
+
637
+ # parses socks 5 server responses as specified
638
+ # on http://www.faqs.org/rfcs/rfc1928.html
639
+ def parse_socks_response
640
+ if @socks_state == :method_negotiation
641
+ return false unless has_bytes? 2
642
+
643
+ _, method = @data.read(2).unpack('CC')
644
+
645
+ if socks_methods.include?(method)
646
+ if method == 0
647
+ @socks_state = :connecting
648
+
649
+ return send_socks_connect_request
650
+
651
+ elsif method == 2
652
+ @socks_state = :authenticating
653
+
654
+ credentials = @options[:proxy][:authorization]
655
+ if credentials.size < 2
656
+ @state = :invalid
657
+ on_error "username and password are not supplied"
658
+ return false
659
+ end
660
+
661
+ username, password = credentials
662
+
663
+ send_data [5, username.length, username, password.length, password].pack('CCA*CA*')
664
+ end
665
+
666
+ else
667
+ @state = :invalid
668
+ on_error "proxy did not accept method"
669
+ return false
670
+ end
671
+
672
+ elsif @socks_state == :authenticating
673
+ return false unless has_bytes? 2
674
+
675
+ _, status_code = @data.read(2).unpack('CC')
676
+
677
+ if status_code == 0
678
+ # success
679
+ @socks_state = :connecting
680
+
681
+ return send_socks_connect_request
682
+
683
+ else
684
+ # error
685
+ @state = :invalid
686
+ on_error "access denied by proxy"
687
+ return false
688
+ end
689
+
690
+ elsif @socks_state == :connecting
691
+ return false unless has_bytes? 10
692
+
693
+ _, response_code, _, address_type, _, _ = @data.read(10).unpack('CCCCNn')
694
+
695
+ if response_code == 0
696
+ # success
697
+ @socks_state = :connected
698
+ @state = :proxy_connected
699
+
700
+ @response_header = HttpResponseHeader.new
701
+
702
+ # connection_completed will invoke actions to
703
+ # start sending all http data transparently
704
+ # over the socks connection
705
+ connection_completed
706
+
707
+ else
708
+ # error
709
+ @state = :invalid
710
+
711
+ error_messages = {
712
+ 1 => "general socks server failure",
713
+ 2 => "connection not allowed by ruleset",
714
+ 3 => "network unreachable",
715
+ 4 => "host unreachable",
716
+ 5 => "connection refused",
717
+ 6 => "TTL expired",
718
+ 7 => "command not supported",
719
+ 8 => "address type not supported"
720
+ }
721
+ error_message = error_messages[response_code] || "unknown error (code: #{response_code})"
722
+ on_error "socks5 connect error: #{error_message}"
723
+ return false
724
+ end
725
+ end
726
+
727
+ true
728
+ end
729
+
573
730
  def parse_chunk_header
574
731
  return false unless parse_header(@chunk_header)
575
732
 
@@ -1,6 +1,6 @@
1
- # bytesize was introduced in 1.8.7+
2
- if RUBY_VERSION <= "1.8.6"
3
- class String
4
- def bytesize; self.size; end
5
- end
1
+ # bytesize was introduced in 1.8.7+
2
+ if RUBY_VERSION <= "1.8.6"
3
+ class String
4
+ def bytesize; self.size; end
5
+ end
6
6
  end
@@ -1,53 +1,53 @@
1
- class Hash
2
- # Stolen partially from Merb : http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
3
- # Convert this hash to a query string:
4
- #
5
- # { :name => "Bob",
6
- # :address => {
7
- # :street => '111 Ruby Ave.',
8
- # :city => 'Ruby Central',
9
- # :phones => ['111-111-1111', '222-222-2222']
10
- # }
11
- # }.to_params
12
- # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
13
- #
14
- def to_params
15
- params = ''
16
- stack = []
17
-
18
- each do |k, v|
19
- if v.is_a?(Hash)
20
- stack << [k,v]
21
- elsif v.is_a?(Array)
22
- stack << [k,Hash.from_array(v)]
23
- else
24
- params << "#{k}=#{v}&"
25
- end
26
- end
27
-
28
- stack.each do |parent, hash|
29
- hash.each do |k, v|
30
- if v.is_a?(Hash)
31
- stack << ["#{parent}[#{k}]", v]
32
- else
33
- params << "#{parent}[#{k}]=#{v}&"
34
- end
35
- end
36
- end
37
-
38
- params.chop! # trailing &
39
- params
40
- end
41
-
42
- ##
43
- # Builds a hash from an array with keys as array indices.
44
- def self.from_array(array = [])
45
- h = Hash.new
46
- array.size.times do |t|
47
- h[t] = array[t]
48
- end
49
- h
50
- end
51
-
52
- end
53
-
1
+ class Hash
2
+ # Stolen partially from Merb : http://noobkit.com/show/ruby/gems/development/merb/hash/to_params.html
3
+ # Convert this hash to a query string:
4
+ #
5
+ # { :name => "Bob",
6
+ # :address => {
7
+ # :street => '111 Ruby Ave.',
8
+ # :city => 'Ruby Central',
9
+ # :phones => ['111-111-1111', '222-222-2222']
10
+ # }
11
+ # }.to_params
12
+ # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
13
+ #
14
+ def to_params
15
+ params = ''
16
+ stack = []
17
+
18
+ each do |k, v|
19
+ if v.is_a?(Hash)
20
+ stack << [k,v]
21
+ elsif v.is_a?(Array)
22
+ stack << [k,Hash.from_array(v)]
23
+ else
24
+ params << "#{k}=#{v}&"
25
+ end
26
+ end
27
+
28
+ stack.each do |parent, hash|
29
+ hash.each do |k, v|
30
+ if v.is_a?(Hash)
31
+ stack << ["#{parent}[#{k}]", v]
32
+ else
33
+ params << "#{parent}[#{k}]=#{v}&"
34
+ end
35
+ end
36
+ end
37
+
38
+ params.chop! # trailing &
39
+ params
40
+ end
41
+
42
+ ##
43
+ # Builds a hash from an array with keys as array indices.
44
+ def self.from_array(array = [])
45
+ h = Hash.new
46
+ array.size.times do |t|
47
+ h[t] = array[t]
48
+ end
49
+ h
50
+ end
51
+
52
+ end
53
+
@@ -1,24 +1,24 @@
1
- require 'spec/helper'
2
-
3
- describe Hash do
4
-
5
- describe ".to_params" do
6
- it "should transform a basic hash into HTTP POST Params" do
7
- {:a => "alpha", :b => "beta"}.to_params.split("&").should include "a=alpha"
8
- {:a => "alpha", :b => "beta"}.to_params.split("&").should include "b=beta"
9
- end
10
-
11
- it "should transform a more complex hash into HTTP POST Params" do
12
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "a=a"
13
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[0]=c"
14
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[1]=d"
15
- {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[2]=e"
16
- end
17
-
18
- it "should transform a very complex hash into HTTP POST Params" do
19
- params = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.split("&")
20
- params.should include "a=a"
21
- params.should include "b[0][d]=d"
22
- end
23
- end
24
- end
1
+ require 'spec/helper'
2
+
3
+ describe Hash do
4
+
5
+ describe ".to_params" do
6
+ it "should transform a basic hash into HTTP POST Params" do
7
+ {:a => "alpha", :b => "beta"}.to_params.split("&").should include "a=alpha"
8
+ {:a => "alpha", :b => "beta"}.to_params.split("&").should include "b=beta"
9
+ end
10
+
11
+ it "should transform a more complex hash into HTTP POST Params" do
12
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "a=a"
13
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[0]=c"
14
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[1]=d"
15
+ {:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[2]=e"
16
+ end
17
+
18
+ it "should transform a very complex hash into HTTP POST Params" do
19
+ params = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.split("&")
20
+ params.should include "a=a"
21
+ params.should include "b[0][d]=d"
22
+ end
23
+ end
24
+ end
@@ -99,6 +99,31 @@ describe EventMachine::HttpRequest do
99
99
  }
100
100
  }
101
101
  end
102
+
103
+ it "should follow redirects on HEAD method" do
104
+ EventMachine.run {
105
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/head').head :redirects => 1
106
+ http.errback { failed }
107
+ http.callback {
108
+ http.response_header.status.should == 200
109
+ http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
110
+ EM.stop
111
+ }
112
+ }
113
+ end
114
+
115
+ it "should follow redirects on HEAD method (external)" do
116
+
117
+ EventMachine.run {
118
+ http = EventMachine::HttpRequest.new('http://www.google.com/').head :redirects => 1
119
+ http.errback { failed }
120
+ http.callback {
121
+ http.response_header.status.should == 200
122
+ EM.stop
123
+ }
124
+ }
125
+ end
126
+
102
127
  end
103
128
 
104
129
  it "should perform successfull GET with a URI passed as argument" do
@@ -1,7 +1,7 @@
1
- --colour
2
- --format
3
- specdoc
4
- --loadby
5
- mtime
6
- --reverse
1
+ --colour
2
+ --format
3
+ specdoc
4
+ --loadby
5
+ mtime
6
+ --reverse
7
7
  --backtrace
@@ -85,7 +85,7 @@ Stallion.saddle :spec do |stable|
85
85
  elsif stable.request.path_info == '/echo_content_length'
86
86
  stable.response.write stable.request.content_length
87
87
 
88
- elsif stable.request.head?
88
+ elsif stable.request.head? && stable.request.path_info == '/'
89
89
  stable.response.status = 200
90
90
 
91
91
  elsif stable.request.delete?
@@ -121,6 +121,10 @@ Stallion.saddle :spec do |stable|
121
121
  stable.response.status = 301
122
122
  stable.response["Location"] = "http://127.0.0.1:8080"
123
123
 
124
+ elsif stable.request.path_info == '/redirect/head'
125
+ stable.response.status = 301
126
+ stable.response["Location"] = "/"
127
+
124
128
  elsif stable.request.path_info == '/redirect/nohost'
125
129
  stable.response.status = 301
126
130
  stable.response["Location"] = "http:/"
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 12
9
- version: 0.2.12
8
+ - 13
9
+ version: 0.2.13
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ilya Grigorik
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-12 00:00:00 -04:00
17
+ date: 2010-09-25 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -68,6 +68,7 @@ files:
68
68
  - examples/fetch.rb
69
69
  - examples/fibered-http.rb
70
70
  - examples/oauth-tweet.rb
71
+ - examples/socks5.rb
71
72
  - examples/websocket-handler.rb
72
73
  - examples/websocket-server.rb
73
74
  - ext/buffer/em_buffer.c
@@ -142,5 +143,6 @@ test_files:
142
143
  - examples/fetch.rb
143
144
  - examples/fibered-http.rb
144
145
  - examples/oauth-tweet.rb
146
+ - examples/socks5.rb
145
147
  - examples/websocket-handler.rb
146
148
  - examples/websocket-server.rb