agentsclimarketplace

Bitrix http client

Skill bxmaximum/bitrix-framework-skills/skills/bitrix-http-client

Covers Bitrix\Main\Web\HttpClient HTTP client — legacy mode and PSR-18 (sendRequest), async Promise, proxies/timeouts, http_client_options, main.HttpClient logger, SSRF, redirects, and GeoIp\Manager lookups. Applied in external API integrations, webhooks, async calls and geolocation. Key terms — HttpClient, PSR-18, Promise, SSRF, GeoIp, Manager, webhook.From its SKILL.md

Install
npx -y skills add bxmaximum/bitrix-framework-skills --skill bitrix-http-client

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 19 stars19 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.1 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

HttpClient

Bitrix\Main\Web\HttpClient is a built-in client for external HTTP requests. It works in two modes: legacy (convenient get/post/download) and PSR-18 (full control, PSR-7/18 compatibility, asynchrony).

Global Configuration

Default values are in /local/.settings.php, http_client_options section:

'http_client_options' => [
    'value' => [
        'socketTimeout'  => 10,
        'streamTimeout'  => 30,
        'useCurl'        => true,
        'compress'       => true,
        'redirect'       => true,
        'redirectMax'    => 5,
        'bodyLengthMax'  => 10 * 1024 * 1024,
        'disableSslVerification' => false,
        // Default privateIp is TRUE (private IPs allowed). Set false for SSRF protection:
        'privateIp'      => false,
    ],
    'readonly' => false,
],

Check: \Bitrix\Main\Config\Configuration::getValue('http_client_options').

The same keys are accepted by new HttpClient([...]) constructor — constructor overrides global ones.

Basic Options

  • socketTimeout — connection timeout (sec), default 30.
  • streamTimeout — data reading timeout (sec).
  • compress — accept gzip.
  • redirect, redirectMax — follow redirects (legacy only).
  • useCurl — use cURL instead of sockets (faster for asynchrony and https).
  • disableSslVerification — disable SSL verification (use only for debugging).
  • privateIp — default true = private IPs allowed. Set to false to block private/link-local addresses (SSRF protection for user-provided URLs).
  • bodyLengthMax — response body size limit.
  • waitResponse — false if only headers need to be parsed and connection closed.
  • proxyHost, proxyPort, proxyUser, proxyPassword — proxy settings.
  • debugLevel — HttpDebug::NONE|REQUEST_HEADERS|RESPONSE_HEADERS|ALL.
  • headers, cookies — default dictionaries (legacy only).

Legacy Mode

GET

use Bitrix\Main\Web\HttpClient;

$http = new HttpClient([
    'compress' => true,
    'headers'  => ['User-Agent' => 'VendorBot/1.0'],
    'socketTimeout' => 5,
    'streamTimeout' => 15,
]);

$body = $http->get('https://api.example.com/items');

if ($body === false)
{
    throw new \RuntimeException('HTTP error: ' . $http->getError()[0] ?? 'unknown');
}

$status  = $http->getStatus();        // int
$headers = $http->getHeaders();       // HttpHeaders
$data    = \Bitrix\Main\Web\Json::decode($body);

POST Form

$http->post('https://api.example.com/form', ['login' => 'admin', 'pass' => '***']);

POST JSON

$http->setHeader('Content-Type', 'application/json');
$http->setHeader('Authorization', 'Bearer ' . $token);
$response = $http->post('https://api.example.com/users', \Bitrix\Main\Web\Json::encode(['name' => 'Ivan']));

Downloading File

$http->download(
    'https://files.example.com/report.csv',
    $_SERVER['DOCUMENT_ROOT'] . '/upload/tmp/report.csv',
);

Session via Cookie

$http->query('GET', $loginUrl);
$cookies = $http->getCookies()->toArray();
$http->setCookies($cookies);
$http->post($apiUrl, $payload);

Conditional Body Fetch (from 23.300.0)

To avoid downloading megabytes for "reconnaissance":

$http->shouldFetchBody(
    fn (\Bitrix\Main\Web\Http\Response $r) =>
        str_starts_with($r->getHeadersCollection()->getContentType() ?? '', 'application/json')
);

PSR-18 Mode

Build a Request and call sendRequest:

use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Uri;
use Bitrix\Main\Web\Http\{Request, Method, Stream, ClientException, NetworkException, RequestException};

$http = new HttpClient(['compress' => true, 'useCurl' => true]);

$body = new Stream('php://temp', 'r+');
$body->write(\Bitrix\Main\Web\Json::encode(['id' => 42]));
$body->rewind();

$request = (new Request(
    Method::POST,
    new Uri('https://api.example.com/items'),
    ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $token],
    $body,
));

