Von HTTP auf HTTPS via Apache mittels .htaccess oder nginx-Webserver-Konfiguration weiterleiten

Eine verschlüsselte Verbindung über SSL (bzw. TLS) ist immer eine gute Idee um sensible Daten gegen Dritte zu schützen. Aber was bringt es wenn das SSL-Zertifikat im Webhosting-Paket installiert und die Verschlüsselung aktiviert ist, aber die Webseite trotzdem noch per HTTP erreichbar ist? Sämtlicher Verkehr über HTTP muss auf die HTTPS-Verbindung weitergeleitet werden.

Voraussetzung: Die Apache-Erweiterung mod_rewrite ist aktiviert und Webserver bereits via HTTP und HTTPS erreichbar. Beim Webserver nginx muss nur die Konfiguration veränderbar sein.

HTTP auf HTTPS weiterleiten via .htaccess

Die .htaccess-Datei liegt in der Regel im root-Verzeichnis der Webseite. In dieser Konfiguration werden sämtliche Verbindungen, die per HTTP auf den Server kommen, auf HTTPS weitergeleitet. Für den Endnutzer bleibt dieser Vorgang weitestgehend unsichtbar.

RewriteEngine On<br>
RewriteCond %{SERVER_PORT} !^443$<br>
RewriteRule (.*) https://%{HTTP_HOST}/$1 [R=301,L]<br>

Zunächst (Zeile 1) wird mod_rewrite aktiviert (das ist nicht erforderlich, wenn ohnehin schon getan). In Zeile 2 wird überprüft (in der Rewirte Condition) ob die Anfrage an den Server über den Port 443 (also den Standard-Port von HTTPS) kam. Wenn dies nicht der Fall ist, führt er die Regel aus Zeile 3 aus. Diese leitet sämtliche Anfragen via Statuscode 301 (permanent redirect) auf HTTPS um.

Das ist eine von verschiedenen Möglichkeiten um die Weiterleitung von HTTP auf HTTPS zu realisieren, eine andere Möglichkeit ist:

RewriteEngine On<br>
RewriteCond %{HTTPS} off<br>
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]<br>

Das Ergebnis ist das gleiche, nur der Weg ist ein minimal anderer: Es wird explizit geprüft ob eine HTTPS-Verbindung genutzt wird, ist dies nicht der Fall, so wird weitergeleitet.

HTTP auf HTTPS durch nginx-Konfiguration

Nginx (gesprochen „engine x“) ist ein alternativer Webserver, der im Vergleich zum Marktführer Apache in der Regel schneller ist. Daher hier noch die Konfiguration, wie das gleiche über die Konfiguration des nginx-Server (bzw. im Plesk-Konfigurationsbereich) gelöst wurde:

if ($scheme !~* ^https ){<br>
    rewrite ^ https://www.coding-pioneers.com$request_uri? permanent;<br>
}<br>

Warum den Statuscode 301 und nicht 302?

Im Standard wird in der RewriteRule mit R (Redirect) die Weiterleitung spezifiziert. Diese liefert (wenn man keinen abweichenden Status angibt) den HTTP-Statuscode 302 (found), also eine temporäre Weiterleitung zurück. Es ist zu empfehlen den Statuscode 301 (permanent redirect) zu nutzen, um auch im Bereich SEO keine Nachteile zu erhalten.

Umlaut-Domain per mod_rewrite umleiten

Um im Netz keinen Dublicate Content zu erzeugen, leiten wir auf unseren Apache-Webservern mittels mod_rewrite die verschiedenen Domains immer auf eine URL zusammen.

Unter Apache mit mod_rewrite funktioniert das wie folgt:

RewriteCond %{HTTP_HOST} ^(www\.)?domain1\.de$ [NC,OR]<br>
RewriteCond %{HTTP_HOST} ^(www\.)?domain2\.de$ [NC,OR]<br>
RewriteCond %{HTTP_HOST} ^(www\.)?domain3\.de$ [NC,OR]<br>
RewriteCond %{HTTP_HOST} ^domain0\.de$ [NC]<br>
RewriteRule (.*) http://www.domain0.de/$1 [R=301,L]

Dabei werden alle Domains auf domain0.de weitergeleitet.

Bei den Rewrite Conditions kännen sog. Flags angehängt werden, die in eckigen Klammern geschrieben werden:

’nocase|NC‘ (no case)

Dieses Pattern macht den Test case-insensitive also er differenziert nicht mehr zwischen Groß- und Kleinschreibung: ‚A-Z‘ und ‚a-z‘ werden gleich behandeltd im Test-String und dem Cond-Pattern.

‚ornext|OR‘ (or next condition)

Dieses Flag kann genutzt werden, um die Regeln mit einem oder zu kombinieren. In der Regel würden diese mit einem AND verknüpft

Als Sonderfall haben wir nun noch Domains mit Umlauten, die weitergeleitet werden sollten. Im Web findet man häufig Referenzen, dass die Umlaute direkt eingegeben werden könnten, bei uns hat das nicht funktioniert. Es musste nicht die native Form des IDNs (sog. U-Label), sondern die ASCII-Darstellung (sog. ACE-Form) genutzt werden.

Beispiel:
IDN-Form: müll.de
ACE-String: xn--mll-hoa.de (Puny-Code)

Die Konvertierung erfolgt dabei nach dem sog. IDNA-Standard (RFCs 5890 bis 5894), mit Unterstützung für Unicode 5.2 und mit Hilfe des IDNA Compatibility Processing im „nontransitional“ Modus aus dem Unicode Technical Standard #46.

Android: Implementierung In-App-Käufe

Der Google Play Store stellt Android-Nutzer rund 700.000 Anwendungen zum download bereit. Die Anwendungen können dort kostenfrei oder gegen ein kleines Entgeld gekauft und installiert werden. Damit eine kostenfreie Anwendung für den Entwickler gewinnbringend ist, wird oft mit der Hilfe von Google-Adsense Werbung in die Applikation eingefügt. Durch die vom Google Play Store zur Verfügung gestellten In-App-Käufe kann der Entwickler dem Benutzer ermöglichen die oft lästige Werbung durch den Erwerb einer Premium-Version zu entfernen. Des Weiteren können die Anwendungen durch In-App-Käufe (z.B. bei einem Spiel zusätzlich Leben zu erwerben) dem Entwickler einen zusätzlichen Gewinn bringen.

Die Implementierung von In-App-Käufen ist in erster Linie recht simpel, wenn bestimmte Punkte eingehalten werden. Hält man sich nicht an diese Punkte und berücksichtigt bestimmte Tatsache nicht, kann die Implementierung von In-App-Käufen gerne sehr viel Zeit kosten. Ich selbst habe dies im Rahmen eines Kundenprojekts erfahren und möchte an dieser Stelle mit diesem Blogbeitrag andere Entwickler davor bewahren in die gleichen Fallen zu tappen.

Anforderungen

  • Google Developer Konto
  • Google Play Konto (Darf nicht identisch mit dem Google Developer Konto sein).

Bibliothek in das Projekt einbinden

