BigBastis Blog About Me & my Digital Lifestyle

2Nov/120

Nuetzliche Maven Parameter

Maven ist ein sehr mächtiges und praktisches Werkzeug, aber manchmal ist das vordefinierte Verhalten etwas nervig. Glücklicherweise kann man so ziemlich alles über Parameter beeinflussen.

Test-Fehler ingnorieren

Oft will man alle Testfälle eines Projekts ausführen um einen Überblick zu erhalten, Maven bricht per Default aber nach dem ersten Testfehler ab, dieses Verhalten kann man Maven ganz leicht über diesen Parameter abgewöhnen:

mvn test -Dmaven.test.failure.ignore=true

Wenn man will kann man dieses Verhalten auch für ein Projekt vorschreiben, sodass es bei jedem Testdurchlauf so bleibt. Dazu definiert man folgendes Plugin:


  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
      <testFailureIgnore>true</testFailureIgnore>
    </configuration>
  </plugin>

Tests beim install überspringen

Standardmäßig führt Maven beim install auch alle Tests aus und bricht bei dem ersten nicht bestandenen Test den install ab. Dies kann je nach Projektbröße auch eine ganze Weile dauern. So kann es viel Zeit sparen wenn man die Tests weglässt. Dieses einfache Kommando hilft uns hier aus:

mvn install -Dmaven.test.skip=true

Habt ihr noch nützliche Maven Parameter? Lasst es mich wissen. .)

16Apr/120

GlassFish: Unnoetige HTTP-Response Header entfernen

In dem letzten Artikel habe ich beschrieben, wie ihr unter ASP.NET MVC Anwendungen die verräterischen HTTP Header ausblenden könnt, nun können wir auch mal auf den GlassFish schauen.

Default-Header

Wie ihr in dem oberen Bild erkennen könnt sendet der GlassFish hier seinen Namen, die Version und auch das verwendete Framework mit. Das wollen wir natürlich nicht haben.

Fangen wir mit dem Server-Header an. Dieser ist recht einfach zu entfernen durch das Setzen eines VM-Parameters in der GlassFish Adminoberfläche.

  1. Startet die Admin console
  2. Wählt Configuration ->JVM Settings
  3. Wählt oben JVM Options
  4. Klickst auf Add JVM Option
  5. In der neue textfeld tragt ihr ein:  -Dproduct.name=""
  6. Klickt auf Save und startet den GlassFish neu

Nun ist der Server-Header nciht ausgefüllt

Auch wenn das nun etwas komisch aussieht, haben wir so den Server-Header entfernt. Dabei verhält sich der GlassFish unter verschiedenen Umständen anders. Manchmal zeigt er den Header so an wie in dem Bild, manchmal lässt er ihn auch komplett weg. (Wenn ihr hier mehr wisst hinterlasst bitte ein Kommentar).
X-Powered-By header

  1. Startet die Admin console
  2. Geht zu Configuration -> Network Config -> Network Listeners
  3. Wählt http-listener-2 aus und wählt dann oben "HTTP"
  4. Deaktiviert den Hacken bei "XPowered By" (Siehe Bild)
  5. Speichert und startet den GlassFish neu

Der Hacken zum deaktivieren des Headers

Leider ist die Sache damit noch nicht gegessen, da dies scheinbar nur den "X-Powered-By: Servlet/3.0" Header entfernt den "X-Powered-By: JSP/2.1" muss man nochmal wo anders entfernen.

  1. Navigiert zu domains/domain1/config
  2. öffnet die Datei default-web.xml
  3. sucht nach xpoweredBy und setzt diesen Setting auf "false" (siehe Bild)
  4. Speichert und startet den GlassFish neu

Konfiguration in der default-web.xml

Nun sieht die Sache wieder bessern aus:

nun ist alles fast OK

Wenn ihr noch einen Tipp habt wie man den Server-Header ganz weg bekommt wäre ich für einen Kommentar dankbar!

9Feb/120

Aktuelle Zeile bei Kommandozeilen Applikationen aktualisieren

Vielleicht ist euch das ja auch schon Mal passiert, ihr entwickelt eine Kommandozeilen-App und wollt die Aktuelle Zeile ändern. Ein Beispiel dafür wäre ein Fortschrittsbalken oder der beliebte Command-Line-Spinner ( bestehend aus den Zeichen /, -, \, |. Wenn diese nacheinander angezeigt werden sieht das so aus wie ein sich drehender Ast :)

Das Ganze kann in allen Kommandozeilenfenstern, egal ob Unix oder Windows implementiert werden. Der Schlüssel hierzu ist das Sonderzeichen \b - welches das Drücken der Backspace Taste simuliert. Ähnliche Vertreter sind \t für Tab und \n für Zeilenumbruch.

Solange ihr noch keinen Zeilenumbruch abgesetzt habt könnt ihr jede Ausgabe in der aktuellen Zeile widerrufen durch das ausgeben von \b dabei müsst ihr jedes Zeichen einzeln widerrufen. Wenn wir nun also unser Spinner-beispiel betrachten kann man das sehr einfach in .NET umsetzen:


        static void Main(string[] args) {
            string[] states = { "\\", "|", "/", "-" };
            int cur_state = 0;

            while (true) {
                Thread.Sleep(100);

                Console.Write("\b");
                Console.Write(states[cur_state]);
                cur_state++;
                if (cur_state == 4) {
                    cur_state = 0;
                }
            }
        }

Genauso einfach geht das Ganze dann auch in Java:


        public static void Main(string[] args) {

            string[] states = { "\\", "|", "/", "-" };
            int cur_state = 0;

            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {}

                System.out.print("\b");
                System.out.print(states[cur_state]);
                cur_state++;
                if (cur_state == 4) {
                    cur_state = 0;
                }
            }
        }

Ist in jedem Fall ein nettes Gimmick :) Beachten sollte man noch, dass man hier print() bzw. Write() benutzen muss und nicht println() oder WriteLine() da hier sonst automatisch ein \n mit an das Ende der Zeile gepackt wird!

13Dez/110

SSL Teil 4: Serverseitige Authentifizierung mit Java

Nachdem wir in den letzen Teilen mehr die Theorie durchgenommen haben, möchte ich jetzt zu dem praktischen Teil kommen und euch zeigen wie man diese Theorie in Code umsetzen kann am Beispiel Java. (.NET wird auch noch folgen)

Wir wollen einfach einsteigen und implementieren erst mal nur die eine Serverseitige Authentifizierung, also muss der Server sich uns gegenüber ausweisen und wir sind in der Pflicht sein Zertifikat anzunehmen oder abzulehnen.

