/** * Send the response to the client. * @param RequestInterface|null $req optional request object that triggered this response * @return self * @codeCoverageIgnore */ public function send(RequestInterface $req = null) { $seekBeg = 0; $seekEnd = -1; // modify response according to request if ($req) { // process cached response (not modified) if ($req->hasHeader('If-Modified-Since') && $this->hasHeader('Last-Modified')) { $cached = strtotime($req->getHeader('If-Modified-Since')); $current = strtotime($this->getHeader('Last-Modified')); if ($cached === $current) { $this->setStatusCode(304); } } // process cached response (ETag) if ($req->hasHeader('If-None-Match') && $this->hasHeader('ETag')) { if ($req->getHeader('If-None-Match') === $this->getHeader('ETag')) { $this->setStatusCode(304); } } // process chunks if ($req->hasHeader('Range') && $this->hasHeader('Content-Length')) { $size = (int) $this->getHeader('Content-Length'); $range = $req->getHeader('Range'); $this->setHeader('Accept-Ranges', 'bytes'); try { if (!preg_match('@^bytes=\\d*-\\d*(,\\d*-\\d*)*$@', $range)) { throw new \Exception('Invalid range'); } $range = current(explode(',', substr($range, 6))); list($seekBeg, $seekEnd) = explode('-', $range, 2); $seekBeg = max((int) $seekBeg, 0); $seekEnd = !(int) $seekEnd ? $size - 1 : min((int) $seekEnd, $size - 1); if ($seekBeg > $seekEnd) { throw new \Exception('Invalid range'); } $this->setStatusCode(206); $this->setHeader('Content-Range', 'bytes ' . $seekBeg . '-' . $seekEnd . '/' . $size); $seekEnd = $seekEnd - $seekBeg; } catch (\Exception $e) { $this->setStatusCode(416); $this->setHeader('Content-Range', 'bytes * /' . $size); $this->body = null; } } } if (!headers_sent()) { if ($this->getHeader('Location') && $this->code !== 201 && $this->code !== 202 && floor($this->code / 100) !== 3) { $this->setStatusCode(302); } http_response_code($this->code); foreach ($this->getHeaders() as $k => $v) { header($k . ': ' . $v); } foreach ($this->cookies as $k => $v) { header('Set-Cookie: ' . $k . '=' . urlencode($v[0]) . '; ' . $v[1], false); } } if ($this->body && !in_array($this->getStatusCode(), [204, 304, 416]) && (!$req || $req->getMethod() !== 'HEAD')) { $out = fopen('php://output', 'w'); stream_copy_to_stream($this->body, $out, $seekEnd, $seekBeg); fclose($out); } return $this; }
/** * Builds canonical string from request. * * @param RequestInterface $request Request to get canonical string from. * * @return string */ public function getCanonicalString($request) { $parts = array($request->getHeader('content-type'), $request->getHeader('content-md5'), $request->getPath() . $request->getQuery(), $request->getHeader('date')); return join(',', $parts); }