Um den Billing-Service von Google in einem Projekt verfügbar zu machen, muss die Biliothek zunächst mit Hilfe des Android-SDK-Managers herrunter geladen werden. Das Paket nennt sich Google Play Billing Library und befindet sich unter dem Punkt „Extras“.

Die Datei IInAppBillingService.aidl befindet sich nach dem Download in folgendem Verzeichnis:

 <sdk>/extras/google/play_billing/

Die Datei „IInAppBillingService.aidl“ muss jetzt dem Projekt hinzugefügt werden, wobei die Datei unter Eclipse in dem Projekt-Pfad /src in das Package com.android.vending.billing kopiert wird. Ist das Package nicht vorhanden, muss dieses erstellt werden. Unter einem Nicht-Eclipse-Projekt wird die Datei in das Verzeichnis /src/com/android/vending/billing kopiert. Auch hier müssen die Ordner ggf. erstellt werden. Nachdem die Datei kopiert wurde, muss der Build-Vorgang des Projekts einmal druchgeführt werden. Als Nächstes muss dem Projekt die Berechtigung zur Verwendung der IInAppBillingService.aidl zugeteilt werden. Dazu wird in der AndroidManifest.xml folgende Zeile eingefügt:

 <uses-permission android:name="com.android.vending.BILLING" />

Das Projekt ist jetzt bereit einen Request an den Google Play Store zu senden und eine Antwort zu erhalten.

Hilfsklassen

Das Beispiel-Projekt der Google Play Billing Library verfügt über Hilfsklassen, welche uns die Implementierung der In-App-Käufe erleichtern. Die 9 Klassen, welche unter folgendem Pfad zu finden sind, kopieren wir in unser Projekt in ein dafür angelegtes Package.

 PFAD_SDK/extras/google/play_billing/samples/TrivialDrive/src/com/xample/android/trivialdrivesample/util

Anlegen der Produkte

Damit die Produkte gekauft werden können, müssen diese zunächst der Anwendung in der Google Developer Konsole hinzugefügt werden. Dazu muss die Anwendung in dem Developer Konto erstellt werden (falls noch nicht geschehen). Nach der Auswahl der Anwendung können unter dem Punkt In-App-Produkte neue Produkte für die Anwendung erstellt werden. Bei der Vergabe der Produkt-ID ist darauf zu achten, das diese zu einem späteren Zeitpunkt nicht mehr geändert werden kann. In der API Version 3 stellt Google nur noch ein verwaltetes Produkt und ein Abo zur Verfügung. Bei einem Abo handelt es sich um eine automatisch wiederkehrende Zahlung, welche für normale In-App-Produkte wie z.B. eine Premium-Version oder zusätzliche Leben nicht in Frage kommt. Die API Version 3 unterscheidet bei verwaltet Produkten in verbrauche und nicht verbrauchbare Produkte. Nicht verbrauchbare Produkte können nur einmalig gekauft werden, wobei dessen Erwerb im Google Play Store Konto gespeichert wird. Verbrauchbare Produkte verhalten sich im ersten Moment wie ein Nicht-Verbrauchbares-Produkt bis zu dem Zeitpunkt wo sie verbraucht werden. Nachdem sie verbraucht wurden können die verbrauchbaren Produkte erneut gekauft werden. Wird ein Produkt verbraucht, kann der Erwerb dieses Produkts jedoch nicht mehr nachgewiesen werden. Über diesen Fakt sollte man sich an dieser Stelle im Klaren sein und seine Anwendung dementsprechend anpassen, da Produkte, welche der Benutzer gekauft und nicht verbraucht hat, nach einer Neuinstallation oder Installation auf einem anderen Device wieder zur Verfügung gestellt werden müssen.

Testzugriff auf das Developer Konto

Um über ein Google-Play-Konto den Erwerb der erstellten Produkte zu testen, muss diesem Google-Play-Konto Testzugriff auf das Developer Konto gewährt werden. Die Email-Adresse des für den Testzugang kann im Google Developer Konto unter EinstellungenGmail-Konten mit Testzugriff eingegeben werden.

Billing Service Klasse erstellen

