rev 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +3 -0
- data/ext/http11_client/ext_help.h +14 -0
- data/ext/http11_client/extconf.rb +6 -0
- data/ext/http11_client/http11_client.c +302 -0
- data/ext/http11_client/http11_parser.c +1052 -0
- data/ext/http11_client/http11_parser.h +48 -0
- data/ext/rev/rev_watcher.c +16 -0
- data/lib/rev/buffered_io.rb +42 -25
- data/lib/rev/dns_resolver.rb +27 -26
- data/lib/rev/http_client.rb +419 -0
- data/lib/rev/socket.rb +3 -2
- data/lib/rev.rb +2 -1
- metadata +11 -2
@@ -0,0 +1,48 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2005 Zed A. Shaw
|
3
|
+
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
|
+
*/
|
5
|
+
|
6
|
+
#ifndef http11_parser_h
|
7
|
+
#define http11_parser_h
|
8
|
+
|
9
|
+
#include <sys/types.h>
|
10
|
+
|
11
|
+
#if defined(_WIN32)
|
12
|
+
#include <stddef.h>
|
13
|
+
#endif
|
14
|
+
|
15
|
+
typedef void (*element_cb)(void *data, const char *at, size_t length);
|
16
|
+
typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
|
17
|
+
|
18
|
+
typedef struct httpclient_parser {
|
19
|
+
int cs;
|
20
|
+
size_t body_start;
|
21
|
+
int content_len;
|
22
|
+
size_t nread;
|
23
|
+
size_t mark;
|
24
|
+
size_t field_start;
|
25
|
+
size_t field_len;
|
26
|
+
|
27
|
+
void *data;
|
28
|
+
|
29
|
+
field_cb http_field;
|
30
|
+
element_cb reason_phrase;
|
31
|
+
element_cb status_code;
|
32
|
+
element_cb chunk_size;
|
33
|
+
element_cb http_version;
|
34
|
+
element_cb header_done;
|
35
|
+
element_cb last_chunk;
|
36
|
+
|
37
|
+
|
38
|
+
} httpclient_parser;
|
39
|
+
|
40
|
+
int httpclient_parser_init(httpclient_parser *parser);
|
41
|
+
int httpclient_parser_finish(httpclient_parser *parser);
|
42
|
+
size_t httpclient_parser_execute(httpclient_parser *parser, const char *data, size_t len, size_t off);
|
43
|
+
int httpclient_parser_has_error(httpclient_parser *parser);
|
44
|
+
int httpclient_parser_is_finished(httpclient_parser *parser);
|
45
|
+
|
46
|
+
#define httpclient_parser_nread(parser) (parser)->nread
|
47
|
+
|
48
|
+
#endif
|
data/ext/rev/rev_watcher.c
CHANGED
@@ -28,6 +28,7 @@ static VALUE Rev_Watcher_enable(VALUE self);
|
|
28
28
|
static VALUE Rev_Watcher_disable(VALUE self);
|
29
29
|
static VALUE Rev_Watcher_evloop(VALUE self);
|
30
30
|
static VALUE Rev_Watcher_attached(VALUE self);
|
31
|
+
static VALUE Rev_Watcher_enabled(VALUE self);
|
31
32
|
|
32
33
|
void Init_rev_watcher()
|
33
34
|
{
|
@@ -42,6 +43,7 @@ void Init_rev_watcher()
|
|
42
43
|
rb_define_method(cRev_Watcher, "disable", Rev_Watcher_disable, 0);
|
43
44
|
rb_define_method(cRev_Watcher, "evloop", Rev_Watcher_evloop, 0);
|
44
45
|
rb_define_method(cRev_Watcher, "attached?", Rev_Watcher_attached, 0);
|
46
|
+
rb_define_method(cRev_Watcher, "enabled?", Rev_Watcher_enabled, 0);
|
45
47
|
}
|
46
48
|
|
47
49
|
static VALUE Rev_Watcher_allocate(VALUE klass)
|
@@ -220,3 +222,17 @@ static VALUE Rev_Watcher_attached(VALUE self)
|
|
220
222
|
{
|
221
223
|
return Rev_Watcher_evloop(self) != Qnil;
|
222
224
|
}
|
225
|
+
|
226
|
+
/**
|
227
|
+
* call-seq:
|
228
|
+
* Rev::Watcher.enabled? -> Boolean
|
229
|
+
*
|
230
|
+
* Is the watcher currently enabled?
|
231
|
+
*/
|
232
|
+
static VALUE Rev_Watcher_enabled(VALUE self)
|
233
|
+
{
|
234
|
+
struct Rev_Watcher *watcher_data;
|
235
|
+
Data_Get_Struct(self, struct Rev_Watcher, watcher_data);
|
236
|
+
|
237
|
+
return watcher_data->enabled ? Qtrue : Qfalse;
|
238
|
+
}
|
data/lib/rev/buffered_io.rb
CHANGED
@@ -8,6 +8,9 @@ require File.dirname(__FILE__) + '/../rev'
|
|
8
8
|
|
9
9
|
module Rev
|
10
10
|
class BufferedIO < IOWatcher
|
11
|
+
# Maximum number of bytes to consume at once
|
12
|
+
INPUT_SIZE = 16384
|
13
|
+
|
11
14
|
def initialize(io)
|
12
15
|
# Output buffer
|
13
16
|
@write_buffer = ''
|
@@ -41,7 +44,7 @@ module Rev
|
|
41
44
|
def write(data)
|
42
45
|
# Attempt a zero copy write
|
43
46
|
if @write_buffer.empty?
|
44
|
-
written =
|
47
|
+
written = write_nonblock data
|
45
48
|
|
46
49
|
# If we lucked out and wrote out the whole buffer, return
|
47
50
|
if written == data.size
|
@@ -49,12 +52,11 @@ module Rev
|
|
49
52
|
return data.size
|
50
53
|
end
|
51
54
|
|
52
|
-
# Otherwise
|
53
|
-
|
54
|
-
else
|
55
|
-
@write_buffer << data
|
55
|
+
# Otherwise slice what we wrote out and begin buffered writing
|
56
|
+
data.slice!(0, written) if written
|
56
57
|
end
|
57
|
-
|
58
|
+
|
59
|
+
@write_buffer << data
|
58
60
|
schedule_write
|
59
61
|
data.size
|
60
62
|
end
|
@@ -64,37 +66,47 @@ module Rev
|
|
64
66
|
@write_buffer.size
|
65
67
|
end
|
66
68
|
|
69
|
+
# Close the BufferedIO stream
|
70
|
+
def close
|
71
|
+
detach if attached?
|
72
|
+
@writer.detach if @writer and @writer.attached?
|
73
|
+
@io.close unless @io.closed?
|
74
|
+
|
75
|
+
on_close
|
76
|
+
end
|
77
|
+
|
78
|
+
#########
|
79
|
+
protected
|
80
|
+
#########
|
81
|
+
|
67
82
|
# Attempt to write the contents of the output buffer
|
68
83
|
def write_output_buffer
|
69
84
|
return if @write_buffer.empty?
|
70
85
|
|
71
|
-
written =
|
72
|
-
@write_buffer.slice!(
|
86
|
+
written = write_nonblock @write_buffer
|
87
|
+
@write_buffer.slice!(0, written) if written
|
73
88
|
|
74
89
|
return unless @write_buffer.empty?
|
75
90
|
|
76
91
|
@writer.disable if @writer and @writer.enabled?
|
77
92
|
on_write_complete
|
78
93
|
end
|
79
|
-
|
80
|
-
#
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
94
|
+
|
95
|
+
# Wrapper for handling reset connections and EAGAIN
|
96
|
+
def write_nonblock(data)
|
97
|
+
begin
|
98
|
+
@io.write_nonblock(data)
|
99
|
+
rescue Errno::ECONNRESET, Errno::EPIPE
|
100
|
+
close
|
101
|
+
rescue Errno::EAGAIN
|
102
|
+
end
|
87
103
|
end
|
88
104
|
|
89
|
-
#########
|
90
|
-
protected
|
91
|
-
#########
|
92
|
-
|
93
105
|
# Inherited callback from IOWatcher
|
94
106
|
def on_readable
|
95
107
|
begin
|
96
|
-
on_read @io.read_nonblock(
|
97
|
-
rescue EOFError
|
108
|
+
on_read @io.read_nonblock(INPUT_SIZE)
|
109
|
+
rescue Errno::ECONNRESET, EOFError
|
98
110
|
close
|
99
111
|
end
|
100
112
|
end
|
@@ -103,8 +115,13 @@ module Rev
|
|
103
115
|
return if @writer and @writer.enabled?
|
104
116
|
if @writer
|
105
117
|
@writer.enable
|
106
|
-
else
|
107
|
-
|
118
|
+
else
|
119
|
+
begin
|
120
|
+
@writer = Writer.new(@io, self)
|
121
|
+
rescue IOError
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
108
125
|
@writer.attach(evloop)
|
109
126
|
end
|
110
127
|
end
|
@@ -116,7 +133,7 @@ module Rev
|
|
116
133
|
end
|
117
134
|
|
118
135
|
def on_writable
|
119
|
-
@buffered_io.write_output_buffer
|
136
|
+
@buffered_io.__send__(:write_output_buffer)
|
120
137
|
end
|
121
138
|
end
|
122
139
|
end
|
data/lib/rev/dns_resolver.rb
CHANGED
@@ -37,17 +37,17 @@ module Rev
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def initialize(hostname, *nameservers)
|
40
|
-
if nameservers.
|
40
|
+
if nameservers.empty?
|
41
41
|
nameservers = File.read(RESOLV_CONF).scan(/^\s*nameserver\s+([0-9.:]+)/).flatten
|
42
42
|
raise RuntimeError, "no nameservers found in #{RESOLV_CONF}" if nameservers.empty?
|
43
43
|
end
|
44
44
|
|
45
45
|
@nameservers = nameservers
|
46
|
-
@request = request_message hostname
|
47
46
|
@question = request_question hostname
|
48
47
|
|
49
48
|
@socket = UDPSocket.new
|
50
49
|
@timer = Timeout.new(self)
|
50
|
+
|
51
51
|
super(@socket)
|
52
52
|
end
|
53
53
|
|
@@ -65,7 +65,7 @@ module Rev
|
|
65
65
|
# Send a request to the DNS server
|
66
66
|
def send_request
|
67
67
|
@socket.connect @nameservers.first, DNS_PORT
|
68
|
-
@socket.send
|
68
|
+
@socket.send request_message, 0
|
69
69
|
end
|
70
70
|
|
71
71
|
# Called when the name has successfully resolved to an address
|
@@ -83,7 +83,7 @@ module Rev
|
|
83
83
|
#########
|
84
84
|
protected
|
85
85
|
#########
|
86
|
-
|
86
|
+
|
87
87
|
# Called by the subclass when the DNS response is available
|
88
88
|
def on_readable
|
89
89
|
datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
|
@@ -92,23 +92,9 @@ module Rev
|
|
92
92
|
detach
|
93
93
|
end
|
94
94
|
|
95
|
-
def request_message(hostname)
|
96
|
-
# Standard query header
|
97
|
-
message = "\000\002\001\000"
|
98
|
-
|
99
|
-
# One entry
|
100
|
-
qdcount = 1
|
101
|
-
|
102
|
-
# No answer, authority, or additional records
|
103
|
-
ancount = nscount = arcount = 0
|
104
|
-
|
105
|
-
message << [qdcount, ancount, nscount, arcount].pack('nnnn')
|
106
|
-
message << request_question(hostname)
|
107
|
-
end
|
108
|
-
|
109
95
|
def request_question(hostname)
|
110
96
|
# Query name
|
111
|
-
message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\
|
97
|
+
message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\0"
|
112
98
|
|
113
99
|
# Host address query
|
114
100
|
qtype = 1
|
@@ -119,6 +105,20 @@ module Rev
|
|
119
105
|
message << [qtype, qclass].pack('nn')
|
120
106
|
end
|
121
107
|
|
108
|
+
def request_message
|
109
|
+
# Standard query header
|
110
|
+
message = [2, 1, 0].pack('nCC')
|
111
|
+
|
112
|
+
# One entry
|
113
|
+
qdcount = 1
|
114
|
+
|
115
|
+
# No answer, authority, or additional records
|
116
|
+
ancount = nscount = arcount = 0
|
117
|
+
|
118
|
+
message << [qdcount, ancount, nscount, arcount].pack('nnnn')
|
119
|
+
message << @question
|
120
|
+
end
|
121
|
+
|
122
122
|
def response_address(message)
|
123
123
|
# Confirm the ID field
|
124
124
|
id = message[0..1].unpack('n').first.to_i
|
@@ -128,7 +128,7 @@ module Rev
|
|
128
128
|
qr = message[2].unpack('B1').first.to_i
|
129
129
|
return unless qr == 1
|
130
130
|
|
131
|
-
# Check the RCODE and ensure there wasn't an error
|
131
|
+
# Check the RCODE (lower nibble) and ensure there wasn't an error
|
132
132
|
rcode = message[3].unpack('B8').first[4..7].to_i(2)
|
133
133
|
return unless rcode == 0
|
134
134
|
|
@@ -137,22 +137,23 @@ module Rev
|
|
137
137
|
|
138
138
|
# We only asked one question
|
139
139
|
return unless qdcount == 1
|
140
|
-
message
|
140
|
+
message.slice!(0, 12)
|
141
141
|
|
142
142
|
# Make sure it's the same question
|
143
143
|
return unless message[0..(@question.size-1)] == @question
|
144
|
-
message
|
144
|
+
message.slice!(0, @question.size)
|
145
145
|
|
146
146
|
# Extract the RDLENGTH
|
147
147
|
while not message.empty?
|
148
148
|
type = message[2..3].unpack('n').first.to_i
|
149
149
|
rdlength = message[10..11].unpack('n').first.to_i
|
150
150
|
rdata = message[12..(12 + rdlength - 1)]
|
151
|
-
message
|
151
|
+
message.slice!(0, 12 + rdlength)
|
152
152
|
|
153
153
|
# Only IPv4 supported
|
154
154
|
next unless rdlength == 4
|
155
155
|
|
156
|
+
# If we got an Internet address back, return it
|
156
157
|
return rdata.unpack('CCCC').join('.') if type == 1
|
157
158
|
end
|
158
159
|
|
@@ -169,10 +170,10 @@ module Rev
|
|
169
170
|
def on_timer
|
170
171
|
@attempts += 1
|
171
172
|
return @resolver.send_request if @attempts <= RETRIES
|
172
|
-
|
173
|
-
@resolver.on_timeout
|
173
|
+
|
174
|
+
@resolver.__send__(:on_timeout)
|
174
175
|
@resolver.detach
|
175
176
|
end
|
176
177
|
end
|
177
178
|
end
|
178
|
-
end
|
179
|
+
end
|