95、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等,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容器一起工作,有很多这样的容器可供选择。这里以Tomcat为例,它是Servlet和Java Server Page规范的参考实现。以下是一些流行且免费的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)引擎,常用部署方式。 | 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 方法是抽象的,必须在子类中实现,用于响应Web请求。

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>""") 

与Java Servlet类似,Jython Servlet也继承自GenericServlet类并实现了 service 方法。不过,Jython有一些语法差异,如类定义后的括号、省略 throws 语句、显式的 self 参数、无分号和显式类型声明等。

3.3 测试Java和Jython Servlet

测试这些Servlet需要安装Tomcat,并将 jython.jar 文件包含在Tomcat中。具体步骤如下:

3.3.1 安装Tomcat
  1. 从http://jakarta.apache.org 下载Tomcat,建议下载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 - tomcat3.2.3
  3. 设置 JAVA_HOME 环境变量,例如使用JDK1.3.1:
    • Windows系统: set JAVA_HOME=c:\jdk1.3.1
    • *nix系统: export JAVA_HOME=/usr/java/jdk1.3.1
  4. 启动Tomcat:
    • Windows系统: %TOMCAT_HOME%\bin\startup.bat
    • *nix系统: $TOMCAT_HOME/bin/startup.sh
    • 当看到 date time - PoolTcpConnector: Starting HttpConnectionHandler on 8080 时,Tomcat正在运行并准备在8080端口接受连接。
  5. 停止Tomcat:
    • Windows系统: %TOMCAT_HOME%\bin\shutdown.bat
    • *nix系统: $TOMCAT_HOME/bin/shutdown.sh
3.3.2 安装Java Servlet
  1. JavaServlet.java 文件放在 %TOMCAT_HOME%\webapps\jython\WEB - INF\classes 目录。
  2. 在该目录下编译文件: javac - classpath %TOMCAT_HOME%\lib\servlet.jar JavaServlet.java
  3. 启动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
  1. jython.jar 添加到类路径 :有三种方法,推荐将 jython.jar 添加到上下文的 lib 目录。
    • 添加到上下文的 lib 目录: %TOMCAT_HOME%\webapps\jython\WEB - INF\lib
    • 添加到Tomcat的 lib 目录:不推荐,会使Web应用不再自包含。
    • 留在Jython安装目录,在运行Tomcat前添加到类路径。
  2. 使Jython的 lib 目录对Servlet可用 :有三种方法:
    • 设置 python.home 属性 :创建 %TOMCAT_HOME%\webapps\jython\WEB - INF\jylib 目录,设置 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)
- **冻结所需模块**:在Jython上下文的`classes`目录下执行:`jythonc - w . --deep JythonServlet.py`

测试Jython Servlet时,在浏览器中访问 http://localhost:8080/jython/servlet/jythonServlet ,应看到消息 This is a Servlet of the Jython variety 。如果看不到,可能是 Servlet.jar 不在类路径、文件名和类名不一致或Jython模块不可用等原因。

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 目录,编译并测试。该Servlet的 init 方法在启动时初始化计数器, service 方法处理每个客户端请求, destroy 方法在关闭或卸载Servlet时将计数器值写入文件。

4.2 各方法的作用

  • init(ServletConfig) 方法 :在Servlet启动时调用,仅调用一次,适用于耗时任务,如设置数据库连接、编译正则表达式或加载辅助文件。
  • service(ServletRequest, ServletResponse) 方法 :响应每个客户端请求,必须在子类中定义,接收 ServletRequest ServletResponse 对象。
  • destroy() 方法 :在关闭或卸载Servlet时调用,用于清理代码,如关闭数据库连接、刷新和关闭文件等。

5. HttpServlet

HttpServlet GenericServlet 的子类,特定于HTTP协议。它为每个HTTP方法定义了方法,如 doGet doPost doPut 等。当Tomcat接收到GET类型的客户端请求时,会调用请求的Servlet的 doGet 方法进行响应。

5.1 HttpServlet方法

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 的Servlet示例:

# file get_post.py 
from time import time, ctime 
from javax import servlet 
from javax.servlet import http 
class get_post(http.HttpServlet): 
    head = "<head><title>Jython Servlets</title></head>" 
    title = "<center><H2>%s</H2></center>" 
    def doGet(self, req, res): 
        res.setContentType("text/html") 
        out = res.getWriter() 
        out.println('<html>') 
        out.println(self.head) 
        out.println('<body>') 
        out.println(self.title % req.method) 
        out.println("This is a response to a %s request" % 
                    (req.getMethod(),)) 
        out.println("<P>In this GET request, we see the following " + 
                    "header variables.</P>") 
        out.println("<UL>") 
        for name in req.headerNames: 
            out.println(name + " : " + req.getHeader(name) + "<br>") 
        out.println("</UL>") 
        out.println(self._params(req)) 
        out.println(""" 
            <P>The submit button below is part of a form that uses the 
               "POST" method. Click on this button to do a POST request. 
            </P>""") 
        out.println('<br><form action="get_post" method="POST">' + 
                    '<INPUT type="hidden" name="variable1" value="one">' + 
                    '<INPUT type="hidden" name="variable1" value="two">' + 
                    '<INPUT type="hidden" name="variable2" value="three">' + 
                    '<INPUT type="submit" name="button" value="submit">') 
        out.println('<br><font size="-2">time accessed: %s</font>' 
                    % ctime(time())) 
        out.println('</body></html>') 
    def doPost(self, req, res): 
        res.setContentType("text/html"); 
        out = res.getWriter() 
        out.println('<html>') 
        out.println(self.head) 
        out.println('<body>') 
        out.println(self.title % req.method) 
        out.println("This was a %s<br><br>" % (req.getMethod(),)) 
        out.println(self._params(req)) 
        out.println('<br> back to <a href="get_post">GET</a>') 
        out.println('<br><font size="-2">time accessed: %s</font>' 
                    % ctime(time())) 
        out.println('</body></html>') 
    def _params(self, req): 
        params = "Here are the parameters sent with this request:<UL>" 
        names = req.getParameterNames() 
        if not names.hasMoreElements(): 
            params += "None<br>" 
        for name in names: 
            value = req.getParameterValues(name) 
            params += "%s : %r<br>" % (name, tuple(value)) 
        params += "</UL>" 
        return params 

get_post.py 文件放在 $TOMCAT_HOME/webapps/jython/WEB - INF/classes 目录,编译并测试。该示例展示了如何根据客户端请求类型调用不同的方法,以及如何获取请求参数。

5.3 HttpServletRequest和HttpServletResponse

与客户端连接的通信通过 HttpServletRequest HttpServletResponse 对象进行。以下是 HttpServletRequest 的一些方法和属性:
| 方法和属性 | 描述 |
| — | — |
| getAuthType() / AuthType | 返回描述认证类型的字符串,用户未认证时为 None 。 |
| getContextPath() / contextPath | 返回标识请求上下文的路径信息字符串。 |
| getCookies() / cookies() | 返回客户端请求中发送的所有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 值决定是否创建新会话。 |
| 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编码的流发送回客户端,定义了一些额外的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发行版包含 org.python.util.PyServlet ,这是Jython程序员进行 HttpServlet 编程的一大优势。 PyServlet 可以加载、执行和缓存Jython文件,无需中间编译步骤。它通过Servlet映射工作,将特定的Servlet( PyServlet )与特定的URL模式(如 *.py )关联起来。

6.1 PyServlet的限制

Jython文件必须包含一个继承自 javax.servlet.http.HttpServlet 的类,类名必须与文件名(不含 .py 扩展名)匹配。此外,除了继承自 HttpServlet 的类,使用模块全局标识符是不安全的。

6.2 安装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定义可以包含初始化参数,如 python.home python.path 。默认情况下, python.home 的值是上下文的 lib 目录,这使得上下文自包含且跨平台中立。

6.3 测试PyServlet

可以通过启动Tomcat并在浏览器中查看简单的Jython Servlet来确认Servlet映射是否工作。以下是一个简单的测试文件:

# 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 。如果看到消息 Greetings from a jylet. ,则 PyServlet 映射正确。最后,创建Jython的 lib 目录 %TOMCAT_HOME%\webapps\jython\WEB - INF\lib\Lib ,安装完成。

7. Cookies

Cookies允许在客户端机器上存储信息,以便在后续请求中包含。在Jython中,可以使用 javax.Servlet.http.Cookie 类创建和操作Cookies。以下是一个创建Cookie的示例:

from javax.servlet import http 
name = "book1" 
value = "Lewis Carroll, Alice In Wonderland" 
MyNewCookie = http.Cookie(name, value) 
res.addCookie(MyNewCookie)

添加Cookies应在通过响应流发送其他内容之前进行。每个Cookie实例都有方法来设置其具体属性,如 comment domain maxAge 等。

以下是一个使用Cookies的示例:

# File: cookies.py 
from javax import servlet 
from javax.servlet import http 
class cookies(http.HttpServlet): 
    def doGet(self, req, res): 
        res.setContentType("text/html") 
        out = res.getOutputStream() 
        print >>out, "<html><head><title>Cookies with Jython</title></head>" 
        print >>out, """ 
            <body>\n<H2>Cookie list:</H2> 
            Remember, cookies must be enabled in your browser.<br><br>""" 
        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>""" 

