Sonntag, 11. Dezember 2011

Cross-side RPC Calls with the GWT

In developer mode you have to make rpc calls for example from the jetty-server, running on localhost:8888 to the tomcat 7 server, running on localhost:8080.

In this tutorial I will describe how you can do it.

First you need to download the proxy from: http://www.siafoo.net/snippet/258 or you paste the following code from the other side directly: http://gwtdebugtomcateclipse.blogspot.com/2011/12/quelltext-proxy.html

First let us create the gwt application, to do so click: File -> New -> Project -> Window Builder -> GWT Designer -> Model -> GWT Java Project
I will give it the following properties:
Project name: RPCSender
Create GWT Module: yes
Module name: RPCViewer
Package name: de.tu_freiberg.sfb799

Next we are creating an rpc-service, so please click with your right mouse button at the gwt project and select:
New -> Other -> Window Builder -> GWT Designer -> Model -> GWT Remote Service
I will use the following properties:
client package: RPCSender/src/de.tu_freiberg.sfb799.client
service name: SimpleRPC

Now the SimpleRPC.java opens in Eclipse:
change the java file to the following:
...
@RemoteServiceRelativePath("SimpleRPC")
public interface SimpleRPC extends RemoteService {
    public String getNumber(int i);
...

When you now press the save icon in eclipse, it will change automatically the following files:  SimpleRPCAsync.java, SimpleRPCImpl.java (in the server package)

 Now we want to change the SimpleRPCImpl.java to:
...
@Override
    public String getNumber(int i) {
        // TODO Auto-generated method stub
        switch(i) {
        case 1: return "one";
        default: return "not yet implemented";
        }

    }
...

Next we will change the following file RPCViewer.java to:
...
            public void onClick(ClickEvent event) {
                AsyncCallback<String> cb = new AsyncCallback<String>() {
                    @Override
                    public void onSuccess(String result) {
                        clickMeButton.setText(result);
                    }
                    @Override
                    public void onFailure(Throwable caught) {
                        // TODO Auto-generated method stub
                    }
                };
                SimpleRPCAsync sRPC = (SimpleRPCAsync)GWT.create(SimpleRPC.class);
                sRPC.getNumber(1, cb);

            }
...

Now you can test this rpc with: right click on this project -> Run As ->  WebApplication

When you now click on the button, you will see that the button text changes to one.
And if you have installed Firebug in Firefox for example you will see that the following post will be fired after pressing on the button:
POST http://127.0.0.1:8888/de.tu_freiberg.sfb799.RPCViewer/SimpleRPC

This is the adress we need to proxy with the servlet you downloaded at the beginning of this article.
That means that the proxy will send this request to: http://127.0.0.1:8080/de.tu_freiberg.sfb799.RPCViewer/SimpleRPC where my Tomcat server is listening for the proxy.

So first create ProxyServlet.java in the package de.tu_freiberg.sfb799.server
Now insert the java code from the beginning of this article, and insert at the beginning of the file the line:
package de.tu_freiberg.sfb799.server;

Ok, now we need to change the web.xml file in /RPCSender/war/WEB-INF/web.xml to:
...
<web-app>

    <!-- Default page to serve -->
    <servlet>
        <servlet-name>ProxyServlet</servlet-name>
          <servlet-class>de.tu_freiberg.sfb799.server.ProxyServlet</servlet-class>
     </servlet>
     <servlet-mapping>
          <servlet-name>ProxyServlet</servlet-name>
          <url-pattern>/de.tu_freiberg.sfb799.RPCViewer/SimpleRPC</url-pattern>
     </servlet-mapping>

