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

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

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

1. Jython在Web开发中的定位

Jython适用于Java适用的任何地方,尤其适用于那些需要更快开发速度和更高灵活性的Java应用场景。服务器端Java Web编程主要通过Servlet和Java Server Pages(JSP)实现,因此Jython的主要应用也是Servlet和JSP。不过,Java的企业级包(j2ee)在Web应用开发中也占据重要地位,Jython在这些技术中同样有效。

然而,Jython并非适用于所有场景。CPython是实现CGI脚本的流行语言,但Jython不适合。在CGI中,Web服务器接收请求,启动进程进行响应,响应完成后关闭子进程。虽然可以用Jython这样做,但由于JVM的启动时间,这不是一个好选择。而Servlet和JSP是持久的,它们在Web请求之间保持在内存中。

使用Jython进行Web编程有很多优点。高质量的Servlet容器很容易获得且广泛部署。Java Servlet应用可以受益于Jython的灵活性和高级语言特性,还能利用Java和Jython的库。一些常见的Servlet容器包括WebLogic、WebSphere、Tomcat、Jigsaw、Resin和Jetty。许多组织都部署了这些容器,这使得Jython在大多数情况下都能立即使用。

2. Jython Servlet容器

Jython可以与任何兼容的Java Servlet容器一起使用,有很多这样的容器可供选择。这里介绍几种常见的Servlet容器:
| 容器名称 | 描述 | 官网 |
| — | — | — |
| Jakarta’s Tomcat | 是Servlet和Java Server Pages规范的参考实现,稳定版本3.2.3支持2.2 Servlet和1.1 JSP规范,后续会有4.0版本支持2.3 Servlet和1.2 JSP规范。 | http://jakarta.apache.org/ |
| Apache JServ | 为Apache创建的Servlet(版本2.0)引擎,常用于与Jython一起使用Servlet。 | http://java.apache.org/ |
| Jigsaw | W3C的实验性Web服务器,实际上比“实验性”这个标签更成熟,是完全符合HTTP/1.1的Web服务器和缓存代理服务器,支持Servlet 2.2规范和Java Server Pages 1.1。 | http://www.w3.org/Jigsaw/ |
| Jetty | 紧凑高效的Java Web服务器,支持Servlet 2.2和JSP 1.1规范,支持HTTP 1.1,包括SSL支持,并且易于与EJB服务器集成。 | http://jetty.mortbay.com/ |

这些工具的文档很丰富,安装指南可以从各自的网站获取。

3. 定义简单的Servlet类

3.1 简单的Java Servlet

以下是一个基本的Java Servlet示例:

// Filename: JavaServlet.java 
import javax.servlet.*; 
import java.io.*; 

public class JavaServlet extends GenericServlet {
    public void service(ServletRequest req, ServletResponse res) 
    throws ServletException, IOException {
        res.setContentType("text/html"); 
        PrintWriter toClient = res.getWriter(); 
        toClient.println("<html><body>" + 
                         "This is a Java Servlet." + 
                         "</body></html>"); 
    } 
} 

这个示例使用了Servlet API中的GenericServlet类,由于 service 方法是抽象的,所以必须在子类中实现。输出通过 PrintWriter 对象发送给客户端。

3.2 简单的Jython Servlet

对应的Jython Servlet示例如下:

# Filename: JythonServlet.py 
from javax import servlet 
import random # Not used, just here to test module imports 
class JythonServlet(servlet.GenericServlet): 
    def service(self, req, res): 
        res.setContentType("text/html") 
        toClient = res.getWriter() 
        toClient.println("""<html><body> 
                         This is a Servlet of the Jython variety. 
                         </body></html>""") 

这个Jython Servlet与Java Servlet类似,继承自 GenericServlet 并实现了 service 方法。语法上有一些差异,如类定义后的括号、省略 throws 语句、显式的 self 参数等。

3.3 测试Java和Jython Servlet

测试这些Servlet需要安装Tomcat,Jython Servlet还需要将 jython.jar 文件添加到Tomcat Web应用中。具体步骤如下:

安装Tomcat
  1. 从http://jakarta.apache.org 下载Tomcat,建议下载jakarta - tomcat - 3.2.3版本,根据平台选择zip或tar.gz文件。
  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 时,Tomcat正在运行并准备在8080端口接受连接。
5. 停止Tomcat时,使用相应的关闭脚本:

