Paul: PHP URL open

Moin moin.

Über folgenden Aufruf im Browser bekommt man eine xml-Datei zurück: https://esgf-data.dkrz.de/esg-search/search/?offset=0&limit=10&type=Dataset&replica=false&latest=true&min_version=20161001&project=CORDEX&domain=EUR-11&time_frequency=day&variable=evspsblpot,hurs

Wenn ich versuche dies per PHP zu machen, kommt ein Fehler. Die Meldung lautet „No connection could be made because the target machine actively refused it.“ Ist schon klar was er damit sagen will, aber warum kann man diese Datei dann per Browser aufrufen?

Beispiel in php ($serachString ist die URL):

$xmlDoc = new DOMDocument;
$xmlDoc->load($searchString);
$handle = fopen($searchString, "r");
$test = file_get_contents($searchString);

Danke, Paul

  1. Tach!

    warum kann man diese Datei dann per Browser aufrufen?

    Weil der Server den Request vom Browsr akzeptiert und den von PHP nicht. Warum das so ist, kann man nur spekulieren, solange man nicht den Serverbetreiber fragt. (Warum fragst du den nicht, sondern Außenstehende?)

    Es kann damit zu tun haben, dass der Server bestimmte HTTP-Header erwartet, die ein Browser standardmäßig mitsendet, der PHP-Request jedoch nicht. Andere Möglichkeiten der Auswertung hat der Server nicht.

    Man kann den PHP-Funktionen (abgesehen von DOMDocument::load) einen Stream-Kontext mitgeben, darüber kann man zusätzliche Header setzen. Das ist im PHP-Handbuch auf den Seiten zu den Funktionen und auf verlinkten Kapiteln (Supported Protocols and Wrappers -> Context Options) beschrieben.

    Welche Header der Browser sendet, kann man mit den eingebauten Entwocklertools herausfinden. Mein erster Versuch wäre, einen (beliebigen Wert für) User-Agent mitzusenden.

    dedlfix.

    1. Hallo und guten Morgen,

      Weil der Server den Request vom Browsr akzeptiert und den von PHP nicht. Warum das so ist, kann man nur spekulieren, solange man nicht den Serverbetreiber fragt. (Warum fragst du den nicht, sondern Außenstehende?)

      Vielleicht weil wir hier (virtuell) zusammenkommen, um über genau solche Techniken zu diskutieren? Zeig doch lieber, wie man sowas macht und/oder wie man es erkennen kann, wenn ein Anbieter derartige Einschränkungen macht.

      Es kann damit zu tun haben, dass der Server bestimmte HTTP-Header erwartet, die ein Browser standardmäßig mitsendet, der PHP-Request jedoch nicht. Andere Möglichkeiten der Auswertung hat der Server nicht.

      Oder, dass eben der Header "x-powered-by: php ..." mitgesendet wird

      Man kann den PHP-Funktionen (abgesehen von DOMDocument::load) einen Stream-Kontext mitgeben, darüber kann man zusätzliche Header setzen. Das ist im PHP-Handbuch auf den Seiten zu den Funktionen und auf verlinkten Kapiteln (Supported Protocols and Wrappers -> Context Options) beschrieben.

      Welche Header der Browser sendet, kann man mit den eingebauten Entwocklertools herausfinden. Mein erster Versuch wäre, einen (beliebigen Wert für) User-Agent mitzusenden.

      Man kann auch vor dem Absenden des "Post by Host" oder des "Get by Host" die gesetzten Header abfragen und verändern.

      Grüße
      TS

      --
      es wachse der Freifunk
      http://freifunk-oberharz.de
      1. Tach!

        Weil der Server den Request vom Browsr akzeptiert und den von PHP nicht. Warum das so ist, kann man nur spekulieren, solange man nicht den Serverbetreiber fragt. (Warum fragst du den nicht, sondern Außenstehende?)

        Vielleicht weil wir hier (virtuell) zusammenkommen, um über genau solche Techniken zu diskutieren? Zeig doch lieber, wei man sowas macht und/oder wie man es erkennen kann, wenn ein Anbieter derartige Einschränkungen macht.

        Es scheint mir weitverbreitet zu sein, dass man nicht mit den Betroffenen sondern mit Dritten über sie redet. Das muss nicht sein, egal welche Fachkenntnisse die Dritten haben. Man kann sie ja immer noch zu Rate ziehen, wenn das Gespräch miteinander nicht weiterführt. Aber das direkte Gespräch sollte der erste Schritt sein.

        Man kann die Einschränkungen nicht erkennen, außer dass man in Kleinarbeit testet, was geht und was nicht. Man muss sozusagen probieren, welcher Dietrich passt, statt dass man den Inhaber um einen Schlüssel bittet. Und anscheinend sollen ja bestimmte Requests unbeantwortet bleiben.

        dedlfix.

      2. Hallo

        Weil der Server den Request vom Browsr akzeptiert und den von PHP nicht. Warum das so ist, kann man nur spekulieren, solange man nicht den Serverbetreiber fragt. (Warum fragst du den nicht, sondern Außenstehende?)

        Vielleicht weil wir hier (virtuell) zusammenkommen, um über genau solche Techniken zu diskutieren?

        Und deshalb solle die Anmerkung, doch erst einmal den Dienstanbieter um Hilfe zu fragen, nicht statthaft sein? Wir weisen hier ja auch immer wieder einmal auf den Support des konkreten Hosters hin, wenn es z.B. um PHP-Funktionen geht, deren Arbeitsweise stark von den Einstellungen auf dem Webserver des Hosters abhängt. Warum sollte das in genau diesem Fall anders sein, wo doch der Dienstanbieter am besten wissen sollte, welche Zugriffe er erlaubt und wie die zu erfolgen haben?

        Zeig doch lieber, wei man sowas macht und/oder wie man es erkennen kann, wenn ein Anbieter derartige Einschränkungen macht.

        Mögliche Ursachen und Lösungsansätze beschreibt dedlfix doch im Anschluss?! Was also soll der Seitenhieb?


        Für's Archiv, auch wenn die genannten Fehlermeldungen nicht darauf hinweisen:

        Die Funktionen fopen und file_get_contents, sowie einige weitere, die dem Zugriff auf Dateien dienen, können in PHP grundsätzlich auch auf URLs zugreifen. Das ist in diesem Fall wohl auch gegeben. Viele Hostinganbieter schränken diese Möglichkeiten aber ein, so dass diese Funktionen ausschließlich im lokalen Dateisystem, nicht aber über URLs funktionieren.

        In diesen Fällen kann man für den Zugriff auf externe, per URL zu erreichende Ressourcen z.B. die PHP-Bibliothek für cURL benutzen. Die ist allerdings nicht standardmäßig installiert. Auch diese Bibliothek kann die hier schon angesprochenen HTTP-Header senden.

        Mal als Beispiel eine Funktion. Nicht alle Optionen (gesetzt mit curl_setopt) werden überall gebraucht.

        /**
         * wrapper to load an external ressource into a variable
         *
         * @param string $url
         * @return bool false
         * @return string $string
         */
        function my_load_feed($url) {
        # no value in $url? return false!
        if ($url === NULL) return false;
        # the value of $url is no URL? return false!
        if (filter_var($url, FILTER_VALIDATE_URL) === false) return false;
        
        $string = '';
        
        # does the function exists in the PHP installation?
        if (function_exists('curl_init')) {
        	# read the ressource from the URL with curl
        	$c = curl_init();
        	curl_setopt($c, CURLOPT_URL, $url);
        	curl_setopt($c, CURLOPT_HEADER, false);
        	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
        	curl_setopt($c, CURLOPT_SSL_VERIFYHOST, false);
        	curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);   
        	curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 20);
        	curl_setopt($c, CURLOPT_USERAGENT, "PHP 5.6");
        	curl_setopt($c, CURLOPT_HTTPHEADER, array('X-Identifier: Test-Skript 0.3', 'Content-type: text/xml;charset="utf-8"'));
        	$string = curl_exec($c);
        	curl_close($c);
        	} else {
        		return false;
        	}
        return $string;
        }
        

        Tschö, Auge

        --
        Wenn man ausreichende Vorsichtsmaßnahmen trifft, muss man keine Vorsichtsmaßnahmen mehr treffen.
        Toller Dampf voraus von Terry Pratchett
        1. Und deshalb solle die Anmerkung, doch erst einmal den Dienstanbieter um Hilfe zu fragen, nicht statthaft sein?

          Naja, es ist eine Frage der Augenhöhe. Das ist das REST API des DKRZ und die bieten Tutorials und eine Mailadresse zum Support an. Bevor man Antwort bekommt, kann Zeit vergehen, und da es ein auf der Homepage beworbenes API ist, sollte man eigentlich annehmen, dass der automatisierte Zugriff nicht unerwünscht ist.

          Aber die werden einem keinen potentiellen, möglicherweise dusseligen PHP Fehler debuggen. Um das abzuklären, ist man in Userforen besser aufgehoben...

          Update: Habe gerade mal die 4 Zeilen PHP des OP mit PHP 5.6.21 von der Windows Kommandozeile laufen lassen. Hat funktioniert! Das Script ist also nicht das Problem.

          Rolf

          1. Hallo

            Und deshalb solle die Anmerkung, doch erst einmal den Dienstanbieter um Hilfe zu fragen, nicht statthaft sein?

            Das ist das REST API des DKRZ und die bieten Tutorials …

            Das ist, um bei meinem Beispiel meines letzten Postings zu bleiben, bei vielen Hostern auch nicht anders, wenn man nicht extra Geld in die Hand nehmen will. Oft reicht aber ein Studium der FAQ. Wenn es in diesem Fall Tutorials gibt, sind diese meiner Meinung nach die erste Anlaufstelle.

            … und eine Mailadresse zum Support an. Bevor man Antwort bekommt, kann Zeit vergehen, …

            Das ist halt so. Das ist kein Grund, diesen Weg nicht zu gehen.

            … und da es ein auf der Homepage beworbenes API ist, sollte man eigentlich annehmen, dass der automatisierte Zugriff nicht unerwünscht ist.

            Der automatisierte Zugriff klappt doch grundsätzlich, wie der OP selbst schrieb. Er klappt nur auf einem bestimmten der beschrittenen Wege (konkrete Programmierung in PHP) nicht. Ein mehr als flüchtiger Blick in die Tutorials und die Doku sind aber nicht zu viel verlangt.

            Wenn dann etwas unklar bleibt, was ja weder ungewöhnlich noch eine Schande ist, dann ist der Gang in ein Benutzerforum [1] der richtige Weg. Ich denke du weißt, wie schnell man hier die Frage nach den bereits eingeleiteten Schritten um die Ohren gehauen bekommt.

            Aber die werden einem keinen potentiellen, möglicherweise dusseligen PHP Fehler debuggen.

            Natürlich nicht.

            Tschö, Auge

            --
            Wenn man ausreichende Vorsichtsmaßnahmen trifft, muss man keine Vorsichtsmaßnahmen mehr treffen.
            Toller Dampf voraus von Terry Pratchett

            1. Was ist ein „Benutzerforum“? Gibt es welche ohne Benutzer und sind, wenn es keine Foren ohne Benutzer gibt, dann nicht alle Foren Benutzerforen? ↩︎

        2. Alles richtig, aber ...

          da cUrl manchmal nicht installiert ist lohnt sich ggf. der Rückgriff auf das Systemprogramm wget. Allerdings kann auch das (wie alle Zugriffe auf exec, system, backtick, etc.) vom eigenen Hoster verboten sein. Ferner kann (oder muss) man die entfernte Webseite auch davon überzeugen, ein ganz normaler Browser zu sein. Falls mehr als Referer und Kennung des User-Agent nötig sein sollten (Cookie, Session, ...) liefert das Manual für wget (man wget) sehr viele wertvolle Hinweise.

          Ein aktiviertes $checkCertifikate = '--no-check-certificate'; sorgt dafür, dass beim Transport via HTTPS nicht geprüft wird ob das Zertifikat von einer anerkannten Stelle unterschrieben wurde. Soll die Herkunft der Daten sicher sein, dann geht das natürlich nicht, wird aber (mit wget) immer dann recht aufwendig, wenn auf einem Server die Zertifikate nicht hinterlegt sind, was die Regel ist, weil ja kein Browser existiert. cUrl hat die selbe Kinderkrankheit

           	} else {
                    $userAgent = 'Mozilla/5.0 (Windows) Gecko/20100101 Firefox/50.0';
                    $referer = 'http://www.example.com/';
                    $tmpfile = tempnam( '/tmp', 'php_' );
          
                    $checkCertifikate = '';
                    #$checkCertifikate = '--no-check-certificate';
          
                    $sys = 'wget --user-agent=' . escapeshellarg( $userAgent ) . ' --referer=' . escapeshellarg( $referer ) . ' ' . $checkCertifikate . ' -d -O ' . escapeshellarg( $tmpfile ) . ' ' . escapeshellarg( $url ) . ' &2>1';
                    echo '<pre>';
                    echo "<hr />Fehlermeldungen/Header:<hr />";
                    echo `$sys`;
                    echo '<hr />Abgeholte Daten:<hr />';
                    echo htmlspecialchars( file_get_contents( $tmpfile ) );
                    unlink( $tmpfile );
                    echo '<hr /></pre>';
          }
          

          Es kann aber auch gut sein, dass der Betreiber der entfernten Seite dann sauer wird. Insbesondere wenn er feststellt, dass recht viele Requests erfolgen und er dann auch glaubt, bezüglich des Useragent und/oder des Referer belogen worden zu sein. Immerhin hat er ja vermutlich den Zugriff verboten. Das macht Arbeit und die macht man sich nicht grundlos. Er kann dann auch die IP aussperren. Ich würde genau das tun.

  2. Es gibt ein python script welches benutzt werden kann. Mit PHP klappts nicht. Danke für die Antworten.

    1. Mit PHP klappts nicht.

      Das ist aber merkwürdig, denn das hier klappt sehr wohl:

      <?php
                $url='https://esgf-data.dkrz.de/esg-search/search/?offset=0&limit=10&type=Dataset&replica=false&latest=true&min_version=20161001&project=CORDEX&domain=EUR-11&time_frequency=day&variable=evspsblpot,hurs';
                $userAgent = 'Mozilla/5.0 (Windows) Gecko/20100101 Firefox/50.0';
                $referer = 'http://www.example.com/';
                $tmpfile = tempnam('/tmp', 'php_');
                $sys = 'wget --user-agent=' . escapeshellarg($userAgent) . ' --referer=' . escapeshellarg($referer) . ' -d -O ' . escapeshellarg($tmpfile) . ' ' . escapeshellarg( $url ) . ' &2>1';
                echo '<pre>';
                echo "<hr />Fehlermeldungen/Header:<hr />";
                echo `$sys`;
                echo '<hr />Abgeholte Daten:<hr />';
                echo htmlspecialchars(file_get_contents($tmpfile));
                unlink($tmpfile);
                echo '<hr /></pre>';
      

      Ich vermute den Fehler also auf Level 8.

      1. Tach!

        Mit PHP klappts nicht.

        Das ist aber merkwürdig, denn das hier klappt sehr wohl:

              $sys = 'wget --user-agent=' . escapeshellarg($userAgent) . ' --referer=' . escapeshellarg($referer) . ' -d -O ' . escapeshellarg($tmpfile) . ' ' . escapeshellarg( $url ) . ' &2>1';
        

        Das zählt nicht als "klappt mit PHP". Das muss ohne Umwege über Systembefehle gehen.

        dedlfix.

        1. Hallo und guten Abend,

                $sys = 'wget --user-agent=' . escapeshellarg($userAgent) . ' --referer=' . escapeshellarg($referer) . ' -d -O ' . escapeshellarg($tmpfile) . ' ' . escapeshellarg( $url ) . ' &2>1';
          

          Das zählt nicht als "klappt mit PHP". Das muss ohne Umwege über Systembefehle gehen.

          meistens funktionieren die fsockopen()-Funktionen noch, auch wenn die fopen()-Wrapper für html & Co. ausgeschaltet sind. Und damit kann man dann (fast!) ganz gezielt auf den entfernten Socket und seine Möglichkeiten zugreifen.

          Beispiel head_request():

          function head_request($url, $status_only=false, $parsed=true)
          {
              ### checks, if an url exists. 
              ### if no scheme is provided, 'http://' will be assumed
              ### if not "$parsed", raw headers shell be returned, 
              ###     otherwise an array shell be produced
              ###
              ### Used own Constants: TIMEOUT_SOCK_OPEN, TIMEOUT_STREAM_READ
              defined('TIMEOUT_SOCK_OPEN') or define('TIMEOUT_SOCK_OPEN', 1);
              defined('TIMEOUT_STREAM_READ') or define('TIMEOUT_STREAM_READ', 2);
              
              $url = trim($url);
              $_url_parts = parse_url($url);
          
              if (!isset($_url_parts['scheme'])) $url = 'http://' . $url;
          
              $_url_parts = parse_url($url);
              
          #    * scheme - e.g. http
          #    * host
          #    * port
          #    * user
          #    * pass
          #    * path
          #    * query - after the question mark ?
          #    * fragment - after the hashmark #
          
              if (!isset($_url_parts['host'])) return false;
          
              $fp = @fsockopen($_url_parts['host'], 80, $errno, $errstr, TIMEOUT_SOCK_OPEN);
          
              if (!$fp) return false;
          
              stream_set_timeout($fp, TIMEOUT_STREAM_READ);
          	
             	$path = '/';
          	if (isset($_url_parts['path'])) {$path = $_url_parts['path'];}
          	
              $out = "HEAD $path HTTP/1.1\r\n";
              $out .= "Host: " . $_url_parts['host'] . "\r\n";
              $out .= "Connection: Close\r\n\r\n";
              fwrite($fp, $out);
              
              $res = '';
              
              while (!feof($fp)) 
              {
                  $res .= fgets($fp, 128);
              }
              
              $_info = stream_get_meta_data($fp);
              fclose($fp);
          
              if ($_info['timed_out']) return false;
              
              $res = str_replace(array('   ', '  '), ' ', $res);
              $_lines = explode("\r\n", $res);
          
          #       return $_lines;
              
              foreach ($_lines as $key => $value)
              {
                  if (strlen($value) == 0) 
                  {
                      unset($_lines[$key]);
                      continue;
                  }
                  
                  $_help[$key] = explode(': ', $value);
          
                  if (!isset($_help[$key][1]))
                  {
                      $_out['status'] = explode(' ', trim($_help[$key][0]));
                  }
                  else
                  {
                      $_out[strtolower($_help[$key][0])] = explode(' ', trim($_help[$key][1]));
                  }   
              }
          
              if (!isset($_out['status'][1])) return false;
              if ($status_only) return $_out['status'][1];
              if (!$parsed) return $res;
              
              return $_out;   
          }
          
          

          Nur das Vorhandensein einer (entfernten) HTTP-Ressource feststellen...
          Auf ähnliche Weise kann man sie natürlich auch komplett auslesen.

          Grüße
          TS

          --
          es wachse der Freifunk
          http://freifunk-oberharz.de
          1. Wie ich heute um 10:15 updatend schreibte - Pauls drei Versuche aus dem Eingangsposting funktionieren von einem Windows Kommandozeilen PHP ebenfalls, ohne sich auf die socken zu machen. Die Frage ist, wo Paul seinen Code ausgeführt hat. Es scheint mir ein Problem der Umgebung, in der das PHP Script läuft.

            Kritisch kann die https-Connection sein, weil dafür die openssl Extension aktiv sein muss, und mein Bauchgefühl wäre, dass der Verbindungsaufbau nicht klappt. Mich behelligt allerdings kein bisschen Ahnung, an welchen Stellen der PHP- oder Serverkonfiguration man da welches Misthäufchen hinterlassen kann.

            Rolf