/**
  * @param Request $request
  * @param bool $postponedResponseId
  * @param null $postponedOutput
  * @throws RequestFailed
  * @return Response
  */
 public function sendRequest(Request $request, $postponedResponseId = null, $postponedOutput = null)
 {
     $clientData = $request->getClientData();
     if ($clientData) {
         $request->cookies[\PhpConsole\Connector::CLIENT_INFO_COOKIE] = base64_encode(json_encode($clientData));
     }
     if ($postponedResponseId) {
         $request->postData[\PhpConsole\Connector::POST_VAR_NAME] = array('getPostponedResponse' => $postponedResponseId);
     }
     $request->postData[static::POST_VAR_NAME] = array('scripts' => $request->getScripts());
     $postData = $request->postData;
     array_walk_recursive($postData, function (&$item) {
         if (!is_object($item)) {
             $item = base64_encode($item);
         }
     });
     $rawPostData = serialize($postData);
     $url = $request->isSsl ? str_replace('http://', 'https://', $this->serverWrapperUrl) : $this->serverWrapperUrl;
     $curlOptions = array(CURLOPT_URL => $url . '?signature=' . $this->getPostDataSignature($rawPostData), CURLOPT_CONNECTTIMEOUT => 100, CURLOPT_TIMEOUT => 100, CURLOPT_HEADER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rawPostData, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 3, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false);
     if ($request->cookies) {
         $cookiesData = array();
         foreach ($request->cookies as $name => $value) {
             $cookiesData[] = rawurlencode($name) . '=' . rawurlencode($value);
         }
         $curlOptions[CURLOPT_COOKIE] = implode('; ', $cookiesData);
     }
     $curlHandle = curl_init();
     curl_setopt_array($curlHandle, $curlOptions);
     $responseData = curl_exec($curlHandle);
     $code = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
     $error = curl_error($curlHandle);
     $responseHeaders = substr($responseData, 0, curl_getinfo($curlHandle, CURLINFO_HEADER_SIZE));
     if (substr($responseHeaders, -1) !== "\n") {
         // because curl_getinfo($curlHandle, CURLINFO_HEADER_SIZE) is bugged on some PHP versions
         $responseHeaders = substr($responseData, 0, strpos($responseData, PHP_EOL . PHP_EOL));
     }
     $responseOutput = substr($responseData, strlen($responseHeaders));
     curl_close($curlHandle);
     $response = new Response();
     $response->code = $code;
     $response->output = (string) ($postponedResponseId ? $postponedOutput : $responseOutput);
     $response->headerData = $this->parseHeaderData($responseHeaders);
     $response->cookies = $this->parseCookies($responseHeaders);
     try {
         if ($error || $code != 200 && $code != 204 && $code != 500) {
             throw new \Exception('Connection to "' . $this->serverWrapperUrl . '" failed with code "' . $code . '" and error: ' . $error . '" and response: "' . $responseOutput, $code);
         }
         $packageEncodedData = $postponedResponseId ? $responseOutput : $response->headerData;
         if ($packageEncodedData) {
             $packageData = $this->jsonDecode($packageEncodedData);
             if (!empty($packageData['isPostponed'])) {
                 $request->cookies = $response->cookies;
                 $response = $this->sendRequest($request, $packageData['id'], $responseOutput);
                 $response->isPostponed = true;
                 return $response;
             }
             $response->package = $this->initResponsePackage($packageData);
         }
     } catch (\Exception $exception) {
         throw new RequestFailed($exception, $request, $response);
     }
     return $response;
 }