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

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

1. Jython与Web开发

Jython在Web开发中的适用范围与Java类似,尤其适用于那些追求更快开发速度和更高灵活性的Java应用场景。服务器端Java Web编程主要通过Servlet和Java Server Pages(JSP)实现,因此Jython的主要应用也是Servlet和JSP。不过,Java的企业级软件包(j2ee)在Web应用开发中也很重要,如EJB、JNDI、JDBC等,Jython在这些技术上同样有效。但本文主要聚焦于Servlet和JSP。

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等。

2. Jython Servlet容器

Jython可以与任何兼容的Java Servlet容器配合使用,以下是一些常见的Servlet容器:
| 容器名称 | 描述 | 下载地址 | 支持规范 |
| — | — | — | — |
| Jakarta’s Tomcat | Servlet和Java Server Pages规范的参考实现,本文使用的服务器 | http://jakarta.apache.org/ | 2.2 Servlet和1.1 JSP(3.2.3版本);2.3 Servlet和1.2 JSP(4.0版本) |
| Apache JServ | 为Apache创建的Servlet(2.0版本)引擎,常用部署方式 | http://java.apache.org/ | 2.0 Servlet |
| Jigsaw | W3C的实验性Web服务器,成熟且支持Servlet 2.2和Java Server Pages 1.1 | 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方法是抽象的,必须在子类中实现,用于响应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方法,但语法上有差异,如类定义后的括号、省略throws语句、显式的self参数等。

3.3 测试Java和Jython Servlet

测试这两个Servlet需要安装Tomcat,Jython Servlet还需要将jython.jar文件包含在Tomcat中。
- 安装Tomcat
1. 从http://jakarta.apache.org 下载Tomcat,建议下载jakarta - tomcat - 3.2.3版本。
2. 解压文件到有足够权限的目录,设置TOMCAT_HOME环境变量。
3. 设置JAVA_HOME环境变量为JDK安装的根目录。
4. 使用相应平台的启动脚本启动Tomcat:
- Windows: %TOMCAT_HOME%\bin\startup.bat
- nix: $TOMCAT_HOME/bin/startup.sh
5. 看到 date time - PoolTcpConnector: Starting HttpConnectionHandler on 8080 表示Tomcat正在运行并监听8080端口。
6. 使用相应平台的关闭脚本停止Tomcat:
- Windows: %TOMCAT_HOME%\bin\shutdown.bat
-
nix: $TOMCAT_HOME/bin/shutdown.sh
- 安装Java Servlet
1. 将JavaServlet.java文件放在 %TOMCAT_HOME%\webapps\jython\WEB - INF\classes 目录下。
2. 在该目录下使用以下命令编译JavaServlet.java文件: javac -classpath %TOMCAT_HOME%\lib\servlet.jar JavaServlet.java
3. 启动Tomcat,在浏览器中访问 http://localhost:8080/jython/servlet/JavaServlet ,应看到消息“This is a Java Servlet”。
- 安装Jython Servlet
有两种使用Jython Servlet的方法,这里使用jythonc编译。步骤如下:
1. 使用jythonc编译Jython Servlet模块 :将JythonServlet.py文件放在 %TOMCAT_HOME%\webapps\jython\WEB - INF\classes 目录下,确保CLASSPATH包含servlet.jar,然后在该目录下使用以下命令编译: jythonc -w . JythonServlet.py
2. 将jython.jar文件添加到Web应用 :有三种方式:
- 推荐将jython.jar文件添加到上下文的lib目录,如 %TOMCAT_HOME%\webapps\jython\WEB - INF\lib
- 不建议将jython.jar文件添加到Tomcat的lib目录。
- 将jython.jar文件留在Jython的安装目录,但在启动Tomcat前将其添加到CLASSPATH。
3. 使Jython的lib目录对Servlet可用 :有三种方法:
- 设置python.home属性,可通过设置TOMCAT_OPTS环境变量实现。
- 冻结所需模块,使用 jythonc -w . --deep JythonServlet.py 命令。
- 让每个Servlet显式地将模块位置附加到sys.path。