Zurück in der Anwendung erstellen wir eine Klasse (Im folgenden Beispiel MyBillingService, welche die Schnittstelle zum Google Play Store darstellt.

Wichtig<br> Produkt (SKU) Informationen können nur von bereits gekauften Produkte angefragt werden. Da bis dato keine Produkte gekauft wurden erhält man die Information:<br><br> Querying SKU details.<br> queryPrices: nothing to do because there are no SKUs.<br><br> Produkt-Informationen können erst nach dem Erwerb gelistet werden.

In der folgenden Klasse sind notwendige Anpassungen durch Kommentare gekennzeichnet.

public class MyBillingService {
  // Debug-Tag for logging   public static final String TAG = MyBillingService.class.getSimpleName();   //Konstante, um festzulegen ob die gekauften Produkte des Benutzers beim ersten
  //Start der Anwendung abgefragt wurden   public static final String KEY_CHECK_ITEMS = "check_items";
  //Konstante zum Lesen und Speichern via SharedPreferences   public static final String KEY_LIFE_COUNT = "life_count";   private static MyBillingService instance = null;
  private Activity activity;
  // SKUs der Produkte im App-Store definieren (ID)   public static final String SKU_ONE = "ID Produkt 1";   public static final String SKU_TWO = "ID Produkt 2";

  //(arbitrary) Request Code
  static final int RC_REQUEST = 10001;
  //Status der Produkte   //Jedes Produkt besitzt den Status gekauft oder nicht gekauft   private boolean skuOne;   private boolean skuTwo;
  private HashMap<String, Boolean> status;

  // Atrribut, welches durch In-App-Produkte beeinlusst wird.
  // Zum Beispiel Anzahl der Leben in einem Spiel   int maxLife;
  // Helper Objekt
  private IabHelper mHelper;

  public static MyBillingService getInstance(Activity activity) {
    if(instance == null) {
      instance = new MyBillingService(activity);
    }
    return instance;
  }

  private MyBillingService(Activity activity) {

    //Status setzen     this.activity = activity;
    status = new HashMap<>();
    status.put(SKU_ONE, false);
    status.put(SKU_TWO, false);

    //Muss auf das jeweilige Projekt angepasst werden.
    maxLife = Integer.valueOf(MyApplication.getInstance().readFromPreferences(KEY_LIFE_COUNT, "2"));

    //Befindet sich im Developer Konto nach der Auswahl der Anwendung unter Dienste & APIs
    String base64EncodedPublicKey = "API KEY HIER EINFÜGEN";

    // Erstellen des Helpers
    Log.d(TAG, "Creating IAB helper.");
    mHelper = new IabHelper(this.activity, base64EncodedPublicKey);

    //Schaltet das Debuggen ein. In einer Release-Version sollte hier false übergeben werden.
    mHelper.enableDebugLogging(true);

    Log.d(TAG, "Starting setup.");
    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
      public void onIabSetupFinished(IabResult result) {
        Log.d(TAG, "Setup finished.");

        if (!result.isSuccess()) {
          // Problemfall
          complain("Problem setting up in-app billing: " + result);
          return;
        }

        // Wurde das Helper-Objekt bereits zerstört, beenden
        if (mHelper == null) return;

        // IAB wurde initialisiert. Anfragen welche Produkte der Benutzer besitzt.
        Log.d(TAG, "Setup successful. Querying inventory.");
        mHelper.queryInventoryAsync(mGotInventoryListener);
      }
    });
  }


  // Listener, welcher ausgerufen wird, wenn die Liste mit gekauften Produkten erfolgreich angefragt wurde
  private IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
      Log.d(TAG, "Query inventory finished.");

      if (mHelper == null) return;

      // Meldung im Fehlerfall
      if (result.isFailure()) {
        complain("Failed to query inventory: " + result);
        return;
      }

      Log.d(TAG, "Query inventory was successful.");

      // Welche Produkte hat der Benutzer bereits gekauft?

      Purchase purchase;
      for(Map.Entry<String, Boolean> product: status.entrySet()) {
        purchase = inventory.getPurchase(product.getKey());
        status.put(product.getKey(), (purchase != null && verifyDeveloperPayload(purchase)));
        Log.d(TAG, "User has " + product.getKey() + (product.getValue() ? " bought" : " not bought"));
      }

      //Muss angepasst werden: Beim ersten Start der Anwendung       //müssen alle gekauften Artikel mit ihren Auswirkungen berücksichtigt werden
      if(!Boolean.valueOf(MyApplication.getInstance().readFromPreferences(KEY_CHECK_ITEMS, "false")))       {       for(Map.Entry product: status.entrySet()) {         switch (product.getKey()) {           case SKU_ONE:             if(product.getValue()) {               //TODO             }         }       }       saveData();       Log.i(TAG, "Initialzie in-app-purchase");       MyApplication.getInstance().saveToPreferences(KEY_CHECK_ITEMS, String.valueOf(true));       }

      Log.d(TAG, "Initial inventory query finished; enabling main UI.");
    }
  };

  public HashMap getStatus() {
    return status;
  }

  public void buyProdukt(String id) {
    Log.d(TAG, "Buy Produkt " + id);

    if (status.get(id)) {
      complain("Product already bought!");
      return;
    }

    Log.d(TAG, "Launching purchase flow for: " + id);

    /* TODO: for security, generate your payload here for verification. See the comments on
    * verifyDeveloperPayload() for more info. Since this is a SAMPLE, we just use
    * an empty string, but on a production app you should carefully generate this. */
    String payload = "";

    mHelper.launchPurchaseFlow(activity, id, RC_REQUEST,
    mPurchaseFinishedListener, payload);
  }

  //Funktion muss in der Activity aufgerufen werden in der die Klasse zuerst aufgerufen wird.
  public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
    if (mHelper == null) return false;

    if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
      return false;
    }
    else {
      Log.d(TAG, "onActivityResult handled by IABUtil.");
      return true;
    }
  }

  /** Verifizieren das es sich um einen gültigen Kauf handelt */
  boolean verifyDeveloperPayload(Purchase p) {
    String payload = p.getDeveloperPayload();

    /*
    * TODO: An dieser Stelle muss verifiziert werden, dass es sich um einen gültigen Kauf handelt.     * Der String (getDeveloperPayload) ist der gleiche, welcher beim Kauf übergeben wird.     *     * Einen zufälligen String zur Laufzeit zu generieren ist hierbei wenig zielführen,     * da sichergestellt werden muss, dass der Kauf von einem Gerät durchgeführt werden     * kann und durch ein andere verifiziert werden kann. Zu diesem Punkt wird bald ein     * weiterer Blogbeitrag folgen. Für Testzwecke reicht hier die Rückgabe von true aus     *     * Vor der Veröffentlichung im App-Store sollte diese Funktion auf jeden Fall implementiert werden     */

    return true;
  }
  // Listener, welcher aufgerufen wird, wenn der Kauf erfolgreich war
  private IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
    public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
      Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
      // if we were disposed of in the meantime, quit.
      if (mHelper == null) return;

      if (result.isFailure()) {
        //complain("Error purchasing: " + result);
        return;
      }
      if (!verifyDeveloperPayload(purchase)) {
        complain("Error purchasing. Authenticity verification failed.");
        return;
      }

      //Was soll passieren, wenn der Benutzer ein Produkt erworben hat?
      switch (purchase.getSku()) {       case SKU_ONE:         //TODO         saveData();         break;       }

      saveData();
      Log.d(TAG, "Purchase successful. Bought " + purchase.getSku());

      //Handelt es sich bei SKU ONE um ein verbrauchbaren Produkt?       //Wenn ja, dann dies an dieser Stelle verbraucht werden damit der       //Benutzer das Produkt erneut kaufen kann
      if (purchase.getSku().equals(SKU_ONE)) {
        Log.d(TAG, "Purchase is sku one. Starting sku one consumption.");
        mHelper.consumeAsync(purchase, mConsumeFinishedListener);
      }
    }
  };

  // Wird aufgerufen, sobald der Verbrauch eines Produkts erfolgreich war.
  private IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
    public void onConsumeFinished(Purchase purchase, IabResult result) {
      Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);

      if (mHelper == null) return;

      // An dieser Stelle wissen wir, dass es sich um SKU_ONE handelt
      // da nur dieses Produkt verbraucht werden kann.
      // Sind mehere verbrauchbare Produkte vorhanden, muss dies an dieser Stelle überprüft werden
      if (result.isSuccess()) {
        // Was soll gesehen, wenn der Benutzer SKU_ONE verbraucht hat?
        Log.d(TAG, "Consumption successful. Provisioning.");
        //TODO
        saveData();
      }
      else {
        complain("Error while consuming: " + result);
      }
      Log.d(TAG, "End consumption flow.");
    }
  };

  // Methode muss in der StartActivity aufgerufen werden   public void destroy() {
    // Sehr wichtig:     Log.d(TAG, "Destroying helper.");     if (mHelper != null) {       mHelper.dispose();       mHelper = null;     }   }

  void complain(String message) {
    Log.e(TAG, "**** TrivialDrive Error: " + message);
    alert("Fehler: " + message);
  }

  void alert(String message) {
    AlertDialog.Builder bld = new AlertDialog.Builder(activity);
    bld.setMessage(message);
    bld.setNeutralButton("OK", null);
    Log.d(TAG, "Showing alert dialog: " + message);
    bld.create().show();
  }

  void saveData() {

    /*     * Speichern der Daten in der Applikation     * Eine einfache Möglcihkeit ist die Verwendung von SharedPreferences.     * Bei einer veröffentlichten App sollten diese Daten verschlüsselt werden,     * da sonst unbefugter Zugriff möglich ist.     * Mehr dazu in den Anmerkungen.     */
  }
}

Mit der Anwendung verknüpfen

Die zuvor erstellte Klasse muss in der MainActivity der Anwendung initialisiert werden. Dazu wird die folgende Codezeile im Konstruktor verwendet:

  billing = MyBillingService.getInstance(this);

