Jahrestag: GOV-Inhalte werden im GenWiki nicht mehr angezeigt

Sie muss auf den GenWiki-Servern https://wiki.genealogy.net und https://wiki-test.genealogy.net liegen. @robertpaessler hat auf jeden Fall Zugriff, @jzedlitz vielleicht auch.

Da da das Skript eh derzeit gar keine Funktion hat, kann man auch die Benutzer (bei normaler Vorsicht) nicht beeinträchtigen.

Habe ich im Test-Wiki geändert. Scheint nichts zu bewirken.

Mittels

if (!$client->get('/item/wikihtml/'.$objectId)) {
		echo 'Request failed: HTTP Status: ' . $client->getStatus();
		echo 'Error message: ' . $client->getError();
		return '<p>Request failed!</p>';
	}

habe ich

Error message: Connection failed (32537) Unable to find the socket transport "https" - did you forget to enable it when you configured PHP?

erhalten. Ideen?

Hast Du auch in den anderen Zeilen http: durch https: ersetzt?

Klar. Aber es kommt doch gar nicht zum else . . .

Laut Jesper kommt auf dem GOV-Server die Anfrage aus dem Genwiki nicht
an. Also holpert es zwischen den beiden Servern. Ich schlage folgendes
systematische Vorgehen vor:

Schritt 1: Auf dem Genwiki-Server ein Tracert / traceroute .
Damit stellt man fest, ob der GOV-Server überhaupt erreichbar ist. Und
wenn ja: Über welche Router / Firewalls / Server … läuft der
Datenverkehr. Alternativ kann man auch mit dem Ping etwas herumspielen.
Erforderlich sind detaillierte Kenntnisse über die IT-Landschaft.
Insbesondere die Frage: Auf welcher Route SOLLTE der Verkehr zum
GOV-Server laufen?

Schritt 2: Netzwerktraffic protokollieren, z.B. mit Wireshark: Auf dem
Genwiki-Server Wireshark starten - Aufzeichnung starten - GOV-Abfrage
ausführen - Aufzeichnung stoppen. Nun die Protokolldatei ansehen. Da
müsste eine entsprechende Fehlermeldung zu sehen sein. Welcher Router /
Firewalll … meckert? Aus welchem Grund? Erforderlich ist hier ein
Netzwerkspezialist, der Verständnis für den Datenverkehr hat und die
Protokolldatei interpretieren kann.

Spätestens nach Schritt 2 sollte man die Ursache des Problems eng
eingegrenzt haben.

Werner

Die Fehlermeldung soll darauf hindeuten, dass die openssl-Erweiterung für php auf dem GenWiki-Server nicht aktiviert (z. B. in der php.ini auskommentiert) oder gar nicht installiert ist.

In der php.ini sollte eine Zeile
extension=openssl
existieren und nicht auskommentiert sein.

Ist der Server ein Linux-System?

openssl ist aktiv. Ja, Linux.

Ich vermute einen Fehler beim HttpClient.

Folgendes kann man versuchen:

  • php.ini ansehen wie beschrieben
  • Überprüfen, ob es vielleicht mehrer php.ini gibt
  • phpinfo(); innerhalb eines PHP-Skriptes sollte einen Abschnitt namens openssl liefern
  • Auf Kommandozeilenebene php -m | grep openssl ausführen
  • Innerhalb von PHP mit print_r(stream_get_wrappers()); nachsehen, ob https unterstützt wird

Da der HttpClient intern fsockopen() verwendet, könnte man folgendes testen:

`<?php
$hostname = ‚ssl://gov.genealogy.net‘;
$port = 443;
$errno = 0;
$errstr = ‚‘;

$fp = fsockopen($hostname, $port, $errno, $errstr, 30);

if (!$fp) {
echo „Fehler: $errstr ($errno)
\n“;
} else {
echo „Verbindung erfolgreich.
\n“;
fclose($fp);
}
?>’

Kann schon sein, das Teil ist ja locker 20 Jahre alt.
Man könnte die Funktion natürlich komplett neu schreiben, aber dabei wird man sich neue unbekannte Probleme einhandeln. Erst einmal finde ich das von @Christopher_Ernestus vorgeschlagene schrittweise Testen zielführender.

Ich debugge den HttpClient und habe neue Erkenntnisse:
https://wiki-test.genealogy.net/Bitterfeld

Was sagt uns das? ChatGPT meint im wesentlich das, was auch @Christopher_Ernestus schon meinte:

Der Fehler “400 Malformed Host Header” weist darauf hin, dass ein Problem mit der Host-Header-Anfrage besteht, die ein Client (z. B. ein Webbrowser) an einen Server sendet. Der HTTP-Header Host ist erforderlich, um dem Server mitzuteilen, welche Domain oder Subdomain der Client erreichen möchte, insbesondere wenn der Server mehrere Websites (Virtual Hosts) hostet.

Ursachen des Fehlers
1. Falscher oder ungültiger Host-Header:
• Der Client sendet einen ungültigen oder fehlformatierten Host-Header. Beispielsweise könnte der Host-Header leer, fehlerhaft oder nicht RFC-konform sein.
2. DNS-Problem:
• Die angefragte Domain ist falsch konfiguriert oder auf eine IP-Adresse umgeleitet, die der Server nicht kennt.
3. Webserver-Konfiguration:
• Der Webserver (z. B. Apache, Nginx) akzeptiert die im Host-Header angegebene Domain nicht. Möglicherweise fehlt ein Eintrag in der Konfiguration für diese Domain.
4. Firewall oder Proxy:
• Ein Proxy-Server oder eine Firewall ändert den Host-Header oder blockiert die Anfrage.
5. CORS- oder Sicherheitsbeschränkungen:
• Der Server verweigert Verbindungen von nicht zugelassenen Domains.

Lösungsmöglichkeiten
1. Überprüfung der URL:
• Stelle sicher, dass die eingegebene URL korrekt ist.
2. DNS-Einstellungen prüfen:
• Vergewissere dich, dass die Domain korrekt auf den Webserver zeigt.
3. Webserver-Konfiguration prüfen:
• Stelle sicher, dass der Webserver die angefragte Domain in der Konfiguration (z. B. Virtual Host in Apache/Nginx) erkennt.
4. Fehler in der Firewall oder im Proxy prüfen:
• Überprüfe, ob Firewalls oder Proxies Anfragen korrekt weiterleiten.
5. Server-Logs ansehen:
• Schaue in die Server-Logs, um spezifische Details zu dem Fehler zu erhalten.
6. Host-Header testen:
• Tools wie cURL oder Postman können verwendet werden, um die Anfrage und den Host-Header manuell zu prüfen:

curl -H „Host: example.com“ http://

… Oder etablierte Lösungen verwenden: Guzzle, PHP HTTP client — Guzzle Documentation

Nicht das Rad neu erfinden. Wir sind eh nicht so die Radbauer…

Ich habe das just in webtrees gemacht. Für den Zugriff von webtrees auf wikidata hatte ich erst einen Ansatz mit curl probiert, den ich nicht zum Laufen bekommen habe. Dann habe ich Guzzle eingesetzt, was super funktioniert. Aber das ist ein Hammer-Paket, man kann an sehr vielen Schrauben drehen. Der Code zur Nutzung sieht ganz anders aus. Da muss man vieles ganz anders schreiben. Man kann nicht erwarten, dass das ohne eine tiefgreifende Änderung am Code von statten geht, wenn man einen Hammer aus der Steinzeit durch ein Multipower Black&Decker Super/Tool ersetzt. Vielleicht ist das aber der Weg, wenn Roberts Analyse auch nicht weiter helfen sollte.

Eigentlich unterstützt die http 1.1 Version, die er entdeckt hat, ja virtuelle Hosts und sollte daher reichen (auch wenn http 2 oder 3 noch performanter sein sollen).

@robertpaessler Danke für Deine Versuche. Wie sieht denn der Quelltext aktuell aus?
Angemeckert wird ja der „Hostname“, der ja auch falsch ist:

http://ssl://gov.genealogy.net:443/path

Das ssl:// darf nur in dem fsockopen-Statement stehen, das ich zur Kontrolle vorgeschlagen hatte, aber nicht im httpClient-Aufruf. Der muss eines dieser Formate haben:

$client = new HttpClient('gov.genealogy.net', 443);
$client = new HttpClient('https://gov.genealogy.net');

Guckt mal bitte wieder hier:
https://wiki-test.genealogy.net/Bitterfeld

Ich habe getestet, dass eine Verbindung zum GOV-Server besteht. Von dort werden Zeilen abgeholt, wobei es zu einem Fehler kommt. Habt ihr Ideen?

// start reading back the response
    	while (!feof($fp)) {
    	    $line = fgets($fp, 4096);
			
			if ($line === false) {
				if (feof($fp)) {
					$this->debug("Ende des Streams erreicht.");
				} else {
					$this->errormsg = "Fehler beim Lesen der Zeile.";
					$this->debug($this->errormsg);
				}
				return false;
			} else {
				$this->debug("Gelese Zeile: " . htmlentities($line));
			}
			
			if ($atStart) {
    	        // Deal with first line of returned data
    	        $atStart = false;
    	        if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) {
    	            $this->errormsg = "Status code line invalid: ".htmlentities($line);
    	            $this->debug($this->errormsg);
    	            return false;
    	        }
    	        $http_version = $m[1]; // not used
    	        $this->status = $m[2];
    	        $status_string = $m[3]; // not used
    	        $this->debug(trim($line));
    	        continue;
    	    }
    	    if ($inHeaders) {
    	        if (trim($line) == '') {
    	            $inHeaders = false;
    	            $this->debug('Received Headers', $this->headers);
    	            if ($this->headers_only) {
    	                break; // Skip the rest of the input
    	            }
    	            continue;
    	        }
    	        if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) {
    	            // Skip to the next header
    	            continue;
    	        }
    	        $key = strtolower(trim($m[1]));
    	        $val = trim($m[2]);
    	        // Deal with the possibility of multiple headers of same name
    	        if (isset($this->headers[$key])) {
    	            if (is_array($this->headers[$key])) {
    	                $this->headers[$key][] = $val;
    	            } else {
    	                $this->headers[$key] = array($this->headers[$key], $val);
    	            }
    	        } else {
    	            $this->headers[$key] = $val;
    	        }
    	        continue;
    	    }
    	    // We're not in the headers, so append the line to the contents
    	    $this->content .= $line;
        }
  • Wo genau stehen denn diese Zeilen?
  • Woran siehst Du, dass (nun) eine Verbindung zum GOV-Server besteht?
  • Die Fehlermeldung, die bei Bitterfeld ausgegeben wird („Request failed“), ist ja die gleiche wie bisher immer (und keine, die aus dem geposteten Stück Quellcode kommt)

Sehe ich auch so. Das ist unverändert der Code den Jesper ganz oben gezeigt hat. In der Funktion completeGovTable das zweite if, das hier zuschlägt.

Ich habe natürlich viel mit der HttpClient gespielt und dort gibt es den Fehler beim Lesen des Streams. Und weil es diesen Fehler gibt, kommen wir in der GOV-Erweiterung in den else-Fall.
Hier die HttpClient:

<?php

/* Version 0.9, 6th April 2003 - Simon Willison ( http://simon.incutio.com/ )
   Manual: http://scripts.incutio.com/httpclient/
*/

class HttpClient {
    // Request vars
    var $host;
    var $port;
    var $path;
    var $method;
    var $postdata = '';
    var $cookies = array();
    var $referer;
    var $accept = 'text/xml,application/xml,application/xhtml+xml,text/html,text/plain,image/png,image/jpeg,image/gif,*/*';
    var $accept_encoding = 'gzip';
    var $accept_language = 'en-us';
    var $user_agent = 'Incutio HttpClient v0.9';
    // Options
    var $timeout = 20;
    var $use_gzip = true;
    var $persist_cookies = true;  // If true, received cookies are placed in the $this->cookies array ready for the next request
                                  // Note: This currently ignores the cookie path (and time) completely. Time is not important, 
                                  //       but path could possibly lead to security problems.
    var $persist_referers = true; // For each request, sends path of last request as referer
    var $debug = false;
    var $handle_redirects = true; // Auaomtically redirect if Location or URI header is found
    var $max_redirects = 5;
    var $headers_only = false;    // If true, stops receiving once headers have been read.
    // Basic authorization variables
    var $username;
    var $password;
    // Response vars
    var $status;
    var $headers = array();
    var $content = '';
    var $errormsg;
    // Tracker variables
    var $redirect_count = 0;
    var $cookie_host = '';
    function HttpClient($host, $port=80) {
    // Prüfen, ob Port 443 genutzt wird, um SSL automatisch zu aktivieren
		if ($port == 443) {
			$host = $host; // SSL aktivieren
		}
		$this->host = $host;
		$this->port = $port;
	}
    function get($path, $data = false) {
        $this->path = $path;
        $this->method = 'GET';
        if ($data) {
            $this->path .= '?'.$this->buildQueryString($data);
        }
        return $this->doRequest();
    }
    function post($path, $data) {
        $this->path = $path;
        $this->method = 'POST';
        $this->postdata = $this->buildQueryString($data);
    	return $this->doRequest();
    }
    function buildQueryString($data) {
        $querystring = '';
        if (is_array($data)) {
            // Change data in to postable data
    		foreach ($data as $key => $val) {
    			if (is_array($val)) {
    				foreach ($val as $val2) {
    					$querystring .= urlencode($key).'='.urlencode($val2).'&';
    				}
    			} else {
    				$querystring .= urlencode($key).'='.urlencode($val).'&';
    			}
    		}
    		$querystring = substr($querystring, 0, -1); // Eliminate unnecessary &
    	} else {
    	    $querystring = $data;
    	}
    	return $querystring;
    }
    function doRequest() {
        // Performs the actual HTTP request, returning true or false depending on outcome
		
		$protocol = ($this->port == 443) ? 'ssl://' : '';
		$fp = @fsockopen($protocol . $this->host, $this->port, $errno, $errstr, $this->timeout);
		if (!$fp) {
			echo "Fehler $errno: $errstr\n";
		}
		
		socket_set_timeout($fp, $this->timeout);
        $request = $this->buildRequest();
        $this->debug('Request', $request);
        fwrite($fp, $request);
		
		// Reset all the variables that should not persist between requests
    	$this->headers = array();
    	$this->content = '';
    	$this->errormsg = '';
    	
		// Set a couple of flags
    	$inHeaders = true;
    	$atStart = true;
    	
		// Now start reading back the response
    	while (!feof($fp)) {
    	    $line = fgets($fp, 4096);
			
			if ($line === false) {
				if (feof($fp)) {
					$this->debug("Ende des Streams erreicht.");
				} else {
					$this->errormsg = "Fehler beim Lesen der Zeile.";
					$this->debug($this->errormsg);
				}
				return false;
			} else {
				$this->debug("Gelese Zeile: " . htmlentities($line));
			}
			
			if ($atStart) {
    	        // Deal with first line of returned data
    	        $atStart = false;
    	        if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) {
    	            $this->errormsg = "Status code line invalid: ".htmlentities($line);
    	            $this->debug($this->errormsg);
    	            return false;
    	        }
    	        $http_version = $m[1]; // not used
    	        $this->status = $m[2];
    	        $status_string = $m[3]; // not used
    	        $this->debug(trim($line));
    	        continue;
    	    }
    	    if ($inHeaders) {
    	        if (trim($line) == '') {
    	            $inHeaders = false;
    	            $this->debug('Received Headers', $this->headers);
    	            if ($this->headers_only) {
    	                break; // Skip the rest of the input
    	            }
    	            continue;
    	        }
    	        if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) {
    	            // Skip to the next header
    	            continue;
    	        }
    	        $key = strtolower(trim($m[1]));
    	        $val = trim($m[2]);
    	        // Deal with the possibility of multiple headers of same name
    	        if (isset($this->headers[$key])) {
    	            if (is_array($this->headers[$key])) {
    	                $this->headers[$key][] = $val;
    	            } else {
    	                $this->headers[$key] = array($this->headers[$key], $val);
    	            }
    	        } else {
    	            $this->headers[$key] = $val;
    	        }
    	        continue;
    	    }
    	    // We're not in the headers, so append the line to the contents
    	    $this->content .= $line;
        }
        fclose($fp);
        // If data is compressed, uncompress it
        if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') {
            $this->debug('Content is gzip encoded, unzipping it');
            $this->content = substr($this->content, 10); // See http://www.php.net/manual/en/function.gzencode.php
            $this->content = gzinflate($this->content);
        }
        // If $persist_cookies, deal with any cookies
        if ($this->persist_cookies && isset($this->headers['set-cookie']) && $this->host == $this->cookie_host) {
            $cookies = $this->headers['set-cookie'];
            if (!is_array($cookies)) {
                $cookies = array($cookies);
            }
            foreach ($cookies as $cookie) {
                if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) {
                    $this->cookies[$m[1]] = $m[2];
                }
            }
            // Record domain of cookies for security reasons
            $this->cookie_host = $this->host;
        }
        // If $persist_referers, set the referer ready for the next request
        if ($this->persist_referers) {
            $this->debug('Persisting referer: '.$this->getRequestURL());
            $this->referer = $this->getRequestURL();
        }
        // Finally, if handle_redirects and a redirect is sent, do that
        if ($this->handle_redirects) {
            if (++$this->redirect_count >= $this->max_redirects) {
                $this->errormsg = 'Number of redirects exceeded maximum ('.$this->max_redirects.')';
                $this->debug($this->errormsg);
                $this->redirect_count = 0;
                return false;
            }
            $location = isset($this->headers['location']) ? $this->headers['location'] : '';
            $uri = isset($this->headers['uri']) ? $this->headers['uri'] : '';
            if ($location || $uri) {
                $url = parse_url($location.$uri);
                // This will FAIL if redirect is to a different site
                return $this->get($url['path']);
            }
        }
        return true;
    }
    function buildRequest() {
        $headers = array();
        $headers[] = "{$this->method} {$this->path} HTTP/1.1"; // Using 1.1 leads to all manner of problems, such as "chunked" encoding
        $headers[] = "Host: {$this->host}";
        $headers[] = "User-Agent: {$this->user_agent}";
        $headers[] = "Accept: {$this->accept}";
        if ($this->use_gzip) {
            $headers[] = "Accept-encoding: {$this->accept_encoding}";
        }
        $headers[] = "Accept-language: {$this->accept_language}";
        if ($this->referer) {
            $headers[] = "Referer: {$this->referer}";
        }
    	// Cookies
    	if ($this->cookies) {
    	    $cookie = 'Cookie: ';
    	    foreach ($this->cookies as $key => $value) {
    	        $cookie .= "$key=$value; ";
    	    }
    	    $headers[] = $cookie;
    	}
    	// Basic authentication
    	if ($this->username && $this->password) {
    	    $headers[] = 'Authorization: BASIC '.base64_encode($this->username.':'.$this->password);
    	}
    	// If this is a POST, set the content type and length
    	if ($this->postdata) {
    	    $headers[] = 'Content-Type: application/x-www-form-urlencoded';
    	    $headers[] = 'Content-Length: '.strlen($this->postdata);
    	}
    	$request = implode("\r\n", $headers)."\r\n\r\n".$this->postdata;
    	return $request;
    }
    function getStatus() {
        return $this->status;
    }
    function getContent() {
        return $this->content;
    }
    function getHeaders() {
        return $this->headers;
    }
    function getHeader($header) {
        $header = strtolower($header);
        if (isset($this->headers[$header])) {
            return $this->headers[$header];
        } else {
            return false;
        }
    }
    function getError() {
        return $this->errormsg;
    }
    function getCookies() {
        return $this->cookies;
    }
    function getRequestURL() {
        //$url = 'http://'.$this->host;
        $url = $this->host;
		if ($this->port != 80) {
            $url .= ':'.$this->port;
        }            
        $url .= $this->path;
        return $url;
    }
    // Setter methods
    function setUserAgent($string) {
        $this->user_agent = $string;
    }
    function setAuthorization($username, $password) {
        $this->username = $username;
        $this->password = $password;
    }
    function setCookies($array) {
        $this->cookies = $array;
    }
    // Option setting methods
    function useGzip($boolean) {
        $this->use_gzip = $boolean;
    }
    function setPersistCookies($boolean) {
        $this->persist_cookies = $boolean;
    }
    function setPersistReferers($boolean) {
        $this->persist_referers = $boolean;
    }
    function setHandleRedirects($boolean) {
        $this->handle_redirects = $boolean;
    }
    function setMaxRedirects($num) {
        $this->max_redirects = $num;
    }
    function setHeadersOnly($boolean) {
        $this->headers_only = $boolean;
    }
    function setDebug($boolean) {
        $this->debug = $boolean;
    }
    // "Quick" static methods
    function quickGet($url) {
        $bits = parse_url($url);
        $host = $bits['host'];
        $port = isset($bits['port']) ? $bits['port'] : 80;
        $path = isset($bits['path']) ? $bits['path'] : '/';
        if (isset($bits['query'])) {
            $path .= '?'.$bits['query'];
        }
        $client = new HttpClient($host, $port);
        if (!$client->get($path)) {
            return false;
        } else {
            return $client->getContent();
        }
    }
    function quickPost($url, $data) {
        $bits = parse_url($url);
        $host = $bits['host'];
        $port = isset($bits['port']) ? $bits['port'] : 80;
        $path = isset($bits['path']) ? $bits['path'] : '/';
        $client = new HttpClient($host, $port);
        if (!$client->post($path, $data)) {
            return false;
        } else {
            return $client->getContent();
        }
    }
    function debug($msg, $object = false) {
        if ($this->debug) {
            print '<div style="border: 1px solid red; padding: 0.5em; margin: 0.5em;"><strong>HttpClient Debug:</strong> '.$msg;
            if ($object) {
                ob_start();
        	    print_r($object);
        	    $content = htmlentities(ob_get_contents());
        	    ob_end_clean();
        	    print '<pre>'.$content.'</pre>';
        	}
        	print '</div>';
        }
    }   
}

?>

image

Ich nehme an Du hast schon die 4096 mal extrem hoch genommen oder ganz weg gelassen?