POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
lastName=Franks&firstName=Michael
POST /examples/default.jsp HTTP/1.1
lastName=Franks&firstName=Michael實體內容在一個典型的HTTP請求中能夠很容易的變得更長。
HTTP/1.1 200 OK響應頭部的第一行相似於請求頭部的第一行。第一行告訴你該協議使用HTTP 1.1,請求成功(200=成功),表示一切都運行良好。
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
Content-Length: 112
<html>
<head>
<title>HTTP Response Example</title>
</head>
<body>
Welcome to Brainy Software
</body>
</html>
public Socket (java.lang.String host, int port)在這裏主機是指遠程機器名稱或者IP地址,端口是指遠程應用的端口號。例如,要鏈接yahoo.com的80端口,你須要構造如下的Socket對象:
new Socket ("yahoo.com", 80);一旦你成功建立了一個Socket類的實例,你可使用它來發送和接受字節流。要發送字節流,你首先必須調用Socket類的getOutputStream方法來獲取一個java.io.OutputStream對象。要發送文本到一個遠程應用,你常常要從返回的OutputStream對象中構造一個java.io.PrintWriter對象。要從鏈接的另外一端接受字節流,你能夠調用Socket類的getInputStream方法用來返回一個java.io.InputStream對象。
Socket socket = new Socket("127.0.0.1", "8080");請注意,爲了從web服務器獲取適當的響應,你須要發送一個遵照HTTP協議的HTTP請求。假如你已經閱讀了前面一節超文本傳輸協議(HTTP),你應該可以理解上面代碼提到的HTTP請求。
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(
socket.getOutputStream(), autoflush);
BufferedReader in = new BufferedReader(
new InputStreamReader( socket.getInputstream() ));
// send an HTTP request to the web server
out.println("GET /index.jsp HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
// read the response
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
while (loop) {
if ( in.ready() ) {
int i=0;
while (i!=-1) {
i = in.read();
sb.append((char) i);
}
loop = false;
}
Thread.currentThread().sleep(50);
}
// display the response to the out console
System.out.println(sb.toString());
socket.close();
public ServerSocket(int port, int backLog, InetAddress bindingAddress);對於這個構造方法,綁定地址必須是java.net.InetAddress的一個實例。一種構造InetAddress對象的簡單的方法是調用它的靜態方法getByName,傳入一個包含主機名稱的字符串,就像下面的代碼同樣。
InetAddress.getByName("127.0.0.1");下面一行代碼構造了一個監聽的本地機器8080端口的ServerSocket,它的backlog爲1。
new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));一旦你有一個ServerSocket實例,你可讓它在綁定地址和服務器套接字正在監聽的端口上等待傳入的鏈接請求。你能夠經過調用ServerSocket類的accept方法作到這點。這個方法只會在有鏈接請求時纔會返回,而且返回值是一個Socket類的實例。Socket對象接下去能夠發送字節流並從客戶端應用中接受字節流,就像前一節"Socket類"解釋的那樣。實際上,這章附帶的程序中,accept方法是惟一用到的方法。
package ex01.pyrmont;Listing 1.2: HttpServer類的await方法
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.File;
public class HttpServer {
/** WEB_ROOT is the directory where our HTML and other files reside.
* For this package, WEB_ROOT is the "webroot" directory under the
* working directory.
* The working directory is the location in the file system
* from where the java command was invoked.
*/
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
// shutdown command
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
// the shutdown command received
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
public void await() {
...
}
}
public void await() {web服務器能提供公共靜態final變量WEB_ROOT所在的目錄和它下面全部的子目錄下的靜態資源。以下所示,WEB_ROOT被初始化:
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1,
InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace ();
continue;
}
}
}
public static final String WEB_ROOT =代碼列表包括一個叫webroot的目錄,包含了一些你能夠用來測試這個應用程序的靜態資源。你一樣能夠在相同的目錄下找到幾個servlet用於測試下一章的應用程序。爲了請求一個靜態資源,在你的瀏覽器的地址欄或者網址框裏邊敲入如下的URL:
System.getProperty("user.dir") + File.separator + "webroot";
http://machineName:port/staticResource若是你要從一個不一樣的機器上發送請求到你的應用程序正在運行的機器上,machineName應該是正在運行應用程序的機器的名稱或者IP地址。假如你的瀏覽器在同一臺機器上,你可使用localhost做爲machineName。端口是8080,staticResource是你須要請求的文件的名稱,且必須位於WEB_ROOT裏邊。
http://localhost:8080/index.html要中止服務器,你能夠在web瀏覽器的地址欄或者網址框裏邊敲入預約義字符串,就在URL的host:port的後面,發送一個shutdown命令。shutdown命令是在HttpServer類的靜態final變量SHUTDOWN裏邊定義的:
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";所以,要中止服務器,使用下面的URL:
http://localhost:8080/SHUTDOWN如今咱們來看看Listing 1.2印出來的await方法。
serverSocket = new ServerSocket(port, 1,while循環裏邊的代碼運行到ServletSocket的accept方法停了下來,只會在8080端口接收到一個HTTP請求的時候才返回:
InetAddress.getByName("127.0.0.1"));
...
// Loop waiting for a request
while (!shutdown) {
...
}
socket = serverSocket.accept();接收到請求以後,await方法從accept方法返回的Socket實例中取得java.io.InputStream和java.io.OutputStream對象。
input = socket.getInputStream();await方法接下去建立一個ex01.pyrmont.Request對象而且調用它的parse方法去解析HTTP請求的原始數據。
output = socket.getOutputStream();
// create Request object and parse在這以後,await方法建立一個Response對象,把Request對象設置給它,並調用它的sendStaticResource方法。
Request request = new Request(input);
request.parse ();
// create Response object最後,await關閉套接字並調用Request的getUri來檢測HTTP請求的URI是否是一個shutdown命令。假如是的話,shutdown變量將被設置爲true且程序會退出while循環。
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// Close the socket
socket.close ();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
package ex01.pyrmont;Listing 1.4: Request類的parse方法
import java.io.InputStream;
import java.io.IOException;
public class Request {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public void parse() {
...
}
private String parseUri(String requestString) {
...
}
public String getUri() {
return uri;
}
}
public void parse() {Listing 1.5: Request類的parseUri方法
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
private String parseUri(String requestString) {parse方法解析HTTP請求裏邊的原始數據。這個方法沒有作不少事情。它惟一可用的信息是經過調用HTTP請求的私有方法parseUri得到的URI。parseUri方法在uri變量裏邊存儲URI。公共方法getUri被調用並返回HTTP請求的URI。
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
GET /index.html HTTP/1.1parse方法從傳遞給Requst對象的套接字的InputStream中讀取整個字節流並在一個緩衝區中存儲字節數組。而後它使用緩衝區字節數據的字節來填入一個StringBuffer對象,而且把表明StringBuffer的字符串傳遞給parseUri方法。
package ex01.pyrmont;首先注意到它的構造方法接收一個java.io.OutputStream對象,就像以下所示。
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File;
/*
HTTP Response = Status-Line
*(( general-header | response-header | entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
*/
public class Response {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(HttpServer.WEB_ROOT, request.getUri());
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
else {
// file not found
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
}
catch (Exception e) {
// thrown if cannot instantiate a File object
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
}
public Response(OutputStream output) {響應對象是經過傳遞由套接字得到的OutputStream對象給HttpServer類的await方法來構造的。Response類有兩個公共方法:setRequest和sendStaticResource。setRequest方法用來傳遞一個Request對象給Response對象。
this.output = output;
}
File file = new File(HttpServer.WEB_ROOT, request.getUri());而後它檢查該文件是否存在。假如存在的話,經過傳遞File對象讓sendStaticResource構造一個java.io.FileInputStream對象。而後,它調用FileInputStream的read方法並把字節數組寫入OutputStream對象。請注意,這種狀況下,靜態資源是做爲原始數據發送給瀏覽器的。
if (file.exists()) {假如文件並不存在,sendStaticResource方法發送一個錯誤信息到瀏覽器。
fis = new FileInputstream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
String errorMessage =
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
java ex01.pyrmont.HttpServer爲了測試應用程序,能夠打開你的瀏覽器並在地址欄或網址框中敲入下面的命令:
http://localhost:8080/index.html正如Figure 1.1所示,你將會在你的瀏覽器裏邊看到index.html頁面。
GET /index.html HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,
application/vnd.ms-excel, application/msword, application/vnd.ms-
powerpoint, application/x-shockwave-flash, application/pdf, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive
GET /images/logo.gif HTTP/1.1
Accept: */*
Referer: http://localhost:8080/index.html
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR
1.1.4322)
Host: localhost:8080
Connection: Keep-Alive
import javax.servlet.*;兩個應用程序的類都放在ex02.pyrmont包裏邊。爲了理解應用程序是如何工做的,你須要熟悉javax.servlet.Servlet接口。爲了給你複習一下,將會在本章的首節討論這個接口。在這以後,你將會學習一個servlet容器作了什麼工做來爲一個servlet提供HTTP請求。
import java.io.IOException;
import java.io.PrintWriter;
public class PrimitiveServlet implements Servlet {
public void init(ServletConfig config) throws ServletException {
System.out.println("init");
}
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
System.out.println("from service");
PrintWriter out = response.getWriter();
out.println("Hello. Roses are red.");
out.print("Violets are blue.");
}
public void destroy() {
System.out.println("destroy");
}
public String getServletInfo() {
return null;
}
public ServletConfig getServletConfig() {
return null;
}
}
public void init(ServletConfig config) throws ServletException在Servlet的五個方法中,init,service和destroy是servlet的生命週期方法。在servlet類已經初始化以後,init方法將會被servlet容器所調用。servlet容器只調用一次,以此代表servlet已經被加載進服務中。init方法必須在servlet能夠接受任何請求以前成功運行完畢。一個servlet程序員能夠經過覆蓋這個方法來寫那些僅僅只要運行一次的初始化代碼,例如加載數據庫驅動,值初始化等等。在其餘狀況下,這個方法一般是留空的。
public void service(ServletRequest request, ServletResponse response)
throws ServletException, java.io.IOException
public void destroy()
public ServletConfig getServletConfig()
public java.lang.String getServletInfo()
http://machineName:port/staticResource就像是在第1章提到的,你能夠請求一個靜態資源。
http://machineName:port/servlet/servletClass所以,假如你在本地請求一個名爲PrimitiveServlet的servlet,你在瀏覽器的地址欄或者網址框中敲入:
http://localhost:8080/servlet/PrimitiveServletservlet容器能夠就提供PrimitiveServlet了。不過,假如你調用其餘servlet,如ModernServlet,servlet容器將會拋出一個異常。在如下各章中,你將會創建能夠處理這兩個狀況的程序。
package ex02.pyrmont;類的await方法等待HTTP請求直到一個shutdown命令給發出,讓你想起第1章的await方法。Listing 2.2的await方法和第1章的區別是,在Listing 2.2裏邊,請求能夠分發給一個StaticResourceProcessor或者一個ServletProcessor。假如URI包括字符串/servlet/的話,請求將會轉發到後面去。
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
public class HttpServer1 {
/** WEB_ROOT is the directory where our HTML and other files reside.
* For this package, WEB_ROOT is the "webroot" directory under the
* working directory.
* The working directory is the location in the file system
* from where the java command was invoked.
*/
// shutdown command
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
// the shutdown command received
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer1 server = new HttpServer1();
server.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1,
InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputstream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
// check if this is a request for a servlet or
// a static resource
// a request for a servlet begins with "/servlet/"
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
}
else {
StaticResoureProcessor processor =
new StaticResourceProcessor();
processor.process(request, response);
}
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
package ex02.pyrmont;另外,Request類仍然有在第1章中討論的parse和getUri方法。
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
public class Request implements ServletRequest {
private InputStream input;
private String uri;
public Request(InputStream input){
this.input = input;
}
public String getUri() {
return uri;
}
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer(j));
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
/* implementation of ServletRequest */
public Object getAttribute(String attribute) {
return null;
}
public Enumeration getAttributeNames() {
return null;
}
public String getRealPath(String path) {
return null;
}
public RequestDispatcher getRequestDispatcher(String path) {
return null;
}
public boolean isSecure() {
return false;
}
public String getCharacterEncoding() {
return null;
}
public int getContentLength() {
return 0;
}
public String getContentType() {
return null;
}
public ServletInputStream getInputStream() throws IOException {
return null;
}
public Locale getLocale() {
return null;
}
public Enumeration getLocales() {
return null;
}
public String getParameter(String name) {
return null;
}
public Map getParameterMap() {
return null;
}
public Enumeration getParameterNames() {
return null;
}
public String[] getParameterValues(String parameter) {
return null;
}
public String getProtocol() {
return null;
}
public BufferedReader getReader() throws IOException {
return null;
}
public String getRemoteAddr() {
return null;
}
public String getRemoteHost() {
return null;
}
public String getScheme() {
return null;
}
public String getServerName() {
return null;
}
public int getServerPort() {
return 0;
}
public void removeAttribute(String attribute) { }
public void setAttribute(String key, Object value) { }
public void setCharacterEncoding(String encoding)
throws UnsupportedEncodingException { }
}
package ex02.pyrmont;在getWriter方法中,PrintWriter類的構造方法的第二個參數是一個布爾值代表是否容許自動刷新。傳遞true做爲第二個參數將會使任何println方法的調用都會刷新輸出(output)。不過,print方法不會刷新輸出。
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletResponse;
import javax.servlet.ServletOutputStream;
public class Response implements ServletResponse {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
PrintWriter writer;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
/* This method is used to serve static pages */
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputstream fis = null;
try {
/* request.getUri has been replaced by request.getRequestURI */
File file = new File(Constants.WEB_ROOT, request.getUri());
fis = new FileInputstream(file);
/*
HTTP Response = Status-Line
*(( general-header | response-header | entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
*/
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
catch (FileNotFoundException e) {
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
finally {
if (fis!=null)
fis.close();
}
}
/** implementation of ServletResponse */
public void flushBuffer() throws IOException ( }
public int getBufferSize() {
return 0;
}
public String getCharacterEncoding() {
return null;
}
public Locale getLocale() {
return null;
}
public ServletOutputStream getOutputStream() throws IOException {
return null;
}
public PrintWriter getWriter() throws IOException {
// autoflush is true, println() will flush,
// but print() will not.
writer = new PrintWriter(output, true);
return writer;
}
public boolean isCommitted() {
return false;
}
public void reset() { }
public void resetBuffer() { }
public void setBufferSize(int size) { }
public void setContentLength(int length) { }
public void setContentType(String type) { }
public void setLocale(Locale locale) { }
}
package ex02.pyrmont;process方法接收兩個參數:一個ex02.pyrmont.Request實例和一個ex02.pyrmont.Response實例。這個方法只是簡單的呼叫Response對象的sendStaticResource方法。
import java.io.IOException;
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
response.sendStaticResource();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
package ex02.pyrmont;ServletProcessor1類出奇的簡單,僅僅由一個方法組成:process。這個方法接受兩個參數:一個
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class ServletProcessor1 {
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
// create a URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
// the forming of repository is taken from the
// createClassLoader method in
// org.apache.catalina.startup.ClassLoaderFactory
String repository =(new URL("file", null, classPath.getCanonicalPath() +
File.separator)).toString() ;
// the code for forming the URL is taken from
// the addRepository method in
// org.apache.catalina.loader.StandardClassLoader.
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request,
(ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
}
String uri = request.getUri();請記住URI是如下形式的:
/servlet/servletName在這裏servletName是servlet類的名字。
String servletName = uri.substring(uri.lastIndexOf("/") + 1);接下去,process方法加載servlet。要完成這個,你須要建立一個類加載器並告訴這個類加載器要加載的類的位置。對於這個servlet容器,類加載器直接在Constants指向的目錄裏邊查找。WEB_ROOT就是指向工做目錄下面的webroot目錄。
public URLClassLoader(URL[] urls);這裏urls是一個java.net.URL的對象數組,這些對象指向了加載類時候查找的位置。任何以/結尾的URL都假設是一個目錄。不然,URL會Otherwise, the URL假定是一個將被下載並在須要的時候打開的JAR文件。
public URL(URL context, java.lang.String spec, URLStreamHandler hander)你可使用這個構造方法,併爲第二個參數傳遞一個說明,爲第一個和第三個參數都傳遞null。不過,這裏有另一個接受三個參數的構造方法:
throws MalformedURLException
public URL(java.lang.String protocol, java.lang.String host,所以,假如你使用下面的代碼時,編譯器將不會知道你指的是那個構造方法:
java.lang.String file) throws MalformedURLException
new URL(null, aString, null);你能夠經過告訴編譯器第三個參數的類型來避開這個問題,例如。
URLStreamHandler streamHandler = null;你可使用下面的代碼在組成一個包含資源庫(servlet類能夠被找到的地方)的字符串,並做爲第二個參數,
new URL(null, aString, streamHandler);
String repository = (new URL("file", null,把全部的片斷組合在一塊兒,這就是用來構造適當的URLClassLoader實例的process方法中的一部分:
classPath.getCanonicalPath() + File.separator)).toString() ;
// create a URLClassLoader注意: 用來生成資源庫的代碼是從org.apache.catalina.startup.ClassLoaderFactory的createClassLoader方法來的,而生成URL的代碼是從org.apache.catalina.loader.StandardClassLoader的addRepository方法來的。不過,在如下各章以前你不須要擔憂這些類。
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
String repository = (new URL("file", null,
classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
Class myClass = null;而後,process方法建立一個servlet類加載器的實例, 把它向下轉換(downcast)爲javax.servlet.Servlet, 並調用servlet的service方法:
try {
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request,(ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer1在Linux下,你使用一個冒號來分隔兩個庫:
java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer1要測試該應用程序,在瀏覽器的地址欄或者網址框中敲入:
http://localhost:8080/index.html或者
http://localhost:8080/servlet/PrimitiveServlet當調用PrimitiveServlet的時候,你將會在你的瀏覽器看到下面的文本:
Hello. Roses are red.請注意,由於只是第一個字符串被刷新到瀏覽器,因此你不能看到第二個字符串Violets are blue。咱們將在第3章修復這個問題。
try {這會危害安全性。知道這個servlet容器的內部運做的Servlet程序員能夠分別把ServletRequest和ServletResponse實例向下轉換爲ex02.pyrmont.Request和ex02.pyrmont.Response,並調用他們的公共方法。擁有一個Request實例,它們就能夠調用parse方法。擁有一個Response實例,就能夠調用sendStaticResource方法。
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request,(ServletResponse) response);
}
package ex02.pyrmont;請注意RequestFacade的構造方法。它接受一個Request對象並立刻賦值給私有的servletRequest對象。還請注意,RequestFacade類的每一個方法調用ServletRequest對象的相應的方法。
public class RequestFacade implements ServletRequest {
private ServleLRequest request = null;
public RequestFacade(Request request) {
this.request = request;
}
/* implementation of the ServletRequest*/
public Object getAttribute(String attribute) {
return request.getAttribute(attribute);
}
public Enumeration getAttributeNames() {
return request.getAttributeNames();
}
...
}
if (request.getUri().startWith("/servlet/")) {ServletProcessor2類相似於ServletProcessor1,除了process方法中的如下部分:
servletProcessor2 processor = new ServletProcessor2();
processor.process(request, response);
}
else {
...
}
Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) requestFacade,(ServletResponse)responseFacade);
}
java -classpath ./lib/servlet.jar;./ ex02.pyrmont.HttpServer2在Linux下,你使用一個冒號來分隔兩個庫:
java -classpath ./lib/servlet.jar:./ ex02.pyrmont.HttpServer2你可使用與應用程序1同樣的地址,並獲得相同的結果。
private static Hashtable managers = new Hashtable();注意:一篇關於單例模式的題爲"The Singleton Pattern"的文章能夠在附帶的ZIP文件中找到。
public synchronized static StringManager
getManager(String packageName) {
StringManager mgr = (StringManager)managers.get(packageName);
if (mgr == null) {
mgr = new StringManager(packageName);
managers.put(packageName, mgr);
}
return mgr;
}
StringManager sm =在包ex03.pyrmont.connector.http中,你會找到三個屬性文件:LocalStrings.properties, LocalStrings_es.properties和LocalStrings_ja.properties。StringManager實例是根據運行程序的服務器的區域設置來決定使用哪一個文件的。假如你打開LocalStrings.properties,非註釋的第一行是這樣的:
StringManager.getManager("ex03.pyrmont.connector.http");
httpConnector.alreadyInitialized=HTTP connector has already been initialized要得到一個錯誤信息,可使用StringManager類的getString,並傳遞一個錯誤代號。這是其中一個重載方法:
public String getString(String key)經過傳遞httpConnector.alreadyInitialized做爲getString的參數,將會返回"HTTP connector has already been initialized"。
package ex03.pyrmont.startup;Bootstrap類中的main方法實例化HttpConnector類並調用它的start方法。HttpConnector類在Listing 3.2給出。
import ex03.pyrmont.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
connector.start();
}
}
package ex03.pyrmont.connector.http;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpConnector implements Runnable {
boolean stopped;
private String scheme = "http";
public String getScheme() {
return scheme;
}
public void run() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new
ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stopped) {
// Accept the next incoming connection from the server socket
Socket socket = null;
try {
socket = serverSocket.accept();
}
catch (Exception e) {
continue;
}
// Hand this socket off to an HttpProcessor
HttpProcessor processor = new HttpProcessor(this);
processor.process(socket);
}
}
public void start() {
Thread thread = new Thread(this);
thread.start ();
}
}
public void process(Socket socket) {process首先得到套接字的輸入流和輸出流。請注意,在這個方法中,咱們適合繼承了java.io.InputStream的SocketInputStream類。
SocketInputStream input = null;
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(), 2048);
output = socket.getOutputStream();
// create HttpRequest object and parse
request = new HttpRequest(input);
// create HttpResponse object
response = new HttpResponse(output);
response.setRequest(request);
response.setHeader("Server", "Pyrmont Servlet Container");
parseRequest(input, output);
parseHeaders(input);
//check if this is a request for a servlet or a static resource
//a request for a servlet begins with "/servlet/"
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new
StaticResourceProcessor();
processor.process(request, response);
}
// Close the socket
socket.close();
// no shutdown for this application
}
catch (Exception e) {
e.printStackTrace ();
}
}
SocketInputStream input = null;而後,它建立一個HttpRequest實例和一個 instance and an HttpResponse instance and assigns
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(), 2048);
output = socket.getOutputStream();
// create HttpRequest object and parse本章應用程序的HttpResponse類要比第2章中的Response類複雜得多。舉例來講,你能夠經過調用他的setHeader方法來發送頭部到一個客戶端。
request = new HttpRequest(input);
// create HttpResponse object
response = new HttpResponse(output);
response.setRequest(request);
response.setHeader("Server", "Pyrmont Servlet Container");接下去,process方法調用HttpProcessor類中的兩個私有方法來解析請求。
parseRequest(input, output);而後,它根據請求URI的形式把HttpRequest和HttpResponse對象傳給ServletProcessor或者StaticResourceProcessor進行處理。
parseHeaders (input);
if (request.getRequestURI().startsWith("/servlet/")) {最後,它關閉套接字。
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}
else {
StaticResourceProcessor processor =
new StaticResourceProcessor();
processor.process(request, response);
}
socket.close();也要注意的是,HttpProcessor類使用org.apache.catalina.util.StringManager類來發送錯誤信息:
protected StringManager sm =HttpProcessor類中的私有方法--parseRequest,parseHeaders和normalize,是用來幫助填充HttpRequest的。這些方法將會在下節"建立一個HttpRequest對象"中進行討論。
StringManager.getManager("ex03.pyrmont.connector.http");
protected HashMap headers = new HashMap();注意:ParameterMap類將會在「獲取參數」這節中解釋。
protected ArrayList cookies = new ArrayList();
protected ParameterMap parameters = null;
byte[] buffer = new byte [2048];你沒有試圖爲那兩個應用程序去進一步解析請求。不過,在本章的應用程序中,你擁有 ex03.pyrmont.connector.http.SocketInputStream類,這是 org.apache.catalina.connector.http.SocketInputStream的一個拷貝。這個類提供了方法不只用來獲取請求行,還有請求頭部。
try {
// input is the InputStream from the socket.
i = input.read(buffer);
}
SocketInputStream input = null;就像前面提到的同樣,擁有一個SocketInputStream是爲了兩個重要方法:readRequestLine和readHeader。請繼續往下閱讀。
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(), 2048);
...
GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1請求行的第二部分是URI加上一個查詢字符串。在上面的例子中,URI是這樣的:
/myApp/ModernServlet另外,在問好後面的任何東西都是查詢字符串。所以,查詢字符串是這樣的:
userName=tarzan&password=pwd查詢字符串能夠包括零個或多個參數。在上面的例子中,有兩個參數名/值對,userName/tarzan和password/pwd。在servlet/JSP編程中,參數名jsessionid是用來攜帶一個會話標識符。會話標識符常常被做爲cookie來嵌入,可是程序員能夠選擇把它嵌入到查詢字符串去,例如,當瀏覽器的cookie被禁用的時候。
private void parseRequest(SocketInputStream input, OutputStream output)parseRequest方法首先調用SocketInputStream類的readRequestLine方法:
throws IOException, ServletException {
// Parse the incoming request line
input.readRequestLine(requestLine);
String method =
new String(requestLine.method, 0, requestLine.methodEnd);
String uri = null;
String protocol = new String(requestLine.protocol, 0,
requestLine.protocolEnd);
// Validate the incoming request line
if (method, length () < 1) {
throw new ServletException("Missing HTTP request method");
}
else if (requestLine.uriEnd < 1) {
throw new ServletException("Missing HTTP request URI");
}
// Parse any query parameters out of the request URI
int question = requestLine.indexOf("?");
if (question >= 0) {
request.setQueryString(new String(requestLine.uri, question + 1,
requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
}
else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
// Checking for an absolute URI (with the HTTP protocol)
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// Parsing out protocol and host name
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}
// Parse any requested session ID out of the request URI
String match = ";jsessionid=";
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match,length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
}
else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
}
else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
// Normalize URI (using String operations at the moment)
String normalizedUri = normalize(uri);
// Set the corresponding request properties
((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
((HttpRequest) request).setRequestURI(uri);
}
if (normalizedUri == null) {
throw new ServletException("Invalid URI: " + uri + "'");
}
}
input.readRequestLine(requestLine);在這裏requestLine是HttpProcessor裏邊的HttpRequestLine的一個實例:
private HttpRequestLine requestLine = new HttpRequestLine();調用它的readRequestLine方法來告訴SocketInputStream去填入HttpRequestLine實例。
String method =不過,在URI後面能夠有查詢字符串,假如存在的話,查詢字符串會被一個問好分隔開來。所以,parseRequest方法試圖首先獲取查詢字符串。並調用setQueryString方法來填充HttpRequest對象:
new String(requestLine.method, 0, requestLine.methodEnd);
String uri = null;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
// Parse any query parameters out of the request URI不過,大多數狀況下,URI指向一個相對資源,URI還能夠是一個絕對值,就像下面所示:
int question = requestLine.indexOf("?");
if (question >= 0) { // there is a query string.
request.setQueryString(new String(requestLine.uri, question + 1,
requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
}
else {
request.setQueryString (null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
http://www.brainysoftware.com/index.html?name=TarzanparseRequest方法一樣也檢查這種狀況:
// Checking for an absolute URI (with the HTTP protocol)而後,查詢字符串也能夠包含一個會話標識符,用jsessionid參數名來指代。所以,parseRequest方法也檢查一個會話標識符。假如在查詢字符串裏邊找到jessionid,方法就取得會話標識符,並經過調用setRequestedSessionId方法把值交給HttpRequest實例:
if (!uri.startsWith("/")) {
// not starting with /, this is an absolute URI
int pos = uri.indexOf("://");
// Parsing out protocol and host name
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}
// Parse any requested session ID out of the request URI當jsessionid被找到,也意味着會話標識符是攜帶在查詢字符串裏邊,而不是在cookie裏邊。所以,傳遞true給request的 setRequestSessionURL方法。不然,傳遞false給setRequestSessionURL方法並傳遞null給 setRequestedSessionURL方法。
String match = ";jsessionid=";
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
}
else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL (true);
uri = uri.substring(0, semicolon) + rest;
}
else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
((HttpRequest) request).setMethod(method);還有,假如normalize方法的返回值是null的話,方法將會拋出一個異常:
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
((HttpRequest) request).setRequestURI(uri);
}
if (normalizedUri == null) {
throw new ServletException("Invalid URI: " + uri + "'");
}
HttpHeader header = new HttpHeader();而後,你能夠經過檢測HttpHeader實例的nameEnd和valueEnd字段來測試是否能夠從輸入流中讀取下一個頭部信息:
// Read the next header
input.readHeader(header);
if (header.nameEnd == 0) {假如存在下一個頭部,那麼頭部的名稱和值能夠經過下面方法進行檢索:
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException (sm.getString("httpProcessor.parseHeaders.colon"));
}
}
String name = new String(header.name, 0, header.nameEnd);一旦你獲取到頭部的名稱和值,你經過調用HttpRequest對象的addHeader方法來把它加入headers這個HashMap中:
String value = new String(header.value, 0, header.valueEnd);
request.addHeader(name, value);一些頭部也須要某些屬性的設置。例如,當servlet調用javax.servlet.ServletRequest的getContentLength方法的時候,content-length頭部的值將被返回。而包含cookies的cookie頭部將會給添加到cookie集合中。就這樣,下面是其中一些過程:
if (name.equals("cookie")) {Cookie的解析將會在下一節「解析Cookies」中討論。
... // 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);
}
Cookie: userName=budi; password=pwd;Cookie的解析是經過類org.apache.catalina.util.RequestUtil的parseCookieHeader方法來處理的。這個方法接受cookie頭部並返回一個javax.servlet.http.Cookie數組。數組內的元素數量和頭部裏邊的cookie名/值對個數是同樣的。parseCookieHeader方法在Listing 3.5中列出。
public static Cookie[] parseCookieHeader(String header) {還有,這裏是HttpProcessor類的parseHeader方法中用於處理cookie的部分代碼:
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 ()]));
}
else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
Cookie cookies[] = RequestUtil.ParseCookieHeader (value);
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("jsessionid")) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
request.setRequestedSessionId(cookies[i].getValue());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
}
}
request.addCookie(cookies[i]);
}
}
package org.apache.catalina.util;如今,讓咱們來看parseParameters方法是怎麼工做的。
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));
}
}
if (parsed)而後,parseParameters方法建立一個名爲results的ParameterMap變量,並指向parameters。假如
return;
ParameterMap results = parameters;而後,parseParameters方法打開parameterMap的鎖以便寫值。
if (results == null)
results = new ParameterMap();
results.setLocked(false);下一步,parseParameters方法檢查字符編碼,並在字符編碼爲null的時候賦予默認字符編碼。
String encoding = getCharacterEncoding();而後,parseParameters方法嘗試解析查詢字符串。解析參數是使用org.apache.Catalina.util.RequestUtil的parseParameters方法來處理的。
if (encoding == null)
encoding = "ISO-8859-1";
// Parse any parameters specified in the query string接下來,方法嘗試查看HTTP請求內容是否包含參數。這種狀況發生在當用戶使用POST方法發送請求的時候,內容長度大於零,而且內容類型是application/x-www-form-urlencoded的時候。因此,這裏是解析請求內容的代碼:
String queryString = getQueryString();
try {
RequestUtil.parseParameters(results, queryString, encoding);
}
catch (UnsupportedEncodingException e) {
;
}
// Parse any parameters specified in the input stream最後,parseParameters方法鎖定ParameterMap,設置parsed爲true,並把results賦予parameters。
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");
}
}
// Store the final results
results.setLocked(true);
parsed = true;
parameters = results;
public PrintWriter getWriter() {請看,咱們是如何構造一個PrintWriter對象的?就是經過傳遞一個java.io.OutputStream實例來實現的。你傳遞給PrintWriter的print或println方法的任何東西都是經過底下的OutputStream進行發送的。
// if autoflush is true, println() will flush,
// but print() will not.
// the output argument is an OutputStream
writer = new PrintWriter(output, true);
return writer;
}
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;
}
public void process(HttpRequest request, HttpResponse response) {另外,process方法使用HttpRequestFacade和HttpResponseFacade做爲
servlet = (Servlet) myClass.newInstance();類StaticResourceProcessor幾乎等同於類ex02.pyrmont.StaticResourceProcessor。
HttpRequestFacade requestPacade = new HttpRequestFacade(request);
HttpResponseFacade responseFacade = new HttpResponseFacade(response);
servlet.service(requestFacade, responseFacade);
((HttpResponse) response).finishResponse();
java -classpath ./lib/servlet.jar;./ ex03.pyrmont.startup.Bootstrap在Linux下,你使用一個冒號來分隔兩個庫:
java -classpath ./lib/servlet.jar:./ ex03.pyrmont.startup.Bootstrap要顯示index.html,使用下面的URL:
http://localhost:808O/index.html要調用PrimitiveServlet,讓瀏覽器指向下面的URL:
http://localhost:8080/servlet/PrimitiveServlet在你的瀏覽器中將會看到下面的內容:
Hello. Roses are red.注意:在第2章中運行PrimitiveServlet不會看到第二行。
Violets are blue.
http://localhost:8080/servlet/ModernServlet注意:ModernServlet的源代碼在工做目錄的webroot文件夾能夠找到。
http://localhost:8080/servlet/ModernServlet?userName=tarzan&password=pwd
public void invoke(在invoke方法裏邊,容器加載servlet,調用它的service方法,管理會話,記錄出錯日誌等等。
org.apache.catalina.Request request,
org.apache.catalina.Response response);
connection: keep-alive
I'm as helpless as a kitten up a tree.你將這樣發送:
1D\r\n1D,是29的十六進制,指示第一塊由29個字節組成。0\r\n標識這個事務的結束。
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n
HTTP/1.1 100 Continue接着,服務器應該會繼續讀取輸入流。
private Stack processors = new Stack();在HttpConnector中,建立的HttpProcessor實例數量是有兩個變量決定的:minProcessors和 maxProcessors。默認狀況下,minProcessors爲5而maxProcessors爲20,可是你能夠經過 setMinProcessors和setMaxProcessors方法來改變他們的值。
protected int minProcessors = 5;開始的時候,HttpConnector對象建立minProcessors個HttpProcessor實例。若是一次有比HtppProcessor 實例更多的請求須要處理時,HttpConnector建立更多的HttpProcessor實例,直到實例數量達到maxProcessors個。在到達這點以後,仍不夠HttpProcessor實例的話,請來的請求將會給忽略掉。若是你想讓HttpConnector繼續建立 HttpProcessor實例的話,把maxProcessors設置爲一個負數。還有就是變量curProcessors保存了 HttpProcessor實例的當前數量。
private int maxProcessors = 20;
while (curProcessors < minProcessors) {newProcessor方法構造一個HttpProcessor對象並增長curProcessors。recycle方法把HttpProcessor隊會棧。
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
while (!stopped) {對每一個前來的HTTP請求,會經過調用私有方法createProcessor得到一個HttpProcessor實例。
Socket socket = null;
try {
socket = serverSocket.accept();
...
HttpProcessor processor = createProcessor();然而,大部分時候createProcessor方法並不建立一個新的HttpProcessor對象。相反,它從池子中獲取一個。若是在棧中已經存在一個HttpProcessor實例,createProcessor將彈出一個。若是棧是空的而且沒有超過HttpProcessor實例的最大數量,createProcessor將會建立一個。然而,若是已經達到最大數量的話,createProcessor將會返回null。出現這樣的狀況的話,套接字將會簡單關閉而且前來的HTTP請求不會被處理。
if (processor == null) {若是createProcessor不是返回null,客戶端套接字會傳遞給HttpProcessor類的assign方法:
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
}
...
continue;
processor.assign(socket);如今就是HttpProcessor實例用於讀取套接字的輸入流和解析HTTP請求的工做了。重要的一點是,assign方法不會等到 HttpProcessor完成解析工做,而是必須立刻返回,以便下一個前來的HTTP請求能夠被處理。每一個HttpProcessor實例有本身的線程用於解析,因此這點不是很難作到。你將會在下節「HttpProcessor類」中看到是怎麼作的。
public void run() {第3章中的HttpProcessor類的process方法是同步的。所以,在接受另外一個請求以前,它的run方法要等待process方法運行結束。
...
while (!stopped) {
Socket socket = null;
try {
socket = serversocket.accept();
}
catch (Exception e) {
continue;
}
// Hand this socket off to an Httpprocessor
HttpProcessor processor = new Httpprocessor(this);
processor.process(socket);
}
}
public void run() {run方法中的while循環按照這樣的循序進行:獲取一個套接字,處理它,調用鏈接器的recycle方法把當前的HttpProcessor實例推回棧。這裏是HttpConenctor類的recycle方法:
// Process requests until we receive a shutdown signal
while (!stopped) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
try {
process(socket);
}
catch (Throwable t) {
log("process.invoke", t);
}
// Finish up this request
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
void recycle(HttpProcessor processor) {須要注意的是,run中的while循環在await方法中結束。await方法持有處理線程的控制流,直到從HttpConnector中獲取到一個新的套接字。用另一種說法就是,直到HttpConnector調用HttpProcessor實例的assign方法。可是,await方法和assign方法運行在不一樣的線程上。assign方法從HttpConnector的run方法中調用。咱們就說這個線程是HttpConnector實例的run方法運行的處理線程。assign方法是如何通知已經被調用的await方法的?就是經過一個布爾變量available而且使用java.lang.Object的wait和notifyAll方法。
processors.push(processor);
}
synchronized void assign(Socket socket) {兩個方法的程序流向在Table 4.1中總結。
// Wait for the processor to get the previous socket
while (available) {
try {
wait();
}
catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
...
}
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
}
catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" The incoming request has been awaited");
return (socket);
}
The processor thread (the await method) The connector thread (the assign method)剛開始的時候,當處理器線程剛啓動的時候,available爲false,線程在while循環裏邊等待(見Table 4.1的第1列)。它將等待另外一個線程調用notify或notifyAll。這就是說,調用wait方法讓處理器線程暫停,直到鏈接器線程調用HttpProcessor實例的notifyAll方法。
while (!available) { while (available) {
wait(); wait();
} }
Socket socket = this.socket; this.socket = socket;
available = false; available = true;
notifyAll(); notifyAll();
return socket; // to the run ...
// method
this.socket = socket;鏈接器線程把available設置爲true並調用notifyAll。這就喚醒了處理器線程,由於available爲true,因此程序控制跳出while循環:把實例的socket賦值給一個本地變量,並把available設置爲false,調用notifyAll,返回最後須要進行處理的socket。
boolean ok = true;另外,process方法也使用了布爾變量keepAlive,stopped和http11。keepAlive表示鏈接是不是持久的,stopped表示HttpProcessor實例是否已經被鏈接器終止來確認process是否也應該中止,http11表示 從web客戶端過來的HTTP請求是否支持HTTP 1.1。
boolean finishResponse = true;
SocketInputStream input = null;而後,有個while循環用來保持從輸入流中讀取,直到HttpProcessor被中止,一個異常被拋出或者鏈接給關閉爲止。
OutputStream output = null;
// Construct and initialize the objects we will need
try {
input = new SocketInputStream(socket.getInputstream(),
connector.getBufferSize());
}
catch (Exception e) {
ok = false;
}
keepAlive = true;在while循環的內部,process方法首先把finishResponse設置爲true,並得到輸出流,並對請求和響應對象作些初始化處理。
while (!stopped && ok && keepAlive) {
...
}
finishResponse = true;接着,process方法經過調用parseConnection,parseRequest和parseHeaders方法開始解析前來的HTTP請求,這些方法將在這節的小節中討論。
try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
}
catch (Exception e) {
log("process.create", e); //logging is discussed in Chapter 7
ok = false;
}
try {parseConnection方法得到協議的值,像HTTP0.9, HTTP1.0或HTTP1.1。若是協議是HTTP1.0,keepAlive設置爲false,由於HTTP1.0不支持持久鏈接。若是在HTTP請求裏邊找到Expect: 100-continue的頭部信息,則parseHeaders方法將把sendAck設置爲true。
if (ok) {
parseConnection(socket);
parseRequest(input, output);
if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
parseHeaders(input);
if (http11) {ackRequest方法測試sendAck的值,並在sendAck爲true的時候發送下面的字符串:
// Sending a request acknowledge back to the client if requested.
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
HTTP/1.1 100 Continue\r\n\r\n在解析HTTP請求的過程當中,有可能會拋出異常。任何異常將會把ok或者finishResponse設置爲false。在解析事後,process方法把請求和響應對象傳遞給容器的invoke方法:
try {接着,若是finishResponse仍然是true,響應對象的finishResponse方法和請求對象的finishRequest方法將被調用,而且結束輸出。
((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}
}
if (finishResponse) {while循環的最後一部分檢查響應的Connection頭部是否已經在servlet內部設爲close,或者協議是HTTP1.0.若是是這種狀況的話,keepAlive設置爲false。一樣,請求和響應對象接着會被回收利用。
...
response.finishResponse();
...
request.finishRequest();
...
output.flush();
if ( "close".equals(response.getHeader("Connection")) ) {在這個場景中,若是哦keepAlive是true的話,while循環將會在開頭就啓動。由於在前面的解析過程當中和容器的invoke方法中沒有出現錯誤,或者HttpProcessor實例沒有被中止。不然,shutdownInput方法將會調用,而套接字將被關閉。
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
request.recycle();
response.recycle();
}
try {shutdownInput方法檢查是否有未讀取的字節。若是有的話,跳過那些字節。
shutdownInput(input);
socket.close();
}
...
private void parseConnection(Socket socket) throws IOException, ServletException {
if (debug >= 2)
log(" parseConnection: address=" + socket.getInetAddress() +
", port=" + connector.getPort());
((HttpRequestImpl) request).setInet(socket.getInetAddress());
if (proxyPort != 0)
request.setServerPort(proxyPort);
else
request.setServerPort(serverPort);
request.setSocket(socket);
}
standard HTTP request headers in character arrays:parseHeaders方法包含一個while循環,能夠持續讀取HTTP請求直到再也沒有更多的頭部能夠讀取到。while循環首先調用請求對象的allocateHeader方法來獲取一個空的HttpHead實例。這個實例被傳遞給
static final char[] AUTHORIZATION_NAME = "authorization".toCharArray();
static final char[] ACCEPT_LANGUAGE_NAME = "accept-language".toCharArray();
static final char[] COOKIE_NAME = "cookie".toCharArray();
...
HttpHeader header = request.allocateHeader();假如全部的頭部都被已經被讀取的話,readHeader方法將不會賦值給HttpHeader實例,這個時候parseHeaders方法將會返回。
// Read the next header
input.readHeader(header);
if (header.nameEnd == 0) {若是存在一個頭部的名稱的話,這裏必須一樣會有一個頭部的值:
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon"));
}
}
String value = new String(header.value, 0, header.valueEnd);接下去,像第3章那樣,parseHeaders方法將會把頭部名稱和DefaultHeaders裏邊的名稱作對比。注意的是,這樣的對比是基於兩個字符數組之間,而不是兩個字符串之間的。
if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
request.setAuthorization(value);
}
else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
parseAcceptLanguage(value);
}
else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
// parse cookie
}
else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
// get content length
}
else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
request.setContentType(value);
}
else if (header.equals(DefaultHeaders.HOST_NAME)) {
// get host name
}
else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
keepAlive = false;
response.setHeader("Connection", "close");
}
}
else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))
sendAck = true;
else
throw new ServletException(sm.getstring
("httpProcessor.parseHeaders.unknownExpectation"));
}
else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
//request.setTransferEncoding(header);
}
request.nextHeader();
package ex04.pyrmont.core;我只是提供了SimpleContainer類的invoke方法的實現,由於默認鏈接器將會調用這個方法。invoke方法建立了一個類加載器,加載servlet類,並調用它的service方法。這個方法和第3章的ServletProcessor類在哦個的process方法很是相似。
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
import java.io.IOException;
import javax.naming.directory.DirContext;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Cluster;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Logger;
import org.apache.catalina.Manager;
import org.apache.catalina.Mapper;
import org.apache.catalina.Realm;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
public class SimpleContainer implements Container {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
public SimpleContainer() { }
public String getInfo() {
return null;
}
public Loader getLoader() {
return null;
}
public void setLoader(Loader loader) { }
public Logger getLogger() {
return null;
}
public void setLogger(Logger logger) { }
public Manager getManager() {
return null;
}
public void setManager(Manager manager) { }
public Cluster getCluster() {
return null;
}
public void setCluster(Cluster cluster) { }
public String getName() {
return null;
}
public void setName(String name) { }
public Container getParent() {
return null;
}
public void setParent(Container container) { }
public ClassLoader getParentClassLoader() {
return null;
}
public void setParentClassLoader(ClassLoader parent) { }
public Realm getRealm() {
return null;
}
public void setRealm(Realm realm) { }
public DirContext getResources() {
return null;
}
public void setResources(DirContext resources) { }
public void addChild(Container child) { }
public void addContainerListener(ContainerListener listener) { }
public void addMapper(Mapper mapper) { }
public void addPropertyChangeListener(
PropertyChangeListener listener) { }
public Container findchild(String name) {
return null;
}
public Container[] findChildren() {
return null;
}
public ContainerListener[] findContainerListeners() {
return null;
}
public Mapper findMapper(String protocol) {
return null;
}
public Mapper[] findMappers() {
return null;
}
public void invoke(Request request, Response response)
throws IoException, ServletException {
string servletName = ( (Httpservletrequest)
request).getRequestURI();
servletName = servletName.substring(servletName.lastIndexof("/") +
1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classpath = new File(WEB_ROOT);
string repository = (new URL("file",null,
classpath.getCanonicalpath() + File.separator)).toString();
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
myClass = loader.loadclass(servletName);
}
catch (classNotFoundException e) {
System.out.println(e.toString());
}
servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((HttpServletRequest) request,
(HttpServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
public Container map(Request request, boolean update) {
return null;
}
public void removeChild(Container child) { }
public void removeContainerListener(ContainerListener listener) { }
public void removeMapper(Mapper mapper) { }
public void removoPropertyChangeListener(
PropertyChangeListener listener) {
}
}
package ex04.pyrmont.startup;Bootstrap 類的main方法構造了一個org.apache.catalina.connector.http.HttpConnector實例和一個 SimpleContainer實例。它接下去調用conncetor的setContainer方法傳遞container,讓connector和container關聯起來。下一步,它調用connector的initialize和start方法。這將會使得connector爲處理8080端口上的任何請求作好了準備。
import ex04.pyrmont.core.simplecontainer;
import org.apache.catalina.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(string[] args) {
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
try {
connector.initialize();
connector.start();
// make the application wait until we press any key.
System in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
要在Windows中運行這個程序的話,在工做目錄下輸入如下內容: html
java -classpath ./lib/servlet.jar;./ ex04.pyrmont.startup.Bootstrap