Jython在服务器端Web编程中的应用
在服务器端Web编程领域,Jython凭借其独特的优势逐渐崭露头角。本文将深入探讨Jython在服务器端Web编程中的应用,包括Jython Servlet容器、简单Servlet类的定义、GenericServlet和HttpServlet的使用、Cookies和Sessions的管理、数据库连接以及与JSP的集成等方面。
1. Jython与Web开发
Jython在Web开发中的定位与Java紧密相关,Java能应用的地方,Jython通常也能适用。特别是在需要快速开发和提高灵活性的Java应用场景中,Jython表现出色。服务器端Java Web编程主要通过Servlets和Java Server Pages (JSP) 实现,因此Jython的主要应用也是Servlets和JSP。不过,Java的企业级包(j2ee)在Web应用开发中也占据重要地位,Jython在这些技术中同样有效。
然而,Jython并非适用于所有场景。例如,CPython是实现CGI脚本的流行语言,但Jython并不适合。在CGI中,Web服务器接收请求后启动一个进程进行响应,响应完成后关闭该子进程。虽然可以用Jython以这种方式工作,但由于JVM的启动时间较长,这并不是一个好的选择。而Servlets和JSP则具有持久性,它们在Web请求之间保持在内存中。
使用Jython进行Web编程有诸多优势。高质量的Servlet容器广泛可用且几乎无处不在。Java Servlet应用可以受益于Jython的灵活性和高级语言特性,同时还能利用Java和Jython的库。一些常见的Servlet容器包括WebLogic、WebSphere、Tomcat、Jigsaw、Resin和Jetty等。许多组织都部署了这些容器,这使得Jython在大多数情况下都能立即使用。
2. Jython Servlet容器
Jython可以与任何兼容的Java Servlet容器一起使用,有很多这样的容器可供选择。本文以Tomcat为例,它是Servlet和Java Server Page规范的参考实现。以下是一些流行且免费的Servlet容器的简要介绍:
-
Jakarta’s Tomcat
:是Servlet和Java Server Pages规范的参考实现。可从http://jakarta.apache.org/获取,本文编写时的稳定版本是3.2.3,支持2.2 Servlet和1.1 JSP规范。预计后续会发布支持2.3 Servlet和1.2 JSP规范的Tomcat 4.0。所有Jython Servlets都在Tomcat 3.2.3上进行了测试,根据Sun的“一次编写,到处运行”原则,这些示例应适用于任何兼容Servlet 2.2/JSP 1.1的容器。Tomcat发行版包含了本文所需的Servlet和JSP类,无需额外下载。
-
Apache JServ
:是为Apache创建的Servlet(版本2.0)引擎,应用广泛。如果当前开发已经使用了JServ,那么它是使用Jython Servlets的一个不错选择。可从http://java.apache.org/获取相关信息,JServ需要单独从http://java.sun.com/products/servlet/index.html下载Java Servlet Development Kit 2.0,Java Server Pages需要从http://java.apache.org/jserv/获取外部模块。
-
Jigsaw
:是W3C的实验性Web服务器。虽然名为“实验性”,但实际上它比这个标签所暗示的更成熟。它是一个完全符合HTTP/1.1的Web服务器和缓存代理服务器,完全用Java编写,支持Servlet 2.2规范和Java Server Pages 1.1。可从http://www.w3.org/Jigsaw/获取,包含所需的Servlet和JSP文件。
-
Jetty
:是一个紧凑高效的Java Web服务器,支持Servlet 2.2和JSP 1.1规范,支持HTTP 1.1,包含SSL支持,并且可以轻松与EJB服务器(如JBoss)集成。可从http://jetty.mortbay.com/获取。
这些工具的文档非常丰富,安装指南可从各自的网站获取。
3. 定义简单的Servlet类
下面将比较一个简单的Java Servlet和一个简单的Jython Servlet,并介绍如何在Tomcat Web应用中安装jython.jar文件。
3.1 简单的Java Servlet
以下是一个基本的Java Servlet示例:
// Filename: JavaServlet.java
import javax.servlet.*;
import java.io.*;
public class JavaServlet extends GenericServlet {
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter toClient = res.getWriter();
toClient.println("<html><body>" +
"This is a Java Servlet." +
"</body></html>");
}
}
这个Servlet使用了Servlet API中的GenericServlet类,该类的service方法是抽象的,因此必须在子类中实现。通过ServletResponse对象的PrintWriter将输出发送给客户端。
3.2 简单的Jython Servlet
以下是一个与上述Java Servlet功能类似的Jython Servlet示例:
# Filename: JythonServlet.py
from javax import servlet
import random # Not used, just here to test module imports
class JythonServlet(servlet.GenericServlet):
def service(self, req, res):
res.setContentType("text/html")
toClient = res.getWriter()
toClient.println("""<html><body>
This is a Servlet of the Jython variety.
</body></html>""")
这个Jython Servlet同样继承自GenericServlet类,并实现了service方法。与Java Servlet相比,Jython Servlet在语法上有一些差异,例如类定义后的括号用于指定超类,省略了throws语句,service方法中需要显式的self参数,并且没有分号和显式的类型声明。此外,Jython的导入语句也有所不同。
4. 测试Java和Jython Servlets
测试上述Servlets需要安装Tomcat,并将jython.jar文件包含在Tomcat中。以下是具体步骤:
4.1 安装Tomcat
- 从http://jakarta.apache.org下载Tomcat,建议下载jakarta-tomcat-3.2.3版本,根据平台选择合适的zip或tar.gz文件。
-
将下载的文件解压到有足够权限的目录。例如,在Windows上解压到C:\jakarta-tomcat-3.2.3,在*nix系统上解压到/usr/local/jakarta-tomcat3.2.3。设置环境变量TOMCAT_HOME指向Tomcat的主目录:
-
在Windows上:
set TOMCAT_HOME=c:\jakarta-tomcat-3.2.3 -
在*nix系统上:
export TOMCAT_HOME=/usr/local/jakarta-tomcat-3.2.3
-
在Windows上:
-
设置环境变量JAVA_HOME指向JDK的根目录。例如,使用JDK1.3.1时:
-
在Windows上:
set JAVA_HOME=c:\jdk1.3.1 -
在*nix系统上:
export JAVA_HOME=/usr/java/jdk1.3.1
-
在Windows上:
-
启动Tomcat:
-
在Windows上:
%TOMCAT_HOME%\bin\startup.bat -
在*nix系统上:
$TOMCAT_HOME/bin/startup.sh
-
在Windows上:
-
启动过程中,注意查看以下信息:
date time - PoolTcpConnector: Starting HttpConnectionHandler on 8080,这表示Tomcat正在监听8080端口。 -
停止Tomcat:
-
在Windows上:
%TOMCAT_HOME%\bin\shutdown.bat -
在*nix系统上:
$TOMCAT_HOME/bin/shutdown.sh
-
在Windows上:
Tomcat的Servlet 2.2规范指定了Web应用的目录层次结构,从%TOMCAT_HOME%\webapps开始。本文示例使用名为jython的上下文,其目录结构如下:
- %TOMCAT_HOME%\webapps\
- %TOMCAT_HOME%\webapps\jython(上下文的根目录)
- %TOMCAT_HOME%\webapps\jython\WEB-INF
- %TOMCAT_HOME%\webapps\jython\WEB-INF\classes(Servlet类目录)
- %TOMCAT_HOME%\webapps\jython\WEB-INF\lib(库归档目录)
在继续示例之前,需要创建这些目录。重启Tomcat后,应该会看到以下信息确认新上下文已加载:
date time - ContextManager: Adding context Ctx( /jython )
4.2 安装Java Servlet
将JavaServlet.java文件放置在%TOMCAT_HOME%\webapps\jython\WEB-INF\classes目录中。由于该Servlet没有指定包,因此应放在classes目录的根目录下。在该目录中,使用以下命令编译JavaServlet.java文件:
javac -classpath %TOMCAT_HOME%\lib\servlet.jar JavaServlet.java
编译完成后,启动Tomcat服务器,然后在浏览器中访问http://localhost:8080/jython/servlet/JavaServlet,应该会看到消息“This is a Java Servlet.”
4.3 安装Jython Servlet
有两种方式使用Jython Servlets:一种是使用jythonc编译Servlet并将生成的类文件放在%TOMCAT_HOME%\jython\WEB-INF\classes目录中;另一种是使用Jython的PyServlet映射类。本文使用jythonc编译的方式,具体步骤如下:
1.
使用jythonc编译Jython Servlet模块
:编译时,classpath中必须包含servlet.jar文件,该文件包含javax.Servlet.*类和包,位于Tomcat的lib目录(%TOMCAT_HOME%\lib)中。将JythonServlet.py文件放在%TOMCAT_HOME%\jython\WEB-INF\classes目录中,确保环境变量CLASSPATH包含Servlet.jar,然后在该目录中使用以下命令编译:
jythonc -w . JythonServlet.py
使用 -w 开关指定当前工作目录,避免将生成的类文件从jpywork目录复制出来。编译后,classes目录中应该有两个类文件,如JythonServlet.java、JythonServlet.class和JythonServlet$_PyInner.class。编译过程中,需要注意查看以下信息:
Creating .java files:
JythonServlet module
JythonServlet extends javax.servlet.GenericServlet
如果没有看到这些信息,说明编译可能存在问题,需要检查CLASSPATH和设置选项,重新编译直到看到这些信息。
2.
将jython.jar文件添加到Web应用
:有三种方式将jython.jar文件添加到Tomcat的类路径中:
-
推荐方式
:将jython.jar文件放在上下文的lib目录中,即%TOMCAT_HOME%\webapps\jython\WEB-INF\lib。这样可以使Web应用自包含,便于归档、打包和在其他服务器上安装。
-
不推荐方式
:将jython.jar文件放在Tomcat的lib目录(%TOMCAT_HOME%\lib)中。虽然该目录中的Jar文件会自动添加到类路径中,但会使Web应用不再自包含,并且无法自动访问Jython的lib目录。
-
合理方式
:将jython.jar文件留在Jython的安装目录中,但在运行Tomcat之前将其添加到类路径中。这种方式可以消除重复的jython.jar文件,并且可以访问注册表文件和Jython的lib目录,但自包含的上下文仍然是首选。
3.
使Jython的lib目录对Servlets可用
:有三种方式使Jython的lib目录中的模块对Servlets可用:
-
设置python.home属性
:可以通过设置TOMCAT_OPTS环境变量来设置python.home属性。建议在上下文的WEB-INF目录中创建一个额外的目录,例如jylib。创建目录%TOMCAT_HOME%\webapps\jython\WEB-INF\jylib和%TOMCAT_HOME%\webapps\jython\WEB-INF\jylib\Lib,并将所需的模块放在该目录中。然后设置TOMCAT_OPTS:
- 在Windows上:
set TOMCAT_OPTS=-Dpython.home=%TOMCAT_HOME%\webapps\jython\WEB-INF\jylib
- 在
nix系统上:
export TOMCAT_OPTS=-Dpython.home=$TOMCAT_HOME/webapps/jython/WEB-INF/jylib
-
冻结所需模块
*:使用jythonc的 –deep 选项编译所有所需模块,使python.home和Jython的lib目录不再重要。在Jython上下文的classes目录中,使用以下命令冻结JythonServlet.py文件:
jythonc -w . --deep JythonServlet.py
编译后,JythonServlet和所有所需模块的类文件将位于上下文的classes目录中,实现了自包含的Web应用。但需要注意,以这种方式编译其他Jython Servlets可能会覆盖之前编译的模块,因此在更新模块时需要小心。编译完成后,生成的
.java文件可以删除。
-
每个Servlet显式地将模块位置添加到sys.path
*:这种方式通常需要显式的、依赖于机器的路径,从而限制了跨平台的可移植性。例如:
import sys
libpath = "c:/jakarta-tomcat_3.2.3/webapps/jython/WEB-INF/jylibs/Lib"
sys.path.append(libpath)
编译完成并设置好相关环境后,在浏览器中访问http://localhost:8080/jython/servlet/jythonServlet,应该会看到消息“This is a Servlet of the Jython variety.”如果没有看到该消息,可能会出现以下错误:
- 如果编译JythonServlet时classpath中没有Servlet.jar文件,可能会看到ClassCastException。
- 如果文件名和类名不同(即使只是大小写不同),也会看到ClassCastException。
- 如果jythonc编译生成的类文件之一不在上下文的classes目录中,会看到AttributeError。
- 如果Jython的模块不可用,会看到ImportError。
5. 关于GenericServlet
Listing 12.2中的Jython Servlet继承自javax.Servlet.GenericServlet,这是一个不特定于任何协议的Servlet类。在Web编程中,HttpServlet更为常见,因为它特定于HTTP协议,但GenericServlet是HttpServlet的超类,了解其方法很重要。以下是GenericServlet的一些常用方法及其在Jython子类中的使用示例:
| Java Signature | Usage in Jython Subclass |
| — | — |
|
METHODS YOU SUBCLASS
| |
| public void init(ServletConfig config) throws ServletException; | def init(self, config): |
| public void destroy(); | def destroy(self): |
| public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException; | def service(self, request, response): |
| public String getServletInfo(); | def getServletInfo(self): return “Info string” |
|
METHODS YOU USE WITH THE SELF. METHOD SYNTAX
| |
| public void log(String message); | self.log(“message”) |
| public ServletConfig getServletConfig(); | config = self.getServletConfig() |
| public java.util.enumeration getInitParameterNames(); | nameList = self.getInitParameterNames() |
| public String getInitParameter(String name) | param = self.getInitParameter(“paramName”) |
| public ServletContext getServletContext() | context = self.getServletContext |
以下是一个使用GenericServlet的HitCounter Servlet示例:
# filename: HitCounter.py
from javax import servlet
from time import time, ctime
import os
class HitCounter(servlet.GenericServlet):
def init(self, cfg=None):
if cfg:
servlet.GenericServlet.init(self, cfg)
else:
servlet.GenericServlet.init(self)
# Construct a path + filename to file storing hit data
contextRoot = self.servletContext.getRealPath(".")
self.file = os.path.join(contextRoot, "counterdata.txt")
if os.path.exists(self.file):
lastCount = open(self.file, "r").read()
try:
# within 'try' just in case the file is empty
self.totalHits = int(lastCount)
except:
self.totalHits = 0
else:
self.totalHits = 0
def service(self, req, res):
res.setContentType("text/html")
toClient = res.getWriter()
toClient.println("<html><body>")
toClient.println("Total Hits: %i<br>" % (self.totalHits,))
self.totalHits += 1
toClient.println("</body></html>")
def destroy(self):
f = open(self.file, "w")
f.write(str(self.totalHits))
f.close()
将HitCounter.py文件放在上下文的classes目录(%TOMCAT_HOME%\webapps\jython\WEB-INF\classes)中,并使用以下命令编译:
jythonc -w . --deep HitCounter.py
在浏览器中访问http://localhost:8080/jython/servlet/HitCounter,第一次访问时应该会看到消息“Total Hits: 0”,每次后续访问计数会递增。关闭并重启Tomcat后,计数应该会继续上一次的数值。
GenericServlet的三个重要方法init、service和destroy分别对应Servlet生命周期的不同阶段:
-
init(ServletConfig)方法
:在Servlet启动时调用,且仅调用一次。该方法适用于耗时的任务,如建立数据库连接、编译正则表达式或加载辅助文件。在上述示例中,init方法用于建立存储访问计数信息的文件,并将计数器设置为该文件中最后存储的整数。Servlet API 2.2版本有两个init方法:一个无参数版本和一个接受ServletConfig类实例的版本。Jython重写Java方法时会重写所有同名方法,因此示例中通过为cfg变量指定默认参数None,并测试其是否为None来决定是否使用该变量调用超类的init方法。
-
service(ServletRequest, ServletResponse)方法
:在每次客户端请求时调用。由于该方法在GenericServlet中是抽象的,因此Servlet必须定义该方法。该方法接收ServletRequest和ServletResponse对象作为参数,ServletRequest包含客户端发送到服务器的请求信息,如请求头、请求方法和请求参数;ServletResponse用于将响应流以适当的mime编码格式发送回客户端。
-
destroy()方法
:在关闭或卸载Servlet时调用。这里应放置清理代码,如关闭数据库连接、刷新和关闭文件等。在上述示例中,destroy方法将访问计数器的值写入文件,以确保Servlet卸载时信息不会丢失。但这不是最佳的持久化方法,因为只有在服务器正常关闭时,访问计数才会被保存。如果Servlet容器没有正确关闭,destroy方法可能不会被调用。
6. HttpServlet
GenericServlet适用于任何类似聊天的协议,但Web开发主要涉及HTTP协议。因此,在进行HTTP通信时,最好继承javax.Servlet.http.HttpServlet类,它是GenericServlet的子类,因此在继承HttpServlet时可以使用GenericServlet的init、service和destroy方法。
HttpServlet为每个HTTP方法定义了一个方法,如doGet、doPost、doPut、doOptions、doDelete和doTrace。当Tomcat接收到GET类型的客户端请求时,会调用被请求Servlet的doGet方法进行响应。此外,HttpServlet还有一个特定于HTTP的service方法,与GenericServlet的service方法相比,其参数为HTTP特定的请求和响应对象(javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse)。
以下是HttpServlet类的一些方法及其在Jython中的使用示例:
| Java Signature | Usage in Jython Subclass |
| — | — |
| doDelete(HttpServletRequest req, HttpServletResponse resp) | def doDelete(self, req, res): |
| doGet(HttpServletRequest req, HttpServletResponse resp) | def doGet(self, req, res): |
| doHead(HttpServletRequest req, HttpServletResponse resp) | def doHead(self, req, res): |
| doOptions(HttpServletRequest req, HttpServletResponse resp) | def doOptions(self, req, res): |
| doPost(HttpServletRequest req, HttpServletResponse resp) | def doPost(self, req, res): |
| doPut(HttpServletRequest req, HttpServletResponse resp) | def doPut(self, req, res): |
| doTrace(HttpServletRequest req, HttpServletResponse resp) | def doTrace(self, req, res): |
| getLastModified(HttpServletRequest req) | def getLastModified(self, req): |
| service(HttpServletRequest req, HttpServletResponse resp) | def service(self, req, res): |
| service(ServletRequest req, ServletResponse res) | def service(self, req, res): |
接收HTTP特定请求和响应对象的service方法会将请求重新分发到适当的do*方法(如果未被重写);接收通用Servlet请求和响应对象的service方法会将请求重新分发到HTTP特定的service方法。这种重新分发在实现Servlet映射时非常有用。
以下是一个使用HttpServlet的Servlet示例:
#file get_post.py
from time import time, ctime
from javax import servlet
from javax.servlet import http
class get_post(http.HttpServlet):
head = "<head><title>Jython Servlets</title></head>"
title = "<center><H2>%s</H2></center>"
def doGet(self, req, res):
res.setContentType("text/html")
out = res.getWriter()
out.println('<html>')
out.println(self.head)
out.println('<body>')
out.println(self.title % req.method)
out.println("This is a response to a %s request" %
(req.getMethod(),))
out.println("<P>In this GET request, we see the following " +
"header variables.</P>")
out.println("<UL>")
for name in req.headerNames:
out.println(name + " : " + req.getHeader(name) + "<br>")
out.println("</UL>")
out.println(self._params(req))
out.println("""
<P>The submit button below is part of a form that uses the
"POST" method. Click on this button to do a POST request.
</P>""")
out.println('<br><form action="get_post" method="POST">' +
'<INPUT type="hidden" name="variable1" value="one">' +
'<INPUT type="hidden" name="variable1" value="two">' +
'<INPUT type="hidden" name="variable2" value="three">' +
'<INPUT type="submit" name="button" value="submit">')
out.println('<br><font size="-2">time accessed: %s</font>'
% ctime(time()))
out.println('</body></html>')
def doPost(self, req, res):
res.setContentType("text/html");
out = res.getWriter()
out.println('<html>')
out.println(self.head)
out.println('<body>')
out.println(self.title % req.method)
out.println("This was a %s<br><br>" % (req.getMethod(),))
out.println(self._params(req))
out.println('<br> back to <a href="get_post">GET</a>')
out.println('<br><font size="-2">time accessed: %s</font>'
% ctime(time()))
out.println('</body></html>')
def _params(self, req):
params = "Here are the parameters sent with this request:<UL>"
names = req.getParameterNames()
if not names.hasMoreElements():
params += "None<br>"
for name in names:
value = req.getParameterValues(name)
params += "%s : %r<br>" % (name, tuple(value))
params += "</UL>"
return params
将get_post.py文件放在$TOMCAT_HOME/webapps/jython/WEB-INF/classes目录中,并使用以下命令编译:
jythonc -w . --deep get_post.py
在浏览器中访问http://localhost:8080/jython/servlet/get_post,应该会看到一个类似于Figure 12.1的浏览器窗口。在这个示例中,GET操作的参数为None,但可以通过在URL末尾添加参数(如http://localhost:8080/servlet/get_post?variable1=1&variable2=2)来测试doGet方法中的其他参数。由于页面底部的提交按钮是一个使用POST方法的表单,点击该按钮将执行同一Servlet的doPost方法,结果应与Figure 12.2所示一致。
7. HttpServletRequest和HttpServletResponse
与客户端的通信通过HttpServletRequest和HttpServletResponse对象进行,它们是从客户端接收和发送到客户端的实际请求流的抽象。每个对象都添加了更高级的、特定于HTTP的方法,以方便处理请求和响应。
以下是HttpServletRequest对象的一些方法及其对应的Bean属性名称:
| Method and Property | Description |
| — | — |
|
Method: getAuthType()
Property name: AuthType
| 返回一个字符串(PyString),描述认证类型的名称。如果用户未认证,值为None。 |
|
Method: getContextPath()
Property name: contextPath
| 返回一个字符串(PyString),描述标识所请求上下文的路径信息。 |
|
Method: getCookies()
Property name: cookies()
| 返回客户端请求中发送的所有Cookie,作为avax.servlet.http.Cookie对象的数组。 |
|
Method: getDateHeader(name)
| 以长类型检索指定头的值。 |
|
Method: getHeader(name)
| 以字符串(PyString)形式返回指定头的值。 |
|
Method: getHeaderNames()
Property name: headerNames
| 返回请求中包含的所有头名称,作为一个枚举。 |
|
Method: getHeaders(name)
| 返回指定头名称的所有值,作为一个枚举。 |
|
Method: getIntHeader(name)
| 以Java int类型检索指定头的值,Jython将其转换为PyInteger。 |
|
Method: getMethod()
Property name: method
| 返回请求的类型,作为一个字符串。 |
|
Method: getPathInfo()
Property name: pathInfo
| 客户端发送的所有额外路径信息。 |
|
Method: getPathTranslated()
Property name: pathTranslated
| 返回从客户端请求的额外路径信息派生的实际路径。 |
|
Method: getQueryString()
Property name: queryString
| 返回客户端请求中的查询字符串(路径后面的字符串)。 |
|
Method: getRemoteUser()
Property name: remoteUser
| 返回客户端的登录名。如果客户端未认证,值为None。 |
|
Method: getRequestedSessionId()
Property name: requestedSessionId
| 返回客户端的会话ID。 |
|
Method: getRequestURI()
Property name: requestURI
| 返回协议名称和查询字符串之间的部分。 |
|
Method: getServletPath()
Property name: servletPath
| 返回指定当前Servlet的URL部分。 |
|
Method: getSession()
Property name: session
| 返回当前会话,如果需要则创建一个。会话是javax.servlet.http.HttpSession的实例。 |
|
Method: getSession(create)
| 如果存在当前会话,则返回该会话。如果不存在且create值为true,则创建一个新会话。 |
|
Method: getUserPrincipal()
Property name: userPrincipal
| 返回一个包含当前认证信息的java.security.Principal对象。 |
|
Method: isRequestedSessionIdFromCookie()
| 根据当前会话ID是否来自Cookie返回1或0。 |
|
Method: isRequestedSessionIdFromURL()
| 根据当前会话ID是否来自请求的URL字符串返回1或0。 |
|
Method: isRequestedSessionIdValid()
| 根据请求的会话ID是否仍然有效返回1或0。 |
|
Method: isUserInRole(role)
| 根据用户是否在指定角色中返回1或0。 |
HttpServletResponse对象用于将mime编码的流发送回客户端,它定义了一些在通用ServletResponse对象中不存在的特定于HTTP的方法。以下是HttpServletResponse对象的一些方法及其对应的Bean属性名称:
| Method and Property | Description |
| — | — |
| addCookie(cookie) | 向响应中添加一个Cookie。 |
| addDateHeader(headerName, date) | 添加一个带有日期(长类型)值的头名称。 |
| addHeader(headerName, value) | 添加一个头名称和值。 |
| addIntHeader(headerName, value) | 添加一个带有整数值的头名称。 |
| containsHeader(headerName) | 根据指定的头是否存在返回1或0。 |
| encodeRedirectUrl(url) | 为sendRedirect方法编码URL。对于2.1及更高版本,建议使用encodeRedirectURL。 |
| encodeRedirectURL(url) | 为sendRedirect方法编码URL。 |
| encodeURL(url) | 通过在URL中包含会话ID来编码URL。 |
| sendError(sc) | 使用状态码发送错误。 |
| sendError(sc, msg) | 使用指定的状态码和消息发送错误。 |
| sendRedirect(location) | 发送临时重定向到指定位置。 |
| setDateHeader(headerName, date) | 将头名称设置为指定的日期(长类型)值。 |
| setHeader(headerName, value) | 将头名称设置为指定的值。 |
| setIntHeader(headerName, value) | 将头名称设置为指定的整数值。 |
| setStatus(statusCode) status | 设置响应状态码。 |
HttpServletResponse类还包含与标准HTTP响应代码对应的字段,可以与sendError(int)和setStatus(int)方法一起使用。以下是一些常见的HTTP响应状态码:
| Error Code | Status |
| — | — |
| 100 | SC_CONTINUE |
| 101 | SC_SWITCHING_PROTOCOLS |
| 200 | SC_OK |
| 201 | SC_CONTINUE |
| 202 | SC_ACCEPTED |
| 203 | SC_NON_AUTHORITATIVE_INFORMATION |
| 204 | SC_NO_CONTENT |
| 205 | SC_RESET_CONTENT |
| 206 | SC_PARTIAL_CONTENT |
| 300 | SC_MULTIPLE_CHOICES |
| 301 | SC_MOVED_PERMANENTLY |
| 302 | SC_MOVED_TEMPORARILY |
| 303 | SC_SEE_OTHER |
| 304 | SC_NOT_MODIFIED |
| 305 | SC_USE_PROXY |
| 400 | SC_BAD_REQUEST |
| 401 | SC_UNAUTHORIZED |
| 402 | SC_PAYMENT_REQUIRED |
| 403 | SC_FORBIDDEN |
| 404 | SC_NOT_FOUND |
| 405 | SC_METHOD_NOT_ALLOWED |
| 406 | SC_NOT_ACCEPTABLE |
| 407 | SC_PROXY_AUTHENTICATION_REQUIRED |
| 408 | SC_REQUEST_TIMEOUT |
| 409 | SC_CONFLICT |
| 410 | SC_GONE |
| 411 | SC_LENGTH_REQUIRED |
| 412 | SC_PRECONDITION_FAILED |
| 413 | SC_REQUEST_ENTITY_TOO_LARGE |
| 414 | SC_REQUEST_URI_TOO_LONG |
| 415 | SC_UNSUPPORTED_MEDIA_TYPE |
| 500 | SC_INTERNAL_SERVER_ERROR |
| 501 | SC_NOT_IMPLEMENTED |
| 502 | SC_BAD_GATEWAY |
| 503 | SC_SERVICE_UNAVAILABLE |
| 504 | SC_GATEWAY_TIMEOUT |
| 505 | SC_HTTP_VERSION_NOT_SUPPORTED |
8. PyServlet
对于Jython程序员来说,HttpServlet编程的一个重要优势是Jython发行版附带了org.python.util.PyServlet类。这个Servlet可以加载、执行和缓存Jython文件,使得在编写和查看Jython Servlet时无需中间的编译步骤。这通过Servlet映射实现,Servlet映射是特定Servlet(这里是PyServlet)与特定URL模式(如 .py)之间的关联。通过正确配置web.xml文件,Jython的PyServlet类可以映射到所有 .py文件,当请求 .py文件时,PyServlet类会根据需要加载、缓存和调用 .py文件中的方法进行响应。
PyServlet的设计对其所服务的Jython文件有一些限制。Jython文件必须包含一个继承自javax.servlet.http.HttpServlet的类,且该类的名称必须与文件名称(不包括.py扩展名)匹配。此外,除了继承自HttpServlet的类之外,使用模块全局标识符是不安全的。
为了避免命名混淆,这里将所有实现Servlet功能的代码统称为servlet,PyServlet特指org.python.util.PyServlet类。而PyServlet实际服务的Jython模块(匹配*.py模式的文件),为了便于区分,本文将其称为jylets。
8.1 安装PyServlet
安装PyServlet只需要定义一个Servlet映射,并确保jython.jar文件位于上下文的lib目录中。Servlet映射在上下文的部署描述符文件web.xml中定义,Jython上下文的web.xml文件位于$TOMCAT_HOME/webapps/jython/WEB-INF/web.xml。在Tomcat中,如果上下文的web.xml文件未显式包含某些设置,则使用$TOMCAT_HOME/conf/web.xml中的默认web.xml文件。因此,上下文可能还没有web.xml文件,并且无需在该上下文中定义所有属性,因为默认值已经存在。
以下是一个Jython上下文的示例部署描述符:
<web-app>
<servlet>
<servlet-name>PyServlet</servlet-name>
<servlet-class>
org.python.util.PyServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>PyServlet</servlet-name>
<url-pattern>*.py</url-pattern>
</servlet-mapping>
</web-app>
这个部署描述符定义了Servlet的名称为PyServlet,类为org.python.util.PyServlet。该类的完整包类层次结构唯一标识了Servlet类,实际的类位于jython.jar文件中,该文件应位于上下文的lib目录中。
PyServlet的Servlet定义可以选择性地包含初始化参数(init-params),PyServlet在初始化Jython时会使用这些参数。因此,可以在这里设置Jython的属性,如python.home、python.path、python.respectJavaAccessibility等,这些属性通常在Jython的注册表文件中设置。以下是一个在web.xml文件中提供python.home和python.path值作为init-params的示例:
<web-app>
<servlet>
<servlet-name>PyServlet</servlet-name>
<servlet-class>
org.python.util.PyServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>python.home</param-name>
<param-value>c:\jython-2.1</param-value>
</init-param>
<init-param>
<param-name>python.path</param-name>
<param-value>
c:\jython-2.1\lib\site-packages
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>PyServlet</servlet-name>
<url-pattern>*.py</url-pattern>
</servlet-mapping>
</web-app>
定义Jython资源位置的属性(如python.home和python.path)需要特别注意,因为它们会影响上下文的自包含性和跨平台可移植性。如果python.home属性指向上下文之外,或者python.path属性包含上下文之外的目录,则上下文将不再自包含。此外,这些属性值必须是特定于平台的路径。幸运的是,PyServlet为python.home属性提供了一个默认值,该值可以创建一个自包含且跨平台中立的上下文。
PyServlet的默认python.home值是上下文的lib目录,对于Jython上下文,根据平台的不同,该值可能是%TOMCAT_HOME\webapps\jython\WEB-INF\lib或$TOMCAT_HOME/webapps/jython/WEB-INF/lib。此外,Jython的lib目录会自动变为%TOMCAT_HOME%\webapps\jython\WEB-INF\lib\Lib或$TOMCAT_HOME/webapps/jython/WEB-INF/lib/Lib。这使得所有Jython资源都包含在上下文中,保持了自包含性。而且,PyServlet以平台无关的方式添加默认的python.home路径,使得使用默认值的上下文具有跨平台的独立性。
8.2 测试PyServlet
可以通过启动Tomcat并在浏览器中查看一个简单的jylet来确认Servlet映射是否正常工作。以下是一个简单的测试jylet示例:
# File ServletMappingTest.py
from javax.servlet.http import HttpServlet
class ServletMappingTest(HttpServlet):
def doGet(self, req, res):
out = res.writer
res.contentType = "text/html"
print >> out, "Greetings from a jylet."
将这个测试文件保存为%TOMCAT_HOME%\webapps\jython\ServletMappingTest.py,然后在浏览器中访问http://localhost:8080/jython/ServletMappingTest.py。如果看到问候消息,则说明PyServlet映射正确。为了完成安装,还需要创建Jython的lib目录%TOMCAT_HOME%\webapps\jython\WEB-INF\lib\Lib,这是放置jylets中使用的任何Jython模块的目录。完成这些步骤后,安装就完成了。
9. Cookies
Cookies允许在客户端机器上存储信息,并在后续请求中包含这些信息。在Jython中,可以使用javax.Servlet.http.Cookie类来创建和操作Cookies。创建一个新的Cookie需要使用名称和值作为参数实例化javax.servlet.http.Cookie类。例如,如果要使用Cookies跟踪书籍购买信息,可以这样设置:
from javax.servlet import http
name = "book1"
value = "Lewis Carroll, Alice In Wonderland"
MyNewCookie = http.Cookie(name, value)
现在就创建了一个名为book1的新Cookie,其值为“Lewis Carroll, Alice In Wonderland”。要将这个Cookie发送给客户端,需要使用HttpServletResponse的addCookie(cookie)方法:
res.addCookie(MyNewCookie)
需要注意的是,添加Cookies应该在通过响应流发送其他内容之前进行。
每个Cookie实例都有一些方法可以设置其具体属性,也可以使用Jython的自动Bean属性来设置以下属性:
- comment
- domain
- maxAge
- name
- path
- secure
- value
- version
以下是一个使用Cookie对象的自动Bean属性来创建和读取Web表单中定义的Cookies的示例:
# File: cookies.py
from javax import servlet
from javax.servlet import http
class cookies(http.HttpServlet):
def doGet(self, req, res):
res.setContentType("text/html")
out = res.getOutputStream()
print >>out, "<html><head><title>Cookies with Jython</title></head>"
print >>out, """
<body>\n<H2>Cookie list:</H2>
Remember, cookies must be enabled in your browser.<br><br>"""
# Here's the list of Cookies
for c in req.cookies:
print >>out, """
<b>Cookie Name</b>= %s
<b>Value</b>= %s<br><br>""" % (c.name, c.value)
print >>out, """<br><br><br>
<HR><P>Use this form to add a new cookie</P>
<form action="cookies.py" method="POST">
<P>Name:<br><INPUT type="text" name="name" size="30"></P>
<P>Value:<br><INPUT type="text" name="value" size="30"></P>
<P>Use the MaxAge field to set the cookie's time-to-expire.
A value of "0" deletes the cookie immediately, a value of
"-1" saves the cookie until the browser exits, and
any other integer represents seconds until it expires
(i.e.- using "10" would expire 10 seconds after being set)).</P>
<P>MaxAge:<br><INPUT type="text" name="maxAge" size="30"></P>
<INPUT type="submit" name="button" value="submit">
</body>
</html>
"""
def doPost(self, req, res):
res.setContentType("text/html");
out = res.getWriter()
name = req.getParameterValues("name")[0]
value = req.getParameterValues("value")[0]
maxAge = req.getParameterValues("maxAge")[0]
if name:
newCookie = http.Cookie(name, value)
newCookie.maxAge = int(maxAge or -1)
newCookie.comment = "Jython test cookie"
res.addCookie(newCookie)
print >>out, """
<html><body>Cookie set successfully\n\n
<P>click <a href="cookies.py">here</a>
to view the new cookie.</P>
<P>If cookies are enabled in your
browser that is.</P>
</body>
</html>"""
else:
print >>out, """
<html>\n<body>
Cookie not set
<P>No cookie "Name" provided</P>
<P>click <a href="cookies">here</a>
to try again</P>
</body>
</html>"""
要测试这个jylet,首先确保浏览器允许使用Cookies。然后将cookies.py文件放在%TOMCAT_HOME%\webapps\jython目录中,并在浏览器中访问http://localhost:8080/jython/cookies.py。第一次访问时,应该只会看到一个标题和一个表单。在表单中输入信息(例如,名称为“Jython Cookie”,值为“My first Jython cookie”),然后点击提交按钮(MaxAge可选)。应该会看到确认消息,表明Cookie添加成功。为了确认Cookie是否真的添加成功,可以返回doGet方法,查看Cookie列表中是否显示了新添加的Cookie。
10. Sessions
Cookies是与客户端创建会话的最常见方式,会话是通过一系列请求跟踪客户端信息的一种手段。虽然前面的Cookie示例(见Listing 12.7)可以用于存储会话ID,但Java的HttpSession类使会话跟踪更加容易。
要创建一个会话,可以使用HttpRequest的getSession()方法,该方法返回一个HttpSession实例。HttpSession是会话管理子系统更复杂行为的简单接口。从Jython使用HttpSession对象与Java的区别仅在于语法和对象的get*方法的自动Bean属性。
以下是一个使用简单的req.session Bean属性创建会话对象的示例:
# File: session.py
from javax import servlet
from javax.servlet import http
class session(http.HttpServlet, servlet.RequestDispatcher):
def doGet(self, req, res):
sess = req.session
res.contentType = "text/html"
out = res.getWriter()
name = req.getParameterValues("name")
value = req.getParameterValues("value")
if name and value:
sess.putValue(name[0], value[0])
print >>out, ("""
<html>
<body>
<H3>Session Test</H3>
Created at %s<br>
Last Accessed = %s<br>
<u>Values:</u>""" %
(sess.creationTime, sess.maxInactiveInterval)
)
print >>out, "<ul>"
for key in sess.getValueNames():
print >>out, "<li>%s: %s</li>" % (key, sess.getValue(key))
print >>out, "</ul>"
print >>out, """
<HR><P>Use this form to add a new values to the session</P>
<form action="session.py" method="GET">
<P>Name:<br><INPUT type="text" name="name" size="30"></P>
<P>Value:<br><INPUT type="text" name="value" size="30"></P>
<INPUT type="submit" name="button" value="submit">
</body>
</html>
"""
这个示例允许Cookie或URL包含会话值。由于Cookies可能存储会话值,因此在向输出流发送其他数据之前,应该使用req.session Bean属性或req.getSession()方法。如果客户端禁用了Cookies,会话对象仍然可以工作,因为所有URL都会使用encodeUrl方法进行重写。实际上,只需要重写一个URL,即表单的action属性。如果有其他URL,也必须通过res.encodeUrl()方法进行处理,以支持无Cookies的会话。
一旦有了HttpSession实例,通过会话传递数据就只需要使用键值对,使用putValue(key, value)方法存储数据,使用getValue(key)方法获取数据。
将session.py文件保存到%TOMCAT_HOME%\webapps\jython目录中,然后在浏览器中访问http://localhost:8080/jython/session.py。使用网页表单向会话中添加一些变量,以确认会话功能正常。甚至可以尝试禁用Cookies,查看URL重写是如何工作的。
11. 数据库与Servlets
在jylet中连接数据库、执行语句和遍历结果集与其他Jython数据库应用程序没有区别。然而,管理连接和其他数据库资源是一个主要问题,因为许多Web应用程序包含多个使用数据库内容的jylet。在每个jylet中创建数据库连接会迅速消耗数据库资源,导致不良后果。另一方面,为每个请求创建和关闭数据库连接会带来不可接受的开销。
管理数据库资源有多种选择,但主要有两种方法:每个jylet使用一个连接,或者使用连接池。每个jylet使用一个连接是一种流行但资源密集的方法,这种方法涉及在jylet的init方法中建立数据库连接,并仅在jylet卸载时关闭该连接。这样可以消除响应客户端请求时的连接开销,但只有在所需连接数量在资源限制范围内时才合理。大多数情况下,需要更谨慎的资源管理。
以下是一个在init方法中获取数据库连接和游标对象,并在destroy方法中关闭它们的jylet示例:
# file: DBDisplay.py
from javax.servlet import http
from com.ziclix.python.sql import zxJDBC
class DBDisplay(http.HttpServlet):
def init(self, cnfg):
#define the JDBC url
url = "jdbc:mysql://192.168.1.77/products"
usr = "productsUser" # replace with real user name
passwd = "secret" # replace with real password
driver = "org.gjt.mm.mysql.Driver"
#connect to the database and get cursor object
self.db = zxJDBC.connect(url, usr, passwd, driver)
self.c = self.db.cursor()
def doGet(self, req, res):
res.setContentType("text/html")
out = res.getWriter()
print >>out, """
<html>
<head>
<title>Jylet Database Connection</title>
</head>
<body>
<table align="center">
<tr>
<td><b>ID</b></td>
<td><b>Title</b></td>
<td><b>Description</b></td>
<td><b>Price</b></td>
</tr>"""
self.c.execute("select code, name, description, price from products")
for row in self.c.fetchall():
print >>out, """
<tr>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td>%s</td>""" % row
print >>out, """
</table>
</body>
</html>"""
def destroy(self):
self.c.close()
self.db.close()
这个示例假设存在一个名为products的数据库,其中包含products表,并且该表至少有code、name、description和price字段。要创建这样的数据库,可以使用以下SQL语句:
create database products
要创建products表,可以使用以下SQL语句:
CREATE TABLE products (
primarykey int(11) NOT NULL auto_increment,
code varchar(55) default NULL,
name varchar(255) default NULL,
description text,
price float(5,2) default NULL,
PRIMARY KEY (primarykey)
) TYPE=MyISAM;
创建数据库和表后,为每个字段创建一些任意值,并将DBDisplay.py文件放在上下文的根目录中。然后在浏览器中访问http://localhost:8080/jython/DBDisplay.py,应该可以看到数据库数据。
如果Web应用程序开始使用过多的连接,可以考虑使用连接池。连接池允许进行谨慎的资源管理,并消除连接开销。连接池维护一定数量的活动数据库连接,jylet在响应客户端请求时借用这些连接,并在完成后将其返回给池。这样可以创建一个可预测的、静态的连接数量。也可以使用语句池,为语句提供相同的优势。一个流行的、免费的、经过测试且文档完善的连接池工具是PoolMan,可从http://www.codestudio.com/获取。其他Java连接池包也存在,并且都应该可以与Jython无缝协作。
12. JSP
Java Server Pages (JSP) 是Sun倡导的一种模板系统,作为Servlets的补充。JSP文件包含网页的标记代码和文本,同时还包含指定动态内容的特殊标签。目前,Tomcat只在JSP中实现Java语言,那么如何在JSP中使用Jython呢?目前的答案是不能直接使用Jython。不能在代码标签(<% code %>)中使用Jython语言编写代码。但可以使用jythonc编译的类、嵌入PythonInterpreter或创建特定于Jython的自定义标签库。
12.1 jythonc编译的类与JSP
在JSP中使用jythonc编译的类需要创建一个与Java兼容的Jython类,该类的名称与模块名称相同,继承自Java类,并且为每个不是从超类派生的方法包含@sig字符串。将jythonc生成的类文件放在上下文的classes目录中,JSP页面就可以像使用任何本地Java类一样使用这些类。当然,这也需要将jython.jar文件放在上下文的lib目录中。
JSP文件可以通过两种方式使用jythonc编译的类:在脚本片段中使用,或作为Bean使用。如果要作为Bean使用,Jython类必须符合Bean约定,为每个读写属性包含getProperty和setProperty方法。而脚本片段可以使用任何类。无论是作为Bean还是在脚本片段中使用,都必须首先将jythonc编译的类加载到适当的作用域中。要导入非Bean类,可以使用页面导入指令:
<%@ page import="fully.qualified.path.to.class" %>
对于Bean,可以使用jsp:useBean标签加载Bean:
<jsp:useBean name="beanName"
class="fully.qualified path.to.class"
scope="scope(page or session)">
要使用非Bean类,可以在脚本片段标签(<% %>)或表达式标签(<%= %>)中包含使用该类的Java代码。例如,如果导入了假设的类ProductListing,可以在JSP页面中使用该类:
<%@ page import="ProductListing" %>
<html>
<body>
<!--The next line begins the scriptlet -->
<% ProductListing pl = new ProductListing(); %>
<table>
<tr>
<td><%= pl.productOne %></td>
<td><%= pl.productTwo %></td>
</tr>
</table>
脚本片段通常不被推荐,因为它们会使JSP页面变得复杂。首选的实现方式是使用jythonc编译的Bean,结合JSP的useBean、setProperty和getProperty标签。以下是一个简单的用Jython编写的Bean示例,用于存储用户名:
# file: NameHandler.py
import java
class NameHandler(java.lang.Object):
def __init__(self):
self.name = "Fred"
def getUsername(self):
"@sig public String getname()"
return self.name
def setUsername(self, name):
"@sig public void setname(java.lang.String name)"
self.name = name
将NameHandler.py文件放在%TOMCAT_HOME%\webapps\jython\WEB-INF\classes目录中,并使用以下命令编译:
jythonc -w . Namehandler.py
以下是一个使用NameHandler Bean的简单JSP页面示例:
<!--file: name.jsp -->
<%@ page contentType="text/html" %>
<jsp:useBean id="name" class="NameHandler" scope="session
### Jython在服务器端Web编程中的应用
##### 12.2 嵌入PythonInterpreter到JSP
如果希望在JSP脚本片段中使用Jython代码,可以通过PythonInterpreter实例间接实现。这需要使用导入指令导入`org.python.util.Pythoninterpreter`。以下是一个简单的JSP页面示例,使用PythonInterpreter对象在JSP页面中包含Jython代码:
```jsp
<!--name: interp.jsp-->
<%@ page contentType="text/html" %>
<%@ page import="org.python.util.PythonInterpreter" %>
<% PythonInterpreter interp = new PythonInterpreter();
interp.set("out", out); %>
<html>
<body bgcolor="white">
<% interp.exec("out.println('Hello from JSP and the Jython interpreter.')"); %>
</body>
</html>
要使用
interp.jsp
文件,确保
jython.jar
文件位于上下文的
lib
目录中,并将
interp.jsp
文件放在上下文的根目录(
%TOMCAT_HOME%\webapps\jython
)中。然后在浏览器中访问
http://localhost:8080/jython/interp.jsp
,应该会看到Jython解释器输出的简单消息。
需要注意的是,许多人认为在JSP中使用脚本片段不是好的做法,因为这会增加代码的复杂性。因此,使用Jython在Java脚本片段中编写代码显然是有问题的。更好的方法是使用Bean类和标签库来创建动态内容。
12.3 Jython标签库
标签库是可以在JSP页面中使用的自定义标签库。可以使用
jythonc
将Jython标签库模块编译成Java类来创建Jython标签库。一个Jython标签库模块必须满足一定的限制才能像Java类一样透明地工作。模块必须包含一个与模块名称(不包括
.py
扩展名)相同的类,该类必须继承自
javax.servlet.jsp.tagext.Tag
接口或实现该接口的Java类。编译后的类文件必须能够访问
org.python.*
包和类,以及Jython库模块(如果标签库导入了任何模块)。
为了使标签库能够访问所有必需的类和库,可以使用
jythonc
的
--all
选项进行编译,就像之前在编译Servlet时所做的那样。这样会创建一个
jar
文件,然后将其放在上下文的
lib
目录中。但问题是,很可能许多资源都会使用Jython核心文件,因此反复使用
--core
、
--deep
或
--all
进行编译会造成不必要的冗余。更好的方法是将
jython.jar
文件包含在上下文的
lib
目录中,在上下文中为Jython的模块建立一个
lib
目录(建议使用
{context}\WEB-INF\lib\Lib
,具体解释见前面的“PyServlet”部分),然后在不依赖其他资源的情况下编译Jython标签库模块。
以下是一个简单的Jython标签库模块示例,用于剖析标签库的基本结构:
# file: JythonTag.py
from javax.servlet.jsp import tagext
class JythonTag(tagext.Tag):
def __init__(self):
self.context = None
self.paren = None
def doStartTag(self):
return tagext.Tag.SKIP_BODY
def doEndTag(self):
out = self.context.out
print >>out, "Message from a taglib"
return tagext.Tag.EVAL_PAGE
def release(self):
pass
def setPageContext(self, context):
self.context = context
def setParent(self, parent):
self.paren = parent
def getParent(self):
return self.paren
在使用这个标签库时,需要注意实例变量
self.context
和
self.paren
。由于
Tag
接口要求实现
setPageContext()
、
setParent()
和
getParent()
方法,Jython会为这些方法对应的属性名创建自动Bean属性。这可能会导致循环引用和
StackOverflowExceptions
。例如,在
setPageContext
方法中,如果直接将
context
赋值给
self.context
,由于
self.context
是自动Bean属性,实际上会调用
self.setPageContext(context)
,从而形成循环引用。这种情况在使用
jythonc
编译的类在Java框架中时需要特别关注。
要安装标签库所需的类,首先确保
jython.jar
文件位于上下文的
lib
目录中,然后使用以下命令编译
JythonTag.py
文件:
jythonc -w %TOMCAT_HOME%\webapps\jython\WEB-INF\classes JythonTag.py
这将在上下文的
classes
目录中创建
JythonTag.class
和
JythonTag$_PyInner.class
文件。然而,仅这些类文件还不足以使用标签库,在JSP页面中使用标签之前,还必须创建一个标签库描述文件。以下是一个适合描述上述标签的标签库描述文件示例:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>JythonTag</shortname>
<info>A simple Jython tag library</info>
<tag>
<name>message</name>
<tagclass>JythonTag</tagclass>
</tag>
</taglib>
将上述标签库描述信息保存到
%TOMCAT_HOME%\webapps\jython\WEB-INF\jython-taglibs.tld
文件中。JSP页面将使用这个文件来识别页面中使用的标签。
标签库描述文件用于标识标签库的特性,如版本号、适用的JSP版本等。它还指定了一个简短名称(
<shortname>
)用于引用这个标签库。剩余的元素定义了在前面示例中创建的具体标签。
<tag>
元素标识了完全限定的类名,并为该类分配了一个名称。由于
JythonTag
没有在包中,它本身就是一个完全限定的名称,并且这个类与名称
message
相关联。
在JythonTag编译完成并保存了标签库描述文件后,接下来的步骤是在JSP页面中使用这个标签库。JSP页面必须包含一个指令,指定标签库描述文件的路径,并为该标签库分配一个简单的名称。后续对这个标签库中标签的引用都将以这个分配的名称开头。对于前面创建的标签库,JSP指令如下所示:
<%@ taglib uri="/WEB-INF/jython-taglibs.tld" prefix="jython" %>
由于
.tld
文件为示例标签指定了名称
message
,在声明这个标签库后,可以使用以下方式使用
message
标签:
<jython:message/>
jython
部分来自JSP声明中分配的名称,而
message
部分来自标签库描述中为标签类分配的名称。以下是一个使用上述标签和标签库描述文件的JSP文件示例:
<%@ taglib uri="/WEB-INF/jython-taglibs.tld" prefix="jython" %>
<html>
<body>
<jython:message />
</body>
</html>
将这个
test.jsp
文件保存到
%TOMCAT_HOME%\webapps\jython\test.jsp
,然后在浏览器中访问
http://localhost:8080/jython/test.jsp
,应该会看到自定义标签输出的消息。
使用编译后的Jython模块实现标签库时,再次涉及到Jython的主目录和Jython的
lib
目录的问题。一个
jythonc
编译的标签库不知道
python.home
在哪里或在哪里找到库模块,除非在
PySystemState
中设置了这些信息。可以在
TOMCAT_OPTS
环境变量中设置
python.home
属性,也可以利用
PyServlet
类来建立这个系统状态信息。如果当前上下文加载了
PyServlet
类,那么
python.home
和
sys.path
信息可能已经为需要的标签库设置正确。在默认的
PyServlet
设置下,
python.home
是
%TOMCAT_HOME%\webapps\jython\WEB-INF\lib
,因此Jython的
lib
目录是
%TOMCAT_HOME%\webapps\jython\WEB-INF\lib\Lib
。加载了
PyServlet
后,标签库可以从同一个
lib
目录加载Jython模块。
12.4 BSF
IBM的Bean Scripting Framework (BSF) 是一个实现了多种脚本语言的Java工具,Jython是目前BSF支持的语言之一。Apache Jakarta项目包含一个名为
taglibs
的子项目,它是一个广泛的Java标签库集合,其中有趣的是BSF标签库。BSF标签库与BSF的
jar
文件结合使用,可以让你快速将Jython脚本片段和表达式直接插入到JSP页面中。
BSF发行版目前需要进行一些调整才能与Jython一起使用。由于BSF发行版最终会包含这些小的更改,这里不详细说明具体的调整内容。为了方便使用,与本书相关的网站(
http://www.newriders.com/
)将提供一个链接,你可以从那里下载正确的
bsf.jar
文件。下载
bsf.jar
文件后,将其放在上下文的
lib
目录(
%TOMCAT_HOME%\webapps\jython\WEB-INF\lib
)中。该网站还将包含BSF标签库和
bsf.tld
文件的下载链接。将包含BSF标签库的
jar
文件放在上下文的
lib
目录中,并将
bsf.tld
文件放在上下文的
WEB-INF
目录(
%TOMCAT_HOME%\webapps\jython\WEB-INF\bsf.tld
)中。
安装这些文件后,就可以在JSP文件中使用Jython脚本片段了。首先,必须包含一个指令来识别BSF标签库:
<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %>
然后可以使用
bsf.scriptlet
标签,在标签体中包含Jython代码,如下所示:
<bsf:scriptlet language="jython">
import random
print >> out, random.randint(1, 100)
</bsf:scriptlet>
这个脚本片段在解释器中自动设置了一些JSP对象,以下是这些对象及其Java类名的列表:
| 对象 | Java类名 |
| — | — |
| request | javax.servlet.http.HttpServletRequest |
| response | javax.servlet.http.HttpServletResponse |
| pageContext | javax.servlet.jsp.PageContext |
| application | javax.servlet.ServletContext |
| out | javax.servlet.jsp.JspWriter |
| config | javax.servlet.ServletConfig |
| page | java.lang.Object |
| exception | java.lang.Exception |
| session | javax.servlet.http.HttpSession |
大多数这些对象在Servlet中是熟悉的,而BSF脚本片段标签允许你在JSP页面中使用它们。以下是一个使用
bsf:scriptlet
标签在JSP文件中创建时间戳的JSP页面示例:
<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %>
<html>
<body>
<center><H2>BSF scriptlets</H2></center>
<b>client info:</b><br>
<bsf:scriptlet language="jython">
for x in request.headerNames:
print >>out, "%s: %s<br>\n" % (x, request.getHeader(x))
</bsf:scriptlet>
<br><br>
<b>Time of request:</b><br>
<bsf:scriptlet language="jython">
import time
print >>out, time.ctime(time.time())
</bsf:scriptlet>
</body>
</html>
将上述代码保存为
%TOMCAT_HOME%\webapps\jython\scriptlets.jsp
文件,然后在浏览器中访问
http://localhost:8080/jython/scriptlets.jsp
。你应该会看到所有的客户端信息以及访问时间。
总结
综上所述,Jython在服务器端Web编程中展现出了强大的功能和广泛的应用场景。从与各种Servlet容器的集成,到简单Servlet类的定义和测试;从对GenericServlet和HttpServlet的深入理解和使用,到Cookies和Sessions的管理;从数据库连接和资源管理,到与JSP的多种集成方式,Jython都提供了丰富的解决方案。
在选择使用Jython进行Web开发时,需要根据具体的项目需求和场景,合理选择合适的技术和方法。例如,在需要快速开发和提高灵活性的场景中,Jython的动态特性和简洁的语法可以发挥很大的优势;而在对性能和资源管理要求较高的情况下,则需要谨慎选择数据库连接和管理方式,如使用连接池等技术。
同时,Jython与Java的紧密结合也为开发者提供了更多的选择和便利。通过使用jythonc编译的类、嵌入PythonInterpreter或创建自定义标签库等方式,可以在JSP中间接使用Jython,从而充分利用Jython的功能和Java的生态系统。
总之,Jython为服务器端Web编程带来了新的思路和方法,相信在未来的Web开发中,Jython将发挥越来越重要的作用。
流程图
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B{选择开发方式}:::decision
B -->|Servlet| C(定义Servlet类):::process
B -->|JSP| D(选择集成方式):::process
C --> E(选择Servlet容器):::process
E --> F(安装和配置容器):::process
F --> G(编写和测试Servlet):::process
D --> H{使用方式}:::decision
H -->|jythonc编译类| I(创建Java兼容Jython类):::process
H -->|嵌入PythonInterpreter| J(导入并使用):::process
H -->|自定义标签库| K(创建标签库模块):::process
I --> L(编译并部署):::process
J --> M(编写JSP页面):::process
K --> N(编译和配置标签库):::process
L --> O(在JSP中使用):::process
M --> O
N --> O
G --> P([结束]):::startend
O --> P
这个流程图展示了使用Jython进行服务器端Web编程的主要流程。首先需要选择开发方式,是使用Servlet还是JSP。如果选择Servlet,需要定义Servlet类,选择合适的Servlet容器,进行安装和配置,最后编写和测试Servlet。如果选择JSP,需要进一步选择集成方式,包括使用jythonc编译的类、嵌入PythonInterpreter或创建自定义标签库。每种方式都有相应的步骤,最终都在JSP中使用,整个流程以结束标志结束。
Jython在服务器端Web编程的应用
超级会员免费看
49

被折叠的 条评论
为什么被折叠?



