[size=medium]Parsing Headers [/size]
一个HttpHeader类代表了一个HTTP的头部信息。这个类将在第四章讲解。现在,我们了解以下内容就足够:
你可以通过使用它的无参构造函数来创建一个HttpHeader实例。
一旦你有一个HttpHeader实例,你可以把它传递给SocketInputStream的readHeader方法。如果有头部信息可读的话,readHeader方法将相应地填充好HttpHeader对象。如果没有头部信息可读,HttpHeader实例的nameEnd和valueEnd字段都将为0。
获取头部信息的名字和值,使用下面的方法:
String name = new String (header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
parseHeader方法包含一个while循环持续从SocketInputStream读取头部信息,直到读取完所有的头部信息。这个循环在创建一个HttpHeader实例,并把它传递给SocketInputStream类的readHeader后开始执行:
HttpHeader header = new HttpHeader();
// Read the next header
input.readHeader(header);
然后,你可以测试在输入流中是否有下一个头部信息可读。使用HttpHeader实例的nameEnd和valueEnd字段:
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
} else {
throw new ServletException (sm.getString("httpProcessor.parseHeaders.colon"));
}
}
如果有下一个头部信息,这个头部信息的name和value可以被获取:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
一旦你得到了头部name和value。你调用addHeader方法把它添加到HttpRequest对象中的头部信息HashMap中。
request.addHeader(name, value);
一些头部信息也需要设置一些属性。例如:当servlet调用javax.servlet.ServletRequest的getContentLength方法,content-length头部信息的值被返回,cookie头部信息包含cookies被添加到cookie集合中。下面是处理过程:
[size=medium]Parsing Cookies[/size]
Cookies被浏览器作为HTTP请求头部信息发送。这样的一个头部信息有一个name为“cookie”和value为cookie值的name/value键值对。下面有一个例子:
Cookie: userName=budi; password=pwd;
Cookie的解析是通过使用org.apache.catalina.util.RequestUtil类的parseCookieHeader方法完成的。这个方法接收cookie头部信息和返回一个javax.servlet.http.Cookie类型的数组。这个数组中元素的个数和cookie的name/value键值对的个数是相等的。
[size=medium]Obtaining Parameters[/size]
在你的servlet需要调用javax.servlet.http.HttpServletRequest的getParameter,getParameterMap getParameterNames, 或者getParameterValues方法来获取一个或者全部参数之前,你并没有解析查询字符串或者HTTP请求体来获取参数。因此,HttpRequest中四个方法的实现总是以调用parseParameter方法开始。
这些参数只需要被解析一次,也只可能被解析一次,因为如果这些参数在请求体中被找到话,参数解析让SocketInputStream读取字节流,一直读取到字节流末端。HttpRequest类使用一个叫做parsed的boolean类型值的变量来表明这个参数是否被解析过。
参数可以在查询字符串或者请求体中找到。如果用户请求一个servlet使用GET方法,所有参数在查询字符串中。如果使用POST方法的话,你也可以在请求体中找到。所有的name/value键值对都被存储在一个HashMap中。Servlet程序员可以像使用一个Map一样获得参数。注意,servlet程序员不允许改变参数值。所以这里使用了一个特殊的HashMap:org.apache.catalina.util.ParameterMap.
这个ParameterMap类,继承自java.util.HashMap,使用一个叫locked的boolean类型的变量。如果locked的值为false的时候,name/value键值对只能被添加,更新或移除。如果locked的值为true是,在执行以上name/value键值对操作时,会抛出一个IllegalStateException异常。你可以在任何时候读取里面的值。ParameterMap类在下面给出,它重写了里面的方法来添加,更新和移除值。这些方法只有在locked变量值为false时才可以调用。
现在我们来看看parseParameters方法是怎么工作的。
因为参数可以存在于查询字符串或者HTTP请求体中,parseParameters方法会检查查询字符串和请求体。一旦解析后,参数就可以在对象变量参数中被找到,所以这方法是通过检查parsed的boolean值:这值是true的话说明已经被解析过了。
if (parsed)
return;
然后,parseParameters方法创建一个叫做results的ParameterMap,把results指向参数。如果参数是null的话,就会创建一个新的ParameterMap。
ParameterMap results = parameters;
if (results == null)
results = new ParameterMap();
然后。parseParameters方法打开parameterMap的锁来允许操作这个parameterMap:
results.setLocked(false);
接下来,parseParameters方法检查编码格式,如果编码格式是null,则制定一个默认的格式。
String encoding = getCharacterEncoding();
if (encoding == null)
encoding = "ISO-8859-1";
之后,parseParameters方法解析查询字符串。通过使用org.apache.Catalina.util.RequestUtil的parseParameters方法完成解析参数。
// Parse any parameters specified in the query string
String queryString = getQueryString();
try {
RequestUtil.parseParameters(results, queryString, encoding);
} catch (UnsupportedEncodingException e) {
;
}
接下来。如果用户使用POST方法发送请求时,这方法就会试图查看HTTP请求体中是否包含参数。内容长度大于0,内容类型是application/x-www-form-urlencoded。下面是解析请求体的代码:
最后,parseParameters方法锁住ParameterMap。设置parsed为true,分配results给参数。
// Store the final results
results.setLocked(true);
parsed = true;
parameters = results;
Creating a HttpResponse Object
HttpResponse类实现了javax.servlet.http.HttpServletResponse接口。
有一个叫做HttpResponseFacade 的facade类。你使用的HttpResponse类只有一部分功能,例如:它的getWriter方法返回一个java.io.PrintWriter对象,但是当它的print方法被调用时不会自动刷新。在这章的应用程序中修正这个问题。要知道怎么修正,你需要知道这个Writer是怎样的。
在一个servlet内,你使用PrintWriter来写字符。你可以使用你想要的任何编码格式。字符将会以字节流的形式发送到浏览器。第二章ex02.pyrmont.HttpResponse类的getWriter方法:
public PrintWriter getWriter() {
// if autoflush is true, println() will flush,
// but print() will not.
// the output argument is an OutputStream
writer = new PrintWriter(output, true);
return writer;
}
看,我们怎么创建一个PrintWriter对象?通过传递一个java.io.OutputStream实例。你传递到PrintWriter 的print或println方法的任何内容将会被转换成字节流,然后通过底层的(underlying)OutputStream发送出去。
在这章你可以使用一个ex03.pyrmont.connector.ResponseStream类的实例作为OutputStream给PrintWriter。注意ResponseStream类是间接地从java.io.OutputStream类中获取。
你也可以让ex03.pyrmont.connector.ResponseWriter类继承PrintWriter类。ResponseWriter类重写所有的print和println方法,让所有调用这些方法都自动刷新output到底层OutputStream。所以,我们使用一个带有底层的ResponseStream 对象的ResponseWriter实例。
我们可以通过传递一个ResponseStream对象的实例来初始化ResponseWriter类。但是,我们使用一个java.io.OutputStreamWriter对象来服务,就像是当做一个连接ResponseWriter对象和ResponseStream对象的桥。
有了OutputStreamWriter,字符被指定的字符集编码成字节。字符集可以用名字来指定,或者可能明确的给出,或是平台的默认许可的字符集。每一个write方法的调用都会引起编码转换,转换成指定的字符。这些所有被处理后的字节在写到底层输出流之前都在一个buffer中存储起来。buffer的大小可以被指定,但是默认的大小已经适合大多数的情况了。注意:传递给write方法的字符没有被缓冲(buffered)。下面是getWriter方法:
public PrintWriter getWriter() throws IOException {
ResponseStream newStream = new ResponseStream(this);
newStream.setCommit(false);
OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
writer = new ResponseWriter(osr);
return writer;
}
[size=medium]Static Resource Processor and Servlet Processor[/size]
ServletProcessor类和第二章的ex02.pyrmont.ServletProcessor类相似。他们都只有一个方法:process。但是,ex03.pyrmont.connector.ServletProcessor中的process方法接收一个HttpRequest和一个HttpResponse代替了Request和Response实例。下面是process方法:
public void process(HttpRequest request, HttpResponse response) {
此外,process方法使用HttpRequestFacade和HttpResponseFacade的facade类。同样的,它调用servlet的service方法之后调用HttpResponse类的finishResponse方法。
servlet = (Servlet) myClass.newInstance();
HttpRequestFacade requestPacade = new HttpRequestFacade(request);
HttpResponseFacade responseFacade = new HttpResponseFacade(response);
servlet.service(requestFacade, responseFacade);
((HttpResponse) response).finishResponse();
StaticResourceProcessor类和ex02.pyrmont.StaticResourceProcessor类完全一样。
[size=medium]Running the Application[/size]
运行这个应用程序。http://localhost:8080/servlet/PrimitiveServlet
你可以看到结果:
Hello. Roses are red.
Violets are blue.
注意:第二章中没有打印出第二行的信息Violets are blue.
第三章 完
一个HttpHeader类代表了一个HTTP的头部信息。这个类将在第四章讲解。现在,我们了解以下内容就足够:
你可以通过使用它的无参构造函数来创建一个HttpHeader实例。
一旦你有一个HttpHeader实例,你可以把它传递给SocketInputStream的readHeader方法。如果有头部信息可读的话,readHeader方法将相应地填充好HttpHeader对象。如果没有头部信息可读,HttpHeader实例的nameEnd和valueEnd字段都将为0。
获取头部信息的名字和值,使用下面的方法:
String name = new String (header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
parseHeader方法包含一个while循环持续从SocketInputStream读取头部信息,直到读取完所有的头部信息。这个循环在创建一个HttpHeader实例,并把它传递给SocketInputStream类的readHeader后开始执行:
HttpHeader header = new HttpHeader();
// Read the next header
input.readHeader(header);
然后,你可以测试在输入流中是否有下一个头部信息可读。使用HttpHeader实例的nameEnd和valueEnd字段:
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
} else {
throw new ServletException (sm.getString("httpProcessor.parseHeaders.colon"));
}
}
如果有下一个头部信息,这个头部信息的name和value可以被获取:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
一旦你得到了头部name和value。你调用addHeader方法把它添加到HttpRequest对象中的头部信息HashMap中。
request.addHeader(name, value);
一些头部信息也需要设置一些属性。例如:当servlet调用javax.servlet.ServletRequest的getContentLength方法,content-length头部信息的值被返回,cookie头部信息包含cookies被添加到cookie集合中。下面是处理过程:
if (name.equals("cookie")) {
... // process cookies here
}
else if (name.equals("content-length")) {
int n = -1;
try {
n = Integer.parseInt (value);
}
catch (Exception e) {
throw new ServletException(sm.getString(
"httpProcessor.parseHeaders.contentLength"));
}
request.setContentLength(n);
}
else if (name.equals("content-type")) {
request.setContentType(value);
}
[size=medium]Parsing Cookies[/size]
Cookies被浏览器作为HTTP请求头部信息发送。这样的一个头部信息有一个name为“cookie”和value为cookie值的name/value键值对。下面有一个例子:
Cookie: userName=budi; password=pwd;
Cookie的解析是通过使用org.apache.catalina.util.RequestUtil类的parseCookieHeader方法完成的。这个方法接收cookie头部信息和返回一个javax.servlet.http.Cookie类型的数组。这个数组中元素的个数和cookie的name/value键值对的个数是相等的。
Listing 3.5: The org.apache.catalina.util.RequestUtil class's parseCookieHeader method
public static Cookie[] parseCookieHeader(String header) {
if ((header == null) || (header.length 0 < 1) )
return (new Cookie[0]);
ArrayList cookies = new ArrayList();
while (header.length() > 0) {
int semicolon = header.indexOf(';');
if (semicolon < 0)
semicolon = header.length();
if (semicolon == 0)
break;
String token = header.substring(0, semicolon); if (semicolon < header.length())
header = header.substring(semicolon + 1);
else
header = "";
try {
int equals = token.indexOf('=');
if (equals > 0) {
String name = token.substring(0, equals).trim();
String value = token.substring(equals+1).trim();
cookies.add(new Cookie(name, value));
}
}catch (Throwable e) {
;
}
}
return ((Cookie[]) cookies.toArray (new Cookie [cookies.size ()]));
}
[size=medium]Obtaining Parameters[/size]
在你的servlet需要调用javax.servlet.http.HttpServletRequest的getParameter,getParameterMap getParameterNames, 或者getParameterValues方法来获取一个或者全部参数之前,你并没有解析查询字符串或者HTTP请求体来获取参数。因此,HttpRequest中四个方法的实现总是以调用parseParameter方法开始。
这些参数只需要被解析一次,也只可能被解析一次,因为如果这些参数在请求体中被找到话,参数解析让SocketInputStream读取字节流,一直读取到字节流末端。HttpRequest类使用一个叫做parsed的boolean类型值的变量来表明这个参数是否被解析过。
参数可以在查询字符串或者请求体中找到。如果用户请求一个servlet使用GET方法,所有参数在查询字符串中。如果使用POST方法的话,你也可以在请求体中找到。所有的name/value键值对都被存储在一个HashMap中。Servlet程序员可以像使用一个Map一样获得参数。注意,servlet程序员不允许改变参数值。所以这里使用了一个特殊的HashMap:org.apache.catalina.util.ParameterMap.
这个ParameterMap类,继承自java.util.HashMap,使用一个叫locked的boolean类型的变量。如果locked的值为false的时候,name/value键值对只能被添加,更新或移除。如果locked的值为true是,在执行以上name/value键值对操作时,会抛出一个IllegalStateException异常。你可以在任何时候读取里面的值。ParameterMap类在下面给出,它重写了里面的方法来添加,更新和移除值。这些方法只有在locked变量值为false时才可以调用。
Listing 3.6: The org.apache.Catalina.util.ParameterMap class.
package org.apache.catalina.util;
import java.util.HashMap;
import java.util.Map;
public final class ParameterMap extends HashMap {
public ParameterMap() {
super ();
}
public ParameterMap(int initialCapacity) {
super(initialCapacity); }
public ParameterMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public ParameterMap(Map map) {
super(map);
}
private boolean locked = false;
public boolean isLocked() {
return (this.locked);
}
public void setLocked(boolean locked) {
this.locked = locked;
}
private static final StringManager sm =
StringManager.getManager("org.apache.catalina.util");
public void clear() {
if (locked)
throw new IllegalStateException (sm.getString("parameterMap.locked"));
super.clear();
}
public Object put(Object key, Object value) {
if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
return (super.put(key, value));
}
public void putAll(Map map) {
if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
super.putAll(map);
}
public Object remove(Object key) {
if (locked)
throw new IllegalStateException
(sm.getString("parameterMap.locked"));
return (super.remove(key));
}
}
现在我们来看看parseParameters方法是怎么工作的。
因为参数可以存在于查询字符串或者HTTP请求体中,parseParameters方法会检查查询字符串和请求体。一旦解析后,参数就可以在对象变量参数中被找到,所以这方法是通过检查parsed的boolean值:这值是true的话说明已经被解析过了。
if (parsed)
return;
然后,parseParameters方法创建一个叫做results的ParameterMap,把results指向参数。如果参数是null的话,就会创建一个新的ParameterMap。
ParameterMap results = parameters;
if (results == null)
results = new ParameterMap();
然后。parseParameters方法打开parameterMap的锁来允许操作这个parameterMap:
results.setLocked(false);
接下来,parseParameters方法检查编码格式,如果编码格式是null,则制定一个默认的格式。
String encoding = getCharacterEncoding();
if (encoding == null)
encoding = "ISO-8859-1";
之后,parseParameters方法解析查询字符串。通过使用org.apache.Catalina.util.RequestUtil的parseParameters方法完成解析参数。
// Parse any parameters specified in the query string
String queryString = getQueryString();
try {
RequestUtil.parseParameters(results, queryString, encoding);
} catch (UnsupportedEncodingException e) {
;
}
接下来。如果用户使用POST方法发送请求时,这方法就会试图查看HTTP请求体中是否包含参数。内容长度大于0,内容类型是application/x-www-form-urlencoded。下面是解析请求体的代码:
// Parse any parameters specified in the input stream
String contentType = getContentType();
if (contentType == null)
contentType = "";
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
contentType = contentType.substring (0, semicolon).trim();
}
else {
contentType = contentType.trim();
}
if ("POST".equals(getMethod()) && (getContentLength() > 0)
&& "application/x-www-form-urlencoded".equals(contentType)) {
try {
int max = getContentLength();
int len = 0;
byte buf[] = new byte[getContentLength()];
ServletInputStream is = getInputStream();
while (len < max) {
int next = is.read(buf, len, max - len);
if (next < 0 ) {
break;
}
len += next;
}
is.close();
if (len < max) {
throw new RuntimeException("Content length mismatch");
}
RequestUtil.parseParameters(results, buf, encoding);
}
catch (UnsupportedEncodingException ue) {
;
}
catch (IOException e) {
throw new RuntimeException("Content read fail");
}
}
最后,parseParameters方法锁住ParameterMap。设置parsed为true,分配results给参数。
// Store the final results
results.setLocked(true);
parsed = true;
parameters = results;
Creating a HttpResponse Object
HttpResponse类实现了javax.servlet.http.HttpServletResponse接口。
有一个叫做HttpResponseFacade 的facade类。你使用的HttpResponse类只有一部分功能,例如:它的getWriter方法返回一个java.io.PrintWriter对象,但是当它的print方法被调用时不会自动刷新。在这章的应用程序中修正这个问题。要知道怎么修正,你需要知道这个Writer是怎样的。
在一个servlet内,你使用PrintWriter来写字符。你可以使用你想要的任何编码格式。字符将会以字节流的形式发送到浏览器。第二章ex02.pyrmont.HttpResponse类的getWriter方法:
public PrintWriter getWriter() {
// if autoflush is true, println() will flush,
// but print() will not.
// the output argument is an OutputStream
writer = new PrintWriter(output, true);
return writer;
}
看,我们怎么创建一个PrintWriter对象?通过传递一个java.io.OutputStream实例。你传递到PrintWriter 的print或println方法的任何内容将会被转换成字节流,然后通过底层的(underlying)OutputStream发送出去。
在这章你可以使用一个ex03.pyrmont.connector.ResponseStream类的实例作为OutputStream给PrintWriter。注意ResponseStream类是间接地从java.io.OutputStream类中获取。
你也可以让ex03.pyrmont.connector.ResponseWriter类继承PrintWriter类。ResponseWriter类重写所有的print和println方法,让所有调用这些方法都自动刷新output到底层OutputStream。所以,我们使用一个带有底层的ResponseStream 对象的ResponseWriter实例。
我们可以通过传递一个ResponseStream对象的实例来初始化ResponseWriter类。但是,我们使用一个java.io.OutputStreamWriter对象来服务,就像是当做一个连接ResponseWriter对象和ResponseStream对象的桥。
有了OutputStreamWriter,字符被指定的字符集编码成字节。字符集可以用名字来指定,或者可能明确的给出,或是平台的默认许可的字符集。每一个write方法的调用都会引起编码转换,转换成指定的字符。这些所有被处理后的字节在写到底层输出流之前都在一个buffer中存储起来。buffer的大小可以被指定,但是默认的大小已经适合大多数的情况了。注意:传递给write方法的字符没有被缓冲(buffered)。下面是getWriter方法:
public PrintWriter getWriter() throws IOException {
ResponseStream newStream = new ResponseStream(this);
newStream.setCommit(false);
OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
writer = new ResponseWriter(osr);
return writer;
}
[size=medium]Static Resource Processor and Servlet Processor[/size]
ServletProcessor类和第二章的ex02.pyrmont.ServletProcessor类相似。他们都只有一个方法:process。但是,ex03.pyrmont.connector.ServletProcessor中的process方法接收一个HttpRequest和一个HttpResponse代替了Request和Response实例。下面是process方法:
public void process(HttpRequest request, HttpResponse response) {
此外,process方法使用HttpRequestFacade和HttpResponseFacade的facade类。同样的,它调用servlet的service方法之后调用HttpResponse类的finishResponse方法。
servlet = (Servlet) myClass.newInstance();
HttpRequestFacade requestPacade = new HttpRequestFacade(request);
HttpResponseFacade responseFacade = new HttpResponseFacade(response);
servlet.service(requestFacade, responseFacade);
((HttpResponse) response).finishResponse();
StaticResourceProcessor类和ex02.pyrmont.StaticResourceProcessor类完全一样。
[size=medium]Running the Application[/size]
运行这个应用程序。http://localhost:8080/servlet/PrimitiveServlet
你可以看到结果:
Hello. Roses are red.
Violets are blue.
注意:第二章中没有打印出第二行的信息Violets are blue.
第三章 完