Lotus Notes / Domino Sonstiges > Java und .NET mit Notes/Domino
wann brauchen wir SOAP Webservices und wann nicht?
flaite:
Dies wird nun fortgesetzt. Die Zeit zwischen dem letzten Posting in diesem Thread und jetzt umfasst so ziemlich meine schwerste Identitätskrise als Entwickler seit 2003.
Ich wende mich nun Punkt 2 zu: Client (Consumer) sendet das xml Dokument an einen Webservice Server (producer).
Technisch benötigt man das folgende:
Ein HTTP Post Request muss gegen die URL des Webservice producers gesendet werden. Der body des post requests (in webservice lingua: payload) enthält die xml Nachricht, die vom client an den server gesendet wird.
Domino bietet hier erst einmal nativ nichts an. Mir sind aber 2 Wege bekannt:
1. Läuft Domino/Notes auf Microsoft, können bestimmte Microsoft-Com Objekte genutzt werden. Julian Robichaux hat kürzlich zumindest für HTTP GET requests ein Beispiel geliefert. (http://www.nsftools.com/blog/blog-05-2007.htm#05-10-07)
2. Auch auf der Java Seite hat Julian Robichaux jetzt ein Beispiel gepostet (http://www.nsftools.com/blog/blog-05-2007.htm#05-10-07). Ich werde das auf jeden Fall in meine Erörterungen einbeziehen.
flaite:
Julians Lösung benutzt keine 3rd party openSource Bibliotheken wie jakarta.commons HTTPClient.
Das mindert natürlich den Aufwand für das Deployment.
Auf der anderen Seite ist das ziemlich low level und das kann leicht dazu führen, dass bestimmte Features nicht unterstützt sind und Fehler drin sind.
Die zentralen Klassen befinden sich in den Scriptlibraries. (Z.B. URLFetcher).
Aus meiner Sicht wird in
--- Code: ---public InputStream getUrlAsStream (String urlToGet)
--- Ende Code ---
das writer-Objekt nicht korrekt geschlossen.
--- Code: ---if (postParams.length() > 0) {
con.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(con.getOutputStream());
writer.write(postParams);
writer.flush();
}
--- Ende Code ---
Hier müsste nach flush() noch ein close() kommen. So was kann echt in Betrieb zu Problemen führen.
Die Lösung von Julian unterstützt eine Menge Features (basic authentification, ssl, proxies). Ich vermisse unterschiedliche Encoding schemes wie ISO-8859-1 oder UTF-8. In Webservice Projekten kommt es schnell vor, dass man sich damit beschäftigen muß. Aber auch dieser Support liesse sich leicht einbauen. Ich hab mich jedenfalls auf Julians blog höflich zu Wort gemeldet und werde vermutlich selbst eine Alternative bloggen, sobald das hier fertig ist.
Julian bietet die Datenbank als fertige nsf zum Download an.
Einfach auf data-root* eines Dominoserver kopieren. Unterzeichnen, Datei, Extras, Java Debug Console öffnen und den Agenten "Echo Webservice Test (Java)" starten und in Java Debug Console schauen. Dieser Agent benutzt die Skriptbibliothek URLFetcher und spricht den Domino-7-Webservice EchoTest derselben Datenbank an.
Folgendes wird zurückgeliefert:
Als response:
--- Code: ---<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Body>
<ns1:ECHOResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:DefaultNamespace"><ECHOReturn xsi:type="xsd:string">ECHO</ECHOReturn></ns1:ECHOResponse>
</soapenv:Body>
</soapenv:Envelope>
--- Ende Code ---
Als fetcher.getHeadersReceived():
--- Code: ---HTTP HEADERS RECEIVED
HTTP/1.1 200 OK
Server: Lotus-Domino
Date: Sat, 09 Jun 2007 07:58:04 GMT
Content-Type: text/xml; charset=utf-8
Content-Length: 515
--- Ende Code ---
Wie man bei response sieht, wird bei Webservices eine Menge "Paketmaterial" geliefert, um eine einfache Information einzupacken.
Inhalt:
--- Code: ---ECHO
--- Ende Code ---
"Paketmaterial":
--- Code: ---<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soapenv:Body>
<ns1:ECHOResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="urn:DefaultNamespace"><ECHOReturn xsi:type="xsd:string"> </ECHOReturn></ns1:ECHOResponse>
</soapenv:Body>
</soapenv:Envelope>
--- Ende Code ---
Performance-mässig muss diese ziemlich massive Packetierung nicht schlimm sein. Bei doc/literal Type Webservices steigt der Anteil der Information an der Gesamtnachricht mit dem Umfang der gesendeten Information. In dem Beispiel wird ja einfach nur ein String zurückgeliefert. In der realen Welt werden aber mit Webservices komplexere Datenstrukturen ausgetauscht (größerer Umfang der gesendeten Information).
Ein wirkliches Problem besteht aber darin, dass die Information aus der komplexen Nachricht erst einmal geparsed werden muss. Und dies sollte aus meiner Sicht in einer eigenen Schicht geschehen. In der Java-Welt gibts hierfür fertige Frameworks wie JAXB, die man aber aus meiner Sicht in Domino nicht einsetzen kann. Man kann aber ein eigenes Framework schreiben, das in Folge geschehen soll.
* Der Grund für "in data-root" legen ist einfach, dass die endpointURLs in den Beispielen hartcodiert sind:
--- Code: ---String endpoint = "http://localhost/URLFetcher.nsf/EchoTest?OpenWebService";
--- Ende Code ---
flaite:
Die Datenbank URLFetcher.nsf hat in der ACL Default als Manager stehen.
Was passiert nun, wenn ich default auf "Kein Zugriff" setze und einen technischen User (hier: admin Axel) auf Managerzugriff?
Auf der Java Console erscheint das hier:
--- Code: ---Returning NULL
There was an error: null
java.io.IOException
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:643)
at UrlFetcher.getUrlAsStream(UrlFetcher.java:389)
at UrlFetcher.getUrlAsString(UrlFetcher.java:450)
at JavaAgent.NotesMain(JavaAgent.java:28)
at lotus.domino.AgentBase.runNotes(Unknown Source)
at lotus.domino.NotesThread.run(Unknown Source)
Caused by: java.io.IOException: Server returned HTTP response code: 401 for URL: http://localhost/URLFetcher.nsf/EchoTest?OpenWebService
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:841)
at sun.net.www.protocol.http.HttpURLConnection.getHeaderFieldKey(HttpURLConnection.java:1546)
at UrlFetcher.getUrlAsStream(UrlFetcher.java:375)
... 4 more
HTTP HEADERS RECEIVED
HTTP/1.1 401 Unauthorized
Server: Lotus-Domino
Date: Sat, 09 Jun 2007 11:17:29 GMT
Connection: close
Expires: Tue, 01 Jan 1980 06:00:00 GMT
Content-Type: text/html; charset=US-ASCII
Content-Length: 146
WWW-Authenticate: Basic realm="/"
Cache-control: no-cache
--- Ende Code ---
Der obere Teil zeigt, dass es bzgl. Errorhandling eindeutig Raum für Verbesserungen gibt.
Im Stacktrace findet sich kein Hinweis auf das Problem -> User ist wg. ACL nicht autorisiert.
Die Klasse URLFetcher enthält eine Methode, um mit HTTP Basic Authentification umzugehen.
Nämlich:
--- Code: --- /**
* For web sites that require basic authentication, encode the username
* and password and add the authentication header. Note that this
* method will have to be called again if you call clearHeaders().
* Also, if the website uses session-based authentication, you normally
* have to add the session header yourself.
*/
public void setBasicAuthentication (String username, String password) {
addHeaderValue("Authorization",
createAuthHeader(username, password));
}
--- Ende Code ---
Leider funktioniert das mit Julians code nicht.
Zumindest nicht, wenn ich Notes7 benutze.
Ich bekomme folgenden Fehler:
--- Code: ---java.lang.IllegalArgumentException: Illegal character(s) in message header value: BasicQWRtaW4gQXhlbDprZW5ud29ydA==
--- Ende Code ---
Eine Erklärung findet sich hier:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4615330
Das von Julian benutzte encodeBuffer hängt offenbar \n characters an und das kann nicht funktionieren.
Auch der Workaround encode durch encodeBuffer in URLFetcher.java zu ersetzen funktioniert nicht.
Ich werde in Folge eine eigene Lösung mit apache.jakarta.HTTPClient schreiben und das Problem melden.
HA. Nun hab ich doch einen Workaround gefunden
URLFetcherJava.createAuthHeader in der SkriptBibliothek muss wie folgt aussehen ->
--- Code: ---/**
* Helper to base64-encode the authorization strings.
*/
private String createAuthHeader (String username, String password) {
String auth = "";
if (username == null) { username = ""; }
if (password == null) { password = ""; }
try {
sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
String str = username + ":" + password;
// patch start
auth = "Basic " + encoder.encode(str.getBytes());
// patch end
} catch (Exception e) {
}
return auth;
}
--- Ende Code ---
Im Code des Agenten "Echo Web Service Test (Java)" muss die letzte der folgenden Zeilen eingefügt werden, um das nach der ACL Änderung wieder ans laufen zu bringen. Die letzte Zeile ist neu.
--- Code: ---public void NotesMain() {
try {
// MODIFY: make sure the endpoint is right, and make sure that
// the local http task is running if you do this on localhost
String endpoint = "http://localhost/URLFetcher.nsf/EchoTest?OpenWebService";
UrlFetcher fetcher = new UrlFetcher();
fetcher.setBasicAuthentication("Admin Axel", "kennwort") ;
--- Ende Code ---
Julians Klasse URLFetcher ist schlauer-weise also so aufgebaut, dass das Feature "Unterstützung von Basic Authentification" durch eine einfache set-Methode eingebunden werden kann.
Und genau das ist "DEPENDENCY INJECTION". Das Object UrlFetcher sucht sich die Information über den User, der für die Autorisierung genutzt nicht selbst. NEIN. Diese Information WIRD VON AUSSEN INJEZIERT. Dependency Injection als Design Pattern hat sich in verschiedenen Objekt Orientierten Sprachen auch über Frameworks wie Spring oder Guice in den letzten Jahren sehr stark verbreitet. Hier haben wir einen Fall von nicht-frameworkgestützter programmatischer Dependency Injection, aber es implementiert das Pattern.
flaite:
Julian hat meinen Fix-Vorschlag angenommen und will diesen bald in die Datenbank zum Download einarbeiten (Kommentar 9):
http://www.nsftools.com/blog/blog-05-2007.htm#05-22-07
--- Zitat ---Also, thanks very much for the link to the HTTP authentication bug, and your workaround code. Especially the workaround code! It's always the little things that get you. I'll make the change in the download database.
--- Ende Zitat ---
Hey. Das sind Consumer/Client Side Webservice Endpoints ;D . The Hotness.
Etwas, dass es in Notes offiziell gar nicht gibt.
In der realen Welt kann man damit prima SAP Webservices und eine Menge mehr integrieren.
Ich hab folgende Pläne:
-> Einbau von apache.jakarta.commons.HttpClient als robustere openSource Lösung (bis 24.6.)
-> Demos mit vorhandenen realen Webservices (bis 19.6.)
-> wirklich gutes Errorhandling (bis 28.06.)
-> ein XML-To-Java Binding Framework bis 01.07.)
Gruß Axel
flaite:
Hier ist nun der einfachste Code mit jakarta.commons HTTP Client. Das macht einfach nur ein HTTP-GET gegen google. Wird noch deutlich weiter ausgearbeitet.
apache.Commons HTTPClient besitzt eine Menge von sinnvoller bis mission critical Zusatzfeatures:
- allgemein konfortabler
- Authentifizierung gegen Proxy
- SSL-Unterstützung
- Unterstützung der HTTP Methoden: GET, HEAD, POST, PUT und DELETE
- einfacher lesender und schreibender Zugriff auf HTTP Bodys
- unterstützt Cookies.
--- Code: ---import lotus.domino.*;
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
} catch(Exception e) {
e.printStackTrace();
}
HttpClient client = new HttpClient();
String url = "http://www.google.com";
HttpMethod method = new GetMethod(url);
try {
client.executeMethod(method);
if (method.getStatusCode() == HttpStatus.SC_OK) {
String response = method.getResponseBodyAsString();
System.out.println("response=--------------------\n" + response);
}
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
method.releaseConnection();
}
}
}
--- Ende Code ---
Etwas komplizierter ist die Einbindung der von jakarta.commons.HTTPClient benötigten Bibliotheken. Man kann die einfach in das lib/ext Verzeichnis der JVM des Notes Clients/Notes Servers packen.
Oder in JavaUserClasses in der Notes-ini. Man kann die auch an den Agenten binden.
Zunächst müssen die benötigten jars sowieso runtergeladen werden. Ich habe festgestellt, dass die 3.01 Version Probleme mit Java 1.4 macht. Offenbar haben die von apache.jakarta beim Kompilieren nicht aufgepasst. ;D
Deshalb sollte man sich die binary Version der 3.1 rc1 Version herunterladen:
http://jakarta.apache.org/site/downloads/downloads_commons-httpclient.cgi
Die entsprechende jar befindet sich im Root des downloads.
oder EINFACHER aber OHNE DOKU bei maven: http://www.ibiblio.org/maven/commons-httpclient/jars/ (Datei commons-httpclient-3.1-rc1.jar)
Gleichzeitig werden die folgenden beiden jars benötigt:
- commons-codec-1.3.jar (gibts bei maven: http://www.ibiblio.org/maven/commons-codec/jars/)
- commons-logging-1.3.jar (gibts bei maven: http://www.ibiblio.org/maven/commons-logging/jars/)
Alle diese jars müssen jetzt in ein und dasselbe Verzeichnis kopiert werden.
Dann kann über die Schaltfläche Projekt bearbeiten in der Notes Designersicht des Agenten eine Dialogbox geöffnet werden, mit der diese jars hinzugefügt werden können (s. Screenshot).
Dies ist der Schlüssel, Domino als Webservice Client zu verwenden. Ok. Die WS-* Specs kriegt man so auch nicht unterstützt, aber die sind eh noch in Arbeit. Ansonsten kann man damit aber von SOAP-Zugriffen auf SAP bis rebellischen REST Webservices in Domino alles als Client verarbeiten. Ich werde das noch weiter ausführen.
Gruß Axel
Navigation
[0] Themen-Index
[#] Nächste Seite
[*] Vorherige Sete
Zur normalen Ansicht wechseln