/*
** This file is part of Filius, a network construction and simulation software.
**
** Originally created at the University of Siegen, Institute "Didactics of
** Informatics and E-Learning" by a students' project group:
** members (2006-2007):
** André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding,
** Nadja Haßler, Ernst Johannes Klebert, Michell Weyer
** supervisors:
** Stefan Freischlad (maintainer until 2009), Peer Stechert
** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de>
** and Stefan Freischlad
** Filius is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 2 of the License, or
** (at your option) version 3.
**
** Filius is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied
** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
** PURPOSE. See the GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Filius. If not, see <http://www.gnu.org/licenses/>.
*/
package filius.software.www;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
import java.util.ListIterator;
import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.tags.ImageTag;
import org.htmlparser.visitors.TagFindingVisitor;
import filius.Main;
import filius.exception.VerbindungsException;
import filius.rahmenprogramm.Base64;
import filius.rahmenprogramm.I18n;
import filius.rahmenprogramm.Information;
import filius.software.clientserver.ClientAnwendung;
import filius.software.transportschicht.Socket;
import filius.software.transportschicht.TCPSocket;
/**
* Diese Klasse implementiert die Funktionalitaet eines HTTP-Clients fuer einen
* Webbrowser. Dazu werden folgende Funktionen zur Verfuegung gestellt:
* <ol>
* <li> Zum Abruf einer Webseite von einem Webserver gibt es die Methoden
* <b>holeRessource(url: URL)</b> fuer eine GET-Abfrage und </li>
* <li> <b>holeRessource(url: URL, post: String)</b> fuer eine POST-Abfrage mit
* Daten im Datenteil einer HTTP-Nachricht. </li>
* </ol>
* Diese Methoden haben keinen Rueckgabewert und blockieren auch nicht. Die vom
* Server gelieferten Daten werden an den Beobachter als HTTPNachricht
* weitergegeben.
*
*/
public class WebBrowser extends ClientAnwendung implements I18n {
private static final int ABRUF_HTML = 1, ABRUF_IMG = 2;
private LinkedList<String> bilddateien = new LinkedList<String>();
private int zustand;
private String host;
public void holeWebseite(URL url) {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), holeWebseite("+url+")");
Object[] args;
zustand = ABRUF_HTML;
if (socket != null) {
socket.schliessen();
socket = null;
}
args = new Object[2];
args[0] = url;
args[1] = "";
ausfuehren("internHoleRessource", args);
}
public void holeWebseite(URL url, String post) {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), holeWebseite("+url+","+post+")");
Object[] args;
zustand = ABRUF_HTML;
if (socket != null) {
socket.schliessen();
socket = null;
}
args = new Object[2];
args[0] = url;
args[1] = post;
ausfuehren("internHoleRessource", args);
}
public void internHoleRessource(URL url, String post) {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), internHoleRessource("+url+","+post+")");
HTTPNachricht nachricht;
HTTPNachricht fehler;
Socket aktuellerSocket = socket;
nachricht = new HTTPNachricht(HTTPNachricht.CLIENT);
nachricht.setPfad(url.getFile());
if (url.getHost() != null && !url.getHost().equals("")) {
host = url.getHost();
}
nachricht.setHost(host);
if (nachricht.getHost() != null && !nachricht.getHost().equals("")) {
if (post != null && !post.equals("")) {
nachricht.setMethod(HTTPNachricht.POST);
nachricht.setDaten(post);
}
else {
nachricht.setMethod(HTTPNachricht.GET);
}
if (zustand == ABRUF_HTML || aktuellerSocket == null) {
try {
socket = new TCPSocket(getSystemSoftware(), host, 80);
aktuellerSocket = socket;
}
catch (VerbindungsException e) {
if (zustand == ABRUF_HTML) {
fehler = new HTTPNachricht(HTTPNachricht.CLIENT);
fehler.setDaten(erzeugeHtmlFehlermeldung(0));
benachrichtigeBeobachter(fehler);
}
// print not necessary, since this is a controlled exception.
//e.printStackTrace(Main.debug);
}
}
if (aktuellerSocket != null && !aktuellerSocket.istVerbunden() && aktuellerSocket == socket) {
try {
aktuellerSocket.verbinden();
}
catch (Exception e) {
if (zustand == ABRUF_HTML && aktuellerSocket == socket) {
fehler = new HTTPNachricht(HTTPNachricht.CLIENT);
fehler.setDaten(erzeugeHtmlFehlermeldung(0));
benachrichtigeBeobachter(fehler);
}
e.printStackTrace(Main.debug);
}
}
if (aktuellerSocket != null && aktuellerSocket.istVerbunden() && aktuellerSocket == socket) {
try {
aktuellerSocket.senden(nachricht.toString());
verarbeiteNachricht();
}
catch (Exception e) {
e.printStackTrace(Main.debug);
}
}
}
}
public String holeHost() {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), holeHost()");
return host;
}
public void starten() {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), starten()");
super.starten();
bilddateien = new LinkedList<String>();
}
/**
* liest eine reale Textdatei vom Format .txt ein. Diese befinden sich im
* Ordner /config
*/
private String einlesenTextdatei(String datei)
throws FileNotFoundException, IOException {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), einlesenTextdatei("+datei+")");
BufferedReader dateiPuffer;
StringBuffer fullFile;
String input;
dateiPuffer = new BufferedReader(new FileReader(datei));
fullFile = new StringBuffer();
while ((input = dateiPuffer.readLine()) != null) {
fullFile.append(input + "\n");
}
return fullFile.toString();
}
private String erzeugeHtmlFehlermeldung(int statusCode) {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), erzeugeHtmlFehlermeldung("+statusCode+")");
String quelltext;
String dateipfad;
String meldung;
if (statusCode == 0) {
quelltext = messages.getString("sw_webbrowser_msg1");
}
else {
dateipfad = Information.getInformation().getProgrammPfad()
+ "config/http_fehler.txt";
try {
quelltext = einlesenTextdatei(dateipfad);
}
catch (Exception e) {
quelltext = messages.getString("sw_webbrowser_msg2");
e.printStackTrace(Main.debug);
}
quelltext = quelltext.replace(":code:", "" + statusCode);
meldung = HTTPNachricht.holeStatusNachricht(statusCode);
quelltext = quelltext.replace(":meldung:", meldung);
}
return quelltext;
}
/**
* Methode zur Verarbeitung von IMG-Tags. Mit einem Parser werden IMG-Tags
* im uebergebenen Quelltext gesucht. Alle Bilddateien werden in eine Liste
* geschrieben. Fuer jedes Bild wird dann ein Aufruf der Methode
* internHoleRessource() in die Befehlswarteschlange geschrieben.
*
* @param quelltext
* @param host
*/
private void verarbeiteIMGTags(String quelltext, String host) {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), verarbeiteIMGTags("+quelltext+","+host+")");
Parser parser;
TagFindingVisitor nodeVisitor;
Node[] nodes;
ImageTag img;
ListIterator it;
String dateipfad;
URL url;
Object[] args;
zustand = ABRUF_IMG;
parser = Parser.createParser(quelltext, null);
nodeVisitor = new TagFindingVisitor(new String[] { "img" });
try {
parser.visitAllNodesWith(nodeVisitor);
nodes = nodeVisitor.getTags(0);
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] instanceof ImageTag) {
img = (ImageTag) nodes[i];
synchronized (bilddateien) {
bilddateien.add(img.getImageURL());
}
}
}
}
catch (Exception e) {
e.printStackTrace(Main.debug);
}
/*
* Liste der gefundenen IMG SRCs wird iteriert, die einzelnen Bilder
* werden abgerufen
*/
synchronized (bilddateien) {
it = bilddateien.listIterator();
while (it.hasNext()) {
dateipfad = it.next().toString();
try {
url = new URL("http", host, dateipfad);
args = new Object[2];
args[0] = url;
args[1] = "";
ausfuehren("internHoleRessource", args);
}
catch (MalformedURLException e) {
e.printStackTrace(Main.debug);
}
}
}
}
/**
* Mit dieser Metode wird eine HTTP-Nachricht empfangen und
* weiterverarbeitet. Wenn waehrend des Empfangs ein Fehler auftritt oder
* keine Nachricht empfangen wird, werden Beobachter darueber informiert,
* dass ein Verbindungsfehler aufgetreten ist. <br />
* Wenn eine Nachricht empfangen wurde, wird zunaechst der HTTP-Statuscode
* geprueft. Nur wenn dieser 200 ist, erfolgt eine weitere Verarbeitung.
* Andernfalls wird eine Fehlernachricht erzeugt und an Beobachter weiter
* gegeben, wenn es sich um die Anwort auf eine Webseitenanfrage handelt.
* Wenn es sich um die Antwort auf die Anfrage nach einem Bild handelt, wird
* 'null' an die Beobachter weiter gegeben! <br />
* Zuletzt wird der Socket geschlossen, wenn keine Antworten auf Anfragen
* fuer Bilddateien mehr zu bearbeiten sind.
*
*/
protected void verarbeiteNachricht() {
Main.debug.println("INVOKED ("+this.hashCode()+", T"+this.getId()+") "+getClass()+" (WebBrowser), verarbeiteNachricht()");
HTTPNachricht antwort;
String contentType;
String dateipfad;
String nachricht = null;
Socket aktuellerSocket = socket;
try {
// blockieren, bis eine Nachricht eintrifft
nachricht = aktuellerSocket.empfangen();
}
catch (Exception e) {
nachricht = null;
}
// Main.debug.println(getClass() + "\n\tverarbeiteNachricht()"
// + "\n>>>>\n" + nachricht + "\n<<<<");
if (nachricht != null) {
antwort = new HTTPNachricht(nachricht);
// Nur wenn der HTTP-Statuscode 200 ist,
// erfolgt die weitere Verarbeitung.
if (antwort.getStatusCode() == 200) {
contentType = antwort.getContentType();
if (contentType == null) {
antwort.setDaten("");
}
else if (contentType.equalsIgnoreCase(HTTPNachricht.TEXT_HTML)
&& antwort.getDaten() != null) {
verarbeiteIMGTags(antwort.getDaten(), antwort.getHost());
}
else if (contentType.equalsIgnoreCase(HTTPNachricht.IMAGE_BMP)
|| contentType
.equalsIgnoreCase(HTTPNachricht.IMAGE_GIF)
|| contentType
.equalsIgnoreCase(HTTPNachricht.IMAGE_JPG)
|| contentType
.equalsIgnoreCase(HTTPNachricht.IMAGE_PNG)) {
synchronized (bilddateien) {
if (bilddateien.size() > 0) {
dateipfad = bilddateien.removeFirst();
Base64.decodeToFile(antwort.getDaten(), Information.getInformation().getTempPfad()+ dateipfad);
}
}
antwort = null;
}
}
else if (zustand == ABRUF_HTML && aktuellerSocket == socket) {
antwort.setDaten(erzeugeHtmlFehlermeldung(antwort
.getStatusCode()));
}
else {
antwort = null;
}
if (aktuellerSocket == socket)
benachrichtigeBeobachter(antwort);
}
else if (zustand == ABRUF_HTML && aktuellerSocket == socket) {
antwort = new HTTPNachricht(HTTPNachricht.CLIENT);
antwort.setDaten(erzeugeHtmlFehlermeldung(0));
benachrichtigeBeobachter(antwort);
}
if ((bilddateien == null || bilddateien.size() == 0) && aktuellerSocket == socket) {
if (socket != null) {
socket.schliessen();
socket = null;
}
}
}
}