编译完成后,在浏览器中访问`http://localhost:8080/jython/servlet/jythonServlet`,应看到消息“This is a Servlet of the Jython variety”。如果看不到该消息,可能会出现以下错误:
    - 如果编译JythonServlet时servlet.jar文件不在CLASSPATH中,可能会看到ClassCastException。
    - 如果文件名和类名不同(即使只是大小写不同),也会看到ClassCastException。
    - 如果jythonc编译生成的类文件有一个不在上下文的classes目录中,会看到AttributeError。
    - 如果Jython的模块不可用,会看到ImportError。
4. 关于GenericServlet

Listing 12.2中的Jython Servlet继承自javax.Servlet.GenericServlet,这是一个不特定于任何协议的Servlet类。虽然HttpServlet更常用于Web编程,但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" |
| 从Jython子类调用的超类方法 | |
| 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 |

以下是一个使用这些方法的HitCounter Servlet示例:

# filename: HitCounter.py 
from javax import servlet 
from time import time, ctime 
import os 
class HitCounter(servlet.GenericServlet): 
    def init(self, cfg=None): 
        if cfg: 
            servlet.GenericServlet.init(self, cfg) 
        else: 
            servlet.GenericServlet.init(self) 
        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文件放在 %TOMCAT_HOME%\webapps\jython\WEB - INF\classes 目录下,使用 jythonc -w . --deep HitCounter.py 命令编译。在浏览器中访问 http://localhost:8080/jython/servlet/HitCounter ,第一次访问时应看到消息“Total Hits: 0”,后续每次访问计数会增加。关闭并重启Tomcat后,计数应继续。

这三个方法(init、service和destroy)对应Servlet生命周期的不同阶段:
- init(ServletConfig)方法 :在Servlet启动时调用,仅调用一次,适用于耗时任务,如设置数据库连接、编译正则表达式或加载辅助文件。
- service(ServletRequest, ServletResponse)方法 :响应每个客户端请求时调用,必须在Servlet中定义,因为它在GenericServlet中是抽象的。
- destroy()方法 :在关闭或卸载Servlet时调用,用于清理代码,如关闭数据库连接、刷新和关闭文件等。

5. HttpServlet

GenericServlet适用于任何聊天类型的协议,但Web开发主要使用HTTP协议。因此,建议继承javax.Servlet.http.HttpServlet,它是GenericServlet的子类,继承了init、service和destroy方法。

HttpServlet为每个HTTP方法定义了一个方法,如doGet、doPost、doPut等。当Tomcat接收到GET类型的客户端请求时,会调用请求的Servlet的doGet()方法进行响应。此外,HttpServlet有一个特定于HTTP的service方法,与GenericServlet的版本相比,参数是HTTP特定的请求和响应对象(javax.servlet.http.HttpServletRequest和javax.servlet.http.HttpServletResponse)。

以下是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): |

以下是一个使用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方法。

6. HttpServletRequest和HttpServletResponse

与客户端连接的通信通过HttpServletRequest和HttpServletResponse对象进行,它们是对从客户端接收和发送的请求流的抽象,提供了更高级的、特定于HTTP的方法,方便处理请求和响应。

以下是HttpServletRequest的一些方法和属性:
| 方法和属性 | 描述 |
| — | — |
| getAuthType() / AuthType | 返回描述认证类型名称的字符串,用户未认证时为None。 |
| getContextPath() / contextPath | 返回标识所请求上下文的路径信息字符串。 |
| getCookies() / cookies() | 返回客户端请求中发送的所有Cookie,作为javax.servlet.http.Cookie对象数组。 |
| getDateHeader(name) | 以长类型检索指定头的值。 |
| getHeader(name) | 以字符串形式返回指定头的值。 |
| getHeaderNames() / headerNames | 返回请求中包含的所有头名称的枚举。 |
| getHeaders(name) | 返回指定头名称的所有值的枚举。 |
| getIntHeader(name) | 以Java int类型检索指定头的值,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,表示用户是否在指定角色中。 |

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 |

