public function exec() {
		$this->ch = curl_init();
		curl_setopt_array($this->ch, [
			CURLOPT_URL => $this->url,
			CURLOPT_POSTFIELDS => $this->data,
			CURLOPT_CUSTOMREQUEST => $this->requestType,
			CURLOPT_HTTPHEADER => $this->createHeaders(),
			CURLOPT_TIMEOUT_MS => intval($this->timeout * 1000),
			CURLOPT_ENCODING => 'gzip',
			CURLOPT_FAILONERROR => false,
			CURLOPT_HEADER => true,
			CURLOPT_RETURNTRANSFER => true,
		]);
		curl_setopt_array($this->ch, $this->extraOpts);

		if ($this->username && $this->password) {
			curl_setopt($this->ch, CURLOPT_USERPWD, "{$this->username}:{$this->password}");
		}

		if ($this->data !== null)
			curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->data);

		$this->beforeExec();

		$resultData = curl_exec($this->ch);
		$httpCode = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);

		if (curl_errno($this->ch) !== CURLE_OK) {
			throw new Exception(curl_error($this->ch), curl_errno($this->ch));
		}

		$response = new Response($httpCode, $resultData, curl_getinfo($this->ch, CURLINFO_HEADER_SIZE));

		if  ($httpCode >= 400) {
			throw new Exception("HTTP transfer error {$httpCode}", null, $httpCode, $response);
		}

		curl_close($this->ch);

		return $response;
	}