# Windows 
%TOMCAT_HOME%\bin\shutdown.bat 
# bash (*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 
  1. 启动Tomcat服务器,在浏览器中访问 http://localhost:8080/jython/servlet/JavaServlet ,应该看到消息 This is a Java Servlet.
安装Jython Servlet

有两种使用Jython Servlet的方法,这里使用 jythonc 编译。步骤如下:
1. 使用 jythonc 编译Jython Servlet模块
- 确保 classpath 中包含 servlet.jar 文件,它位于Tomcat的 lib 目录下。
- 将 JythonServlet.py 文件放在 %TOMCAT_HOME%\jython\WEB - INF\classes 目录下。
- 在该目录下使用以下命令编译:

jythonc -w . JythonServlet.py 

编译过程中要注意查看是否有 Creating .java files: JythonServlet module JythonServlet extends javax.servlet.GenericServlet 这样的行,如果没有,说明有问题,需要检查 CLASSPATH 和设置选项并重新编译。
2. jython.jar 文件添加到Web应用中 :有三种方法:
- 首选方法 :将 jython.jar 文件添加到上下文的 lib 目录,如 %TOMCAT_HOME%\webapps\jython\WEB - INF\lib ,这样可以使Web应用自包含。
- 不推荐方法 :将 jython.jar 文件添加到Tomcat的 lib 目录,这样会使Web应用不再自包含。
- 合理方法 :将 jython.jar 文件留在Jython的安装目录,在启动Tomcat之前将其添加到 classpath 中,这样可以访问注册表文件和Jython的 lib 目录,但不如放在上下文的 lib 目录好。
3. 使Jython的 lib 目录对Servlet可用 :有三种方法:
- 设置 python.home 属性 :可以通过设置 TOMCAT_OPTS 环境变量来实现。例如,在Windows上:

set TOMCAT_OPTS=-Dpython.home=%TOMCAT_HOME%\webapps\jython\WEB - INF\jylib 

在*nix系统上:

export TOMCAT_OPTS=-Dpython.home=$TOMCAT_HOME/webapps/jython/WEB - INF/jylib 
- **显式添加模块目录到`sys.path`**:例如:
import sys 
libpath = "c:/jakarta-tomcat_3.2.3/webapps/jython/WEB - INF/jylibs/Lib" 
sys.path.append(libpath) 

但这种方法通常需要显式的、依赖于机器的路径,限制了跨平台的可移植性。
- 冻结所需模块 :使用 jythonc --deep 选项编译所有所需模块,例如:

jythonc -w . --deep JythonServlet.py 

这样可以使Servlet和模块安装在自包含的Web应用中,但要注意更新模块时可能会影响旧的Servlet。

测试Jython Servlet

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

4. 关于GenericServlet

GenericServlet 是一个不特定于任何协议的Servlet类, HttpServlet 更常用于Web编程,因为它特定于HTTP协议,但 GenericServlet HttpServlet 的超类,了解它的方法很重要。以下是 GenericServlet 的一些方法及其在Jython子类中的使用示例:
| Java签名 | Jython子类中的用法 |
| — | — |
| public void init(ServletConfig config) throws ServletException; | def init(self, config): |
| public void destroy(); | def destroy(self): |
| public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException; | def service(self, request, response): |
| public String getServletInfo(); | def getServletInfo(self): return "Info string" |
| public void log(String message); | self.log("message") |
| public ServletConfig getServletConfig(); | config = self.getServletConfig() |
| public java.util.enumeration getInitParameterNames(); | nameList = self.getInitParameterNames() |
| public String getInitParameter(String name) | param = self.getInitParameter("paramName") |
| public ServletContext getServletContext() | context = self.getServletContext |

4.1 计数器Servlet示例

以下是一个计数器Servlet的示例:

# filename: HitCounter.py 
from javax import servlet 
from time import time, ctime 
import os 
class HitCounter(servlet.GenericServlet): 
    def init(self, cfg=None): 
        if cfg: 
            servlet.GenericServlet.init(self, cfg) 
        else: 
            servlet.GenericServlet.init(self) 
        contextRoot = self.servletContext.getRealPath(".") 
        self.file = os.path.join(contextRoot, "counterdata.txt") 
        if os.path.exists(self.file): 
            lastCount = open(self.file, "r").read() 
            try: 
                self.totalHits = int(lastCount) 
            except: 
                self.totalHits = 0 
        else: 
            self.totalHits = 0 
    def service(self, req, res): 
        res.setContentType("text/html") 
        toClient = res.getWriter() 
        toClient.println("<html><body>") 
        toClient.println("Total Hits: %i<br>" % (self.totalHits,)) 
        self.totalHits += 1 
        toClient.println("</body></html>") 
    def destroy(self): 
        f = open(self.file, "w") 
        f.write(str(self.totalHits)) 
        f.close() 