    <welcome-file-list>
        <welcome-file>RPCViewer.html</welcome-file>
    </welcome-file-list>
    <servlet>
        <servlet-name>SimpleRPC</servlet-name>
        <servlet-class>de.tu_freiberg.sfb799.server.SimpleRPCImpl</servlet-class>
    </servlet>
    <!-- 
    <servlet-mapping>
        <servlet-name>SimpleRPC</servlet-name>
        <url-pattern>/de.tu_freiberg.sfb799.RPCViewer/SimpleRPC</url-pattern>
    </servlet-mapping>
    -->
</web-app>

Now we are done with reconfiguring the gwt-application. Now I will create the dynamic webproject:
Create now with File -> New -> Other -> Web -> Dynamic Webproject a new Webproject which will run on your Tomcat 7 or whatever else server.
I will use the following properties:
Project name: RPCReceiver
Target Runtime: Apache Tomcat v 7.0
ContextRoot: de.tu_freiberg.sfb799.RPCViewer
Generate web.xml ..: yes 

Now copy the two packages de.tu_freiberg.sfb799.server and de.tu_freiberg.sfb799.client from the RPCSender project to the RPCReceiver project. You can delete the following files: SimpleRPCAsync.java, RPCViewer and ProxyServlet.java because we do not need them anymore.
Now we need to copy the gwt-servlet.jar from /RPCSender/war/WEB-INF/lib/gwt-servlet.jar to /RPCReceiver/WebContent/WEB-INF/lib/gwt-servlet.jar
This is needed because Tomcat needs the library because our servlet SimpleRPCImpl extends RemoteServiceServlet.

Next we need to insert the following in the web.xml file at /RPCReceiver/WebContent/WEB-INF/web.xml:
    <servlet>
        <servlet-name>SimpleRPC</servlet-name>
        <servlet-class>de.tu_freiberg.sfb799.server.SimpleRPCImpl</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SimpleRPC</servlet-name>
        <url-pattern>/SimpleRPC</url-pattern>
    </servlet-mapping>

Please note that the url-pattern is just /SimpleRPC and not de.tu_freiberg.sfb799.RPCViewer/SimpleRPC, because de.tu_freiberg.sfb799.RPCViewer is our ContextRoot.


Now it would run, but to see a difference please add the following line to /RPCReceiver/src/de/tu_freiberg/sfb799/server/SimpleRPCImpl.java:
...
        case 1: return "one";
        case 2: return "two";
        default: return "not yet implemented";

...


And make the following change in /RPCSender/src/de/tu_freiberg/sfb799/client/RPCViewer.java:
sRPC.getNumber(2, cb);


Now please start the Tomcatserver by clicking on the RPCReceiver project with the right mouse button -> Run As -> Run on Server
And start your RPCSender like you did it before.
If you now click on the button, you should see the word two.


To verify to 100% that the answer from the call comes from apache, please stop the apache server and reload the page.
If you now click on the button, then you will see that the text will not change.
And Firebug will show the following:
POST http://127.0.0.1:8888/de.tu_freiberg.sfb799.RPCViewer/SimpleRPC 500 Connection to http://localhost:8080 refused


"NetworkError: 500 Connection to http://localhost:8080 refused - http://127.0.0.1:8888/de.tu_freiberg.sfb799.RPCViewer/SimpleRPC"







If you see this error, then you know that the proxy works well and you can do cross-script rpc calls. So you can use the big adventage of Tomcat debugging in database pools and you can debug with the jetty server in the javascript source -> generated from gwt.

Quelltext proxy

    package com.mycompany.project.server;
    
    /**
     * Copyright 2010 Kenneth Jorgensen (kennethjorgensen.com) (modifications)
     * - original code can be found at http://www.siafoo.net/snippet/258
     * Copyright 2009 Stou Sandalski (Siafoo.net)
     * Copyright 1999-2008 The Apache Software Foundation
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     */
    
    import java.io.InputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.util.Enumeration;
    
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.ServletException;
    
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
    import org.apache.http.client.methods.HttpRequestBase;
    import org.apache.http.entity.InputStreamEntity;
    import org.apache.http.Header;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.StatusLine;
    
    /**
    * This servlet provides a proxy to other servers for use in development with GWT devmode.
    * It is not meant, in any way shape or form, to be used in production.
    */
    public class ProxyServlet extends HttpServlet {
    
