This repository was archived by the owner on Sep 4, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathRequestParser.jl
158 lines (136 loc) · 4.91 KB
/
RequestParser.jl
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# `RequestParser` handles all the `HttpParser` module stuff for `HttpServer`
#
# The `HttpParser` module wraps [Joyent's `http-parser` C library][hprepo].
# A new `HttpParser` is created for each TCP connection being handled by
# our server. Each `HttpParser` is initialized with a set of callback
# functions. When new data comes in, it is fed into the `http-parser` which
# executes the callbacks as different elements are parsed. Finally, it calls
# on_message_complete when the incoming `Request` is fully built. The parser
# does not care if it receives just one byte at a time, or multiple requests.
# It will simply parse in order and run the callbacks normally.
#
# Note that this is not a module, it is included directly in `HttpServer.jl`
#
# [hprepo]: https://github.com/joyent/http-parser
#
using HttpParser
using HttpCommon
export RequestParser,
clean!,
add_data
# Datatype Tuples for the different `cfunction` signatures used by `HttpParser`
HTTP_CB = (Int, (Ptr{Parser},))
HTTP_DATA_CB = (Int, (Ptr{Parser}, Ptr{Cchar}, Csize_t,))
# All the `HttpParser` callbacks to be run in C land
# Each one adds data to the `Request` until it is complete
#
function on_message_begin(parser)
pd(parser).request = Request()
return 0
end
function on_url(parser, at, len)
r = pd(parser).request
r.resource = string(r.resource, unsafe_string(convert(Ptr{UInt8}, at), Int(len)))
return 0
end
function on_status_complete(parser)
return 0
end
# Gather the header_field, set the field
# on header value, set the value for the current field
# there might be a better way to do
# this: https://github.com/joyent/node/blob/master/src/node_http_parser.cc#L207
function on_header_field(parser, at, len)
par = pd(parser)
if par.num_fields == par.num_values
par.num_fields += 1
push!(par.vec_fields, "")
end
header = unsafe_string(convert(Ptr{UInt8}, at))
header_field = header[1:len]
par.vec_fields[par.num_fields] = string(par.vec_fields[par.num_fields], header_field)
return 0
end
function on_header_value(parser, at, len)
par = pd(parser)
if par.num_values != par.num_fields
par.num_values += 1
push!(par.vec_values, "")
end
s = unsafe_string(convert(Ptr{UInt8}, at), Int(len))
header_value = s[1:len]
par.vec_values[par.num_values] = string(par.vec_values[par.num_values], header_value)
return 0
end
function on_headers_complete(parser)
par = pd(parser)
r = par.request
merge!(r.headers, Dict(zip(par.vec_fields, par.vec_values)))
par.num_fields = 0
par.num_values = 0
empty!(par.vec_fields)
empty!(par.vec_values)
p = unsafe_load(parser)
# get first two bits of p.type_and_flags
ptype = p.type_and_flags & 0x03
if ptype == 0
r.method = http_method_str(convert(Int, p.method))
elseif ptype == 1
r.headers["status_code"] = string(convert(Int, p.status_code))
end
r.headers["http_major"] = string(convert(Int, p.http_major))
r.headers["http_minor"] = string(convert(Int, p.http_minor))
r.headers["Keep-Alive"] = string(http_should_keep_alive(parser))
return 0
end
function on_body(parser, at, len)
r = pd(parser).request
# write(pd(parser).data, convert(Ptr{UInt8}, at), len)
append!(r.data, unsafe_wrap(Array,convert(Ptr{UInt8}, at), (len,)))
r.data
return 0
end
function on_message_complete(parser)
state = pd(parser)
r = state.request
# r.data = takebuf_array(state.data)
# Get the `parser.id` from the C pointer `parser`.
# Retrieve our callback function from the global Dict.
# Call it with the completed `Request`
#
state.complete_cb(r)
return 0
end
default_complete_cb(r::Request) = nothing
mutable struct RequestParserState
request::Request
complete_cb::Function
num_fields::Int
num_values::Int
vec_fields::Vector{String}
vec_values::Vector{String}
end
RequestParserState() = RequestParserState(Request(),default_complete_cb,0,0,Vector{String}(),Vector{String}())
pd(p::Ptr{Parser}) = (unsafe_load(p).data)::RequestParserState
# `ClientParser` wraps our `HttpParser`
# Constructed with `on_message_complete` function.
#
struct ClientParser
parser::Parser
settings::ParserSettings
function ClientParser(on_message_complete::Function)
parser = Parser()
parser.data = RequestParserState()
http_parser_init(parser)
parser.data.complete_cb = on_message_complete
settings = ParserSettings(on_message_begin_cb, on_url_cb,
on_status_complete_cb, on_header_field_cb,
on_header_value_cb, on_headers_complete_cb,
on_body_cb, on_message_complete_cb)
new(parser, settings)
end
end
# Passes `request_data` into `parser`
function add_data(parser::ClientParser, request_data::Vector{UInt8})
http_parser_execute(parser.parser, parser.settings, request_data)
end