├── .gitignore ├── LICENSE ├── README.md └── http.axi /.gitignore: -------------------------------------------------------------------------------- 1 | *.src 2 | *.tkn 3 | *.tko 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Corporate Initiatives 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Netlinx HTTP lib 2 | ==== 3 | A minimal HTTP library for working with web services natively in NetLinx. 4 | 5 | 6 | Usage 7 | ----- 8 | 9 | As there’s no blocking socket IO in NetLinx when you make a call to any of the request function (`http_get`, `http_head`, `http_put`, `http_post`, `http_patch` or `http_delete`) this will return you a unique ‘sequence number’. 10 | 11 | When a response is received it will call back to a function `http_response_received(..)` which you implement in your code. This will contain 3 arguments: the sequence number, the full request object and a fully parsed response object ready for you to deal with as you like. If you’re not interested in response feel free to omit this, otherwise just pop in a `#define HTTP_RESPONSE_CALLBACK` - similar to the RMS SDK. 12 | 13 | An error, when encountered, will callback to `http_error(..)` along with the sequence number, the target request host, the full request object and an error number. `#define HTTP_ERROR_CALLBACK` signifies you’re interested in dealing with these. 14 | 15 | By default it can handle up to 10 parallel requests but if you’re really hammering it, or dealing with a slow connection / server this can be bumped up or down by changing `HTTP_MAX_PARALLEL_REQUESTS`. 16 | 17 | 18 | Other info 19 | ---------- 20 | 21 | This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), so feel free to integrate it in your commercial products. -------------------------------------------------------------------------------- /http.axi: -------------------------------------------------------------------------------- 1 | /** 2 | * A minimal HTTP library for working with web services natively in NetLinx. 3 | * 4 | * ...still not sure why this isn't a builtin library... 5 | */ 6 | program_name='http' 7 | 8 | 9 | define_constant 10 | 11 | // Due the the way NetLinx works timeline event handlers cannot use an id array. 12 | // If changing this value, update event handler and definitions below for the 13 | // response timeout timelines to match. 14 | integer HTTP_MAX_PARALLEL_REQUESTS = 15 15 | 16 | integer HTTP_BASE_PORT = 30 17 | integer HTTP_MAX_REQUEST_LENGTH = 4096 18 | integer HTTP_MAX_RESPONSE_LENGTH = 16384 19 | integer HTTP_MAX_LINE_LENGTH = 2048 20 | 21 | long HTTP_TIMEOUT_TL_1 = 30 22 | long HTTP_TIMEOUT_TL_2 = 31 23 | long HTTP_TIMEOUT_TL_3 = 32 24 | long HTTP_TIMEOUT_TL_4 = 33 25 | long HTTP_TIMEOUT_TL_5 = 34 26 | long HTTP_TIMEOUT_TL_6 = 35 27 | long HTTP_TIMEOUT_TL_7 = 36 28 | long HTTP_TIMEOUT_TL_8 = 37 29 | long HTTP_TIMEOUT_TL_9 = 38 30 | long HTTP_TIMEOUT_TL_10 = 39 31 | long HTTP_TIMEOUT_TL_11 = 40 32 | long HTTP_TIMEOUT_TL_12 = 41 33 | long HTTP_TIMEOUT_TL_13 = 42 34 | long HTTP_TIMEOUT_TL_14 = 43 35 | long HTTP_TIMEOUT_TL_15 = 44 36 | long HTTP_TIMEOUT_TL[] = { 37 | HTTP_TIMEOUT_TL_1, 38 | HTTP_TIMEOUT_TL_2, 39 | HTTP_TIMEOUT_TL_3, 40 | HTTP_TIMEOUT_TL_4, 41 | HTTP_TIMEOUT_TL_5, 42 | HTTP_TIMEOUT_TL_6, 43 | HTTP_TIMEOUT_TL_7, 44 | HTTP_TIMEOUT_TL_8, 45 | HTTP_TIMEOUT_TL_9, 46 | HTTP_TIMEOUT_TL_10, 47 | HTTP_TIMEOUT_TL_11, 48 | HTTP_TIMEOUT_TL_12, 49 | HTTP_TIMEOUT_TL_13, 50 | HTTP_TIMEOUT_TL_14, 51 | HTTP_TIMEOUT_TL_15 52 | } 53 | long HTTP_TIMEOUT_INTERVAL[] = {10000} 54 | 55 | integer HTTP_ERR_RESPONSE_TIME_OUT = 1 56 | integer HTTP_ERR_NO_MEM = 2 57 | integer HTTP_ERR_UNKNOWN_HOST = 4 58 | integer HTTP_ERR_CONNECTION_REFUSED = 6 59 | integer HTTP_ERR_CONNECT_TIME_OUT = 7 60 | integer HTTP_ERR_UNKNOWN = 8 61 | integer HTTP_ERR_ALREADY_CLOSED = 9 62 | integer HTTP_ERR_LOCAL_PORT_ALREADY_USED = 14 63 | integer HTTP_ERR_TOO_MANY_OPEN_SOCKETS = 16 64 | integer HTTP_ERR_LOCAL_PORT_NOT_OPEN = 17 65 | integer HTTP_ERR_MALFORMED_RESPONSE = 18 66 | char HTTP_ERR_TEXT[][32] = { 67 | 'response time out', 68 | 'out of memory', 69 | 'error 3 - unknown', 70 | 'unknown host', 71 | 'error 5 - unknown', 72 | 'connection refused', 73 | 'connect time out', 74 | 'error 8 - unknown', 75 | 'already closed', 76 | 'error 10 - unknown', 77 | 'error 11 - unknown', 78 | 'error 12 - unknown', 79 | 'error 13 - unknown', 80 | 'local port already used', 81 | 'error 15 - unknown', 82 | 'too many open sockets', 83 | 'local port not open', 84 | 'malformed response' 85 | } 86 | 87 | integer HTTP_SOCKET_STATE_CLOSED = 0 88 | integer HTTP_SOCKET_STATE_OPENING = 1 89 | integer HTTP_SOCKET_STATE_OPEN = 2 90 | integer HTTP_SOCKET_STATE_CLOSING = 3 91 | 92 | 93 | define_type 94 | 95 | structure http_header { 96 | char key[64] 97 | char value[256] 98 | } 99 | 100 | structure http_request { 101 | char method[7] 102 | char uri[256] 103 | http_header headers[5] 104 | char body[2048] 105 | } 106 | 107 | structure http_response { 108 | char version[8] 109 | integer code 110 | char message[64] 111 | http_header headers[10] 112 | char body[16384] 113 | } 114 | 115 | structure http_req_obj { 116 | long seq 117 | char host[512] 118 | http_request request 119 | } 120 | 121 | structure http_url { 122 | char protocol[16] 123 | char host[256] 124 | char uri[256] 125 | } 126 | 127 | 128 | define_variable 129 | 130 | volatile dev http_sockets[HTTP_MAX_PARALLEL_REQUESTS] 131 | volatile integer http_socket_state[HTTP_MAX_PARALLEL_REQUESTS] 132 | volatile char http_socket_buff[HTTP_MAX_PARALLEL_REQUESTS][HTTP_MAX_RESPONSE_LENGTH] 133 | volatile http_req_obj http_req_objs[HTTP_MAX_PARALLEL_REQUESTS] 134 | 135 | 136 | /** 137 | * Sends a HTTP GET request 138 | */ 139 | define_function long http_get(char resource[]) { 140 | stack_var http_url url 141 | stack_var http_request request 142 | 143 | http_parse_url("resource", url) 144 | 145 | request.method = 'GET' 146 | request.body = '' 147 | 148 | return http_execute_request(url, request) 149 | } 150 | 151 | /** 152 | * Sends a HTTP HEAD request 153 | */ 154 | define_function long http_head(char resource[]) { 155 | stack_var http_url url 156 | stack_var http_request request 157 | 158 | http_parse_url("resource", url) 159 | 160 | request.method = 'HEAD' 161 | request.body = '' 162 | 163 | return http_execute_request(url, request) 164 | } 165 | 166 | /** 167 | * Sends a HTTP PUT request 168 | */ 169 | define_function long http_put(char resource[], char body[]) { 170 | stack_var http_url url 171 | stack_var http_request request 172 | 173 | http_parse_url("resource", url) 174 | 175 | request.method = 'PUT' 176 | request.body = body 177 | 178 | return http_execute_request(url, request) 179 | } 180 | 181 | /** 182 | * Sends a HTTP POST request 183 | */ 184 | define_function long http_post(char resource[], char body[]) { 185 | stack_var http_url url 186 | stack_var http_request request 187 | 188 | http_parse_url("resource", url) 189 | 190 | request.method = 'POST' 191 | request.body = body 192 | 193 | return http_execute_request(url, request) 194 | } 195 | 196 | /** 197 | * Sends a HTTP PATCH request 198 | */ 199 | define_function long http_patch(char resource[], char body[]) { 200 | stack_var http_url url 201 | stack_var http_request request 202 | 203 | http_parse_url("resource", url) 204 | 205 | request.method = 'PATCH' 206 | request.body = body 207 | 208 | return http_execute_request(url, request) 209 | } 210 | 211 | /** 212 | * Sends a HTTP DELETE request 213 | */ 214 | define_function long http_delete(char resource[]) { 215 | stack_var http_url url 216 | stack_var http_request request 217 | 218 | http_parse_url("resource", url) 219 | 220 | request.method = 'DELETE' 221 | 222 | return http_execute_request(url, request) 223 | } 224 | 225 | /** 226 | * Retrieves the values of a header from a http_header array. 227 | */ 228 | define_function char[256] http_get_header(http_header headers[], 229 | char key[]) { 230 | stack_var integer x 231 | 232 | for (x = 1; x <= length_array(headers); x++) { 233 | if (headers[x].key == key) { 234 | return headers[x].value 235 | } 236 | } 237 | 238 | return '' 239 | } 240 | 241 | /** 242 | * Builds a HTTP request ready for transmission. 243 | */ 244 | define_function char[HTTP_MAX_REQUEST_LENGTH] http_build_request(char host[], 245 | http_request request) { 246 | stack_var char ret[HTTP_MAX_REQUEST_LENGTH] 247 | stack_var http_header header 248 | stack_var integer x 249 | 250 | request.method = upper_string(request.method) 251 | if (request.method != 'GET' && 252 | request.method != 'HEAD' && 253 | request.method != 'POST' && 254 | request.method != 'PUT' && 255 | request.method != 'DELETE' && 256 | request.method != 'TRACE' && 257 | request.method != 'OPTIONS' && 258 | request.method != 'CONNECT' && 259 | request.method != 'PATCH') { 260 | amx_log(AMX_ERROR, "'Invalid HTTP method in request (', request.method, ')'") 261 | return '' 262 | } 263 | 264 | ret = "request.method, ' ', request.uri, ' HTTP/1.1', $0d, $0a" 265 | 266 | ret = "ret, 'Host: ', host, $0d, $0a" 267 | ret = "ret, 'Connection: close', $0d, $0a" 268 | 269 | if (request.body != '') { 270 | ret = "ret, 'Content-Length: ', itoa(length_string(request.body)), $0d, $0a" 271 | if (request.body[1] == '{' && 272 | request.body[length_array(request.body)] == '}') { 273 | ret = "ret, 'Content-Type: application/json', $0d, $0a" 274 | } 275 | } 276 | 277 | for (x = 1; x <= length_array(request.headers); x++) { 278 | header = request.headers[x] 279 | if (header.key != '') { 280 | ret = "ret, header.key, ': ', header.value, $0d, $0a" 281 | } 282 | } 283 | ret = "ret, $0d, $0a" 284 | 285 | if (request.body != '') { 286 | ret = "ret, request.body, $0d, $0a" 287 | ret = "ret, $0d, $0a" 288 | } 289 | 290 | return ret 291 | } 292 | 293 | /** 294 | * Creates a sequence id to assist in identifying http responses. 295 | */ 296 | define_function long http_next_seq() { 297 | local_var long next_seq 298 | stack_var long seq 299 | 300 | if (next_seq == 0) { 301 | next_seq = 1 302 | } 303 | 304 | seq = next_seq 305 | next_seq++ // not need to mod, we'll just wrap around > 0xffffffff 306 | 307 | return seq 308 | } 309 | 310 | /** 311 | * Checks if the specified HTTP request resource slot is currently in use. 312 | */ 313 | define_function char http_resource_in_use(integer resource_id) { 314 | return http_req_objs[resource_id].seq > 0 || 315 | http_socket_state[resource_id] != HTTP_SOCKET_STATE_CLOSED 316 | } 317 | 318 | /** 319 | * Allocates a request to one of our HTTP request resource slots for execution. 320 | * 321 | * note: req_obj argument will be written to 322 | */ 323 | define_function integer http_build_req_obj(http_req_obj req_obj, char host[], 324 | http_request request) { 325 | stack_var integer resource_id 326 | 327 | for (resource_id = 1; resource_id <= HTTP_MAX_PARALLEL_REQUESTS; resource_id++) { 328 | if (http_resource_in_use(resource_id) == false) { 329 | req_obj.seq = http_next_seq() 330 | req_obj.host = host 331 | req_obj.request = request 332 | 333 | http_req_objs[resource_id] = req_obj 334 | 335 | return resource_id 336 | } 337 | } 338 | 339 | return 0 340 | } 341 | 342 | /** 343 | * Returns a resource slot back to our pool. 344 | */ 345 | define_function http_release_resources(integer resource_id) { 346 | stack_var http_req_obj null 347 | 348 | if (timeline_active(HTTP_TIMEOUT_TL[resource_id])) { 349 | timeline_kill(HTTP_TIMEOUT_TL[resource_id]) 350 | } 351 | 352 | if (http_socket_state[resource_id] == HTTP_SOCKET_STATE_OPEN) { 353 | ip_client_close(http_sockets[resource_id].port) 354 | http_socket_state[resource_id] = HTTP_SOCKET_STATE_CLOSING 355 | } 356 | 357 | clear_buffer http_socket_buff[resource_id] 358 | 359 | http_req_objs[resource_id] = null 360 | } 361 | 362 | /** 363 | * Sends a HTTP request. 364 | * 365 | * As we don't have the ability to block whist awaiting a response this will 366 | * return a sequence id. When a response is received by the http lib this 367 | * sequence id is passed to the callback function along with the response data. 368 | * It is the responsibility of the implementer to then handle these accordingly. 369 | */ 370 | define_function long http_execute_request(http_url url, http_request request) { 371 | stack_var integer resource_id 372 | stack_var http_req_obj req_obj 373 | stack_var char server_address[256] 374 | stack_var integer server_port 375 | stack_var integer pos 376 | 377 | if (url.host == '') { 378 | amx_log(AMX_ERROR, 'Invalid host') 379 | return 0 380 | } 381 | 382 | if (request.uri == '') { 383 | request.uri = url.uri 384 | } 385 | 386 | if (request.uri[1] != '/' && 387 | request.uri != '*' && 388 | left_string(request.uri, 7) != 'http://') { 389 | amx_log(AMX_ERROR, "'Invalid request URI (', request.uri, ')'") 390 | return 0 391 | } 392 | 393 | resource_id = http_build_req_obj(req_obj, url.host, request) 394 | if (resource_id == 0) { 395 | amx_log(AMX_ERROR, 'HTTP lib resources at capacity. Request dropped.') 396 | return 0 397 | } 398 | 399 | pos = find_string(url.host, ':', 1) 400 | if (pos) { 401 | server_address = left_string(url.host, pos - 1) 402 | server_port = atoi(right_string(url.host, length_string(url.host) - pos)) 403 | } else { 404 | server_address = url.host 405 | server_port = 80 406 | } 407 | 408 | ip_client_open(http_sockets[resource_id].port, server_address, server_port, IP_TCP) 409 | http_socket_state[resource_id] = HTTP_SOCKET_STATE_OPENING 410 | 411 | timeline_create(HTTP_TIMEOUT_TL[resource_id], 412 | HTTP_TIMEOUT_INTERVAL, 413 | 1, 414 | TIMELINE_ABSOLUTE, 415 | TIMELINE_ONCE) 416 | 417 | // note: request transmitted from socket online event 418 | 419 | return req_obj.seq 420 | } 421 | 422 | /** 423 | * Remove one line of data from a buffer and return. 424 | * 425 | * Warning: this performs a desctructive action to the passed character buffer. 426 | */ 427 | define_function char[HTTP_MAX_LINE_LENGTH] http_readln(char buff[]) { 428 | stack_var char line[HTTP_MAX_LINE_LENGTH] 429 | 430 | line = remove_string(buff, "$0d, $0a", 1) 431 | line = left_string(line, length_string(line) - 2) 432 | 433 | return line 434 | } 435 | 436 | /** 437 | * Parse out HTTP response headers from a raw response in to a http_header 438 | * array. 439 | * 440 | * Warning: this performs a desctructive action to the passed character buffer. 441 | */ 442 | define_function http_parse_headers(char buff[], http_header headers[]) { 443 | stack_var char line[HTTP_MAX_LINE_LENGTH] 444 | stack_var integer pos 445 | stack_var integer x 446 | 447 | for (x = 1; x <= max_length_array(headers); x++) { 448 | line = http_readln(buff) 449 | if (line == '') { 450 | break; 451 | } 452 | if (left_string(line, 5) != 'HTTP/') { 453 | pos = find_string(line, ': ', 1) 454 | headers[x].key = left_string(line, pos - 1) 455 | headers[x].value = right_string(line, length_string(line) - pos - 1) 456 | } 457 | } 458 | set_length_array(headers, x) 459 | } 460 | 461 | /** 462 | * Parse a raw HTTP response into a response structure. 463 | * 464 | * Warning: this performs a desctructive action on the passed character buffer 465 | * and also writes directly to the passed http_response variable 466 | */ 467 | define_function char http_parse_response(char buff[], http_response response) { 468 | stack_var char line[HTTP_MAX_LINE_LENGTH] 469 | 470 | line = http_readln(buff) 471 | if (left_string(line, 5) != 'HTTP/') { 472 | return false 473 | } 474 | response.version = left_string(remove_string(line, ' ', 1), 8) 475 | response.code = atoi(remove_string(line, ' ', 1)) 476 | response.message = line 477 | 478 | http_parse_headers(buff, response.headers) 479 | 480 | response.body = buff 481 | 482 | return true 483 | } 484 | 485 | /** 486 | * Parses a URL string into it's component values. 487 | * 488 | * Warning: this performs a desctructive action on the passed character buffer 489 | * and also writes directly to the passed http_url variable 490 | */ 491 | define_function char http_parse_url(char buff[], http_url url) { 492 | stack_var integer pos 493 | stack_var char tmp[256] 494 | 495 | pos = find_string(buff, '://', 1) 496 | if (pos) { 497 | url.protocol = remove_string(buff, '://', 1) 498 | url.protocol = left_string(url.protocol, length_string(url.protocol) - 3) 499 | url.protocol = lower_string(url.protocol) 500 | } else { 501 | url.protocol = 'http' 502 | } 503 | 504 | pos = find_string(buff, '/', 1) 505 | if (pos) { 506 | url.host = left_string(buff, pos - 1) 507 | url.uri = right_string(buff, length_string(buff) - (pos - 1)) 508 | } else { 509 | url.host = buff 510 | url.uri = '/' 511 | } 512 | 513 | return true 514 | } 515 | 516 | 517 | define_start 518 | 519 | { 520 | stack_var integer x 521 | 522 | for (x = 1; x <= HTTP_MAX_PARALLEL_REQUESTS; x++) { 523 | http_sockets[x].number = 0 524 | http_sockets[x].port = HTTP_BASE_PORT + x - 1 525 | http_sockets[x].system = system_number 526 | create_buffer http_sockets[x], http_socket_buff[x] 527 | } 528 | set_length_array(http_sockets, HTTP_MAX_PARALLEL_REQUESTS) 529 | 530 | rebuild_event() 531 | } 532 | 533 | 534 | define_event 535 | 536 | data_event[http_sockets] { 537 | 538 | online: { 539 | stack_var integer resource_id 540 | stack_var http_req_obj req_obj 541 | 542 | resource_id = get_last(http_sockets) 543 | req_obj = http_req_objs[resource_id] 544 | 545 | http_socket_state[resource_id] = HTTP_SOCKET_STATE_OPEN 546 | 547 | if (http_resource_in_use(resource_id)) { 548 | send_string data.device, http_build_request(req_obj.host, req_obj.request) 549 | } else { 550 | http_release_resources(resource_id) 551 | } 552 | } 553 | 554 | offline: { 555 | stack_var integer resource_id 556 | stack_var http_req_obj req_obj 557 | stack_var http_response response 558 | stack_var char valid_response 559 | 560 | resource_id = get_last(http_sockets) 561 | req_obj = http_req_objs[resource_id] 562 | 563 | http_socket_state[resource_id] = HTTP_SOCKET_STATE_CLOSED 564 | 565 | if (http_resource_in_use(resource_id)) { 566 | 567 | valid_response = http_parse_response(http_socket_buff[resource_id], response) 568 | 569 | http_release_resources(resource_id) 570 | 571 | if (valid_response) { 572 | #if_defined HTTP_RESPONSE_CALLBACK 573 | http_response_received(req_obj.seq, req_obj.host, req_obj.request, response) 574 | #end_if 575 | } else { 576 | amx_log(AMX_ERROR, "'HTTP parse error (', HTTP_ERR_TEXT[HTTP_ERR_MALFORMED_RESPONSE], ')'") 577 | 578 | #if_defined HTTP_ERROR_CALLBACK 579 | http_error(req_obj.seq, req_obj.host, req_obj.request, HTTP_ERR_MALFORMED_RESPONSE) 580 | #end_if 581 | } 582 | } 583 | } 584 | 585 | onerror: { 586 | stack_var integer resource_id 587 | stack_var http_req_obj req_obj 588 | 589 | resource_id = get_last(http_sockets) 590 | req_obj = http_req_objs[resource_id] 591 | 592 | if (data.number != HTTP_ERR_LOCAL_PORT_ALREADY_USED) { 593 | http_socket_state[resource_id] = HTTP_SOCKET_STATE_CLOSED 594 | } 595 | 596 | if (http_resource_in_use(resource_id)) { 597 | amx_log(AMX_ERROR, "'HTTP socket error (', HTTP_ERR_TEXT[data.number], ')'") 598 | 599 | http_release_resources(resource_id) 600 | 601 | #if_defined HTTP_ERROR_CALLBACK 602 | http_error(req_obj.seq, req_obj.host, req_obj.request, data.number) 603 | #end_if 604 | } 605 | } 606 | 607 | string: {} 608 | 609 | } 610 | 611 | timeline_event[HTTP_TIMEOUT_TL_1] 612 | timeline_event[HTTP_TIMEOUT_TL_2] 613 | timeline_event[HTTP_TIMEOUT_TL_3] 614 | timeline_event[HTTP_TIMEOUT_TL_4] 615 | timeline_event[HTTP_TIMEOUT_TL_5] 616 | timeline_event[HTTP_TIMEOUT_TL_6] 617 | timeline_event[HTTP_TIMEOUT_TL_7] 618 | timeline_event[HTTP_TIMEOUT_TL_8] 619 | timeline_event[HTTP_TIMEOUT_TL_9] 620 | timeline_event[HTTP_TIMEOUT_TL_10] 621 | timeline_event[HTTP_TIMEOUT_TL_11] 622 | timeline_event[HTTP_TIMEOUT_TL_12] 623 | timeline_event[HTTP_TIMEOUT_TL_13] 624 | timeline_event[HTTP_TIMEOUT_TL_14] 625 | timeline_event[HTTP_TIMEOUT_TL_15] { 626 | stack_var integer resource_id 627 | stack_var http_req_obj req_obj 628 | 629 | for (resource_id = 1; resource_id <= length_array(HTTP_TIMEOUT_TL); resource_id++) { 630 | if (timeline.id == HTTP_TIMEOUT_TL[resource_id]) { 631 | break 632 | } 633 | } 634 | req_obj = http_req_objs[resource_id] 635 | 636 | if (http_resource_in_use(resource_id)) { 637 | amx_log(AMX_ERROR, 'HTTP response timeout') 638 | 639 | http_release_resources(resource_id) 640 | 641 | #if_defined HTTP_ERROR_CALLBACK 642 | http_error(req_obj.seq, req_obj.host, req_obj.request, HTTP_ERR_RESPONSE_TIME_OUT) 643 | #end_if 644 | } 645 | } 646 | 647 | --------------------------------------------------------------------------------