HitCounter.py 文件放在上下文的 classes 目录下,使用 jythonc -w . --deep HitCounter.py 编译。在浏览器中访问 http://localhost:8080/jython/servlet/HitCounter ,第一次访问应该看到 Total Hits: 0 ,每次后续访问计数会增加。关闭并重启Tomcat后,计数应该继续。

4.2 方法说明

  • init(ServletConfig) 方法 :在Servlet启动时调用,仅调用一次,适用于耗时的任务,如设置数据库连接、编译正则表达式或加载辅助文件。在上述示例中,该方法用于建立存储点击信息的文件,并将计数器设置为该文件中最后存储的整数。
  • service(ServletRequest, ServletResponse) 方法 :每次客户端请求时调用,Servlet必须定义此方法,因为它在 GenericServlet 中是抽象的。参数 ServletRequest 包含客户端发送的请求信息, ServletResponse 用于将响应流以适当的mime编码格式发送回客户端。
  • destroy() 方法 :在关闭或卸载Servlet时调用,这里用于将点击计数器的值写入文件,以防止信息丢失,但这不是最好的持久化方法,因为如果服务器不正常关闭,该方法可能不会被调用。

5. HttpServlet

HttpServlet GenericServlet 的子类,更适合用于HTTP协议的Web开发。它为每个HTTP方法定义了一个方法,如 doGet doPost doPut 等。当Tomcat接收到GET类型的客户端请求时,会调用请求的Servlet的 doGet() 方法进行响应。 HttpServlet 还有一个特定于HTTP的 service 方法,与 GenericServlet service 方法不同之处在于参数是 HttpServletRequest HttpServletResponse 对象。

5.1 HttpServlet方法

以下是 HttpServlet 的一些方法及其在Jython子类中的使用示例:
| Java签名 | Jython子类中的用法 |
| — | — |
| doDelete(HttpServletRequest req, HttpServletResponse resp) | def doDelete(self, req, res): |
| doGet(HttpServletRequest req, HttpServletResponse resp) | def doGet(self, req, res): |
| doHead(HttpServletRequest req, HttpServletResponse resp) | def doHead(self, req, res): |
| doOptions(HttpServletRequest req, HttpServletResponse resp) | def doOptions(self, req, res): |
| doPost(HttpServletRequest req, HttpServletResponse resp) | def doPost(self, req, res): |
| doPut(HttpServletRequest req, HttpServletResponse resp) | def doPut(self, req, res): |
| doTrace(HttpServletRequest req, HttpServletResponse resp) | def doTrace(self, req, res): |
| getLastModified(HttpServletRequest req) | def getLastModified(self, req): |
| service(HttpServletRequest req, HttpServletResponse resp) | def service(self, req, res): |
| service(ServletRequest req, ServletResponse res) | def service(self, req, res): |

5.2 HttpServlet示例

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

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

get_post.py 文件放在 $TOMCAT_HOME/webapps/jython/WEB - INF/classes 目录下,使用 jythonc -w . --deep get_post.py 编译。在浏览器中访问 http://localhost:8080/jython/servlet/get_post ,可以看到一个页面,点击提交按钮会执行 doPost 方法。

5.3 HttpServletRequest和HttpServletResponse

