Jython在服务器端Web编程中的应用
1. Jython在Web开发中的定位
Jython适用于Java适用的任何地方,尤其在需要快速开发和更高灵活性的Java应用场景中表现出色。服务器端Java Web编程主要通过Servlet和Java Server Pages(JSP)实现,因此Jython的主要应用也是Servlet和JSP。不过,Java的企业级包(j2ee)在Web应用开发中也很重要,EJB、JNDI、JDBC等是大多数Java Web应用不可或缺的部分,Jython在这些技术中同样有效。但需要注意的是,Jython并不适合所有场景,例如CPython常用于实现CGI脚本,但Jython并不适合,因为CGI中Web服务器处理请求的方式会使JVM的启动时间成为劣势,而Servlet和JSP在请求之间可以保持在内存中。
使用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规范的参考实现,本章使用的服务器。 | http://jakarta.apache.org/ | 3.2.3版本支持2.2 Servlet和1.1 JSP规范;4.0版本实现2.3 Servlet和1.2 JSP规范 |
| Apache JServ | 为Apache创建的Servlet(版本2.0)引擎,常用部署方式。 | http://java.apache.org/ | 2.0 Servlet版本,Java Server Pages需要外部模块 |
| Jigsaw | W3C的实验性Web服务器,实际上比“实验性”更成熟,是完全符合HTTP/1.1的Web服务器和缓存代理服务器。 | http://www.w3.org/Jigsaw/ | 支持Servlet 2.2规范和Java Server Pages 1.1 |
| Jetty | 紧凑高效的Java Web服务器,支持Servlet 2.2和JSP 1.1规范,支持HTTP 1.1,包含SSL支持,可轻松与EJB服务器集成。 | http://jetty.mortbay.com/ | Servlet 2.2和JSP 1.1规范 |
这些工具的文档丰富,安装指南可从各自的网站获取。
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
方法在GenericServlet中是抽象的,所以必须在子类中实现。
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在功能上类似,但语法不同,如类定义后的括号指定超类、省略
throws
语句、
service
方法中显式的
self
参数等。
3.3 测试Java和Jython Servlet
3.3.1 安装Tomcat
安装Tomcat的步骤如下:
1. 从http://jakarta.apache.org 下载适合平台的zip或tar.gz文件,建议下载jakarta - tomcat - 3.2.3版本。
2. 将存档文件解压到有足够权限的目录,设置
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
这一行,它表示将使用的端口,默认是8080。
5. 停止Tomcat时,使用相应的关闭脚本:
# Windows
%TOMCAT_HOME%\bin\shutdown.bat
# bash (*nix)
$TOMCAT_HOME/bin/shutdown.sh
3.3.2 安装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。
3.3.3 安装Jython Servlet
使用Jython Servlet有两种方式,这里使用
jythonc
编译。安装步骤如下:
1.
编译Jython Servlet模块
:将
JythonServlet.py
文件放在
%TOMCAT_HOME%\jython\WEB - INF\classes
目录下,确保
CLASSPATH
包含
Servlet.jar
,然后使用以下命令编译:
jythonc -w . JythonServlet.py
编译时要注意查看是否有
Creating .java files: JythonServlet module JythonServlet extends javax.servlet.GenericServlet
这些行,若没有则说明有问题,需检查
CLASSPATH
和设置选项并重新编译。
2.
添加jython.jar文件到类路径
:有三种方式添加
jython.jar
文件到Tomcat的类路径:
-
推荐方式
:将
jython.jar
添加到上下文的
lib
目录,如
%TOMCAT_HOME%\webapps\jython\WEB - INF\lib
,这样可以使Web应用自包含。
-
不推荐方式
:将
jython.jar
添加到Tomcat的
lib
目录,虽然可以减少重复文件,但Web应用不再自包含。
-
合理方式
:将
jython.jar
留在Jython的安装目录,在运行Tomcat前将其添加到类路径,这种方式可以访问注册表文件和Jython的
lib
目录,但自包含性不如第一种方式。
3.
使Jython的lib目录对Servlet可用
:有三种方法:
-
设置python.home属性
:可以通过设置
TOMCAT_OPTS
环境变量来实现。例如,创建
%TOMCAT_HOME%\webapps\jython\WEB - INF\jylib
和
%TOMCAT_HOME%\webapps\jython\WEB - INF\jylib\Lib
目录,将所需模块放在其中,然后设置:
# Windows
set TOMCAT_OPTS=-Dpython.home=%TOMCAT_HOME%\webapps\jython\WEB-INF\jylib
# bash (*nix)
export TOMCAT_OPTS=-Dpython.home=$TOMCAT_HOME/webapps/jython/WEB-INF/jylib
- **冻结所需模块**:使用`jythonc --deep`选项编译,例如:
jythonc -w . --deep JythonServlet.py
这种方式可以使Servlet和模块安装在自包含的Web应用中,但更新模块时要小心。
-
显式添加模块位置到sys.path
:在Servlet顶部添加如下代码:
import sys
libpath = "c:/jakarta-tomcat_3.2.3/webapps/jython/WEB-INF/jylibs/Lib"
sys.path.append(libpath)
但这种方式可能需要显式的、依赖机器的路径,限制了跨平台的可移植性。
测试Jython Servlet时,在浏览器中访问
http://localhost:8080/jython/servlet/jythonServlet
,应看到消息
This is a Servlet of the Jython variety
。若未看到该消息,可能出现以下错误:
- 编译时
Servlet.jar
不在类路径中,会出现
ClassCastException
。
- 文件名和类名不同(即使只是大小写不同),也会出现
ClassCastException
。
- 编译生成的类文件不在上下文的
classes
目录中,会出现
AttributeError
。
- Jython的模块不可用,会出现
ImportError
。
4. 关于GenericServlet
javax.Servlet.GenericServlet
是一个不特定于任何协议的Servlet类,虽然在Web编程中
HttpServlet
更常用,但
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对象,分别包含客户端请求信息和用于发送响应流。 - destroy()方法 :在关闭或卸载Servlet时调用,用于清理代码,如关闭数据库连接、刷新和关闭文件等。但上述示例中存储点击计数器值的方式不是最佳的持久化方法,因为只有在服务器正常关闭时才会调用该方法。
5. HttpServlet
javax.Servlet.http.HttpServlet
是
GenericServlet
的子类,更适合用于HTTP协议的Web开发。
HttpServlet
为每个HTTP方法定义了方法,如
doGet
、
doPost
、
doPut
、
doOptions
、
doDelete
和
doTrace
。当Tomcat接收到客户端的GET请求时,会调用请求的Servlet的
doGet()
方法进行响应。此外,
HttpServlet
还有一个HTTP特定版本的
service
方法,其参数是
javax.servlet.http.HttpServletRequest
和
javax.servlet.http.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特定方法。
5.3.1 HttpServletRequest方法和属性
| 方法和属性 | 描述 |
|---|---|
getAuthType()
/
AuthType
|
返回描述认证类型的字符串(
PyString
),如果用户未认证则为
None
。
|
getContextPath()
/
contextPath
|
返回描述请求上下文路径信息的字符串(
PyString
)。
|
getCookies()
/
cookies()
|
返回客户端请求中发送的所有
Cookie
对象数组。
|
getDateHeader(name)
| 以长类型检索指定头的值。 |
getHeader(name)
|
以字符串(
PyString
)返回指定头的值。
|
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
|
返回当前会话,需要时创建一个会话,会话是
javax.servlet.http.HttpSession
的实例。
|
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,表示用户是否在指定角色中。 |
5.3.2 HttpServletResponse方法和属性
| 方法和属性 | 描述 |
|---|---|
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)
方法。以下是一些常见的状态码:
| 错误代码 | 状态 |
| — | — |
| 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发行版包含
org.python.util.PyServlet
,它可以加载、执行和缓存Jython文件,无需中间编译步骤。通过Servlet映射,
PyServlet
可以将特定的URL模式(如
*.py
)与自身关联。
6.1 安装PyServlet
安装
PyServlet
只需定义一个Servlet映射并确保
jython.jar
文件在上下文的
lib
目录中。Servlet映射在上下文的部署描述符文件
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-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.2 测试PyServlet
以下是一个简单的测试
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
映射正确。完成安装后,创建
%TOMCAT_HOME%\webapps\jython\WEB - INF\lib\Lib
目录,用于放置Jython模块。
7. Cookies
Cookies
允许在客户端机器上存储信息,以便在后续请求中包含这些信息。在Jython中,可以使用
javax.Servlet.http.Cookie
类创建和操作
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
实例可以使用
get
和
set
方法或Jython的自动Bean属性来设置以下属性:
-
comment
-
domain
-
maxAge
-
name
-
path
-
secure
-
value
-
version
以下是一个使用
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
,按照提示操作即可。
8. Sessions
Cookies
是创建与客户端会话的最常见方式,会话用于通过一系列请求跟踪客户端信息。虽然
Cookies
可以用于存储会话ID,但
Java HttpSession
类使会话跟踪更加容易。
以下是一个会话管理的示例:
# 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>
"""
将
session.py
文件保存到
%TOMCAT_HOME%\webapps\jython
目录下,在浏览器中访问
http://localhost:8080/jython/session.py
,使用表单添加变量到会话中,还可以尝试禁用
Cookies
,查看URL重写是否正常工作。
9. 数据库和Servlet
在
jylet
中连接数据库、执行语句和遍历结果集与其他Jython数据库应用程序类似,但管理连接和其他数据库资源是一个主要问题,因为许多Web应用包含多个使用数据库内容的
jylet
。在每个
jylet
中创建数据库连接会迅速消耗数据库资源,而为每个请求创建和关闭数据库连接的开销又太大。
管理数据库资源有多种选择,主要有两种方法:
-
每个
jylet
一个连接
:在
jylet
的
init
方法中建立数据库连接,仅在
jylet
卸载时关闭连接。这种方法可以消除响应客户端请求时的连接开销,但只有在所需连接数量在资源限制范围内时才合理。
-
连接池
:维护一定数量的活动数据库连接,
jylet
在响应客户端请求时借用连接,完成后返回连接池。这种方法可以实现合理的资源管理并消除连接开销。
以下是一个使用数据库连接的
jylet
示例:
# file: DBDisplay.py
from javax.servlet import http
from com.ziclix.python.sql import zxJDBC
class DBDisplay(http.HttpServlet):
def init(self, cnfg):
url = "jdbc:mysql://192.168.1.77/products"
usr = "productsUser"
passwd = "secret"
driver = "org.gjt.mm.mysql.Driver"
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数据库,需要将
mm_mysql - 2_0_4 - bin.jar
文件和
zxJDBC.jar
文件放在
%TOMCAT_HOME%\webapps\jython\WEB - INF\lib
目录下,并重启Tomcat。创建数据库和表的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等工具。
10. JSP
Java Server Pages(JSP)是Sun倡导的与Servlet互补的模板系统。目前,Tomcat仅在JSP中实现Java语言,使用Jython与JSP可以通过以下几种方式:
10.1 jythonc编译的类和JSP
使用
jythonc
编译的类与JSP配合使用,需要创建一个与Java兼容的Jython类,该类具有与模块相同的名称,继承自Java类,并为每个非从超类派生的方法包含
@sig
字符串。将
jythonc
生成的类文件放在上下文的
classes
目录中,JSP页面就可以像使用任何本地Java类一样使用这些类,同时需要将
jython.jar
文件放在上下文的
lib
目录中。
JSP文件可以通过两种方式使用
jythonc
编译的类:
-
作为脚本片段
:可以使用任何类,通过
<%@ page import="fully.qualified.path.to.class" %>
导入类,然后在脚本标签(
<% %>
)或表达式标签(
<%= %>
)中使用。
-
作为Bean
:Jython类必须符合Bean约定,为每个读写属性包含
getProperty
和
setProperty
方法。使用
<jsp:useBean name="beanName" class="fully.qualified path.to.class" scope="scope(page or session)">
加载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
以下是一个使用该Bean的JSP页面示例:
<!--file: name.jsp -->
<%@ page contentType="text/html" %>
<jsp:useBean id="name" class="NameHandler" scope="session"/>
<html>
<head>
<title>hello</title>
</head>
<body bgcolor="white">
Hello, my name is
<jsp:getProperty name="name" property="username"/>
<br>
No, wait...
<jsp:setProperty name="name" property="username" value="Robert"/>
, It's really <%= name.getUsername() %>.
</body>
</html>
将
name.jsp
文件放在上下文的根目录下,在浏览器中访问
http://localhost:8080/jython/name.jsp
,可以看到默认名称
Fred
和修改后的名称
Robert
。
10.2 嵌入PythonInterpreter在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
目录中,在浏览器中访问
http://localhost:8080/jython/interp.jsp
,可以看到Jython解释器的消息。但需要注意,许多人认为脚本片段不是好的实践,使用Jython在Java脚本片段中会增加复杂度,有更好的方式创建动态内容,如Bean类和标签库。
10.3 Jython标签库
可以使用
jythonc
将Jython标签库模块编译成Java类,创建Jython标签库。Jython标签库模块必须满足以下条件:
- 包含一个与模块名称相同(不带
.py
扩展名)的类。
- 该类必须继承
javax.servlet.jsp.tagext.Tag
接口或实现该接口的Java类。
- 编译后的类文件必须能够访问
org.python.*
包和类以及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
将
jython.jar
文件放在上下文的
lib
目录中,使用以下命令编译
JythonTag.py
文件:
jythonc -w %TOMCAT_HOME%\webapps\jython\WEB-INF\classes JythonTag.py
编译后,还需要创建一个标签库描述文件,以下是一个示例:
<?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页面中使用标签库,需要添加以下指令:
<%@ taglib uri="/WEB-INF/jython-taglibs.tld" prefix="jython" %>
然后可以使用标签:
<jython:message/>
以下是一个使用该标签的JSP文件示例:
<%@ taglib uri="/WEB-INF/jython-taglibs.tld" prefix="jython" %>
<html>
<body>
<jython:message />
</body>
</html>
将该文件保存为
%TOMCAT_HOME%\webapps\jython\test.jsp
,在浏览器中访问
http://localhost:8080/jython/test.jsp
,可以看到自定义标签的消息。
10.4 BSF
IBM的Bean Scripting Framework(BSF)是一个实现各种脚本语言的Java工具,Jython是其支持的语言之一。Apache Jakarta项目的
taglibs
子项目包含一个BSF标签库,结合BSF的jar文件,可以在JSP页面中快速插入Jython脚本片段和表达式。
下载并将
bsf.jar
文件、包含BSF标签库的jar文件和
bsf.tld
文件分别放在上下文的
lib
目录和
WEB - INF
目录中。在JSP文件中使用Jython脚本片段,首先需要包含以下指令:
<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %>
然后可以使用
bsf.scriptlet
标签,例如:
<bsf.scriptlet language="jython">
import random
print >> out, random.randint(1, 100)
</bsf.scriptlet>
脚本片段中有一些JSP对象会自动设置在解释器中,包括
request
、
response
、
pageContext
等。以下是一个使用
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编程中的应用
11. 总结与建议
11.1 技术选择总结
在服务器端Web编程中使用Jython,有多种技术可供选择,每种技术都有其适用场景:
-
Servlet类选择
:
-
GenericServlet
:适用于不特定于任何协议的场景,是
HttpServlet
的超类,了解其方法有助于理解Servlet的基本生命周期和操作。
-
HttpServlet
:专门用于HTTP协议的Web开发,为每个HTTP方法定义了相应的处理方法,如
doGet
、
doPost
等,更适合处理Web请求。
-
数据库资源管理
:
-
每个
jylet
一个连接
:适用于所需连接数量在资源限制范围内的情况,可以消除响应客户端请求时的连接开销。
-
连接池
:当Web应用需要管理大量数据库连接时,连接池是更好的选择,可以实现合理的资源管理并消除连接开销。
-
JSP与Jython结合方式
:
-
jythonc编译的类
:适合创建与Java兼容的Jython类,可在JSP中作为脚本片段或Bean使用。
-
嵌入PythonInterpreter
:可间接在JSP脚本片段中使用Jython代码,但由于脚本片段不是好的实践,使用时需谨慎。
-
Jython标签库
:通过编译Jython标签库模块成Java类,可在JSP中创建自定义标签,增加代码的复用性和可维护性。
-
BSF
:结合IBM的Bean Scripting Framework和BSF标签库,可在JSP页面中快速插入Jython脚本片段和表达式。
11.2 部署建议
-
自包含性
:尽量使Web应用自包含,将所需的库文件(如
jython.jar)放在上下文的lib目录中,避免依赖外部资源,方便应用的部署和迁移。 - 资源管理 :合理管理数据库连接和其他资源,根据应用的实际需求选择合适的资源管理方式,避免资源浪费和性能问题。
-
代码规范
:编写Jython代码时,遵循良好的代码规范,如为每个非从超类派生的方法包含
@sig字符串,确保代码的可读性和可维护性。
12. 流程图示例
12.1 安装Tomcat流程图
graph LR
A[下载Tomcat] --> B[解压文件]
B --> C[设置TOMCAT_HOME环境变量]
C --> D[设置JAVA_HOME环境变量]
D --> E[启动Tomcat]
E --> F{是否看到启动信息}
F -- 是 --> G[Tomcat启动成功]
F -- 否 --> H[检查环境变量和配置]
H --> E
12.2 处理客户端请求流程图
graph LR
A[客户端发送请求] --> B[Tomcat接收请求]
B --> C{请求类型}
C -- GET --> D[调用doGet方法]
C -- POST --> E[调用doPost方法]
D --> F[处理请求并生成响应]
E --> F
F --> G[Tomcat发送响应给客户端]
13. 常见问题及解决方法
13.1 编译问题
-
ClassCastException
:
-
原因
:编译Jython Servlet时,
Servlet.jar文件不在类路径中;或者文件名和类名不同(即使只是大小写不同)。 -
解决方法
:确保
CLASSPATH包含Servlet.jar文件;检查文件名和类名是否一致。
-
原因
:编译Jython Servlet时,
-
AttributeError
:
-
原因
:编译生成的类文件不在上下文的
classes目录中。 -
解决方法
:将编译生成的类文件正确放置在
%TOMCAT_HOME%\webapps\jython\WEB - INF\classes目录下。
-
原因
:编译生成的类文件不在上下文的
-
ImportError
:
- 原因 :Jython的模块不可用。
-
解决方法
:通过设置
python.home属性、冻结所需模块或显式添加模块位置到sys.path等方法,确保Jython的模块可用。
13.2 运行问题
-
Cookie问题
:
-
原因
:浏览器不允许使用
Cookies;或者Cookie的设置和获取代码存在问题。 -
解决方法
:确保浏览器允许使用
Cookies;检查Cookie的创建、设置和获取代码是否正确。
-
原因
:浏览器不允许使用
-
Session问题
:
-
原因
:客户端禁用了
Cookies,导致会话跟踪失败。 -
解决方法
:使用
res.encodeUrl()方法对URL进行重写,支持无Cookie会话。
-
原因
:客户端禁用了
13.3 数据库问题
-
连接失败
:
- 原因 :数据库连接信息(如URL、用户名、密码、驱动程序)不正确;或者数据库服务未启动。
- 解决方法 :检查数据库连接信息是否正确;确保数据库服务正常运行。
-
资源泄漏
:
-
原因
:在
Servlet的destroy方法中未正确关闭数据库连接和游标对象。 -
解决方法
:在
destroy方法中添加关闭数据库连接和游标对象的代码,如self.c.close()和self.db.close()。
-
原因
:在
14. 未来发展趋势
随着Web技术的不断发展,Jython在服务器端Web编程中的应用也可能会有新的发展趋势:
-
更广泛的应用场景
:随着Jython与更多Java框架和工具的集成,它可能会在更多的Web应用场景中得到应用,如微服务架构、大数据处理等。
-
性能优化
:未来可能会有更多的性能优化技术和工具出现,进一步提高Jython在服务器端的运行效率。
-
社区支持增强
:随着Jython社区的不断发展壮大,会有更多的开发者参与到Jython的开发和维护中,提供更多的文档、示例和工具,促进Jython在服务器端Web编程中的应用。
15. 学习资源推荐
15.1 官方文档
- Jython官方文档 :提供了Jython的详细介绍、安装指南、语法说明和示例代码等,是学习Jython的重要资源。
- Tomcat官方文档 :包含了Tomcat的安装、配置和使用说明,对于理解和使用Tomcat服务器非常有帮助。
15.2 在线教程和博客
- 开源中国 :有许多关于Jython和Java Web开发的技术文章和教程,涵盖了各种应用场景和技术问题的解决方案。
- 优快云 :提供了丰富的技术博客和论坛,开发者可以在这里分享和交流Jython在服务器端Web编程中的经验和心得。
15.3 书籍
- 《Python与Java集成开发实战》:详细介绍了Python和Java的集成开发技术,包括Jython的使用和应用,对于学习Jython在服务器端Web编程中的应用有很大的帮助。
16. 实战项目示例
16.1 项目需求
开发一个简单的Web应用,实现用户登录、商品展示和购物车功能,使用Jython和JSP技术。
16.2 项目架构
- 前端 :使用HTML、CSS和JavaScript实现页面的布局和交互效果。
- 后端 :使用Jython Servlet处理用户请求,使用JSP页面生成动态内容,使用MySQL数据库存储用户信息和商品信息。
16.3 代码实现
16.3.1 用户登录Servlet
# file: LoginServlet.py
from javax.servlet import http
import com.ziclix.python.sql.zxJDBC as zxJDBC
class LoginServlet(http.HttpServlet):
def doPost(self, req, res):
username = req.getParameter("username")
password = req.getParameter("password")
# 连接数据库
url = "jdbc:mysql://localhost:3306/mydb"
usr = "root"
passwd = "password"
driver = "org.gjt.mm.mysql.Driver"
db = zxJDBC.connect(url, usr, passwd, driver)
c = db.cursor()
# 查询用户信息
c.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))
result = c.fetchone()
if result:
# 登录成功,创建会话
sess = req.getSession(True)
sess.setAttribute("username", username)
res.sendRedirect("products.jsp")
else:
# 登录失败,返回登录页面
res.sendRedirect("login.jsp?error=1")
# 关闭数据库连接
c.close()
db.close()
16.3.2 商品展示JSP页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.ziclix.python.sql.zxJDBC" %>
<%@ page import="java.sql.*" %>
<html>
<head>
<title>商品展示</title>
</head>
<body>
<h2>商品列表</h2>
<table border="1">
<tr>
<th>商品ID</th>
<th>商品名称</th>
<th>商品价格</th>
</tr>
<%
// 连接数据库
String url = "jdbc:mysql://localhost:3306/mydb";
String usr = "root";
String passwd = "password";
String driver = "org.gjt.mm.mysql.Driver";
zxJDBC.Connection db = zxJDBC.connect(url, usr, passwd, driver);
Statement stmt = db.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM products");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
double price = rs.getDouble("price");
%>
<tr>
<td><%= id %></td>
<td><%= name %></td>
<td><%= price %></td>
</tr>
<%
}
rs.close();
stmt.close();
db.close();
%>
</table>
</body>
</html>
16.4 部署和测试
- 将上述代码部署到Tomcat服务器中,确保数据库连接信息正确。
-
启动Tomcat服务器,在浏览器中访问登录页面
http://localhost:8080/jython/login.jsp,输入正确的用户名和密码进行登录。 - 登录成功后,会跳转到商品展示页面,查看商品列表。
通过这个实战项目示例,你可以更深入地理解Jython和JSP在服务器端Web编程中的应用,掌握用户登录、数据库查询和页面展示等基本功能的实现方法。
超级会员免费看
50

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