Wie in Teil 1 erklärt benötigen wir also zunächst einen Ort wo wir die Zertifikate sichern, denen wir vertrauen. Hier können wir (da wir in Java unterwegs sind) nicht die Windows-eigenen nehmen sondern müssen die so genannten Keystores von Java Nutzen.

Ein Keystore, wie der Name schon sagt ist nichts anderes als ein Speicher für Zertifikate. Wenn ihr Java installiert dann legt Java bereits einen Keystore für euch an, diesen findet ihr dann in eurem Java Verzeichnis Java\jre6\lib\security\cacerts die Datei „cacerts“ enthält hier bereits ähnlich wie bei Windows bereits die CA-Zertifikate von den großen Zertifizierungsstellen.

Es wir desweiterhin in Java unterschieden zwischen  Keystore und Truststore, beides sind im Grunde Keystores und unterscheiden sich nur im Namen und in ihrem Inhalt. Eine einzige Datei kann auch beide beinhalten, ist aber eher unüblich, da man die eigenen (privaten) Zertifikate von den CA-Zertifikaten separieren will.

Zuständigkeiten

So enthalten die Keystores für gewöhnlich die eigenen Privaten Schlüssel und die eigenen von anderen CAs signierte Zertifikate, die bei Verbindungen als Serverzertifikate (Public Keys) genutzt werden. Der Truststore ist das Gegenstück und enthält die Zertifikate denen man vertraut. So müssen wir die Zertifikate die wir vom Server beim Verbindungsaufbau erhalten gegen einen Truststore prüfen.

Fangen wir mit unserem Code an, wir wollen einen Client erstellen, der eine Socket-basierte Verbindung zu einem Server über SSL aufbaut und das vom Server zurückgegebene Zertifikat prüft.

Java ist auf diesem Gebiet sehr entgegenkommend und bietet für fast alle Anwendungsfälle fertige APIs und Klassen die uns fast die ganze Arbeit abnehmen.

Um den Prozess besser aufzeigen zu können schreiben wir uns eine Eigene SSLSocketFactory die für uns den Aufbau der Verbindung übernimmt und unserem Programm im Prinzip ein fertiges Socket liefert.

Dazu erstellen wir eine neue Klasse und lassen diese von SSLSocketFactory erben. Da das eine abstrakte Klasse ist müssen eir einige Methoden implementieren. Wenn das erledigt ist sieht unsere Klasse schon so aus:


/**
 * Stellt eine SSLSocketFactory bereit die einen angegebenen TrustStore nutzt
 * @author Sebastian Gross
 */
public class ServerSSLAuthSocketFactory extends SSLSocketFactory{

    @Override
    public String[] getDefaultCipherSuites() {

    }

    @Override
    public String[] getSupportedCipherSuites() {

    }

    @Override
    public Socket createSocket(Socket socket, String string,
            int i, boolean bln) throws IOException {

    }

    @Override
    public Socket createSocket(String destinationAddress,
            int destinationPort) throws IOException, UnknownHostException {

    }

