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

CURLrequest fix debug verbose #2263

Closed
wants to merge 4 commits into from
Closed

CURLrequest fix debug verbose #2263

wants to merge 4 commits into from

Conversation

jbotka
Copy link
Contributor

@jbotka jbotka commented Sep 23, 2019

Description

  • Issue fix: CURLRequest - supplied argument is not a valid File-Handle resource #Windows x64 #2202 cURL request returns 404 #2250
  • Added additional check inside check if $config['debug'] is true to check if is set exactly to 'true'. If yes, it will assign 'php://temp' stream for CURLOPT_STDERR instead of 'php://output' that is not allowed to be used there.
  • Added check inside sendRequest method to check if is $this->config['debug'] set exactly to 'true'. If yes it will know that it was used temp stream and it will rewind stream to the begging and after that, it will send a formatted text to output from temp stream. If it will not pass the check for 'true' it will throw an exception like in normal mode.
  • Fixed header issue when CI automatically set the header for base_url. This shouldn' happen because it will produce 404 error when accessing URL outside of the local server. So I removed 'Host' header from headers.
  • Fixed also test case since https://example.com is valid URL it will never throw error, to test debugging we need to produce error so I have chosen 'google.com/CodeIgniter4' to produce 404 error. (or we can change it to point to invalid addresses on CI installation 'localhost/curlTest'.

Checklist:

  • Securely signed commits
  • Component(s) with PHPdocs
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@jbotka
Copy link
Contributor Author

jbotka commented Sep 23, 2019

@michalsn Now it should be properly set. Sorry I didn' understand that verbose produce something else than standard errors.

@michalsn
Copy link
Member

For the starter - you're right about debug mode when the error occurs. We always throw an exception. I guess when debug = true we should just display curl error... but the more I think about it, the more unsure I am about it 😅 It could be helpful for debugging in production environment though.


'php://output' that is not allowed to be used there

Can you elaborate on this? Because setting it that way works just fine to me.

Fixed header issue when CI automatically set the header for base_url

I couldn't reproduce it but maybe I don't understand the problem. Can you give us any sample code to recreate this bug?

Fixed also test case since https://example.com is valid URL...

All curl tests are mocked, so we don't really make a request.