7. PyServlet

Jython发行版包含org.python.util.PyServlet,它可以加载、执行和缓存Jython文件,无需中间编译步骤。通过Servlet映射,将PyServlet与特定的URL模式(如 .py)关联起来。当收到 .py文件的请求时,PyServlet会加载、缓存并调用*.py文件中的方法进行响应。

使用PyServlet有一些限制:
- Jython文件必须包含一个继承自javax.servlet.http.HttpServlet的类,类名必须与文件名(不含.py扩展名)相同。
- 除了继承HttpServlet的类之外,使用模块全局标识符是不安全的。

7.1 安装PyServlet

安装PyServlet只需定义一个Servlet映射,并确保jython.jar文件在上下文的lib目录中。Servlet映射在上下文的部署描述符文件web.xml中定义,如 $TOMCAT_HOME/webapps/jython/WEB - INF/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> 

需要注意的是,定义Jython资源位置的属性(如python.home和python.path)会影响上下文的自包含性和跨平台可移植性。幸运的是,PyServlet的python.home默认值是上下文的lib目录,这使得上下文既自包含又跨平台。

7.2 测试PyServlet

可以通过以下简单的测试jylet来确认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 ,如果看到问候消息,则PyServlet映射正确。创建Jython的lib目录 %TOMCAT_HOME%\webapps\jython\WEB - INF\lib\Lib ,将所需的Jython模块放在该目录中,安装完成。

8. 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实例可以使用get和set方法或Jython的自动bean属性来设置以下属性:
- comment
- domain
- maxAge
- name
- path
- secure
- value
- version