Die Methoden unserer Billing Klasse onDestroy() und onActivityResult() müssen jetzt mit der MainActivity verknüpft werden. Dazu wird folgender Code in die MainActivity des Projekts eingefügt.

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {     Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);     if(!billing.onActivityResult(requestCode, resultCode, data)) {       super.onActivityResult(requestCode, resultCode, data);     }     else {       Log.d(TAG, "onActivityResult handled by IABUtil.");     }   }
  @Overridee   protected void onDestroy() {     billing.destroy();     super.onDestroy();   }

Unsere Billing Klasse ist nun mit der Anwendung verknüpft und kann in jedem Fragment aufgerufen werden.

Produkte kaufen

Produkte können nun in einem dafür zuständigen Fragment gekauft werden. Beispielweise kann der Erwerb des ersten Produkts durch folgende Codezeile ausgelöst werden.

  MyBillingService.getInstance(getActivity()).buyProdukt(MyBillingService.SKU_ONE);

Testen wir diesen Vorgang nun in unserer lokalen Testumgebung wird die Anfrage der Produkte fehlschlagen und wir erhalten folgende Meldung.

Dieser Fehler ist darauf zurück zu führen das es sich bei der APK, welche den App-Store nach Podukten anfragt, nicht von uns signiert wurde.

APK signieren

Die Signierung einer APK erfolgt unter Android Studio problemlos unter dem Menüpunkt Build und Generate signed APK. Die Generierung einer signierten APK ist auf der Google Developer Seite ausführlich beschrieben.

Hochladen der APK

Die generierte APK kann jetzt im Google Developer Konto in die Alpha- oder Beta-Phase hochgeladen werden. Wichtig dabei ist, das diese veröffentlicht und nicht nur als Entwurf gespeichert wird. Nach dem Hochladen der APK dauert es in der Regel 2-3 Stunden bis diese den Testern zur Verfügung gestellt wird. Auf dem aktuellen Stand haben wir jedoch noch keinen Alpha- Oder Beta-Test eingerichtet.

Alpha- oder Beta-Test einrichten

Damit unsere APK an Testnutzer ausgeliefert werden kann muss ein Alpha- oder Beta-Test eingerichtet werden. Die Email-Adresse unseres Google Play Kontos muss in die Liste der Tester eingetragen werden. Testbenutzer müssen die Einladung zum Alpha- oder Beta-Test über den unten stehenden Link annehmen. Direkt nach dem Hochladen der APK ist dieser Link ungütlig und erhält nach 2-3 Stunden Gültigkeit. Weitere Informationen zum Testen von APKs befinden sich auf der Google Developer Seite.

Wichtig<br> Es muss bereits eine aktive Version der Anwendung im Release sein, da die In-App-Produkte sonst nicht aktiviert werden und diese nicht gekauft werden können. Daher empfiehlt es sich zunächst eine Version ohne In-App-Käufe zu veröffentlichen und die In-App-Käufe dann in der nächsten Version zu implementierten.

Ist bereits eine Version der Anwendung im Release wird nach 2-3 Stunden ein Update an die Benutzer, welche sich in der Liste des Alpha- oder Beta-Tests befinden, ausgeliefert. Die APK der Alpha oder Beta-Phase muss eine höhere Versionsnummer als die Release Version haben, da immer die Version mit der höchsten Versions-Nummer ausgeliefert wird.


Testen der In-App-Käufe

Nachdem die neue Version auf den Devices des Testbenutzers aufgespielt wurde, kann der In-App-Kauf getestet werden. Da wir unser Testkonto zuvor mit Testzugriff versehen haben, wird uns für den Erwerb der Produkte kein Geld in Rechnung gestellt.

Anmerkungen

  • Produkt-Informationen können erst nach dem Erwerb gelistet werden
  • Kauf der Produkte ist nur über eine signierte APK möglich
  • Werden die Informationen über In-App-Käufe via Applikation-Preferences in der Anwendung gespeichert sollten diese verschlüsselt werden. Mehr dazu.
  • API-Key sollte aus Sicherheitsgründen zur Laufzeit zusammengesetzt werden
  • Produkte werden erst nach dem Release der Anwendung aktiviert

Abschluss

Wie zu sehen ist, ist die Implementierung von In-App-Käufen kein Hexenwerk, solange bestimmte Punkte beachtet werden. Bei Fehlern, wie zum Beispiel das Anfragen von Produktinformationen von nicht erworbenen Produkten, hält man sich unter Umständen sehr lange auf, wenn man über bestimmte Fakten nicht Bescheid weiß.

Quellen

Tutorial: Einführung in die Template-Engine Smarty

Schon wieder hat der Designer mit seinen Grundkenntnissen in HTML an Ihrem funktionalen PHP-Quellcode Änderungen vorgenommen, weil er das Design nach seinen Vorgaben anpassen wollte? Oft entstehen durch keine klaren Abgrenzungen von Funktion und Design ärgerliche Missgeschicke, welche häufig eine Menge unnötige Arbeit verursachen. Das so genannte Templating Framework Smarty schafft hier Abhilfe.

Die Template-Engine Smarty sorgt dafür, dass bei der Entwicklung einer Webanwendung die Applikations-Logik klar von dem Design/Ausgabe getrennt ist. Die Darstellung des Designs erfolgt bei Smarty mit der Hilfe von Templates. In diesen Templates wird mit einer Kombination von HTML, CSS und Smarty-Template-Tags festgelegt, wie die Informationen dargestellt werden sollen. Durch diese Trennung kann das Design jederzeit auf ein zeitgemäßes Layout angepasst werden, ohne dabei eine Änderung der kompletten Applikation vorzunehmen. Dieses Tutorial wird Ihnen anhand eines Beispielprojekts die Basics von Smarty näher bringen.

Anforderungen

  • Webserver mit PHP ≥ 4.0.6

Vorbereitung/Installation

Vorbereitung und Installation der Smarty-Template-Engine

Zu Beginn erstellen wir ein neues Projekt und nennen dies einfachheitshalber „smarty_tutorial“. In diesem Projekt werden als nächstes die Datei „index.php“ und der Ordner „lib“ und erzeugt. Im Ordner „lib“ werden die Datei „smtemplate.php“ sowie der Ordner „views“ erstellt. Jetzt können wir das Framework von der offiziellen Smarty Seite herunterladen und entpacken. Der Ordner des Archives „demo“ enthält Demo-Dateien, welche wir für unser Beispielprojekt nicht benötigen. Wichtig für uns ist der Ordner „libs“, welchen wir in „smarty“ umbenennen in dann in unser zuvor erstelltes Projekt „smarty_tutorial“ in den Ordner „lib“ einfügen. Um zu einem späteren Zeitpunkt den Update-Vorgang zu vereinfachen, sollten die Smarty-Dateien im Ordner „smarty“ nicht verändert werden. Des Weiteren benötigt Smarty die Ordner „templates_c“, „cache“ und „config“, welche wir im Ordner „smarty“ anlegen. Smarty benötigt Schreibrechte (755) auf den Inhalt des Ordners „smarty“. Die Struktur unseres Projekts sollte nun wie folgt aussehen:

Konfiguration

Als nächstes erstellen wir eine Datei „smtemplate.php“ im Ordner „lib“, welche eine Klasse für unsere Smarty Template Engine darstellt. In dem Konstruktor dieser Klasse müssen die Pfade zu den Unterverzeichnissen im „smarty“-Ordner konfiguriert werden. Für die Realisierung dieser Konfiguration gibt es zwei Möglichkeiten: zum einen die Angabe der Pfade als Klassenkonstanten direkt in der „smtemplate.php“-Datei oder zum anderen die Definition der Pfade in einer Konfig-Datei. Wir definieren unsere Einstellungen über eine Konfig-Datei, weil sich auf diese Weise alle Smarty-Konfigurationen über diese Datei regeln lassen. Also erstellen wir zunächst eine Datei „smtemplate_config.php“ und speichern die Pfade in einem Array.

$smtemplate_config =
 array(
      'template_dir' => 'views/',
      'compile_dir' => 'lib/smarty/templates_c/',
      'cache_dir' => 'lib/smarty/cache/',
      'config_dir' => 'lib/smarty/config/',
);

Jetzt können wir unsere Konfig-Datei in die Datei „smtemplate-php“ einbinden und die Konfiguration wie im Folgenden zu sehen ist vornehmen.

/* Smarty-Dateien laden */
require_once('smarty/Smarty.class.php');
require_once('smtemplate_config.php');

/* An dieser Stelle können Applikations spezifische Bibliotheken geladen werden * Bsp: require(bib.lib.php); * */
class SMTemplate extends Smarty { /**   * Konstruktor.   * Erzeugt eine neue Smarty-Instanz und konfiguriert die Smarty-Pfade   */
  function __construct(){   parent::__construct();   global $smtemplate_config;   $this->template_dir = $smtemplate_config['template_dir'];   $this->compile_dir = $smtemplate_config['compile_dir'];   $this->cache_dir = $smtemplate_config['cache_dir'];   $this->config_dir = $smtemplate_config['config_dir'];   } }

Erstes Template erstellen und darstellen

Um ein Template darstellen zu können, benötigt die SMTemplate-Klasse eine Funktion „render“, welche dies übernimmt.

function render($template){
     $this->display($template . '.tpl');
}

Als nächsten Schritt erstellen wir im Ordner „views“ eine Template mit dem Namen „hello.tpl“ und folgendem Inhalt.

<html>
    <head>
         <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
         <title>Home</title>
    </head>
  <body>   <p>Hello, World!</p>   </body>
</html>

Zur Darstellung des Templates wird in der Datei “index.php” die Funktion “render” unserer Smarty-Klasse aufgerufen.

require_once('lib/smtemplate.php');
$tpl = new SMTemplate(); $tpl->render('hello');

Der Aufruf der Seite „index.php“ sollte „Hello, World!“ ausgeben.


Übergabe von Variablen

Bei dem jetzigen Stand der Beispielanwendung können wir noch keine dynamischen Inhalte darstellen. Für diesen Fall verwendet Smarty Variablen, welche in einem Template benutzt werden können. Eine Übergabe dieser Variablen erfolgt durch die Übergabe eines Daten-Arrays an die Render-Funktion oder durch die Übergabe von Objekten, welche anschließend zu einem Array umgewandelt werden. In einem Template können die Variablen durch {$variablenname} aufgerufen werden. Um unsere Beispielapplikation etwas mit Leben zu füllen, muss zunächst die Render-Funktion angepasst werden, sodass diese das übergebene Daten-Array mit Hilfe der Funktion „assign“ an Smarty weitergibt.

function render($template, $data = array()){
     foreach($data as $key => $value) {
         $this->assign($key, $value);
     }
     $this->display($template . '.tpl');
}

In der Datei „index.php“ wird ein Array mit Daten gefüllt und an die Render-Funktion weitergeleitet.

$data = array(
     'user' => 'Coding Pioneers',
     'date' => time()
);

$tpl = new SMTemplate(); $tpl->render('hello', $data);

Auf die Variablen “user” und “date” können wir jetzt in dem Template “hello.tpl” zugreifen.

<body>
    <p>Hello {$user}! Date: {$date}</p>
</body>

Der Aufruf der Seite „index.php” gibt nun „Hello Coding Pioneers! Date: …“ aus. Zu sehen ist, dass das Datum noch unformatiert ist. Auch dazu bietet Smarty eine Lösung: Formatierung von Variablen.

Formatierung von Variablen

Smarty ermöglicht neben der einfachen Übergabe auch die Formatierung der übergebenen Variablen durch sogenannte Modifier, welche durch eine Pipe (|) angegeben werden. Zum Beispiel verwenden wir an unserer Variablen {$user} den Modifier „upper“, welcher den Inhalt in Großbuchstaben darstellt. Auch das Datum lässt sich mit Hilfe eines Modifiers formatieren. Ändern wir also unser Template „hello.tpl“ und verwenden die zuvor beschriebenen Modifier.

<body>
    <p>Hello {$user|upper}! Date: {$date|date_format:"%d %B"}</p>
</body>

Es ist zu sehen, dass der User-Name in Großbuchstaben dargestellt wird und das Datum formatiert ist. Neben dem Aufruf eines einzelnen Modifiers lassen sich diese durch {$var|mod1|mod2|mod3} kombinieren. Eine Liste aller in Smarty verfügbaren Modifier befindet sich in der ausführlichen Doku ab Seite 40. Reichen die rund 20 Modifier nicht aus, ermöglicht Smarty das Erstellen eigener Modifier.


Eigene Variablenmodifier

Für einen eigenen Variablenmodifier muss lediglich eine PHP-Funktion geschrieben werden. Dazu erstellen wir in dem Ordner „smarty/plugins“ die Datei „smarty_modifier_minimize“. Die Funktion minimiert HTML-Code, indem unnötige Leerzeichen, Umbrüche und Tabstopps entfernt werden.

function smarty_modifier_minimize($input) {
  // Input Kürzen   // s+ - mehrere Whitespaces aufeinanderfolgend   // \n - Zeilenumbruch   // \r - Zeilenumbruch   // \t - Tab
  $input = preg_replace('/^\s+|\n|\r|\t$/m', '', $input);
  /* Kommentare entfernen */   $input = preg_replace('//sU', '', $input);
  return $input; }

Auf dem aktuellen Standpunkt unserer Beispielanwendung können wir diesen Modifier noch nicht sinnvoll verwenden, da wir uns bisher nur mit dem Übergeben von Variablen mit einfachen Werten beschäftigt haben. Den Minimize-Modifier werden wir verwenden, nachdem wir uns die Layouts in Smarty näher angeschaut haben.

Layouts

