public function exec() {
    if (!$this->prepareRequest()) {
      return false;
    }
    $this->response = new \google\appengine\URLFetchResponse();

    try {
      ApiProxy::makeSyncCall('urlfetch',
                             'Fetch',
                             $this->request,
                             $this->response);
    } catch (ApplicationError $e) {
      $error_number = $e->getApplicationError();
      $curl_error_number = static::$urlfetch_curl_error_map[$error_number];
      $error_message = static::$curle_error_code_str_map[$curl_error_number];

      static::log(LOG_ERR,
                  sprintf('Call to URLFetch failed with application error %d ' .
                          '(%s) for url %s.',
                          $error_number,
                          $error_message,
                          $this->request->getUrl()));
      $this->setCurlErrorFromUrlFetchError($e->getApplicationError(),
                                           $e->getMessage());
      return false;
    }

    $response = $this->prepareResponse();

    // Must be after prepareResponse() so data is available for info.
    $this->info = self::$default_getinfo_values;
    $this->prepareCurlInfo();

    if ($this->tryGetOption(CURLOPT_RETURNTRANSFER, $value) && $value) {
      return $response;
    } else if ($this->tryGetOption(CURLOPT_FILE, $value) && $value) {
      $length = fwrite($value, $response);
      return ($length === strlen($response));
    } else if ($this->tryGetOption(CURLOPT_WRITEFUNCTION, $cb) && $cb) {
      $response_len = strlen($response);
      do {
        // TODO - what if cb returns 0 or -ve?
        $response_len -= $cb($this, $response);
      } while ($response_len > 0);
    } else {
      echo $response;
    }
    return true;
  }