Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add overrides for curl parameters #2

Merged
merged 10 commits into from
Mar 18, 2024
159 changes: 113 additions & 46 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,71 @@
public const CONTENT_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data';
public const CONTENT_TYPE_GRAPHQL = 'application/graphql';

private array $headers = [];

Check failure on line 26 in src/Client.php

View workflow job for this annotation

GitHub Actions / CodeQL

Property Utopia\Fetch\Client::$headers type has no value type specified in iterable type array.
private int $timeout = 15;
private int $connectTimeout = 60;
private int $maxRedirects = 5;
private bool $allowRedirects = true;

/**
* @param string $key
* @param string $value
* @return self
*/
public function addHeader(string $key, string $value): self
{
$this->headers[$key] = $value;
return $this;
}

/**
* Set the request timeout.
*
* @param int $timeout
* @return self
*/
public function setTimeout(int $timeout): self
{
$this->timeout = $timeout;
return $this;
}

/**
* Set whether to allow redirects.
*
* @param int $allow
* @return self
*/
public function setAllowRedirects(bool $allow): self

Check failure on line 61 in src/Client.php

View workflow job for this annotation

GitHub Actions / CodeQL

PHPDoc tag @param for parameter $allow with type int is incompatible with native type bool.
{
$this->allowRedirects = $allow;
return $this;
}

/**
* Set the maximum number of redirects.
*
* @param int $maxRedirects
* @return self
*/
public function setMaxRedirects(int $maxRedirects): self
{
$this->maxRedirects = $maxRedirects;
return $this;
}

/**
* Set the connection timeout.
*
* @param int $connectTimeout
* @return self
*/
public function setConnectTimeout(int $connectTimeout): self
{
$this->connectTimeout = $connectTimeout;
return $this;
}

/**
* Flatten request body array to PHP multiple format
*
Expand All @@ -45,86 +110,88 @@

return $output;
}

/**
* This method is used to make a request to the server
* This method is used to make a request to the server.
*
* @param string $url
* @param array<string, string> $headers
* @param string $method
* @param array<string>|array<string, mixed> $body
* @param array<string, mixed> $query
* @param int $timeout
* @return Response
*/
public static function fetch(
public function fetch(
string $url,
array $headers = [],
string $method = self::METHOD_GET,
array $body = [],
array $query = [],
int $timeout = 15
): Response {
// Process the data before making the request
if (!in_array($method, [self::METHOD_PATCH, self::METHOD_GET, self::METHOD_CONNECT, self::METHOD_DELETE, self::METHOD_POST, self::METHOD_HEAD, self::METHOD_OPTIONS, self::METHOD_PUT, self::METHOD_TRACE ])) { // If the method is not supported
if (!in_array($method, [self::METHOD_PATCH, self::METHOD_GET, self::METHOD_CONNECT, self::METHOD_DELETE, self::METHOD_POST, self::METHOD_HEAD, self::METHOD_OPTIONS, self::METHOD_PUT, self::METHOD_TRACE])) {
throw new FetchException("Unsupported HTTP method");
}
if(isset($headers['content-type'])) {
match ($headers['content-type']) { // Convert the body to the appropriate format
self::CONTENT_TYPE_APPLICATION_JSON => $body = json_encode($body),
self::CONTENT_TYPE_APPLICATION_FORM_URLENCODED, self::CONTENT_TYPE_MULTIPART_FORM_DATA => $body = self::flatten($body),
self::CONTENT_TYPE_GRAPHQL => $body = $body[0],
default => $body = $body,

if (isset($this->headers['content-type'])) {
$body = match ($this->headers['content-type']) {
self::CONTENT_TYPE_APPLICATION_JSON => json_encode($body),
self::CONTENT_TYPE_APPLICATION_FORM_URLENCODED, self::CONTENT_TYPE_MULTIPART_FORM_DATA => self::flatten($body),
self::CONTENT_TYPE_GRAPHQL => $body[0],
default => $body,
};
}
$headers = array_map(function ($i, $header) { // convert headers to appropriate format
return $i . ':' . $header;
}, array_keys($headers), $headers);
if($query) { // if the request has a query string, append it to the request URI
$url = rtrim($url, '?');
$url .= '?' . http_build_query($query);

$formattedHeaders = array_map(function ($key, $value) {
return $key . ':' . $value;
}, array_keys($this->headers), $this->headers);

if ($query) {
$url = rtrim($url, '?') . '?' . http_build_query($query);
}
$responseHeaders = [];
// Initialize the curl session

$ch = curl_init();
// Set the request URI
curl_setopt($ch, CURLOPT_URL, $url);
// Set the request headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Set the request method
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
// Set the request body
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
// Save the response headers
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);

if (count($header) < 2) { // ignore invalid headers
$curlOptions = [
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => $formattedHeaders,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);
if (count($header) < 2) { // ignore invalid headers
return $len;
}
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
}
},
CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_MAXREDIRS => $this->maxRedirects,
CURLOPT_FOLLOWLOCATION => $this->allowRedirects,
CURLOPT_RETURNTRANSFER => true,
];

$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
});
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$responseBody = curl_exec($ch); // Execute the curl session
$responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Merge user-defined CURL options with defaults
foreach ($curlOptions as $option => $value) {
curl_setopt($ch, $option, $value);
}

$responseBody = curl_exec($ch);
$responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$errorMsg = curl_error($ch);
}

