1111
1212use RuntimeException ;
1313use Toolkit \Cli \Cli ;
14+ use function array_merge ;
1415use function basename ;
1516use function error_get_last ;
1617use function fclose ;
1718use function file_put_contents ;
1819use function fopen ;
1920use function getcwd ;
21+ use function getenv ;
2022use function is_resource ;
23+ use function preg_replace ;
2124use function stream_context_create ;
2225use function stream_context_set_params ;
26+ use function strpos ;
2327use function trim ;
2428use const STREAM_NOTIFY_AUTH_REQUIRED ;
2529use const STREAM_NOTIFY_AUTH_RESULT ;
@@ -41,7 +45,7 @@ final class Download
4145{
4246 public const PROGRESS_TEXT = 'text ' ;
4347
44- public const PROGRESS_BAR = 'bar ' ;
48+ public const PROGRESS_BAR = 'bar ' ;
4549
4650 /** @var string */
4751 private $ url ;
@@ -55,6 +59,19 @@ final class Download
5559 /** @var string */
5660 private $ showType ;
5761
62+ /**
63+ * @var bool
64+ */
65+ private $ debug = false ;
66+
67+ /**
68+ * http context options
69+ *
70+ * @var array
71+ * @link https://www.php.net/manual/en/context.http.php
72+ */
73+ private $ httpCtxOptions = [];
74+
5875 /**
5976 * @param string $url
6077 * @param string $saveAs
@@ -118,14 +135,15 @@ public function start(): self
118135 $ this ->saveAs = $ save ;
119136 }
120137
121- $ ctx = stream_context_create ();
138+ $ ctx = $ this -> createStreamContext ();
122139
123140 // register stream notification callback
141+ // https://www.php.net/manual/en/function.stream-notification-callback.php
124142 stream_context_set_params ($ ctx , [
125143 'notification ' => [$ this , 'progressShow ' ]
126144 ]);
127145
128- Cli::write ("Download: {$ this ->url }\n Save As: {$ save }\n" );
146+ Cli::write ("Download: {$ this ->url }\n Save As: {$ save }\n" );
129147
130148 $ fp = fopen ($ this ->url , 'rb ' , false , $ ctx );
131149
@@ -145,16 +163,71 @@ public function start(): self
145163 return $ this ;
146164 }
147165
166+ protected function createStreamContext ()
167+ {
168+ // https://www.php.net/manual/en/context.http.php
169+ $ httpOpts = [
170+ 'max_redirects ' => '15 ' ,
171+ 'protocol_version ' => '1.1 ' ,
172+ 'header ' => [
173+ 'Connection: close ' , // on 'protocol_version' => '1.1'
174+ ],
175+ // 'follow_location' => '1',
176+ // 'timeout' => 0,
177+ // 'proxy' => 'tcp://my-proxy.localhost:3128',
178+ ];
179+
180+ if ($ this ->httpCtxOptions ) {
181+ $ httpOpts = array_merge ($ httpOpts , $ this ->httpCtxOptions );
182+ }
183+
184+ $ isHttps = strpos ($ this ->url , 'https ' ) === 0 ;
185+
186+ if (!isset ($ httpOpts ['proxy ' ])) {
187+ if ($ isHttps ) {
188+ $ proxyUrl = (string )getenv ('https_proxy ' );
189+ } else {
190+ $ proxyUrl = (string )getenv ('http_proxy ' );
191+ }
192+
193+ if ($ proxyUrl ) {
194+ $ this ->debugf ('Uses proxy ENV variable: http%s_proxy=%s ' , $ isHttps ? 's ' : '' , $ proxyUrl );
195+
196+ // convert 'http://127.0.0.1:10801' to 'tcp://127.0.0.1:10801'
197+ // see https://github.com/guzzle/guzzle/issues/1555#issuecomment-239450114
198+ if (strpos ($ proxyUrl , 'http ' ) === 0 ) {
199+ $ proxyUrl = preg_replace ('/^http[s]?/ ' , 'tcp ' , $ proxyUrl );
200+ }
201+
202+ $ httpOpts ['proxy ' ] = $ proxyUrl ;
203+ // see https://www.php.net/manual/en/context.http.php#110449
204+ $ httpOpts ['request_fulluri ' ] = true ;
205+ }
206+ }
207+
208+ return stream_context_create ([
209+ 'http ' => $ httpOpts ,
210+ ]);
211+ }
212+
148213 /**
149- * @param int $notifyCode stream notify code
150- * @param int $severity severity code
151- * @param string $message Message text
152- * @param int $messageCode Message code
153- * @param int $transferredBytes Have been transferred bytes
154- * @param int $maxBytes Target max length bytes
214+ * @link https://www.php.net/manual/en/function.stream-notification-callback.php
215+ *
216+ * @param int $notifyCode stream notify code
217+ * @param int $severity severity code
218+ * @param string|null $message Message text
219+ * @param int $messageCode Message code
220+ * @param int $transferredBytes Have been transferred bytes
221+ * @param int $maxBytes Target max length bytes
155222 */
156- public function progressShow (int $ notifyCode , $ severity , string $ message , $ messageCode , int $ transferredBytes , int $ maxBytes ): void
157- {
223+ public function progressShow (
224+ int $ notifyCode ,
225+ int $ severity ,
226+ ?string $ message ,
227+ $ messageCode ,
228+ int $ transferredBytes ,
229+ int $ maxBytes
230+ ): void {
158231 $ msg = '' ;
159232
160233 switch ($ notifyCode ) {
@@ -172,7 +245,7 @@ public function progressShow(int $notifyCode, $severity, string $message, $messa
172245 break ;
173246
174247 case STREAM_NOTIFY_CONNECT :
175- $ msg = 'Connected ... ' ;
248+ $ msg = '> Connected ... ' ;
176249 break ;
177250
178251 case STREAM_NOTIFY_FILE_SIZE_IS :
@@ -226,6 +299,17 @@ public function showProgressByType($transferredBytes): string
226299 return '' ;
227300 }
228301
302+ /**
303+ * @param string $format
304+ * @param mixed ...$args
305+ */
306+ public function debugf (string $ format , ...$ args ): void
307+ {
308+ if ($ this ->debug ) {
309+ Cli::printf ("[DEBUG] $ format \n" , ...$ args );
310+ }
311+ }
312+
229313 /**
230314 * @return string
231315 */
@@ -273,4 +357,20 @@ public function setSaveAs(string $saveAs): void
273357 {
274358 $ this ->saveAs = trim ($ saveAs );
275359 }
360+
361+ /**
362+ * @param bool $debug
363+ */
364+ public function setDebug (bool $ debug ): void
365+ {
366+ $ this ->debug = $ debug ;
367+ }
368+
369+ /**
370+ * @param array $httpCtxOptions
371+ */
372+ public function setHttpCtxOptions (array $ httpCtxOptions ): void
373+ {
374+ $ this ->httpCtxOptions = $ httpCtxOptions ;
375+ }
276376}
0 commit comments