Smarty verfügt über die Möglichkeit Templates zu laden, diese als Text zurückzugeben und in einer Variablen zu speichern. Diese Methode ermöglicht es, die Struktur der Seite in einem Template zu speichern und den dynamischen Inhalt an die gewünschte Position im Layout zu laden. Als erstes erstellen wir im Hauptverzeichnis des Projekts einen Ordner „layouts“ und in diesem Ordner ein neues Template mit dem Namen „page.tpl“ und den im folgenden Code dargestellten Inhalt. Die Smarty-Variable, welche ein Template als Text beinhalten soll, erhält zur Unterscheidung von normalen Variablen zwei Unterstriche. An dieser Stelle lässt sich der zuvor erstellte Variablenmodifier „minimize“ verwenden um die Inhaltsseite zu minieren.

<html>
     <head>
         <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
         <title>Home</title>
     </head>
  <body>   <header>   <h2>Header Content</h2>   <hr />   </header>
  <div>   {$__content|minimize}   </div>
  <footer>   <hr />   <h2>Footer Content</h2>   </footer>   </body> </html>

Im Ordner „views“ erstellen wir zwei neue Templates „start“ und „about“, welche die dynamischen Inhalte repräsentieren, mit folgendem Inhalt:

start.tpl

<h1>Start<h1/> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean aliquet dignissim diam at vulputate. Aenean nec ligula ac dolor fringilla pharetra. Cras in augue ac tellus dictum pellentesque. Integer elementum tempus lectus, non rutrum sem viverra a. Sed tincidunt sollicitudin dolor, ut blandit magna auctor non. </p>
about.tpl
<h1>Start<h1/> <p> Maecenas sed nibh felis. Donec dictum porta ante at faucibus. Morbi massa tellus, pulvinar id porta id, imperdiet vel nibh. Donec lectus nulla, porttitor a tempor id, cursus vitae leo. </p>

Im nächsten Schritt muss der Pfad zu dem Layout-Ordner in der Smarty-Engine konfiguriert werden. Dazu fügen wir zunächst ein neues Element in das smtemplate_config-Array.

$smtemplate_config =
 array(
      'layouts_dir' => 'layouts',
      'template_dir' => 'views/',
      'compile_dir' => 'lib/smarty/templates_c/',
      'cache_dir' => 'lib/smarty/cache/',
      'config_dir' => 'lib/smarty/config/',
);

In der SMTemplate-Klasse müssen wir den Layout-Ordner als weiteren Template-Ordner angeben, wobei zu beachten ist, dass Views und Layouts nicht den gleichen Namen besitzen dürfen, da Smarty nicht zwischen View- und Layout-Templates unterscheiden kann. Als Lösung des Problems können die Präfixe „view_“ und „layout_“ verwendet werden. Als nächstes muss die Render-Funktion angepasst werden, sodass sie eine View als Text lädt und diese in dem Layout „page“ darstellt. Um mit Smarty ein Template zu laden und dieses als Text zurückzugeben anstatt darzustellen, wird die Funktion „fetch“ verwendet und mit der bekannten Funktion „assign“ an die Smarty-Variable „__content“ übergeben. Das Layout „page“ wird wie zuvor mit der Funktion „display“ dargestellt.

function __construct(){
     parent::__construct();
         
     global $smtemplate_config;
     $this->template_dir = $smtemplate_config['template_dir'];
     $this->addTemplateDir($smtemplate_config['layouts_dir']);
     $this->compile_dir = $smtemplate_config['compile_dir'];
     $this->cache_dir = $smtemplate_config['cache_dir'];
     $this->config_dir = $smtemplate_config['config_dir'];
}
function render($template, $data = array(), $layout = 'page') {   foreach($data as $key => $value){   $this->assign($key, $value);   }
  $content = $this->fetch($template . '.tpl');   $this->assign('__content', $content);   $this->display($layout . '.tpl'); }

Um den Seiteninhalt in dem Layout darzustellen, wird in der „index.php“ wie gewohnt die Render-Funktion mit dem Inhaltstemplate „start“ aufgerufen.

$tpl = new SMTemplate();
$tpl->render('start');
Einfache Startseite mit Smarty
Einfache About-Seite mit Smarty

Der Aufrufe von “index.php” liefert folgende Ausgabe, indem unser „start“-Template in dem Layout „page“ dargestellt wird:



Wird das Template in „about“ geändert, ist zu erkennen, dass sich der Inhalt bei gleichbleibendem Layout geändert hat.



Header und Footer

Auf einer Webseite sind Header und Footer meistens auf jeder Seite identisch. Bei einem Layout für einen Blogbeitrag und das Impressum ändert sich oft an Header und Footer nichts außer dem Titel der Seite (<head><title>Titel</title></head>). Aus diesem Grund ist es sinnvoll, Header und Footer in Templates auszulagern. In Smarty können Templates durch den Befehl „include“ in andere Templates eingebunden werden. Des Weiteren besteht die Möglichkeit, dem Template, welches mit Hilfe von „include“ eingebunden wird, eine Variable als Attribut zu übergeben, wodurch sich z.B. ein sich ändernder Seitentitel realisieren lässt. Der Befehl {include file=“header.tpl“ title=“Start“} entspricht dem Einbinden eines Headertemplates und der Übergabe des Wertes „Start“ an die Variable {$title}. Um das beschriebene Vorgehen in der Praxis umzusetzen, erstellen wir die Templates „header.tpl“ und „footer.tpl“ im Ordner „layouts“.

header.tpl
<html>   <head>   <meta http-equiv="Content-type" content="text/html; charset=utf-8" />   <title>{$title|default:"Title"} | Smarty Tutorial</title>   </head>
  <body>   <header>   <h2>Header Content</h2>   <hr />   </header>
  <div>   footer.tpl   </div>
  <footer>   <hr />   <h2>Footer Content</h2>   </footer>
  </body>
</html>

Als nächstes erstellen wir ein neues Seitenlayout „page-head-foot“, binden die zwei Templates ein und übergeben dem Header die Variable $title.

{include file="../layouts/header.tpl" title="$title"}
     {$__content|minimize}
{include file="../layouts/footer.tpl"}

Danach muss die Funktion “render” angepasst werden, sodass sie den Seitentitel an die Smarty-Engine übergibt.

function render($template, $data = array(), $layout = 'page', $pagetitle = '') {
     foreach($data as $key => $value){
         $this->assign($key, $value);
     }
  $content = $this->fetch($template . '.tpl');   $this->assign('__content', $content);   $this->assign('title', $pagetitle);   $this->display($layout . '.tpl'); }

In der Datei „index.php“ muss der Aufruf des „start“-Templates durch den Seitentitel und das neue Layout ergänzt werden.

$tpl = new SMTemplate();
$tpl->render('start', $data, 'page_head_foot', 'Start');
Einfache Startseite mit Smarty

Der Aufruf der Seite „index.php” stellt den inkludierten Header mit Titel, den Inhalt des Templates „start” sowie den Footer dar.



Wiederkehrende Elemente

