92、Jython在服务器端Web编程中的应用

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 
  1. 设置 JAVA_HOME 环境变量指向JDK安装的根目录,例如:
# on Windows 
set JAVA_HOME=c:\jdk1.3.1 
# bash (*nix) setup 
export JAVA_HOME=/usr/java/jdk1.3.1 
  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
  1. JavaServlet.java 文件放在 %TOMCAT_HOME%\webapps\jython\WEB - INF\classes 目录下。
  2. 在该目录下使用以下命令编译JavaServlet.java文件:
javac -classpath %TOMCAT_HOME%\lib\servlet.jar JavaServlet.java 
  1. 启动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 &nbsp; 
                <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: &nbsp;%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 文件;检查文件名和类名是否一致。
  • 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编程中的应用,掌握用户登录、数据库查询和页面展示等基本功能的实现方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值