-
Notifications
You must be signed in to change notification settings - Fork 0
/
netutils.ecs
301 lines (279 loc) · 8.55 KB
/
netutils.ecs
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
package netutils
import network.*, regex, curl
# Internal Functions
var request_line_reg = regex.build("^([^ ]*) ([^? ]*)(\\?([^ ]*))? HTTP/([^ ]*)$")
var request_header_reg = regex.build("^([^:]*): ?(.*)$")
namespace state_codes
constant code_200 = "200 OK"
constant code_400 = "400 Bad Request"
constant code_403 = "403 Forbidden"
constant code_404 = "404 Not Found"
constant code_500 = "500 Internal Server Error"
constant code_503 = "503 Service Unavailable"
end
var wday_map = {
"Mon", "Tues", "Wed", "Thur", "Fri", "Sat", "Sun"
}
var mon_map = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
}
function time_padding(obj, width)
var time = to_string(obj)
var last = width - time.size
if last <= 0
return time
end
var str = new string
foreach it in range(last) do str += "0"
return str + time
end
function compose_http_time()
var tm = runtime.utc_time()
return "Date: " +
wday_map[tm.wday - 1] + ", " +
tm.mday + " " + mon_map[tm.mon] + " " + to_string(1900 + tm.year) + " " +
time_padding(tm.hour, 2) + ":" + time_padding(tm.min, 2) + ":" + time_padding(tm.sec, 2) + " GMT"
end
class http_session
var url = null
var args = null
var sock = null
var host = null
var method = null
var version = null
var connection = null
var content_length = null
function construct(socket, request_header : array)
sock = socket
var request_line = request_header.front
request_header.pop_front()
var match = request_line_reg.match(request_line)
if !match.empty()
method = match.str(1)
url = match.str(2)
args = match.str(4)
version = match.str(5)
end
foreach line in request_header
var match = request_header_reg.match(line)
if !match.empty()
switch match.str(1)
case "Host"
host = match.str(2)
end
case "Connection"
connection = match.str(2)
end
case "Content-Length"
content_length = match.str(2).to_number()
end
end
end
end
end
function send_response(code, data, type)
sock.send("HTTP/" + version + " " + code + "\r\n")
sock.send(compose_http_time() + "\r\n")
sock.send("Connection: " + connection)
sock.send("Content-Length: " + data.size + "\r\n")
sock.send("Content-Type: " + type + "\r\n")
sock.send("\r\n")
sock.send(data)
end
end
# Logs
var log_stream = null
function log(msg)
if log_stream != null
log_stream.println("[" + compose_http_time() + "]: " + msg)
end
end
# Coroutines
struct worker_type
var co = null
var server = null
end
function wait_sock(sock, size)
while sock.available() < size
runtime.yield()
end
end
function worker_main(self)
constant buffer_size = 256
loop
# Accept new connection
while self->server->ac_lock
runtime.yield()
end
self->server->ac_lock = true
var sock = new tcp.socket
runtime.await_s(sock.accept, {self->server->ac})
self->server->ac_lock = false
runtime.yield()
# Process request
var header = new array
var buffer = new string
loop
wait_sock(sock, 1)
var str = sock.receive(buffer_size)
var skip_cntl_n = false
var end_of_header = 0
foreach ch in str
if end_of_header == 2
buffer += ch
continue
end
if skip_cntl_n
skip_cntl_n = false
if ch == '\n'
header.push_back(buffer)
buffer = new string
++end_of_header
continue
end
log("Data transmission error: expected \\n after \\r.")
end
if ch == '\r'
skip_cntl_n = true
continue
end
end_of_header = 0
buffer += ch
end
until end_of_header == 2
# Construct session
var session = new http_session{sock, header}
log("Received: Method = " + session.method + ", URL = " + session.url + ", Host = " + session.host)
# Receive POST data
var data = null
if session.method == "POST"
data = buffer
if session.content_length > buffer.size
var last_length = session.content_length - buffer.size
while last_length > buffer_size
wait_sock(sock, buffer_size)
data += sock.receive(buffer_size)
last_length -= buffer_size
end
wait_sock(sock, last_length)
data += sock.receive(last_length)
end
else
data = session.args
end
# Calling function
link url_map = self->server->url_map
if url_map.exist(session.url)
url_map[session.url](session, data)
else
url_map["404"](session, data)
end
sock.close()
runtime.yield()
end
end
function read_file(path)
var ifs = iostream.ifstream(path)
var data = new string
loop
var ch = ifs.get()
if ifs.good() && !ifs.eof()
data += ch
else
break
end
end
return move(data)
end
# Public Interfaces
var proxy = null, timeout_ms = null, low_speed_limit = null
function http_get(url)
var buff = new iostream.char_buff
var session = curl.make_session_os(buff.get_ostream())
session.set_url(url)
session.allow_redirect(true)
if proxy != null
session.set_proxy(proxy)
end
session.set_ssl_verify_host(false)
session.set_ssl_verify_peer(false)
if timeout_ms != null
session.set_connect_timeout_ms(timeout_ms)
session.set_accept_timeout_ms(timeout_ms)
session.set_low_speed_time(timeout_ms)
end
if low_speed_limit != null
session.set_low_speed_limit(low_speed_limit)
end
if session.perform()
return buff.get_string()
else
return null
end
end
function http_post(url, post_fields)
var buff = new iostream.char_buff
var session = curl.make_session_os(buff.get_ostream())
session.set_url(url)
session.allow_redirect(true)
if proxy != null
session.set_proxy(proxy)
end
session.set_http_post(true)
session.set_http_post_fields(post_fields)
session.set_ssl_verify_host(false)
session.set_ssl_verify_peer(false)
if timeout_ms != null
session.set_connect_timeout_ms(timeout_ms)
session.set_accept_timeout_ms(timeout_ms)
session.set_low_speed_time(timeout_ms)
end
if low_speed_limit != null
session.set_low_speed_limit(low_speed_limit)
end
if session.perform()
return buff.get_string()
else
return null
end
end
class http_server
var ac = null
var ac_lock = false
var url_map = new hash_map
var worker_list = new array
var worker_count = 10
function initialize()
url_map.insert("404", [](session, post_data){
var response_data = "<html><head><meta charset=\"UTF-8\"></head><body><p>404 Not Found: " + session.url + "</p></body></html>"
session.send_response(state_codes.code_404, response_data, "text/html")
})
end
function bind_page(url, path, state_code)
var response_data = read_file(path)
url_map.insert(url, [response_data, state_code](session, post_data){
session.send_response(state_code, response_data, "text/html")
})
end
function bind_func(url, func : function)
url_map.insert(url, func)
end
function listen(port : integer)
ac = tcp.acceptor(tcp.endpoint_v4(port))
end
function run()
# Init workers
foreach it in range(worker_count)
var worker = gcnew worker_type
worker->server = &this
worker->co = runtime.create_co_s(worker_main, {worker})
runtime.resume(worker->co)
worker_list.push_back(worker)
end
loop
foreach worker in worker_list
runtime.resume(worker->co)
end
end
end
end