HTTP Client
From Yate Documentation
(Difference between revisions)
(→Testing) |
(→Testing) |
||
Line 123: | Line 123: | ||
== Testing == | == Testing == | ||
− | url: "http://www.null.ro", | + | |
+ | httptest.js: | ||
+ | |||
+ | #require "lib_str_util.js" | ||
+ | |||
+ | Engine.debugName("httptest"); | ||
+ | Message.trackName("httptest"); | ||
+ | |||
+ | pending = new Engine.HashList(101); | ||
+ | targetid = 0; | ||
+ | |||
+ | // Default request parameters | ||
+ | http_params = { | ||
+ | method: "GET", | ||
+ | url: "http://www.null.ro", | ||
+ | // Parameters overriding module defaults | ||
+ | // timeout: 5000, | ||
+ | // redirect: 0, | ||
+ | // agent: "HTTP Test", | ||
+ | // max_hdr_length: 100, | ||
+ | // max_body_length: 100, | ||
+ | // server_keep_alive: false, | ||
+ | // debug: false, | ||
+ | // Parameters for requests with body | ||
+ | // accept: "application/json", | ||
+ | // type: "application/json", | ||
+ | // body: "{request:\"REQ\"}", | ||
+ | }; | ||
+ | |||
+ | // Send a HTTP request | ||
+ | // params: Message parameters | ||
+ | // sync: Send synchronous | ||
+ | // notify: Async only, indicate if response is expected | ||
+ | // enq: true to enqueue, false to dispatch and check if request was started | ||
+ | // Ignored if notify is not requested | ||
+ | function httpRequest(params,sync,notify,enq) | ||
+ | { | ||
+ | var req = undefined; | ||
+ | var m = new Message("http.request",false,params); | ||
+ | m.wait = !!sync; | ||
+ | if (!sync) { | ||
+ | if (!notify) { | ||
+ | m.enqueue(); | ||
+ | return true; | ||
+ | } | ||
+ | notify = Engine.debugName() + "/" + ++targetid; | ||
+ | req = pending[notify] = {id:notify}; | ||
+ | req.info = m.method + ":" + m.url; | ||
+ | m.notify = notify; | ||
+ | if (enq) { | ||
+ | // Instruct the module to notify us if request failed to be started | ||
+ | m.local_failure_response_async = true; | ||
+ | m.enqueue(); | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | var ok = m.dispatch(true); | ||
+ | if (req) { | ||
+ | if (ok) | ||
+ | return true; | ||
+ | // Failed to start, remove from tracking list | ||
+ | delete pending[req.id]; | ||
+ | } | ||
+ | handleHttpResult(m,req,ok); | ||
+ | return ok; | ||
+ | } | ||
+ | |||
+ | // Handle HTTP result message | ||
+ | // req: pending request if async | ||
+ | // ok: boolean true/false for immediate response, undefined if this function is called from async notify | ||
+ | function handleHttpResult(msg,req,ok) | ||
+ | { | ||
+ | if (req) { | ||
+ | var info = req.info; | ||
+ | // Async result in http.notify | ||
+ | if (undefined === ok) | ||
+ | info += " (async notify)"; | ||
+ | } | ||
+ | else | ||
+ | var info = msg.method + ":" + msg.url; | ||
+ | |||
+ | var result = ""; | ||
+ | ok = (undefined !== msg.code); | ||
+ | if (ok) { | ||
+ | result += " '" + msg.code + " " + msg.code_text + "'"; | ||
+ | if (msg.xtype) | ||
+ | result += " Content-Type='" + msg.xtype + "'"; | ||
+ | if (msg.xcontent_length) | ||
+ | result += " Content-Length=" + msg.xcontent_length; | ||
+ | var b = msg.retValue(); | ||
+ | if (b.length) { | ||
+ | // Answer body may contain NULL chars | ||
+ | if ("hex" == msg.xbody_encoding) | ||
+ | result += " body_len=" + (b.length / 2) + " (hex)"; | ||
+ | else | ||
+ | result += " body_len=" + b.length; | ||
+ | } | ||
+ | } | ||
+ | else { | ||
+ | if (msg.reason) | ||
+ | result += " reason=" + msg.reason; | ||
+ | if (msg.error) | ||
+ | result += " error='" + msg.error + "'"; | ||
+ | if (!result) | ||
+ | result = " unknown error"; | ||
+ | } | ||
+ | if (ok) | ||
+ | Engine.debug(Engine.DebugInfo,info,"got answer" + result); | ||
+ | else | ||
+ | Engine.debug(Engine.DebugNote,info,"failed" + result); | ||
+ | } | ||
+ | |||
+ | function onHttpNotify(msg) | ||
+ | { | ||
+ | var p = pending[msg.targetid]; | ||
+ | if (!p) | ||
+ | return false; | ||
+ | delete pending[msg.targetid]; | ||
+ | handleHttpResult(msg,p); | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | function parseParams(line) | ||
+ | { | ||
+ | var res = {}; | ||
+ | while (line.length) { | ||
+ | var m = line.match(/^(.* )?([^= ]+)=([^=]*)$/); | ||
+ | if (!m) | ||
+ | break; | ||
+ | var s = "" + m[3]; | ||
+ | res[m[2]] = s.trim(); | ||
+ | line = "" + m[1]; | ||
+ | } | ||
+ | return res; | ||
+ | } | ||
+ | |||
+ | // Handle commands | ||
+ | function onCommand(msg) | ||
+ | { | ||
+ | if (!msg.line) { | ||
+ | switch (msg.partline) { | ||
+ | case undefined: | ||
+ | case "": | ||
+ | case "help": | ||
+ | oneCompletion(msg,"httptest",msg.partword); | ||
+ | return false; | ||
+ | case "httptest": | ||
+ | oneCompletion(msg,"send",msg.partword); | ||
+ | return false; | ||
+ | case "httptest send": | ||
+ | oneCompletion(msg,"sync",msg.partword); | ||
+ | oneCompletion(msg,"async",msg.partword); | ||
+ | oneCompletion(msg,"async_enq",msg.partword); | ||
+ | oneCompletion(msg,"fire_and_forget",msg.partword); | ||
+ | return false; | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | var s = msg.line; | ||
+ | var m = s.match(/^httptest send (sync|async|async_enq|fire_and_forget)( .*)?$/); | ||
+ | if (m) { | ||
+ | var params = parseParams(m[2]); | ||
+ | for (var p in http_params) | ||
+ | if (undefined === params[p]) | ||
+ | params[p] = http_params[p]; | ||
+ | Engine.debug(Engine.DebugAll,"Sending",m[1],params.method + ":" + params.url); | ||
+ | var ok = false; | ||
+ | switch (m[1]) { | ||
+ | case "sync": | ||
+ | ok = httpRequest(params,true); | ||
+ | break; | ||
+ | case "async": | ||
+ | ok = httpRequest(params,false,true); | ||
+ | break; | ||
+ | case "async_enq": | ||
+ | ok = httpRequest(params,false,true,true); | ||
+ | break; | ||
+ | default: | ||
+ | ok = httpRequest(params,false); | ||
+ | } | ||
+ | if (ok) | ||
+ | msg.retValue("OK\r\n"); | ||
+ | else | ||
+ | msg.retValue("Failure\r\n"); | ||
+ | return true; | ||
+ | } | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | help = " httptest send {sync|async|async_enq|fire_and_forget} [param=value ...]\r\n"; | ||
+ | function onHelp(msg) | ||
+ | { | ||
+ | if (msg.line) { | ||
+ | if ("httptest" == msg.line) { | ||
+ | msg.retValue(help + "Control the HTTP Test operation\r\n"); | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | msg.retValue(msg.retValue() + help); | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | Engine.setDebug("level 10"); | ||
+ | |||
+ | Message.install(onHttpNotify,"http.notify",100); | ||
+ | Message.install(onCommand,"engine.command",150); | ||
+ | Message.install(onHelp,"engine.help",150); |
Revision as of 13:06, 2 October 2019
Contents |
Description
The module implements sending of HTTP requests to a server.
It is based on libcurl.
Handled message
Message name http.request.
Parameters:
- url: Required HTTP URL
- method: HTTP method to send. Defaults to GET if missing
- body: Request body to set (plain character string). Ignored if method is GET or HEAD
- accept: Optional Accept header contents
- type: Optional Content-Type header contents
- wait: Boolean (default: false). The sender is synchronously waiting for completion
- notify: Target id for notification. Ignored for synchronous requests
- local_failure_response_async: Boolean (default: false). Always emit a notification on local failure (don't return error in handled message).
This parameter is ignored for synchronous requests or empty notify.
This parameter may be used by modules using full asynchronous sending without needing to check if the request started.
Further message processing by other module(s) will stop if this parameter is set to true.
The following parameters may be used to override the configured defaults: timeout, redirect, agent, max_hdr_length, max_body_length, server_keep_alive, debug.
See Configuration section for all module parameters allowed to be overridden in handled message.
The module will return true if:
- Message was accepted (validated)
- Synchronous requests: the sender should check the presence of code parameter on return to match success
- Asynchronous requests: the request was queued
- Request was not accepted but a notification was asynchronously sent (see local_failure_response_async parameter)
The module will return false in all other scenarios. reason and error parameters may be present in the message.
Returning result
Success
- code: Answer code
- code_text: Answer text (e.g. OK)
- xtype: The contents of received Content-Type header
- xcontent_length: The contents of received Content-Length header
- xbody_encoding: hex if received body contains NULL char(s). The returned body is a string containing HEX chars with no spaces
The received body is set in message's return value.
Failure
- reason: Failure reason. Here are some reasons that worth to be mentioned:
- timeout: Request timed out
- congestion: An asynchronous request was not taken from queue fast enough by the processing thread
- protocol-error: The module was not able to parse received response line to detect the code or received a header containing NULL char(s)
- rejected: The module can't accept the request because it already holds the configured maximum allowed active requests
- error: String describing the failure reason
Notification message
Message: http.notify
Parameters:
- targetid: The value set in the request's notify parameter
See Returning result section for all other parameters.
Status report
Status parameters:
- total: Total number of requests
- maxrequests: Maximum number of active (accepted) requests
- maxqueue: Maximum queue length (asynchronous requests waiting to start)
- total_accepted: Total number of accepted requests
- total_sync: Total number of accepted synchronous requests
- total_async_notify: Total number of accepted asynchronous requests requiring notification
- total_async_no_notify: Total number of accepted asynchronous requests not requiring notification
- queued: Current number of asynchronous requests waiting to be started
- process_async: Current number of running asynchronous requests
- process_sync: Current number of running synchronous requests
- timeout: The number of timed out requests
- congestion: The number of congested requests
- rejected: The number of rejected requests
Configuration
httpclient.conf
[general] ; General settings for the HTTP Client module ; redirect: integer: Enable HTTP 3xx redirects (max number of allowed redirects) ; This parameter is applied on reload and can be overridden in handled message ; Allowed interval: 0..3 ; 0: disable redirect ;redirect=0 ; timeout: integer: Maximum time, in milliseconds, that the request or a response is allowed to take ; This parameter is applied on reload and can be overridden in handled message ; Allowed interval: 100..600000 ;timeout=10000 ; max_hdr_length: integer: Maximum length for received headers ; This parameter is applied on reload and can be overridden in handled message ; Minimum allowed value s 200. Defaults to 16384 ;max_hdr_length=16384 ; max_body_length: integer: Maximum length for received body ; This parameter is applied on reload and can be overridden in handled message ; Minimum allowed value s 200. Defaults to 65535 ;max_body_length=65535 ; max_requests: integer: Maximum number of concurrent requests (sync or async) handled by the module ; This parameter is applied on reload ; New requests will be rejected by the module when this limit is reached ; Minimum allowed value is 100. Defaults to 20000 ;max_requests=20000 ; server_keep_alive: boolean: Tell the server we want it to keep the connection alive ; If enabled a 'Connection: Keep-Alive' header will be added to request ; This parameter is applied on reload and can be overridden in handled message ;server_keep_alive=yes ; agent: string: User agent to set in request ; This parameter is applied on reload and can be overridden in handled message ;agent=YATE/${version} ; async_priority: keyword: Priority of the thread processing the asynchronous requests ;async_priority=normal ; priority: integer: Priority level of http.request message handler ;priority=90 ; debug: boolean/integer: Debug mode for CURL library (displaying SSL data, headers, sent and received data) ; This parameter is applied on reload and can be overridden in handled message by a debug parameters ; This will translate to a debug level: 0: use output, greater than 0 use debug ; Boolean true is translated to debug level 0 ;debug=no
Testing
httptest.js:
#require "lib_str_util.js" Engine.debugName("httptest"); Message.trackName("httptest"); pending = new Engine.HashList(101); targetid = 0; // Default request parameters http_params = { method: "GET", url: "http://www.null.ro", // Parameters overriding module defaults // timeout: 5000, // redirect: 0, // agent: "HTTP Test", // max_hdr_length: 100, // max_body_length: 100, // server_keep_alive: false, // debug: false, // Parameters for requests with body // accept: "application/json", // type: "application/json", // body: "{request:\"REQ\"}", }; // Send a HTTP request // params: Message parameters // sync: Send synchronous // notify: Async only, indicate if response is expected // enq: true to enqueue, false to dispatch and check if request was started // Ignored if notify is not requested function httpRequest(params,sync,notify,enq) { var req = undefined; var m = new Message("http.request",false,params); m.wait = !!sync; if (!sync) { if (!notify) { m.enqueue(); return true; } notify = Engine.debugName() + "/" + ++targetid; req = pending[notify] = {id:notify}; req.info = m.method + ":" + m.url; m.notify = notify; if (enq) { // Instruct the module to notify us if request failed to be started m.local_failure_response_async = true; m.enqueue(); return true; } } var ok = m.dispatch(true); if (req) { if (ok) return true; // Failed to start, remove from tracking list delete pending[req.id]; } handleHttpResult(m,req,ok); return ok; } // Handle HTTP result message // req: pending request if async // ok: boolean true/false for immediate response, undefined if this function is called from async notify function handleHttpResult(msg,req,ok) { if (req) { var info = req.info; // Async result in http.notify if (undefined === ok) info += " (async notify)"; } else var info = msg.method + ":" + msg.url; var result = ""; ok = (undefined !== msg.code); if (ok) { result += " '" + msg.code + " " + msg.code_text + "'"; if (msg.xtype) result += " Content-Type='" + msg.xtype + "'"; if (msg.xcontent_length) result += " Content-Length=" + msg.xcontent_length; var b = msg.retValue(); if (b.length) { // Answer body may contain NULL chars if ("hex" == msg.xbody_encoding) result += " body_len=" + (b.length / 2) + " (hex)"; else result += " body_len=" + b.length; } } else { if (msg.reason) result += " reason=" + msg.reason; if (msg.error) result += " error='" + msg.error + "'"; if (!result) result = " unknown error"; } if (ok) Engine.debug(Engine.DebugInfo,info,"got answer" + result); else Engine.debug(Engine.DebugNote,info,"failed" + result); } function onHttpNotify(msg) { var p = pending[msg.targetid]; if (!p) return false; delete pending[msg.targetid]; handleHttpResult(msg,p); return true; } function parseParams(line) { var res = {}; while (line.length) { var m = line.match(/^(.* )?([^= ]+)=([^=]*)$/); if (!m) break; var s = "" + m[3]; res[m[2]] = s.trim(); line = "" + m[1]; } return res; } // Handle commands function onCommand(msg) { if (!msg.line) { switch (msg.partline) { case undefined: case "": case "help": oneCompletion(msg,"httptest",msg.partword); return false; case "httptest": oneCompletion(msg,"send",msg.partword); return false; case "httptest send": oneCompletion(msg,"sync",msg.partword); oneCompletion(msg,"async",msg.partword); oneCompletion(msg,"async_enq",msg.partword); oneCompletion(msg,"fire_and_forget",msg.partword); return false; } return false; } var s = msg.line; var m = s.match(/^httptest send (sync|async|async_enq|fire_and_forget)( .*)?$/); if (m) { var params = parseParams(m[2]); for (var p in http_params) if (undefined === params[p]) params[p] = http_params[p]; Engine.debug(Engine.DebugAll,"Sending",m[1],params.method + ":" + params.url); var ok = false; switch (m[1]) { case "sync": ok = httpRequest(params,true); break; case "async": ok = httpRequest(params,false,true); break; case "async_enq": ok = httpRequest(params,false,true,true); break; default: ok = httpRequest(params,false); } if (ok) msg.retValue("OK\r\n"); else msg.retValue("Failure\r\n"); return true; } return false; } help = " httptest send {sync|async|async_enq|fire_and_forget} [param=value ...]\r\n"; function onHelp(msg) { if (msg.line) { if ("httptest" == msg.line) { msg.retValue(help + "Control the HTTP Test operation\r\n"); return true; } } else msg.retValue(msg.retValue() + help); return false; } Engine.setDebug("level 10"); Message.install(onHttpNotify,"http.notify",100); Message.install(onCommand,"engine.command",150); Message.install(onHelp,"engine.help",150);