    @Override
    public Socket createSocket(String string, int i,
            InetAddress ia, int i1) throws IOException, UnknownHostException {

    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {

    }

    @Override
    public Socket createSocket(InetAddress ia, int i,
            InetAddress ia1, int i1) throws IOException {

    }
}

Nun müssen wir diesen Methoden Leben einhauchen, aber keine Angst das ist nicht so schlimm wie es aussieht, denn die einzige Änderung die wir machen müssen ist es der SSLSocktFactory beizubringen mit unserem Truststore zu arbeiten, der Rest der Funktionalität soll unberührt bleiben.

Fügen wir also einen Konstruktor zu unserer Klasse hinzu der das Initialisieren unserer Internen SSLSocketFactory mit unserem Truststore übernimmt.


/** Delegate {@link SSLSocketFactory}. */
private transient SSLSocketFactory factory;

private File caFile;
private KeyStore ks;

private SSLContext context;
private TrustManagerFactory tmf;
private X509TrustManager defTrustManager;
private SavingTrustManager tm;

/**
 * Neue Truststore-basierte SSL-SocketFactory
 * @param truststoreLocation Vollqualifizierter Pfad zum Truststore oder
			leer lassen für Java-Default-Truststore
 * @param truststorePass Passwort für den Truststore. Wenn leer wird
			"changeit" ala Passwort benutzt.
 */
public ServerSSLAuthSocketFactory(String truststoreLocation,
								  String truststorePass){
	super();

	//Wenn ein Pfad zum TrustStore angegeben wurde diesen benutzen
	if(!truststoreLocation.isEmpty()){
		caFile = new File(truststoreLocation);
	}else{
		//Pfad zum default Java TrustStore finden
		caFile = new File("cacerts");
		if (!caFile.exists() || !caFile.isFile()) {
			char SEP = File.separatorChar;
			File dir = new File(System.getProperty("java.home") + SEP
					+ "lib" + SEP + "security");
			caFile = new File(dir, "cacerts");
			if (!caFile.exists() || !caFile.isFile()) {
				caFile = new File(dir, "cacerts");
			}
		}
	}

	//Prüfen, ob ein TrustStore Password angegeben wurde
	String truststorePassword = truststorePass.isEmpty() ? "changeit" : truststorePass;

	//TrustStore öffnen
	try {
		InputStream in = new FileInputStream(caFile);
		ks = KeyStore.getInstance(KeyStore.getDefaultType());
		ks.load(in, truststorePassword.toCharArray());
		in.close();
	} catch (KeyStoreException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (NoSuchAlgorithmException ex){
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (CertificateException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (FileNotFoundException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}catch (IOException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}

	try {
		context = SSLContext.getInstance("TLS");
		tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		defTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
		context.init(null, new TrustManager[] {defTrustManager}, null);
		factory = context.getSocketFactory();
	} catch (NoSuchAlgorithmException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (KeyStoreException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	} catch (KeyManagementException ex) {
		Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
	}

}

Das sieht auf den ersten Blick recht komplex aus, ist aber im Grunde recht simpel.

  • In den Zeilen 23 - 28 prüfen wir ob der Konstruktor einen Pfad zu einem Truststore übergeben bekommen hat, und wenn dies nicht der Fall ist, wird der Pfad zu dem Standard-Java-Truststore in eurem JRE-Verzeichnis ermittelt.
  • Direkt danach in Zeile 41 prüfen wir ob ein Passwort übergeben wurde. Wenn keins übergeben wurde wird "changeit" genutzt. "changeit" ist das Defaultpasswort für den Standard-Java-Truststore.
  • In Zeile 45 - 48 öffnen wir den Keystore und laden dessen Inhalt in unsere Kokale KeyStore Variable.
  • Das Wichtigste geschieht nun in Zeile 67 - 72 hier holen wir uns einen Verweis auf den SSLContext (67) und erstellen uns mit Hilfe unseres KeyStores einene TrustManagerFactory(68-69). Anschließend lassen wir die TrustManagerFactory für uns einen neuen X509TrustManager "produzieren" der dann den Verweis auf unseren eigenen Keystore trägt. (70)
  • Nun sagen wir dem SSLContext, dass es unseren Trustmanager nutzen soll (71) für die darüber laufende Verbindungen und erstellen uns eine SSLSocketFactory (72)

Nun haben wir intern eine SocketFactory, die unseren eigenen (über den Konstruktor angegebenen) Keystore nutzt. Der Rest der Klasse die wir erstellt haben dient hierbei eigentlich nur als Wrapper und Durchreiche, der Wichtigste Teil war der Konstruktor.

Die restlichen Methoden können nun also alle Anfragen direkt an die produzierte SSLSocketFactory weiterleiten. So sieht dann die fertige Klasse aus:


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Stellt eine SSLSocketFactory bereit die einen angegebenen TrustStore nutzt
 * @author Sebastian Gross
 */
public class ServerSSLAuthSocketFactory extends SSLSocketFactory{

    /** Delegate {@link SSLSocketFactory}. */
    private transient SSLSocketFactory factory;

    private File caFile;
    private KeyStore ks;

    private SSLContext context;
    private TrustManagerFactory tmf;
    private X509TrustManager defTrustManager;
    private SavingTrustManager tm;

    /**
     * Neue Truststore-basierte SSL-SocketFactory
     * @param truststoreLocation Vollqualifizierter Pfad zum
	 *			Truststore oder leer lassen für Java-Default-Truststore
     * @param truststorePass Passwort für den Truststore.
	 *			Wenn leer wird "changeit" ala Passwort benutzt.
     */
    public ServerSSLAuthSocketFactory(String truststoreLocation,
				String truststorePass){
        super();

        //Wenn ein Pfad zum TrustStore angegeben wurde diesen benutzen
        if(!truststoreLocation.isEmpty()){
            caFile = new File(truststoreLocation);
        }else{
            //Pfad zum default Java TrustStore finden
            caFile = new File("cacerts");
            if (!caFile.exists() || !caFile.isFile()) {
                char SEP = File.separatorChar;
                File dir = new File(System.getProperty("java.home") + SEP
                        + "lib" + SEP + "security");
                caFile = new File(dir, "cacerts");
                if (!caFile.exists() || !caFile.isFile()) {
                    caFile = new File(dir, "cacerts");
                }
            }
        }

        //Prüfen, ob ein TrustStore Password angegeben wurde
        String truststorePassword =
				truststorePass.isEmpty() ? "changeit" : truststorePass;

        //TrustStore öffnen
        try {
            InputStream in = new FileInputStream(caFile);
            ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(in, truststorePassword.toCharArray());
            in.close();
        } catch (KeyStoreException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex){
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (CertificateException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }catch (IOException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }

        try {
            context = SSLContext.getInstance("TLS");
            tmf = TrustManagerFactory.getInstance(TrustManagerFactory
													.getDefaultAlgorithm());
            tmf.init(ks);
            defTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
            context.init(null, new TrustManager[] {defTrustManager}, null);
            factory = context.getSocketFactory();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (KeyStoreException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        } catch (KeyManagementException ex) {
            Logger.getLogger(ServerSSLAuthSocketFactory.class.getName())
						.log(Level.SEVERE, null, ex);
        }

    }

    @Override
    public String[] getDefaultCipherSuites() {
        return factory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return factory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String string,
					int i, boolean bln) throws IOException {
        return factory.createSocket(socket, string, i, bln);
    }

    @Override
    public Socket createSocket(String destinationAddress,
					int destinationPort) throws IOException, UnknownHostException {
        return (SSLSocket)factory.createSocket(destinationAddress, destinationPort);
    }

    @Override
    public Socket createSocket(String string, int i,
					InetAddress ia, int i1) throws IOException, UnknownHostException {
        return factory.createSocket(string, i, ia, i1);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {
        return factory.createSocket(ia, i);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i,
					InetAddress ia1, int i1) throws IOException {
        return factory.createSocket(ia, i, ia1, i1);
    }
}

Da wir nun eine Factory haben die uns Sockets liefert, die auf unseren Truststore zugreift, können wir diese nun nutzen um eine Verbindung zu SSL Servern aufzubauen. Das Schöne hierbei ist, dass Java an sehr vielen Stellen SocketFactories nutzt, so können wir fast in allen Szenarien unsere Klasse einsetzen.

So können wir unsere SSLFactory nun ganz simpel testen:


public static void main (String [] args) throws IOException{

    int port = 443; // default https port
    String host = "httpsurl";

    try {
      SSLSocketFactory factory
       = (SSLSocketFactory) new ServerSSLAuthSocketFactory("","");

      SSLSocket socket = (SSLSocket) factory.createSocket(host, port);

      // enable all the suites
      String[] supported = socket.getSupportedCipherSuites();
      socket.setEnabledCipherSuites(supported);

      Writer out = new OutputStreamWriter(socket.getOutputStream());
      // https requires the full URL in the GET line
      out.write("GET https://" + host + "/ HTTP/1.1\r\n");
      out.write("Host: " + host + "\r\n");
      out.write("\r\n");
      out.flush();

      // read response
      BufferedReader in = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));

      // read the header
      String s;
      while (!(s = in.readLine()).equals("")) {
          System.out.println(s);
      }
      System.out.println();

      // read the length
      String contentLength = in.readLine();
      int length = Integer.MAX_VALUE;
      try {
        length = Integer.parseInt(contentLength.trim(), 16);
      }
      catch (NumberFormatException ex) {
        // This server doesn't send the content-length
        // in the first line of the response body
      }
      System.out.println(contentLength);

      int c;
      int i = 0;
      while ((c = in.read()) != -1 && i++ < length) {
        System.out.write(c);
      }

      System.out.println();
      out.close();
      in.close();
      socket.close();

    }
    catch (IOException ex) {
      System.err.println(ex);
    }
}

Tragt in Zeile 4 einfach den gewünschten SSL-Host ein wie z.B. Google oder Facebook und startet das kleine Programm. Falls ihr keine Parameter beim Erzeugen der Factory (Zeile 7 und 8) angegeben habt solltet ihr auch keine Probleme beim Verbinden haben und wenn das Programm durchgelaufen ist seht ihr die Antwort des Servers im HTML Format.

Falls ihr doch einen eigenen Truststore eingetragen habt müsst ihr sicherstellen, dass dieser den Public Key des Servers zu dem ihr euch verbinden wollt beinhaltet, sonst wird die Verbindung nicht erfolgen.

Teil 1: Serverseitige Authentifizierung und Zertifikate
Teil 2: Beidseitige Authentifizierung
Teil 3: Der SSL Handshake
Teil 4: Serverseitige Authentifizierung mit Java

5Aug/110

Java: SubjectDN eines X509Zertifikates zu OpenSSL kompatibel machen

Java stellt uns zum angenehmen Arbeiten mit X509-Zertifikaten die Klasse java.security.cert.X509Certificate zur Verfügung. Hier können wir uns sehr einfach über die methode .getSubjectDN().getName() den Distinguished Name des Zertifikats besorgen.

Wenn man in der Javawelt bleibt ist das auch alles ok und funktioniert Problemlos. Will man aber andere Programme wie zum Beispiel OpenSSL anbinden, bekommt man hier ein Problem, denn OpenSSL liefert einen anderen SubjectDN String als unsere Java Klasse.

Wobei mit anders meine ich, dass die Reihenfolge der ausgegebenen Parameter anders ist, hier ein Beispiel:

Ausgabe von OpenSSL (über die X509_NAME_oneline Methode):

/C=DE/ST=NRW/L=Bielefeld/O=ORG/OU=AB1/CN=130.122.146.121

Wogegen die Java-Klasse folgenden String liefert (.getSubjectDN().getName()) :

CN=130.122.146.121, OU=AB1, O=ORG, L=Bielefeld, ST=NRW, C=DE

Die Reihenfolge scheint genau umgekehrt zu sein und auch die Trenner sind anders, und wenn man den String nun als Auth-String benutzen will bekommt man ein Problem.

Leider bietet uns Java hier keine Möglichkeit den String anders zu sortieren, man kann zwar über die Methode getSubjectX500Principal.getName(String format) einen RFC als Parameter mitgeben, welcher die Sortierung etwas anpasst, aber teilweise noch komischere Ergebnisse liefert.

Hier bleibt einem nun leider nichts anderes übrig, als selbst einzugreifen und die Sortierung selbst vorzunehmen. Dazu habe ich eine kleine Klasse geschrieben die das für euch erledigt. Diese hat zwei Methoden, eine die einen OpenSSL Kompatiblen String ausgibt und eine bei der ihr die Reihenfolge der Parameter selbst bestimmen könnt:

 


/**
 * Teilt die Informationen aus dem X509Certificate.SubjectDN in die
 * einzelnen Bestandteile auf und macht diese einzeln verfügbar
 * @author sebastian gross blog.bigbasti.com
 */
public class CertificateDataSeperator {

    private String _C = "";
    private String _ST = "";
    private String _L = "";
    private String _O = "";
    private String _OU = "";
    private String _CN = "";
    private String _EMAILADRESS = "";

    /**
     * Erstellt eine neue instanz
     * @param cert Das Zwetifikat aus dem die Daten gelesen werden sollen
     * @param seperator Die Zeichenfolge ander die parameter
     *        getrennt werden sollen, bei Java ist Standard ", "
     */
    public CertificateDataSeperator(X509Certificate cert, String seperator){
        String [] subjectDNData;

        subjectDNData = cert.getSubjectDN().getName().split(seperator);

        seperateData(subjectDNData);
    }

    /**
     * Duchläuft das Array und ordnet die Informationen
     * @param data Array mit dem SubjectDN String
     */
    private void seperateData(String [] data){
        for(String s : data){
            if((s.toUpperCase()).startsWith("C=")){
                _C = s;
            }else if((s.toUpperCase()).startsWith("ST=")){
                _ST = s;
            }else if((s.toUpperCase()).startsWith("L=")){
                _L = s;
            }else if((s.toUpperCase()).startsWith("O=")){
                _O = s;
            }else if((s.toUpperCase()).startsWith("OU=")){
                _OU = s;
            }else if((s.toUpperCase()).startsWith("CN=")){
                _CN = s;
            }else if((s.toUpperCase()).startsWith("EMAILADRESS=")){
                _EMAILADRESS = s;
            }
        }
    }

    /**
     * Generiert einen angepassten SubjectDN String
     * @param params Die gewünschten Werte in der gewünschten Reihenfolge als Array
     * @param seperator Die Zeichenfolge, die zum Trennen der Parameter genutzt werden soll
     * @return Einen angepassten SubjectDN String
     */
    public String getNewDNString(String [] params, String seperator){
        StringBuilder sb = new StringBuilder();

        for(String s : params){
            if((s.toUpperCase()).equals("C")){
                sb.append(_C);
            }else if((s.toUpperCase()).equals("ST")){
                sb.append(_ST);
            }else if((s.toUpperCase()).equals("L")){
                sb.append(_L);
            }else if((s.toUpperCase()).equals("O")){
                sb.append(_O);
            }else if((s.toUpperCase()).equals("OU")){
                sb.append(_OU);
            }else if((s.toUpperCase()).equals("CN")){
                sb.append(_CN);
            }else if((s.toUpperCase()).equals("EMAILADRESS")){
                sb.append(_EMAILADRESS);
            }

            if(!s.equals(params[params.length-1])){
                sb.append(seperator);
            }
        }

        return sb.toString();
    }

    /**
     * Erzeugt einen DN String in dem Format, wie ihn auch die
     * X509_NAME_oneline Methode von OpenSSL generiert, dies ist
     * auch das Format, welches von dem Exim Email Server genutzt wird.
     * Das Forman ist wiefolgt: /C=$/ST=$/L=$/O=$/OU=$/CN=$
     * @return OpenSSL
     */
    public String getOpenSSLCompatibleDNString(){
        StringBuilder sb = new StringBuilder();

        sb.append("/").append(_C).append("/").append(_ST).append("/").append(_L).append("/")
          .append(_O).append("/").append(_OU).append("/").append(_CN);

        //EMAILADRESS Ist ein optionales Feld und könnte nicht gesetzt sein
        if(_EMAILADRESS != null){
            sb.append("/").append(_EMAILADRESS);
        }

        return sb.toString();
    }

    public String getC() {
        return _C;
    }

    public String getCN() {
        return _CN;
    }

    public String getEMAILADRESS() {
        return _EMAILADRESS;
    }

    public String getL() {
        return _L;
    }

    public String getO() {
        return _O;
    }

    public String getOU() {
        return _OU;
    }

    public String getST() {
        return _ST;
    }
}

Mit dieser kleinen Klasse ist es sehr einfach einen OpenSSL kompatiblen SubjectDN zu erhalten:


X509Certificate myCert = session.getClientCertificate();
CertificateDataSeperator sep = new CertificateDataSeperator(myCert, ", ");
String dn = sep.getOpenSSLCompatibleDNString();

Wenn man selbst die Reihenfolge der parameter bestimmen will kann man das z.B. so machen:


X509Certificate myCert = session.getClientCertificate();
String [] myParams = new String[]{"O", "CN", "L"};
CertificateDataSeperator sep = new CertificateDataSeperator(myCert, ", ");
String dn = sep.getNewDNString(myParams, "|");

Nun soll erst O, dann CN und zum Schluss L ausgegeben, jeweils von einem | (pipe) getrennt.

Das Ganze lässt sich natürlich je nach verwendeten optionalen SSL Parametern noch beliebig erweitern, und ist eine einfache und schnelle Methode um den SubjectDN neu zu sortieren.

4Aug/110

Visual Studio 2010 und das Intellisense im Vergleich

Da ich Beruflich auch viel im Javaumfeld arbeite, komme ich in Kontakt mit verschiedenen IDEs. Am besten gefällt hier das kostenlose NetBeans von Sun Oracle. Und bereits nach kurzer Zeit sieht man die vielen Unterschiede der IDEs, besonders fällt hier aber der Unterschied des Code Completion Features der IDEs auf.

Im Visual Studio haben wir Intellisense kennen- und lieben gelernt, es ist schnell und liefert uns die Informationen die wir haben wollen. Doch schaut man hier mal auf das andere Ufer, wird man schnell feststellen, dass hier doch tatsächlich noch Nachholbedarf besteht.

Schauen wir uns beispielsweise ein typischen Intellisense Pupup an, der uns Informationen zu der Methode Create() der HttpWebRequest-Klasse liefert:

Intellisense Pupup

Intellisense Pupup

Wir bekommen eine Auflistung der uns zur Verfüfung stehenden Methoden (1) und die markierte Methode wirft noch ein weiteres Popup auf, welches uns den Methodenkopf, eine Kurze Beschreibung und die möglichen Exceptions liefert (2).

Eigentlich doch alles was manbraucht, stimmt! Vergleichen wir das Ganze doch mal mit dem "Intellisense" von NetBeans 7:

NetBeans Popup

NetBeans Popup

Was haben wir hier, als erstes taucht, wie im Visual Studio auch, eine Liste mit dem möglichen Befehlen auf (1), diese ist hier etwas großzügiger gestaltet und nimmt etwas mehr Platz auf dem Bildschirm ein, liefert uns aber auch auf den ersten Blick den Return-Typ der Funktion (das was rechtsbündig in der Liste steht) - Im VS müsste man erst die Methode auswählen und auf den "Hilfspopup" warten um an diese Information zu kommen.

Als zweites taucht auch hier ein weiteres Popup auf, mit Details zur Funktion an sich (2). Und das ist der Teil wo es interessant wird, denn hier bekommen viel mehr Informationen als im VS.
Als erstes fällt auf, dass es viel mehr zu lesen gibt, hier wird nämlich das dazugehörige Javadoc geladen und angezeigt, dieses bietet natürlich eine Fülle an Informationen und sogar Beispielcode für die gerade gewählte Funktion.

Um diese Informationen im VS zu erhalten müssen wir die Funktion markieren und dann [F1] drücken, damit sich dann der Browser öffnet und uns zu dem passenden Artikel auf MSDN navigiert, wo wir dann alle nötigen Infos inkl. Beispiele finden, aber warum der Umweg?

Als nächstes fällt auf, dass das in dem zweiten Popup auch Links auftauchen (3), diese kann man natürlich auch anklicken und man wird innerhalb des kleinen Fensters zu dem geklickten Dokueintrag weitergeleitet - das ist wirklich sehr sehr praktisch! Dazu kommt noch oben eine kleine Navigation (4) um zwischen den besuchten Seiten zu wählen, den Artikel im Browser zu öffnen oder direkt zu der gewählten Klasse zu wechseln.

Das macht wirklich Spaß mit dieser Hilfe zu arbeiten und man kann schnell die nötigen Informationen nachschlagen ohne überhaupt die Maus bewegen zu müssen.

Natürlich bin ich mit dessen bewusst, dass es auch für das VS Erweiterungen gibt wie ReSharper oder CodeRush, die sich dieser "Schwäche" annehmen und versuchen es zu verbessern, diese kosten aber erstens Geld und machen aber auch nicht alles perfekt.

Aber es gibt auch Sachen die mich umgekehrt an NetBeans extrem stören, wie zb. die Tatsache, dass NetBeans sich nicht mehrkt welchen Befehl ich als letztes benutzt habe.

Sehr sehr nervig!

Wenn wir das obere Bild anschauen, will ich die methode callVRFY() aufrufen und das mehr fach hintereinander. NetBeans wird mir jedes Mal den Vorschlag genau so machen wie wir ihn hier sehen, ich muss also jedes Mal 12x [Pfeil nach unten] drücken um den Eintrag auszuwählen oder den Namen direkt voll ausschreiben.

Visual Studio reagiert hier deutlich intelligenter, denn es merkt sich, dass man diese Methode nun 2x benutzt hat und schlägt beim nächsten Mal wenn man "call" tippt direkt die callVRFY() Methode als erstes vor.

Eine weitere Sache die bei Visual Studio genial ist, ist die Tatsache, dass ich eigentlich den "call"-Teil des Methodennamen überspringen kann und direkt anfangen kann "VRFY" zu tippen, Intellisense würde dennoch erkennen, dass ich die "callVRFY" Methode meine und diese für mich auswählen.

Beispiel:

Visual Studio Popup

Wie man sieht schlägt mit VS hier alle Klassen & Methoden vor die "response" im Namen haben. - Super!

Das sind nun natürlich zwei "Kleinigkeiten" die ich mir hier herausgepickt habe, aber glaubt mir wenn man plötzlich auf diese verzichten muss ist das mehr als nervig!

Zum Schluss kann ich sagen, dass beide IDEs von ein an der lernen können und dass ich, egal mit welcher IDE ich gerade arbeite das ein oder andere Feature aus der anderen IDE vermisse. Bleibt noch zu hoffen, dass die Entwickler sich gegenseitig auf die Finger schauen und diese netten kleinen Features gegenseitig "ausleihen" ;)

Wie steht ihr dazu? Seit ihr mit dem Intellisense glücklich, nutzt ihr Erweiterungen (ReSharper etc..)?

6Jan/113

Java: Webservice anlegen und konsumieren

Aus gegebenen Anlass gibt es heute mal ein kleines Tutorial wie man in Java ein WebService anlegt und diesen dann benutzt. Das ganze wird mit NetBeans 9.6.1 als IDE und einem Glassfish als Application Server realisiert.

Erstmal kurz: was ist ein WebService?
Ein WebService unter Java ist (vergleichbar mit RMI) eine Art Dienst, der sowohl von anderen Anwendungen als auch von Webseiten genutzt werden kann. Anders als eine gewöhnliche Webseite besitzt ein WebService kein eigenes Benutzerinterface sondern stellt nur in einer Schnittstellenbeschreibung seine Dienste nach außen zur Verfügung.

Diese Schnittstellenbeschreibungen werden in WSDL Dateien (Web Service Description Language) gespeichert, diese sind XML Dokumente, die genau beschreiben, welche Methoden der WebService bietet und wie diese angesprochen werden. Glücklicherweise müssen wir diese WSDL Dateien nicht selbst erstellen sondern können uns diese generieren lassen, aber dazu gleich mehr.

Kommunikation mit dem WebService
Unser WebService wird dann später über das SOAP Protokoll (Simple Object Access Protocol) kommunizieren, dieses ist ebenfalls XML basierend und auch hier müssen wir nicht selbst Hand anlegen sondern lassen alles für uns generieren ;)

In diesem Beispiel werden wir einen einfachen WebService erstellen und eine gewöhnliche App, die dann mit dem WebService kommunizieren wird. Wir werden hier die NetBeans IDE 6.9.1 benutzen, diese wird uns hierbei viel Arbeit abnehmen, sodass der unangenehme Teil der Arbeit klein gehalten wird ;)

Downloads - falls nicht bereits vorhanden
NetBeans - Download
Glassfish - Download
JDK - Download

Legen wir mal los

Als Erstes sollten wir damit starten den WebService zu erstellen. Dazu legen wir ein neues Web Application Projekt an und nennes es "CheckUser" die Restlichen Dialoge könnt ihr unverändert lassen und schließlich auf Fertigstellen klicken.

Nun sollte das Projekt angelegt sein und ihr solltet den Quelltext der index.jsp vor euch haben. Diese Datei benötigen wir nicht da wir einen WebService erstellen wollen, deswegen könnt ihr diese Löschen, wenn ihr mögt.

Nun müssen wir einen neuen WebService anlegen, das machen wir, indem wir auf unser Projekt rechtsklicken und über "New -> WebService" wählen. Nennt den Service "UserDB" und packt es in das "com.bigbasti.db.user.service" Package rein. Zum Schluss macht ihr noch den Haken bei "Implement Web Service as Stateless Session Bean" rein und klickt auf Fertig.

Was ist nun passiert? NetBeans hat für uns nun einen neuen Ordner im Projekt namens "Web Services" angelegt und auch eine neue Enterprise Bean die so heißt wie unser Service ist im Projekt aufgetaucht. NetBeans kümmert sich größtenteils selbst um diese automatisch generierten Beans so sollten wir erst einmal die Finger davon lassen.

Nun solltet ihr die UserDB.java vor euch aufgeklappt sehen, diese Klasse ist mit der Annotation Stateless und WebService markiert. Da ein WebService mindestens eine Methode (Operation besitzen muss wird euch wahrscheinlich ein Fehler angezeigt. Das wollen wir nun ändern und fügen dem Service eine neue Operation hinzu.

Macht dazu einen Rechtsklick innerhalb der UserDB Klasse und klickt auf "Insert Code..." wählt hier nun "Add Web Service Operation...". In dem nun angezeigten Fenster solltet ihr eure Operation "checkUserName" nennen und den Rückgabetyp auf Boolean stellen. Desweiteren fügt ihr einen Parameter hinzu und tauft ihn "userName".

Bestätigt das Fenster mit "OK" und ihr solltet folgendes zu sehen bekommen:

Diese neue Methode ist mit "WebMethod" annotiert und weist so darauf hin, dass auf diese von außen zugegriffen werden darf. Alle Parameter sind ebenfalls annotiert sodass diese zugreifbar sind. Diese Annotation ist wichtig, damit wir später WSDLs und Requests generieren können. Beim Arbeiten mit dieser Methode könnt ihr diese aber für euch ausblenden ;)

Diese erzeugte Methode funktioniert genauso wie jede andere Java Methode, somit könnt ihr mit ihr wie gewohnt arbeiten. Diese Methode soll für uns prüfen, ob ein Benutzername bereits von jemandem verwendet wird. Da wir das simel halten wollen nutzen wir eine LinkedList als Datenbank :) So können wir eine einfache Prüffunktion implementieren:

Nun haben wir einen WebService mit einer Operation. Diese wollen wir nun mal testen. Dazu verwenden wir zunächst die Test-Tools vom Glassfish Server. Dazu müssen wir unsere Web Application zunächst Deployen (Veröffentlichen), das machen wir ganz einfach indem wir das Projekt rechts-klicken und dann "Deploy" wählen. Nun wird der Glassfish Server gestartet, die Anwendung kompiliert und auf dem Server veröffentlicht.

Wenn dieser Vorgang erfolgreich war könnt ihr im "Services"-Tab unter "Servers->GlassFish Server 3" euere Web App sehen (Siehe Bild 11). Nun wollen wir mal gucken ob unsere Operation funktioniert, dazu macht ihr einen Rechtsklick auf dem "GlassFish Server 3" und wählt View Admin Console (Bild 12).

Nun könnt ihr euch einen Kaffee holen...

Wenn die Admin Console irgendwann offen ist, und es kann dauern, klickt ihr auf "Anwendungen" und wählt eure "CheckUser" App. In dem "Allgemein"-Tab seht ihr nun unten eine Tabelle mit allen Komponenten eurer App, sucht hier unseren Service "UserDB" und klickt auf "Endpunkt anzeigen".

Hier seht ihr nun alle wichtigen Informationen zu eurem Web Service, für uns ist im Moment die Tester URL wichtig (Roter Pfeil) Diese öffnen wir mal in einem neuen Tab.

Was wir hier sehen ist die Testseite für unseren Dienst. Hier hat der Glassfish für jede von uns erstellte Operation (momentan haben wir nur eine) eine Testmethode erstellt. Diese Testmethode macht nichts anderes, als eine SOAP Nachricht zu generieren und an unseren Dienst zu senden und uns die Antwort von diesem anzuzeigen.

Probieren wir das Ganze mal aus, probiert einfach mal ein paar Namen durch und verwendet auch ab und zu einen der von uns Reservierten Namen, ihr werdet feststellen, dass unsere Methode super funktioniert.

Dabei zeigt das Test-Tool auch an welche SOAP Requests es generiert und welche Antworten unser Service gibt, so wird folgender SOAP Aufruf getätigt um zu prüfen, ob der Name vorhanden ist:

Hier finden wir alle unsere Werte aus der Klasse wieder. Der Name der Operation die wir mit diesem Request aufrufen wollen (Rot), diese Operation benötigt einen Parameter namens "userName" den wir hier angeben (Blau) und der Wert für diesen Parameter (Orange). Das Ganze ist dann noch in einen SOAP Envelope verpackt und wird so zum Server geschickt.

Dieser führt die gewünschte Operation aus und gibt uns eine Antwort:

Die Response ist weitestgehend genauso aufgebaut die der Request. Der Knoten hat nun noch "Response" an den Namen der Methode/Operation angehängt bekommen (Rot) und auch das Ergebnis der Operation ist angegeben (Blau).

Das Ergebnis können wir nun mit Hilfe eines Parsers auslesen, aber dazu gleich mehr. Nach dem Test wissen wir nun, dass unser dienst läuft und auch dass die Operation zuverlässig arbeitet, Zeit unsere "Consumer"-Applikation zu bauen.

Web Service benutzen (konsumieren)

Nun haben wir also einen Web Service laufen, und natürlich wollen wir diesen nun auch nutzen (konsumieren ist das Fachwort), aus einer App mit der sich Benutzer irgendwo registrieren können. Diese App muss natürlich irgendwie prüfen, ob der gewünschte Benutzername schon vorhanden (bzw. bereits benutzt) ist, das wird die App über unseren Web Service UserDB erledigen.

Legen wir zunächst ein neues Projekt in NetBeans an, diesmal eine normale Java Application, die wir "RegisterUser" nennen.

Nun müssen wir unserer frischen Applikation beibringen mit einem WebService zusammen zuarbeiten. Das macht man über so genannte Schnittstellen, denn diese beschreiben genau was möglich ist und wie man da dran kommt. Schnittstellen haben hier das Format WSDL, und wenn ihr gut aufgepasst habt habt ihr sicher schon gesehen dass der GlassFish für unser Service eine WSDL erstellt hat.

Geht die Schritte in den Bildern 13,14 und 15 durch (falls ihr den Tab schon geschlossen habt), sodass ihr wieder die URLs des Services angezeigt bekommt. Der Blaue Pfeil im Bild 15 zeigt euch die URL der WSDL, diesen link klickt ihr bitte an, sodass ihr die XML Datei zu Gesicht bekommt.

Auf den Inhalt werde ich hier nicht weiter eingehen, das könnt ihr euch ja bei Wiki durchlesen ;) Die URL zu der WSDL sieht bei mir wie folgt aus:

http://localhost:8080/UserDBService/UserDB?wsdl

Diese könnt ihr mal in die Zwischenablage kopieren, da wir diese gleich benötigen um einen Web Service Client zu erstellen. Das machen wir mit einem Rechtsklick auf das Projekt und wählen "New->Web Service Client". Hier wählt ihr als Bezugsort für die WSDL "URL" aus und fügt die URL ein. Als Paketnamen habe ich "com.bigbasti.user.register" gewählt.

NetBeans lädt nun die WSDL herunter und erzeugt daraus für euch die nötigen Klassen für den Zugriff auf diese Schnittstelle, deswegen sind einige neue Dateien bei euch im Projektordner aufgetaucht (Bild 24). Der etwas ausgegraute Ordner "Generated Sources" enthält hierbei die wichtigen Klassen und Methoden für das Erzeugen von SOAP Requests und für das Lesen von Responses. Diese Dateien solltet ihr auch möglichst nicht anfassen.

Nun sind wir komplett gerüstet um unseren Web Service zu nutzen. Der Code der dafür geschrieben werden muss ist auch nicht weltbewegend:

Als Erstes sind die imports wichtig, damit wir Zugriff auf die generierten Klassen haben. Mit denen können wir nun (Blaue Pfeile) die nötigen Instanzen der Service Objekte anlegen. Ist das erst geschafft können wir mit unserem UserDB Objekt, das den Service repräsentiert bequem arbeiten und die Service Operationen aufrufen, als ob es eine gewöhnliche kleine Klasse wäre (Oranger Pfeil).

Nachdem wir unsere Operation aufgerufen haben bekommen wir eine Antwort ob der Name schon benutzt wird oder nicht. So könnte es aussehen wenn man das obige Programm startet:

Wie man sieht funktioniert das Ganze sehr gut und es hat gerade mal ein paar Minuten gedauert diesen Service zu erstellen. Ihr könnt natürlich selber weiter herumspielen und weitere komplexere Operationen hinzufügen, die mit echten Datenbanken kommunizieren.

5Jan/115

Unbegrenzte Anzahl an Parametern uebergeben

Mir ist aufgefallen, dass viele gar nicht wissen, dass man Methoden so gestalten kann, dass diese eine unbestimmte menge an Parametern akzeptieren. Das gibt es sowohl in Java als auch in .NET (und wohl jeder anderen höheren Programmiersprache)

Wie sieht das Ganze aus? Naja wenn ihr schon mit C#/.NET Gearbeitet habt werdet ihr wohl nicht um diese Funktion herum gekommen sein:


string var = String.Format("Hallo mein Name ist {0} und ich komme aus {1}", "Bernd", "Kassel");

Die Zahlen in den Geschweiften Klammern sind hierbei Platzhalter, die später über eine Variable gefüllt werden. Von diesen Platzhaltern kann man beliebig viele angeben, dementsprechend müssen dann aber auch genauso viele Parameter mit den Werten angegeben werden.

Wie sieht hier die Deklaration dieser Methode aus?


public void DoSomething(int someValue, params string[] values)
{
    foreach (string value in values){
        Console.WriteLine(value);
	}
}

Diese Methode nimmt nun einen normalen int Parameter an und ab dann können beliebig viele Werte vom Typ String folgen:


DoSomething(12345, "Hallo", "Wer", "Bist", "Du");

Genauso kann man das auch in Java machen, hier ist es sogar etwas schöner gelöst wie ich finde:


//Methoden Deklaration
public DoSomething(int someValue, String... values) {
	for(String value : values){
		System.out.println(value);
	}
}

//Nutzung:
DoSomething(12345, "Hallo", "Wer", "Bist", "Du");

In Java werden hier nach dem Variablentyp drei Punkte angehängt die signalisieren, dass dieser Typ nun beliebig oft folgen kann.

Dabei wird intern aber auch nur ein Array mit den Werten erzeugt, das kann man bei der .NET Variante bereits in der Deklaration erkennen. So lässt sich in Java die main-Methode auch so schreiben, ohne dass es zu Problemen kommt:


public static void main(String... args) {
    for(String s : args){
        System.out.println(s);
    }
}

Es sei an dieser Stelle noch erwähnt, dass der Parameter, der beliebig oft folgen kann immer als letztes angegeben werden muss - aus offensichtlichen Gründen ;-)

