Peter Kleinhans: TTFB Messen

Hallo,

ich habe ein Anliegen, wo ich nicht weiterkomme. Für eine Schularbeit bzw. eine Projektarbeit sollen wir eine Webseite erstellen. Ich möchte eine Webseite erstellen, die den Time to first Byte misst.

So ählinch wie diese Seite hier: https://www.bytecheck.com/

Mir ist jetzt nicht ganz klar, wie die das machen. Linux kann ich einigermaßen und einen eigenen Webserver habe ich auch. Auch die Darstellung macht mir keine Probleme. Mir geht es darum, den Request-Response-Zyklus genau aufzudröseln in DNS-Lookup, Connect, Handshake, Wait (?) und Receive und was es da sonst noch alles gibt.

Wobei ich auch nicht genau weiß, was eigetnlich das Connect (um bei dem Beispiel von bytecheck zu bleiben), Send und Wait eigentlich genau sein soll.

mit curl sehe ich zwar, was da so passiert, aber leider keine Zeiträume zwischen den Aktionen.

Vielleicht könnte mir jemand hier freundlich sagen, wo ich das am besten nachlesen kann bzw. ob es dafür eine Art Tutorial gibt, um diesen Vorgang ganz genau zu verstehen.

Weiß man da als Profi denn alles so genau, oder ist das eher unnützes Wissen?