try
{
    $response = $http->sendRequest($request);

    $status = $response->getStatusCode();
    $payload = \Bitrix\Main\Web\Json::decode((string)$response->getBody());
}
catch (NetworkException $e) { /* connection failed */ }
catch (RequestException $e) { /* incorrect request */ }
catch (ClientException $e) { /* general client error */ }

PSR-7 objects are immutable — withHeader, withUri, withMethod return a copy.

File Upload (multipart)

use Bitrix\Main\Web\Http\MultipartStream;

$fh = fopen('/tmp/report.pdf', 'r');
$body = new MultipartStream([
    'title' => 'Monthly report',
    'file'  => ['resource' => $fh, 'filename' => 'report.pdf'],
]);

$request = new Request(
    Method::POST,
    new Uri('https://api.example.com/upload'),
    ['Content-Type' => 'multipart/form-data; boundary=' . $body->getBoundary()],
    $body,
);

$response = $http->sendRequest($request);
fclose($fh);

Manual Redirects

In PSR-18, redirects are not followed automatically:

do {
    $response = $http->sendRequest($request);
    if ($response->hasHeader('Location'))
    {
        $request = $request->withUri(new Uri($response->getHeader('Location')[0]));
    }
} while ($response->hasHeader('Location'));

Asynchronous Requests

$promises = [];
foreach ($urls as $url)
{
    $promises[$url] = $http->sendAsyncRequest(new Request(Method::GET, new Uri($url)));
}

foreach ($promises as $url => $promise)
{
    try {
        $response = $promise->wait();
        // ...
    } catch (\Throwable $e) { /* ... */ }
}

Wait for all:

use Bitrix\Main\Web\Http\Promise;

Promise::all($promises)->then(
    fn (array $responses) => /* ... */,
    fn (array $errors) => /* ... */
)->wait();

Logging

HttpClient uses the PSR-3 logger main.HttpClient. Configure in .settings.php:

'loggers' => [
    'value' => [
        'main.HttpClient' => [
            'constructor' => static function (
                \Bitrix\Main\Web\Http\DebugInterface $debug,
                \Psr\Http\Message\RequestInterface $request,
            ) {
                $debug->setDebugLevel(\Bitrix\Main\Web\HttpDebug::ALL);
                return new \Bitrix\Main\Diag\FileLogger(
                    '/var/log/bitrix/http-' . spl_object_hash($request) . '.log',
                );
            },
            'level' => \Psr\Log\LogLevel::DEBUG,
        ],
    ],
],

SSRF Protection

HttpClient property $privateIp defaults to true (private IPs are allowed).

  • For SSRF protection on user-controlled URLs, set 'privateIp' => false — blocks 127.0.0.1, 192.168.*, 10.*, 169.254.* (AWS/GCP metadata), etc.
  • Only keep the default (true) when the client must call trusted internal services.

GeoIP

Canonical entry: Bitrix\Main\Service\GeoIp\Manager — do not call built-in handler classes directly.

NeedAPI
One attribute; empty string on miss OKgetCountryCode(), getCityName(), getTimezoneName(), …
Lat/lon pair; null on missgetGeoPosition()
Several fields + isSuccess() / handler metadatagetDataResult($ip, $lang, $required)
Client IP with X-Forwarded-For awarenessgetRealIp()
use Bitrix\Main\Service\GeoIp\Manager;

$code = Manager::getCountryCode(); // current request IP
$result = Manager::getDataResult($storedIp, 'en', ['cityName', 'latitude']);
if ($result && $result->isSuccess())
{
    $data = $result->getGeoData();
}

Rules:

  • Pass explicit $ip for stored/proxied addresses; empty $ip only as shorthand for current request (getRealIp()).
  • Use $required in getDataResult when you depend on specific fields so unsuitable handlers are skipped.
  • Miss semantics differ: getDataResult → null; convenience getters → ''; getGeoPosition → null.
  • In-request cache covers IPv4/IPv6; ManagedCache path in Manager is IPv4-oriented — do not assume identical IPv6 persistence.
  • Invalidate via Manager::cleanCache() (or handler cascade), not ad hoc deletes under geoip_manager.
  • Custom provider: event onMainGeoIpHandlersBuildList + subclass of GeoIp\Base. Post-process only via onGeoIpGetResult.

Logger id for this subsystem: main.GeoIpManager (see bitrix-logger).

Checklist

  • useCurl is enabled (requires php-curl).
  • Timeouts (socketTimeout, streamTimeout) are set and reasonable.
  • Response status is checked for 2xx before decoding.
  • SSL verification is not disabled in production.
  • For user-provided URLs, privateIp => false (default is true = private IPs allowed).
  • Binary data/files are downloaded via download() or streams, not read entirely into memory.
  • GeoIP goes through GeoIp\Manager with explicit IP when not the current request.

See skill bitrix-security for SSRF protection details.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.