em-http-request 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

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