HTTP Client

From Yate Documentation
Jump to: navigation, search

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
  • accept: Optional Accept 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.
  • http_: Add header(s) to request from parameters starting with this prefix. This parameter may be repeated to add multiple header.
    E.g. http_X-Hdr=custom will add the header X-Hdr: custom
  • header: Add header(s) to request. This parameter may be repeated to add multiple header.
    E.g. header=X-Hdr2: custom2 will add the header X-Hdr2: custom2
  • ret_header: Return header(s) from response. Comma separated list of header name(s).
    E.g. ret_header=Location will return the Location header if present in response as xhttp_location parameter
  • version_http: Force using the request HTTP version. Handled values: 1, 1.0, 1.1, 2, 2.0
  • version_http_required: Boolean true: start connection with requested version. Applicable for HTTP ver. 2 only

Body handling:

  • body_parse: Boolean. Parse received body and return it in message parameter(s) instead of return value
  • body: Request body to set (plain character string). Ignored if method is GET or HEAD
  • type: Optional Content-Type header contents
  • multipart_subtype: Non empty indicates a multipart body subtype. E.g. related will build a multipart/related body. Bodies will be handled as body, body.1 until first not found index.
    For subsequent bodies the parameters are given with body prefix. E.g. body.1.type
  • body.type: Alternative for type. Required for multipart bodies
  • body.encoding: hex indicates the body value is a hex string containing binary data
  • body.header_: Multipart bodies. Extra header(s) to be added to body. See the header_ description


The following parameters may be used to override the configured defaults: timeout, redirect, agent, max_hdr_length, max_body_length, server_keep_alive, ssl_verify_peer, ssl_verify_host, 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. Common parameters

  • code: Answer code
  • code_text: Answer text (e.g. OK)
  • xcontent_length: The contents of received Content-Length header

Success. Body parsed

  • xbody: The body contents
  • xbody.type: The body content type with no header parameters
  • xbody.encoding: hex if received body was detected to be a non text one or contains NULL char(s). The returned body is a string containing HEX chars with no spaces
  • xbody.typeparam_: Single Content-Type header parameter. E.g. xbody.typeparam_charset=iso-8859-1

Multipart:

  • xmultipart_subtype: Subtype of a received multipart body
  • xbody.header_: Received multipart body header(s). Header name is lower case. E.g. xbody.header_content-id=1

Subsequent bodies will be added to message for multipart starting with index 1: xbody.1, xbody.1.type ...

Success. Body not parsed

  • xtype: The contents of received Content-Type 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 ever
  • maxqueue: Maximum queue length (asynchronous requests waiting to start) ever
  • maxthread_concurrent: Maximum number of per processing thread concurrent requests ever
  • total_accepted: Total number of accepted (validated and started/queued) requests
  • queued: Current number of asynchronous requests waiting to be started
  • 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
  • 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 (timeout in queue) requests
  • rejected: The number of rejected (by module) 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

; queue_timeout: integer: Timeout, in milliseconds, of queued requests: how much time a request can wait
;  in input queue before beeing processed
; This parameter is applied on reload
; Allowed interval: 300..3000
;queue_timeout=2000

; 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 requests (sync or async) pending in 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

; max_concurrent_requests: integer: Maximum number of concurrent requests per async processor
; This parameter is applied on reload
; Allowed interval: 10..3000
;max_concurrent_requests=3000

; 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

; ssl_verify_peer: boolean: Verify peer certificate when SSL is used
; This parameter is applied on reload and can be overridden in handled message
; Empty value indicates nothing to be done: use library default
;ssl_verify_peer=

; ssl_verify_host: boolean: Verify peer host in certificate when SSL is used
; This parameter is applied on reload and can be overridden in handled message
; Empty value indicates nothing to be done: use library default
;ssl_verify_host=

; agent: string: User agent to set in request
; This parameter is applied on reload and can be overridden in handled message
;agent=YATE/${version}

; printmsg: boolean/keyword: Print sent/received message contents
; This parameter is applied on reload and can be overridden in handled message
; Boolean false: Don't print
; Boolean true: Print headers only
; 'verbose': Print headers and body
; 'firstline': Print request/response first line only
; 'headers': Print headers only (same as boolean true)
;printmsg=no

; printmsg_localaddr: boolean: Print locall address when printing a message
; This parameter is applied on reload and can be overridden in handled message
;printmsg_localaddr=no

; threads: integer: The number of threads used for async processing
; Allowed interval: 1..50
;threads=1

; 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