/** * Analyzes this response, considering the given request and makes additions * or removes certain headers in order to make the response compliant to * RFC 2616 and related standards. * * It is recommended to call this method before the response is sent and Flow * does so by default in its built-in HTTP request handler. * * @param Request $request The corresponding request * @return void * @api */ public function makeStandardsCompliant(Request $request) { if ($request->hasHeader('If-Modified-Since') && $this->headers->has('Last-Modified') && $this->statusCode === 200) { $ifModifiedSinceDate = $request->getHeader('If-Modified-Since'); $lastModifiedDate = $this->headers->get('Last-Modified'); if ($lastModifiedDate <= $ifModifiedSinceDate) { $this->setStatus(304); $this->content = ''; } } elseif ($request->hasHeader('If-Unmodified-Since') && $this->headers->has('Last-Modified') && ($this->statusCode >= 200 && $this->statusCode <= 299 || $this->statusCode === 412)) { $unmodifiedSinceDate = $request->getHeader('If-Unmodified-Since'); $lastModifiedDate = $this->headers->get('Last-Modified'); if ($lastModifiedDate > $unmodifiedSinceDate) { $this->setStatus(412); } } if (in_array($this->statusCode, [100, 101, 204, 304])) { $this->content = ''; } if ($this->headers->getCacheControlDirective('no-cache') !== null || $this->headers->has('Expires')) { $this->headers->removeCacheControlDirective('max-age'); } if ($request->getMethod() === 'HEAD') { if (!$this->headers->has('Content-Length')) { $this->headers->set('Content-Length', strlen($this->content)); } $this->content = ''; } if (!$this->headers->has('Content-Length')) { $this->headers->set('Content-Length', strlen($this->content)); } if ($this->headers->has('Transfer-Encoding')) { $this->headers->remove('Content-Length'); } }
/** * Generates the Matching cache identifier for the given Request * * @param Request $httpRequest * @return string */ protected function buildRouteCacheIdentifier(Request $httpRequest) { return md5(sprintf('%s_%s_%s', $httpRequest->getUri()->getHost(), $httpRequest->getRelativePath(), $httpRequest->getMethod())); }
/** * Checks whether $routePath corresponds to this Route. * If all Route Parts match successfully TRUE is returned and * $this->matchResults contains an array combining Route default values and * calculated matchResults from the individual Route Parts. * * @param Request $httpRequest the HTTP request to match * @return boolean TRUE if this Route corresponds to the given $routePath, otherwise FALSE * @throws InvalidRoutePartValueException * @see getMatchResults() */ public function matches(Request $httpRequest) { $routePath = $httpRequest->getRelativePath(); $this->matchResults = null; if ($this->uriPattern === null) { return false; } if (!$this->isParsed) { $this->parse(); } if ($this->hasHttpMethodConstraints() && !in_array($httpRequest->getMethod(), $this->httpMethods)) { return false; } $matchResults = []; $routePath = trim($routePath, '/'); $skipOptionalParts = false; $optionalPartCount = 0; /** @var $routePart RoutePartInterface */ foreach ($this->routeParts as $routePart) { if ($routePart->isOptional()) { $optionalPartCount++; if ($skipOptionalParts) { if ($routePart->getDefaultValue() === null) { return false; } continue; } } else { $optionalPartCount = 0; $skipOptionalParts = false; } if ($routePart->match($routePath) !== true) { if ($routePart->isOptional() && $optionalPartCount === 1) { if ($routePart->getDefaultValue() === null) { return false; } $skipOptionalParts = true; } else { return false; } } $routePartValue = $routePart->getValue(); if ($routePartValue !== null) { if ($this->containsObject($routePartValue)) { throw new InvalidRoutePartValueException('RoutePart::getValue() must only return simple types after calling RoutePart::match(). RoutePart "' . get_class($routePart) . '" returned one or more objects in Route "' . $this->getName() . '".'); } $matchResults = Arrays::setValueByPath($matchResults, $routePart->getName(), $routePartValue); } } if (strlen($routePath) > 0) { return false; } $this->matchResults = Arrays::arrayMergeRecursiveOverrule($this->defaults, $matchResults); return true; }
/** * Sends the given HTTP request * * @param Http\Request $request * @return Http\Response The response or FALSE * @api * @throws Http\Exception * @throws CurlEngineException */ public function sendRequest(Http\Request $request) { if (!extension_loaded('curl')) { throw new Http\Exception('CurlEngine requires the PHP CURL extension to be installed and loaded.', 1346319808); } $requestUri = $request->getUri(); $curlHandle = curl_init((string) $requestUri); curl_setopt_array($curlHandle, $this->options); // Send an empty Expect header in order to avoid chunked data transfer (which we can't handle yet). // If we don't set this, cURL will set "Expect: 100-continue" for requests larger than 1024 bytes. curl_setopt($curlHandle, CURLOPT_HTTPHEADER, ['Expect:']); // If the content is a stream resource, use cURL's INFILE feature to stream it $content = $request->getContent(); if (is_resource($content)) { curl_setopt_array($curlHandle, [CURLOPT_INFILE => $content, CURLOPT_INFILESIZE => $request->getHeader('Content-Length')]); } switch ($request->getMethod()) { case 'GET': if ($request->getContent()) { // workaround because else the request would implicitly fall into POST: curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'GET'); if (!is_resource($content)) { curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $content); } } break; case 'POST': curl_setopt($curlHandle, CURLOPT_POST, true); if (!is_resource($content)) { $body = $content !== '' ? $content : http_build_query($request->getArguments()); curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $body); } break; case 'PUT': curl_setopt($curlHandle, CURLOPT_PUT, true); if (!is_resource($content) && $content !== '') { $inFileHandler = fopen('php://temp', 'r+'); fwrite($inFileHandler, $request->getContent()); rewind($inFileHandler); curl_setopt_array($curlHandle, [CURLOPT_INFILE => $inFileHandler, CURLOPT_INFILESIZE => strlen($request->getContent())]); } break; default: if (!is_resource($content)) { $body = $content !== '' ? $content : http_build_query($request->getArguments()); curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $body); } curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, $request->getMethod()); } $preparedHeaders = []; foreach ($request->getHeaders()->getAll() as $fieldName => $values) { foreach ($values as $value) { $preparedHeaders[] = $fieldName . ': ' . $value; } } curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $preparedHeaders); // curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP && CURLPROTO_HTTPS); // CURLOPT_UPLOAD if ($requestUri->getPort() !== null) { curl_setopt($curlHandle, CURLOPT_PORT, $requestUri->getPort()); } // CURLOPT_COOKIE $curlResult = curl_exec($curlHandle); if ($curlResult === false) { throw new CurlEngineException(sprintf('cURL reported error code %s with message "%s". Last requested URL was "%s" (%s).', curl_errno($curlHandle), curl_error($curlHandle), curl_getinfo($curlHandle, CURLINFO_EFFECTIVE_URL), $request->getMethod()), 1338906040); } elseif (strlen($curlResult) === 0) { return false; } curl_close($curlHandle); $response = Http\Response::createFromRaw($curlResult); if ($response->getStatusCode() === 100) { $response = Http\Response::createFromRaw($response->getContent(), $response); } return $response; }