Diese Schreibweise kann an einigen Stellen für deutlich bessere Lesbarkeit sorgen und vereinfacht einfach nur die Anwendung der Funktionen, sollte man auf jeden Fall schon mal gesehen haben.

5Jan/111

Fehler: Web Services testen unter NetBeans 6.9.1 (mit Glassfish 3)

Gestern habe ich ziemlich viel Zeit damit verbracht einen ziemlich nervigen Bug in NetBeans 9.6.1 zu umgehen, der auftritt, wenn man WebServices testen möchte.

Wenn ihr einen eigenen WebService anlegt wie zum beispiel in diesem NetBeans Tutorial beschrieben und diesen anschließend mit den Glassfish-eigenen Testern testen wollt werdet ihr diesen Fehler hier sehen:

Service Testen schlägt fehl

Service Testen schlägt fehl

Wie es sich nun herausstellte machen wir als User keinen Fehler, sondern NetBeans, denn NetBeans generiert für uns eine Falsche URL, die er dann dementsprechend nicht aufrufen kann und uns diesen Fehler anzeigt.

Interessant an dieser Stelle ist auch, dass dieser Fehler nur in Verbindung mit Stateless WebServices auftritt. Denn wenn man die @Stateless Annotation aus dem Code entfernt scheint es doch zu gehen.