curl_close($ch);

if (isset($errorMsg)) {
throw new FetchException($errorMsg);
}

$response = new Response(
statusCode: $responseStatusCode,
headers: $responseHeaders,

Check failure on line 191 in src/Client.php

View workflow job for this annotation

GitHub Actions / CodeQL

Parameter $headers of class Utopia\Fetch\Response constructor expects array<string, string>, array<string, string>|null given.
body: $responseBody
);

return $response;
}
}
20 changes: 10 additions & 10 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
): void {
$resp = null;
try {
$resp = Client::fetch(

Check failure on line 30 in tests/ClientTest.php

View workflow job for this annotation

GitHub Actions / CodeQL

Static call to instance method Utopia\Fetch\Client::fetch().
url: $url,
method: $method,
headers: $headers,

Check failure on line 33 in tests/ClientTest.php

View workflow job for this annotation

GitHub Actions / CodeQL

Unknown parameter $headers in call to method Utopia\Fetch\Client::fetch().
body: $body,
query: $query
);
Expand All @@ -38,14 +38,14 @@
echo $e;
return;
}
if ($resp->getStatusCode()===200) { // If the response is OK
if ($resp->getStatusCode() === 200) { // If the response is OK
$respData = $resp->json(); // Convert body to array
$this->assertEquals($respData['method'], $method); // Assert that the method is equal to the response's method
if($method != Client::METHOD_GET) {
if(empty($body)) { // if body is empty then response body should be an empty string
$this->assertEquals($respData['body'], '');
} else {
if($headers['content-type']!="application/x-www-form-urlencoded") {
if($headers['content-type'] != "application/x-www-form-urlencoded") {
$this->assertEquals( // Assert that the body is equal to the response's body
$respData['body'],
json_encode($body) // Converting the body to JSON string
Expand Down Expand Up @@ -92,10 +92,10 @@
): void {
$resp = null;
try {
$resp = Client::fetch(

Check failure on line 95 in tests/ClientTest.php

View workflow job for this annotation

GitHub Actions / CodeQL

Static call to instance method Utopia\Fetch\Client::fetch().
url: 'localhost:8000',
method: Client::METHOD_POST,
headers: [

Check failure on line 98 in tests/ClientTest.php

View workflow job for this annotation

GitHub Actions / CodeQL

Unknown parameter $headers in call to method Utopia\Fetch\Client::fetch().
'content-type' => 'multipart/form-data'
],
body: [
Expand All @@ -107,7 +107,7 @@
echo $e;
return;
}
if ($resp->getStatusCode()===200) { // If the response is OK
if ($resp->getStatusCode() === 200) { // If the response is OK
$respData = $resp->json(); // Convert body to array
if(isset($respData['method'])) {
$this->assertEquals($respData['method'], Client::METHOD_POST);
Expand All @@ -120,9 +120,9 @@
$files = [ // Expected files array from response
'file' => [
'name' => $fileName,
'full_path'=> $fileName,
'type'=> $contentType,
'error'=> 0
'full_path' => $fileName,
'type' => $contentType,
'error' => 0
]
];
$resp_files = json_decode($respData['files'], true);
Expand All @@ -145,10 +145,10 @@
): void {
$resp = null;
try {
$resp = Client::fetch(

Check failure on line 148 in tests/ClientTest.php

View workflow job for this annotation

GitHub Actions / CodeQL

Static call to instance method Utopia\Fetch\Client::fetch().
url: 'localhost:8000/'.$type,
method: Client::METHOD_GET,
headers: [],

Check failure on line 151 in tests/ClientTest.php

View workflow job for this annotation

GitHub Actions / CodeQL

Unknown parameter $headers in call to method Utopia\Fetch\Client::fetch().
body: [],
query: []
);
Expand All @@ -156,11 +156,11 @@
echo $e;
return;
}
if ($resp->getStatusCode()===200) { // If the response is OK
if ($resp->getStatusCode() === 200) { // If the response is OK
$data = fopen($path, 'rb');
$size=filesize($path);
$size = filesize($path);
if($data && $size) {
$contents= fread($data, $size);
$contents = fread($data, $size);
fclose($data);
$this->assertEquals($resp->getBody(), $contents); // Assert that the body is equal to the expected file contents
} else {
Expand All @@ -178,7 +178,7 @@
{
$resp = null;
try {
$resp = Client::fetch(

Check failure on line 181 in tests/ClientTest.php

View workflow job for this annotation

GitHub Actions / CodeQL

Static call to instance method Utopia\Fetch\Client::fetch().
url: 'localhost:8000/redirect',
method: Client::METHOD_GET,
headers: [],
Expand All @@ -189,7 +189,7 @@
echo $e;
return;
}
if ($resp->getStatusCode()===200) { // If the response is OK
if ($resp->getStatusCode() === 200) { // If the response is OK
$respData = $resp->json(); // Convert body to array
$this->assertEquals($respData['page'], "redirectedPage"); // Assert that the page is the redirected page
} else { // If the response is not OK
Expand Down
2 changes: 1 addition & 1 deletion tests/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function testClassMethods(
public function dataSet()
{
return [
'dummyResponse'=>[
'dummyResponse' => [
'{"name":"John Doe","age":30}',
[
'content-type' => 'application/json'
Expand Down
10 changes: 5 additions & 5 deletions tests/router.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@
$body = file_get_contents("php://input"); // Get the request body
$files = $_FILES; // Get the request files

$curPageName = substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], "/")+1);
$curPageName = substr($_SERVER['REQUEST_URI'], strrpos($_SERVER['REQUEST_URI'], "/") + 1);

if($curPageName == 'redirect') {
header('Location: http://localhost:8000/redirectedPage');
exit;
}
if($curPageName=='image') {
$filename=__DIR__."/resources/logo.png";
if($curPageName == 'image') {
$filename = __DIR__."/resources/logo.png";
header("Content-disposition: attachment;filename=$filename");
header("Content-type: application/octet-stream");
readfile($filename);
exit;
} elseif($curPageName=='text') {
$filename=__DIR__."/resources/test.txt";
} elseif($curPageName == 'text') {
$filename = __DIR__."/resources/test.txt";
header("Content-disposition: attachment;filename=$filename");
header("Content-type: application/octet-stream");
readfile($filename);
Expand Down
Loading