簡體   English   中英

如何升級Tomcat Java Applet以使用JNLP?

[英]How do you upgrade a Tomcat Java Applet to use JNLP?

我有一組在Tomcat上運行的Java Applet程序。 這些節目跟蹤非正式的高爾夫“錦標賽”活動,包括友好的球員比賽。

雖然程序細節並不重要,但代碼集包含超過30,000個源代碼行。 我選擇Java作為可移植性的實現語言並避免維護問題。 我使用Tomcat部署應用程序和javascript來調用applet。 我的所有applet都使用參數,例如事件名稱,課程名稱和播放日期。

不幸的是,Java和瀏覽器的更改現在已經導致我的應用程序的維護問題 第一個問題是java添加了一個jar文件簽名的要求。 第二個問題是第一個Chrome和現在的Firefox已經刪除了對NPAPI插件的支持,這實際上從html中刪除了Java Applet支持。

JNLPJava Web Start )是新的替代品。 這兩個問題都難以解決,因為沒有明確的逐步文檔詳細說明實際需要做什么。

可能有不同的,甚至更好的方法將applet遷移到JNLP,但這里描述的過程是有效的並且是完整的。 但是,在描述它們時,我必須假設您已經知道如何創建Java Web應用程序,因為不需要更新您還沒有的內容。

我在Windows Cygwin環境中使用Tomcat。 我的示例mkJavaKey腳本顯式使用該環境,但所有Java和javascript代碼都是可移植的。 Tomcat使用web.xml來定義如何調用Servlet。 如果您使用不同的部署方法,我的web.xml文件至少應該作為起點。

你為什么需要簽一個jar文件?

我無法回答這部分問題。 但是,對於任何非平凡的應用程序,您至少需要通過該過程自行簽署您的jar文件, 即使此自簽名不提供真正的額外安全性。 任何人都可以使用Java Development Kit中提供的工具自行簽署應用程序。 自簽名證書適用於開發工作,但每次運行應用程序時都必須單擊風險驗收復選框。

好的,我的應用程序是非常重要的,我需要我的jar文件簽名。 程序是什么?

這是快速回答:這是一個兩步過程。 首先使用keytool程序創建必要的憑據,然后使用jarsigner工具對jar文件進行簽名。 您只需要偶爾創建一次憑證,但需要簽署每個部署的jar文件。

要創建這些憑據(自簽名證書),請使用:

$JAVA_HOME/bin/keytool -genkeypair -keyalg RSA -keysize 2048 -alias mydomain -validity 1825

這將在您的主目錄中創建一個名為.keystore的證書,該證書.keystore為五年。 您必須回復其提示,我使用“密碼”作為密碼。 由於我只使用此證書進行自簽名jar文件,因此安全性不是一個大問題。 validity參數指定證書有效的時間(以天為單位)。

每次更新jar文件時都需要對其進行簽名。 假設您在分發目錄中並需要簽署applet.jar ,請使用:

$JAVA_HOME/bin/jarsigner -tsa http://timestamp.digicert.com -storepass password applet.jar mydomain

-storepass之后的“password”與您使用keytool的密碼匹配,“mydomain”與keytool -alias參數匹配。 您需要指定-tsa(時間戳授權)參數,並且http://timestamp.digicert.com是(或至少是)公開可用的參數。 我不確切知道TSA的作用或者你為什么需要它,但是沒有它,jarsigner不高興,不會默認它,也不會直接記錄如何找到它。

您現在可以使用或忽略以下批處理文件。 我創建它是因為當我需要創建一個新證書(我的原始證書已過期)時,我忘記了如何創建它。 希望我們能夠在下次需要時找到這個批處理文件,也許是從現在開始的五年。

#!/bin/bash
#
# Title-
#        mkJavaKey
#
# Function-
#        Create a new key using $JAVA_HOME/bin/keytool
#
# Usage-
#        mkJavaKey ## CYGWIN ONLY ##
#        (This is required when jarsigner complains about an expired key.)
#        NOTE: This *REMOVES* and *REPLACES* your existing .keystore file!
#
#######