测试该示例时,确保浏览器允许Cookies,将 cookies.py 文件放在 %TOMCAT_HOME%\webapps\jython 目录,在浏览器中访问 http://localhost:8080/jython/cookies.py ,添加Cookie后确认是否成功。

8. Sessions

Cookies是与客户端创建会话的最常见方式,会话用于跟踪一系列请求中的客户端信息。不过,Java的 HttpSession 类使会话跟踪更加容易。可以使用 HttpRequest getSession() 方法创建会话:

sess = req.session

以下是一个会话管理的示例:

# 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

在Jython Servlet中连接数据库、执行语句和遍历结果集与其他Jython数据库应用程序没有区别。但管理数据库资源是一个主要问题,因为许多Web应用程序包含多个使用数据库内容的Jython Servlet。常见的数据库资源管理方法有两种:
- 每个Jython Servlet一个连接 :在Jython Servlet的 init 方法中建立数据库连接,仅在Servlet卸载时关闭。这种方法资源消耗大,仅在所需连接数在资源限制内时合理。
- 连接池 :维护一定数量的活动数据库连接,Jython Servlet在响应客户端请求时借用连接,完成后归还。这是更谨慎的资源管理方式。

以下是一个使用数据库连接的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): 
        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() 

要使用该示例,需要创建数据库和表:

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语言,要在JSP中使用Jython,可以使用 jythonc 编译的类、嵌入 PythonInterpreter 或创建Jython特定的自定义标签库。

