rev 0.1.0 → 0.1.1
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.
- 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
|