与客户端连接的通信通过 HttpServletRequest HttpServletResponse 对象进行,它们是对从客户端接收和发送到客户端的请求流的抽象,每个对象都添加了更高级的、特定于HTTP的方法,方便处理请求和响应。以下是 HttpServletRequest 对象的一些方法和属性:
| 方法和属性 | 描述 |
| — | — |
| getAuthType() / AuthType | 返回描述认证类型名称的字符串,如果用户未认证则为 None 。 |
| getContextPath() / contextPath | 返回描述请求上下文的路径信息的字符串。 |
| getCookies() / cookies() | 返回客户端请求发送的所有cookie,作为 javax.servlet.http.Cookie 对象的数组。 |
| getDateHeader(name) | 以长类型检索指定头的值。 |
| getHeader(name) | 以字符串形式返回指定头的值。 |
| getHeaderNames() / headerNames | 返回请求中包含的所有头名称的枚举。 |
| getHeaders(name) | 返回指定头名称的所有值的枚举。 |
| getIntHeader(name) | 以Java整数形式检索指定头的值,Jython将其转换为 PyInteger 。 |
| getMethod() / method | 返回请求类型的字符串。 |
| getPathInfo() / pathInfo | 客户端发送的所有额外路径信息。 |
| getPathTranslated() / pathTranslated | 返回从客户端请求的额外路径信息派生的真实路径。 |
| getQueryString() / queryString | 返回客户端请求的查询字符串(路径后面的字符串)。 |
| getRemoteUser() / remoteUser | 返回客户端的登录名,如果客户端未认证则为 None 。 |
| getRequestedSessionId() / requestedSessionId | 返回客户端的会话ID。 |
| getRequestURI() / requestURI | 返回协议名称和查询字符串之间的部分。 |
| getServletPath() / servletPath | 返回指定当前Servlet的URL部分。 |
| getSession() / session | 返回当前会话,如果需要则创建一个新会话。 |
| getSession(create) | 如果存在则返回当前会话,如果 create 值为 true ,则创建一个新会话。 |
| getUserPrincipal() / userPrincipal | 返回包含当前认证信息的 java.security.Principal 对象。 |
| isRequestedSessionIdFromCookie() | 根据当前会话ID是否来自cookie返回1或0。 |
| isRequestedSessionIdFromURL() | 根据当前会话ID是否来自请求的URL字符串返回1或0。 |
| isRequestedSessionIdValid() | 根据请求的会话ID是否仍然有效返回1或0。 |
| isUserInRole(role) | 指示用户是否在指定角色中返回1或0。 |

HttpServletResponse 对象用于将mime编码的流发送回客户端,它定义了一些在通用 ServletResponse 对象中不存在的特定于HTTP的方法,以下是一些方法和属性:
| 方法和属性 | 描述 |
| — | — |
| addCookie(cookie) | 向响应中添加一个cookie。 |
| addDateHeader(headerName, date) | 添加一个带有日期(长类型)值的头。 |
| addHeader(headerName, value) | 添加一个头名称和值。 |
| addIntHeader(headerName, value) | 添加一个带有整数值的头。 |
| containsHeader(headerName) | 根据指定的头是否存在返回1或0。 |
| encodeRedirectUrl(url) | 为 sendRedirect 方法编码URL(2.1及更高版本使用 encodeRedirectURL )。 |
| encodeRedirectURL(url) | 为 sendRedirect 方法编码URL。 |
| encodeURL(url) | 通过包含会话ID对URL进行编码。 |
| sendError(sc) | 使用状态码发送错误。 |
| sendError(sc, msg) | 使用指定的状态码和消息发送错误。 |
| sendRedirect(location) | 发送临时重定向到指定位置。 |
| setDateHeader(headerName,date) | 将头名称设置为指定的日期(长类型)值。 |
| setHeader(headerName,value) | 将头名称设置为指定的值。 |
| setIntHeader(headerName, value) | 将头名称设置为指定的整数值。 |
| setStatus(statusCode) / status | 设置响应状态码。 |

HttpServletResponse 类还包含与标准HTTP响应代码对应的字段,可以与 sendError(int) setStatus(int) 一起使用,以下是一些状态码:
| 错误代码 | 状态 |
| — | — |
| 100 | SC_CONTINUE |
| 101 | SC_SWITCHING_PROTOCOLS |
| 200 | SC_OK |
| 201 | SC_CONTINUE |
| 202 | SC_ACCEPTED |
| 203 | SC_NON_AUTHORITATIVE_INFORMATION |
| 204 | SC_NO_CONTENT |
| 205 | SC_RESET_CONTENT |
| 206 | SC_PARTIAL_CONTENT |
| 300 | SC_MULTIPLE_CHOICES |
| 301 | SC_MOVED_PERMANENTLY |
| 302 | SC_MOVED_TEMPORARILY |
| 303 | SC_SEE_OTHER |
| 304 | SC_NOT_MODIFIED |
| 305 | SC_USE_PROXY |
| 400 | SC_BAD_REQUEST |
| 401 | SC_UNAUTHORIZED |
| 402 | SC_PAYMENT_REQUIRED |
| 403 | SC_FORBIDDEN |
| 404 | SC_NOT_FOUND |
| 405 | SC_METHOD_NOT_ALLOWED |
| 406 | SC_NOT_ACCEPTABLE |
| 407 | SC_PROXY_AUTHENTICATION_REQUIRED |
| 408 | SC_REQUEST_TIMEOUT |
| 409 | SC_CONFLICT |
| 410 | SC_GONE |
| 411 | SC_LENGTH_REQUIRED |
| 412 | SC_PRECONDITION_FAILED |
| 413 | SC_REQUEST_ENTITY_TOO_LARGE |
| 414 | SC_REQUEST_URI_TOO_LONG |
| 415 | SC_UNSUPPORTED_MEDIA_TYPE |
| 500 | SC_INTERNAL_SERVER_ERROR |
| 501 | SC_NOT_IMPLEMENTED |
| 502 | SC_BAD_GATEWAY |
| 503 | SC_SERVICE_UNAVAILABLE |
| 504 | SC_GATEWAY_TIMEOUT |
| 505 | SC_HTTP_VERSION_NOT_SUPPORTED |

