5.6 例子:HTTP参数处理

在服务器收到一个HTTP请求后,服务器需要访问请求中的一些信息。处理图5.1中请求的代码可能需要知道photo_id参数的值。参数可以在请求的第一行指定(图5.1中的photo_id),有时也可以在请求体中指定(图5.1中的commentpriority)。每个参数都有一个名称和一个值。参数的值使用一种特殊的编码,称为URL编码(URL encoding);例如,在图5.1中comment的值中,“+”被用来代表一个空格字符,而“%21”被用来代替“!”。为了处理一个请求,服务器将需要一些参数的值,并希望它们是未编码的形式。

图5.1:HTTP协议中的POST请求由通过TCP套接字发送的文本组成。每个请求都包含一个初始行,一个以空行结尾的头部信息集合,以及一个可选的请求体。初始行包含请求类型(POST用于提交表单数据),表示操作的URL(/comments/create)和可选参数(photo_id的值为246),以及发送方使用的HTTP协议版本。请求头中的每一行都包含一个名称,如Content-Length,然后是其值。对于这个请求而言,请求体包含额外的参数(comment和priority)。

大多数的学生项目在参数处理方面做出了两个很好的选择。首先,他们认识到服务器应用程序并不关心一个参数是在请求行还是在请求体中指定的,所以他们向调用者隐藏了这种区别,并将两个位置的参数合并在一起。其次,他们隐藏了关于URL编码的知识:HTTP解析器在将参数值返回给Web服务器之前会对其进行解码,因此图5.1中的comment参数的值会被返回为“What a cute baby!”,而不是“what+a+cute+baby%21”)。在这两种情况下,信息隐藏都导致使用HTTP模块的代码的API更加简单。

然而,大多数学生使用的返回参数的接口过于浅,这导致他们丧失了信息隐藏的机会。大多数项目使用一个HTTPRequest类型的对象来保存解析后的HTTP请求,而HTTPRequest类只有一个类似下面这样的方法来返回参数:

public Map<String, String> getParams() { 
    return this.params; 
}

该方法返回的不是一个参数,而是一个内部用来存储所有参数的Map的引用。这个方法是浅的,它暴露了HTTPRequest类用来存储参数的内部表示。对该表示法的任何改变都将导致对接口的改变,这将需要对所有调用者进行修改。当实现被修改时,这些变化往往涉及关键数据结构的表示方法的变化(例如,为了提高性能)。因此,尽可能地避免暴露内部数据结构是很重要的。这种方法也给调用者带来了更多的工作:调用者必须首先调用getParams,然后它必须调用另一个方法来从Map中获取特定的参数。最后,调用者必须意识到,他们不应该修改getParams返回的Map,因为这将影响HTTPRequest的内部状态。

下面是一个更好的检索参数值的接口:

public String getParameter(String name) { ... } 
public int getIntParameter(String name) { ... }

getParameter以字符串形式返回参数值。它提供了一个比上面的getParams更深的接口;更重要的是,它隐藏了参数的内部表示。 getIntParameter将一个参数的值从HTTP请求中的字符串形式转换为一个整数(例如,图5.1中的photo_id参数)。这就省去了调用者单独请求字符串到整数的转换,并对调用者隐藏了这一机制。如果需要,可以为其他数据类型定义额外的方法,如getDoubleParameter。(如果所需的参数不存在,或者它不能被转换为所要求的类型,所有这些方法都会抛出异常;在上面的代码中省略了异常声明)。

Last updated