Die zuvor beschriebene Funktion kann ebenfalls für wiederkehrende Elemente verwendet werden. Zum Beispiel hat jede Seite einen Seitentitel (<h1>), welcher zwecks einheitlichem Erscheinungsbild der Webseite immer das gleiche Layout aufweisen sollte. Dazu erstellen wir ein Template im Ordner „layouts“ mit dem Namen „pagetitle“ und folgendem Inhalt:

<h1 style="color: red;">{$pagetitle|default:"Pagetitle"}</h1>

Dieses Template inkludieren wir in dem Inhaltstemplate „start”, um den Seitentitel jederzeit individuell, bei gleichbleibendem Layout, anpassen zu können.

{ include file="../layouts/pagetitle.tpl" pagetitle="Startpagetitle" }
<p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean aliquet dignissim diam at vulputate.
Aenean nec ligula ac dolor fringilla pharetra. Cras in augue ac tellus dictum pellentesque.
Integer elementum tempus lectus, non rutrum sem viverra a. Sed tincidunt sollicitudin dolor, ut blandit magna auctor non. </p>
Einfache Startseite mit wiederkehrenden Elementen

Ein erneuter Aufruf der Datei “index.php” zeigt, dass sich der Titel in “Startpagetitle” geändert hat und rot dargestellt wird, welches zeigt, dass das „pagetitle“-Template verwendet wird.

War das schon alles?

Wie durch die aufgezeigten Möglichkeiten von Smarty zu sehen ist, lässt sich durch die Verwendung von Smarty das Design einer Webanwendung einfach von dem funktionalen Quellcode trennen. Smarty bietet weitaus mehr Funktionen als die hier dargestellten, welche ich im Rahmen dieses Tutorials nicht weiter ausführen möchte. Der weitere Funktionsumfang umfasst unter anderem Schleifen, IF-Abfragen, weitere Plugins (nicht nur Variablenmodifier) sowie das Caching der geladenen Seiten. Eine ausführliche Dokumentation zu Smarty, in der die zuvor genannten Funktionen näher beschrieben sind, befindet sich auf der offiziellen Smarty-Seite.


Quellen

Website-Tuning Stage 2: JS zusammenfassen & komprimieren

Im WWW ist sie kaum noch wegzudenken: Die Skriptsprache JavaScript. Sie dient unter anderem zur Dynamisierung der Inhalte einer Website. Der JS-Quellcode wird im HTML zwischen den script-Tags eingebunden oder dort mittels Attribut aus einer externen Datei geladen:

<script src="script.js"></script>

Wie bereits im letzten Teil der Serie erklärt wurde, ist es wichtig, dass so wenig Dateien wie möglich eingeladen werden. Das gilt auch für den eingebundenen JavaScript-Code. Die Ziele sind folgende:

  • Skriptdateien zusammenfassen
  • Quellcode minifizieren

Skriptdateien zusammenfassen

Auf einer Website können mehrere Skript-Dateien eingebunden werden. Wird eine Website in einem Web-Browser aufgerufen, läd dieser in der Regel 6 Dateien gleichzeitig. Dabei werden neben dem Skript auch Bilder, CSS-Dateien und weitere Elemente der Seite geladen. Deswegen ist es sinnvoll, die Anzahl der eingebundenen Dateien möglichst gering zu halten. Dies kann durch das Zusammenfassen aller erforderlichen Skript-Dateien zu einer Datei umgesetzt werden.

Quellcode minifizieren

Das weitere Vorgehen besteht in der Komprimierung der zusammengefassten Skript-Datei. Quellcode enthält oft für den Interpreter unwichtige Zeichen wie Zeilenumbrüche, Tabstopps und Kommentare. Diese Zeichen dienen nur dem Verständnis sowie der Förderung der Lesbarkeit und können daher restlos aus dem Quellcode entfernt werden. Durch diese Operation lässt sich die Größe der Skript-Datei um ein Vielfaches verkleinern, wodurch die Ladezeit der Datei verringert wird.

Folgende Elemente können restlos entfernt werden:

  • Zeilenumbrüche
  • Tabstops
  • Kommentare
  • Zeilenumbrüche
  • Aufeinander folgende Leerzeichen

MODX Snippet

Durch das folgende MODX-Snippet „minimize-js“ kann die Performance beim Laden das JavaScripts mit den zuvor beschriebenen Operation simpel verbessert werden. Der Aufruf des Snippets erfolgt durch [[minimize_js? ]]. Durch das Snippet werden die übergebenen Dateien zusammen gefasst und komprimiert. Da es sich um eine simple Komprimierung handelt, führen einzeilige If-Abfragen, welche einen Zeilenumbruch enthalten, zu einer fehlerhaften Ausgabe.

Parameter

NameBeschreibung
srcFilesPfade zu den JavaScript-Dateien, welche zusammengefasst und komprimiert werden sollen. Pfade werden durch ein Komma getrennt.

Quellcode des MODX-Snippets

<?php<br>
<br>
$srcFiles = $modx->getOption('srcFiles', $scriptProperties);<br><br>

/* Leerzeichen entfernen */<br>
$srcFiles = str_replace(" ", "", $srcFiles);<br><br>

/* Parameter aufsplitten */<br>
$params = explode(",",$srcFiles);<br><br>

$output = "";<br><br>

/* Source-Dateien optimieren */<br>
foreach($params as $file)<br>
{<br><br>

  /*Dateiinhalt auslesen */<br>
  $string = file_get_contents($file);<br><br>

  /* Einzeile Kommentare entfernen */<br>
  $string = preg_replace("/[^:](\/\/.*)/", "\n", $string);<br><br>

  /* Mehrzeilige Kommentare entfernen */<br>
  $string = preg_replace("/(\/\*.*\*\/)/s", "\n", $string);<br><br>

  /* carriage returns durch Zeilenumbruch ersetzen */<br>
  $string = str_replace("\r", "\n", $string);<br><br>

  /* tabs durch Leerzeichen ersetzen */<br>
  $string = str_replace("\t", "\n", $string);<br><br>

  /* Mehrere Zeilenumbrueche durch einen ersetzen */<br>
  $string = preg_replace("/\n+/", "\n", $string);<br><br>

  /* Zeilenumbrueche an schließenden Klammern durch Semikolons ersetzen */<br>
  $string = preg_replace("/[)]\n/", ");", $string);<br><br>

  /* Entfernt das Semikolon an If-Abfragen und Schleifen */<br>
  $string = str_replace(");{", "){", $string);<br><br>

  /* Entfernt das Semikolon am Punkt-Operator */ <br>
  $string = str_replace(");.", ").", $string);<br><br>

  /* Entfernt das Semikolon an zwei aufeinander folgenden runden Klammern*/<br>
  $string =preg_replace("/[);]\s+[);]/", ")", $string);<br><br>

  /* Restliche Zeilenumbrüche entfernen*/<br>
  $string = preg_replace("/\n/", "", $string);<br><br>

  /* Mehrere Leerzeichen durch ein Leerzeichen ersetzen */<br>
  $string = preg_replace("/\s+/", " ", $string);<br><br>


  $output = $output.$string;<br>
}<br>
<br>
return $output;<br>
?>

Verwendung