6. PyServlet

对于Jython程序员来说,使用 HttpServlet 编程的一个很大优势是Jython发行版自带了 org.python.util.PyServlet 。这个Servlet可以加载、执行并缓存Jython文件,这样就可以直接编写和查看Jython Servlet,而无需中间的编译步骤,这通过Servlet映射来实现。Servlet映射是特定Servlet(这里是 PyServlet )与特定URL模式(如 *.py )之间的关联。通过合适的 web.xml 文件,Jython的 PyServlet 类会映射到所有 *.py 文件,当对 *.py 文件的请求到来时, PyServlet 类会根据需要加载、缓存并调用 *.py 文件中的方法进行响应。

6.1 限制条件

PyServlet 的设计对它所服务的Jython文件有一些限制:
- Jython文件必须包含一个继承自 javax.servlet.http.HttpServlet 的类。
- 该类的名称必须与文件名(去掉 .py 扩展名)匹配。
- 除了继承自 HttpServlet 的那个类之外,使用模块全局标识符是不安全的。

6.2 安装PyServlet

安装 PyServlet 只需要定义一个Servlet映射,并确保 jython.jar 文件在上下文的 lib 目录中。Servlet映射在上下文的部署描述符文件 web.xml 中定义,该文件位于 $TOMCAT_HOME/webapps/jython/WEB - INF/web.xml 。以下是一个示例 web.xml 文件:

<web-app> 
    <servlet> 
        <servlet-name>PyServlet</servlet-name> 
        <servlet-class> 
            org.python.util.PyServlet 
        </servlet-class> 
        <load-on-startup>1</load-on-startup> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>PyServlet</servlet-name> 
        <url-pattern>*.py</url-pattern> 
    </servlet-mapping> 
</web-app> 

PyServlet 的Servlet定义可以可选地包含初始化参数( init-params ),用于设置Jython的属性,如 python.home python.path 等。以下是一个包含初始化参数的 web.xml 示例:

<web-app> 
    <servlet> 
        <servlet-name>PyServlet</servlet-name> 
        <servlet-class> 
            org.python.util.PyServlet 
        </servlet-class> 
        <load-on-startup>1</load-on-startup> 
        <init-param> 
            <param-name>python.home</param-name> 
            <param-value>c:\jython-2.1</param-value> 
        </init-param> 
        <init-param> 
            <param-name>python.path</param-name> 
            <param-value> 
                c:\jython-2.1\lib\site-packages 
            </param-value> 
        </init-param> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>PyServlet</servlet-name> 
        <url-pattern>*.py</url-pattern> 
    </servlet-mapping> 
</web-app> 

PyServlet 的默认 python.home 值是上下文的 lib 目录,这使得上下文既自包含又具有平台无关性。

6.3 测试PyServlet

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

# File ServletMappingTest.py 
from javax.servlet.http import HttpServlet 
class ServletMappingTest(HttpServlet): 
    def doGet(self, req, res): 
        out = res.writer 
        res.contentType = "text/html" 
        print >> out, "Greetings from a jylet." 

将这个测试文件保存为 %TOMCAT_HOME%\webapps\jython\ServletMappingTest.py ,然后在浏览器中访问 http://localhost:8080/jython/ServletMappingTest.py 。如果看到问候消息,则说明 PyServlet 映射正确。为了完成安装,还需要创建Jython的 lib 目录 %TOMCAT_HOME%\webapps\jython\WEB - INF\lib\Lib ,并将Jython模块放在该目录中。

7. Cookies

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

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