10.1 jythonc编译的类与JSP

使用 jythonc 编译的类与JSP需要创建与Java兼容的Jython类,该类与模块同名、继承自Java类,并为每个非从超类派生的方法包含 @sig 字符串。将 jythonc 生成的类文件放在上下文的 classes 目录中,JSP页面就可以像使用任何本地Java类一样使用这些类。

JSP文件可以通过两种方式使用 jythonc 编译的类:
- 作为脚本片段 :使用 <%@ page import="fully.qualified.path.to.class" %> 导入类,然后在脚本片段标签( <% %> )或表达式标签( <%= %> )中使用。
- 作为Bean :使用 <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脚本片段中使用Jython代码,可以通过嵌入 PythonInterpreter 间接实现。以下是一个示例:

<!--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解释器的消息。不过,很多人认为脚本片段不是好的做法,有更好的方式创建动态内容,如Bean类和标签库。

10.3 Jython标签库

可以使用 jythonc 将Jython标签库模块编译成Java类来创建Jython标签库。Jython标签库模块必须包含一个与模块同名(不含 .py 扩展名)的类,该类必须继承 javax.servlet.jsp.tagext.Tag 接口或实现该接口的Java类。

以下是一个简单的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 

编译 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" %> 
<html> 
<body> 
<jython:message /> 
</body> 
</html> 

test.jsp 文件保存到 %TOMCAT_HOME%\webapps\jython 目录,在浏览器中访问 http://localhost:8080/jython/test.jsp ,应看到自定义标签消息。

