Curl::setup_handle( string $url, array $headers, string|array $data, array $options )

In this article

This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only in other core functions. It is listed here for completeness.

Setup the cURL handle for the given data


URL to request
Associative array of request headers
Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
Request options, see WpOrgRequestsRequests::response() for documentation


	$options['hooks']->dispatch('curl.before_request', [&$this->handle]);

	// Force closing the connection for old versions of cURL (<7.22).
	if (!isset($headers['Connection'])) {
		$headers['Connection'] = 'close';

	 * Add "Expect" header.
	 * By default, cURL adds a "Expect: 100-Continue" to most requests. This header can
	 * add as much as a second to the time it takes for cURL to perform a request. To
	 * prevent this, we need to set an empty "Expect" header. To match the behaviour of
	 * Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use
	 * HTTP/1.1.
	if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) {
		$headers['Expect'] = $this->get_expect_header($data);

	$headers = Requests::flatten($headers);

	if (!empty($data)) {
		$data_format = $options['data_format'];

		if ($data_format === 'query') {
			$url  = self::format_get($url, $data);
			$data = '';
		} elseif (!is_string($data)) {
			$data = http_build_query($data, '', '&');

	switch ($options['type']) {
		case Requests::POST:
			curl_setopt($this->handle, CURLOPT_POST, true);
			curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
		case Requests::HEAD:
			curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
			curl_setopt($this->handle, CURLOPT_NOBODY, true);
		case Requests::TRACE:
			curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
		case Requests::PATCH:
		case Requests::PUT:
		case Requests::DELETE:
		case Requests::OPTIONS:
			curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
			if (!empty($data)) {
				curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);

	// cURL requires a minimum timeout of 1 second when using the system
	// DNS resolver, as it uses `alarm()`, which is second resolution only.
	// There's no way to detect which DNS resolver is being used from our
	// end, so we need to round up regardless of the supplied timeout.
	$timeout = max($options['timeout'], 1);

	if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
		curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
	} else {
		// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound
		curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));

	if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
		curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
	} else {
		// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound
		curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));

	curl_setopt($this->handle, CURLOPT_URL, $url);
	curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
	if (!empty($headers)) {
		curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);

	if ($options['protocol_version'] === 1.1) {
		curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
	} else {
		curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);

	if ($options['blocking'] === true) {
		curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']);
		curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, [$this, 'stream_body']);
		curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);

User Contributed Notes

You must log in before being able to contribute a note or feedback.