HTTP Client

From Yate Documentation
(Difference between revisions)
Jump to: navigation, search
(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);
Personal tools
Namespaces

Variants
Actions
Preface
Configuration
Administrators
Developers