10.4 BSF

IBM的Bean Scripting Framework(BSF)是一个实现各种脚本语言的Java工具,Jython是其支持的语言之一。要在JSP中使用Jython脚本片段,需要下载正确的 bsf.jar 文件和相关的标签库文件,并将它们放在上下文的 lib 目录和 WEB - INF 目录中。

在JSP文件中使用Jython脚本片段的示例如下:

<%@ 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编程中具有广泛的应用,可以通过Servlet和JSP实现高效、灵活的Web应用开发。在实际应用中,需要根据具体需求选择合适的方法和工具,合理管理资源,以提高应用程序的性能和可维护性。

11. 总结与建议

在服务器端Web编程中,Jython提供了一种结合Python灵活性和Java强大功能的有效方式。以下是一些总结和建议,帮助你更好地利用Jython进行Web开发:

11.1 选择合适的Servlet容器

不同的Servlet容器有不同的特点和适用场景。例如,Tomcat是Servlet和JSP规范的参考实现,稳定且功能丰富;Jetty则紧凑高效,适合对性能要求较高的场景。在选择时,需要考虑项目的规模、性能需求以及团队的技术栈。

11.2 合理使用Jython Servlet
  • 编译与非编译方式 :使用 jythonc 编译Jython Servlet可以提高性能,但增加了编译步骤;而使用PyServlet则无需编译,开发更便捷。可以根据项目的开发阶段和性能要求选择合适的方式。
  • 遵循命名和结构规范 :在编写Jython Servlet时,要遵循命名规范,确保类名与文件名匹配,并且只使用安全的模块全局标识符。
11.3 有效管理数据库资源
  • 连接方式选择 :根据项目的并发量和资源限制,选择合适的数据库连接方式。如果并发量较低,可以考虑每个Servlet一个连接;如果并发量较高,连接池是更好的选择。
  • 连接池工具 :选择成熟、稳定的连接池工具,如PoolMan,以确保数据库连接的高效管理。
11.4 利用JSP的优势
  • 选择合适的集成方式 :在JSP中使用Jython时,可以根据具体需求选择 jythonc 编译的类、嵌入 PythonInterpreter 或创建自定义标签库等方式。
  • 避免脚本片段滥用 :虽然脚本片段可以实现动态内容,但会使JSP页面变得复杂,建议优先使用Bean类和标签库来创建动态内容。

12. 未来趋势与展望

随着Web技术的不断发展,Jython在服务器端Web编程中的应用也可能会发生变化。以下是一些可能的未来趋势:

12.1 与新兴技术的融合

Jython可能会与新兴的Web技术,如微服务、容器化和无服务器架构等进行融合。例如,将Jython Servlet封装成微服务,通过容器化技术进行部署,实现更高的可扩展性和灵活性。

12.2 性能优化

随着Jython和Java虚拟机的不断发展,Jython的性能可能会得到进一步提升。未来可能会出现更多的性能优化技术和工具,使Jython在服务器端Web编程中更加高效。

12.3 社区支持和生态系统发展

随着Jython在Web开发中的应用越来越广泛,社区支持和生态系统可能会得到进一步发展。更多的开源项目、工具和文档可能会出现,为开发者提供更好的支持。

13. 常见问题与解决方案

在使用Jython进行服务器端Web编程时,可能会遇到一些常见问题。以下是一些常见问题及其解决方案:

13.1 编译问题
  • 问题描述 :使用 jythonc 编译Jython Servlet时,可能会出现编译错误,如找不到类或方法等。
  • 解决方案 :确保 CLASSPATH 包含所需的类库,如 Servlet.jar jython.jar 。检查代码中是否存在语法错误或命名错误。
13.2 类路径问题
  • 问题描述 :在运行Jython Servlet时,可能会出现类路径错误,导致找不到所需的类。
  • 解决方案 :确保 jython.jar 文件在正确的位置,如上下文的 lib 目录。检查 TOMCAT_HOME JAVA_HOME 环境变量是否正确设置。
