2017-03-28 37 views
3

我有一套運行在Tomcat上的Java Applet程序。這些節目跟蹤非正式的高爾夫「比賽」事件,包括友好球員比賽。如何升級Tomcat Java Applet以使用JNLP?

程序細節並不重要,代碼集包含超過30,000個源代碼行。我選擇Java作爲可移植性的實現語言並避免維護問題。我使用Tomcat來部署應用程序和JavaScript來調用小程序。我的所有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文件至少應該作爲一個起點。

回答

3

爲什麼你需要簽署一個jar文件?

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

好的,我的應用程序並不重要,我需要簽署我的jar文件。什麼是程序?

下面是快速回答:這是一個兩步過程。您首先使用keytool程序創建必要的證書,然後使用jarsigner工具簽署您的jar文件。您只需稍後創建憑證,但需要簽署每個部署的jar文件。

要創建這些證書(自簽名證書)使用方法:

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

這將創建一個在你的主目錄這是很好的五年評爲.keystore證書。你必須迴應它的提示,並使用「密碼」作爲密碼。由於我只使用此證書進行自簽名jar文件,因此安全性不是一個大問題。有效性參數指定證書有效的時間(以天計)。

每次更新jar文件時都需要對它進行簽名。假設你在你的分發目錄,並需要簽署applet.jar,用途:

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

「密碼」 -storepass你以後再密鑰工具使用的密碼相匹配,而「MYDOMAIN」密鑰工具-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來執行此操作。下面是我用的化妝代碼片段:

.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的構建環境中設置的目錄路徑。

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

既然我們有自簽名的jar文件,我們可以開始計算如何運行它們。許多瀏覽器不再支持NPAPI,因此<applet>標記將不起作用。也不會deployJava.runApplet()。我不會理解爲什麼NPAPI支持被丟棄,只是需要做些什麼來使現有的應用程序運行。

我發現遷移我的代碼最大的問題是,最終我不得不創建.jnlp文件而不是.html文件。我會告訴你如何做到這一點,描述我修改和添加的代碼。

這是(現在已廢棄)的JavaScript代碼我用來生成html:

//------------------------------------------------------------------------ 
// 
// 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,包括用於調用cardEvents功能表單按鈕生成的html。它與「完成」按鈕生成類似,不需要更改。

它應該是非常簡單的轉換這個JavaScript來生成一個jnlp文件。這是不可能的,或者至少我找不到如何做到這一點的任何工作示例,也無法找到修改任何破壞示例的方法。 window.open()聲明總是會添加<html><body>部分,即使我只想生成jnlp xml。我也試過document.open("application/x-java-jnlp-file")。即使指定了mime類型,不需要的html和正文部分仍然存在。

無我發現的文件展示瞭如何動態生成我需要.jnlp文件,其中包括用戶選擇的小應用程序的參數。這是我用來替代的解決方法。

我取代的HTML代applet.js與此:

//------------------------------------------------------------------------ 
// 
// 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是我的「調試啓用」標誌,並log()到標準輸出寫入調試信息。在這個新的代碼版本中,高度和寬度不作爲參數傳遞,而是用硬編碼代替。事實證明,在HTML版本中,「100%」總是用作高度和寬度,並且運行良好。對於一些(我不知道)原因,當使用100%高度和寬度的.jnlp代碼調用時,我的applet窗口在底部被截斷,可能在右側被截斷。我使用這些新的高度和寬度參數來解決這個格式問題。

爲了調用我的新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> 

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

爲了更流暢的操作,你需要設置你的Windows文件關聯,以便.jnlp文件調用Java(TM)網絡開始啓動。在Windows中,您的JWS啓動器是C:\Program Files\java\jre*\bin\javaws.exe(使用最新的jre文件夾。)另外,如果您使用Chrome,則您的下載目錄將包含生成的Applet.jnlp文件。你需要立即清理它們。

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

雖然我用剪切和操作代碼粘貼到創建的例子,它可能是拼寫錯誤可能在已悄悄請評論,如果您發現任何不正確,缺失或不清晰。