添加cookie应该在通过响应流发送其他内容之前进行。每个cookie实例都有一些方法或Jython的自动bean属性,可以设置以下属性:
- comment
- domain
- maxAge
- name
- path
- secure
- value
- version

以下是一个使用cookie对象的自动bean属性创建和读取cookie的示例:

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

8. Sessions

Cookies是与客户端创建会话的最常见方式,会话是通过一系列请求跟踪客户端信息的一种手段。虽然前面的cookie示例可以用于存储会话ID,但Java的 HttpSession 类使会话跟踪更加容易。

8.1 创建会话

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

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

8.2 注意事项

由于cookie可能存储会话值,因此应该在向输出流发送其他数据之前使用 req.session 属性或 req.getSession() 方法。如果客户端禁用了cookie,会话对象仍然可以正常工作,因为所有URL都会通过 res.encodeUrl() 方法重写,以支持无cookie会话。

session.py 文件保存到 %TOMCAT_HOME%\webapps\jython 目录中,然后在浏览器中访问 http://localhost:8080/jython/session.py 。可以使用网页表单向会话中添加一些变量来确认会话是否正常工作,甚至可以尝试禁用cookie,看看URL重写是如何工作的。

9. Databases and Servlets

在Jython Servlet中连接数据库、执行语句和遍历结果集与其他Jython数据库应用程序没有区别,但管理连接和其他数据库资源是一个主要问题,因为许多Web应用程序包含大量使用数据库内容的Jython Servlet。

9.1 资源管理方法

有两种主要的数据库资源管理方法:
- 每个Servlet一个连接 :在Servlet的 init 方法中建立数据库连接,只有在Servlet卸载时才关闭该连接。这种方法虽然流行,但资源消耗较大,只在所需连接数量在资源限制范围内时才合理。
- 连接池 :维护一定数量的活动数据库连接,Jython Servlet在响应客户端请求时从连接池中借用连接,完成后归还。这种方法可以实现更合理的资源管理,消除连接开销。

9.2 示例代码

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

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

这个示例使用了 zxJDBC 包和MySQL数据库,需要将MySQL和 zxJDBC 所需的类文件放在上下文的 lib 目录中,如 mm_mysql-2_0_4-bin.jar zxJDBC.jar 。创建数据库和表的SQL语句如下:

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

创建数据库和表后,向表中插入一些任意值,并将 DBDisplay.py 文件放在上下文的根目录中,然后在浏览器中访问 http://localhost:8080/jython/DBDisplay.py ,应该可以看到数据库中的数据。

如果Web应用程序开始使用过多的连接,可以考虑使用连接池。一个流行、免费、经过测试且文档完善的连接池工具是 PoolMan ,其他Java连接池包也应该可以与Jython无缝配合使用。

10. JSP

Java Server Pages(JSP)是Sun公司倡导的一种与Servlet互补的模板系统。JSP文件包含网页的标记代码和文本,同时还包含指定动态内容的特殊标签。目前,Tomcat只支持在JSP中使用Java语言,那么如何在JSP中使用Jython呢?目前有以下几种方法:
- 使用 jythonc 编译的类。
- 使用嵌入式Python解释器。
- 创建特定于Jython的自定义标签库。

10.1 jythonc - Compiled Classes and JSP

使用 jythonc 编译的类与JSP配合使用,需要创建一个与Java兼容的Jython类。这个类要满足以下条件:
- 类名与模块名相同。
- 继承自Java类。
- 为每个非从超类派生的方法包含 @sig 字符串。

jythonc 生成的类文件放在上下文的 classes 目录中,JSP页面就可以像使用任何原生Java类一样使用这些类。当然,这也需要将 jython.jar 文件放在上下文的 lib 目录中。

JSP文件可以通过两种方式使用 jythonc 编译的类:
- 作为脚本片段 :可以使用任何类。通过 <%@ page import="fully.qualified.path.to.class" %> 导入非bean类。
- 作为bean :Jython类必须符合bean约定,为每个读写属性包含 getProperty setProperty 方法。使用 <jsp:useBean> 标签加载bean。

以下是一个简单的Jython bean示例:

# file: NameHandler.py 
import java 
class NameHandler(java.lang.Object): 
    def __init__(self): 
        self.name = "Fred" 
    def getUsername(self): 
        "@sig public String getname()" 
        return self.name 
    def setUsername(self, name): 
        "@sig public void setname(java.lang.String name)" 
        self.name = name 

