gpt4 book ai didi

java - 如何升级Tomcat Java Applet以使用JNLP?

转载 作者:行者123 更新时间:2023-12-02 10:46:29 26 4
gpt4 key购买 nike

我有一组在Tomcat上运行的Java Applet程序。这些程序跟踪非正式的高尔夫“锦标赛”事件,包括友好的球员比赛。

尽管程序的细节并不重要,但是代码集包含了30,000多个源代码行。我选择Java作为实现语言,以实现可移植性并避免维护问题。我使用Tomcat部署应用程序,并使用javascript调用小程序。我所有的小程序都使用参数,例如事件名称, class 名称和播放日期。

不幸的是,Java和浏览器的更改现在已导致我的应用程序出现维护问题。第一个问题是java添加了对jar文件进行签名的要求。第二个问题是,首先是Chrome,现在是Firefox,已经删除了对NPAPI插件的支持,这实际上从html中删除了Java Applet的支持。

JNLP(Java Web Start)是新的替换。这两个问题都很难解决,因为没有清晰的分步文档详细说明了实际需要执行的操作。

将小程序迁移到JNLP的方法可能有所不同,甚至更好,但是此处描述的过程可以正常工作。但是,在描述它们时,我必须假设您已经知道如何创建Java Web应用程序,因为不需要更新您尚不具备的内容。

我在Windows Cygwin环境中使用Tomcat。我的示例mkJavaKey脚本明确使用了该环境,但是所有Java和javascript代码都是可移植的。 Tomcat使用web.xml定义如何调用Servlet。如果您使用其他部署方法,则我的web.xml文件至少应作为起点。

最佳答案

为什么需要签署jar文件?

我无法回答问题的这一部分。但是,对于任何非平凡的应用程序,您将至少需要完成该过程以对jar文件自签名,即使这种自签名也没有提供真正的额外安全性。任何人都可以使用Java开发工具包中提供的工具对应用程序进行自签名。自签名证书可以很好地进行开发工作,但是每次运行应用程序时都必须单击“风险接受”复选框。

好的,我的应用程序很简单,我需要签名我的jar文件。怎么办?

快速答案:这是一个两步过程。您首先使用keytool程序创建必要的凭据,然后使用jarsigner工具对jar文件进行签名。您只需要偶尔创建一次凭据,但需要对每个部署的jar文件进行签名。

要创建这些凭据(自签名证书),请使用:

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

这将在您的主目录中创建一个名为 .keystore的证书,有效期为五年。您必须响应其提示,而我使用“password”作为密码。由于我仅将此证书用于自签名jar文件,因此安全性不是大问题。有效性参数指定证书有效的时间(天)。

每次更新jar文件时,都需要对其进行签名。假设您位于分发目录中,并且需要签署 applet.jar,请使用:
$JAVA_HOME/bin/jarsigner -tsa http://timestamp.digicert.com -storepass password applet.jar mydomain
-storepass后的“密码”与您与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环境?

现在我们有了自签名的jarfile,我们可以开始弄清楚如何运行它们了。许多浏览器不再支持NPAPI,因此 <applet>标记将不起作用。两者都不会部署Java.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函数的表单按钮。它类似于“完成”按钮的生成,不需要更改。

仅将这个javascript转换为jnlp文件就应该很简单。这是不可能的,或者至少我找不到如何执行该操作的示例,也找不到一种修改任何损坏示例的方法。即使我只想生成jnlp xml, window.open()语句也将始终添加 <html><body>部分。我也尝试过 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是我的“启用调试”标志,并且 log()将调试消息写入stdout。在此新代码版本中,高度和宽度未作为参数传递,而是硬编码。事实证明,在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>

这将导致为任何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文件。您需要不时清理它们。

这样就完成了迁移过程。在此迁移中,没有任何applet受到损害(或更改),因此30,000个源代码行中的大部分保持不变。

当我使用操作代码中的剪切和粘贴来创建示例时,错别字可能会被误读。如果发现任何不正确,遗漏或不清楚的地方,请发表评论。

关于java - 如何升级Tomcat Java Applet以使用JNLP?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43074225/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com