Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is a StreamContext reusable ? And when should it not be reused?

Tags:

php

stream

I'm passing from http to https, and therefore I have to add a StreamContext to several read_file and get_file_contents calls.

I need to replace

read_file('http://'.$host.$uri);

by

$stream_context = stream_context_create([
    /* some lenghty options array */
]);
read_file('https://'.$host.$uri, false, $stream_context);

Now my question: Is a $stream_context reusable like this:

$stream_context = stream_context_create([
    /* some lenghty options array */
]);
read_file('https://'.$host.$uri, false, $stream_context);
get_file_contents($another_url, false, $stream_context);
read_file($even_another, false, $stream_context);

or do I need to recreate a new StreamContext for each URL ?

Asked differently: Is a stream context just a descriptor for parameters and options, or does it get bound to the resource when using it ?

Edit: It seems from the comments, that one can reuse StreamContext often, but not always. This is not quite satisfactory as an answer.

When can or should it be reused, and when can't it be reused ? Can someone shed some light on the internal working of StreamContext. The documentation looks quite sparse to me.

like image 484
Lorenz Meyer Avatar asked Mar 24 '17 08:03

Lorenz Meyer


Video Answer


3 Answers

stream contexts are re-usable and they can be re-used always, not often.

The comment from @ilpaijin pointing to "unpredicted behaviour comment" is simple a misunderstanding of the author leaving the comment.

When you specify your context for HTTP wrapper, you specify the wrapper as HTTP regardless of schema you are targeting, meaning there is no such thing as HTTPS wrapper.

If you try to do the following:

"https" => [
// options will not be applied to HTTPS stream as there is no such wrapper (https)
]

The correct way:

"http" => [
// options will apply to http:// and https:// streams.
]

When should/could re-use?

It's really up to you and up to the logic you are trying to implement.

Don't forget you have default context set for all native PHP wrappers.

The example you have posted where you have the same context stream being passed to 3 different call s is unnecessary, simple use stream_context_set_default and set the default context for request originating from your code.

There are certain situations where you set the default but for one particular request you want to have different context, this would be a good idea to create another stream and pass it in.

Does the stream context contain state, like for instance cookies or tls initial negotiation that are passes from one call to another?

Stream context does not contain state, however you could achieve a mock like this with additional code. Any state, let it be cookie or TLS handshake, are simply request headers. You would need to read that information from incoming request and set it in the stream, and then pass that stream to other request, thus mocking "the state" of parent request. That being said - don't do it, just use CURL.

On a side, the real power of streams is creating your own/custom stream. The header manipulation and state control are much easier (and better) achieved with CURL.

like image 56
rock3t Avatar answered Sep 29 '22 15:09

rock3t


It apparently serves as a connection object (same logic like with database connection) and can be reused in a similar way:

<?php
$default_opts = array(
  'http'=>array(
    'method'=>"GET",
    'header'=>"Accept-language: en\r\n" .
              "Cookie: foo=bar",
    'proxy'=>"tcp://10.54.1.39:8000"
  )
);


$alternate_opts = array(
  'http'=>array(
    'method'=>"POST",
    'header'=>"Content-type: application/x-www-form-urlencoded\r\n" .
              "Content-length: " . strlen("baz=bomb"),
    'content'=>"baz=bomb"
  )
);

$default = stream_context_get_default($default_opts);
$alternate = stream_context_create($alternate_opts);

/* Sends a regular GET request to proxy server at 10.54.1.39
 * For www.example.com using context options specified in $default_opts
 */
readfile('http://www.example.com');

/* Sends a POST request directly to www.example.com
 * Using context options specified in $alternate_opts
 */
readfile('http://www.example.com', false, $alternate);

?>
like image 36
DenisSt Avatar answered Sep 29 '22 16:09

DenisSt


It appears that you can. I used xdebug_debug_zval and ran some simple tests to see if PHP was retaining it internally (I used PHP 7.1.3 with xdebug on an internal development server)

$context = stream_context_create(['https' => ['method' => 'GET']]);
xdebug_debug_zval('context');
$stream = file_get_contents('https://secure.php.net/manual/en/function.file-get-contents.php', false, $context);
xdebug_debug_zval('context');
$stream = fopen('https://secure.php.net/', 'r', false, $context);
xdebug_debug_zval('context');

What I got back was

context:

(refcount=1, is_ref=0)resource(2, stream-context)

context:

(refcount=1, is_ref=0)resource(2, stream-context)

context:

(refcount=2, is_ref=0)resource(2, stream-context)

Interestingly, the second call increased the refcount, meaning it was passed by reference internally. Even unsetting $stream didn't remove it or prevent me from calling it again.

Edit

Since the question was modified...

A context creates a resource data type. Because it contains an instance of PHP data, it is passed by reference implicitly, meaning that PHP is passing the internal data directly and not simply making a copy of it. There's no native way to destroy a context.

like image 25
Machavity Avatar answered Sep 29 '22 17:09

Machavity