Da man aber im Normalfall alle Services stateless sind, müssen wir uns die passende URL selber basteln. Die korrekte URL ist nämlich so aufgebaut:

http://localhost:8080/[ServiceName]/[ServiceKlasse]?Tester
in meinem obigen Fall wäre es dann

http://localhost:8080/myServiceService/myService?Tester

Die korrekte URL findet ihr übrigens auch auf der Glassfish Konsole im Bereich Anwendungen->[Anwendung]->[Euer WebService]->Endpunkt Anzeigen - hier stehen alle wichtigen URLs (WSDL, Tester, Service)

Das ist natürlich etwas blöd, da diese Praktische Funktion von NetBeans die sehr einfach mit nur einem Rechtsklick erreichbar ist nicht funktioniert, aber gut was solls, schreiben wir die URLs halt selbst ;)

Dieses Problem tritt bei der 7.0 Beta von NetBeans übrigens nicht mehr auf.

13Dez/102

Java: New oder nicht New?

Heute gibt es wieder mal nach langer Zeit ein Java Thema. Dieses Thema ist auf den ersten Blick sehr trivial und man schreibt das bestimmt vielfach am Tag ohne drüber nachzudenken.

Konkret gehts heute um return-Statements und auf welche Art diese einen Wert zurück geben. Ein Beispiel soll dies verdeutlichen, nehmen wir an wir haben eine toString() Methode. Diese kann man auf 2 Arten implementieren:


	public String toString(){
		return "hallo";
	}

	public String toString2(){
		return new String("hallo");
	}