以下是一个使用Cookie对象的自动bean属性创建和读取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>""" 

测试该jylet时,确保浏览器允许使用Cookies。将cookies.py文件放在 %TOMCAT_HOME%\webapps\jython 目录下,在浏览器中访问 http://localhost:8080/jython/cookies.py ,第一次访问时应看到标题和表单。填写表单并提交,应看到添加Cookie成功的确认信息。返回doGet方法,查看Cookie列表以确认Cookie是否添加成功。

9. Sessions

Cookies是与客户端创建会话的常见方式,但Java的HttpSession类使会话跟踪更加容易。

要创建会话,可以使用HttpRequest的getSession()方法,返回一个HttpSession实例。使用Jython的HttpSession对象与Java的区别仅在于语法和对象的get*方法的自动bean属性。

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

# File: session.py 
from javax import servlet 
from javax.servlet import http 
class session(http.HttpServlet, servlet.RequestDispatcher): 
    def doGet(self, req, res): 
        sess = req.session 
        res.contentType = "text/html" 
        out = res.getWriter() 
        name = req.getParameterValues("name") 
        value = req.getParameterValues("value") 
        if name and value: 
            sess.putValue(name[0], value[0]) 
        print >>out, (""" 
            <html> 
            <body> 
            <H3>Session Test</H3> 
            Created at %s<br> 
            Last Accessed = %s<br> 
            <u>Values:</u>""" % 
            (sess.creationTime, sess.maxInactiveInterval) 
        ) 
        print >>out, "<ul>" 
        for key in sess.getValueNames(): 
            print >>out, "<li>%s: %s</li>" % (key, sess.getValue(key)) 
        print >>out, "</ul>" 
        print >>out, """ 
            <HR><P>Use this form to add a new values to the session</P> 
            <form action="session.py" method="GET"> 
            <P>Name:<br><INPUT type="text" name="name" size="30"></P> 
            <P>Value:<br><INPUT type="text" name="value" size="30"></P> 
            <INPUT type="submit" name="button" value="submit"> 
            </body> 
            </html> 
            """ 

将session.py文件放在 %TOMCAT_HOME%\webapps\jython 目录下,在浏览器中访问 http://localhost:8080/jython/session.py ,使用表单添加一些变量到会话中以确认其正常工作。尝试禁用Cookies,查看URL重写是否正常工作。

10. 数据库与Servlet

在jylet中连接数据库、执行语句和遍历结果集与其他Jython数据库应用没有区别,但管理连接和其他数据库资源是一个主要问题,因为许多Web应用包含多个使用数据库内容的jylet。在每个jylet中创建数据库连接会迅速消耗数据库资源,而为每个请求创建和关闭数据库连接的开销又太大。

管理数据库资源有多种选择,两种主要方法是每个jylet一个连接或连接池。每个jylet使用一个连接是一种常见但资源密集的方法,它在jylet的init方法中建立数据库连接,仅在jylet卸载时关闭该连接,这样可以消除响应客户端请求时的连接开销,但仅在所需连接数量在资源限制范围内时才合理。大多数情况下需要更谨慎的资源管理。

以下是一个在init方法中获取数据库连接和游标对象,并在destroy方法中关闭它们的jylet示例:

# file: DBDisplay.py 
from javax.servlet import http 
from com.ziclix.python.sql import zxJDBC 
class DBDisplay(http.HttpServlet): 
    def init(self, cnfg): 
        url = "jdbc:mysql://192.168.1.77/products" 
        usr = "productsUser"     # replace with real user name 
        passwd = "secret"        # replace with real password 
        driver = "org.gjt.mm.mysql.Driver" 
        self.db = zxJDBC.connect(url, usr, passwd, driver) 
        self.c = self.db.cursor() 
    def doGet(self, req, res): 
        res.setContentType("text/html") 
        out = res.getWriter() 
        print >>out, """ 
            <html> 
            <head> 
              <title>Jylet Database Connection</title> 
            </head> 
            <body> 
            <table align="center"> 
              <tr> 
                <td><b>ID</b></td> 
                <td><b>Title</b></td> 
                <td><b>Description</b></td> 
                <td><b>Price</b></td> 
                </tr>""" 
        self.c.execute("select code, name, description, price from products") 
        for row in self.c.fetchall(): 
            print >>out, """ 
                <tr> 
                  <td>%s</td> 
                  <td>%s</td> 
                  <td>%s</td> 
                  <td>%s</td>""" % row 
        print >>out, """ 
            </table> 
            </body> 
            </html>""" 
    def destroy(self): 
        self.c.close() 
        self.db.close() 

该示例假设存在一个名为products的数据库,其中包含products表,该表至少有code、name、description和price字段。可以使用以下SQL语句创建数据库和表:

create database products;
CREATE TABLE products (
  primarykey int(11) NOT NULL auto_increment, 
  code varchar(55) default NULL, 
  name varchar(255) default NULL, 
  description text, 
  price float(5,2) default NULL, 
  PRIMARY KEY (primarykey) 
) TYPE=MyISAM;

创建数据库和表后,为每个字段创建一些任意值,并将DBDisplay.py文件放在上下文的根目录中,在浏览器中访问 http://localhost:8080/jython/DBDisplay.py ,应能看到数据库数据。

如果Web应用开始使用过多连接,可以考虑使用连接池。连接池可以实现谨慎的资源管理并消除连接开销,它维护一定数量的活动数据库连接,jylet在响应客户端请求时借用这些连接,完成后归还。一个流行、免费、经过测试且文档完善的连接池工具是PoolMan(http://www.codestudio.com/),其他Java连接池包也应该能与Jython无缝配合。

11. JSP

Java Server Pages(JSP)是Sun推荐的与Servlet互补的模板系统。JSP文件包含网页的标记代码和文本,同时包含指定动态内容的特殊标签。目前,Tomcat仅在JSP中实现Java语言,因此如何在JSP中使用Jython是一个问题。目前不能直接在JSP中包含Jython代码,但可以使用jythonc编译的类、嵌入PythonInterpreter或创建Jython特定的自定义标签库。

11.1 jythonc编译的类与JSP

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

JSP文件可以通过两种方式使用jythonc编译的类:在脚本中或作为bean。如果作为bean使用,Jython类必须符合bean约定,并为每个读写属性包含getProperty和setProperty方法。脚本可以使用任何类。无论作为bean还是在脚本中,都必须先将jythonc编译的类加载到适当的作用域中。

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

11.2 嵌入PythonInterpreter到JSP

如果想在JSP脚本中使用Jython代码,可以通过嵌入PythonInterpreter实例间接实现。需要使用导入指令导入org.python.util.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> 

确保jython.jar文件在上下文的lib目录中,将interp.jsp文件放在上下文的根目录中,在浏览器中访问 http://localhost:8080/jython/interp.jsp ,应能看到Jython解释器的简单消息。

需要注意的是,许多人认为脚本是不好的做法,因此在脚本中从Java使用Jython增加了复杂性,有更好的方法来创建动态内容,如bean类和标签库。

11.3 Jython标签库

标签库是可以在JSP页面中使用的自定义标签库。可以使用jythonc将Jython标签库模块编译成Java类来创建Jython标签库。Jython标签库模块必须符合一定的限制才能透明地作为Java类使用,模块必须包含一个与模块名称(不含.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

这将在上下文的classes目录中创建JythonTag.class和JythonTag$_PyInner.class文件。但仅这些类还不足以使用标签库,还需要创建一个标签库描述文件。

以下是一个标签库描述文件示例:

<?xml version="1.0" encoding="ISO-8859-1" ?> 
<!DOCTYPE taglib 
  PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" 
  "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> 
<taglib> 
    <tlibversion>1.0</tlibversion> 
    <jspversion>1.1</jspversion> 
    <shortname>JythonTag</shortname> 
    <info>A simple Jython tag library</info> 
    <tag> 
        <name>message</name> 
        <tagclass>JythonTag</tagclass> 
    </tag> 
</taglib> 

将该文件保存为 %TOMCAT_HOME%\webapps\jython\WEB - XML\jython-taglibs.tld ,JSP页面将使用该文件来识别页面中使用的标签。

在JSP页面中使用标签库,需要包含一个指令来指定标签库描述文件的路径并为该库指定一个简单的名称。

以下是一个使用该标签库的JSP文件示例:

<%@ taglib uri="/WEB-INF/jython-taglibs.tld" prefix="jython" %> 
<html> 
<body> 
<jython:message /> 
</body> 
</html> 

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

使用编译后的Jython模块实现标签库会再次引发Jython的主目录和Jython的lib目录的问题。jythonc编译的标签库不知道python.home在哪里或在哪里找到库模块,除非在PySystemState中建立该信息。可以在TOMCAT_OPTS环境变量中设置python.home属性,也可以利用PyServlet类来建立该系统状态信息。如果上下文加载了PyServlet类,那么python.home和sys.path信息可能已经正确,标签库可以从相同的lib目录加载Jython模块。

12. BSF

IBM的Bean Scripting Framework(BSF)是一个实现各种脚本语言的Java工具,Jython是目前支持的语言之一。Apache Jakarta项目的taglibs子项目包含大量可用的Java标签库,其中有趣的是BSF标签库。BSF标签库与BSF jar文件结合使用,可以快速将Jython脚本和表达式插入JSP页面。

BSF发行版目前需要一些调整才能与Jython配合使用,可从相关网站下载正确的bsf.jar文件。将bsf.jar文件放在上下文的lib目录中,将包含BSF标签库的jar文件和bsf.tld文件分别放在上下文的lib目录和WEB - INF目录中。

在JSP文件中使用Jython脚本,首先需要包含一个指令来识别BSF标签库:

<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %>

然后可以使用bsf.scriptlet标签,在标签体中包含Jython代码:

<bsf.scriptlet language="jython"> 
import random 
print >> out, random.randint(1, 100) 
</bsf.scriptlet>

脚本中有一些JSP对象会自动在解释器中设置,包括:
| 对象名称 | Java类名 |
| — | — |
| request | javax.servlet.http.HttpServletRequest |
| response | javax.servlet.http.HttpServletResponse |
| pageContext | javax.servlet.jsp.PageContext |
| application | javax.servlet.ServletContext |
| out | javax.servlet.jsp.JspWriter |
| config | javax.servlet.ServletConfig |
| page | java.lang.Object |
| exception | java.lang.Exception |
| session | javax.servlet.http.HttpSession |

以下是一个使用bsf:scriptlet标签在JSP文件中创建时间戳的示例:

<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %> 
<html> 
<body> 
<center><H2>BSF scriptlets</H2></center> 
<b>client info:</b><br> 
<bsf:scriptlet language="jython"> 
for x in request.headerNames: 
    print >>out, "%s: &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应用开发。不同的技术和工具各有优缺点,开发者可以根据具体需求选择合适的方法。

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

13. 总结与对比

在服务器端Web编程中,Jython提供了多种方式来实现不同的功能,下面对前面介绍的各种技术和工具进行总结与对比。

技术/工具 优点 缺点 适用场景
GenericServlet 不特定于任何协议,方法通用性强 对HTTP协议支持不够直接 适用于需要通用协议处理的场景
HttpServlet 专门针对HTTP协议,提供了丰富的HTTP方法 依赖于HTTP协议 主要用于Web开发中处理HTTP请求
PyServlet 无需编译,可直接加载和执行Jython文件 有一定的使用限制 快速开发和测试Jython Servlet
Cookies 简单易用,可在客户端存储信息 安全性较低,容量有限 存储一些不太敏感的信息,如用户偏好
Sessions 方便跟踪客户端信息,支持无Cookie会话 需要额外的管理和维护 跟踪用户会话,存储用户登录状态等
数据库连接(单连接) 响应请求时无连接开销 资源消耗大 连接数量在资源限制内的场景
数据库连接池 资源管理更合理,消除连接开销 配置和管理相对复杂 高并发、大量连接的场景
jythonc编译的类与JSP 可在JSP中使用Jython类,与Java集成度高 需要编译步骤,可能存在兼容性问题 需要在JSP中使用Jython功能的场景
嵌入PythonInterpreter 可间接在JSP中使用Jython代码 增加复杂性,脚本使用不被推荐 临时需要在JSP中使用Jython代码的场景
Jython标签库 可创建自定义标签,提高代码复用性 编译和配置相对复杂 需要在JSP中使用自定义标签的场景
BSF 可快速将Jython脚本插入JSP页面 需要调整以适配Jython 快速在JSP中使用Jython脚本的场景
14. 开发流程建议

为了更好地使用Jython进行服务器端Web编程,以下是一个推荐的开发流程:

graph LR
    A[需求分析] --> B[选择技术方案]
    B --> C[环境搭建]
    C --> D[代码开发]
    D --> E[测试和调试]
    E --> F[部署和上线]
    F --> G[维护和优化]
  • 需求分析 :明确项目的功能需求、性能需求和安全需求等,确定是否适合使用Jython进行开发。
  • 选择技术方案 :根据需求分析的结果,选择合适的技术和工具,如Servlet类型、数据库连接方式、JSP集成方法等。
  • 环境搭建 :安装和配置所需的软件和工具,如Tomcat、Jython、数据库等,确保环境正常运行。
  • 代码开发 :根据选择的技术方案,编写Jython代码实现具体的功能,注意代码的可读性和可维护性。
  • 测试和调试 :对开发的代码进行测试,包括功能测试、性能测试和安全测试等,及时发现和解决问题。
  • 部署和上线 :将经过测试的代码部署到生产环境中,确保系统正常运行。
  • 维护和优化 :对上线后的系统进行维护和优化,根据用户反馈和系统运行情况,不断改进系统性能和功能。
15. 常见问题及解决方法

在使用Jython进行服务器端Web编程过程中,可能会遇到一些常见问题,以下是一些解决方法:
- ClassCastException :如果在编译Jython Servlet时servlet.jar文件不在CLASSPATH中,或者文件名和类名不同(即使只是大小写不同),可能会出现该异常。解决方法是确保CLASSPATH包含servlet.jar文件,并且文件名和类名一致。
- AttributeError :如果jythonc编译生成的类文件有一个不在上下文的classes目录中,会出现该错误。检查类文件是否正确放置在指定目录中。
- ImportError :如果Jython的模块不可用,会出现该错误。可以通过设置python.home属性、冻结所需模块或显式地将模块位置附加到sys.path来解决。
- 数据库连接问题 :如果数据库连接失败,检查数据库的配置信息(如URL、用户名、密码等)是否正确,确保数据库服务正常运行。
- JSP集成问题 :在使用jythonc编译的类、嵌入PythonInterpreter或创建Jython标签库时,如果出现问题,检查类文件是否正确放置,配置文件是否正确,以及相关的依赖是否满足。

16. 示例项目

为了更好地理解和应用上述知识,以下是一个简单的示例项目,实现一个用户登录和信息展示的Web应用。

16.1 数据库准备

首先,创建一个数据库和用户表,用于存储用户信息。使用MySQL数据库,执行以下SQL语句:

CREATE DATABASE userdb;

USE userdb;

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    password VARCHAR(50) NOT NULL
);

INSERT INTO users (username, password) VALUES ('testuser', 'testpassword');
16.2 编写Jython Servlet

创建一个Jython Servlet来处理用户登录请求:

# file: login.py
from javax import servlet
from javax.servlet import http
from com.ziclix.python.sql import zxJDBC

class login(http.HttpServlet):
    def doPost(self, req, res):
        username = req.getParameter("username")
        password = req.getParameter("password")

        url = "jdbc:mysql://localhost/userdb"
        usr = "root"  # 替换为实际的用户名
        passwd = "root"  # 替换为实际的密码
        driver = "org.gjt.mm.mysql.Driver"

        try:
            db = zxJDBC.connect(url, usr, passwd, driver)
            c = db.cursor()
            c.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
            result = c.fetchone()

            if result:
                res.setContentType("text/html")
                out = res.getWriter()
                out.println("<html><body>")
                out.println("<h2>Login successful!</h2>")
                out.println("<p>Welcome, " + username + "!</p>")
                out.println("</body></html>")
            else:
                res.setContentType("text/html")
                out = res.getWriter()
                out.println("<html><body>")
                out.println("<h2>Login failed!</h2>")
                out.println("<p>Invalid username or password.</p>")
                out.println("</body></html>")

            c.close()
            db.close()
        except Exception as e:
            res.setContentType("text/html")
            out = res.getWriter()
            out.println("<html><body>")
            out.println("<h2>Error occurred!</h2>")
            out.println("<p>" + str(e) + "</p>")
            out.println("</body></html>")
16.3 配置和部署
  • 将login.py文件放在 %TOMCAT_HOME%\webapps\jython\WEB - INF\classes 目录下。
  • 使用 jythonc -w . --deep login.py 命令编译该文件。
  • 确保jython.jar文件和数据库驱动文件(如mm_mysql - 2_0_4 - bin.jar和zxJDBC.jar)放在上下文的lib目录中。
  • 在浏览器中访问 http://localhost:8080/jython/servlet/login ,输入用户名和密码进行登录测试。
17. 总结

通过本文的介绍,我们了解了Jython在服务器端Web编程中的多种应用方式,包括Servlet的开发、Cookies和Sessions的使用、数据库连接管理以及与JSP的集成等。Jython结合了Python的灵活性和Java的强大功能,为Web开发提供了更多的选择和便利。在实际开发中,根据项目的需求和特点,选择合适的技术和工具,遵循合理的开发流程,能够高效地开发出高质量的Web应用。同时,要注意解决开发过程中可能遇到的问题,不断优化和改进系统。希望本文能够帮助你更好地掌握Jython在服务器端Web编程中的应用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值