13.3 数据库连接问题
  • 问题描述 :在使用数据库连接时,可能会出现连接失败或连接超时等问题。
  • 解决方案 :检查数据库的配置信息,如URL、用户名和密码是否正确。确保数据库服务正常运行。如果使用连接池,检查连接池的配置是否正确。
13.4 JSP集成问题
  • 问题描述 :在JSP中使用Jython时,可能会出现找不到类或方法、脚本片段执行错误等问题。
  • 解决方案 :确保 jythonc 编译的类文件在正确的位置,并且JSP页面正确导入了所需的类。检查脚本片段中的代码是否存在语法错误。

14. 示例项目实践

为了帮助你更好地理解和应用上述知识,下面给出一个简单的示例项目,展示如何使用Jython进行服务器端Web编程。

14.1 项目需求

创建一个简单的Web应用程序,用于展示图书信息。用户可以查看图书列表,并添加新的图书信息。

14.2 项目结构
webapps/
└── jython/
    ├── WEB-INF/
    │   ├── classes/
    │   │   ├── BookServlet.py
    │   │   └── Book.py
    │   ├── lib/
    │   │   ├── jython.jar
    │   │   └── zxJDBC.jar
    │   └── web.xml
    ├── index.jsp
    └── add_book.jsp
14.3 代码实现
14.3.1 Book.py
# Book.py
class Book:
    def __init__(self, id, title, author):
        self.id = id
        self.title = title
        self.author = author

    def get_id(self):
        return self.id

    def get_title(self):
        return self.title

    def get_author(self):
        return self.author
14.3.2 BookServlet.py
# BookServlet.py
from javax.servlet import http
from com.ziclix.python.sql import zxJDBC

class BookServlet(http.HttpServlet):
    def init(self, cnfg):
        url = "jdbc:mysql://localhost:3306/bookstore"
        usr = "root"
        passwd = "password"
        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()
        out.println("<html><body>")
        out.println("<h2>Book List</h2>")
        self.c.execute("SELECT id, title, author FROM books")
        for row in self.c.fetchall():
            book = Book(row[0], row[1], row[2])
            out.println("<p>ID: %s, Title: %s, Author: %s</p>" % (book.get_id(), book.get_title(), book.get_author()))
        out.println("<a href='add_book.jsp'>Add Book</a>")
        out.println("</body></html>")

    def doPost(self, req, res):
        title = req.getParameter("title")
        author = req.getParameter("author")
        self.c.execute("INSERT INTO books (title, author) VALUES (%s, %s)", (title, author))
        self.db.commit()
        res.sendRedirect("BookServlet")

    def destroy(self):
        self.c.close()
        self.db.close()
14.3.3 index.jsp
<!-- index.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Bookstore</title>
</head>
<body>
    <h1>Welcome to the Bookstore</h1>
    <a href="BookServlet">View Book List</a>
</body>
</html>
14.3.4 add_book.jsp
<!-- add_book.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Add Book</title>
</head>
<body>
    <h2>Add a New Book</h2>
    <form action="BookServlet" method="post">
        <label for="title">Title:</label>
        <input type="text" id="title" name="title" required><br>
        <label for="author">Author:</label>
        <input type="text" id="author" name="author" required><br>
        <input type="submit" value="Add Book">
    </form>
</body>
</html>
14.4 部署与测试
  1. 将上述代码文件按照项目结构放置到相应的目录中。
  2. 确保 jython.jar zxJDBC.jar 文件在 WEB-INF/lib 目录中。
  3. 启动Tomcat服务器。
  4. 在浏览器中访问 http://localhost:8080/jython/index.jsp ,即可看到图书列表页面。
  5. 点击“Add Book”链接,进入添加图书页面,输入图书信息并提交,即可添加新的图书。

13. 总结

Jython在服务器端Web编程中提供了丰富的功能和灵活的开发方式。通过合理选择Servlet容器、有效管理数据库资源、利用JSP的优势以及解决常见问题,开发者可以构建出高效、可维护的Web应用程序。同时,关注未来趋势和进行示例项目实践,有助于不断提升开发技能和应对新的挑战。希望本文能为你在服务器端Web编程中使用Jython提供有价值的参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值