Hallo,
Wichtig: Funktioniert für Version ab Notes 7. Ist extra Java1.4 kompatibel programmiert.
ich hab in der Vergangenheit in verschiedenen Projekten apache.jakarta HttpClient eingesetzt, um von Notes Anwendungen heraus mit HTTP zu kommunizieren. Oft waren das irgendwelche Arten Webservices. Zwar verfügte Notes ab 6.5 (glaub ich) über SOAP Consumer und Producer, aber diese Implementierung hatte in der Praxis auf Consumer-Seite ihre Tücken, insbesondere wenn der Producer Datentypen schickte, die Notes unbekannt waren. Noch problematischer war, dass viele Webservice-Producer überhaupt nicht mit SOAP arbeiteten sondern einfach nur eine HTTP Schnittstelle anboten oder/und auch die immer beliebtere REST-Architektur benutzen.
Um sowas zu integrieren benötigt man vor allem einen flexibel programmierbaren HTTPClient und den liefert halt jakarta HTTPClient.
Selbstverständlich lassen sich damit auch Dinge angehen, die vielleicht erstmal gar nicht als Webservices wahrgenommen werden, es aber in gewisser Weise sind. Z.B. das Herunterladen von PDF Dateien über eine Web-URL, wobei der Server ein aktives Http-Session Cookie erwartet.
Ich habs häufiger angekündigt das OpenSource zu stellen und jetzt komm ich endlich dazu. Das ganze ist angestossen worden, durch einen Thread im hiesigen Notes 7 Forum.
Wer also einen entsprechenden Anwendungsfall hat, kann sich jetzt an mich wenden. Das ganze befindet sich aktuell nominell in der Version 0.5.0 und das ist eher optimistisch. Konkrete Anwendungsfälle unterstützen die weitere Ausgestaltung des Designs sicher positiv.
Kurze architektonische Beschreibung.
Thematisch geht es immer um einen HTTP Request, der von Notes initiiert wird und dessen zurückkommender HTTP-Response verarbeitet werden soll.
Das Framework ist modular konzeptiert, um möglichst viele Anwendungsfälle mit minimalen Programmieraufwand zu unterstützen.
Ein HTTP-Request (GET, in Kürze auch auch POST) wird über HashMaps (ähnlich wie LotusScript List) konfiguriert.
Dabei wird
- url gesetzt
- bestimmt ob redirect Antworten des Servers automatisch gefolgt werden soll
- der Proxy der Organisation für ausgehende HTTP-Requests wird gesetzt, optional mit Username und Kennwort.
- Es gibt die Möglichkeit die Anzahl von Retry-Versuchen anzugeben.
- Username/Passwort können gesetzt werden, falls die Zielwebseite Authentifizierung erfordert (noch nicht implementiert, kein Problem)
- ssl wird unterstützt (noch nicht implementiert)
Für den zurückgelieferten Response vom entfernten Server gibt es ebenfalls mit HashMaps konfigurierbar Handler
- Unterschiedliches Verhalten je nach zurückgelieferten HTTP-Status Code.
- Es werden die zurückgelieferten Cookies ausgelesen.
- Der Body des Response wird als Stream zurückgegeben
- Der Body des Response wird als String zurückgegeben.
- etc.
Hier 2 Beispiele aus meinen junit-Integrationstests
java.util.Properties ist eine HashMap. Die entsprechenden Werte müssen natürlich nicht hartkodiert werden, sondern können etwa aus einem NotesDocument gelesen werden. Sie müssen auch nicht in java Properties File gespeichert werden, sondern können selbstverständlich in NotesItems gespeichert werden.
Anwendungsfall 1 auslesen der Cookies aus dem Response
public void testRetrieveResponse() throws Exception {
Properties props = new Properties(); // HashMap zur Konfiguration
props.put("url", "http://someWebsite.de/");
props.put("de.aja.preparecall.AddFollowRedirects.followRedirects", "false"); // FollowRedirect wird standardmässig immer benutzt. Festgelegt, dass Redirects nicht gefolgt wird.
props.put("clazzProcessReturn", "de.aja.docall.ReturnCookiesHandler"); // die Cookies der Response werden ausgelesen
HttpClientInvoker invoker = HttpClientInvoker.getDefaultHttpClientInvoker(props); // Einstiegsklasse des Frameworks
Object res = invoker.doInvoke(new GetMethod()); // Aufrufmethode des Frameworks. Gibt HTTP Get Aufruf
System.out.println("res=" + res);
Properties propsCookies = new Properties();
// Inhalt der Cookies wird als String in ein java Properties File geschrieben, kann auch in NotesItems geschrieben werden.
propsCookies.put("globals.requestHeader.Cookie", res);
System.out.println(propsCookies);
propsCookies.store(new FileOutputStream("cookies.properties"), "Generated by Program");
}
Anwendungsfall 2: Speichern eines zurückgelieferten PDF Files, falls der Server mit HTTPStatus 200 geantwortet hat.
public final void testRetrieveResponse() throws Exception {
Properties props = new Properties();
props.put("url", "http://someurl?action=textpdf&docid=DE03003827T1");
props.put("clazzesPrepareCall", "de.aja.preparecall.AddRequestHeader"); // konkret hier: füge SessionCookie dem Request Header hinzu.
props.put("de.aja.preparecall.AddFollowRedirects.followRedirects", "false"); // followRedirects nie folgen
props.put("clazzProcessReturn", "de.aja.docall.ReturnBodyAsStreamHandler"); // Response als Stream zurückgeben, falls Statuscode 200 von Server
props.put("folderDest", "D:/test/notes/"); // Ordner in denen PDF gespeichert werden
// Cookies aus Java Properties Datei laden, kann auch von Notes Items geholt werden
Properties propsCookies = new Properties();
File cookiesProperties = new File("cookies.properties");
if (cookiesProperties.exists()) {
propsCookies.load(new FileInputStream("cookies.properties"));
}
System.out.println("propsCookies=" + propsCookies);
props.putAll(propsCookies);
HttpClientInvoker invoker = HttpClientInvoker.getDefaultHttpClientInvoker(props); // Einstiegsklasse des Frameworks
Object res = invoker.doInvoke(new GetMethod()); // Aufrufmethode des Frameworks
if (res instanceof java.lang.Integer) { // Returncode wird zurückgeliefert, wenn HTTPStatus != 200
System.out.println("Return Integer is:" + res);
} else if(res instanceof java.io.InputStream) { // InputStream sollte PDF File sein, spezifische Logik, um Dateiname zu ermitteln, speichern auf Festplatte.
InputStream in = (InputStream) res;
String[] urlParts = invoker.getUrl().split("=");
String fileDest = props.getProperty("folderDest")
+ urlParts[urlParts.length - 1] + ".pdf";
System.out.println("file destination is " + fileDest);
OutputStream out = new FileOutputStream(fileDest);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} else {
System.out.println("Unexpected return" + (res == null ? "null" : res.getClass().getName()));
}
}
Wenn man jetzt hinter einem Authentifizierenden Proxy sitzt, kann man einfach die Konfiguration ein wenig ändern und neue Properties für ProxyHost, proxyPort, ProxyUser und ProxyPassword setzen.
In Beispiel2 sähe das so aus:
// statt props.put("clazzesPrepareCall", "de.aja.preparecall.AddRequestHeader");
props.put("clazzesPrepareCall", "de.aja.preparecall.AddRequestHeader:de.aja.preparecall.AddReverseProxyBaseAuth");
// UND ZUSÄTZLICH
props.put("de.aja.preparecall.AddReverseProxyBaseAuth.user", "axel");
props.put("de.aja.preparecall.AddReverseProxyBaseAuth.pwd", "kennwort");
props.put("de.aja.preparecall.AddReverseProxyBaseAuth.proxyHost", "192.168.12.7");
props.put("de.aja.preparecall.AddReverseProxyBaseAuth.proxyPort", "8080");
Wenn die Ziel-Website Authentifizierung erfordert, kann man einfach die Konfiguration ein wenig ändern und neue Properties für HostPassword und HostUsername setzen.
Falls für ein Spezialfall die bisher vorhandenen Klassen zur Request Vorbereitung und die ResponseHandler nicht ausreichen, kann das Framework über das Implementieren von bestehenden Interfaces problemlos erweitert werden.
Hört sich vielleicht verwirrender an als es ist.
Wer Interesse hat, kann sich jederzeit melden.
Gruß Axel