##########################################################################
# Environment check
if [ -z "$JAVA_HOME" ] ; then
  . setupJAVA ## (This personal script sets JAVA_HOME)
  if [ -z "$JAVA_HOME" ] ; then
    echo "JAVA_HOME environment variable missing"
    exit 1
  fi
fi

if [ -z "$HOMEPATH" ] ; then
  echo "HOMEPATH environment variable missing"
  echo "Try export HOMEPATH=\Users\myname"
  exit 1
fi

home_path=`cygpath --path --unix C:$HOMEPATH`
PGM=$JAVA_HOME/bin/keytool
if [ ! -x "$PGM" ] ; then
  echo "$PGM not executable"
  exit 1
fi

##########################################################################
# Create a new .keystore
set -x
rm -Rf $home_path/.keystore
$PGM -genkeypair -keyalg RSA -keysize 2048 -alias mydomain -validity 1825
exit $?

注意:我的setupJAVA腳本設置了JAVA_HOME環境變量。 對於Linux,使用$HOME而不是$HOMEPATH並跳過cygpath部分。 這些在Cygwin環境中在Linux和Windows文件名格式之間進行轉換。

每次安裝時都需要對jar文件進行簽名。 為了自動執行此操作,我修改了Makefile來執行此操作。 這是我使用的make代碼段:

.PHONY: golfer.install
golfer.install: test golfer
: (Not relevant to discussion)
cp -p $(OBJDIR)/usr/fne/golfer/Applet/applet.jar $(DEPLOYDIR)/webapps/golfer/.
jarsigner -tsa http://timestamp.digicert.com -storepass password "$(shell cygpath --path --windows "$(DEPLOYDIR)/webapps/golfer/applet.jar")" mydomain
: (Not relevant to discussion)

$(OBDIR)$(DEPLOYDIR)變量與此討論無關。 它們是在基於Makefile的構建環境中設置的目錄路徑。

如何將Applet遷移到新的JNLP環境?

既然我們有自簽名的jar文件,我們就可以開始弄清楚如何運行它們了。 許多瀏覽器不再支持NPAPI,因此<applet>標記不起作用。 deployJava.runApplet()也不會。 我不會理解為什么NPAPI支持被刪除,只需要做什么來使現有的應用程序運行。

我發現遷移代碼的最大問題是,最終,我必須創建.jnlp文件而不是.html文件。 我將向您展示如何執行此操作,描述我修改和添加的代碼。

這是我用來生成html的(現在過時的)javascript代碼:

//------------------------------------------------------------------------
//
// Title-
//       applet.js
//
// Purpose-
//       Common applet javascript.
//
// Last change date-
//       2010/10/19
//
//------------------------------------------------------------------------
var out;     // Output document

//------------------------------------------------------------------------
// appHead
//
// Generate html header for application.
//------------------------------------------------------------------------
function appHead(title,cname,height,width)
{
   var todoWindow= window.open('','','');
   out= todoWindow.document;
   out.write('<html>');
   out.write('<head><title>' + title + '</title></head>');
   out.write('<body>\n');
   out.write('<applet code="' + cname + '.class"');
   out.write('    codebase="./"')
   out.write('    archive="applet.jar,jars/common.jar"');
   out.write('    width="' + width + '" height="' + height + '">\n');
}

//------------------------------------------------------------------------
// appParm
//
// Add parameter information
//------------------------------------------------------------------------
function appParm(name, value)
{
     out.write('        <param-name="' + name + '" value="' + value + '"/>\n');
}

//------------------------------------------------------------------------
// appTail
//
// Generate html trailer information.
//------------------------------------------------------------------------
function appTail()
{
   out.write('Your browser is completely ignoring the &lt;APPLET&gt; tag!\n');
   out.write('</applet>');
   out.write('<form>');
   out.write('<input type="button" value="Done" onclick="window.close()">');
   out.write('</form>');
   out.write('</body>');
   out.write('</html>');
   out.close();
   out= null;
}

//------------------------------------------------------------------------
// cardEvents
//
// Display scorecard for selected date.
//------------------------------------------------------------------------
function cardEvents(eventsID, obj) 
{
   if( obj.selectedIndex == 0 )
   {
     alert("No date selected");
     return;
   }
   appHead('Score card', 'EventsCard', '100%', '100%');
   appParm('events-nick', eventsID);
   appParm('events-date', obj[obj.selectedIndex].value);
   appTail();
   reset();
}