Der Unterschied liegt auf der Hand, in der ersten Methode geben wir direkt eine Zeichenfolge zurück und in der zweiten erzeugen wir erstmal ein Objekt von Typ String und geben das dann zurück. Funktioniert beides, aber was benutzt du, und warum?

Denn so unscheinbar das auch aussieht gibt es hier Unterschiede, denn je nachdem welche der beiden Funktionen wir ausführen bekommen wir etwas anderes, ein Beispiel:


		String t1 = toString();
		String t2 = toString();

		String b1 = toString2();
		String b2 = toString2();

		System.out.println(t1.equals(t2));	//true
		System.out.println((t1 == t2));		//true

		System.out.println(b1.equals(b2));	//true
		System.out.println((b1 == b2));		//false

		System.out.println(b1.equals(t1));	//true
		System.out.println((b1 == t1));		//false

Interessant sind hier immer jeweils die zweiten Ausgabezeilen, denn diese sagen, dass wir den == Operator nicht benutzen dürfen und bekommen somit unterschiedliche Referenzen auf verschiedene Objekte zurück.

Nur nicht im ersten Fall, wo wir das Ergebnis ohne new zurück geben, dort können wir den == Operator verwenden - demnach bekommen wir immer exakt die gleiche Referenz zurück egal wie oft wir diese Methode aufrufen, aber wie kann das sein?

Die Antwort liegt wieder in den Grundlagen :) denn auch in der ersten Methode geben wir ein Objekt vom Typ String zurück. Dieses Objekt wird erzeugt wenn die Klasse gestartet wird und ist ab dann Klasseninventar, was unter anderem heißt dass es exakt nur ein einziges Mal vorhanden ist.

Daher geben alle Abfragen dieses Strings immer die eine Referenz auf diesen Klassenstring zurück, was dafür sorgt dass wir dem == Operator hier anwenden dürfen und auch folgendes funktioniert:


String a = "hallo";
String b = "hallo";

System.out.println(a == b);	//true

Da Strings in Java ebenfalls immutable ist können diese ihren Inhalt nicht ändern, womit die Referenz immer die selbe sein wird.

Warum nun das Ganze? Ich wollte einfach auf diesen kleinen aber doch wichtigen Unterschied hinweisen, da es da zu Fehlern kommen kann wenn man Ergebnisse vergleichen will und man das nicht weiß.

Get Adobe Flash player