viele Grüße P.

  1. Hi,

    ich habe ein Anliegen, wo ich nicht weiterkomme. Für eine Schularbeit bzw. eine Projektarbeit sollen wir eine Webseite erstellen. Ich möchte eine Webseite erstellen, die den Time to first Byte misst.

    So ählinch wie diese Seite hier: https://www.bytecheck.com/

    ich kann natürlich deine Kenntnisse schwer einschätzen, aber ich denke, für ein Schulprojekt ist das um einige Nummern zu hoch angesetzt. Das ist eine technisch sehr anspruchsvolle Aufgabenstellung und hat mit der eigentlichen Aufgabe "ein Webseite erstellen" nur noch wenig zu tun. Es ist Softwareentwicklung auf Systemebene.

    Das ist etwa so, als solltet ihr im Kunst-Unterricht ein Portrait malen, und du fängst an, indem du ins Chemielabor gehst und dir die Farben aus verschiedenen Pigmenten, Lösungsmitteln und anderen Zusätzen selbst herstellst. Zweifellos interessant und anspruchsvoll, aber - Thema verfehlt.

    Mir ist jetzt nicht ganz klar, wie die das machen.

    Die müssen einen HTTP(S)-Client haben, der jeden einzelnen Schritt des HTTP-Requests mit einem exakten Timestamp protokolliert. So ähnlich tun das die Developer-Tools von z.B. Firefox wohl auch.

    Linux kann ich einigermaßen

    Was bedeutet das?

    Mir geht es darum, den Request-Response-Zyklus genau aufzudröseln in DNS-Lookup, Connect, Handshake, Wait (?) und Receive und was es da sonst noch alles gibt.

    Das heißt, du kannst eben keine fertige Bibliothek nehmen, sondern muss den Ablauf des HTTP-Requests komplett selbst implementieren. Und dabei dran denken, auch alle möglichen Fehler irgendwie zu behandeln.

    Wobei ich auch nicht genau weiß, was eigetnlich das Connect (um bei dem Beispiel von bytecheck zu bleiben), Send und Wait eigentlich genau sein soll.

    Connect: Die Socket-Verbindung ist aufgebaut.
    Send: Der Client sendet den HTTP-Request über die eben hergestellte Verbindung.
    Wait: Der Client wartet auf die Antwort des Servers.

    Weiß man da als Profi denn alles so genau, oder ist das eher unnützes Wissen?

    Kommt drauf an. Profi für welches Wissensgebiet? Als Web-Developer, der "nur" Webseiten erstellt, muss man das sicher nicht alles wissen. Als Software-Entwickler auf Anwendungsebene auch nicht unbedingt. Als Programmierer, der auch ab und zu mal Treiber für bestimmte Netzwerkprotokolle schreibt, aber schon.
    Als Autofahrer muss man schließlich auch nicht wissen, wie ein Automatik-Getriebe genau funktioniert. Nur wie man damit umgeht.

    Ciao,
     Martin

    --
    Ich stamme aus Ironien, einem Land am sarkastischen Ozean.
    1. Hallo Martin,

      Danke für deine Antwort. Ich bin 14 und kann Linux dementsprechend schlecht. Aber halt so das übliche (ssh, command line, git) und etwas mit dem System umgehen, also Environment-Variablen setzen, mounten und so Zeug halt. Ich bin sicherlich (noch) kein Hacker 😀

      Raketenbuch-Leser hat mir diverse Einstiegspunkte genannt, wie ich mit curl das Problem zu lösen habe, das finde ich schonmal richtig cool. Curl scheint mir doch um eniges mehr zu können, als ich dachte.

      mein nächstes Ziel ist ein Text-Adventure auf der Command Line mit Python, da bin ich schon recht weit und diverse Simulatoren für Physik und Mathe halt. Nichts besonderes bisher.

      Ich müsste mich erstmal mit Netzwerktechnik vertraut machen, habe ich das Gefühl, irgendwie habe ich noch diverse Lücken und kann das Große und Ganze nicht sehen.

  2. Linux kann ich einigermaßen

    Soso.

    mit curl sehe ich zwar, was da so passiert, aber leider keine Zeiträume zwischen den Aktionen.

    man curl zu lesen gehört zu „Linux kann ich“. Und es verhilft zu einer einfachen Lösung

    Datei: ./curl_outs

    time_namelookup:%{time_namelookup}\n
    time_pretransfer:%{time_pretransfer}\n
    time_redirect:%{time_redirect}\n
    time_redirect:%{time_total}\n
    

    Die Liste der (relevanten) Variablen:

    • time_appconnect: The time, in seconds, it took from the start untilthe SSL/SSH/etc connect/handshake to the remote host was completed. (Added in 7.19.0)

    • time_connect: The time, in seconds, it took from the start until the TCP connect to the remote host (or proxy) was completed.

    • time_namelookup: The time, in seconds, it took from the start until the name resolving was completed.

    • time_pretransfer: The time, in seconds, it took from the start until the file transfer was just about to begin. This includes all pre-transfer commands and negotiations that are specific to the particular protocol(s) involved.

    • time_redirect: The time, in seconds, it took for all redirection steps including name lookup, connect, pretransfer and transfer before the final transaction wasstarted. time_redirect shows the complete execution time for multiple redirections. (Added in 7.12.3)

    • time_starttransfer The time, in seconds, it took from the start until the first byte was just about to be transferred. This includes time_pretransfer and also the time the server needed to calculate the result.

    • time_total: The total time, in seconds, that the full operation lasted.

    LANG=C curl -I --write-out @./curl_outs https://www.example.com  2> /dev/null | grep -P '^time_'
    

    Den Befehl, und die URL sicher in PHP einbetten kannst Du?

    $sys = 'LANG=C curl -I --write-out @./curl_outs '
           . ecsapeshellarg( $URL ) 
           . ' 2> /dev/null | grep -P \'^time_\';'
         ;
    

    (Das hab ich „blind“ geschrieben.)

    Jetzt musst Du nur noch die Ausgaben auswerten...

    1. <?php
      if (! isset( $_GET['URL'] ) ) {
        $_GET['URL'] = 'https://www.example.com/';
      }
      
      $options = 'time_namelookup:%{time_namelookup}\n'
               . 'time_pretransfer:%{time_pretransfer}\n'
               . 'time_redirect:%{time_redirect}\n'
               . 'time_total:%{time_total}';
               
      $sys = 'LANG=C curl -I --write-out "'
             . $options . '" '
             . escapeshellarg( trim ( $_GET['URL'] ) )
             . ' 2> /dev/null | grep -P \'^time_\';'
           ;
      
      $ret = `$sys`;
      
      $ar = explode( "\n", $ret );
      $erg = [];
      foreach ( $ar as $row ) {
      	$row = trim($row);
      	if ( $row ) {
      		list( $name, $value ) = explode( ':', $row, 2);
      		$erg[$name] = $value;
      	}
      }
      
      print_r( $erg );
      
      Array
      (
          [time_namelookup] => 0.004270
          [time_pretransfer] => 0.458685
          [time_redirect] => 0.000000
          [time_total] => 0.604829
      )
      
      1. Das gezeigte Skript vermeidet zwar (viele) Angriffe auf den lokalen Rechner (DDoS wäre noch möglich...) aber

        Wenn Du das Skript verwendest, dann solltest Du

        1. Einen serverseitigen Cache vorsehen, um ggf. Missbrauch(DDoS zu vermeiden.
        2. Verhindern, dass ungültige URLs (Protokolle. Hostnamen) eingegeben werden.
        3. URL-Parameter (alles hinter dem ersten Fragezeichen) sollten blockiert werden. Ansonsten könnte das böse Jungs auf die Idee bringen, den Dienst für Angriffe auf Dritte zu missbrauchen und also die Herkunft des Angriffs zu verschleiern.
        • Ich würde URL-Parameter also nur angemeldeten und mir bekannten Benutzern erlauben.
        <?php
        
        define ( 'ALLOW_PARAMS', false );
        define (
        	'options',
        [
        	'content_type',
        	'http_code',
        	'http_connect',
        	'http_version',
        	'num_connects',
        	'num_redirects',
        	'redirect_url',
        	'remote_ip',
        	'remote_port',
        	'scheme',
        	'size_download',
        	'size_header',
        	'size_request',
        	'speed_download',
        	'time_appconnect',
        	'time_connect',
        	'time_namelookup',
        	'time_pretransfer',
        	'time_redirect',
        	'time_starttransfer',
        	'time_total',
        	'url_effective'
        ]
        );
        
        # Für Tests in der Konsole:
        if ( ! isset( $_GET['URL'] ) ) {
        	$_GET['URL'] = 'https://www.example.com/';
          #$_GET['URL'] = 'https://www.%example.com/';
          #$_GET['URL'] = 'https://www.example.com/?foo=bar';
        } 
        
        @list( $REST, $DELETED ) = explode('#', trim( $_GET['URL'], 2 ) );
        @list( $REST, $GET_PARAMS ) = explode('?', trim( $REST, 2 ) );
        $REST = strtolower( $REST );
        @list( $PROTO, $REST ) = explode( '://', $REST, 2 );
        @list( $HOST, $RESSOURCE ) = explode( '/', $REST, 2 );
        $t = preg_replace( '/[^\p{L}\p{N}\._-]/', '', $HOST );
        if ( $t != $HOST )  {
        	http_response_code ( 403 );
        	echo '<h1>Nice Try</h1><hr>';
        	trigger_error('Nicht erlaubtes Zeichen in URL: "' . $_GET['URL'] . '"', E_USER_ERROR );
        }
        
        if (
        	'http' != $PROTO 
        and 'https' != $PROTO
        ) {
        	http_response_code ( 404 );
        	echo '<h1>Falsches Protokoll</h1><hr><p>Gehen Sie zurück und geben Sie "http://" oder "https://" als Protokoll an.</p>';
        	exit;
        }
        
        if ( 
        	! ALLOW_PARAMS
        and $GET_PARAMS 
        ) {
        	http_response_code ( 403 );
        	echo '<h1>Verboten: URL enthält Parameter</h1><hr><p>Bitte gehen Sie zurück und geben Sie eine URL ohne Parameter an.</p>';
        	exit;		
        }
        
        if ( false == dns_get_record( $HOST ) ) {
        	http_response_code ( 404 );
        	echo '<h1>Hostname unbekannt</h1><hr><p>Für den Hostname konnte kein DNS-Rekord ermittelt werden.</p>';
        	exit;	
        }
        
        $_GET['URL'] = $PROTO . '://' . $HOST . '/' . $RESSOURCE;	
        
        if (
        	ALLOW_PARAMS 
        and isset( $GET_PARAMS )
        and $GET_PARAMS
        ) {
        	$_GET['URL'] = $_GET['URL'] . '?' . $GET_PARAMS;
        } 
        
        $curl_options = [];
        $grepHelper = '___THIS_IS_ONLY_FOR_GREP___';
        
        foreach ( options as $s ) {
        	$curl_options[] = $grepHelper . $s . ':%{' . $s. '}';
        }
        $curl_options = implode('\n', $curl_options );
                 
        $sys = 'LANG=C curl -s -I --write-out "'
               . $curl_options . '" '
               . escapeshellarg( $_GET['URL'] ) 
               . ' 2> /dev/null | grep "^' . $grepHelper . '";'
             ;
        $ret = `$sys`;
        $ar = explode( "\n", $ret );
        $erg = [];
        foreach ( $ar as $row ) {
        	$row = trim( str_replace( $grepHelper, '', $row ) );
        	if ( $row ) {
        		list( $name, $value ) = explode( ':', $row, 2);
        		$erg[$name] = $value;
        	}
        }
        
        print_r( $erg );
        
        1. Da ich nicht wissen soll, was davon schlecht war, habe ich das mal verbessert und eine vollständige Testversion geschrieben.

          Wenn Fehler bzw. keine Ausgaben kommen, dann arbeite ich gerade daran.

          1. Da ich nicht wissen soll, was davon schlecht war, habe ich das mal verbessert und eine vollständige Testversion geschrieben.

            Da ich nicht wissen soll, was davon schlecht war, hab ich, (m)einer Laune folgend, vor allem auch den Quelltext NICHT veröffentlicht.

            1. Da ich nicht wissen soll, was davon schlecht war, hab ich, (m)einer Laune folgend, vor allem auch den Quelltext NICHT veröffentlicht.

              Sehr gut.

              MFG

              1. Da ich nicht wissen soll, was davon schlecht war, hab ich, (m)einer Laune folgend, vor allem auch den Quelltext NICHT veröffentlicht.

                Sehr gut.

                Gefällt Dir, was? Steht ja alles auf der Seite. LOL.

                1. Da ich nicht wissen soll, was davon schlecht war, hab ich, (m)einer Laune folgend, vor allem auch den Quelltext NICHT veröffentlicht.

                  Sehr gut.

                  Gefällt Dir, was? Steht ja alles auf der Seite.

                  Fenau genommen genau an dieser Stelle.

    2. Hallo,

      ja danke, mir hätte schon die Info gereicht, dass es mit curl geht. Ich hatte da wohl falsch gesucht, vieles ist mir natürlich noch nicht klar. Ich will es ja lernen und nicht nur abschreiben.

      von daher schaue ich mir dein Ergebnis jetzt auch erstmal nicht weiter an, aber vielleicht ist es ja für andere interessant. Abgesehen davon kann ich auch kein PHP, wir nutzen eine andere serverseitige Sprache.

      Trotzdem echt vielen Dank für deine Mühen und falls ich nicht weiterkomme, werde ich mir das mal zu Gemüte führen.

      1. von daher schaue ich mir dein Ergebnis jetzt auch erstmal nicht weiter an, aber vielleicht ist es ja für andere interessant. Abgesehen davon kann ich auch kein PHP, wir nutzen eine andere serverseitige Sprache.

        Das solltest Du dennoch tun. Immerhin soll es ja eine Webseite werden. Wie alle Programmiersprachen ist auch PHP nur "englisch für arme" (ich empfinde es als „leicht lesbar“) und mein Code (besonders der zweite) lässt auf so manches Sicherheitsproblem und dessen Lösungsmöglichkeit schließen.

      2. n'Abend,

        ja danke, mir hätte schon die Info gereicht, dass es mit curl geht.

        das wusste ich selbst bisher auch nicht.

        von daher schaue ich mir dein Ergebnis jetzt auch erstmal nicht weiter an, aber vielleicht ist es ja für andere interessant.

        Nein, es sollte vor allem dir sagen, dass dein gewähltes Projekt sicher spannend und interessant ist, aber wahrscheinlich nicht wirklich zur Aufgabe passt.

        Abgesehen davon kann ich auch kein PHP, wir nutzen eine andere serverseitige Sprache.

        Ja, auch Python ist interessant und sehr vielseitig, im Web-Umfeld aber bisher relativ selten. Da ist IMO PHP noch eine der populärsten Scriptsprachen (nicht dass ich PHP überhaupt erwähnt hätte).

        Trotzdem echt vielen Dank für deine Mühen und falls ich nicht weiterkomme, werde ich mir das mal zu Gemüte führen.

        Wenn du meinst ...

        So long,
         Martin

        --
        Ich stamme aus Ironien, einem Land am sarkastischen Ozean.
  3. Ich hab das erstmal „als technische Idee fertig“ - und die Aufgabe hat sich (in dem Fall erwartungsgemäß) als nicht trivial erwiesen.

    Da entstehen nämlich - jenseits des User-Interfaces (Spielkram auf der Webseite) - Nebenaufgaben:

    • Absicherung dagegen, dass Programm selbst angegriffen wird. Unter anderem wird ja ein Shell-Befehl ausgeführt. Dedlfix hat dazu viel gutes geschrieben, heutiger Zustand.
    • Absicherung dagegen, dass das Programm bzw. der Dienst für DoS-Attacken missbraucht wird. (Cachen!)
    • Absicherung dagegen, dass das Programm bzw. der Dienst für Login-Atacken missbraucht wird. (Filtern)
    • Absicherung dagegen, dass das Programm bzw. der Dienst für Attacken auf Sitzungen pder API-Keys missbraucht wird. (Filtern)
    • Absicherung dagegen, dass möglicherweise schlechter gesicherte, nur (Netzwerk-)lokale Server (man denke an CUPS auf localhost Port 631, Cockpit auf Port 9090, swat auf Port 901, die Fritzbox,…) über das Programm bzw. den Dienst adressiert werden. (Filtern)
    • Beachtung von Umlaut- (IDN-) Domains.