    private static final String targetServer = "http://localhost:8080";
    
    @Override
    @SuppressWarnings("unchecked")
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // Create new client to perform the proxied request
    HttpClient httpclient = new DefaultHttpClient();
    
    // Determine final URL
    StringBuffer uri = new StringBuffer();
    uri.append(targetServer);
    uri.append(req.getRequestURI());
    
    // Add any supplied query strings
    String queryString = req.getQueryString();
    if (queryString != null){
    uri.append("?" + queryString);
    }
    
    // Get HTTP method
    final String method = req.getMethod();
    // Create new HTTP request container
    HttpRequestBase request = null;
    
    // Get content length
    int contentLength = req.getContentLength();
    // Unknown content length ...
    // if (contentLength == -1)
    // throw new ServletException("Cannot handle unknown content length");
    // If we don't have an entity body, things are quite simple
    if (contentLength < 1) {
        request = new HttpRequestBase() {
            public String getMethod() {
                return method;
            }
        };
    }
    else {
    // Prepare request
        HttpEntityEnclosingRequestBase tmpRequest = new HttpEntityEnclosingRequestBase() {
            public String getMethod() {
                return method;
            }
    };
    
    // Transfer entity body from the received request to the new request
    InputStreamEntity entity = new InputStreamEntity(req.getInputStream(), contentLength);
    tmpRequest.setEntity(entity);
    
    request = tmpRequest;
    }
    
    // Set URI
    try {
    request.setURI(new URI(uri.toString()));
    }
    catch (URISyntaxException e) {
    throw new ServletException("URISyntaxException: " + e.getMessage());
    }
    
    // Copy headers from old request to new request
    // @todo not sure how this handles multiple headers with the same name
    Enumeration<String> headers = req.getHeaderNames();
    while (headers.hasMoreElements()) {
        String headerName = headers.nextElement();
        String headerValue = req.getHeader(headerName);
        // Skip Content-Length and Host
        String lowerHeader = headerName.toLowerCase();
        if (lowerHeader.equals("content-length") == false && lowerHeader.equals("host") == false) {
            // System.out.println(headerName.toLowerCase() + ": " + headerValue);
            request.addHeader(headerName, headerValue);
        }
        System.out.print(headerName + " ");
        System.out.println(headerValue);
    }
    
    // Execute the request
    HttpResponse response = httpclient.execute(request);
    
    // Transfer status code to the response
    StatusLine status = response.getStatusLine();
    resp.setStatus(status.getStatusCode());
    // resp.setStatus(status.getStatusCode(), status.getReasonPhrase()); // This seems to be deprecated. Yes status message is "ambigous", but I don't approve
    
    // Transfer headers to the response
    Header[] responseHeaders = response.getAllHeaders();
    for (int i=0 ; i<responseHeaders.length ; i++) {
    Header header = responseHeaders[i];
    resp.addHeader(header.getName(), header.getValue());
    }
    
    // Transfer proxy response entity to the servlet response
    HttpEntity entity = response.getEntity();
    InputStream input = entity.getContent();
    OutputStream output = resp.getOutputStream();
    int b = input.read();
    while (b != -1) {
    output.write(b);
    b = input.read();
    }
    
    // Clean up
    input.close();
    output.close();
    httpclient.getConnectionManager().shutdown();
    }
    }

Freitag, 9. Dezember 2011

GWT Debugging mit Eclipse und Tomcat 7 und Benutzen einer MySQL Datenbank

Da ich eine Swing-Desktopanwendung in eine Internetanwendung umprogrammieren wollte, bin ich auf GWT gekommen.  Dort habe ich festgestellt, dass das Debuggen mit dem integrierten Jetty-Server sehr gut funktioniert und diesen Komfort wollte ich auf keinen Fall aufgeben. Um alles richtig zu machen las ich sehr viele Tutorials, wie man denn nun einen MySQL-Pool in Jetty einbinden kann.  Ich probierte alles, aber entweder unterstützte Jetty keine Contents oder als ich dann Jetty nach folgenden Tutorial http://curtstech.blogspot.com/2009/07/gwt-16-hosted-mode-internen-jetty.html konfiguriert hatte, konnte ich mein Modul nicht mehr laden, bekam immer den Fehlercode 503.
Und deshalb entschied ich mich alles über Tomcat 7 laufen zu lassen, da dieser viel leichter zu konfigurieren ist.

Am Ende des Artikels kann man das Eclipse-Projekt herunterladen, allerdings muss die Server.xml Datei für die eigene Datenbank angepasst werden .

Zuerst lädt man Tomcat 7 von Apache herunter: http://tomcat.apache.org/download-70.cgi
Dort wählt man dann je nach Betriebssystem 32-bit Windows.zip oder, wie ich, die 64 bit Version aus. Auf jeden Fall nicht den Windows Service Installer benutzen, diesen hatte ich zuerst installiert, aber dort ging das Debuggen von Eclipse aus nicht.

Als nächstes habe ich Tomcat in C:\ entpackt.

Nun richtet man den Server in Eclipse ein:
Dazu wählt man File -> New -> Other aus, oder drückt einfach Strg+N.
Dann wählt man dort Server -> Server aus:
  
Im nächsten Schritt wählt man dann Apache -> Tomcat v7.0 Server aus und klickt dann weiter unten auf den Link: Configure Runtime Environments oder wenn dieser noch nicht verfügbar ist einfach weiter auf Next >
Im nächsten Schritt wählen Sie dann über den Browse ... Button Ihr Tomcat-Verzeichnis aus, bei mir ist es zum Beispiel:

Danach klicken Sie auf Finish.
Jetzt ist ihr Tomcat7 Server eingerichtet und die Anzeige sieht wie folgt aus:
Da ich einen Datenbankpool zu unserer MySQL-Datenbank herstellen möchte, sehen meine server.xml und context.xml Dateien wie folgt aus:



 server.xml:
<?xml version="1.0" encoding="UTF-8"?>
  <Server port="8005" shutdown="SHUTDOWN">
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--APR library loader. Documentation at /docs/apr.html -->
  <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
  <Listener className="org.apache.catalina.core.JasperListener"/>
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
        <Resource type="javax.sql.DataSource"
            name="jdbc/global_SFB799"
            factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://xxxxxx/sfb799"
            username="Michael"
            password="XXX"
            initialSize="10"
            maxActive="100"
            maxIdle="50"
            minIdle="10"
            validationQuery="Select `ID` FROM `users` LIMIT 1"
            testOnBorrow="true"
            testWhileIdle="true"
            timeBetweenEvictionRunsMillis="30000"
   />
   ...
context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <ResourceLink type="javax.sql.DataSource"
                name="jdbc/SFB799"
                global="jdbc/global_SFB799"
    />
    <!-- Default set of monitored resources -->
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
</Context>

Jetzt darf man nur nicht vergessen den MySQL-Connector in das Tomcat 7 - Lib- Verzeichnis zu kopieren:


Damit haben wir den Tomcat 7 Server mit MySQL erfolgreich in Eclipse integriert.

Als nächstes erstellen wir ein dynamisches Web-Projekt, welches wir dann mit unserem Tomcat-Server verbinden:
Dazu wählen Sie: File -> New -> Project aus: Und dann unter Web -> Dynamic Web Project:

Geben Sie einen beliebigen Namen ein und wählen als Configuration: Default Tomcat 7 Configuration aus:
Danach klicken Sie zweimal auf Next > und setzen noch einen Haken bei Generate web.xml deployment descriptor

Nun klicken Sie auf Finish.

Jetzt klicken Sie mit der rechten Maustaste auf ihr neues Projekt -> New -> HTML File:

Diesen nennen Sie index.html.



index.html:



