Jython在服务器端Web编程中的应用
1. Jython在Web开发中的定位
Jython适用于Java适用的任何地方,尤其适用于那些需要更快开发速度和更高灵活性的Java应用场景。服务器端Java Web编程主要通过Servlet和Java Server Pages(JSP)实现,因此Jython的主要应用也是Servlet和JSP。不过,Java的企业级包(j2ee)在Web应用开发中也占据重要地位,Jython在这些技术中同样有效。
然而,Jython并非适用于所有场景。CPython是实现CGI脚本的流行语言,但Jython不适合。在CGI中,Web服务器接收请求,启动进程进行响应,响应完成后关闭子进程。虽然可以用Jython这样做,但由于JVM的启动时间,这不是一个好选择。而Servlet和JSP是持久的,它们在Web请求之间保持在内存中。
使用Jython进行Web编程有很多优点。高质量的Servlet容器很容易获得且广泛部署。Java Servlet应用可以受益于Jython的灵活性和高级语言特性,还能利用Java和Jython的库。一些常见的Servlet容器包括WebLogic、WebSphere、Tomcat、Jigsaw、Resin和Jetty。许多组织都部署了这些容器,这使得Jython在大多数情况下都能立即使用。
2. Jython Servlet容器
Jython可以与任何兼容的Java Servlet容器一起使用,有很多这样的容器可供选择。这里介绍几种常见的Servlet容器:
| 容器名称 | 描述 | 官网 |
| — | — | — |
| Jakarta’s Tomcat | 是Servlet和Java Server Pages规范的参考实现,稳定版本3.2.3支持2.2 Servlet和1.1 JSP规范,后续会有4.0版本支持2.3 Servlet和1.2 JSP规范。 | http://jakarta.apache.org/ |
| Apache JServ | 为Apache创建的Servlet(版本2.0)引擎,常用于与Jython一起使用Servlet。 | http://java.apache.org/ |
| Jigsaw | W3C的实验性Web服务器,实际上比“实验性”这个标签更成熟,是完全符合HTTP/1.1的Web服务器和缓存代理服务器,支持Servlet 2.2规范和Java Server Pages 1.1。 | http://www.w3.org/Jigsaw/ |
| Jetty | 紧凑高效的Java Web服务器,支持Servlet 2.2和JSP 1.1规范,支持HTTP 1.1,包括SSL支持,并且易于与EJB服务器集成。 | http://jetty.mortbay.com/ |
这些工具的文档很丰富,安装指南可以从各自的网站获取。
3. 定义简单的Servlet类
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 API中的GenericServlet类,由于
service
方法是抽象的,所以必须在子类中实现。输出通过
PrintWriter
对象发送给客户端。
3.2 简单的Jython 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与Java Servlet类似,继承自
GenericServlet
并实现了
service
方法。语法上有一些差异,如类定义后的括号、省略
throws
语句、显式的
self
参数等。
3.3 测试Java和Jython Servlet
测试这些Servlet需要安装Tomcat,Jython Servlet还需要将
jython.jar
文件添加到Tomcat Web应用中。具体步骤如下:
安装Tomcat
- 从http://jakarta.apache.org 下载Tomcat,建议下载jakarta - tomcat - 3.2.3版本,根据平台选择zip或tar.gz文件。
-
将下载的文件解压到有足够权限的目录,设置
TOMCAT_HOME环境变量指向该目录。例如,在Windows上:
set TOMCAT_HOME=c:\jakarta-tomcat-3.2.3
在*nix系统上:
export TOMCAT_HOME=/usr/local/jakarta-tomcat-3.2.3
-
设置
JAVA_HOME环境变量指向JDK安装的根目录,例如:
# on Windows
set JAVA_HOME=c:\jdk1.3.1
# bash (*nix) setup
export JAVA_HOME=/usr/java/jdk1.3.1
- 使用适合平台的启动脚本启动Tomcat:
# Windows
%TOMCAT_HOME%\bin\startup.bat
# bash (*unix)
$TOMCAT_HOME/bin/startup.sh
当看到
date time - PoolTcpConnector: Starting HttpConnectionHandler on 8080
时,Tomcat正在运行并准备在8080端口接受连接。
5. 停止Tomcat时,使用相应的关闭脚本:
# Windows
%TOMCAT_HOME%\bin\shutdown.bat
# bash (*nix)
$TOMCAT_HOME/bin/shutdown.sh
安装Java Servlet
-
将
JavaServlet.java文件放在%TOMCAT_HOME%\webapps\jython\WEB - INF\classes目录下。 -
在该目录下使用以下命令编译
JavaServlet.java文件:
javac -classpath %TOMCAT_HOME%\lib\servlet.jar JavaServlet.java
-
启动Tomcat服务器,在浏览器中访问
http://localhost:8080/jython/servlet/JavaServlet,应该看到消息This is a Java Servlet.
安装Jython Servlet
有两种使用Jython Servlet的方法,这里使用
jythonc
编译。步骤如下:
1.
使用
jythonc
编译Jython Servlet模块
:
- 确保
classpath
中包含
servlet.jar
文件,它位于Tomcat的
lib
目录下。
- 将
JythonServlet.py
文件放在
%TOMCAT_HOME%\jython\WEB - INF\classes
目录下。
- 在该目录下使用以下命令编译:
jythonc -w . JythonServlet.py
编译过程中要注意查看是否有
Creating .java files: JythonServlet module JythonServlet extends javax.servlet.GenericServlet
这样的行,如果没有,说明有问题,需要检查
CLASSPATH
和设置选项并重新编译。
2.
将
jython.jar
文件添加到Web应用中
:有三种方法:
-
首选方法
:将
jython.jar
文件添加到上下文的
lib
目录,如
%TOMCAT_HOME%\webapps\jython\WEB - INF\lib
,这样可以使Web应用自包含。
-
不推荐方法
:将
jython.jar
文件添加到Tomcat的
lib
目录,这样会使Web应用不再自包含。
-
合理方法
:将
jython.jar
文件留在Jython的安装目录,在启动Tomcat之前将其添加到
classpath
中,这样可以访问注册表文件和Jython的
lib
目录,但不如放在上下文的
lib
目录好。
3.
使Jython的
lib
目录对Servlet可用
:有三种方法:
-
设置
python.home
属性
:可以通过设置
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
- **显式添加模块目录到`sys.path`**:例如:
import sys
libpath = "c:/jakarta-tomcat_3.2.3/webapps/jython/WEB - INF/jylibs/Lib"
sys.path.append(libpath)
但这种方法通常需要显式的、依赖于机器的路径,限制了跨平台的可移植性。
-
冻结所需模块
:使用
jythonc --deep
选项编译所有所需模块,例如:
jythonc -w . --deep JythonServlet.py
这样可以使Servlet和模块安装在自包含的Web应用中,但要注意更新模块时可能会影响旧的Servlet。
测试Jython Servlet
编译好Jython Servlet,将
jython.jar
添加到
classpath
,并使Jython的模块可访问后,在浏览器中访问
http://localhost:8080/jython/servlet/jythonServlet
,应该看到消息
This is a Servlet of the Jython variety.
如果看不到,可能会出现以下错误:
- 如果编译时
Servlet.jar
不在
classpath
中,可能会看到
ClassCastException
。
- 如果文件名和类名不同(即使只是大小写不同),也会看到
ClassCastException
。
- 如果
jythonc
编译生成的类文件有一个不在上下文的
classes
目录中,会看到
AttributeError
。
- 如果Jython的模块不可用,会看到
ImportError
。
4. 关于GenericServlet
GenericServlet
是一个不特定于任何协议的Servlet类,
HttpServlet
更常用于Web编程,因为它特定于HTTP协议,但
GenericServlet
是
HttpServlet
的超类,了解它的方法很重要。以下是
GenericServlet
的一些方法及其在Jython子类中的使用示例:
| Java签名 | Jython子类中的用法 |
| — | — |
|
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"
|
|
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
|
4.1 计数器Servlet示例
以下是一个计数器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)
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:
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
目录下,使用
jythonc -w . --deep HitCounter.py
编译。在浏览器中访问
http://localhost:8080/jython/servlet/HitCounter
,第一次访问应该看到
Total Hits: 0
,每次后续访问计数会增加。关闭并重启Tomcat后,计数应该继续。
4.2 方法说明
-
init(ServletConfig)方法 :在Servlet启动时调用,仅调用一次,适用于耗时的任务,如设置数据库连接、编译正则表达式或加载辅助文件。在上述示例中,该方法用于建立存储点击信息的文件,并将计数器设置为该文件中最后存储的整数。 -
service(ServletRequest, ServletResponse)方法 :每次客户端请求时调用,Servlet必须定义此方法,因为它在GenericServlet中是抽象的。参数ServletRequest包含客户端发送的请求信息,ServletResponse用于将响应流以适当的mime编码格式发送回客户端。 -
destroy()方法 :在关闭或卸载Servlet时调用,这里用于将点击计数器的值写入文件,以防止信息丢失,但这不是最好的持久化方法,因为如果服务器不正常关闭,该方法可能不会被调用。
5. HttpServlet
HttpServlet
是
GenericServlet
的子类,更适合用于HTTP协议的Web开发。它为每个HTTP方法定义了一个方法,如
doGet
、
doPost
、
doPut
等。当Tomcat接收到GET类型的客户端请求时,会调用请求的Servlet的
doGet()
方法进行响应。
HttpServlet
还有一个特定于HTTP的
service
方法,与
GenericServlet
的
service
方法不同之处在于参数是
HttpServletRequest
和
HttpServletResponse
对象。
5.1 HttpServlet方法
以下是
HttpServlet
的一些方法及其在Jython子类中的使用示例:
| Java签名 | Jython子类中的用法 |
| — | — |
|
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):
|
5.2 HttpServlet示例
以下是一个使用
HttpServlet
的示例:
#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
,可以看到一个页面,点击提交按钮会执行
doPost
方法。
5.3 HttpServletRequest和HttpServletResponse
与客户端连接的通信通过
HttpServletRequest
和
HttpServletResponse
对象进行,它们是对从客户端接收和发送到客户端的请求流的抽象,每个对象都添加了更高级的、特定于HTTP的方法,方便处理请求和响应。以下是
HttpServletRequest
对象的一些方法和属性:
| 方法和属性 | 描述 |
| — | — |
|
getAuthType()
/
AuthType
| 返回描述认证类型名称的字符串,如果用户未认证则为
None
。 |
|
getContextPath()
/
contextPath
| 返回描述请求上下文的路径信息的字符串。 |
|
getCookies()
/
cookies()
| 返回客户端请求发送的所有cookie,作为
javax.servlet.http.Cookie
对象的数组。 |
|
getDateHeader(name)
| 以长类型检索指定头的值。 |
|
getHeader(name)
| 以字符串形式返回指定头的值。 |
|
getHeaderNames()
/
headerNames
| 返回请求中包含的所有头名称的枚举。 |
|
getHeaders(name)
| 返回指定头名称的所有值的枚举。 |
|
getIntHeader(name)
| 以Java整数形式检索指定头的值,Jython将其转换为
PyInteger
。 |
|
getMethod()
/
method
| 返回请求类型的字符串。 |
|
getPathInfo()
/
pathInfo
| 客户端发送的所有额外路径信息。 |
|
getPathTranslated()
/
pathTranslated
| 返回从客户端请求的额外路径信息派生的真实路径。 |
|
getQueryString()
/
queryString
| 返回客户端请求的查询字符串(路径后面的字符串)。 |
|
getRemoteUser()
/
remoteUser
| 返回客户端的登录名,如果客户端未认证则为
None
。 |
|
getRequestedSessionId()
/
requestedSessionId
| 返回客户端的会话ID。 |
|
getRequestURI()
/
requestURI
| 返回协议名称和查询字符串之间的部分。 |
|
getServletPath()
/
servletPath
| 返回指定当前Servlet的URL部分。 |
|
getSession()
/
session
| 返回当前会话,如果需要则创建一个新会话。 |
|
getSession(create)
| 如果存在则返回当前会话,如果
create
值为
true
,则创建一个新会话。 |
|
getUserPrincipal()
/
userPrincipal
| 返回包含当前认证信息的
java.security.Principal
对象。 |
|
isRequestedSessionIdFromCookie()
| 根据当前会话ID是否来自cookie返回1或0。 |
|
isRequestedSessionIdFromURL()
| 根据当前会话ID是否来自请求的URL字符串返回1或0。 |
|
isRequestedSessionIdValid()
| 根据请求的会话ID是否仍然有效返回1或0。 |
|
isUserInRole(role)
| 指示用户是否在指定角色中返回1或0。 |
HttpServletResponse
对象用于将mime编码的流发送回客户端,它定义了一些在通用
ServletResponse
对象中不存在的特定于HTTP的方法,以下是一些方法和属性:
| 方法和属性 | 描述 |
| — | — |
|
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)
| 通过包含会话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)
一起使用,以下是一些状态码:
| 错误代码 | 状态 |
| — | — |
| 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
|
6. 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
文件中的方法进行响应。
6.1 限制条件
PyServlet
的设计对它所服务的Jython文件有一些限制:
- Jython文件必须包含一个继承自
javax.servlet.http.HttpServlet
的类。
- 该类的名称必须与文件名(去掉
.py
扩展名)匹配。
- 除了继承自
HttpServlet
的那个类之外,使用模块全局标识符是不安全的。
6.2 安装PyServlet
安装
PyServlet
只需要定义一个Servlet映射,并确保
jython.jar
文件在上下文的
lib
目录中。Servlet映射在上下文的部署描述符文件
web.xml
中定义,该文件位于
$TOMCAT_HOME/webapps/jython/WEB - INF/web.xml
。以下是一个示例
web.xml
文件:
<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>
PyServlet
的Servlet定义可以可选地包含初始化参数(
init-params
),用于设置Jython的属性,如
python.home
、
python.path
等。以下是一个包含初始化参数的
web.xml
示例:
<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>
PyServlet
的默认
python.home
值是上下文的
lib
目录,这使得上下文既自包含又具有平台无关性。
6.3 测试PyServlet
可以通过启动Tomcat并在浏览器中查看一个简单的Jython Servlet(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
,并将Jython模块放在该目录中。
7. Cookies
Cookies允许在客户端机器上存储信息,这些信息会包含在后续的请求中。要在Jython中创建和操作cookies,可以使用
javax.Servlet.http.Cookie
类。以下是一个创建和发送cookie的示例:
from javax.servlet import http
name = "book1"
value = "Lewis Carroll, Alice In Wonderland"
MyNewCookie = http.Cookie(name, value)
res.addCookie(MyNewCookie)
添加cookie应该在通过响应流发送其他内容之前进行。每个cookie实例都有一些方法或Jython的自动bean属性,可以设置以下属性:
-
comment
-
domain
-
maxAge
-
name
-
path
-
secure
-
value
-
version
以下是一个使用cookie对象的自动bean属性创建和读取cookie的示例:
# 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>"""
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">
\n</body>
\n</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>
\n</body>
\n</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>
\n</body>
/n</html>"""
要测试这个jylet,首先确保浏览器允许使用cookies,然后将
cookies.py
文件放在
%TOMCAT_HOME%\webapps\jython
目录中,并在浏览器中访问
http://localhost:8080/jython/cookies.py
。第一次访问时,应该只看到一个标题和一个表单。填写表单并点击提交按钮,应该会看到添加cookie成功的确认信息。返回
doGet
方法,应该可以在cookie列表中看到新添加的cookie。
8. Sessions
Cookies是与客户端创建会话的最常见方式,会话是通过一系列请求跟踪客户端信息的一种手段。虽然前面的cookie示例可以用于存储会话ID,但Java的
HttpSession
类使会话跟踪更加容易。
8.1 创建会话
可以使用
HttpRequest
的
getSession()
方法创建一个会话,该方法返回一个
HttpSession
实例。在Jython中使用
HttpSession
对象与Java的区别仅在于语法和对象的
get*
方法的自动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>
"""
8.2 注意事项
由于cookie可能存储会话值,因此应该在向输出流发送其他数据之前使用
req.session
属性或
req.getSession()
方法。如果客户端禁用了cookie,会话对象仍然可以正常工作,因为所有URL都会通过
res.encodeUrl()
方法重写,以支持无cookie会话。
将
session.py
文件保存到
%TOMCAT_HOME%\webapps\jython
目录中,然后在浏览器中访问
http://localhost:8080/jython/session.py
。可以使用网页表单向会话中添加一些变量来确认会话是否正常工作,甚至可以尝试禁用cookie,看看URL重写是如何工作的。
9. Databases and Servlets
在Jython Servlet中连接数据库、执行语句和遍历结果集与其他Jython数据库应用程序没有区别,但管理连接和其他数据库资源是一个主要问题,因为许多Web应用程序包含大量使用数据库内容的Jython Servlet。
9.1 资源管理方法
有两种主要的数据库资源管理方法:
-
每个Servlet一个连接
:在Servlet的
init
方法中建立数据库连接,只有在Servlet卸载时才关闭该连接。这种方法虽然流行,但资源消耗较大,只在所需连接数量在资源限制范围内时才合理。
-
连接池
:维护一定数量的活动数据库连接,Jython Servlet在响应客户端请求时从连接池中借用连接,完成后归还。这种方法可以实现更合理的资源管理,消除连接开销。
9.2 示例代码
以下是一个在
init
方法中获取数据库连接和游标对象,并在
destroy
方法中关闭它们的Jython Servlet示例:
# 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()
这个示例使用了
zxJDBC
包和MySQL数据库,需要将MySQL和
zxJDBC
所需的类文件放在上下文的
lib
目录中,如
mm_mysql-2_0_4-bin.jar
和
zxJDBC.jar
。创建数据库和表的SQL语句如下:
create database products;
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应用程序开始使用过多的连接,可以考虑使用连接池。一个流行、免费、经过测试且文档完善的连接池工具是
PoolMan
,其他Java连接池包也应该可以与Jython无缝配合使用。
10. JSP
Java Server Pages(JSP)是Sun公司倡导的一种与Servlet互补的模板系统。JSP文件包含网页的标记代码和文本,同时还包含指定动态内容的特殊标签。目前,Tomcat只支持在JSP中使用Java语言,那么如何在JSP中使用Jython呢?目前有以下几种方法:
- 使用
jythonc
编译的类。
- 使用嵌入式Python解释器。
- 创建特定于Jython的自定义标签库。
10.1 jythonc - Compiled Classes and JSP
使用
jythonc
编译的类与JSP配合使用,需要创建一个与Java兼容的Jython类。这个类要满足以下条件:
- 类名与模块名相同。
- 继承自Java类。
- 为每个非从超类派生的方法包含
@sig
字符串。
将
jythonc
生成的类文件放在上下文的
classes
目录中,JSP页面就可以像使用任何原生Java类一样使用这些类。当然,这也需要将
jython.jar
文件放在上下文的
lib
目录中。
JSP文件可以通过两种方式使用
jythonc
编译的类:
-
作为脚本片段
:可以使用任何类。通过
<%@ page import="fully.qualified.path.to.class" %>
导入非bean类。
-
作为bean
:Jython类必须符合bean约定,为每个读写属性包含
getProperty
和
setProperty
方法。使用
<jsp:useBean>
标签加载bean。
以下是一个简单的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
编译。以下是一个使用这个Jython bean的JSP页面示例:
<!--file: name.jsp -->
<%@ page contentType="text/html" %>
<jsp:useBean name="beanName"
class="NameHandler"
scope="session"/>
<html>
<head>
<title>hello</title>
</head>
<body bgcolor="white">
Hello, my name is
<jsp:getProperty name="beanName" property="username"/>
<br>
No, wait...
<jsp:setProperty name="beanName" property="username" value="Robert"/>
, It's really <%= beanName.getUsername() %>.
</body>
</html>
将
name.jsp
文件放在上下文的根目录中,然后在浏览器中访问
http://localhost:8080/jython/name.jsp
,应该可以看到默认名称
Fred
和修改后的名称
Robert
。
10.2 Embedding a PythonInterpreter in JSP
如果想在JSP脚本片段中使用Jython代码,可以通过嵌入式
PythonInterpreter
间接实现。以下是一个简单的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
文件放在上下文的根目录中,然后在浏览器中访问
http://localhost:8080/jython/interp.jsp
,应该可以看到Jython解释器输出的简单消息。不过,很多人认为脚本片段不是好的做法,在脚本片段中从Java使用Jython会增加复杂度,有更好的方法来创建动态内容,如bean类和标签库。
10.3 A Jython Taglib
可以使用
jythonc
将Jython标签库模块编译成Java类,从而在JSP页面中创建自定义标签库。Jython标签库模块必须满足以下限制才能像Java类一样透明地工作:
- 模块必须包含一个与模块名(去掉
.py
扩展名)相同的类。
- 该类必须继承自
javax.servlet.jsp.tagext.Tag
接口或实现该接口的Java类。
- 编译后的类文件必须能够访问
org.python.*
包和类以及Jython库模块(如果标签库导入了这些模块)。
为了使标签库能够访问所有必需的类和库,可以使用
jythonc
的
--all
选项进行编译,就像之前编译Servlet一样。但更好的方法是将
jython.jar
文件放在上下文的
lib
目录中,在上下文中建立一个Jython的
lib
目录(推荐
{context}\WEB - INF\lib\Lib
),然后编译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
)可能会导致循环引用和
StackOverflowExceptions
,因为
Tag
接口要求实现
setPageContext()
、
setParent()
和
getParent()
方法,Jython会为这些方法的相关属性名创建自动bean属性。
要安装这个标签库所需的类,首先确保
jython.jar
文件在上下文的
lib
目录中,然后使用以下命令编译
JythonTag.py
文件:
jythonc -w %TOMCAT_HOME%\webapps\jython\WEB - INF\classes JythonTag.py
这将在上下文的
classes
目录中创建
JythonTag.class
和
JythonTag$_PyInner.class
文件。但仅这些类还不足以使用标签库,还需要创建一个标签库描述文件。以下是一个标签库描述文件示例:
<?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 - XML\jython-taglibs.tld
,JSP页面将使用这个文件来识别页面中使用的标签。
在JSP页面中使用标签库,需要包含一个指令来指定标签库描述文件的路径,并为该标签库指定一个简单的名称。以下是一个使用上述标签库的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的主目录和
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模块。
10.4 BSF
IBM的Bean Scripting Framework(BSF)是一个实现各种脚本语言的Java工具,Jython是目前BSF支持的语言之一。Apache Jakarta项目包含一个名为
taglibs
的子项目,这是一个广泛的Java标签库集合,其中有趣的是BSF标签库。BSF标签库与BSF的jar文件结合使用,可以让你直接在JSP页面中插入Jython脚本片段和表达式。
10.4.1 安装步骤
-
从相关网站下载正确的
bsf.jar文件,并将其放在上下文的lib目录中(%TOMCAT_HOME%\webapps\jython\WEB - INF\lib)。 -
下载BSF标签库的jar文件和
bsf.tld文件,将标签库的jar文件放在上下文的lib目录中,将bsf.tld文件放在上下文的WEB - INF目录中(%TOMCAT_HOME%\webapps\jython\WEB - INF\bsf.tld)。
10.4.2 使用方法
在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类名 |
| — | — |
|
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
|
以下是一个使用
bsf:scriptlet
标签在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和JSP结合使用,为Web开发带来更高的灵活性和开发效率。无论是处理Cookies、管理会话、连接数据库,还是在JSP中使用Jython代码,都有相应的方法和技巧。在实际开发中,可以根据具体需求选择合适的方法来实现功能。
Jython在服务器端Web编程的应用
超级会员免费看
5434

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