我們不需要查看我的servlet生成的html,其中包含用於調用cardEvents函數的表單按鈕。 它與“DONE”按鈕生成類似,不需要更改。

將這個javascript轉換為生成jnlp文件應該非常簡單。 這是不可能的,或者至少我找不到任何有關如何做的工作示例,也無法找到修改任何破壞的示例的方法。 window.open()語句總是會添加<html><body>部分,即使我只想生成jnlp xml。 我也嘗試過document.open("application/x-java-jnlp-file") 盡管指定了mime-type,但仍然存在不需要的html和body部分。

我找到的所有文檔都沒有顯示如何動態生成我需要的.jnlp文件,其中包括用戶選擇的applet參數。 這是我用過的解決方法。

我用這個替換了applet.js中的html生成:

//------------------------------------------------------------------------
//
// Title-
//       applet.js
//
// Purpose-
//       Common applet javascript.
//
// Last change date-
//       2017/03/15
//
//------------------------------------------------------------------------
var out;     // Output URL

//------------------------------------------------------------------------
// appHead
//
// Generate application URL header.
//------------------------------------------------------------------------
function appHead(title,cname,height,width)
{
   out= cname + ',' + title;
}

//------------------------------------------------------------------------
// appParm
//
// Generate html parameter information.
//------------------------------------------------------------------------
function appParm(name, value)
{
   out= out + ',' + name + '=' + value;
}

//------------------------------------------------------------------------
// appTail
//
// Generate html trailer information.
//------------------------------------------------------------------------
function appTail()
{
   var specs= 'menubar=yes,toolbar=yes';
   window.open('Applet.jnlp?' + out, '_self', specs);
}

//------------------------------------------------------------------------
// cardEvents
//
// Display scorecard for selected date.
//------------------------------------------------------------------------
function cardEvents(eventsID, obj)
{
    // (UNCHANGED!)
}

Applet.jnlp,className,description,parm=value,parm=value,...的形式生成URL。

然后我創建了一個名為AppletServlet.java的新Servlet。 傳遞給它的URL提供了生成.jnlp文件所需的所有信息。 此代碼遵循標准樣本Servlet結構,其中調用doGet來處理請求。 這是代碼:

//------------------------------------------------------------------------
//
// Method-
//       AppletServlet.doGet
//
// Purpose-
//       Called for each HTTP GET request.
//
//------------------------------------------------------------------------
public void
   doGet(                           // Handle HTTP "GET" request
     HttpServletRequest  req,       // Request information
     HttpServletResponse res)       // Response information
   throws ServletException, IOException
{
   String q= req.getQueryString();
   if( debug ) log("doGet("+q+")");

   res.setContentType("text/html");

   query(req, res);
}

//------------------------------------------------------------------------
//
// Method-
//       AppletServlet.putError
//
// Purpose-
//       Generate error response.
//
//------------------------------------------------------------------------
public void
   putError(                        // Generate error response
     PrintWriter       out,         // The response writer
     String            msg)         // The error message
{       out.println("<HTML>");
   out.println("<HEAD><TITLE>" + msg + "</TITLE></HEAD>");
   out.println("<BODY>");
   out.println("<H1 align=\"center\">" + msg + "</H1>");
   out.println("</BODY>");
   out.println("</HTML>");
}