NameHandler.py 文件放在 %TOMCAT_HOME%\webapps\jython\WEB - INF\classes 目录中,并使用 jythonc -w . Namehandler.py 编译。以下是一个使用这个Jython bean的JSP页面示例:

<!--file: name.jsp --> 
<%@ page contentType="text/html" %> 
<jsp:useBean name="beanName" 
             class="NameHandler" 
             scope="session"/> 
<html> 
<head> 
  <title>hello</title> 
</head> 
<body bgcolor="white"> 
Hello, my name is 
<jsp:getProperty name="beanName" property="username"/> 
<br> 
No, wait... 
<jsp:setProperty name="beanName" property="username" value="Robert"/> 
, It's really <%= beanName.getUsername() %>. 
</body> 
</html> 

name.jsp 文件放在上下文的根目录中,然后在浏览器中访问 http://localhost:8080/jython/name.jsp ,应该可以看到默认名称 Fred 和修改后的名称 Robert

10.2 Embedding a PythonInterpreter in JSP

如果想在JSP脚本片段中使用Jython代码,可以通过嵌入式 PythonInterpreter 间接实现。以下是一个简单的JSP页面示例:

<!--name: interp.jsp--> 
<%@ page contentType="text/html" %> 
<%@ page import="org.python.util.PythonInterpreter" %> 
<% PythonInterpreter interp = new PythonInterpreter(); 
   interp.set("out", out); %> 
<html> 
<body bgcolor="white"> 
<% interp.exec("out.println('Hello from JSP and the Jython interpreter.')"); %> 
</body> 
</html> 

要使用这个 interp.jsp 文件,确保 jython.jar 文件在上下文的 lib 目录中,将 interp.jsp 文件放在上下文的根目录中,然后在浏览器中访问 http://localhost:8080/jython/interp.jsp ,应该可以看到Jython解释器输出的简单消息。不过,很多人认为脚本片段不是好的做法,在脚本片段中从Java使用Jython会增加复杂度,有更好的方法来创建动态内容,如bean类和标签库。

10.3 A Jython Taglib

可以使用 jythonc 将Jython标签库模块编译成Java类,从而在JSP页面中创建自定义标签库。Jython标签库模块必须满足以下限制才能像Java类一样透明地工作:
- 模块必须包含一个与模块名(去掉 .py 扩展名)相同的类。
- 该类必须继承自 javax.servlet.jsp.tagext.Tag 接口或实现该接口的Java类。
- 编译后的类文件必须能够访问 org.python.* 包和类以及Jython库模块(如果标签库导入了这些模块)。

为了使标签库能够访问所有必需的类和库,可以使用 jythonc --all 选项进行编译,就像之前编译Servlet一样。但更好的方法是将 jython.jar 文件放在上下文的 lib 目录中,在上下文中建立一个Jython的 lib 目录(推荐 {context}\WEB - INF\lib\Lib ),然后编译Jython标签库模块而不考虑依赖关系。

以下是一个简单的Jython标签库示例:

# file: JythonTag.py 
from javax.servlet.jsp import tagext 
class JythonTag(tagext.Tag): 
    def __init__(self): 
        self.context = None 
        self.paren = None 
    def doStartTag(self): 
        return tagext.Tag.SKIP_BODY 
    def doEndTag(self): 
        out = self.context.out 
        print >>out, "Message from a taglib" 
        return tagext.Tag.EVAL_PAGE 
    def release(self): 
        pass 
    def setPageContext(self, context): 
        self.context = context 
    def setParent(self, parent): 
        self.paren = parent 
    def getParent(self): 
        return self.paren 

需要注意的是,保存页面上下文和父标签信息的实例变量( self.context self.paren )可能会导致循环引用和 StackOverflowExceptions ,因为 Tag 接口要求实现 setPageContext() setParent() getParent() 方法,Jython会为这些方法的相关属性名创建自动bean属性。

要安装这个标签库所需的类,首先确保 jython.jar 文件在上下文的 lib 目录中,然后使用以下命令编译 JythonTag.py 文件:

jythonc -w %TOMCAT_HOME%\webapps\jython\WEB - INF\classes JythonTag.py 

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

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

将这个标签库描述信息保存为 %TOMCAT_HOME%\webapps\jython\WEB - XML\jython-taglibs.tld ,JSP页面将使用这个文件来识别页面中使用的标签。

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

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