@@ -489,6 +489,9 @@ protected function applyRequestHeaders(array $curl_options = []): array
{
$headers = $this->getHeaders();

// Unset 'Host' header, CURL will determinate it automatically from URL.
unset($headers['Host']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can do this. There are many different scenarios where Host header may be necessary, i.e when you call internal IP on the server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can do this. There are many different scenarios where Host header may be necessary, i.e when you call internal IP on the server.

True, we can put here check if is set custom header for Host, if not it will be unset, if yes it will use set from options. Because now in every request you have to define Host, it will never determinate it automatically what is redundant to write it to each request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the issue you have with a Host header at all. The only place where we set something automatically are tests. And you can set default base_url when grabbing the service.

$client = Services::curlrequest([
    'base_uri' => '...',
]);

Copy link
Contributor Author

@jbotka jbotka Sep 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you are grabbing service without options it will set Host from $_SERVER like a host in example case it is 'example.com' or for local development server, it will be 'localhost or so... Case is that if you are trying to access any server you need to specify host each time. It is not necessary since CURL php library will set Host based on URL. Another thing is that it is not mentioned in the guide that you need set host to make a valid request for another server. Simply it is redundant and it confuses people why there is 404 error when you set full valid URL but CI will use localhost all the time.

For example, this will produce error.

$client = \Config\Services::curlrequest();
$response = $client->get('https://google.com');

This is how looks default header from above example:

Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Accept-Encoding: gzip, deflate, br
Accept-Language: sk-SK,sk;q=0.9,cs;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: SEARCH_MORE_RESULTS=Show+more+results

To make work above example you need to do this:

$client = \Config\Services::curlrequest(['headers' => ['Host' => 'google.com']]);
$response = $client->get('https://google.com');

Does it make sense to do it this way all the time when using CURL when we can skip host part completely if we do not need specific server?

@lonnieezell should it work this way?

Another is directly from the guide:

$client = \Config\Services::curlrequest();
$response = $client->request('GET', 'https://api.github.com/user', [
        'auth' => ['user', 'pass']
]);

It will produce 404 error as well, but curl request has all necessary data to make it work but we specified in default headers Host and other headers from $_SERVER[] global variable.

foreach ($_SERVER as $key => $val)
		{
			if (sscanf($key, 'HTTP_%s', $header) === 1)
			{
				// take SOME_HEADER and turn it into Some-Header
				$header = str_replace('_', ' ', strtolower($header));
				$header = str_replace(' ', '-', ucwords($header));

				$this->setHeader($header, $_SERVER[$key]);

				// Add us to the header map so we can find them case-insensitively
				$this->headerMap[strtolower($header)] = $header;
			}
		}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xbotkaj - thanks for the details, now I get it.

I think the solution would be a change in Message class for populateHeaders() method. We should skip HTTP_HOST in foreach loop:

if ($key === 'HTTP_HOST')
{
    continue;
}

Copy link
Contributor Author

@jbotka jbotka Sep 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michalsn we cannot do that since Message is deeper class than CURLrequest class that is extend of Request and that is extend of Message class is used by other method and calls that parse response headers.

For fix just in CURL we could use something like this in applyRequestHeaders() :

if (empty($this->headers))
{
	$headers = $this->getHeaders();
	unset($headers['Host']);
}
else
{
	$headers = $this->getHeaders();
}		

or make new option 'default_headers' that can be boolean and it will handle it. Now users use it and they do not know that CI automatically grab default headers and use it. It can make problems in some cases.


if ($config['debug'] === true)
{
$curl_options[CURLOPT_STDERR] = fopen('php://temp', 'w+');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fopen('php://output', 'w+') is working just fine for outputting things. We should stay with that because otherwise it just complicates things later. So I would just go with the initial one-liner.

Copy link
Contributor Author

@jbotka jbotka Sep 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me using php://output will produce error: CRITICAL --> curl_setopt_array(): supplied argument is not a valid File-Handle resource

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... it can't be happening. fopen('php://output', 'w+') is a perfectly valid resource. Please double-check if you're using the latest framework version from develop branch when you're testing it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely want php://output as that directs tot he buffered output, whereas php://temp would require harvesting the contents from the temp directory.

The issue may be that php://output is explicitly write-only, and w+ specifies opening a resource for "reading and writing" which may cause errors on some systems and not others (just a guess). Try using fopen('php://output', 'w')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually reviewing the docs it states "this will enable additional debugging to echo to STDOUT during the script execution" so we may want to use fopen('php://stdout', 'w') which bypasses buffering altogether. Hardcore debugging :)

Copy link
Contributor Author

@jbotka jbotka Sep 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MGatner I tried:

  • fopen('php://output', 'w') -> produce CRITICAL - 2019-09-24 12:07:11 --> curl_setopt_array(): cannot represent a stream of type Output as a STDIO FILE*
  • fopen('php://stdout', 'w') ->produce nothing (empty output on screen but maybe something in CLI)
  • STDOUT ->produce CRITICAL - 2019-09-24 12:10:04 --> Use of undefined constant STDOUT-assumed 'STDOUT' (this will throw an Error in a future version of PHP)

Only working solution how to print to screen is from php://temp or any file that we can make but this is temporary and we do not have to delete it afterwards. I am not sure about output in CLI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I tested fopen('php://stderr', 'w+') - it works fine, plus it play nice with php spark serve.

@MGatner in the past, Windows used to have an old versions of libraries - which suffered from the bugs. I'm sure it have changed. In this case the difference may be the type of the server (apache, nginx) - and in what mode does php work? Idk... it's a blind shot.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michalsn yep but it will work only in CLI and not like the output to screen during the regular request (if it is wanted the behavior to debug only in CLI).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xbotkaj A quick internet search reveals that this definitely is an issue that crops up with Windows PHP cURL. One question (from guzzle/guzzle#1413 (comment)), are you testing against the same server? If so, can you test against some external site and see if you still get the error with fopen('php://output', 'w')? I believe that is our desired stream since php://stdout doesn't seem to write to the web-accessible buffer.

Copy link
Contributor Author

@jbotka jbotka Sep 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MGatner It is confirmed. It is a windows problem. I uploaded it to my linux server and it works without problem. :)

  • I am suggesting to put to the guide INFO block about the issue on windows platform with quick-fix with php://temp or to do not use this feature.
  • I will put header issue in own commit so it will not be merged here.
  • I am closing this PR since it should be addressed in issues.

@@ -812,7 +821,19 @@ protected function sendRequest(array $curl_options = []): string

if ($output === false)
{
throw HTTPException::forCurlError(curl_errno($ch), curl_error($ch));
// Check if debug is set to 'true'. If yes send verbose to output. If not throw exceptions.
if ($this->config['debug'] === true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ($this->config['debug'] === true)
if ($this->config['debug'] === true && $this->config['debug'] !== true)

I would change it to this condition and only then would throw an exception. Only when debug is turned on we want to see the "pure" error from curl.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's an extra condition in here, or may an extra =? IMO it is fine to specify in the docs that $config['debug'] can be a string file path OR boolean true and then let this throw otherwise.

Copy link
Contributor Author

@jbotka jbotka Sep 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michalsn Nope, it works fine here. Print to the screen is triggered only when is set to 'true'. condition in this form if($this->config['debug]) would be triggered even when there would be string. Now it is triggered only and only with 'true'.

@MGatner if there would be something else, it will throw a critical error from CURL or fopen() method since we are passing this value to fopen().

if ($this->config['debug'] === true)
{
// Rewind written stream to the begging
rewind($curl_options[CURLOPT_STDERR]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we use fopen('php://output', 'w+'), all this code is no longer needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CURLRequest - supplied argument is not a valid File-Handle resource #Windows x64
3 participants