//------------------------------------------------------------------------
//
// Method-
//       AppletServlet.query
//
// Purpose-
//       Handle a query.
//
//------------------------------------------------------------------------
protected void
   query(                           // Handle a query
     HttpServletRequest  req,       // Request information
     HttpServletResponse res)       // Response information
   throws ServletException, IOException
{
   String q= req.getQueryString();
   if( debug ) log("query("+q+")");

   PrintWriter out = res.getWriter();
   String BOGUS= "<br> Malformed request: query: '" + q + "'";

   //=====================================================================
   // Applet.jnlp?classname,title,parm=value,parm=value,...
   int index= q.indexOf(',');
   if( index < 0 || index == (q.length() - 1) )
   {
     putError(out, BOGUS);
     return;
   }
   String invoke= q.substring(0, index);

   q= q.substring(index+1);
   index= q.indexOf(',');
   if( index < 0 )
     index= q.length();
   String title= q.substring(0, index);
   title= java.net.URLDecoder.decode(title, "UTF-8");

   // Parameter extraction
   Vector<String> param= new Vector<String>();
   if( index < q.length() )
   {
     q= q.substring(index+1);
     for(;;)
     {
       index= q.indexOf(',');
       if( index < 0 )
         index= q.length();

       String s= q.substring(0, index);
       int x= s.indexOf('=');
       if( x < 0 )
       {
         putError(out, BOGUS);
         return;
       }

       param.add(s);
       if( index >= q.length() )
         break;

       q= q.substring(index+1);
     }
   }

   //---------------------------------------------------------------------
   // We now have enough information to generate the response
   //---------------------------------------------------------------------
   res.setContentType("application/x-java-jnlp-file");
   out.println("<?xml version='1.0' encoding='utf-8'?>");
   out.println("<jnlp spec='1.0+' codebase='http://localhost:8080/golfer'>");
   out.println(" <information>");
   out.println("  <title>" + title + "</title>");
   out.println("  <vendor>My Name</vendor>");
   out.println("  <description>" + title + "</description>");
   out.println(" </information>");
   out.println(" <security><all-permissions/></security>");
   out.println(" <resources>");
   out.println("  <j2se version='1.7+'/>");
   out.println("  <jar href='applet.jar'/>");
   out.println("  <jar href='jars/common.jar'/>");
   out.println(" </resources>");
   out.println(" <applet-desc main-class='" + invoke + "' name='" + title + "'" +
                " height='90%' width='98%'>");

   // Insert applet parameters
   for(int i= 0; i<param.size(); i++)
   {
     String s= param.elementAt(i);
     int    x= s.indexOf('=');
     String n= s.substring(0,x);
     String v= s.substring(x+1);
     out.println("  <param name='" + n+ "' value='" + v + "'/>");
   }
   out.println(" </applet-desc>");
   out.println("</jnlp>");
}

注意: debug是我的“debug enabled”標志, log()將調試消息寫入stdout。 在這個新的代碼版本中,高度和寬度不作為參數傳遞,而是硬編碼。 事實證明,在HTML版本中,“100%”始終用作高度和寬度並且運行良好。 對於一些(我不知道)的原因,我的applet窗口在底部被截斷,當使用具有100%高度和寬度的.jnlp代碼調用時可能在右邊。 我使用這些新的高度和寬度參數來解決此格式問題。

為了調用我的新AppletServlet,我修改了我的web.xml文件:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
   <servlet>
     <servlet-name>Applet</servlet-name>
     <servlet-class>usr.fne.golfer.AppletServlet</servlet-class>
     <init-param>
       <param-name>property-path</param-name>
       <param-value>profile</param-value>
     </init-param>
     <init-param>
       <param-name>property-file</param-name>
       <param-value>golfer.pro</param-value>
     </init-param>
     <load-on-startup>30</load-on-startup>
   </servlet>

   <servlet-mapping>
     <servlet-name>Applet</servlet-name>
     <url-pattern>/Applet.jnlp</url-pattern>
   </servlet-mapping>

   : (Other Servlets unchanged) 
</web-app>

這會導致為任何Applet.jnlp URL調用AppletServlet。 瀏覽器忽略查詢字符串並將結果視為文件名為Applet.jnlp。

為了更順暢的操作,您需要設置Windows文件關聯,以便.jnlp文件調用Java(TM)Web Start Launcher。 在Windows中,JWS Launcher是C:\\Program Files\\java\\jre*\\bin\\javaws.exe (使用最新的jre文件夾。)此外,如果您使用Chrome,則下載目錄將包含生成的Applet.jnlp文件。 你需要偶爾清理它們。

這樣就完成了遷移過程。 在此遷移中沒有小程序受到傷害(或更改),因此30,000個源代碼行中的大部分保持不變。

雖然我使用操作代碼中的剪切和粘貼來創建示例,但錯別字可能會被竊聽。如果您發現任何不正確,缺失或不清楚的內容,請發表評論。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM