requestBuilder->buildOptions($request); // Handle authentication if ($request->options->auth !== null) { $authResult = $this->authenticationHandler->configure($request->options->auth, $request->headers); if ($authResult->headers !== $request->headers) { $updatedRequest = $request->with(['headers' => $authResult->headers]); $options = $this->requestBuilder->buildOptions($updatedRequest); } if (! empty($authResult->curlOptions)) { $options = array_replace($options, $authResult->curlOptions); } } // Set all options using fluent API $handle->setOptions($options); // Execute request (automatically uses CURLOPT_RETURNTRANSFER) $rawResponse = $handle->fetch(); // Parse response using underlying CurlHandle resource return $this->responseParser->parse($rawResponse, $handle->getResource()); } catch (\Throwable $e) { // Wrap any exception in CurlExecutionFailed for backward compatibility throw new CurlExecutionFailed($e->getMessage(), $e->getCode(), $e); } } /** * Send request with streaming response to destination * * Streams HTTP response directly to a writable stream resource. * Useful for large file downloads without loading entire response into memory. * * @param ClientRequest $request HTTP request to send * @param resource $destination Writable stream resource (e.g., fopen('file.txt', 'w')) * @return StreamingResponse Response with headers and status, but no body (streamed to destination) * @throws CurlExecutionFailed If request execution fails */ public function sendStreaming(ClientRequest $request, $destination): StreamingResponse { if (! is_resource($destination)) { throw new \InvalidArgumentException('Destination must be a valid stream resource'); } $handle = new Handle(); try { // Build options using HandleOption enum $options = $this->requestBuilder->buildOptions($request); // Remove CURLOPT_RETURNTRANSFER (we're streaming to destination) unset($options[\App\Framework\HttpClient\Curl\HandleOption::ReturnTransfer->value]); // Handle authentication if ($request->options->auth !== null) { $authResult = $this->authenticationHandler->configure($request->options->auth, $request->headers); if ($authResult->headers !== $request->headers) { $updatedRequest = $request->with(['headers' => $authResult->headers]); $options = $this->requestBuilder->buildOptions($updatedRequest); unset($options[\App\Framework\HttpClient\Curl\HandleOption::ReturnTransfer->value]); } if (! empty($authResult->curlOptions)) { $options = array_replace($options, $authResult->curlOptions); } } // Enable header capture for streaming $headerBuffer = ''; $headerFunction = function ($ch, $header) use (&$headerBuffer) { $headerBuffer .= $header; return strlen($header); }; $options[\App\Framework\HttpClient\Curl\HandleOption::HeaderFunction->value] = $headerFunction; // Set all options $handle->setOptions($options); // Execute and stream directly to destination $handle->execute($destination); // Parse response headers from buffer $statusCode = $handle->getInfo(\App\Framework\HttpClient\Curl\Info::ResponseCode); $status = \App\Framework\Http\Status::from((int) $statusCode); $headers = $this->responseParser->parseHeaders($headerBuffer); // Get bytes written (if available) $bytesWritten = (int) ($handle->getInfo(\App\Framework\HttpClient\Curl\Info::SizeDownload) ?: 0); return new StreamingResponse( status: $status, headers: $headers, bytesWritten: $bytesWritten ); } catch (\Throwable $e) { throw new CurlExecutionFailed($e->getMessage(), $e->getCode(), $e); } } /** * Send request with streaming body from source * * Streams HTTP request body from a readable stream resource. * Useful for large file uploads without loading entire file into memory. * * @param ClientRequest $request HTTP request to send (body will be replaced by stream) * @param resource $source Readable stream resource (e.g., fopen('file.txt', 'r')) * @param int|null $contentLength Content-Length in bytes (null for chunked transfer) * @return ClientResponse HTTP response * @throws CurlExecutionFailed If request execution fails */ public function sendStreamingUpload(ClientRequest $request, $source, ?int $contentLength = null): ClientResponse { if (! is_resource($source)) { throw new \InvalidArgumentException('Source must be a valid stream resource'); } $handle = new Handle(); try { // Build options using HandleOption enum $options = $this->requestBuilder->buildOptions($request); // Remove CURLOPT_POSTFIELDS (we're using stream) unset($options[\App\Framework\HttpClient\Curl\HandleOption::PostFields->value]); // Set streaming upload options $options[\App\Framework\HttpClient\Curl\HandleOption::Upload->value] = true; $options[\App\Framework\HttpClient\Curl\HandleOption::InFile->value] = $source; if ($contentLength !== null) { $options[\App\Framework\HttpClient\Curl\HandleOption::InFileSize->value] = $contentLength; } // Handle authentication if ($request->options->auth !== null) { $authResult = $this->authenticationHandler->configure($request->options->auth, $request->headers); if ($authResult->headers !== $request->headers) { $updatedRequest = $request->with(['headers' => $authResult->headers]); $options = array_replace($options, $this->requestBuilder->buildOptions($updatedRequest)); unset($options[\App\Framework\HttpClient\Curl\HandleOption::PostFields->value]); $options[\App\Framework\HttpClient\Curl\HandleOption::Upload->value] = true; $options[\App\Framework\HttpClient\Curl\HandleOption::InFile->value] = $source; if ($contentLength !== null) { $options[\App\Framework\HttpClient\Curl\HandleOption::InFileSize->value] = $contentLength; } } if (! empty($authResult->curlOptions)) { $options = array_replace($options, $authResult->curlOptions); } } // Set all options $handle->setOptions($options); // Execute request (automatically uses CURLOPT_RETURNTRANSFER) $rawResponse = $handle->fetch(); // Parse response return $this->responseParser->parse($rawResponse, $handle->getResource()); } catch (\Throwable $e) { throw new CurlExecutionFailed($e->getMessage(), $e->getCode(), $e); } } /* // URL mit Query-Parametern verarbeiten $url = $request->url; if (!empty($request->options->query)) { $separator = str_contains($url, '?') ? '&' : '?'; $url .= $separator . http_build_query($request->options->query); } $options = [ CURLOPT_URL => $url, CURLOPT_CUSTOMREQUEST => $request->method->value, CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_TIMEOUT => $request->options->timeout, CURLOPT_CONNECTTIMEOUT => $request->options->connectTimeout, CURLOPT_FOLLOWLOCATION => $request->options->followRedirects, CURLOPT_MAXREDIRS => $request->options->maxRedirects, CURLOPT_SSL_VERIFYPEER => $request->options->verifySsl, CURLOPT_SSL_VERIFYHOST => $request->options->verifySsl ? 2 : 0, ]; // User-Agent setzen wenn vorhanden if ($request->options->userAgent !== null) { $options[CURLOPT_USERAGENT] = $request->options->userAgent; } // Proxy setzen wenn vorhanden if ($request->options->proxy !== null) { $options[CURLOPT_PROXY] = $request->options->proxy; } // Authentifizierung einrichten if ($request->options->auth !== null) { $this->setupAuthentication($ch, $request->options->auth, $request->headers); } // Request-Body verarbeiten if ($request->body !== '') { $options[CURLOPT_POSTFIELDS] = $request->body; } // Headers formatieren und setzen if (count($request->headers->all()) > 0) { $options[CURLOPT_HTTPHEADER] = HeaderManipulator::formatForCurl($request->headers); } curl_setopt_array($ch, $options); $raw = curl_exec($ch); if ($raw === false) { throw new CurlExecutionFailed(curl_error($ch), curl_errno($ch)); } $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $headersRaw = substr($raw, 0, $headerSize); $body = substr($raw, $headerSize); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $headers = HeaderManipulator::fromString($headersRaw); return new ClientResponse( status: Status::from($status), headers: $headers, body: $body ); } */ }