Nachdem mit dem zuvor beschriebenen Quellcode das MODX-Snippet erstellt wurde, muss dieses in die Website eingebunden werden. Dazu wird eine neue Ressource vom Content-Type „javascript“ erzeugt und das Snippet mit

[[minimize_js? &srcFiles=`js/script1.js,js/script2.js,js/script3.js`]]

eingebunden. Zu beachten ist, dass der Aufruf des Snippets ohne ein Ausrufungszeichen nach der zweiten öffnenden eckigen Klammer erfolgt, damit das Snippet gecacht wird. Durch das Cachen des Snippets wird zusätzliche Ladezeit gespart, da die Datei nicht neu geladen werden muss.

Die zuvor erstellte Ressource wird im Footer durch

<script src="ressourcen-name.js"></script>

Fazit

Durch die Verwendung des MODX-Snippet konnten wir die Größe unserer JS-Datei um 60% reduzieren. Hier bewegen wir uns ebenfalls im KB-Bereich. Dank moderner Datenübertragungstechniken spielt das Einsparen von KBs in der heutigen Zeit kaum noch eine Rolle. Aber auch hier gilt: „Serverentlastung bei Lastzeiten“ und „VDSL, Glasfaser und LTE sind nicht immer verfügbar“.

Bei dieser Methode der Komprimierung handelt es sich um ein simples Verfahren, welches eine Alternative zu externen Programmen, wie zum Beispiel dem Closure Compiler von Google, liefert. Durch das MODX-Snippet bleibt die gesamte Kontrolle bei dem Programmierer der Website und der Quellcode wird nicht an eine externe Schnittstelle gesendet.

Website-Tuning Stage 1: Code validieren und optimieren

Das WWW – wie wir es heute kennen – hat eine gewisse Entwicklung hinter sich. Durch diese ist letztlich auch HTML bzw. HTML5 entstanden, da es vorher keine Standards für die strukturierte Übertragung von digitalen Informationen zwischen mehreren Personen gab.

Zur Historie von HTML: Die Hypertext Markup Language gehört zur Klasse der sog. Auszeichnungssprachen, die einen Text semantisch strukturiert und es erstmals Ende 1992 ermöglichte Informationen spezifiziert über Ländergrenzen hinweg zu übertragen. Heutzutage wird HTML vom W3C und der WHATWG weiterentwickelt. Die derzeitige aktuellste Spezifikation ist HTML5, die bereits von den meisten Webbrowsern unterstützt wird.

Wenn nun entsprechend dieser Spezifikation der Quellcode geschrieben wird, so besteht auch eine größere Chance, dass dieser dann auf allen Endgeräten gleichartig interpretiert und angezeigt wird.

HTML5 validieren

Damit der Quellcode einer Webseite entsprechend der Spezifikation des W3C geschrieben wird, bzw. das Ergebnis sich prüfen lässt, gibt es vom W3C einen sog. Validatior. Werden hier Fehler oder Warnungen ausgegeben, so können diese korrigiert werden. Ein bekannter Validator ist unter folgender Domain zu finden:

html5.validator.nu

CSS validieren

Weiter verhält es sich mit dem CSS genauso wie mit dem HTML, hier wird auch ein Validator von W3C bereitgestellt:

jigsaw.w3.org/css-validator

Es fällt auf, dass hier etliche Fehler gefunden werden und der Quellcode angeblich alles aber eben nicht valide ist. Das liegt daran, dass CSS3 noch nicht spezifiziert ist (und damit kein Standard, der sich prüfen ließe) und bisher nur ein Entwurf ist, der permanent erweitert wird und dann durch die Browser-Engines implementiert wird. Dadurch gibt es auch die verschiedenen Browser-Präfixe (wie bspw. „-webkit“).

Nichtsdestotrotz lassen sich so einige Fehler im Stylesheet erkennen und bereinigen.

HTML5 und CSS optimieren

Damit das CSS und HTML so schnell wie möglich angezeigt werden, ist es wichtig, dass dieser Quellcode so schnell wie möglich geladen wird. Daraus folgt, dass zum einen der HTML und CSS-Code immer so klein wie möglich sein sollten und zum zweiten, dass auch nur das übertragen wird, was auch wirklich gebraucht wird.


Folgendes ist empfehlenswert:

  • Kommentare Entfernen
  • Kommentare aus Scripts und Styles entfernen
  • Entfernen von CDATA-Sektionen von Skripten und Styles
  • Überflüssige Leerzeichen und Zeilenumbrüche entfernen
  • Entfernen von leeren Attributen (class=„“)
  • Entfernen von leeren Elementen
  • URLs relativ angeben (URLs minifizieren)

Texte per HTTP-Kompression verkleinern

HTTP-Kompression, die von Servern und Webbrowsern unterstützt wird, dient der Verkürzung der Übertragungsgeschwindigkeit und der Optimierung der Bandbreitenausnutzung.

Komprimierbare Ressourcen (wie Textdateien also vor allem HTML, CSS und Javascript) auf einer Webseite, die mittels HTTP-Komprimierung bereitgestellt werden, sorgen dafür, dass sich bis zu 90% des benötigten Datenvolumens bei der Übertragung einsparen lassen. Viele Webserver können Dateien vor dem Senden mittels gzip komprimieren. Dies geschieht durch Aufrufen eines Drittanbietermoduls oder mithilfe integrierter Routinen. Dadurch können für das Rendern Ihrer Website erforderliche Ressourcen signifikant schneller heruntergeladen werden.

Bei anderen Ressourcen wie z.B. JPEG-Bildern lohnt diese Vorgehensweise nicht, da das Bild selber schon komprimiert ist.

HTTP-Kompression (gzip) im nginx-Webserver aktivieren

Aus allgemeinen Performancegründen arbeiten wir mit dem Webserver nginx. Dieser liefert die statischen Dateien deutlich schneller als ein Apache Webserver aus, sodass dieser hierhingehend konfiguriert werden muss. Die folgende Konfiguration beinhaltet die gesamte nginx-Konfiguration für das MODX-CMS:


location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {<br>
    expires 30d;<br>
    add_header Pragma public;<br>
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";<br>
    try_files $uri = @fallback;<br>
<br>
    if (!-e $request_filename) {<br>
        rewrite ^/(.*)$ /index.php?q=$1 last;<br>
    }<br>
}<br>
<br>
gzip on;<br>
gzip_proxied any;<br>
gzip_types  application/javascript application/json application/xml application/xml+rss application/x-javascript text/css text/javascript text/plain text/xml;<br>
gzip_vary on;

Fazit

Alleine durch diese kleinen Optimierungen konnte der Textanteil der übertragenen Daten von ca. 250 kB auf rund 70 kB, also um über 70% gesenkt werden. Diese läppischen 180 kB sind zwar in Zeiten von VDSL, Glasfaser und LTE kaum der Rede wert, entlasten aber dennoch gerade in Lastzeiten den Server enorm und auch einen geneigten Interessenten, der eben doch nur über EDGE oder GPRS mit dem Internet verbunden ist.