将这个 test.jsp 文件保存为 %TOMCAT_HOME%\webapps\jython\test.jsp ,然后在浏览器中访问 http://localhost:8080/jython/test.jsp ,应该可以看到自定义标签输出的消息。

使用编译后的Jython模块实现标签库会再次引发Jython的主目录和 lib 目录的问题。 jythonc 编译的标签库不知道 python.home 在哪里,也不知道在哪里找到库模块,除非在 PySystemState 中设置这些信息。可以通过设置 TOMCAT_OPTS 环境变量来设置 python.home 属性,也可以利用 PyServlet 类来建立系统状态信息。如果上下文中加载了 PyServlet 类,那么 python.home sys.path 信息可能已经为需要的标签库设置正确。在默认的 PyServlet 设置下, python.home %TOMCAT_HOME%\webapps\jython\WEB - INF\lib ,因此Jython的 lib 目录是 %TOMCAT_HOME%\webapps\jython\WEB - INF\lib\Lib 。加载 PyServlet 后,标签库可以从同一个 lib 目录中加载Jython模块。

10.4 BSF

IBM的Bean Scripting Framework(BSF)是一个实现各种脚本语言的Java工具,Jython是目前BSF支持的语言之一。Apache Jakarta项目包含一个名为 taglibs 的子项目,这是一个广泛的Java标签库集合,其中有趣的是BSF标签库。BSF标签库与BSF的jar文件结合使用,可以让你直接在JSP页面中插入Jython脚本片段和表达式。

10.4.1 安装步骤
  • 从相关网站下载正确的 bsf.jar 文件,并将其放在上下文的 lib 目录中( %TOMCAT_HOME%\webapps\jython\WEB - INF\lib )。
  • 下载BSF标签库的jar文件和 bsf.tld 文件,将标签库的jar文件放在上下文的 lib 目录中,将 bsf.tld 文件放在上下文的 WEB - INF 目录中( %TOMCAT_HOME%\webapps\jython\WEB - INF\bsf.tld )。
10.4.2 使用方法

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

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

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

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

脚本片段在解释器中自动设置了一些JSP对象,如下表所示:
| 对象 | Java类名 |
| — | — |
| request | javax.servlet.http.HttpServletRequest |
| response | javax.servlet.http.HttpServletResponse |
| pageContext | javax.servlet.jsp.PageContext |
| application | javax.servlet.ServletContext |
| out | javax.servlet.jsp.JspWriter |
| config | javax.servlet.ServletConfig |
| page | java.lang.Object |
| exception | java.lang.Exception |
| session | javax.servlet.http.HttpSession |

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

<%@ taglib uri="/WEB-INF/bsf.tld" prefix="bsf" %> 
<html> 
<body> 
<center><H2>BSF scriptlets</H2></center> 
<b>client info:</b><br> 
<bsf:scriptlet language="jython"> 
for x in request.headerNames: 
    print >>out, "%s: &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开发带来更高的灵活性和开发效率。无论是处理Cookies、管理会话、连接数据库,还是在JSP中使用Jython代码,都有相应的方法和技巧。在实际开发中,可以根据具体需求选择合适的方法来实现功能。

内容概要:本文围绕六自由度机械臂的人工神经网络(ANN)设计展开,重点研究了正向与逆向运动学求解、正向动力学控制以及基于拉格朗日-欧拉法推导逆向动力学方程,并通过Matlab代码实现相关算法。文章结合理论推导与仿真实践,利用人工神经网络对复杂的非线性关系进行建模与逼近,提升机械臂运动控制的精度与效率。同时涵盖了路径规划中的RRT算法与B样条优化方法,形成从运动学到动力学再到轨迹优化的完整技术链条。; 适合人群:具备一定机器人学、自动控制理论基础,熟悉Matlab编程,从事智能控制、机器人控制、运动学六自由度机械臂ANN人工神经网络设计:正向逆向运动学求解、正向动力学控制、拉格朗日-欧拉法推导逆向动力学方程(Matlab代码实现)建模等相关方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握机械臂正/逆运动学的数学建模与ANN求解方法;②理解拉格朗日-欧拉法在动力学建模中的应用;③实现基于神经网络的动力学补偿与高精度轨迹跟踪控制;④结合RRT与B样条完成平滑路径规划与优化。; 阅读建议:建议读者结合Matlab代码动手实践,先从运动学建模入手,逐步深入动力学分析与神经网络训练,注重理论推导与仿真实验的结合,以充分理解机械臂控制系统的设计流程与优化策略。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值