<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  <title>Startseite</title>
</head>
<body>
  <h3> Links: </h3>
  <p> <a href="/TestWebApp/SQLServlet">/TestWebApp/SQLServlet</a> </p>
</body>
</html>

Jetzt erstellen Sie ein Servlet, indem Sie wieder mit der rechten Maustaste auf Ihr Projekt klicken -> New -> Servlet
Java Package: servlets
Class Name: SQLServlet


SQLServlet.java:


package servlets;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

/**
 * Servlet implementation class SQLServlet
 */
@WebServlet("/SQLServlet")
public class SQLServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
      
    @Override
       public void doGet( HttpServletRequest requ, HttpServletResponse resp )
       throws ServletException, IOException
       {
         
          resp.setStatus(200);
          resp.setContentType( "text/html" );
          PrintWriter out = resp.getWriter();
          try {   
              String jdbcname = "jdbc/SFB799";
              String cmd = "SELECT `name`, `first_name` FROM `users`";
              Context ctx = new InitialContext();
              Context envCtx = (Context)ctx.lookup("java:comp/env");
              DataSource ds = (DataSource)envCtx.lookup(jdbcname);
              Connection conn = ds.getConnection();
              Statement stmt = conn.createStatement();
              ResultSet rs = stmt.executeQuery(cmd);
              ResultSetMetaData rsmd = rs.getMetaData();
              int cols = rsmd.getColumnCount();
              out.println("<table border\"1\">");
              out.println("<tr bgcolor=\"lightGrey\">");
              for (int i = 1; i <= cols; i++) {
                  out.println("<td align=\"center\" width=\"200\"><b>" +
                          rsmd.getColumnName(i) + "</b></td>");
              }
              out.println("</tr>");
              while (rs.next()) {
                  out.println("<tr>");
                  for (int i = 1; i <= cols; i++) {
                      out.println("<td align=\"left\">" +
                              rs.getString(i) + "</td>");
                  }
                  out.println("</tr>");
              }
              out.println("</table>");
          } catch(Exception e) { e.printStackTrace(out);}
          out.close();
       }

}




Zum Testen des Servlets klicken Sie mit der rechten Maustaste auf  Ihr Projekt -> Run As -> Run on Server:

Jetzt kann es sein, dass Sie noch einen Server auswählen müssen:
Und danach auf Finish.

Jetzt startet der Tomcat 7 Server (bei mir kommt noch so eine komische Fehlermeldung, die kann man aber ruhig ignorieren). Wenn man dann auf den Link klickt, bekommt man den Inhalt seiner Datenbank angezeigt:

Damit ist das Servlet konfiguriert, quasi der Teil, der dann die Daten an unsere HTML-Seite sendet.
Als nächstes werden wir jetzt unsere GWT-Anwendung konfigurieren.

Dazu wählen Sie File -> New -> Project -> WindowBuilder -> GWT Designer -> Model -> GWT Java Project aus und klicken auf Next >

Als nächstes geben wir einen Namen ein:
Project Name: GWTView
Location: Location des Dynamic Web Project + "\WebContent\gwt\"
Wobei das "\gwt\" nur da ist, damit die Ordner sauberer aussehen, aber dieses Projekt muss unbedingt in das Verzeichnis WebContent, da man sonst keinen Zugriff auf den Server hat.  Wir könnten zwar auch den Jetty-Server von Google nehmen, dann würden wir auch unsere JavaScript-Seite angezeigt bekommen, aber würden bei response.getStatusCode() immer Null und bei response.getText() immer eine leere Zeichenkette bekommen, da wir mit unserem HTTP-Request auf einen anderen Server zugreifen; dies ist der XSS-Schutz von Google. Jetzt nochmal das Bild:

Jetzt klicken Sie noch zweimal auf Next> und setzen einen Haken bei Create GWT module und geben z.B. als Module name: MainView ein:
Jetzt können Sie auf Finish klicken.

Öffnen Sie jetzt die MainView.java Datei: